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:
agra
2026-06-21 09:21:18 +03:00
parent 7057175fb6
commit 4fc5411cd9
8 changed files with 75 additions and 17 deletions

View File

@@ -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

View File

@@ -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);
},