fibers B1.2: 0151 fixed → async surface callable; blocked on 0152 (Atomic(bool) i1 atomic)
issue 0151 (generic $T inference through generic-struct / pointer / UFCS-pack params) is fixed and committed, so io.sx's async/await/cancel are now callable in every form. Building the async examples then tripped a SEPARATE codegen bug: Atomic(bool) emits a sub-byte (i1) atomic load/store that fails LLVM verification (must be byte-sized). Future.canceled: Atomic(bool) hits it. Filed issues/0152 with a standalone repro + investigation prompt (codegen fix in src/backend/llvm/ops.zig — promote sub-byte atomics to i8 storage). Per the STOP rule, paused B1.2's async examples (1805/1806) pending the 0152 fix. Checkpoint updated: 0151 RESOLVED, async surface BLOCKED on 0152.
This commit is contained in:
@@ -4,10 +4,29 @@ 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
|
||||
**B1.2 (Io capability) — PARTIALLY LANDED + adversarially reviewed. Infrastructure GOOD;
|
||||
async SURFACE blocked on a generic-inference compiler bug (issue 0151, widened).**
|
||||
Commits `a1b14f0` (lock) + `45d869d` (Io capability) + `3eeb965` (issue 0151). Suite green
|
||||
**726/0**, master clean.
|
||||
**issue 0151 FIXED — generic `$T` now infers through generic-struct / pointer / UFCS-pack
|
||||
params.** `await`/`cancel`/`async` are CALLABLE again. The async SURFACE is now blocked on a
|
||||
DIFFERENT, freshly-exposed codegen bug: **issue 0152 — `Atomic(bool)` emits a sub-byte (i1)
|
||||
atomic that LLVM rejects** (the `Future.canceled: Atomic(bool)` field hits it). Suite green
|
||||
**728/0**, master clean.
|
||||
- **0151 fix (committed):** four gaps closed on the inference + UFCS-dispatch path —
|
||||
(1) `extractTypeParam`/`matchTypeParam(Static)` got a `parameterized_type_expr` arm
|
||||
(recover the arg instance's recorded per-param bindings via `struct_instance_bindings` +
|
||||
the template's ordered `type_params`, recurse positionally; this also fixes `*Box($T)` —
|
||||
it recurses into its `Box($T)` pointee); (2) the `pointer_type_expr` arm now falls through
|
||||
to match the pointee against a non-pointer arg (auto-address-of: a `*Box($T)` param accepts
|
||||
a by-value `Box($T)`, e.g. a UFCS receiver `b.m()`); (3) `ExprTyper.inferType` got a
|
||||
`.lambda` arm 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 bind `Closure(..) -> $R` from
|
||||
the worker's declared return type); (4) a pack UFCS target routes through the SAME
|
||||
`lowerPackFnCall` the direct call uses, with the receiver spliced in as `args[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) + `io` field on `Context`
|
||||
(`{allocator; data; io}`, io LAST); BOTH `__sx_default_context` materializers
|
||||
@@ -82,24 +101,27 @@ body); closed + locked. The review's `.naked`-lambda CRITICAL was a false positi
|
||||
(unparseable — `isLambda` breaks on the `abi` keyword).
|
||||
|
||||
## Current state
|
||||
**B1.2 is UNBLOCKED.** Master GREEN (726/0), installed `sx` clean. The earlier "blockers"
|
||||
were NOT real: issue **0151 was INVALID** (its repro used the non-idiomatic `($A)->$R`
|
||||
bare-fn-ptr form) — **removed**. The correct `async` idiom **works today with no compiler
|
||||
change** (verified live): `spawn :: (worker: Closure(..$args) -> $R, ..$args) -> Wrap($R)`
|
||||
with a **lambda worker** + the `w : Wrap($R) = ---; w.v = worker(..args);` build form —
|
||||
mirrors the canonical `examples/0543-packs-canonical-map.sx`. Ran `42 42` for homogeneous +
|
||||
heterogeneous args. Caveats (work within them, not "bugs"): lambda params must be annotated
|
||||
(`(a: i64, b: i64) -> i64 => …`); a bare *named* fn passed as the worker is non-idiomatic —
|
||||
use a lambda; build the result struct with `= ---` + field-assign, not a struct-literal in
|
||||
`return`. Issue **0150** (`void` struct field → SIGTRAP exit 133) is a **real** bug but only
|
||||
reached via `Future(void)` (void-returning worker / `timeout`) — **DEFERRED**: B1.2 supports
|
||||
non-void workers; revisit `Future(void)` in B1.4 (or fix 0150 standalone). The B1.2 *design*
|
||||
(Io protocol on Context, blocking `CBlockingIo`, `context.io.now_ms()`) was validated live;
|
||||
WIP at `.sx-tmp/b12-wip/` has the working Io/Context/materializer parts — reuse those, rewrite
|
||||
the async layer to the pack-lambda idiom above.
|
||||
**B1.2 Io capability is LANDED; the async surface is now CALLABLE but blocked on issue 0152.**
|
||||
Master GREEN (728/0), installed `sx` clean.
|
||||
- **0151 fixed (this session, committed):** the generic-inference engine now binds `$T`
|
||||
through a generic-struct param head (`Box($T)`), through a pointer (`*Box($T)`, incl. the
|
||||
UFCS auto-address-of receiver), and through a closure-return-via-pack on the UFCS dot-call
|
||||
path. So `async`/`await`/`cancel` in `library/modules/std/io.sx` — all `ufcs`, with
|
||||
`await`/`cancel` taking `*Future($R)` — are callable in every form. Details in the
|
||||
"Last completed step" block above; regression tests `examples/0214` + `0215`.
|
||||
- **NEW blocker — issue 0152 (`Atomic(bool)` → sub-byte i1 atomic):** with the surface
|
||||
callable, building the async examples now trips LLVM verification: `Atomic($T)` lowers a
|
||||
`bool` element to `i1` and emits an `i1` atomic load/store, which LLVM rejects ("atomic
|
||||
memory access' size must be byte-sized"). `Future.canceled: Atomic(bool)` hits this in
|
||||
`async`/`cancel`/`await`. Standalone repro + investigation prompt filed at
|
||||
`issues/0152-atomic-bool-sub-byte-atomic-llvm-reject.md`. The fix is codegen-level
|
||||
(`src/backend/llvm/ops.zig` atomic emitters — promote sub-byte atomics to `i8` storage with
|
||||
`trunc`/`zext` at the value boundary). A secondary `or`-merge PHI i1/i64 mismatch appeared in
|
||||
the same probe — likely entangled with the malformed `Atomic(bool)` field; re-check after 0152.
|
||||
- Issue **0150** (`void` struct field → SIGTRAP) remains DEFERRED — only `Future(void)` /
|
||||
`timeout`, which are B1.4.
|
||||
|
||||
### B1.2 attempt (BLOCKED — design proven, two compiler bugs filed)
|
||||
What was built + verified WORKING (then reverted to keep master green):
|
||||
### B1.2 Io capability — what is LANDED + verified (commit 45d869d)
|
||||
- `Io :: protocol #inline { spawn_raw; suspend_raw -> !; ready; poll; now_ms; arm_timer; }`
|
||||
in `core.sx` next to `Allocator`, with `SpawnOpts{ pin: PinTarget }` + `ParkToken{ handle }`.
|
||||
Six methods, each justified by a downstream consumer (B1.3-B1.5).
|
||||
@@ -124,22 +146,15 @@ What was built + verified WORKING (then reverted to keep master green):
|
||||
`!` 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.
|
||||
|
||||
Where it BROKE (the two blockers — both INDEPENDENT of the Io design, both repro standalone):
|
||||
- **issue 0150** — `Future(void)` (for `timeout -> Future(void)`) makes a `result: void` field;
|
||||
a `void` struct field crashes the compiler with an unsized-type SIGTRAP in LLVM
|
||||
`getTypeSizeInBits` (a bare `struct { v: void; }` repros it). `timeout` was DEFERRED (it is a
|
||||
B1.4 stub needing `arm_timer` anyway) rather than routed around with a non-void shape.
|
||||
- **issue 0151** — `async(io, worker: ($A) -> $R, arg: $A) -> Future($R)`: `$R` inferred from a
|
||||
fn-pointer parameter's RETURN type type-checks the call but is NOT bound as a usable type in
|
||||
the body, so `Future(R)` errors `unknown type 'R'`. A direct `arg: $A` binds fine — the gap is
|
||||
specific to type-vars nested in a fn-ptr/closure param signature. This blocks the central
|
||||
`async`/`await` free-fns. (Manifested as the "unresolved type reached LLVM emission" panic —
|
||||
the same one another session filed against my dirty binary as issue 0149, now moot after the
|
||||
revert.)
|
||||
Status of the blockers that originally stopped B1.2:
|
||||
- **issue 0151 — FIXED this session** (generic `$T` through generic-struct / pointer /
|
||||
UFCS-pack params). `async`/`await`/`cancel` are callable. See "Last completed step".
|
||||
- **issue 0152 — NEW, the current blocker** (`Atomic(bool)` → sub-byte i1 atomic; LLVM reject).
|
||||
Blocks the async examples via `Future.canceled: Atomic(bool)`. Filed; codegen-level fix.
|
||||
- **issue 0150** — `void` struct field SIGTRAP; only `Future(void)`/`timeout` (B1.4). DEFERRED.
|
||||
|
||||
Per the IMPASSABLE STOP rule: filed 0150 + 0151, reverted all B1.2 working changes (master
|
||||
green again, photo project unbroken), STOPPED. Resume B1.2 once 0150 + 0151 land — the WIP in
|
||||
`.sx-tmp/b12-wip/` makes it ~mechanical (the design is proven).
|
||||
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
|
||||
@@ -163,18 +178,18 @@ fibers/Io/scheduler code yet. Grounded floor facts:
|
||||
boundary; a sharper sx diagnostic for it is a candidate polish, not a blocker.
|
||||
|
||||
## Next step
|
||||
**Fix issue 0151 (generic inference through a pointer) so `await`/`cancel` become callable —
|
||||
then complete B1.2's async surface.** Sequence:
|
||||
1. **Fix 0151 (WIDENED):** make the generic-inference engine bind `$T` from a pointer-wrapped
|
||||
param (`b: *Box($T)` ⇒ `unbox(@b)` infers `T`), which unblocks `await`/`cancel`
|
||||
(`*Future($R)`). The same bug has two more faces — `$R` from a closure-return via a pack,
|
||||
and via UFCS dot-dispatch (the original 0151 + the `f.await()` SIGTRAP). Acceptance cases
|
||||
are in `issues/0151-...md`. This is generic-inference-engine work
|
||||
(`src/ir/lower/generic.zig` `extractTypeParam` + the UFCS call path) — its own focused step.
|
||||
2. **Restore/add the async examples:** `examples/1805-concurrency-io-blocking-async.sx`
|
||||
(`context.io.async((a:i64,b:i64)->i64 => a+b, 40, 2).await()` → 42) + `1806-...-io-cancel.sx`
|
||||
(cancel → state `.canceled`). lock→green. Regen `.ir` only after green; confirm layout-only.
|
||||
3. Then B1.2 is truly done → proceed to **B1.3 (fiber runtime)**.
|
||||
**BLOCKED on issue 0152 (`Atomic(bool)` sub-byte atomic).** Once it lands, complete B1.2's
|
||||
async surface:
|
||||
1. **Add the async examples:** `examples/1805-concurrency-io-blocking-async.sx`
|
||||
(`context.io.async((a:i64,b:i64)->i64 => a+b, 40, 2).await() or {…}` → 42; `double:`/`sum:`)
|
||||
+ `1806-...-io-cancel.sx` (cancel → `await` raises `.Canceled`). The async surface is already
|
||||
callable (0151 fixed); a working probe lives at `.sx-tmp/async_surface.sx` — it builds the
|
||||
Futures and dispatches `async`/`await`/`cancel` correctly, only the `Atomic(bool)` codegen
|
||||
(0152) stops it. lock→green, then regen `.ir` (confirm layout-only).
|
||||
- Note: `await` returns a value-failable `($R, !IoErr)`; use `f.await() or { default }` (a
|
||||
braced `or`, not `catch <expr>`). Re-verify the `or`-merge PHI i1/i64 symptom is gone once
|
||||
0152 lands (it was likely a cascade from the malformed `Atomic(bool)` field).
|
||||
2. Then B1.2 is truly done → proceed to **B1.3 (fiber runtime)**.
|
||||
|
||||
**Deferred (do NOT block B1.2 on these):** issue **0150** (`void` struct field SIGTRAP) — only
|
||||
`Future(void)`/`timeout`, which are B1.4. The **`::` callable-parameter feature** (named-fn
|
||||
@@ -185,16 +200,16 @@ done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idi
|
||||
`call.zig:1229`, io last). Io protocol + materializers + push-inherit are LANDED + reviewed.
|
||||
|
||||
## Known issues / capability gaps
|
||||
- **🔴 B1.2 BLOCKERS (both filed, both standalone-reproducible, both independent of the Io
|
||||
design):**
|
||||
- **issue 0150** — a `void` struct field crashes the compiler (unsized-type SIGTRAP in LLVM
|
||||
`getTypeSizeInBits`). Blocks `Future(void)` → `timeout`. Repro: `issues/0150-...`.
|
||||
- **issue 0151** — a type-var inferred from a fn-pointer parameter's RETURN type is not bound
|
||||
in the function body (`unknown type 'R'`). Blocks `async(io, worker: ($A)->$R, arg)`'s
|
||||
`Future(R)`. Repro: `issues/0151-...`.
|
||||
- (Note: **issue 0149**, filed by another session against the dirty in-progress binary, was a
|
||||
manifestation of 0151 — "unresolved type reached LLVM emission". Moot after the revert; its
|
||||
real root cause is 0151.)
|
||||
- **🔴 B1.2 BLOCKER — issue 0152** (`Atomic(bool)` → sub-byte i1 atomic load/store; LLVM
|
||||
rejects "atomic memory access' size must be byte-sized"). Blocks the async examples via
|
||||
`Future.canceled: Atomic(bool)`. Standalone repro + fix prompt: `issues/0152-...`. Codegen-
|
||||
level (`src/backend/llvm/ops.zig` atomic emitters; promote sub-byte → i8 storage).
|
||||
- **✅ issue 0151 — FIXED this session** (generic `$T` through generic-struct / pointer /
|
||||
UFCS-pack params). Regression: `examples/0214` + `0215`. Was the original B1.2 surface blocker.
|
||||
- **issue 0150** (deferred) — a `void` struct field crashes the compiler (unsized-type SIGTRAP
|
||||
in LLVM `getTypeSizeInBits`). Blocks `Future(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
|
||||
@@ -291,3 +306,16 @@ done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idi
|
||||
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 `$T` through 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_expr` arm in `extractTypeParam`/`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` `.lambda` arm
|
||||
(closure type from annotations — UFCS types args from raw AST pre-lowering); pack UFCS target
|
||||
routes through `lowerPackFnCall` with the receiver spliced in as `args[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.canceled` hits 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.
|
||||
|
||||
79
issues/0152-atomic-bool-sub-byte-atomic-llvm-reject.md
Normal file
79
issues/0152-atomic-bool-sub-byte-atomic-llvm-reject.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 0152 — `Atomic(bool)` emits a sub-byte (i1) atomic load/store that LLVM rejects
|
||||
|
||||
## Symptom
|
||||
|
||||
`Atomic(bool)` lowers `bool` to LLVM `i1` and emits the atomic load/store
|
||||
at that type. LLVM requires atomic memory accesses to be byte-sized, so
|
||||
codegen fails verification:
|
||||
|
||||
```
|
||||
LLVM verification failed: atomic memory access' size must be byte-sized
|
||||
i1 store atomic i1 %load5, ptr %gep release, align 1
|
||||
i1 %atomic_load = load atomic i1, ptr %gep11 acquire, align 1
|
||||
```
|
||||
|
||||
- `Atomic(i64)`, `Atomic(i32)`, … → fine (byte-sized).
|
||||
- `Atomic(bool)` → LLVM verifier error (i1 is 1 *bit*, not byte-sized).
|
||||
|
||||
## Reproduction
|
||||
|
||||
Standalone — depends on no project symbols beyond `modules/std.sx` +
|
||||
`modules/std/atomic.sx`:
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/atomic.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
a := Atomic(bool).init(false);
|
||||
a.store(true, .release);
|
||||
if a.load(.acquire) { print("yes\n"); } else { print("no\n"); }
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Expected: prints `yes`. Actual: LLVM verification failure (i1 atomic).
|
||||
|
||||
## Impact
|
||||
|
||||
Blocks the B1.2 async surface. `library/modules/std/io.sx`'s
|
||||
`Future($R)` carries a `canceled: Atomic(bool)` cancellation flag (atomic
|
||||
so a future scheduler thread can flip it). `async`/`cancel`/`await` all
|
||||
touch it (`Atomic(bool).init`, `.store(true, .release)`,
|
||||
`.load(.acquire)`), so the async examples (`1805`/`1806`) cannot build.
|
||||
This is independent of issue 0151 (generic inference) — that is now fixed,
|
||||
which is what newly exposed this codegen path.
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
The atomic load/store emitters in `src/backend/llvm/ops.zig`
|
||||
(`emitAtomicLoad` ~line 387, `emitAtomicStore`, `emitAtomicRmw`,
|
||||
`emitAtomicCmpxchg`) use `toLLVMType(instruction.ty)` directly as the
|
||||
atomic access type. For a `bool` element that is `i1`, which LLVM rejects
|
||||
for atomics (must be a byte-multiple).
|
||||
|
||||
The fix should promote a sub-byte atomic to its byte-sized storage type:
|
||||
load/store as `i8` (the ABI storage type for `bool`) and `trunc`/`zext`
|
||||
between `i1` and `i8` at the value boundary — mirroring how a non-atomic
|
||||
`bool` field is already stored as a byte. Apply consistently across
|
||||
load / store / rmw / cmpxchg so an `Atomic(bool)` round-trips. Confirm the
|
||||
alignment (`LLVMSetAlignment`) uses the promoted byte size.
|
||||
|
||||
Possible alternative: have `Atomic($T)` (in `library/modules/std/atomic.sx`)
|
||||
constrain / widen a `bool` element to a byte-sized integer in the type
|
||||
itself — but the codegen-level promotion is more robust (any i1-typed
|
||||
atomic, however it arises, becomes legal).
|
||||
|
||||
Verification: run the repro above; expect `yes`. Then restore
|
||||
`examples/1805-concurrency-io-blocking-async.sx` (+ add `1806` cancel) per
|
||||
Stream B1 / CHECKPOINT-FIBERS and confirm the async surface builds and runs
|
||||
(`sum: 42` / `double: 42`, cancel → `.canceled`).
|
||||
|
||||
### Possibly-related secondary symptom (verify after the i1 fix)
|
||||
|
||||
The same async probe also tripped an `or`-merge PHI type mismatch
|
||||
(`%bp = phi i1 [ true, … ], [ 0, … ]`) when `f.await() or { <i64> }` was
|
||||
lowered. A minimal `(i64, !E)` + `or { -1 }` does NOT reproduce it, so this
|
||||
may be entangled with the malformed `Atomic(bool)` field in the `Future`
|
||||
struct rather than a second bug. Re-check once the i1 atomic is fixed; if it
|
||||
persists, file separately.
|
||||
Reference in New Issue
Block a user