From 3d8f9ca09433311ecc1bb4986cfe328dff6abda6 Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 26 Jun 2026 18:08:03 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20PLAN-RACE=20=E2=80=94=20race=20runtime?= =?UTF-8?q?=20DONE=20(GAP=201/2=20fixed,=20826/0,=20both=20platforms)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- current/PLAN-RACE.md | 64 +++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/current/PLAN-RACE.md b/current/PLAN-RACE.md index 86133789..604d426e 100644 --- a/current/PLAN-RACE.md +++ b/current/PLAN-RACE.md @@ -90,23 +90,49 @@ Prereqs DONE (each committed + adversarially reviewed + suite-green): runtime; its label can't be a literal). Pure sx (writes the i64 tag@0 + payload@8). Verified for complex payloads (struct / string / 40-byte struct). **This resolves the variant-construction gap.** -**ONE remaining blocker for the runtime: GAP 1 — comptime-cursor indexing of a named-tuple VALUE.** -`race(tasks: $T)` must read the i-th task `*Task(T_i)` with its concrete type where `i` is the -`inline for` cursor. Today `tasks[i]` → *"cannot index a value of type '(…)'"* and -`field_value(tasks, i)` returns a **void** Any (tuple field-VALUE reflection is unimplemented — the -0195 family covered count/name/type but not value). Fix options: (a) make `tasks[i]` work with a -comptime cursor on a named-tuple value (mirror packs' `xs[i]`), or (b) implement `field_value` for -tuples + recover the concrete type via `field_type(T, i)`. (a) is cleaner for `race` (direct typed -access). This is the last thing between here and the pure-sx runtime. +**GAP 1 — comptime-cursor indexing of a named-tuple VALUE — DONE** (`fee86adf`). `tasks[i]` with a +comptime cursor now reads the i-th element with its concrete type (option (a), a `structGet`). -**THEN (pure-sx, unblocked once GAP 1 lands): the runtime in `sched.sx`.** -- `RaceResult :: ($T) -> Type` over `*Task(..)` (the 0649 shape, with `Task` instead of `Box`). -- `race :: ufcs (self: *Scheduler, tasks: $T) -> RaceResult(T)`: suspend the caller until the FIRST - task is `.ready` (register caller as waiter on all pending; on wake DEREGISTER from all to avoid a - double-wake of the merge — the queue-corruption hazard `wake` guards); winner = first ready; - build it with `make_variant` in the matching `inline for` arm; then `cancel` + JOIN every loser - (needs a `Task.finished` flag set at the end of the `go` body so the joiner distinguishes a - finished worker from a merely-flagged-cancelled one, checked before parking like `wait` checks - `.ready`). Reuses `suspend_self`/`wake`. -- Lock with a 2/3-task example (deterministic winner via `sleep` ordering; assert losers cancelled). -- Validate byte-identical on aarch64-macOS host AND aarch64-linux container; full `zig build test`. +**GAP 2 — surfaced during the runtime, all DONE** (`6a976287`): three compiler enablers the runtime +needed beyond the read path. (1) a named-tuple LITERAL passed directly as `$T` lost its element names +(`field_name` → ""), breaking `RaceResult`'s `make_enum`; (2) tuple-element L-VALUES by comptime index +(`tasks[i].waiter = …`) panicked at LLVM emit (an `index_gep` with `ptrTo(.unresolved)`); (3) a user +`($X) -> Type` call couldn't bind a `$E: Type` arg, blocking `make_variant(RaceResult(T), …)`. All +fixed + adversarially reviewed; OOB comptime tuple indices now diagnose loudly on every L-value path. + +**Runtime in `sched.sx` — DONE** (`9099735e`). +- `RaceResult :: ($T) -> Type` over `*Task(..)` (the 0649 shape, with `Task`). +- `race :: ufcs (self: *Scheduler, tasks: $T) -> RaceResult(T)`: Phase 1 suspend until the FIRST + `.ready` (register waiter on all pending; on wake DEREGISTER from all; lowest-index winner). Phase 2 + build the winner with `make_variant`. Phase 3 cancel + JOIN each loser one-at-a-time (only the joined + loser carries a waiter → no mid-join double-wake). Join rides a new `Task.finished` flag (set at the + end of the `go` body, checked before parking). Cooperative-cancel: a loser parked mid-`sleep` runs to + its natural end before `race` returns. +- Locked by `examples/concurrency/1821-concurrency-fiber-race.sx` (3 tasks i64/bool/f64, sleep 10/20/30, + shortest wins, losers cancelled + joined). Byte-identical on aarch64-macOS host AND aarch64-linux + container; full `zig build test` green (826/0). + +**Remaining (step 5, future work):** +- POSITIONAL tuple form (`._0`/`._1`): `field_name` yields "" for an unnamed element → `make_enum` + rejects the duplicate. Needs a `_N` fallback in `RaceResult` (a comptime int→string) or a compiler + `field_name` default for positional tuples. +- Bare named-arg call form `s.race(a = ta, b = tb)` (no `.(…)` wrapper) — see the note below. +- Edge cases: already-ready-at-call (works today), all-cancelled (would deadlock-abort loudly). +- By-design caveat to document: `race` returns only when the SLOWEST loser finishes (cooperative + cancel can't preempt a mid-`sleep` loser; a loser blocked on `block_on_fd` that never fires blocks + the join forever — `cancel` doesn't unblock an fd waiter). + +### Future work — bare named-arg call form `s.race(a = ta, b = tb)` +Today the result-tuple is passed as a named-tuple LITERAL: `s.race(.(a = ta, b = tb))`. The user asked +about dropping the `.(…)` so it reads `s.race(a = ta, b = tb)`. That is a CALL-SITE syntax/binding +feature, independent of the race runtime: +- The parser would have to accept `name = expr` call arguments and, for a `(tasks: $T)` parameter whose + type is inferred, MATERIALIZE them into a single named-tuple value bound to `$T` (rather than treating + each `name = expr` as a separate positional/keyword arg). Effectively "collect trailing `k = v` call + args into one anonymous named-tuple when the callee has a single inferred aggregate param". +- Risk: `name = expr` in call position currently has no meaning (or would collide with a future + keyword-argument feature). Decide whether `k = v` call args are (a) always tuple-materialized for an + inferred aggregate param, or (b) a dedicated `..` / sugar. Option (a) is the least new syntax. +- Once the call site produces the same named-tuple value, the entire race runtime is unchanged (it + already reflects `$T`). So this is purely front-end sugar — schedule it with the keyword-args work, + not the concurrency stream.