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).
67 lines
2.8 KiB
Plaintext
67 lines
2.8 KiB
Plaintext
// 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.0–B1.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;
|
||
}
|