fibers: end-to-end M:1 capstone (B1.5) — Stream B1 complete

1817 composes the whole colorblind pure-sx async stack: the M:1
scheduler, suspending go/wait async, and deterministic virtual-time
sleep/now_ms, over the naked swap_context on guarded mmap stacks. A
coordinator launches three async tasks (sleep 30/10/20 -> return
100/20/3), awaits all three in spawn order, and sums them; tasks
complete in DEADLINE order (task 2@10, 3@20, 1@30), sum 123, final
virtual clock 30 -- fully deterministic.

Stream B1 (fibers + Io + M:1 scheduler) is feature-complete: examples
1800-1817, suite 755/0. Checkpoint + plan marked COMPLETE; next carve
is Stream B2 (channels / cancel / async stdlib).
This commit is contained in:
agra
2026-06-21 19:43:22 +03:00
parent 1b0d640f73
commit 5949a88439
7 changed files with 149 additions and 30 deletions

View File

@@ -0,0 +1,66 @@
// Stream B1 (fibers) B1.5 — the M:1 colorblind async stack, end-to-end.
//
// One program exercises the whole pure-sx runtime together: the M:1 scheduler
// (B1.5a), the suspending fiber-task async layer `go`/`wait` (B1.4a), and the
// deterministic virtual-time timers `sleep`/`now_ms` (B1.4b) — all over the
// `abi(.naked)` `swap_context` on guarded `mmap` stacks (B1.0B1.3).
//
// A coordinator fiber launches three async tasks; each `sleep`s a different
// duration, records its completion (id @ virtual-ms) into a shared log, then
// returns a value. The coordinator `wait`s on all three (in SPAWN order) and
// sums their results. Because tasks complete in DEADLINE order — not spawn
// order, not await order — the completion log is the deterministic contract:
//
// task A: sleep 30 → returns 100
// task B: sleep 10 → returns 20
// task C: sleep 20 → returns 3
// completion order (by deadline): B@10, C@20, A@30
// coordinator awaits A,B,C → sum = 123, final virtual clock = 30
//
// `wait(A)` parks the coordinator until A finishes at t=30; B and C finish
// earlier (at 10 and 20) and are already `.ready` by the time their `wait`s run,
// so they return without re-parking — the values are correct regardless of await
// order, while the timer-driven schedule fixes the completion ORDER. Fully
// deterministic + reproducible (virtual time, no real clock).
//
// aarch64-macOS-pinned (the scheduler's per-arch asm + Apple mmap constants):
// runs end-to-end on a matching host, ir-only on a mismatch.
#import "modules/std.sx";
sched :: #import "modules/std/sched.sx";
Log :: struct { id: [8]i64; at: [8]i64; n: i64; }
rec :: (l: *Log, id: i64, at: i64) { l.id[l.n] = id; l.at[l.n] = at; l.n = l.n + 1; }
main :: () -> i64 {
lg : Log = ---; lg.n = 0;
s := sched.Scheduler.init();
ps := @s; pl := @lg;
// The coordinator runs as a fiber so `wait` has a `current` to park.
s.spawn(() => {
// Launch three async tasks; each sleeps, logs its completion, returns.
a := ps.go(() -> i64 => { ps.sleep(30); rec(pl, 1, ps.now_ms()); 100 });
b := ps.go(() -> i64 => { ps.sleep(10); rec(pl, 2, ps.now_ms()); 20 });
c := ps.go(() -> i64 => { ps.sleep(20); rec(pl, 3, ps.now_ms()); 3 });
// Await in SPAWN order; results come back correct regardless.
va := a.wait() or { -1 };
vb := b.wait() or { -1 };
vc := c.wait() or { -1 };
sum := va + vb + vc;
rec(pl, 9, sum); // sentinel row: id=9 carries the sum in `at`
});
s.run();
print("completion order (id @ virtual-ms):\n");
i := 0;
while i < lg.n {
if lg.id[i] == 9 { print("sum: {}\n", lg.at[i]); }
else { print(" task {} @ {}ms\n", lg.id[i], lg.at[i]); }
i = i + 1;
}
print("final virtual clock: {}ms\n", s.now_ms());
print("tasks: {}\n", s.n_spawned);
return 0;
}

View File

@@ -0,0 +1 @@
{ "target": "macos" }

View File

@@ -0,0 +1,7 @@
completion order (id @ virtual-ms):
task 2 @ 10ms
task 3 @ 20ms
task 1 @ 30ms
sum: 123
final virtual clock: 30ms
tasks: 4