fix: allow void (zero-sized) struct/tuple fields instead of crashing (issue 0150)
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
This commit is contained in:
27
examples/0190-types-void-struct-field-zero-sized.sx
Normal file
27
examples/0190-types-void-struct-field-zero-sized.sx
Normal file
@@ -0,0 +1,27 @@
|
||||
// A `void` (zero-sized) struct/tuple field is legitimate (e.g. `Future(void)`)
|
||||
// and must NOT crash the compiler. It lowers to a zero-width `[0 x i8]` LLVM
|
||||
// slot (TypeLowering.fieldLLVMType), and aggregate init skips storing a value
|
||||
// into it (emitStructInit) — so the struct stays sized and field access past
|
||||
// the void field is correct, instead of the old unsized-type SIGTRAP / a
|
||||
// corrupt aggregate constant.
|
||||
//
|
||||
// Regression (issue 0150).
|
||||
#import "modules/std.sx";
|
||||
|
||||
Holder :: struct { v: void; ok: bool; }
|
||||
|
||||
Box :: struct($T: Type) { v: T; tag: i32; }
|
||||
|
||||
main :: () -> i32 {
|
||||
h : Holder = .{ ok = true };
|
||||
if h.ok { print("ok\n"); }
|
||||
|
||||
// Through a generic instantiated at `void`.
|
||||
b : Box(void) = .{ tag = 7 };
|
||||
print("tag={}\n", b.tag);
|
||||
|
||||
// A tuple with a void element.
|
||||
t : (void, i32) = .{ {}, 9 };
|
||||
print("t1={}\n", t.1);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ok
|
||||
tag=7
|
||||
t1=9
|
||||
@@ -1,7 +1,18 @@
|
||||
# 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
|
||||
OPEN — surfaced by Stream B1 (fibers) B1.2: `Future(void)` (needed by
|
||||
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).
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// Repro for issue 0150 — a `void` struct field crashes the compiler with an
|
||||
// unsized-type SIGTRAP (LLVM getTypeSizeInBits). Unpinned (no expected marker)
|
||||
// because it currently aborts the compiler; pin it as a regression test once
|
||||
// the fix lands.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Holder :: struct { v: void; ok: bool; }
|
||||
|
||||
main :: () -> i32 {
|
||||
h : Holder = .{ ok = true };
|
||||
if h.ok { print("ok\n"); }
|
||||
return 0;
|
||||
}
|
||||
@@ -1720,6 +1720,18 @@ pub const Ops = struct {
|
||||
const elem_llvm_ty = if (is_array) c.LLVMGetElementType(struct_ty) else null;
|
||||
var result = c.LLVMGetUndef(struct_ty);
|
||||
for (agg.fields, 0..) |field_ref, i| {
|
||||
// A `void` (zero-sized) struct/tuple field lowers to a zero-width
|
||||
// `[0 x i8]` slot (see `TypeLowering.fieldLLVMType`); it carries no
|
||||
// data. Skip inserting a value — the field's lowered ref is an i64
|
||||
// placeholder (`emitConstInt`'s void path) whose type mismatches the
|
||||
// slot and would corrupt the aggregate. The undef `[0 x i8]` element
|
||||
// is already the correct zero-width value.
|
||||
const field_is_void = switch (self.e.ir_mod.types.get(instruction.ty)) {
|
||||
.@"struct" => |s| i < s.fields.len and s.fields[i].ty == .void,
|
||||
.tuple => |t| i < t.fields.len and t.fields[i] == .void,
|
||||
else => false,
|
||||
};
|
||||
if (field_is_void) continue;
|
||||
var field_val = self.e.resolveRef(field_ref);
|
||||
if (is_vector) {
|
||||
// Coerce element to match vector element type
|
||||
|
||||
@@ -39,6 +39,22 @@ pub const TypeLowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Lower a *field* (struct/tuple element, or the payload of `?T`). Identical
|
||||
/// to `toLLVMType` except that a field which lowers to LLVM's unsized
|
||||
/// `void` type (a `void`/`noreturn`/zero-sized field — legitimate, e.g.
|
||||
/// `Future(void)`) is substituted with a SIZED zero-byte `[0 x i8]`. LLVM's
|
||||
/// `getTypeSizeInBits` traps on an unsized struct element ("Cannot
|
||||
/// getTypeInfo() on a type that is unsized!"); `[0 x i8]` reports size 0 and
|
||||
/// keeps the element COUNT and INDICES identical, so field-access codegen
|
||||
/// (GEP / extractvalue by `field_index`) needs no remapping.
|
||||
pub fn fieldLLVMType(self: TypeLowering, ty: TypeId) c.LLVMTypeRef {
|
||||
const lowered = self.toLLVMType(ty);
|
||||
if (lowered == self.e.cached_void) {
|
||||
return c.LLVMArrayType2(self.e.cached_i8, 0);
|
||||
}
|
||||
return lowered;
|
||||
}
|
||||
|
||||
fn toLLVMTypeInfo(self: TypeLowering, ty: TypeId) c.LLVMTypeRef {
|
||||
const info = self.e.ir_mod.types.get(ty);
|
||||
return switch (info) {
|
||||
@@ -83,7 +99,7 @@ pub const TypeLowering = struct {
|
||||
}
|
||||
// ?T → { T, i1 }
|
||||
var field_types: [2]c.LLVMTypeRef = .{
|
||||
self.toLLVMType(opt.child),
|
||||
self.fieldLLVMType(opt.child),
|
||||
self.e.cached_i1,
|
||||
};
|
||||
return c.LLVMStructTypeInContext(self.e.context, &field_types, 2, 0);
|
||||
@@ -108,7 +124,7 @@ pub const TypeLowering = struct {
|
||||
const field_llvm_types = self.e.alloc.alloc(c.LLVMTypeRef, s.fields.len) catch unreachable;
|
||||
defer self.e.alloc.free(field_llvm_types);
|
||||
for (s.fields, 0..) |field, j| {
|
||||
field_llvm_types[j] = self.toLLVMType(field.ty);
|
||||
field_llvm_types[j] = self.fieldLLVMType(field.ty);
|
||||
}
|
||||
return c.LLVMStructTypeInContext(self.e.context, field_llvm_types.ptr, n, 0);
|
||||
},
|
||||
@@ -162,7 +178,7 @@ pub const TypeLowering = struct {
|
||||
const field_llvm_types = self.e.alloc.alloc(c.LLVMTypeRef, t.fields.len) catch unreachable;
|
||||
defer self.e.alloc.free(field_llvm_types);
|
||||
for (t.fields, 0..) |f, j| {
|
||||
field_llvm_types[j] = self.toLLVMType(f);
|
||||
field_llvm_types[j] = self.fieldLLVMType(f);
|
||||
}
|
||||
return c.LLVMStructTypeInContext(self.e.context, field_llvm_types.ptr, n, 0);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user