A fiber needs its own root Context (the spawner's snapshot), not the
ambient one. Probed whether that needs compiler support: it does not.
context is an implicit slot-0 *Context param (call-carried, rides the
callee's own stack) and push Context allocates on the caller frame —
never TLS, never re-read from the __sx_default_context global mid-stack.
So the spawn convention is pure library sx:
snap := context; // snapshot the spawner's context
f := Fiber.{ root = snap }; // store it
push f.root { entry(args) } // trampoline installs it as the fiber root
examples/1804-concurrency-context-snapshot.sx locks it: a trampoline
running under ambient ctx 99 installs a stored snapshot (42); the body
reads 42, and the push scope restores 99 on exit. No fiber runtime yet
(B1.3) — this proves the plumbing it builds on.
The design doc's "lower context as swappable indirection, never raw
TLS" guarded a non-problem — context was already param-carried.
Suite green (726/0).
49 lines
2.2 KiB
Plaintext
49 lines
2.2 KiB
Plaintext
// Stream B1 (fibers) step B1.1 — per-fiber `context` root, via plain
|
|
// snapshot + `push` (NO compiler change — the substrate the fiber spawn relies on).
|
|
//
|
|
// `context` is an implicit `*Context` parameter (slot 0) threaded through every
|
|
// sx call, and `push Context` allocates the new context on the caller's stack
|
|
// frame — so a function's context is CALL-CARRIED and rides its own stack, never
|
|
// read from a global. That is exactly what a fiber needs: each fiber, on its own
|
|
// stack, reads its own root context.
|
|
//
|
|
// This locks the spawn convention end-to-end with ordinary language features:
|
|
// 1. capture the spawner's context as a value (`snap := context`)
|
|
// 2. store the snapshot in a struct (the stand-in `Fiber`)
|
|
// 3. a trampoline, running under a DIFFERENT ambient context, installs the
|
|
// fiber's stored root before entering the body (`push f.root { … }`)
|
|
// The body sees the snapshot (42), not the trampoline's ambient context (99),
|
|
// and the `push` scope restores the ambient context on exit. No fiber runtime
|
|
// exists yet (that is B1.3) — this proves the context plumbing it will build on.
|
|
#import "modules/std.sx";
|
|
|
|
// Stand-in for a Fiber: stores the spawner's snapshotted root Context.
|
|
Fiber :: struct { root: Context; }
|
|
|
|
// Reads this call's context sentinel (carried in `context.data` here for the
|
|
// test; a real fiber carries its allocator / io the same way).
|
|
sentinel :: () -> i64 { return xx context.data; }
|
|
|
|
// Trampoline: runs under its own ambient context, but installs the fiber's
|
|
// stored root before entering the fiber body.
|
|
run_fiber :: (f: *Fiber) -> i64 {
|
|
push f.root {
|
|
return sentinel();
|
|
}
|
|
}
|
|
|
|
main :: () {
|
|
snap := context;
|
|
snap.data = xx 42; // the spawner's sentinel
|
|
f := Fiber.{ root = snap }; // snapshot stored in the fiber
|
|
|
|
other := context;
|
|
other.data = xx 99; // a DIFFERENT ambient context
|
|
push other {
|
|
// Ambient is 99 here; the trampoline must install f.root (42).
|
|
print("fiber root: {}\n", run_fiber(@f));
|
|
// After run_fiber returns, this scope's ambient context is intact.
|
|
print("ambient after: {}\n", sentinel());
|
|
}
|
|
}
|