Files
sx/issues/0150-void-struct-field-unsized-llvm-trap.md
agra e78320637f fibers B1.2: BLOCKED on compiler bugs 0150 + 0151 (Io design proven)
Stream B1 B1.2 (Io capability + context.io + Future + cancel) is blocked on
two newly-discovered, independent compiler bugs, both with standalone repros:

- 0150: a `void` struct field crashes the compiler with an unsized-type
  SIGTRAP in LLVM getTypeSizeInBits. Blocks `Future(void)` -> `timeout`.
- 0151: a type-var inferred from a fn-pointer parameter's RETURN type is not
  bound as a usable type in the function body (`unknown type 'R'`). Blocks the
  central `async(io, worker: ($A)->$R, arg)` free-fn's `Future(R)`.

The B1.2 design itself is validated end-to-end (the Io protocol threaded on
Context like Allocator, the stateless blocking CBlockingIo default, both
__sx_default_context materializers, and `context.io.now_ms()` all work live).
Only the async/await/timeout ergonomic layer hits the two bugs. Per the
IMPASSABLE STOP rule, all B1.2 working changes were reverted (master green,
726/0) and the work paused pending fixes; WIP is saved at .sx-tmp/b12-wip/.

Checkpoint + plan updated to mark B1.2 BLOCKED with full resume notes.
2026-06-20 18:54:04 +03:00

73 lines
3.4 KiB
Markdown

# 0150 — a `void` struct field crashes the compiler (unsized-type SIGTRAP in LLVM)
## Status
OPEN — surfaced by Stream B1 (fibers) B1.2: `Future(void)` (needed by
`timeout(io, ms) -> Future(void)`) instantiates a struct with a `result: void`
field, which hits this bug. Independent of the fibers work (a plain
`struct { v: void; }` reproduces it standalone).
## Symptom
Declaring or instantiating any struct that has a field of type `void` aborts the
compiler with `SIGTRAP` (exit 133/134) — no sx diagnostic. The trap is LLVM's
`llvm_unreachable("Cannot getTypeInfo() on a type that is unsized!")`:
```
libLLVM`llvm::DataLayout::getTypeSizeInBits + 912 brk #0x1 (EXC_BREAKPOINT)
```
Reached via `declareFunction``toLLVMType(func.ret)` when a function returns
such a struct, or directly when laying out the struct.
Observed: SIGTRAP, no output, no diagnostic.
Expected: either zero-size the `void` field (a `void`/zero-sized field is a
legitimate construct — cf. Zig) OR emit a clean type diagnostic
("a struct field may not have type `void`") — never a raw backend crash.
## Reproduction
```sx
#import "modules/std.sx";
Holder :: struct { v: void; ok: bool; }
main :: () -> i32 {
h : Holder = .{ ok = true };
if h.ok { print("ok\n"); }
return 0;
}
```
`./zig-out/bin/sx run repro.sx` → SIGTRAP (exit 133), no output.
Also reproduces through a generic: `Box :: struct($T: Type) { v: T; }` then
`Box(void)` — i.e. any monomorphization that binds a struct field to `void`.
## Suspected area
- `src/backend/llvm/types.zig` `toLLVMTypeInfo` (struct field loop ~line 111):
a `void` field's LLVM type is the unsized `void` type, then `getTypeSizeInBits`
on the enclosing struct traps.
- The type layout / size code (`src/ir/types.zig` `typeSizeBytes` and the LLVM
struct builder) should treat a `void` field as zero-sized (skip it in the LLVM
struct, size 0, align 1) — the same way a zero-field struct is handled.
## Investigation prompt (paste into a fresh session)
> A `void` struct field crashes the sx compiler with an unsized-type SIGTRAP in
> LLVM `getTypeSizeInBits` (no diagnostic). Repro: `issues/0150-...` (run it →
> exit 133). Decide the semantics: a `void` field should be ZERO-SIZED (preferred
> — it is a legitimate construct, e.g. `Future(void).result`), laid out as
> nothing (size 0, align 1) and OMITTED from the LLVM struct body; OR, if
> zero-sized fields are out of scope, a clean front-end diagnostic ("a struct
> field may not have type `void`, found in field `<name>` of `<Struct>`") before
> emission — NEVER a backend trap. Likely sites: `src/backend/llvm/types.zig`
> `toLLVMTypeInfo` (skip `void` fields when building the LLVM struct element
> list) + `src/ir/types.zig` size/align (`typeSizeBytes`/align: a `void` field
> contributes 0). If choosing the diagnostic route, add it where struct fields
> are validated at type-resolution time. Verify: the repro prints `ok` (zero-size
> route) or emits the diagnostic + clean exit 1 (diagnostic route); then move the
> repro into `examples/` as a regression test.
## Why this matters for B1 (fibers)
`Future($R)` with `$R = void` is the natural shape for `timeout(io, ms) ->
Future(void)` (B1.2 spec) and for any future-of-no-value. B1.2 deferred
`timeout` pending this fix rather than route around it with a substitute
non-void shape (which would hide the bug). Once 0150 lands, re-add `timeout`
with `Future(void)` (see the saved WIP at `.sx-tmp/b12-wip/io.sx`).