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).
7.1 KiB
CHECKPOINT-FIBERS — Stream B1 (fibers + Io + M:1 scheduler)
Companion to 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.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) — set fromfd.abi == .pureat bothdeclareFunctiondecl sites (decl.zig). funcWantsImplicitCtxreturns false for.pure(mirrors the.cskip, decl.zig:515) — a.purefn gets no synthetic__sx_ctx.- Both body-lowering paths bypass
lowerValueBodyfor.pure: lower the asm body as statements + cap withunreachable(a.purebody has no sx return — the asm rets itself; this avoids the implicit-return diagnostic). emit_llvmPass 2 (~line 402) bails loudly whenfunc.is_pure("abi(.pure)function '…' LLVM emission not yet implemented") viacomptime_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.buildpin; 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
purethroughout (field, diagnostic); LLVM'snakedattribute is only the B1.0b lowering mechanism (per user direction — don't call the function "naked"). zig build && zig build testgreen: 722 ran, 0 failed.
Current state
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:
contextis already an implicit*Contextparam (slot 0) +push Contextis a stackalloca⇒ fiber-local for free. Only shared root =__sx_default_contextglobal (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
.purebody reuses it.
Next step
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 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
#builtinsilently 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
Iodoes 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 (contextalready fiber-local — B1.1). Schedulers, fibers, channels, futures,Iovtables,mmapstacks are all sx. abi(.pure)is the real spelling of the design'scallconv(.naked)— postfix slot,name :: (sig) -> Ret abi(.pure) { asm { … }; }. B1.0 = carry it into IR + emit LLVMnaked+ skip prologue/ctx (mirror the existing.cskip), NOT extend the enum (it's already there, just inert)..pure≠.c: a.cepilogue 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 ownret. This is why the switch must be.pure.- Naming: sx-facing name is
pure(fieldis_pure, the diagnostic). LLVM'snakedfunction attribute is only the lowering mechanism (B1.0b) — do not call the function "naked" (user direction). - B1.0 snapshot scope: a
.purebody is raw per-arch asm; LLVM'snakedattr text is arch-invariant. B1.0a = one host example locked to the emit bail (host-independent — fires before instruction selection; no.buildpin). B1.0b = pin aarch64 + add an x86_64 cross sibling (.buildtarget-gated, ir-only on mismatch), like the asm corpus split. The.irproves thenakedattr + 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
Iocan't test — §8.1.1, §10.7); the B1.4 deterministic-simIo(calibrated against blockingIo— §8.1.3) gates all scheduling tests. Both must exist + be calibrated before the async tests they gate are trusted.18xxasserts program-emitted ordering contracts, not raw interleaving.
Log
- carve — wrote PLAN-FIBERS.md + CHECKPOINT-FIBERS.md. Grounded the B1 compiler floor:
ABI.pureinert (type_resolver.zig:237), IRFunctionhas no naked flag (inst.zig:605), attribute API pattern (emit_llvm.zig:1339 nounwind),.cctx-skip precedent (decl.zig:515),push Contextstack-alloca + slot-0 implicit ctx (stmt.zig:1263, lower.zig:259),__sx_default_contextroot (decl.zig:2667/2815), inline-asm corpus (1645/1651). Corrected the design'scallconv(.naked)→ realabi(.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 fromfd.abi == .pureat both decl sites);funcWantsImplicitCtxskips.pure(no implicit ctx, like.c); both body-lowering paths bypasslowerValueBodyfor.pure(asm body +unreachablecap — no sx return);emit_llvmPass 2 bails loudly onfunc.is_pure.examples/1800-concurrency-pure-asm.sxlocked to the bail (exit 1 + diagnostic). Renamedis_naked→is_pureper user direction (sx sayspure, not "naked"; LLVMnakedattr is only the B1.0b mechanism). Suite green (722/0). Next: B1.0b (realnakedemission).