Adversarial review of B1.0b found a param-bearing abi(.pure) function
emitted invalid LLVM ("cannot use argument of naked function" — loud
verifier error, not silent) because the param-alloca loop spilled the
args to stack slots, which a naked function cannot have.
Fixed forward — this ENABLES the B1.3 context-switch use case rather
than rejecting it: gate the param-alloca loop on fd.abi != .pure in
decl.zig (both body-lowering paths) and generic.zig. A naked function's
args stay in their ABI registers and are read directly by the asm body
(e.g. swap_context reads from/to from x0/x1); the LLVM args are
declared-but-unused, which the verifier allows.
examples/1803-concurrency-pure-asm-param.sx: naked add(a, b) reads x0/x1
(add x0, x0, x1; ret) -> 40 + 2 = 42. aarch64-pinned.
Pack abi(.pure) (variadic + naked — nonsensical, can't read a runtime
pack from registers) left unsupported: pack.zig's param loop is
intertwined with comptime-param/#insert handling, so that case still
hits the loud verifier error. Documented in the checkpoint.
Also updates PLAN-FIBERS / CHECKPOINT-FIBERS for B1.0 completion.
B1.0 complete. Suite green (725/0).
145 lines
10 KiB
Markdown
145 lines
10 KiB
Markdown
# CHECKPOINT-FIBERS — Stream B1 (fibers + Io + M:1 scheduler)
|
|
|
|
Companion to [PLAN-FIBERS.md](PLAN-FIBERS.md). Update after every step (one step at a time,
|
|
per the cadence rule). New corpus category: `18xx` concurrency.
|
|
|
|
## Last completed step
|
|
**B1.0b (`abi(.pure)` real emission) — DONE. B1.0 complete.** Replaced the emit bail with
|
|
real LLVM `naked` emission:
|
|
- `emit_llvm` declaration pass: for `func.is_pure`, add the LLVM `naked` + `noinline` +
|
|
`nounwind` attributes and **skip** the `frame-pointer=all` attribute (incompatible with a
|
|
frameless function). Pass 2 now emits the `.pure` body normally — `naked` makes the
|
|
backend emit it verbatim (the inline asm + its own `ret`) with no prologue/epilogue.
|
|
- IR shape (verified): `; Function Attrs: naked noinline nounwind` / `define internal i64
|
|
@answer() #0 { entry: call void asm sideeffect "…ret…", ""() unreachable }` /
|
|
`attributes #0 = { naked noinline nounwind }`. The caller invokes it as an ordinary
|
|
`() -> i64` call (`.pure` is `call_conv == .default`).
|
|
- `examples/1800-concurrency-pure-asm.sx` — now GREEN, aarch64-pinned (`.build {"target":
|
|
"macos"}`): runs end-to-end → **exit 42** on this host, ir-only on a mismatch; `.ir`
|
|
snapshot captured.
|
|
- `examples/1801-concurrency-pure-generic.sx` (renamed from `-bail`) — the generic `.pure`
|
|
now emits a correct naked `answer__i64` (exit 42), proving generic.zig produces a naked
|
|
body, not a framed one. aarch64-pinned.
|
|
- `examples/1802-concurrency-pure-asm-x86.sx` — x86_64 cross sibling (`.build {"target":
|
|
"x86_64-linux"}`, ir-only here): `.ir` locks `naked` + `movl $42, %eax` / `ret`.
|
|
- Unit test `emit: abi(.pure) function gets the naked attribute (no frame-pointer)` in
|
|
`emit_llvm.test.zig` (asserts `naked` present, `frame-pointer` absent).
|
|
- **B1.0c (review-hardening):** a param-bearing `.pure` fn emitted invalid LLVM (loud
|
|
verifier error "cannot use argument of naked function") because the param-alloca loop
|
|
wasn't gated. Fixed forward (this *enables* the B1.3 context-switch use case rather than
|
|
rejecting it): gated the param-alloca loop on `fd.abi != .pure` in decl.zig (both paths) +
|
|
generic.zig; a naked fn's args stay in registers (read by asm), declared-but-unused in
|
|
LLVM. Locked by `examples/1803-concurrency-pure-asm-param.sx` (`add(a,b)` → x0+x1 → 42).
|
|
- `zig build && zig build test` green: **725 ran, 0 failed** + unit tests.
|
|
|
|
### Earlier — B1.0a (lock + review hardening)
|
|
Plumbed `Function.is_pure` (set from `fd.abi == .pure` at both decl sites + generic.zig +
|
|
pack.zig); `funcWantsImplicitCtx` skips `.pure` (no synthetic ctx, like `.c`); all
|
|
body-lowering paths bypass `lowerValueBody` for `.pure` (asm body + `unreachable` cap — no sx
|
|
return); `emit_llvm` Pass 2 bailed loudly (since flipped to real emission). Adversarial
|
|
review caught the generic/pack `is_pure` gap (a generic `.pure` silently shipped a framed
|
|
body); closed + locked. The review's `.pure`-lambda CRITICAL was a false positive
|
|
(unparseable — `isLambda` breaks on the `abi` keyword).
|
|
|
|
## Current state
|
|
Stream A (atomics) is feature-complete (✅). Stream B1: **B1.0 complete** — `abi(.pure)`
|
|
emits a real LLVM `naked` function end-to-end (decl, generic, pack paths), the substrate for
|
|
the fiber context-switch. 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**. 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.
|
|
- **`.pure` with PARAMS works** (B1.0c, the B1.3 substrate): the param-alloca loop is gated
|
|
on `fd.abi != .pure` in decl.zig (both paths) + generic.zig — a naked fn's args stay in
|
|
ABI registers (read by the asm body), declared-but-unused in LLVM (verifier-legal).
|
|
Example `1803-concurrency-pure-asm-param.sx` (`add(a,b)` reads x0/x1). **Unsupported (loud,
|
|
not silent):** a `.pure` *variadic-pack* fn (pack.zig's param loop is intertwined with
|
|
comptime-param/`#insert` handling, and a naked fn can't read a runtime-sized pack from
|
|
registers anyway) → loud LLVM-verifier error for that nonsensical construct. Acceptable
|
|
boundary; a sharper sx diagnostic for it is a candidate polish, not a blocker.
|
|
|
|
## Next step
|
|
**B1.1 (per-fiber `context` root) — probe-first.** Per PLAN-FIBERS.md "Phases → B1.1". Write
|
|
a probe confirming a spawn trampoline can pass a snapshotted `Context` as slot 0 with no
|
|
compiler change (grounded as likely zero-change); lock the behavior with an `18xx` example +
|
|
a checkpoint note on the convention. Only if the probe surfaces a real gap (a path re-reads
|
|
`__sx_default_context` mid-stack) does this become a compiler step.
|
|
|
|
## Known issues / capability gaps
|
|
- **Orthogonal (not a B1 blocker):** default VALUES for comptime params don't bind on
|
|
generic-struct methods (free-fn defaults DO work) — inherited from Stream A. Only matters
|
|
if a B2 lib type wants a defaulted comptime param; atomics/fibers require explicit, so
|
|
unaffected.
|
|
- **Issue 0144 (open, independent):** calling an unrecognized bodiless `#builtin` silently
|
|
returns 0 / exit 0 — a silent-fallback footgun in the generic builtin-call path. Filed;
|
|
leave for its own fix session unless prioritized. Not a B1 blocker.
|
|
- **Deferred design gap (documented):** the B1.4 event-loop `Io` does not yet cooperate with
|
|
a platform UI run loop (CFRunLoop/NSRunLoop/ALooper); pinning gives thread-affinity, not
|
|
run-loop integration — a §6 app-target concern, out of B1 scope.
|
|
|
|
## Decisions (Stream B1 specifics; surface locked in design §4 / §4.6)
|
|
- **The async runtime is sx LIBRARY code.** The compiler provides only: the general
|
|
primitives (inline asm ✅, `abi(.pure)` naked [B1.0], atomics ✅) + fiber-safe codegen
|
|
(`context` already fiber-local — B1.1). Schedulers, fibers, channels, futures, `Io`
|
|
vtables, `mmap` stacks are all sx.
|
|
- **`abi(.pure)` is the real spelling of the design's `callconv(.naked)`** — postfix slot,
|
|
`name :: (sig) -> Ret abi(.pure) { asm { … }; }`. B1.0 = carry it into IR + emit LLVM
|
|
`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). `.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
|
|
TLS). Probe to confirm before sizing any compiler work.
|
|
- **Test keystones (design §10):** the **B1.3 switch-stress harness** gates the
|
|
context-switch (the one piece the deterministic `Io` can't test — §8.1.1, §10.7); the
|
|
**B1.4 deterministic-sim `Io`** (calibrated against blocking `Io` — §8.1.3) gates all
|
|
scheduling tests. Both must exist + be calibrated before the async tests they gate are
|
|
trusted. `18xx` asserts program-emitted ordering contracts, not raw interleaving.
|
|
|
|
## Log
|
|
- **carve** — wrote PLAN-FIBERS.md + CHECKPOINT-FIBERS.md. Grounded the B1 compiler floor:
|
|
`ABI.pure` inert (type_resolver.zig:237), IR `Function` has no naked flag (inst.zig:605),
|
|
attribute API pattern (emit_llvm.zig:1339 nounwind), `.c` ctx-skip precedent
|
|
(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. 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).
|
|
- **B1.0a review-hardening** — adversarial review found generic/pack Function-creation paths
|
|
left `is_pure` false (silent framed body for a generic `.pure` instance — returned 42 but
|
|
corrupted the stack). Fixed generic.zig + pack.zig (set `is_pure` + asm-only `unreachable`
|
|
cap); locked by `examples/1801-concurrency-pure-generic-bail.sx`. The review's `.pure`-
|
|
lambda CRITICAL was a false positive (unparseable — `isLambda` breaks on `abi`). Suite
|
|
green (723/0).
|
|
- **B1.0b** — real `naked` emission: emit_llvm declaration pass adds LLVM `naked`/`noinline`/
|
|
`nounwind` + skips `frame-pointer` for `func.is_pure`; Pass 2 emits the body verbatim (no
|
|
prologue). `1800` green aarch64-pinned (exit 42 + `.ir`); renamed `1801` → `-generic`
|
|
(generic `.pure` emits a naked body, exit 42); added x86_64 sibling `1802` (ir-only, `.ir`
|
|
locks `naked` + `movl $42, %eax`). Unit test asserts `naked` present + `frame-pointer`
|
|
absent. Suite green (724/0).
|
|
- **B1.0c** — review-hardening: param-bearing `.pure` emitted invalid LLVM (loud verifier
|
|
error). Gated the param-alloca loop on `fd.abi != .pure` (decl.zig both paths + generic.zig)
|
|
— naked args stay in registers, read by the asm body (the B1.3 context-switch shape).
|
|
Locked by `examples/1803-concurrency-pure-asm-param.sx`. Pack `.pure` left unsupported
|
|
(loud, nonsensical). **B1.0 complete.** Suite green (725/0). **Next: B1.1 (per-fiber
|
|
context, probe-first).**
|