A struct/tuple/?T with a void field crashed the compiler: the field lowered to LLVM's unsized 'void' type, which traps getTypeSizeInBits. Lower a void field to a SIZED zero-byte [0 x i8] (fieldLLVMType) so the enclosing aggregate stays sized with identical element indices, and skip inserting a value for a void field in emitStructInit (the i64 placeholder would type-mismatch the [0 x i8] slot and corrupt the aggregate constant -> runtime bus error). Future(void) now works. Regression: examples/0190-types-void-struct-field-zero-sized.sx
84 lines
4.2 KiB
Markdown
84 lines
4.2 KiB
Markdown
# 0150 — a `void` struct field crashes the compiler (unsized-type SIGTRAP in LLVM)
|
|
|
|
> **RESOLVED.** Two coordinated changes let a `void` (zero-sized) field be a
|
|
> legitimate construct (so `Future(void)` works): (1) `TypeLowering.fieldLLVMType`
|
|
> (src/backend/llvm/types.zig) lowers a `void` struct/tuple/`?T` field to a SIZED
|
|
> zero-byte `[0 x i8]` instead of LLVM's unsized `void` (which trapped
|
|
> `getTypeSizeInBits`), keeping element count/indices identical; (2) `emitStructInit`
|
|
> (src/backend/llvm/ops.zig) skips inserting a value for a `void` field — the i64
|
|
> placeholder would type-mismatch the `[0 x i8]` slot and corrupt the aggregate
|
|
> constant (the original runtime bus-error). Regression test:
|
|
> `examples/0190-types-void-struct-field-zero-sized.sx` (covers a plain struct, a
|
|
> generic `Box(void)`, and a tuple void element).
|
|
|
|
## Status
|
|
RESOLVED (was: 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`).
|