fibers B1.0a: plumb abi(.pure), emit bails (lock)
First implementation step of Stream B1 (fibers). Make the inert abi(.pure) ABI carry an is_pure flag through lowering, with LLVM emission deliberately bailing loudly until B1.0b — the lock half of the lock->green cadence. - IR Function.is_pure, set from fd.abi == .pure at both declareFunction decl sites. - funcWantsImplicitCtx skips .pure (no synthetic __sx_ctx, mirroring the .c skip): a pure fn reads args from ABI registers, an implicit ctx would occupy a register slot the asm doesn't expect. - both body-lowering paths bypass lowerValueBody for .pure: lower the asm body as statements + cap with unreachable. A pure body has no sx return (the asm rets itself), so the implicit-return diagnostic must not fire. - emit_llvm Pass 2 bails loudly when func.is_pure (build-gating nonzero exit) rather than emit a framed body, whose epilogue would corrupt a context switch's deliberate SP-in != SP-out. examples/1800-concurrency-pure-asm.sx: one host example (no .build pin -- the bail fires before instruction selection, so it is host-independent), locked to the bail snapshot. B1.0b flips emit to LLVM's naked attribute + asm-only body and pins the example per-arch. The sx-facing name is "pure" throughout (field, diagnostic); LLVM's naked attribute is only the B1.0b lowering mechanism. Suite green (722/0).
This commit is contained in:
@@ -4,29 +4,43 @@ 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
|
||||
**Carve** — wrote PLAN-FIBERS.md + this checkpoint. Grounded the B1 compiler floor against
|
||||
the tree (see Decisions). Baseline verified green: `zig build && zig build test` → **721
|
||||
ran, 0 failed** (one Android-SDK-gated example skipped; the trailing "failed command:" line
|
||||
is the zig listen-protocol echo, not a failure). HEAD `3fad2d5`, tree clean.
|
||||
**B1.0a (`abi(.pure)` lock commit) — DONE.** Plumbed the `is_pure` flag end-to-end and made
|
||||
emit bail loudly:
|
||||
- IR `Function.is_pure: bool` ([inst.zig](../src/ir/inst.zig)) — set from `fd.abi == .pure`
|
||||
at both `declareFunction` decl sites ([decl.zig](../src/ir/lower/decl.zig)).
|
||||
- `funcWantsImplicitCtx` returns false for `.pure` (mirrors the `.c` skip, decl.zig:515) —
|
||||
a `.pure` fn gets no synthetic `__sx_ctx`.
|
||||
- Both body-lowering paths bypass `lowerValueBody` for `.pure`: lower the asm body as
|
||||
statements + cap with `unreachable` (a `.pure` body has no sx return — the asm rets
|
||||
itself; this avoids the implicit-return diagnostic).
|
||||
- `emit_llvm` Pass 2 (~line 402) **bails loudly** when `func.is_pure`
|
||||
("`abi(.pure)` function '…' LLVM emission not yet implemented") via `comptime_failed`
|
||||
(driver aborts nonzero) — NOT a framed body (whose epilogue would corrupt a context
|
||||
switch's SP-in ≠ SP-out).
|
||||
- `examples/1800-concurrency-pure-asm.sx` — one host example (no `.build` pin; the bail is
|
||||
host-independent, fires before any asm/instruction selection), locked to the bail snapshot
|
||||
(exit 1, empty stdout, the loud diagnostic on stderr).
|
||||
- **Naming:** the sx-facing name is **`pure`** throughout (field, diagnostic); LLVM's
|
||||
`naked` attribute is only the B1.0b lowering mechanism (per user direction — don't call
|
||||
the function "naked").
|
||||
- `zig build && zig build test` green: **722 ran, 0 failed**.
|
||||
|
||||
## Current state
|
||||
Stream A (atomics) is feature-complete (✅) and unblocks B2-channels. Stream B1 is **carved,
|
||||
not started**. No fibers/Io/scheduler code exists yet. The compiler floor for B1 is grounded:
|
||||
- `abi(.pure)` exists in the `ABI` enum but is **inert** — maps to `.default` CC, emits no
|
||||
naked attribute. B1.0 makes it actually emit LLVM `naked`.
|
||||
Stream A (atomics) is feature-complete (✅) and unblocks B2-channels. Stream B1: **B1.0a
|
||||
landed**; the `abi(.pure)` ABI is plumbed but emit deliberately bails (B1.0b flips it to
|
||||
real LLVM `naked` emission). No fibers/Io/scheduler code yet. Grounded floor facts:
|
||||
- `context` is already an implicit `*Context` param (slot 0) + `push Context` is a stack
|
||||
`alloca` ⇒ **fiber-local for free**. The only shared root is the `__sx_default_context`
|
||||
global (entry-point bind). B1.1 is therefore expected to be a **library convention** (spawn
|
||||
trampoline snapshots the spawner's ctx into slot 0), **likely zero compiler change** —
|
||||
confirm by probe first.
|
||||
- Inline asm works end-to-end (lower→emit→JIT, aarch64 + x86_64) — the naked body reuses it.
|
||||
`alloca` ⇒ **fiber-local for free**. Only shared root = `__sx_default_context` global
|
||||
(entry-point bind). B1.1 expected to be a **library convention** (spawn trampoline
|
||||
snapshots the spawner's ctx into slot 0), **likely zero compiler change** — probe first.
|
||||
- Inline asm works end-to-end (lower→emit→JIT, aarch64 + x86_64) — the `.pure` body reuses it.
|
||||
|
||||
## Next step
|
||||
**B1.0a (naked-ABI lock commit)** — per PLAN-FIBERS.md "Phases → B1.0 → B1.0a" and the
|
||||
kickoff prompt at the bottom of that file. Add `Function.is_naked`, thread `abi == .pure`
|
||||
through `decl.zig` (skip implicit-ctx like `.c`), make `emit_llvm` **BAIL loudly** on a naked
|
||||
fn, add the two arch-gated examples (`1800` aarch64 / `1801` x86_64), lock to the bail
|
||||
diagnostic. STOP before B1.0b (real emission) — separate commit (cadence rule).
|
||||
**B1.0b (`abi(.pure)` real emission)** — per PLAN-FIBERS.md "Phases → B1.0 → B1.0b" and the
|
||||
kickoff prompt at the bottom of that file. Replace the emit bail with LLVM's `naked`
|
||||
attribute + asm-only body; pin `1800` aarch64 (run end-to-end → exit 42, capture `.ir`); add
|
||||
x86_64 cross sibling `1801` (ir-only); add an `emit_llvm.test.zig` unit test asserting the
|
||||
`naked` attr. Separate commit (cadence rule — B1.0a locked, B1.0b greens).
|
||||
|
||||
## Known issues / capability gaps
|
||||
- **Orthogonal (not a B1 blocker):** default VALUES for comptime params don't bind on
|
||||
@@ -50,12 +64,17 @@ diagnostic. STOP before B1.0b (real emission) — separate commit (cadence rule)
|
||||
`naked` + skip prologue/ctx (mirror the existing `.c` skip), NOT extend the enum (it's
|
||||
already there, just inert).
|
||||
- **`.pure` ≠ `.c`:** a `.c` epilogue would restore SP from the wrong stack across a context
|
||||
switch (SP-in ≠ SP-out by design). naked = no prologue/epilogue/frame; the asm emits its
|
||||
own `ret`. This is why the switch must be naked.
|
||||
- **B1.0 snapshot scope:** the `naked` attr text is arch-invariant, but a naked body is raw
|
||||
per-arch asm — so B1.0 needs **two arch-gated examples** (aarch64 + x86_64, `.build`
|
||||
target-gated, ir-only on mismatch), unlike atomics' single host `.ir`. The `.ir` proves
|
||||
`naked` + asm emitted, NOT register-save correctness (that's B1.3's stress harness).
|
||||
switch (SP-in ≠ SP-out by design). `.pure` = no prologue/epilogue/frame; the asm emits its
|
||||
own `ret`. This is why the switch must be `.pure`.
|
||||
- **Naming:** sx-facing name is **`pure`** (field `is_pure`, the diagnostic). LLVM's `naked`
|
||||
function attribute is only the lowering mechanism (B1.0b) — do not call the function
|
||||
"naked" (user direction).
|
||||
- **B1.0 snapshot scope:** a `.pure` body is raw per-arch asm; LLVM's `naked` attr text is
|
||||
arch-invariant. **B1.0a** = one host example locked to the emit bail (host-independent —
|
||||
fires before instruction selection; no `.build` pin). **B1.0b** = pin aarch64 + add an
|
||||
x86_64 cross sibling (`.build` target-gated, ir-only on mismatch), like the asm corpus
|
||||
split. The `.ir` proves the `naked` attr + asm emitted, NOT register-save correctness
|
||||
(that's B1.3's stress harness).
|
||||
- **B1.1 grounded as library-only (pending probe):** push frames are stack-`alloca`'d and
|
||||
the implicit ctx rides slot 0, so a spawn trampoline can pass a snapshotted ctx with no
|
||||
compiler change. The design doc's "never raw TLS" guards a non-problem (context is not
|
||||
@@ -73,6 +92,11 @@ diagnostic. STOP before B1.0b (real emission) — separate commit (cadence rule)
|
||||
(decl.zig:515), `push Context` stack-alloca + slot-0 implicit ctx (stmt.zig:1263,
|
||||
lower.zig:259), `__sx_default_context` root (decl.zig:2667/2815), inline-asm corpus
|
||||
(1645/1651). Corrected the design's `callconv(.naked)` → real `abi(.pure)` spelling and
|
||||
the B1.0 snapshot story (two arch-gated examples, not one host `.ir`). B1.1 grounded as
|
||||
likely library-only. Baseline green (721/0). Stream ready; **B1.0a is the first
|
||||
implementation step.**
|
||||
the B1.0 snapshot story. B1.1 grounded as likely library-only. Baseline green (721/0).
|
||||
- **B1.0a** — plumbed `Function.is_pure` (set from `fd.abi == .pure` at both decl sites);
|
||||
`funcWantsImplicitCtx` skips `.pure` (no implicit ctx, like `.c`); both body-lowering
|
||||
paths bypass `lowerValueBody` for `.pure` (asm body + `unreachable` cap — no sx return);
|
||||
`emit_llvm` Pass 2 bails loudly on `func.is_pure`. `examples/1800-concurrency-pure-asm.sx`
|
||||
locked to the bail (exit 1 + diagnostic). Renamed `is_naked`→`is_pure` per user direction
|
||||
(sx says `pure`, not "naked"; LLVM `naked` attr is only the B1.0b mechanism). Suite green
|
||||
(722/0). **Next: B1.0b (real `naked` emission).**
|
||||
|
||||
Reference in New Issue
Block a user