feat: async/await colorblind over the fiber Io (Phase 2 of Io unification)
`context.io.async(worker)` / `await` now run over the `Io` PROTOCOL, so the
same code interleaves under the fiber scheduler or runs inline under the
blocking `CBlockingIo` — one async stack, reached purely through `context.io`.
- Protocol: `suspend_raw(park: *ParkToken)` (was by-value). A suspending impl
records the parked execution context into `park.handle` before parking, so a
cross-context `ready(park)` knows whom to resume; `Scheduler.suspend_raw`
writes `self.current`, `CBlockingIo` ignores it.
- io.sx async layer rewritten colorblind: `async` submits the worker through
`io.spawn_raw` (inline under blocking, a fiber under the scheduler) and returns
a HEAP `*Future($R)` the worker fills later; `await` suspends via `suspend_raw`
until ready, then returns/raises. The generic worker is bridged to spawn_raw's
raw `(*void)->void` entry via a monomorphic `ThunkBox` (a heap-boxed nullary
completion closure) — all genericity lives in the closure env. Workers are
nullary (inputs captured at the call site) because a variadic pack can't cross
the fiber boundary. `CBlockingIo.spawn_raw` now runs the worker inline.
- Migrated 1805/1806 to the nullary `*Future` form; retrofit 1822/1823 to the
`push .{ … }` partial-context literal (inherits allocator/data).
- The async machinery adds a few prelude types, shifting the type-name table —
40 `.ir` snapshots regenerated (no behavior change; only `.exit`/`.stdout`/
`.stderr` would signal that, and none changed).
Locked by examples/concurrency/1824 — two async tasks under the fiber Io, the
completion log proving deferral (1 2 then 10 20 then 123). Suite 829/0,
byte-identical aarch64-macOS host + aarch64-linux container.
This commit is contained in:
@@ -1,28 +1,29 @@
|
||||
// B1.2 — the async ergonomic layer over the `Io` capability, blocking
|
||||
// default. `context.io.async(worker, ..args)` runs the worker to completion
|
||||
// inline and returns a `.ready` Future($R); `f.await()` yields the result
|
||||
// (a value-failable `($R, !IoErr)`, handled with `or`). `context.io.now_ms()`
|
||||
// reads the monotonic clock through the same capability.
|
||||
// B1.2 / B2 — the async ergonomic layer over the `Io` capability, blocking
|
||||
// default. `context.io.async(worker)` submits a NULLARY `worker: Closure() -> $R`
|
||||
// and returns a `*Future($R)` handle; under the blocking `CBlockingIo` the worker
|
||||
// runs to completion inline, so the Future is born `.ready`. `f.await()` yields
|
||||
// the result (a value-failable `($R, !IoErr)`, handled with `or`).
|
||||
// `context.io.now_ms()` reads the clock through the same capability.
|
||||
//
|
||||
// Worker form: a lambda whose params are annotated at the call site
|
||||
// (`(a: i64, b: i64) -> i64 => …`); `..args` forwards the call-site
|
||||
// arguments to it.
|
||||
// Worker form: a nullary lambda capturing any inputs at the CALL SITE
|
||||
// (`() -> i64 => compute(a, b)`) — the colorblind shape that also works when the
|
||||
// worker is deferred onto a fiber (a captured variadic pack can't cross the fiber
|
||||
// boundary), mirroring `sched.go`.
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
// Homogeneous args.
|
||||
s := context.io.async((a: i64, b: i64) -> i64 => a + b, 40, 2);
|
||||
// Inputs captured at the call site.
|
||||
s := context.io.async(() -> i64 => 40 + 2);
|
||||
print("sum: {}\n", s.await() or { -1 });
|
||||
|
||||
// Single arg.
|
||||
d := context.io.async((x: i64) -> i64 => x * 2, 21);
|
||||
d := context.io.async(() -> i64 => 21 * 2);
|
||||
print("double: {}\n", d.await() or { -1 });
|
||||
|
||||
// Nullary worker — the variadic `async` binds an empty pack, so no separate
|
||||
// `async_void` entry is needed.
|
||||
n := context.io.async(() -> i64 => 42);
|
||||
// A worker that closes over a local.
|
||||
base := 42;
|
||||
n := context.io.async(() -> i64 => base);
|
||||
print("nullary: {}\n", n.await() or { -1 });
|
||||
|
||||
// The Io capability also carries a monotonic clock.
|
||||
// The Io capability also carries a clock.
|
||||
if context.io.now_ms() >= 0 { print("clock ok\n"); }
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
main :: () {
|
||||
// Not canceled → await yields the value.
|
||||
ok := context.io.async((n: i64) -> i64 => n, 7);
|
||||
ok := context.io.async(() -> i64 => 7);
|
||||
print("ok: {}\n", ok.await() or { -1 });
|
||||
|
||||
// Canceled → await raises .Canceled → the `or` default is taken.
|
||||
c := context.io.async((n: i64) -> i64 => n, 7);
|
||||
c := context.io.async(() -> i64 => 7);
|
||||
c.cancel();
|
||||
print("canceled: {}\n", c.await() or { -99 });
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ main :: () -> i64 {
|
||||
s := sched.Scheduler.init();
|
||||
ps := @s;
|
||||
print("outside: marker id = {}\n", mk.id);
|
||||
push Context.{ allocator = context.allocator, data = xx @mk, io = context.io } {
|
||||
push .{ data = xx @mk } {
|
||||
ps.spawn(() => {
|
||||
m : *Marker = xx context.data; // inherited from the spawn-time context
|
||||
print("inside fiber: context.data marker id = {}\n", m.id);
|
||||
|
||||
@@ -23,7 +23,7 @@ sleeper :: (arg: *void) {
|
||||
n : *i64 = xx arg;
|
||||
tok : ParkToken = .{ handle = null };
|
||||
context.io.arm_timer(context.io.now_ms() + n.*, tok);
|
||||
context.io.suspend_raw(tok) catch {};
|
||||
context.io.suspend_raw(@tok) catch {};
|
||||
print("worker(sleep {}) resumed at now_ms = {}\n", n.*, context.io.now_ms());
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ main :: () -> i64 {
|
||||
ps := @s;
|
||||
d1 : i64 = 20;
|
||||
d2 : i64 = 10;
|
||||
push Context.{ allocator = context.allocator, data = null, io = xx s } {
|
||||
push .{ io = xx s } {
|
||||
context.io.spawn_raw(xx sleeper, xx @d1, .{});
|
||||
context.io.spawn_raw(xx sleeper, xx @d2, .{});
|
||||
ps.run();
|
||||
|
||||
42
examples/concurrency/1824-concurrency-fiber-async-await.sx
Normal file
42
examples/concurrency/1824-concurrency-fiber-async-await.sx
Normal file
@@ -0,0 +1,42 @@
|
||||
// Stream B2 — `async`/`await` (the io.sx ergonomic layer) running COLORBLIND over
|
||||
// the fiber `Io` scheduler. The SAME `context.io.async(worker)` that runs inline
|
||||
// under the blocking `CBlockingIo` (1805) here spawns the worker as a real fiber
|
||||
// and returns a PENDING `*Future`; `await` suspends the calling fiber until the
|
||||
// worker completes. No bespoke `go`/`wait` — this is the unified async stack
|
||||
// (io.sx async over the `Io` protocol), reaching the fiber scheduler purely
|
||||
// through `context.io`.
|
||||
//
|
||||
// The completion log makes the deferral visible: the coordinator records 1,2
|
||||
// BEFORE either worker runs (async only SPAWNS them), then `await` parks it while
|
||||
// the workers run (10,20), then it resumes and sums (123). Deterministic.
|
||||
//
|
||||
// aarch64-pinned (the scheduler's per-arch asm): runs end-to-end on a matching
|
||||
// host (macOS + linux), ir-only on a mismatch.
|
||||
#import "modules/std.sx";
|
||||
sched :: #import "modules/std/sched.sx";
|
||||
|
||||
Log :: struct { seq: [8]i64; n: i64; }
|
||||
rec :: (l: *Log, v: i64) { l.seq[l.n] = v; l.n = l.n + 1; }
|
||||
|
||||
main :: () -> i64 {
|
||||
lg : Log = ---; lg.n = 0;
|
||||
s := sched.Scheduler.init();
|
||||
ps := @s; pl := @lg;
|
||||
push .{ io = xx s } {
|
||||
ps.spawn(() => {
|
||||
rec(pl, 1); // coordinator starts
|
||||
a := context.io.async(() -> i64 => { rec(pl, 10); 100 }); // worker A — deferred
|
||||
b := context.io.async(() -> i64 => { rec(pl, 20); 23 }); // worker B — deferred
|
||||
rec(pl, 2); // both spawned, neither has run
|
||||
va := a.await() or { -1 }; // park; A runs, wakes us
|
||||
vb := b.await() or { -1 };
|
||||
rec(pl, va + vb); // 123
|
||||
});
|
||||
ps.run();
|
||||
}
|
||||
print("sequence:");
|
||||
i := 0;
|
||||
while i < lg.n { print(" {}", lg.seq[i]); i = i + 1; }
|
||||
print("\n");
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{ "target": "macos" }
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
sequence: 1 2 10 20 123
|
||||
Reference in New Issue
Block a user