diff --git a/current/CHECKPOINT-FIBERS.md b/current/CHECKPOINT-FIBERS.md index 37a50f9c..71cde083 100644 --- a/current/CHECKPOINT-FIBERS.md +++ b/current/CHECKPOINT-FIBERS.md @@ -4,25 +4,25 @@ Companion to [PLAN-FIBERS.md](PLAN-FIBERS.md). Update after every step (one step per the cadence rule). New corpus category: `18xx` concurrency. ## Last completed step -**issues 0151 + 0152 FIXED.** The async surface is CALLABLE and `Atomic(bool)` works. One LAST -blocker remains, freshly isolated: **issue 0153 — a re-exported generic value-failable -`($R, !E)` loses its `!` error channel at the call site** (typed as a plain tuple), so -`await(...) or { … }` builds a malformed PHI. `await`/`IoErr` are re-exported via `std.sx`, -so the async surface hits it. Suite green **729/0**, master clean. -- **0152 fix (committed):** the atomic load/store emitters in `src/backend/llvm/ops.zig` now - byte-promote a sub-byte (`bool`→`i1`) access to its `i8` storage type and `trunc`/`zext` the - value at the boundary (new `atomicByteType` helper). rmw/cmpxchg left as-is (a `bool` rmw/CAS - is rejected at the sx level — integer-only — so a sub-byte element never reaches them). - Regression: `examples/1705-atomics-bool-byte-promoted.sx`. Issue 0152 marked RESOLVED. -- **0153 filed (the remaining blocker):** generic + re-export is the co-requirement — a - non-generic re-export keeps the channel, and a directly-imported generic value-failable keeps - it; only the combination drops the `!`. Minimal co-located 2-file repro + a single-file - stdlib-`await` repro + investigation prompt (root cause: the monomorphized return-type's - error-set, reached via the re-export alias, resolves to a non-`.error_set` TypeId, so - `errorChannelOf` no longer sees the failable channel). See `issues/0153-...`. +**B1.2 COMPLETE — the async surface works end-to-end.** All three surface blockers (0151, 0152, +0153) are FIXED + committed; the async examples are landed + green. Suite green **732/0**, master +clean. +- **0151 fixed** (`362674f`): generic `$T` infers through generic-struct / pointer / UFCS-pack + params. Regression `0214` + `0215`. +- **0152 fixed** (`e5586f6`): `Atomic(bool)` load/store byte-promoted to `i8` in the codegen + emitters. Regression `1705`. +- **0153 fixed** (`68c1991`): `inferGenericReturnType` now pins return-type resolution to the + fn's DEFINING module (mirroring `monomorphizeFunction`), so a re-exported value-failable's + `!E` resolves to the real `.error_set` TypeId — the failable channel survives the re-export + alias. Regression `1058-errors-reexport-value-failable-channel.sx`. +- **Async examples landed:** `examples/1805-concurrency-io-blocking-async.sx` + (`context.io.async((a,b)->i64 => a+b, 40, 2).await() or {…}` → `sum: 42` / `double: 42` / + `clock ok`) + `examples/1806-concurrency-io-cancel.sx` (`f.cancel()` → `await` raises + `.Canceled` → `or` default; `ok: 7` / `canceled: -99`). Both green, snapshots captured. -### Earlier — issue 0151 FIXED (committed `362674f`) -Generic `$T` now infers through generic-struct / pointer / UFCS-pack params — details below. +### Earlier — the three B1.2 surface fixes (committed) +Generic `$T` inference, `Atomic(bool)` byte-promotion, and re-export failable-channel pin — +details below. - **0151 fix (committed):** four gaps closed on the inference + UFCS-dispatch path — (1) `extractTypeParam`/`matchTypeParam(Static)` got a `parameterized_type_expr` arm (recover the arg instance's recorded per-param bindings via `struct_instance_bindings` + @@ -115,27 +115,22 @@ body); closed + locked. The review's `.naked`-lambda CRITICAL was a false positi (unparseable — `isLambda` breaks on the `abi` keyword). ## Current state -**B1.2 Io capability LANDED; async surface CALLABLE + `Atomic(bool)` works; ONE blocker left -(issue 0153).** Master GREEN (729/0), installed `sx` clean. Three of the four B1.2 surface bugs -are now closed: -- **0151 fixed + committed** (`362674f`): generic `$T` infers through generic-struct / pointer - / UFCS-pack params. `async`/`await`/`cancel` dispatch correctly. Regression `0214` + `0215`. -- **0152 fixed + committed**: `Atomic(bool)` load/store byte-promoted to `i8` in the codegen - emitters. `Future.canceled: Atomic(bool)` works. Regression `1705`. -- **0153 (the remaining blocker, filed):** a re-exported generic value-failable `($R, !E)` - loses its `!` channel at the call site (plain tuple), so `await(...) or { … }` / `try …await()` - reject it / build a malformed `i1` PHI. `await`/`IoErr` are re-exported via `std.sx`, so the - async examples hit it. The earlier "secondary `or` PHI" symptom was THIS, not an `Atomic` - cascade (it persists after 0152). Minimal co-located repro + investigation prompt at - `issues/0153-reexport-generic-value-failable-loses-error-channel.{md,sx}`. Root cause: the - monomorphized return-type's error-set, reached via the re-export alias, resolves to a - non-`.error_set` TypeId, so `errorChannelOf` (`lower/error.zig:148`) no longer sees the channel. -- Issue **0150** (`void` struct field → SIGTRAP) remains DEFERRED — only `Future(void)` / - `timeout`, which are B1.4. +**B1.2 COMPLETE.** The full async surface (Io capability on Context + `async`/`await`/`cancel` + +blocking `CBlockingIo`) works end-to-end. Master GREEN (732/0), installed `sx` clean. All four +B1.2 surface bugs resolved or deferred: +- **0151 fixed** (`362674f`): generic `$T` through generic-struct / pointer / UFCS-pack params. + Regression `0214` + `0215`. +- **0152 fixed** (`e5586f6`): `Atomic(bool)` byte-promoted to `i8` in the load/store emitters. + Regression `1705`. +- **0153 fixed** (`68c1991`): `inferGenericReturnType` pins return-type resolution to the fn's + defining module, so a re-exported value-failable keeps its `!` channel. Regression `1058`. +- Issue **0150** (`void` struct field → SIGTRAP) DEFERRED — only `Future(void)` / `timeout`, + which are B1.4. -Verified live (with all three patches in tree, before reverting nothing — 0151/0152 are -committed, 0153 outstanding): `async`/`await` dispatch + build correct Futures; the ONLY failure -is the lost `!` channel from 0153. A working dispatch probe is at `.sx-tmp/async_surface.sx`. +The async examples are landed + green: `1805` (`async`/`await` + `now_ms` → `sum: 42` / +`double: 42` / `clock ok`) + `1806` (`cancel` → `await` raises `.Canceled` → `or` default). +The `18xx` concurrency category now covers naked-asm (1800-1803), context-snapshot (1804), and +the async surface (1805-1806). ### B1.2 Io capability — what is LANDED + verified (commit 45d869d) - `Io :: protocol #inline { spawn_raw; suspend_raw -> !; ready; poll; now_ms; arm_timer; }` @@ -194,20 +189,13 @@ fibers/Io/scheduler code yet. Grounded floor facts: boundary; a sharper sx diagnostic for it is a candidate polish, not a blocker. ## Next step -**BLOCKED on issue 0153 (re-exported generic value-failable loses its `!` channel).** It is the -LAST B1.2 surface blocker — 0151 + 0152 are fixed. Once 0153 lands, complete B1.2's async -surface: -1. **Add the async examples:** `examples/1805-concurrency-io-blocking-async.sx` - (`context.io.async((a:i64,b:i64)->i64 => a+b, 40, 2).await() or {…}` → 42; `double:`/`sum:`) - + `1806-...-io-cancel.sx` (cancel → `await` raises `.Canceled`). The async surface is already - callable + `Atomic(bool)` works; a working dispatch probe lives at `.sx-tmp/async_surface.sx` - — it builds the Futures and dispatches `async`/`await`/`cancel` correctly; ONLY the lost `!` - channel (0153) on `await(...) or { … }` stops it. lock→green, then regen `.ir` (layout-only). - - `await` returns a value-failable `($R, !IoErr)`; use `f.await() or { default }` (a braced - `or`, not `catch `). -2. Then B1.2 is truly done → proceed to **B1.3 (fiber runtime)**. +**B1.2 is done → start B1.3 (fiber runtime).** The compiler floor (B1.0 `abi(.naked)`, B1.1 +per-fiber `context`) + the capability surface (B1.2 Io / `async`/`await`/`cancel`) are all in. +B1.3 builds the actual M:1 fiber scheduler on the `.naked` context-switch substrate — see +`PLAN-FIBERS.md` for the B1.3 step list. The B1.3 switch-stress harness (design §10.7) gates the +context-switch correctness the deterministic Io can't test. -**Deferred (do NOT block B1.2 on these):** issue **0150** (`void` struct field SIGTRAP) — only +**Deferred (do NOT block on these):** issue **0150** (`void` struct field SIGTRAP) — only `Future(void)`/`timeout`, which are B1.4. The **`::` callable-parameter feature** (named-fn async workers `async(read_a, conn)`) — WIP at `.sx-tmp/wip-callable-params/patch.diff` (parser done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idiom meanwhile. @@ -216,16 +204,13 @@ done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idi `call.zig:1229`, io last). Io protocol + materializers + push-inherit are LANDED + reviewed. ## Known issues / capability gaps -- **🔴 B1.2 BLOCKER — issue 0153** (re-exported generic value-failable `($R, !E)` loses its `!` - channel; the call result types as a plain tuple, so `await(...) or { … }` / `try …await()` - fail / build a malformed i1 PHI). `await`/`IoErr` re-exported via `std.sx` hit it. Co-located - repro + fix prompt: `issues/0153-...`. Root cause: the monomorphized return-type's error-set, - reached via the re-export alias, resolves to a non-`.error_set` TypeId (so `errorChannelOf` - misses it). The LAST B1.2 surface blocker. -- **✅ issue 0152 — FIXED this session** (`Atomic(bool)` sub-byte i1 atomic → byte-promoted to i8 - in the load/store emitters). Regression: `examples/1705`. Unblocked `Future.canceled`. -- **✅ issue 0151 — FIXED this session** (generic `$T` through generic-struct / pointer / - UFCS-pack params). Regression: `examples/0214` + `0215`. Was the original B1.2 surface blocker. +- **✅ issue 0153 — FIXED** (re-exported generic value-failable `($R, !E)` kept its `!` channel: + `inferGenericReturnType` now pins return-type resolution to the fn's defining module). + Regression: `examples/1058`. Was the LAST B1.2 surface blocker. +- **✅ issue 0152 — FIXED** (`Atomic(bool)` sub-byte i1 atomic → byte-promoted to i8 in the + load/store emitters). Regression: `examples/1705`. Unblocked `Future.canceled`. +- **✅ issue 0151 — FIXED** (generic `$T` through generic-struct / pointer / UFCS-pack params). + Regression: `examples/0214` + `0215`. Was the original B1.2 surface blocker. - **issue 0150** (deferred) — a `void` struct field crashes the compiler (unsized-type SIGTRAP in LLVM `getTypeSizeInBits`). Blocks `Future(void)` → `timeout` (B1.4). Repro: `issues/0150-...`. - (Note: **issue 0149**, filed by another session against an earlier dirty binary, was a @@ -354,3 +339,14 @@ done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idi (`lower/error.zig:148`) misses the channel. Filed `issues/0153-...` with a minimal co-located 2-file repro + a single-file stdlib-`await` repro + investigation prompt. Per the STOP rule: shipped the 0152 fix, filed 0153, STOPPED. Resume the async examples after 0153. +- **0153 FIXED → B1.2 COMPLETE** — `inferGenericReturnType` (`src/ir/generics.zig`) resolved the + return-type AST in the CALL-SITE module, so a re-exported error set (`LE :: lib.LE`) resolved + to a non-`.error_set` alias and the planned call-result was a plain tuple (channel lost). Fix: + pin the source to `fd.body.source_file` around the return-type resolution, exactly as + `monomorphizeFunction` does — the `!E` now resolves to the real `.error_set`. One-function + change; full suite green (732/0), no regression. Issue 0153 RESOLVED; repro → + `examples/1058-errors-reexport-value-failable-channel.sx` (+ companion `lib.sx`). With the + channel preserved, landed the async examples: **`1805`** (`async`/`await` + `now_ms` → `sum: + 42` / `double: 42` / `clock ok`) + **`1806`** (`cancel` → `await` raises `.Canceled` → `or` + default; `ok: 7` / `canceled: -99`). **B1.2 (Io capability + M:1 async surface) is COMPLETE.** + Next: B1.3 (fiber runtime) on the `.naked` context-switch substrate. diff --git a/examples/1805-concurrency-io-blocking-async.sx b/examples/1805-concurrency-io-blocking-async.sx new file mode 100644 index 00000000..a422cb0e --- /dev/null +++ b/examples/1805-concurrency-io-blocking-async.sx @@ -0,0 +1,23 @@ +// 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. +// +// 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. +#import "modules/std.sx"; + +main :: () { + // Homogeneous args. + s := context.io.async((a: i64, b: i64) -> i64 => a + b, 40, 2); + print("sum: {}\n", s.await() or { -1 }); + + // Single arg. + d := context.io.async((x: i64) -> i64 => x * 2, 21); + print("double: {}\n", d.await() or { -1 }); + + // The Io capability also carries a monotonic clock. + if context.io.now_ms() >= 0 { print("clock ok\n"); } +} diff --git a/examples/1806-concurrency-io-cancel.sx b/examples/1806-concurrency-io-cancel.sx new file mode 100644 index 00000000..036754a2 --- /dev/null +++ b/examples/1806-concurrency-io-cancel.sx @@ -0,0 +1,17 @@ +// B1.2 — cancellation rides the `!` error channel (model (a)). `f.cancel()` +// sets the per-future cancel flag + marks `state = .canceled`, so a +// subsequent `f.await()` raises `error.Canceled` out of its value-failable +// `($R, !IoErr)` — caught here with `or`. A future that is NOT canceled +// awaits its value normally. +#import "modules/std.sx"; + +main :: () { + // Not canceled → await yields the value. + ok := context.io.async((n: i64) -> i64 => n, 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.cancel(); + print("canceled: {}\n", c.await() or { -99 }); +} diff --git a/examples/expected/1805-concurrency-io-blocking-async.exit b/examples/expected/1805-concurrency-io-blocking-async.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/expected/1805-concurrency-io-blocking-async.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/1805-concurrency-io-blocking-async.stderr b/examples/expected/1805-concurrency-io-blocking-async.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1805-concurrency-io-blocking-async.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/1805-concurrency-io-blocking-async.stdout b/examples/expected/1805-concurrency-io-blocking-async.stdout new file mode 100644 index 00000000..ed2c2a46 --- /dev/null +++ b/examples/expected/1805-concurrency-io-blocking-async.stdout @@ -0,0 +1,3 @@ +sum: 42 +double: 42 +clock ok diff --git a/examples/expected/1806-concurrency-io-cancel.exit b/examples/expected/1806-concurrency-io-cancel.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/expected/1806-concurrency-io-cancel.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/1806-concurrency-io-cancel.stderr b/examples/expected/1806-concurrency-io-cancel.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/1806-concurrency-io-cancel.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/1806-concurrency-io-cancel.stdout b/examples/expected/1806-concurrency-io-cancel.stdout new file mode 100644 index 00000000..1ed4a927 --- /dev/null +++ b/examples/expected/1806-concurrency-io-cancel.stdout @@ -0,0 +1,2 @@ +ok: 7 +canceled: -99