55 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.4a — a truly-SUSPENDING fiber-task async layer (go/wait/cancel) — landed +
adversarially reviewed; cleared two more compiler blockers en route. library/modules/std/sched.sx
now carries Task($R) + Scheduler.go(work) -> *Task($R) + wait/cancel (a ufcs layer over
the M:1 scheduler). s.go(work) runs the nullary thunk work as a REAL fiber; t.wait() SUSPENDS
the caller until it completes (vs io.sx's blocking context.io.async, which runs inline). Locked by
examples/concurrency/1813-concurrency-fiber-async-suspend.sx: two tasks interleave (A yields
mid-body so B runs first → 1 2 3), awaited values 42/100, and a canceled task's wait raises
.Canceled → or -99 → sequence: 1 2 3 42 100 -99.
- Design: a NULLARY thunk, not
async(worker, ..args). A comptime variadic pack can't cross a deferred (fiber) boundary —..argscaptured into a closure re-expands from the spawner's now-gone locals (issue 0156 Part 2). Sogotakeswork: Closure() -> $R; the user captures inputs in the lambda at the call site (thego func(){…}()idiom). Self-contained in sched.sx (NOT io.sx): io.sx importing sched.sx duplicates the_fib_trampglobal asm when a program also imports sched.sx directly (global asm emits per import-path) — so the Io-protocolspawn_raw/suspend_raw/readyhooks stay reserved for the future M:N model; M:1 usesgo/waitdirectly. Heap*Task(must outlivego's frame; leak documented).TaskErris LOCAL (the!failable detection doesn't see through io.sx'sIoErrre-export alias). - Two compiler blockers hit + FIXED (user-authorized in-session):
- issue 0156 Part 1 — a single-type generic
$R(parsed ascomptime_pack_ref) used as a type-arg (Box($R),size_of(Box($R))) inside a pack-fn body hit a missing arm inresolveTypeWithBindings→.unresolved→ LLVM panic. Fix: mirrorresolveTypeArg'scomptime_pack_refarm (look uptype_bindings, else a loud diagnostic). Regressionexamples/generics/0216-generics-typearg-in-pack-fn-body.sx. (Part 2 — deferred..spread crashes — reframed OPEN/non-blocking,issues/0156.) - issue 0157 — a user generic
ufcsmethod whose name collides with a stdlib re-export (cancelon*Taskvs io.sx'scancelon*Future) resolved via last-winsfn_ast_mapwith NO receiver filtering → wrong overload →$Runbound → LLVM panic. Fix (src/ir/lower/call.zigselectUfcsGenericByReceiver): every generic-ufcs dispatch enumerates ALL module authors (module_decls), keeps receiver-binding ones, picks the most receiver-SPECIFIC (concrete > bare$T), dedups re-exports, and flags a genuine 2-specific tie as a deterministic "ambiguous — qualify" diagnostic (never a silent order-dependent pick). Regressionexamples/generics/0217-generics-ufcs-method-name-collides-stdlib.sx.
- issue 0156 Part 1 — a single-type generic
- Adversarial review (worker) of the 0157 fix + Task layer. Caught the determinism CRITICAL
(fixed: always-run selection + specificity + ambiguity),
wait-outside-a-fiber null-deref (fixed: loud guard insuspend_self/yield_now), and cancel-doesn't-skip-work (fixed: worker skipswork()if already canceled). Lost-wakeup / cancel-after-complete / reap traced safe. Also simplified1812(**Fibershared handle → aSh.parkedfield; output identical). - Suite GREEN 751/0 (749 + 1813 + 0217). Next: B1.4b (deterministic-sim
Io).
Earlier — B1.5a — the M:1 cooperative fiber scheduler CORE — landed + adversarially reviewed
The hand-bootstrapped ping-pong (1807-1810) is now a reusable scheduler API in pure sx:
library/modules/std/sched.sx — a generic Fiber (body: Closure() -> void) + Scheduler
with init/spawn/yield_now/suspend_self/wake/run over the proven swap_context on
guarded mmap stacks. The ONE generic dispatch (fib_dispatch, reached from the _fib_tramp
trampoline) runs ANY stored closure body on a fresh stack — replacing the fixed bl _fib_body.
Reaping munmaps the stack + frees the heap Fiber on completion; an intrusive FIFO gives
round-robin order.
- Foundational design de-risked by probe before building: a fiber can store + call a
Closure() -> voidon its fresh stack via the generic dispatch; outputs flow OUT through pointers captured in the closure (capture-by-value does NOT write back — pushed onto the user). - Hit + FIXED a blocker compiler bug — issue 0154 (user-authorized in-session fix).
null/---assigned to a struct field picked up a leaked enclosingtarget_type(the function's RETURN type, set for the whole body at decl.zig:2691) and built a WHOLE-STRUCT-typed null → an oversizedzeroinitializerstore through the field's GEP that overran the field's slot and clobbered the saved x29/x30, so the fnret'd to 0x0. This was EXACTLY theScheduler.init()by-value-return shape (sched_ctx: [13]u64beforecurrent: *Fiber). Fix: added.null_literal, .undef_literalto theneeds_targetswitch inlowerAssignment(src/ir/lower/stmt.zig) so the field's type is used. Repro → regression testexamples/types/0193-types-sret-array-before-pointer.sx;issues/0154-*.mdRESOLVED. - Adversarial review (worker): asm/bootstrap/lifetime SOUND (the headline closure-env-lifetime
fear was disproven — envs are heap-promoted, survive the spawn frame). Found 1 CRITICAL +
robustness gaps, ALL hardened: (CRITICAL)
wakere-enqueued an already-queued fiber → FIFO corruption/segfault → now GUARDED on.suspended(spurious/double/stale wake = safe no-op); orphan-suspend leak/deadlock →n_suspendedaccounting + a loudrun()-drain diagnostic+abort;mmapMAP_FAILED(=-1, not null) /mprotect/ Fiber-OOM → loud bails (per §8.1.1 the guard is mandatory); the per-fiber closure-env leak (sx exposes no env-free) → documented as a KNOWN LIMITATION (bounded by spawn count; invisible under the default GPA). - Locked two
18xxexamples (aarch64-macos.build-pinned, ir-only on a mismatch):1811-concurrency-fiber-scheduler.sx(3 fibers round-robin viayield_now→ ordering contractsequence: 0 1 2 0 1 2 0 1 2, all.done) +1812-concurrency-fiber-suspend-wake.sx(park viasuspend_self, resumed by another fiber'swake, + the spurious-wake no-op — the CRITICAL-fix regression →log: 10 20 21 11/suspended-left: 0). - Filed issue 0155 (NON-blocking, NOT fixed) — found incidentally in the review: indexing a
scalar pointer (
pc[0],pc: *i64) panics codegen (.unresolvedreaching LLVM emission). The scheduler uses array-field indexing +.*, never this, so it's filed for its own session. - Suite GREEN 748/0 (746 base + 1811 + 1812 + 0193 regression). Next: B1.4a (FiberIo —
wire
Io.spawn_raw/suspend_raw/readyonto the scheduler soasync/awaittruly suspend).
Earlier — B1.3b-1 — the x86_64 / Win64 swap_context sibling — VALIDATED on real hardware
The
context switch is now proven on a SECOND architecture + ABI. A Win64 swap_context saves the
COMPLETE Win64 callee-saved set — 8 GP (rbx, rbp, rdi, rsi, r12-r15) + rsp and xmm6-xmm15
(10 XMM, 128-bit via movups — Win64 has callee-saved XMM, unlike SysV/aarch64) — plus a Win64
scribble_verify (32-byte shadow + 16-align at each call, COFF symbols, rsp-carried return
addr). Locked by examples/1810-concurrency-fiber-switch-win64.sx (pinned x86_64-windows-gnu,
ir-only here): the 2-fiber mutual scribble printed 0 0 P when built --target x86_64-windows-gnu --self-contained and run on a Windows 7 x64 VM (UTM) — every GP + XMM
callee-saved survived. Adversarially reviewed before the VM run (worker emitted the real .s
and verified every call alignment, the 264-byte frame offsets, the rsp/return-addr round-trip,
swap ordering, and COFF naming against the Win64 ABI — no critical/minor bugs). The build→VM→run
loop was set up this session (cross-build needs --self-contained; output via the Win32
WriteFile boundary, the 1660 pattern). Suite green. Note: this is the GOOD-swap-only mutual
scribble (self-validating by construction; the in-process negative control was dropped to avoid an
sx fn-ptr-convention rabbit hole — the detection of this exact logic was negative-controlled on
aarch64 in 1808). The SysV/Linux x86_64 sibling (different reg set: no callee-saved XMM, args
rdi/rsi) remains for a Linux x86_64 host.
Earlier — B1.3b-2 — mmap guard-page stacks (commit dd532ab)
Fiber stacks are mmap'd with a PROT_NONE GUARD PAGE at the low end (§8.1.1: a
fixed stack without a guard silently corrupts neighbors on overflow). mmap the [guard | usable] region, mprotect the low 16KB page PROT_NONE; SP descends into the guard and faults
loudly at the boundary instead of corrupting a neighbor. Locked by
examples/1809-concurrency-fiber-guard-stack.sx (aarch64-macos-pinned): guard armed: 1
(mprotect→0) + sum: 20100 (a fiber runs real recursion on the guarded stack + yields).
- Guard FIRING validated (manually, not corpus-pinned — a deliberate overflow crash is
host-fragile): a fiber recursing past its 128KB stack faults with
Bus errorat the guard page (region+GUARD); the sx crash handler turns it into exit 134. Documented in the example header. - x86_64 sibling: was deferred here (couldn't run x86_64 on this arm64 host), then DONE as
Win64 once a Windows 7 x64 VM became available — see B1.3b-1 above (
examples/1810,0 0 P).
Earlier — B1.3a-2 — the context-switch STRESS GATE (design §10.7) — DONE + adversarially reviewed
The explicit every-callee-saved-register scribble that B1.3a-1 owed. swap_context now saves the
COMPLETE AAPCS64 callee-saved set — integer x19-x28 + fp/lr + sp AND FP d8-d15 (per §6.1.2
only the low 64 bits of v8-v15 are callee-saved, so d8-d15 is exactly sufficient; x18 is Apple's
reserved platform reg, untouched). A naked scribble_verify(self_ctx, peer, base) loads a unique
sentinel into all 18 callee-saved regs, yields, and on resume counts the ones that didn't survive
(honoring its own caller ABI via a 176-byte frame that saves+restores the caller's callee-saved;
base reloaded from the frame post-swap; the original lr round-trips through the swap). The gate is
a 2-fiber MUTUAL scribble (A and B scribble DISTINCT sentinels into the same physical regs, so
each survives only if swap_context saved+restored it — a lone fiber yielding to an idle peer
would NOT exercise preservation). Locked by examples/1808-concurrency-fiber-switch-stress.sx
(aarch64-pinned): A mismatches: 0 / B mismatches: 0.
- Validity proven by NEGATIVE controls: dropping the d8-d15 save/restore → 8/8 mismatches (exactly the FP regs); dropping x27/x28 → 2/2. The gate genuinely catches a broken switch.
- Adversarial review (worker, per the plan): no CRITICAL bugs. Verified the callee-saved set
is complete + correct, all frame offsets/16-alignment, the lr/sp dance, and swap read-ordering
against AAPCS64. Applied its one recommendation:
bootnow zeroes the FP ctx slots [13..20] so a first switch-to loads 0 (not garbage) into d8-d15. Residual gaps it flagged (all spec-correct for a call-boundary swap, documented in the example header): NZCV/FPSR not swapped; FPCR (rounding mode — thread-global, bleeds across fibers if changed) and TPIDR_EL0/TLS (errno, allocator thread-caches — shared by same-thread fibers) not swapped; fp=0 bootstrap blocks unwind/signal walking past a fiber trampoline. These bite at the N×M:1 / signals stages, not the single-thread switch. - Suite green 734/0, master clean. WIP probes:
.sx-tmp/scribble2.sx(+_broken/_gp).
Earlier — B1.3a-1 — the foundational stackful context switch (commit b234b7d)
Pure sx over abi(.naked): naked swap_context (GP-only 13-slot save) + by-hand fiber bootstrap
(SP = alloc_bytes stack top, LR = global-asm trampoline, x19 = *Fiber). Locked by
examples/1807-concurrency-fiber-context-switch.sx: 2-fiber ping-pong (rounds: 6 / canary fails: 0) + 64-frame deep recursion (frames verified: 64 / depth fails: 0). Indirect
register/stack survival; 1808 supersedes its switch with the complete GP+FP save area + the
explicit gate.
Earlier — B1.2 COMPLETE — the async surface works end-to-end
All three surface blockers (0151, 0152, 0153) FIXED + committed; async examples landed + green.
- 0151 fixed (
362674f): generic$Tinfers through generic-struct / pointer / UFCS-pack params. Regression0214+0215. - 0152 fixed (
e5586f6):Atomic(bool)load/store byte-promoted toi8in the codegen emitters. Regression1705. - 0153 fixed (
68c1991):inferGenericReturnTypenow pins return-type resolution to the fn's DEFINING module (mirroringmonomorphizeFunction), so a re-exported value-failable's!Eresolves to the real.error_setTypeId — the failable channel survives the re-export alias. Regression1058-errors-reexport-value-failable-channel.sx. - Async examples landed:
examples/1805-concurrency-io-blocking-async.sx(context.io.async((a,b)->i64 => a+b, 40, 2).await() or {…}→sum: 42/double: 42/clock ok) +examples/1806-concurrency-io-cancel.sx(f.cancel()→awaitraises.Canceled→ordefault;ok: 7/canceled: -99). Both green, snapshots captured.
Earlier — the three B1.2 surface fixes (committed)
Generic $T inference, Atomic(bool) byte-promotion, and re-export failable-channel pin —
details below.
- 0151 fix (committed): four gaps closed on the inference + UFCS-dispatch path —
(1)
extractTypeParam/matchTypeParam(Static)got aparameterized_type_exprarm (recover the arg instance's recorded per-param bindings viastruct_instance_bindings+ the template's orderedtype_params, recurse positionally; this also fixes*Box($T)— it recurses into itsBox($T)pointee); (2) thepointer_type_exprarm now falls through to match the pointee against a non-pointer arg (auto-address-of: a*Box($T)param accepts a by-valueBox($T), e.g. a UFCS receiverb.m()); (3)ExprTyper.inferTypegot a.lambdaarm building the closure type from the lambda's annotations (the UFCS binder types args from the raw AST before they're lowered, so it can now bindClosure(..) -> $Rfrom the worker's declared return type); (4) a pack UFCS target routes through the SAMElowerPackFnCallthe direct call uses, with the receiver spliced in asargs[0]. - Regression tests:
examples/0214-generics-ufcs-closure-return-pack.sx(direct + UFCS closure-return pack) +examples/0215-generics-infer-through-pointer.sx(by-value / pointer / multi-param / nested / UFCS-auto-ref struct-head inference). Issue 0151 marked RESOLVED; repro moved into the suite.
Earlier — B1.2 (Io capability) — LANDED + adversarially reviewed
Commits a1b14f0 (lock) + 45d869d (Io capability) + 3eeb965 (issue 0151 lock).
- LANDED + review-confirmed correct (commit
45d869d):Io :: protocol #inline(spawn_raw/suspend_raw/ready/poll/now_ms/arm_timer) +iofield onContext({allocator; data; io}, io LAST); BOTH__sx_default_contextmaterializers (protocol.zig + comptime_vm.zig) build an identical CBlockingIo→Io vtable (review verified byte-for-byte agreement;context.io.now_ms()dispatches at runtime AND comptime); thepush Context.{…}omitted-field-inherits-ambient fix (review: correct, right fix, no bad blast radius);library/modules/std/io.sx(Future($R),CBlockingIo,async/await/cancel); the!-protocol-impl-lint suppression; 37.irregens (review: pure layout/type-table, no error text, zero .exit/.stdout/.stderr change). - BLOCKED — async surface non-functional:
await/canceltake*Future($R)and are uncallable in EVERY form (not just UFCS) — sx can't infer a generic$Tfrom a pointer-wrapped arg (*Future($R)).async(...)(create) works via explicit call and produces a correct.readyFuture, but you can'tawaitit. Root bug = issue 0151 (WIDENED): infer$Tfrom*T-wrapped params + closure-return-via-pack + UFCS dispatch. Minimal repro:unbox :: (b: *Box($T)) -> $Tfails to inferT. - No async example in the corpus (1805 was removed because it needs the blocked surface)
→ the green suite does NOT cover async. Restore
1805(async/await) + add1806(cancel) once 0151 is fixed.
Earlier — B1.1 (per-fiber context root) — DONE. Zero compiler change (confirmed by probe).
The fiber-spawn context convention works end-to-end with ordinary language features:
snap := contextcaptures the spawner'sContextas a value;- the snapshot is stored in a struct (the stand-in
Fiber); - a trampoline running under a different ambient context installs the fiber's stored root
with
push f.root { … }, and the body reads the snapshot — not the trampoline's ambient context — becausecontextis an implicit slot-0*Contextparam (call-carried, rides the callee's own stack) andpushallocates on the caller frame (no global, no TLS). - Locked by
examples/1804-concurrency-context-snapshot.sx: printsfiber root: 42(the installed snapshot wins over ambient 99) +ambient after: 99(thepushscope restores the ambient context on exit). No fiber runtime yet (that's B1.3) — this proves the plumbing it will build on. No.buildpin (pure sx, host-independent). - Probe result: the design doc's "lower as swappable indirection, never raw TLS" guarded
a non-problem — context was already param-carried, never TLS. No path re-reads
__sx_default_contextmid-stack, so there is no compiler obligation here. zig build && zig build testgreen: 726 ran, 0 failed.
Earlier — B1.0 (abi(.naked) codegen) — complete
Replaced the emit bail with real LLVM naked emission:
emit_llvmdeclaration pass: forfunc.is_naked, add the LLVMnaked+noinline+nounwindattributes and skip theframe-pointer=allattribute (incompatible with a frameless function). Pass 2 now emits the.nakedbody normally —nakedmakes the backend emit it verbatim (the inline asm + its ownret) 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() -> i64call (.nakediscall_conv == .default). examples/1800-concurrency-naked-asm.sx— now GREEN, aarch64-pinned (.build {"target": "macos"}): runs end-to-end → exit 42 on this host, ir-only on a mismatch;.irsnapshot captured.examples/1801-concurrency-naked-generic.sx(renamed from-bail) — the generic.nakednow emits a correct nakedanswer__i64(exit 42), proving generic.zig produces a naked body, not a framed one. aarch64-pinned.examples/1802-concurrency-naked-asm-x86.sx— x86_64 cross sibling (.build {"target": "x86_64-linux"}, ir-only here):.irlocksnaked+movl $42, %eax/ret.- Unit test
emit: abi(.naked) function gets the naked attribute (no frame-pointer)inemit_llvm.test.zig(assertsnakedpresent,frame-pointerabsent). - B1.0c (review-hardening): a param-bearing
.nakedfn 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 onfd.abi != .nakedin decl.zig (both paths) + generic.zig; a naked fn's args stay in registers (read by asm), declared-but-unused in LLVM. Locked byexamples/1803-concurrency-naked-asm-param.sx(add(a,b)→ x0+x1 → 42). zig build && zig build testgreen: 725 ran, 0 failed + unit tests.
Earlier — B1.0a (lock + review hardening)
Plumbed Function.is_naked (set from fd.abi == .naked at both decl sites + generic.zig +
pack.zig); funcWantsImplicitCtx skips .naked (no synthetic ctx, like .c); all
body-lowering paths bypass lowerValueBody for .naked (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_naked gap (a generic .naked silently shipped a framed
body); closed + locked. The review's .naked-lambda CRITICAL was a false positive
(unparseable — isLambda breaks on the abi keyword).
Current state
B1.4a COMPLETE — truly-suspending fiber-task async exists. library/modules/std/sched.sx carries
the M:1 scheduler core (B1.5a) PLUS the async-task layer: Task($R) + Scheduler.go(work) -> *Task($R) + wait/cancel. s.go(work) spawns a nullary thunk as a fiber; t.wait() suspends
the caller until it completes. Locked by 1813 (sequence: 1 2 3 42 100 -99 — real interleave +
awaited values + cancel). Two compiler blockers fixed en route (0156 Part 1 — $R type-arg in a
pack-fn; 0157 — UFCS generic name collision), both regression-tested (0216, 0217). Adversarially
reviewed; determinism + non-fiber-wait + cancel-skip-work all hardened. The io.sx blocking
context.io.async (1805/1806) is untouched and coexists. Suite GREEN 751/0.
The remaining B1.4 work: B1.4b the deterministic-sim Io (virtual clock + timer min-heap,
calibrated against blocking — the KEYSTONE test harness), B1.4c the event-loop Io
(kqueue/epoll). Then B1.5 end-to-end M:1 validation under the deterministic Io. NOTE: the
suspending async lives as sched.go/wait (M:1, receiver-driven), NOT routed through the erased
context.io (which would force sched.sx into every std consumer + duplicate the _fib_tramp global
asm); the Io protocol's spawn_raw/suspend_raw/ready remain reserved for the M:N evolution.
Earlier — B1.5a COMPLETE — the M:1 scheduler CORE exists
library/modules/std/sched.sx drives N fibers
(generic Closure() -> void bodies) cooperatively over the proven swap_context, on guarded
mmap stacks: spawn / yield_now (round-robin) / suspend_self + wake (off-queue park/resume)
/ run (drives to drain, reaps on .done). Adversarially reviewed + hardened (wake guarded, loud
mmap/mprotect/OOM/deadlock bails, env-leak documented). Locked by 1811 (round-robin ordering
contract) + 1812 (suspend/wake park-resume + spurious-wake guard). Suite GREEN 748/0.
The remaining B1.4 work wires this scheduler under the Io capability: B1.4a (FiberIo) makes
context.io route spawn_raw/suspend_raw/ready onto the Scheduler so async/await truly
SUSPEND (today's CBlockingIo runs the worker to completion inline); B1.4b the deterministic-sim
Io (virtual clock + timer queue, calibrated against blocking — the KEYSTONE test harness);
B1.4c the event-loop Io (kqueue/epoll). Then B1.5 is the end-to-end M:1 validation under
the deterministic Io.
Earlier — B1.2 COMPLETE
The full async surface (Io capability on Context + async/await/cancel +
blocking CBlockingIo) works end-to-end. Master GREEN (732/0), installed sx clean. All four
B1.2 surface bugs resolved or deferred:
- 0151 fixed (
362674f): generic$Tthrough generic-struct / pointer / UFCS-pack params. Regression0214+0215. - 0152 fixed (
e5586f6):Atomic(bool)byte-promoted toi8in the load/store emitters. Regression1705. - 0153 fixed (
68c1991):inferGenericReturnTypepins return-type resolution to the fn's defining module, so a re-exported value-failable keeps its!channel. Regression1058. - Issue 0150 (
voidstruct field → SIGTRAP) DEFERRED — onlyFuture(void)/timeout, which are B1.4.
The async examples are landed + green: 1805 (async/await + now_ms → sum: 42 /
double: 42 / clock ok) + 1806 (cancel → await raises .Canceled → or default).
The 18xx concurrency category now covers naked-asm (1800-1803), context-snapshot (1804), and
the async surface (1805-1806).
B1.2 Io capability — what is LANDED + verified (commit 45d869d)
Io :: protocol #inline { spawn_raw; suspend_raw -> !; ready; poll; now_ms; arm_timer; }incore.sxnext toAllocator, withSpawnOpts{ pin: PinTarget }+ParkToken{ handle }. Six methods, each justified by a downstream consumer (B1.3-B1.5).Context :: struct { allocator; data; io: Io; }—ioappended LAST soallocatorstays index 0 (thecall.zig:1229hardcode) anddatakeeps index 1 (minimal VM-fallback churn).- Both
__sx_default_contextmaterializers updated in lockstep + verified:protocol.zigemitDefaultContextGlobal(extendedctx_fields2→3, built theCBlockingIo→Ioinline 7-word vtable{null-ctx, fn0..fn5}viagetOrCreateThunks("Io","CBlockingIo")) andcomptime_vm.zigmaterializeDefaultContextfallback (wrote the 6 thunk func-refs atio_base = addr + 4*ps, offset+ (i+1)*ps). The global path auto-followed the 3-field Context type.context.io.now_ms()printedclock oklive — the capability threads + the vtable dispatches correctly. - Stateless
CBlockingIo :: struct {}+impl Io for CBlockingIo(mirror ofCAllocator): blocking semantics —spawn_raw/ready/poll/arm_timerno-op/0,now_ms→time.mono_ms(). - push-inherit-omitted fix (
stmt.ziglowerPush): apush Context.{...}now SEEDS the new slot from the ambient context (load+store), then overwrites ONLY the literal's named fields — so omitted fields (now incl.io) are INHERITED, never zero-inited to a null vtable. Eliminates the omitted-field footgun globally (zero per-site churn across the 17 partial-literal sites). This is the correct capability-bag semantics; it compiled clean. !-protocol-method warning fix (error_analysis.zig+ a newLowering.impl_method_namesset populated inprotocols.zigregisterImplBlock): a protocol impl method may be declared!by contract (e.g.Io.suspend_raw) yet never raise; the "declared!but never errors — drop the!" hint is a false positive for impl methods, now suppressed for them.
Status of the blockers that originally stopped B1.2:
- issue 0151 — FIXED this session (generic
$Tthrough generic-struct / pointer / UFCS-pack params).async/await/cancelare callable. See "Last completed step". - issue 0152 — NEW, the current blocker (
Atomic(bool)→ sub-byte i1 atomic; LLVM reject). Blocks the async examples viaFuture.canceled: Atomic(bool). Filed; codegen-level fix. - issue 0150 —
voidstruct field SIGTRAP; onlyFuture(void)/timeout(B1.4). DEFERRED.
Per the IMPASSABLE STOP rule: 0151 fix shipped (suite green 728/0), 0152 filed, STOPPED. Resume B1.2's async examples once 0152 lands.
Earlier — B1.0 + B1.1 complete
Stream A (atomics) is feature-complete (✅). Stream B1: B1.0 + B1.1 complete. The two
compiler-floor preconditions for the fiber runtime are in place: (1) abi(.naked) emits a
real LLVM naked function end-to-end (decl, generic, pack paths) — the context-switch
substrate; (2) per-fiber context root needs no compiler change — the spawn convention
(snapshot context, store, push it from the trampoline) is pure library sx. No
fibers/Io/scheduler code yet. Grounded floor facts:
contextis an implicit slot-0*Contextparam +push Contextis a stackalloca⇒ fiber-local for free (confirmed by the B1.1 probe — never TLS, never re-read from the__sx_default_contextglobal mid-stack). A spawn passes the snapshot as the fiber-entry fn's slot-0 ctx viapush f.root { entry(args) }. Locked by1804-...-context-snapshot.- Inline asm works end-to-end (lower→emit→JIT, aarch64 + x86_64) — the
.nakedbody reuses it. .nakedwith PARAMS works (B1.0c, the B1.3 substrate): the param-alloca loop is gated onfd.abi != .nakedin 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). Example1803-concurrency-naked-asm-param.sx(add(a,b)reads x0/x1). Unsupported (loud, not silent): a.nakedvariadic-pack fn (pack.zig's param loop is intertwined with comptime-param/#inserthandling, 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.4b — the deterministic-sim Io (the KEYSTONE test harness). B1.4a (suspending fiber-task
async, sched.go/wait) is done. Now build a deterministic Io impl: a virtual clock (now_ms
returns simulated time), a timer min-heap (arm_timer schedules a wake at a sim deadline), and
poll advances the clock to the next due timer and wakes its parked fiber. Drive it over the M:1
scheduler so a program using sim-time sleeps/timeouts runs fully deterministically. Calibrate it
against blocking Io (§8.1.3): the same program under blocking vs deterministic Io must produce
the same observable result before the deterministic one is trusted to gate async tests. Lock with an
18xx example asserting a program-emitted ORDERING contract (sim-time scheduling), aarch64-pinned
(.build {"target":"macos"}). This harness gates B1.5 + Stream B2.
Then: B1.4c event-loop Io (kqueue mac / epoll linux — real fd readiness), B1.5 end-to-end
M:1 validation under the deterministic Io. The §10.7 gate (1808) + guarded-stack (1809) + Win64
(1810) + scheduler (1811/1812) + async (1813) must keep passing throughout.
Open design question for B1.4b/c: a deterministic/event-loop Io needs a current-Scheduler
handle to park/wake. sched.go/wait thread it via the Task; an Io impl that wants the same
will likely need an ambient current-scheduler accessor in sched.sx (deferred from B1.4a — the
Task-threaded form sufficed). Decide when wiring arm_timer → a parked fiber.
Side thread (optional, low priority): the SysV/Linux x86_64 sibling. A THIRD switch variant
for x86_64-linux: SysV callee-saved = rbx, rbp, r12-r15 + rsp (6 GP + sp; no callee-saved
XMM, unlike Win64) — a 7-slot ctx, args rdi/rsi/rdx, the rsp-carried return addr. Needs a Linux
x86_64 host (or a working cross-run) to RUN + the mutual-scribble gate. Not blocking — the switch
is already validated on two arch/ABI pairs.
Deferred (do NOT block on these): issue 0150 (void struct field SIGTRAP) — only
Future(void)/timeout (B1.4). The :: callable-parameter feature (named-fn async workers
async(read_a, conn)) — WIP at .sx-tmp/wip-callable-params/patch.diff (parser done, inference
incomplete); a dedicated effort; lambda workers are the idiom meanwhile.
Context layout settled: { allocator; data; io; } (allocator index 0 fixed by
call.zig:1229, io last). Io protocol + materializers + push-inherit are LANDED + reviewed.
Known issues / capability gaps
- issue 0157 (OPEN, BLOCKING B1.4a) — a user-defined generic ufcs method whose NAME collides
with a stdlib re-export (
cancel, re-exported bystd.sxfromio.sxasufcs (f: *Future($R))), called via UFCS on a different generic struct (*Task($R)), leaves$Runresolved →.unresolvedreaches LLVM emission → panic (src/backend/llvm/types.zig:196). Renaming → works; the non-UFCS call form already diagnosescannot infer generic type parameter 'R', so the UFCS path skips that diagnostic. Surfaced bycancel :: ufcs (t: *Task($R))instd/sched.sx. Minimal repro (no fibers/closures):issues/0157-ufcs-generic-method-name-collides-stdlib-unresolved.{md,sx}. - ✅ issue 0154 — FIXED (
null/---to a struct field over-stored a whole-struct null when the function's return type leaked astarget_type, corrupting the frame →retto 0x0; surfaced buildingScheduler.init()'s by-value return). Fix:.null_literal/.undef_literaladded toneeds_targetinlowerAssignment(src/ir/lower/stmt.zig). Regression:examples/types/0193. - issue 0155 (OPEN, NON-blocking) — indexing a scalar pointer (
pc[0],pc: *i64) panics codegen (.unresolvedreaching LLVM emission,src/backend/llvm/types.zig:196). Found in the B1.5a review; the scheduler doesn't use it (array-field index +.*only). Filed for its own session:issues/0155-scalar-pointer-index-llvm-panic.{md,sx}. - ✅ issue 0157 — FIXED (B1.4a) — a user generic
ufcsmethod whose name collides with a stdlib re-export resolved via last-winsfn_ast_mapwith no receiver filtering → wrong overload →$Runbound → LLVM panic. Fix:selectUfcsGenericByReceiver(src/ir/lower/call.zig) — most receiver-specific binding author across ALL module authors, deterministic, ambiguity-diagnosing. Regression:examples/generics/0217. - ✅ issue 0156 Part 1 — FIXED (B1.4a) — single-type generic
$Ras a type-arg in a pack-fn body (Box($R)/size_of(Box($R))) →.unresolved→ panic. Fix:comptime_pack_refarm inresolveTypeWithBindings. Regression:examples/generics/0216.- Part 2 (OPEN, NON-blocking) — a deferred
..spread (a comptime pack captured into a closure, or a tuple..tspread) crashes instead of working/diagnosing. The fiber async layer avoids it by design (nullary thunks), so it's filed for its own session:issues/0156.
- Part 2 (OPEN, NON-blocking) — a deferred
- Heap leaks in the fiber runtime (documented limitations, NOT bugs):
spawn's closure env +go's heapTaskare never freed (sx exposes no closure-env free; Task ownership is deferred). Bounded by spawn/go count, invisible under the default GPA. Revisit for a long-running arena-backed scheduler. - ✅ issue 0153 — FIXED (re-exported generic value-failable
($R, !E)kept its!channel:inferGenericReturnTypenow pins return-type resolution to the fn's defining module). Regression:examples/1058. Was the LAST B1.2 surface blocker. - ✅ issue 0152 — FIXED (
Atomic(bool)sub-byte i1 atomic → byte-promoted to i8 in the load/store emitters). Regression:examples/1705. UnblockedFuture.canceled. - ✅ issue 0151 — FIXED (generic
$Tthrough generic-struct / pointer / UFCS-pack params). Regression:examples/0214+0215. Was the original B1.2 surface blocker. - issue 0150 (deferred) — a
voidstruct field crashes the compiler (unsized-type SIGTRAP in LLVMgetTypeSizeInBits). BlocksFuture(void)→timeout(B1.4). Repro:issues/0150-....- (Note: issue 0149, filed by another session against an earlier dirty binary, was a manifestation of the pre-fix 0151 — now moot.)
- 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(.naked)naked [B1.0], atomics ✅) + fiber-safe codegen (contextalready fiber-local — B1.1). Schedulers, fibers, channels, futures,Iovtables,mmapstacks are all sx. abi(.naked)is the real spelling of the design'scallconv(.naked)— postfix slot,name :: (sig) -> Ret abi(.naked) { 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)..naked≠.c: a.cepilogue 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 ownret. This is why the switch must be.naked.- Naming: sx-facing name is
naked(keywordabi(.naked), fieldis_naked, the diagnostic), matching LLVM'snakedattribute and the industry term (Zig/Rust/GCC/Clang). The ABI variant was renamed.pure → .naked(user direction): "pure" universally means side-effect-free, the opposite of a register-clobbering context switch. - B1.0 snapshot scope: a
.nakedbody 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 — per-fiber context is library-only (CONFIRMED by probe): push frames are
stack-
alloca'd and the implicit ctx rides slot 0, so the spawn convention — snapshotcontext, store it,push f.root { entry(args) }from the trampoline — installs the fiber's root with no compiler change. Verified: the body reads the snapshot over a different ambient context, andpushrestores ambient on exit (1804-...-context-snapshot). The design doc's "never raw TLS" guarded a non-problem (context was never TLS). - 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.nakedinert (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(.naked)spelling and the B1.0 snapshot story. B1.1 grounded as likely library-only. Baseline green (721/0). - B1.0a — plumbed
Function.is_naked(set fromfd.abi == .nakedat both decl sites);funcWantsImplicitCtxskips.naked(no implicit ctx, like.c); both body-lowering paths bypasslowerValueBodyfor.naked(asm body +unreachablecap — no sx return);emit_llvmPass 2 bails loudly onfunc.is_naked.examples/1800-concurrency-naked-asm.sxlocked to the bail (exit 1 + diagnostic). Suite green (722/0). (ABI variant later renamed.pure → .naked— see the Naming decision above — so allis_*/abi(.*)/example names here readnaked.) - B1.0a review-hardening — adversarial review found generic/pack Function-creation paths
left
is_nakedfalse (silent framed body for a generic.nakedinstance — returned 42 but corrupted the stack). Fixed generic.zig + pack.zig (setis_naked+ asm-onlyunreachablecap); locked byexamples/1801-concurrency-naked-generic-bail.sx. The review's.naked- lambda CRITICAL was a false positive (unparseable —isLambdabreaks onabi). Suite green (723/0). - B1.0b — real
nakedemission: emit_llvm declaration pass adds LLVMnaked/noinline/nounwind+ skipsframe-pointerforfunc.is_naked; Pass 2 emits the body verbatim (no prologue).1800green aarch64-pinned (exit 42 +.ir); renamed1801→-generic(generic.nakedemits a naked body, exit 42); added x86_64 sibling1802(ir-only,.irlocksnaked+movl $42, %eax). Unit test assertsnakedpresent +frame-pointerabsent. Suite green (724/0). - B1.0c — review-hardening: param-bearing
.nakedemitted invalid LLVM (loud verifier error). Gated the param-alloca loop onfd.abi != .naked(decl.zig both paths + generic.zig) — naked args stay in registers, read by the asm body (the B1.3 context-switch shape). Locked byexamples/1803-concurrency-naked-asm-param.sx. Pack.nakedleft unsupported (loud, nonsensical). B1.0 complete. Suite green (725/0). - rename — ABI variant
.pure → .naked(keyword,Function.is_naked, diagnostics, examples 1800-1803*-pure-* → *-naked-*, docs). "pure" universally means side-effect-free — wrong for a register-clobbering switch; "naked" matches LLVM/Zig/Rust/GCC/Clang. Pure cosmetics, no semantic change. Suite green (725/0). - B1.1 — per-fiber
contextroot: zero compiler change (probe-confirmed). The spawn convention (snapshotcontext→ store in a struct →push f.root { entry() }from the trampoline) installs the fiber's root via the implicit slot-0*Contextparam; the body reads the snapshot, not the trampoline's ambient ctx, and thepushscope restores ambient on exit. Locked byexamples/1804-concurrency-context-snapshot.sx(printsfiber root: 42/ambient after: 99). Suite green (726/0). Next: B1.2 (Io interface + context.io). - B1.2 (BLOCKED) — built the full
Iocapability (protocol onContext, statelessCBlockingIoblocking default, both__sx_default_contextmaterializers, push-inherit-omitted fix,!-impl-method warning fix) and VERIFIED the core works live (context.io.now_ms()→clock ok). Two independent compiler bugs blocked theasync/await/timeoutlayer: 0150 (voidstruct field → unsized SIGTRAP, blocksFuture(void)) and 0151 (type-var from a fn-ptr param's return type not bound in the body, blocksasync'sFuture(R)). Both filed with standalone repros + investigation prompts. Per the STOP rule: reverted ALL B1.2 working changes (master green again, 726/0; the dirty binary had broken the photo project — see the now-moot 0149), saved WIP to.sx-tmp/b12-wip/, STOPPED. Resume after 0150 + 0151. - 0151 FIXED — generic inference now binds
$Tthrough a generic-struct param head, a pointer (*Box($T), incl. UFCS auto-ref), and a closure-return-via-pack on the UFCS path. Four gaps closed:parameterized_type_exprarm inextractTypeParam/matchTypeParam(Static)(recovers the arg instance's recorded per-param bindings, recurses positionally); pointer arm falls through to match a value arg (auto-address-of);ExprTyper.inferType.lambdaarm (closure type from annotations — UFCS types args from raw AST pre-lowering); pack UFCS target routes throughlowerPackFnCallwith the receiver spliced in asargs[0]. Issue 0151 marked RESOLVED; repro →examples/0214-generics-ufcs-closure-return-pack.sx; widened cases →examples/0215-generics-infer-through-pointer.sx. Suite green 728/0. The now-callable async surface immediately exposed a SEPARATE codegen bug — issue 0152 (Atomic(bool)→ sub-byte i1 atomic, LLVM reject;Future.canceledhits it). Filed with standalone repro + fix prompt. Per the STOP rule: shipped the 0151 fix, filed 0152, STOPPED. Resume the async examples (1805/1806) after 0152. - 0152 FIXED — the atomic load/store emitters (
src/backend/llvm/ops.zig) byte-promote a sub-byte (bool→i1) access to itsi8storage type andtrunc/zextthe value at the boundary (newatomicByteTypehelper). rmw/cmpxchg left as-is (aboolrmw/CAS is rejected at the sx level — integer-only — so a sub-byte element never reaches them; comments record this). Regressionexamples/1705-atomics-bool-byte-promoted.sx(load/store round-trip). Issue 0152 marked RESOLVED. Suite green 729/0. WithAtomic(bool)working, the async surface exposed the TRUE remaining blocker — issue 0153: a re-exported generic value-failable($R, !E)loses its!channel at the call site (the earlier "secondaryorPHI" symptom was this, NOT anAtomiccascade — confirmed it persists after 0152). Narrowed to the generic+re-export co-requirement (non-generic re-export OK; direct generic import OK; only the combination drops!). Root cause: the monomorphized return-type's error-set, reached via the re-export alias, resolves to a non-.error_setTypeId, soerrorChannelOf(lower/error.zig:148) misses the channel. Filedissues/0153-...with a minimal co-located 2-file repro + a single-file stdlib-awaitrepro + investigation prompt. Per the STOP rule: shipped the 0152 fix, filed 0153, STOPPED. Resume the async examples after 0153. - 0153 FIXED → B1.2 COMPLETE —
inferGenericReturnType(src/ir/generics.zig) resolved the return-type AST in the CALL-SITE module, so a re-exported error set (LE :: lib.LE) resolved to a non-.error_setalias and the planned call-result was a plain tuple (channel lost). Fix: pin the source tofd.body.source_filearound the return-type resolution, exactly asmonomorphizeFunctiondoes — the!Enow resolves to the real.error_set. One-function change; full suite green (732/0), no regression. Issue 0153 RESOLVED; repro →examples/1058-errors-reexport-value-failable-channel.sx(+ companionlib.sx). With the channel preserved, landed the async examples:1805(async/await+now_ms→sum: 42/double: 42/clock ok) +1806(cancel→awaitraises.Canceled→ordefault;ok: 7/canceled: -99). B1.2 (Io capability + M:1 async surface) is COMPLETE. Next: B1.3 (fiber runtime) on the.nakedcontext-switch substrate. - B1.3a-1 — context switch works. Implemented the stackful switch in pure sx over
abi(.naked):swap_context(from, to)(save callee-saved x19-x28 + fp/lr + sp into*from, load from*to,retontoto's stack) + by-hand fiber bootstrap (SP = top of analloc_bytesstack, LR = a.global _fib_trampglobal-asm trampoline that doesmov x0, x19; bl _fib_body, x19 =*Fiber). Proven via a probe (main↔fiber), then locked byexamples/1807-concurrency-fiber-context-switch.sx(aarch64-pinned): a 2-fiber ping-pong (rounds: 6,canary fails: 0— a per-fiber stack canary survives every switch) + a 64-frame deep recursive chain suspended at the bottom and resumed (frames verified: 64/depth fails: 0). Thebl _fib_bodyreaches the sx body viaexport "fib_body"(the 1655 asm→sx pattern); runs under JIT, ir-only on a non-arm host (.ircaptured —swap_contextshowsnaked noinline nounwind). Suite green 733/0. Honest scope: indirect register/stack survival only; the EXPLICIT every-callee-saved + FP scribble (§10.7) is B1.3a-2, still owed. Next: B1.3a-2. - B1.3a-2 — the §10.7 stress gate, adversarially reviewed. Extended
swap_contextto the COMPLETE AAPCS64 callee-saved set (added FP d8-d15 → 21-slot ctx) and wrote a nakedscribble_verifythat loads a unique sentinel into all 18 callee-saved regs, yields, and counts non-survivors on resume (176-byte frame saves/restores the caller's callee-saved + base; lr round-trips the swap). The gate is a 2-fiber MUTUAL scribble (each clobbers the other's regs, so survival ⇒ the switch saved+restored them). Locked byexamples/1808-concurrency-fiber-switch-stress.sx(A/B mismatches: 0). Validity proven by negative controls (drop d8-d15 → 8/8; drop x27/x28 → 2/2). Spawned an adversarial-review worker (per the plan + user request): NO critical bugs — callee-saved set complete (x18 rightly excluded; d8-d15 suffices per §6.1.2), offsets/alignment/lr-sp dance all verified. Applied its one rec:bootzeroes FP ctx slots so first-entry loads 0, not garbage. Honest residual gaps (spec-correct for a call-boundary swap; in the example header): FPCR/FPSR/NZCV + TPIDR/TLS not swapped, fp=0 blocks unwind — relevant at N×M:1 / signals, not here. Suite green 734/0. Next: B1.3b (x86_64 sibling + mmap guard-page stacks). - B1.3b — mmap guard-page stacks (x86_64 sibling deferred). Fiber stacks now
mmapa[guard | usable]region andmprotectthe low 16KB pagePROT_NONE, so a stack overflow faults at the guard boundary instead of silently corrupting a neighbor (§8.1.1). Locked byexamples/1809-concurrency-fiber-guard-stack.sx(aarch64-macos-pinned):guard armed: 1(mprotect→0) +sum: 20100(a fiber runs real recursion on the guarded stack + yields). Guard FIRING validated manually (overflow →Bus erroratregion+GUARD, exit 134 via the sx crash handler) — not corpus-pinned because a deliberate-overflow crash is host-fragile (and a mere "child faulted" fork test wouldn't prove the BOUNDARY catch). The x86_64swap_contextsibling was DEFERRED:--target x86_64-macosmislinks on this arm64 host andx86_64-linuxcan't run here, so it could only ship un-run/un-negative-controlled — which §10.7 forbids for the highest-risk asm. SysV target notes (rbx/rbp/r12-r15/rsp, no callee-saved XMM, rsp-carried return addr) recorded in Next step. Suite green 735/0. Next: x86_64 sibling (needs an x86_64 host) OR B1.4 (Ioimpls / scheduler) on the proven aarch64 substrate. - B1.3b-1 — x86_64 / Win64 switch sibling VALIDATED on real hardware. The user provided a
Windows 7 x64 VM (UTM), so the x86_64 switch became RUNNABLE (as Win64). Validated the
cross-build→VM→run loop (
--target x86_64-windows-gnu --self-contained→ PE32+; output via the Win32WriteFileboundary, the 1660 pattern). Wrote a Win64swap_context(8 GP rbx/rbp/rdi/ rsi/r12-r15 + rsp + xmm6-xmm15 viamovups— Win64 has callee-saved XMM) + a Win64scribble_verify(264-byte frame, 32-byte shadow + 16-align at eachcall, COFF symbols, rsp-carried return addr) driving the 2-fiber mutual scribble. Adversarially reviewed (worker emitted the real.s, verified every alignment/offset/round-trip against the Win64 ABI — no critical/minor bugs), THEN run on the VM →0 0 P(all 8 GP + 10 XMM callee-saved survived). Locked byexamples/1810-concurrency-fiber-switch-win64.sx(pinnedx86_64-windows-gnu, ir-only on this host; the VM run is the runtime-correctness provenance). Good-swap-only (the in-process negative control was dropped to avoid an sx fn-ptr-convention rabbit hole; the detection of this exact logic was negative-controlled on aarch64 in 1808). Suite green 736/0. The B1.3 context switch is now proven on TWO arch/ABI pairs. Next: B1.4 (Io impls / M:1 scheduler) on the proven substrate. (Side thread: the SysV/Linux x86_64 sibling, when a Linux x86_64 host is available.) - B1.5a — M:1 scheduler CORE + a fixed blocker bug. Built
library/modules/std/sched.sx: a genericFiber/Scheduleroverswap_contexton guardedmmapstacks.spawnheap-allocs a fiber, bootstraps its ctx, enqueues it; the ONE generic dispatch (fib_dispatchvia_fib_tramp) runs ANY storedClosure() -> voidon a fresh stack (replacing the fixedbl _fib_body);yield_nowround-robins,suspend_self/wakepark/resume off-queue,rundrives to drain + reaps.donefibers (munmap+ free). De-risked first by probe (closure-on-fiber + output via captured pointer). Hit blocker bug 0154 (user-authorized fix):null/---to a struct field over-stored a whole-struct null when the fn return type leaked astarget_type, corrupting the frame (ret0x0) — exactly theScheduler.init()by-value-return shape. Fixed instmt.zig(needs_target+=null/undefliterals); regressionexamples/types/0193;0154RESOLVED. Adversarial review: asm/bootstrap/lifetime sound (env-lifetime fear disproven — heap-promoted); 1 CRITICAL (wakere-enqueue → FIFO segfault) + robustness gaps ALL hardened (wake guarded on.suspended,n_suspendeddeadlock diagnostic+abort, loud mmap/mprotect/OOM bails, env-leak documented). Locked1811(round-robin0 1 2 ×3) +1812(suspend/wake + spurious-wake guard,log: 10 20 21 11). Filed NON-blocking0155(scalar-pointer index panics codegen — review incidental, unused by sched). Suite GREEN 748/0. Next: B1.4a (FiberIo). - B1.4a (truly-suspending fiber-task async, nullary-thunk design) — BLOCKED on issue 0157.
Implemented the async layer SELF-CONTAINED in
library/modules/std/sched.sx(kept its lone#import "modules/std.sx"to avoid the duplicate-_fib_tramptrap):TaskState, a LOCALTaskErr :: error { Canceled }(the re-exportedIoErralias is NOT seen through by theraise/failable-type check — verified),Task($R), andgo/wait/cancelufcs. Design is the validated nullary-thunk (.sx-tmp/pnullary.sx→log: 1 2 3 42 100):workis aClosure() -> $R, user captures inputs at the call site, NO..argscrosses the fiber boundary (deliberately sidesteps 0156).go+waitrun correctly; both wake-orderings traced. Wrote the exampleexamples/concurrency/1813-concurrency-fiber-async-suspend.sx(+{ "target": "macos" }.build) but itscancelufcs surfaced a NEW compiler bug — issue 0157: a user generic ufcs whose name collides with a stdlib re-export (cancelfrom io.sx) is mis-resolved on UFCS call over a different generic struct, leaving$Runresolved → LLVM panic. Bisected to a minimal no-fiber repro (name is the sole trigger; non-UFCS form diagnoses correctly). Example NOT seeded into the corpus (no.exitmarker) — do NOT regen its goldens until 0157 lands. Per the STOP rule: filedissues/0157-*.{md,sx}, marked state BLOCKED, paused. - B1.4a COMPLETE (this session) — suspending fiber-task async + two compiler fixes. Built the
Task($R)+go/wait/cancellayer insched.sx(nullary-thunk design; self-contained to avoid the_fib_trampduplicate-symbol trap). Locked1813(sequence: 1 2 3 42 100 -99). FIXED the two blockers the worker had filed: 0156 Part 1 (comptime_pack_refarm inresolveTypeWithBindings; regression0216) and 0157 (receiver-driven UFCS overload selectionselectUfcsGenericByReceiver; regression0217). Adversarial review of the 0157 fix + Task layer found a determinism CRITICAL (always-run selection + specificity + ambiguity diagnostic), await-outside-fiber null-deref (loud guard), and cancel-not-skipping-work (skip if pre-canceled) — all fixed. Simplified1812(**Fiber→Sh.parked). 0156 Part 2 reframed OPEN/non-blocking. Suite GREEN 751/0. Next: B1.4b (deterministic-simIo, the KEYSTONE).