Files
sx/current/PLAN-RACE.md

113 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLAN-RACE — Stream B2/A1: `race` over the M:1 fiber scheduler
Carved from the async roadmap ([../design/execution-evolution-roadmap.md](../design/execution-evolution-roadmap.md)
§4.5, §4.6, §7 step 3). The headline A1 feature still missing after Stream B1: **`race`** — start N
async tasks, return when the FIRST completes, and **structurally** cancel + join the losers before
returning. The result is a synthesized tagged-union mirroring the input named tuple's labels.
```sx
fa := s.go(() -> A => read_a(conn)); // *Task(A)
fb := s.go(() -> B => read_b(conn)); // *Task(B)
winner := s.race((a: fa, b: fb)); // RaceResult = enum { a: A; b: B }
if winner == {
case .a: (v) { handle_a(v); } // v : A (fb cancelled + joined)
case .b: (v) { handle_b(v); } // v : B (fa cancelled + joined)
}
// positional form: s.race((fa, fb)) → tags ._0 / ._1
```
## Design decisions (grounded against the tree, 2026-06-26)
- **Built over the M:1 `Task` layer, NOT `context.io`/`Future`.** The suspending async is
`sched.go`/`wait`/`cancel` over `*Task($R)` (B1.4a); `context.io.async``Future` is the BLOCKING
impl (workers run inline → racing is meaningless there). The roadmap's "Future" maps to our
`*Task`. `race` is a `Scheduler`/`Task` UFCS function in `library/modules/std/sched.sx`.
- **The result is a comptime-synthesized nominal tagged-union** (`RaceResult`), one variant per input
tuple element: variant NAME = the tuple label (positional → `_0`/`_1`), payload = the task's result
type. Synthesis uses the proven `declare`/`define`/`make_enum` + `field_count`/`field_name`/
`field_type` reflection (examples 06190623, 0646). The input arrives as an inferred type PARAMETER
`$T` (a named tuple of `*Task(..)`), which reflects correctly (issue 0195 fixed; the tuple-*alias*
gap is issue 0196, NOT on this path).
- **Cancellation rides the existing cooperative `Task.cancel`** (sets the flag + `.canceled` state).
`race` cancels every loser, then `wait`s each (joins) so no loser fiber outlives the `race` call —
structured. Reuses `suspend_self`/`wake`; no new scheduler machinery.
## The one net-new compiler primitive (step 1)
**`pointee($P: Type) -> Type #builtin`** — given a pointer type `*X`, return `X`. This is the only
missing reflection capability: `race` must project each tuple element `*Task(A)` to its result type
`A`, and there is currently NO way to get a pointer's target type at comptime (`field_count(*X)`=0,
`type_info` has no pointer variant). With it the projection is pure sx:
```sx
TaskResult :: ($P: Type) -> Type { // P = *Task(A) → A
return field_type(pointee(P), 0); // pointee → Task(A); field 0 = `value: A`
}
```
Small + generally useful (reflection is currently complete for aggregates but blind to pointer
targets). Mirror `field_type`'s `#builtin` plumbing (`src/ir/lower/call.zig` + `src/ir/calls.zig`),
backed by the pointer TypeInfo's pointee TypeId (`src/ir/types.zig`). Lock with a comptime example.
## Steps (each: implement → lock with an example → `zig build test` green → both platforms)
1. **`pointee` reflection builtin.** Add `pointee($P: Type) -> Type` (core.sx + compiler). Example:
`pointee(*i64)` = `i64`, `field_type(pointee(*Task(i64)), 0)` = the task value type. (worker+review)
2. **`RaceResult($T) -> Type` synthesis.** Type-fn: reflect the named-tuple `$T` of `*Task(..)`,
project each element via `TaskResult`, mint the tagged-union (labels → variants). Comptime-only
example asserting the minted type's `field_count`/`field_name` match the input tuple.
3. **`Task.Value` projection + result construction.** Confirm a winner's value can be boxed into the
minted variant by label/index (uses the existing variant-construction path).
4. **Runtime `race(tasks: $T) -> RaceResult(T)`.** Suspend the caller until the first task is
`.ready`; build the winner variant; then cancel + `wait`-join every loser before returning.
Single-winner (first by completion order; FIFO tiebreak). Example: 2 tasks, deterministic winner
via `sleep` ordering (like 1817), asserting the loser is cancelled + joined.
5. **Positional tuple form** (`._0`/`._1`) + edge cases (already-ready task → immediate, single-task
race, all-cancelled). Examples.
6. **Validate** every new example byte-identical on aarch64-macOS host AND aarch64-linux container;
full `zig build test` green; adversarially review each step.
## Status
Prereqs DONE (each committed + adversarially reviewed + suite-green):
- **issue 0195** (tuple/array/vector field reflection) fixed (`8ac6c573`). Tuple reflection works on
inline + `$T`-param forms. Issue 0196 (tuple *alias*) filed, not on the critical path.
- **`pointee($P) -> Type`** builtin added (`f1d29876`) — projects `*Task(A)``Task(A)`.
- **`field_count`/`size_of`/`align_of` fold as comptime constants** (`2a6ef398`) — so a generic
`($T) -> Type` builder can `inline for 0..field_count(T)` and size `[field_count(T)]EnumVariant`.
Verified: the variable-arity loop + array dim now work inside `RaceResult`.
- **comptime type-call composition** fixed (`eb18bbc6`) — a `field_type(...)`/`pointee(...)` result is
now usable as a `Type`-typed struct-field value, a generic `$P: Type` arg, and a nested type-call
arg (incl. with an `inline for` loop-var index). The **variable-arity `RaceResult` synthesis works
end-to-end** (proven by `examples/comptime/0649-comptime-typecall-composition.sx`: reflect a named
tuple of `*Box(..)` handles → mint a tagged-union with the tuple's labels, projecting `*Box(A)``A`).
- **`return` inside `inline if` fixed** (`84c2ae4f`) — the natural early-return-per-arm pattern (a
`return` in an `inline if`/comptime-`case` branch inside an `inline for`) no longer drops the
function's trailing statements. Lets `race` build the winner variant with a clean
`inline if i == { case 0: … else: … }` per-arm form.
- **`make_variant($E, idx, payload)`** added to `modules/std/meta.sx` (`1c26944e`) — the WRITE side of
the metatype triad: construct a minted tagged-union value by variant INDEX (the winner is chosen at
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.
**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`.