commit ea7e34224a8c3c0551e17a60fd849efee82447f7 (tree)
parent 0f3c883245f27870472978f738d6e81958e15c52
Author: Matthew Lugg <mlugg@mlugg.co.uk>
Date: Wed, 4 Mar 2026 10:51:45 +0000
compiler: don't call `getUnionLayout` on packed unions
Diffstat:
4 files changed, 168 insertions(+), 172 deletions(-)
diff --git a/src/Type.zig b/src/Type.zig
@@ -1571,6 +1571,7 @@ pub fn externUnionBackingType(ty: Type, pt: Zcu.PerThread) !Type {
}
}
+/// Asserts that `ty` is a non-packed union type.
pub fn unionGetLayout(ty: Type, zcu: *const Zcu) Zcu.UnionLayout {
assertHasLayout(ty, zcu);
const union_obj = zcu.intern_pool.loadUnionType(ty.toIntern());
@@ -2689,7 +2690,10 @@ pub fn arrayBase(ty: Type, zcu: *const Zcu) struct { Type, u64 } {
return .{ cur_ty, cur_len };
}
+/// Asserts that `loaded_union.layout` is not `.@"packed"`.
pub fn getUnionLayout(loaded_union: InternPool.LoadedUnionType, zcu: *const Zcu) Zcu.UnionLayout {
+ assert(loaded_union.layout != .@"packed");
+
const ip = &zcu.intern_pool;
var most_aligned_field: u32 = 0;
var most_aligned_field_align: InternPool.Alignment = .@"1";
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
@@ -2369,6 +2369,30 @@ pub const Object = struct {
const line = ty.typeDeclSrcLine(zcu).? + 1;
const enum_tag_ty: Type = .fromInterned(union_type.enum_tag_type);
+
+ if (union_type.layout == .@"packed") {
+ const bitpack_field = try o.builder.debugMemberType(
+ try o.builder.metadataString("bits"),
+ null, // file
+ ty_fwd_ref,
+ 0, // line
+ try o.getDebugType(pt, .fromInterned(union_type.packed_backing_int_type)),
+ ty.abiSize(zcu) * 8,
+ ty.abiAlignment(zcu).toByteUnits().? * 8,
+ 0, // offset
+ );
+ return o.builder.debugStructType(
+ name,
+ file,
+ scope,
+ line,
+ null, // underlying type
+ ty.abiSize(zcu) * 8,
+ ty.abiAlignment(zcu).toByteUnits().? * 8,
+ try o.builder.metadataTuple(&.{bitpack_field}),
+ );
+ }
+
const layout = Type.getUnionLayout(union_type, zcu);
if (layout.payload_size == 0) {
@@ -2411,10 +2435,7 @@ pub const Object = struct {
const field_ty = union_type.field_types.get(ip)[field_index];
const field_size = Type.fromInterned(field_ty).abiSize(zcu);
- const field_align: InternPool.Alignment = switch (union_type.layout) {
- .@"packed" => .none,
- .auto, .@"extern" => ty.explicitFieldAlignment(field_index, zcu),
- };
+ const field_align: InternPool.Alignment = ty.explicitFieldAlignment(field_index, zcu);
const field_name = enum_tag_ty.enumFieldName(field_index, zcu);
fields.appendAssumeCapacity(try o.builder.debugMemberType(
@@ -3318,7 +3339,6 @@ pub const Object = struct {
if (o.type_map.get(t.toIntern())) |value| return value;
const union_obj = ip.loadUnionType(t.toIntern());
- const layout = Type.getUnionLayout(union_obj, zcu);
if (union_obj.layout == .@"packed") {
const int_ty = try o.lowerType(pt, .fromInterned(union_obj.packed_backing_int_type));
@@ -3326,6 +3346,8 @@ pub const Object = struct {
return int_ty;
}
+ const layout = Type.getUnionLayout(union_obj, zcu);
+
if (layout.payload_size == 0) {
const enum_tag_ty = try o.lowerType(pt, .fromInterned(union_obj.enum_tag_type));
try o.type_map.put(o.gpa, t.toIntern(), enum_tag_ty);
@@ -6760,7 +6782,7 @@ pub const FuncGen = struct {
const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
const struct_ptr = try self.resolveInst(struct_field.struct_operand);
const struct_ptr_ty = self.typeOf(struct_field.struct_operand);
- return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, struct_field.field_index);
+ return self.fieldPtr(struct_ptr, struct_ptr_ty, struct_field.field_index);
}
fn airStructFieldPtrIndex(
@@ -6771,7 +6793,7 @@ pub const FuncGen = struct {
const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
const struct_ptr = try self.resolveInst(ty_op.operand);
const struct_ptr_ty = self.typeOf(ty_op.operand);
- return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index);
+ return self.fieldPtr(struct_ptr, struct_ptr_ty, field_index);
}
fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
@@ -10704,18 +10726,11 @@ pub const FuncGen = struct {
const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
const union_ty = self.typeOfIndex(inst);
const union_llvm_ty = try o.lowerType(pt, union_ty);
- const layout = union_ty.unionGetLayout(zcu);
const union_obj = zcu.typeToUnion(union_ty).?;
- if (union_obj.layout == .@"packed") {
- const big_bits = union_ty.bitSize(zcu);
- const int_llvm_ty = try o.builder.intType(@intCast(big_bits));
- const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[extra.field_index]);
- const non_int_val = try self.resolveInst(extra.init);
- const small_int_ty = try o.builder.intType(@intCast(field_ty.bitSize(zcu)));
- const small_int_val = try self.wip.cast(.bitcast, non_int_val, small_int_ty, "");
- return self.wip.conv(.unsigned, small_int_val, int_llvm_ty, "");
- }
+ assert(union_obj.layout != .@"packed");
+
+ const layout = Type.getUnionLayout(union_obj, zcu);
const tag_int_val = blk: {
const tag_ty = union_ty.unionTagTypeHypothetical(zcu);
@@ -11051,65 +11066,45 @@ pub const FuncGen = struct {
fn fieldPtr(
self: *FuncGen,
- inst: Air.Inst.Index,
- struct_ptr: Builder.Value,
- struct_ptr_ty: Type,
+ aggregate_ptr: Builder.Value,
+ aggregate_ptr_ty: Type,
field_index: u32,
) !Builder.Value {
const o = self.ng.object;
const pt = self.ng.pt;
const zcu = pt.zcu;
- const struct_ty = struct_ptr_ty.childType(zcu);
- switch (struct_ty.zigTypeTag(zcu)) {
- .@"struct" => switch (struct_ty.containerLayout(zcu)) {
- .@"packed" => {
- const result_ty = self.typeOfIndex(inst);
- const result_ty_info = result_ty.ptrInfo(zcu);
- const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(zcu);
- const struct_type = zcu.typeToStruct(struct_ty).?;
-
- if (result_ty_info.packed_offset.host_size != 0) {
- // From LLVM's perspective, a pointer to a packed struct and a pointer
- // to a field of a packed struct are the same. The difference is in the
- // Zig pointer type which provides information for how to mask and shift
- // out the relevant bits when accessing the pointee.
- return struct_ptr;
- }
-
- // We have a pointer to a packed struct field that happens to be byte-aligned.
- // Offset our operand pointer by the correct number of bytes.
- const byte_offset = @divExact(zcu.structPackedFieldBitOffset(struct_type, field_index) + struct_ptr_ty_info.packed_offset.bit_offset, 8);
- if (byte_offset == 0) return struct_ptr;
- const usize_ty = try o.lowerType(pt, Type.usize);
- const llvm_index = try o.builder.intValue(usize_ty, byte_offset);
- return self.wip.gep(.inbounds, .i8, struct_ptr, &.{llvm_index}, "");
- },
- else => {
- if (!struct_ty.hasRuntimeBits(zcu)) {
- return struct_ptr;
- }
- const struct_llvm_ty = try o.lowerType(pt, struct_ty);
- if (o.llvmFieldIndex(struct_ty, field_index)) |llvm_field_index| {
- return self.wip.gepStruct(struct_llvm_ty, struct_ptr, llvm_field_index, "");
- } else {
- // If we found no index then this means this is a zero sized field at the
- // end of the struct. Treat our struct pointer as an array of two and get
- // the index to the element at index `1` to get a pointer to the end of
- // the struct.
- const llvm_index = try o.builder.intValue(
- try o.lowerType(pt, Type.usize),
- @intFromBool(struct_ty.hasRuntimeBits(zcu)),
- );
- return self.wip.gep(.inbounds, struct_llvm_ty, struct_ptr, &.{llvm_index}, "");
- }
- },
+ const aggregate_ty = aggregate_ptr_ty.childType(zcu);
+ if (aggregate_ty.containerLayout(zcu) == .@"packed") {
+ // A pointer to a bitpack field is equivalent to a pointer to the whole bitpack; the
+ // bit offset is represented in the pointer *type*.
+ return aggregate_ptr;
+ }
+ switch (aggregate_ty.zigTypeTag(zcu)) {
+ .@"struct" => {
+ if (!aggregate_ty.hasRuntimeBits(zcu)) {
+ return aggregate_ptr;
+ }
+ const struct_llvm_ty = try o.lowerType(pt, aggregate_ty);
+ if (o.llvmFieldIndex(aggregate_ty, field_index)) |llvm_field_index| {
+ return self.wip.gepStruct(struct_llvm_ty, aggregate_ptr, llvm_field_index, "");
+ } else {
+ // If we found no index then this means this is a zero sized field at the
+ // end of the struct. Treat our struct pointer as an array of two and get
+ // the index to the element at index `1` to get a pointer to the end of
+ // the struct.
+ const llvm_index = try o.builder.intValue(
+ try o.lowerType(pt, Type.usize),
+ @intFromBool(aggregate_ty.hasRuntimeBits(zcu)),
+ );
+ return self.wip.gep(.inbounds, struct_llvm_ty, aggregate_ptr, &.{llvm_index}, "");
+ }
},
.@"union" => {
- const layout = struct_ty.unionGetLayout(zcu);
- if (layout.payload_size == 0 or struct_ty.containerLayout(zcu) == .@"packed") return struct_ptr;
+ const layout = aggregate_ty.unionGetLayout(zcu);
+ if (layout.payload_size == 0) return aggregate_ptr;
const payload_index = @intFromBool(layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align));
- const union_llvm_ty = try o.lowerType(pt, struct_ty);
- return self.wip.gepStruct(union_llvm_ty, struct_ptr, payload_index, "");
+ const union_llvm_ty = try o.lowerType(pt, aggregate_ty);
+ return self.wip.gepStruct(union_llvm_ty, aggregate_ptr, payload_index, "");
},
else => unreachable,
}
diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig
@@ -4519,30 +4519,7 @@ fn unionInit(
const layout = cg.unionLayout(ty);
const payload_ty: Type = .fromInterned(union_ty.field_types.get(ip)[active_field]);
- if (union_ty.layout == .@"packed") {
- if (!payload_ty.hasRuntimeBits(zcu)) {
- const int_ty = try pt.intType(.unsigned, @intCast(ty.bitSize(zcu)));
- return cg.constInt(int_ty, 0);
- }
-
- assert(payload != null);
- if (payload_ty.isInt(zcu)) {
- if (ty.bitSize(zcu) == payload_ty.bitSize(zcu)) {
- return cg.bitCast(ty, payload_ty, payload.?);
- }
-
- const trunc = try cg.buildConvert(ty, .{ .ty = payload_ty, .value = .{ .singleton = payload.? } });
- return try trunc.materialize(cg);
- }
-
- const payload_int_ty = try pt.intType(.unsigned, @intCast(payload_ty.bitSize(zcu)));
- const payload_int = if (payload_ty.ip_index == .bool_type)
- try cg.convertToIndirect(payload_ty, payload.?)
- else
- try cg.bitCast(payload_int_ty, payload_ty, payload.?);
- const trunc = try cg.buildConvert(ty, .{ .ty = payload_int_ty, .value = .{ .singleton = payload_int } });
- return try trunc.materialize(cg);
- }
+ assert(union_ty.layout != .@"packed");
const tag_int = if (layout.tag_size != 0) blk: {
const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field);
@@ -4761,33 +4738,36 @@ fn structFieldPtr(
},
.@"struct" => switch (object_ty.containerLayout(zcu)) {
.@"packed" => return cg.todo("implement field access for packed structs", .{}),
- else => {
+ .auto, .@"extern" => {
return try cg.accessChain(result_ty_id, object_ptr, &.{field_index});
},
},
- .@"union" => {
- const layout = cg.unionLayout(object_ty);
- if (!layout.has_payload) {
- // Asked to get a pointer to a zero-sized field. Just lower this
- // to undefined, there is no reason to make it be a valid pointer.
- return try cg.module.constUndef(result_ty_id);
- }
+ .@"union" => switch (object_ty.containerLayout(zcu)) {
+ .@"packed" => return cg.todo("implement field access for packed unions", .{}),
+ .auto, .@"extern" => {
+ const layout = cg.unionLayout(object_ty);
+ if (!layout.has_payload) {
+ // Asked to get a pointer to a zero-sized field. Just lower this
+ // to undefined, there is no reason to make it be a valid pointer.
+ return try cg.module.constUndef(result_ty_id);
+ }
- const storage_class = cg.module.storageClass(object_ptr_ty.ptrAddressSpace(zcu));
- const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);
- const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, storage_class);
- const pl_ptr_id = blk: {
- if (object_ty.containerLayout(zcu) == .@"packed") break :blk object_ptr;
- break :blk try cg.accessChain(pl_ptr_ty_id, object_ptr, &.{layout.payload_index});
- };
+ const storage_class = cg.module.storageClass(object_ptr_ty.ptrAddressSpace(zcu));
+ const layout_payload_ty_id = try cg.resolveType(layout.payload_ty, .indirect);
+ const pl_ptr_ty_id = try cg.module.ptrType(layout_payload_ty_id, storage_class);
+ const pl_ptr_id = blk: {
+ if (object_ty.containerLayout(zcu) == .@"packed") break :blk object_ptr;
+ break :blk try cg.accessChain(pl_ptr_ty_id, object_ptr, &.{layout.payload_index});
+ };
- const active_pl_ptr_id = cg.module.allocId();
- try cg.body.emit(cg.module.gpa, .OpBitcast, .{
- .id_result_type = result_ty_id,
- .id_result = active_pl_ptr_id,
- .operand = pl_ptr_id,
- });
- return active_pl_ptr_id;
+ const active_pl_ptr_id = cg.module.allocId();
+ try cg.body.emit(cg.module.gpa, .OpBitcast, .{
+ .id_result_type = result_ty_id,
+ .id_result = active_pl_ptr_id,
+ .operand = pl_ptr_id,
+ });
+ return active_pl_ptr_id;
+ },
},
else => unreachable,
}
diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig
@@ -4009,69 +4009,86 @@ fn updateConstInner(dwarf: *Dwarf, pt: Zcu.PerThread, debug_const_index: link.Co
.union_type => {
const loaded_union = ip.loadUnionType(value_index);
const file = loaded_union.zir_index.resolveFile(ip);
- const need_terminator: bool = if (loaded_union.name_nav.unwrap()) |nav_index| t: {
- const nav = ip.getNav(nav_index);
- const decl_inst = nav.srcInst(ip).resolve(ip).?;
- const decl = zcu.fileByIndex(file).zir.?.getDeclaration(decl_inst);
- try wip_nav.declCommon(.{
- .decl = .decl_union,
- .generic_decl = .generic_decl_const,
- .decl_instance = .decl_instance_union,
- }, &nav, file, &decl);
- break :t true;
- } else t: {
- const file_gop = try dwarf.getModInfo(unit).files.getOrPut(dwarf.gpa, file);
- try wip_nav.abbrevCode(if (loaded_union.field_types.len > 0) .union_type else .empty_union_type);
- try diw.writeUleb128(file_gop.index);
- try wip_nav.strp(loaded_union.name.toSlice(ip));
- break :t loaded_union.field_types.len > 0;
- };
- const union_layout = Type.getUnionLayout(loaded_union, zcu);
- try diw.writeUleb128(union_layout.abi_size);
- try diw.writeUleb128(union_layout.abi_align.toByteUnits().?);
- const loaded_tag = ip.loadEnumType(loaded_union.enum_tag_type);
- if (loaded_union.has_runtime_tag) {
- try wip_nav.abbrevCode(.tagged_union);
- try wip_nav.infoSectionOffset(
- .debug_info,
- wip_nav.unit,
- wip_nav.entry,
- @intCast(diw.end + dwarf.sectionOffsetBytes()),
- );
- {
- try wip_nav.abbrevCode(.generated_field);
- try wip_nav.strp("tag");
- try wip_nav.refType(.fromInterned(loaded_union.enum_tag_type));
- try diw.writeUleb128(union_layout.tagOffset());
-
- for (0..loaded_union.field_types.len) |field_index| {
- try wip_nav.enumConstValue(loaded_tag, .{
- .sdata = .signed_tagged_union_field,
- .udata = .unsigned_tagged_union_field,
- .block = .big_tagged_union_field,
- }, field_index);
+ switch (loaded_union.layout) {
+ .auto, .@"extern" => {
+ const need_terminator: bool = if (loaded_union.name_nav.unwrap()) |nav_index| t: {
+ const nav = ip.getNav(nav_index);
+ const decl_inst = nav.srcInst(ip).resolve(ip).?;
+ const decl = zcu.fileByIndex(file).zir.?.getDeclaration(decl_inst);
+ try wip_nav.declCommon(.{
+ .decl = .decl_union,
+ .generic_decl = .generic_decl_const,
+ .decl_instance = .decl_instance_union,
+ }, &nav, file, &decl);
+ break :t true;
+ } else t: {
+ const file_gop = try dwarf.getModInfo(unit).files.getOrPut(dwarf.gpa, file);
+ try wip_nav.abbrevCode(if (loaded_union.field_types.len > 0) .union_type else .empty_union_type);
+ try diw.writeUleb128(file_gop.index);
+ try wip_nav.strp(loaded_union.name.toSlice(ip));
+ break :t loaded_union.field_types.len > 0;
+ };
+ const union_layout = Type.getUnionLayout(loaded_union, zcu);
+ try diw.writeUleb128(union_layout.abi_size);
+ try diw.writeUleb128(union_layout.abi_align.toByteUnits().?);
+ const loaded_tag = ip.loadEnumType(loaded_union.enum_tag_type);
+ if (loaded_union.has_runtime_tag) {
+ try wip_nav.abbrevCode(.tagged_union);
+ try wip_nav.infoSectionOffset(
+ .debug_info,
+ wip_nav.unit,
+ wip_nav.entry,
+ @intCast(diw.end + dwarf.sectionOffsetBytes()),
+ );
{
- try wip_nav.abbrevCode(.struct_field);
- try wip_nav.strp(loaded_tag.field_names.get(ip)[field_index].toSlice(ip));
- const field_type: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
- try wip_nav.refType(field_type);
- try diw.writeUleb128(union_layout.payloadOffset());
- try diw.writeUleb128(loaded_union.field_aligns.getOrNone(ip, field_index).toByteUnits() orelse
- if (field_type.isNoReturn(zcu)) 1 else field_type.abiAlignment(zcu).toByteUnits().?);
+ try wip_nav.abbrevCode(.generated_field);
+ try wip_nav.strp("tag");
+ try wip_nav.refType(.fromInterned(loaded_union.enum_tag_type));
+ try diw.writeUleb128(union_layout.tagOffset());
+
+ for (0..loaded_union.field_types.len) |field_index| {
+ try wip_nav.enumConstValue(loaded_tag, .{
+ .sdata = .signed_tagged_union_field,
+ .udata = .unsigned_tagged_union_field,
+ .block = .big_tagged_union_field,
+ }, field_index);
+ {
+ try wip_nav.abbrevCode(.struct_field);
+ try wip_nav.strp(loaded_tag.field_names.get(ip)[field_index].toSlice(ip));
+ const field_type: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
+ try wip_nav.refType(field_type);
+ try diw.writeUleb128(union_layout.payloadOffset());
+ try diw.writeUleb128(loaded_union.field_aligns.getOrNone(ip, field_index).toByteUnits() orelse
+ if (field_type.isNoReturn(zcu)) 1 else field_type.abiAlignment(zcu).toByteUnits().?);
+ }
+ try diw.writeUleb128(@intFromEnum(AbbrevCode.null));
+ }
}
try diw.writeUleb128(@intFromEnum(AbbrevCode.null));
+ } else for (0..loaded_union.field_types.len) |field_index| {
+ try wip_nav.abbrevCode(.untagged_union_field);
+ try wip_nav.strp(loaded_tag.field_names.get(ip)[field_index].toSlice(ip));
+ const field_type: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
+ try wip_nav.refType(field_type);
+ try diw.writeUleb128(loaded_union.field_aligns.getOrNone(ip, field_index).toByteUnits() orelse
+ if (field_type.isNoReturn(zcu)) 1 else field_type.abiAlignment(zcu).toByteUnits().?);
}
- }
- try diw.writeUleb128(@intFromEnum(AbbrevCode.null));
- } else for (0..loaded_union.field_types.len) |field_index| {
- try wip_nav.abbrevCode(.untagged_union_field);
- try wip_nav.strp(loaded_tag.field_names.get(ip)[field_index].toSlice(ip));
- const field_type: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]);
- try wip_nav.refType(field_type);
- try diw.writeUleb128(loaded_union.field_aligns.getOrNone(ip, field_index).toByteUnits() orelse
- if (field_type.isNoReturn(zcu)) 1 else field_type.abiAlignment(zcu).toByteUnits().?);
+ if (need_terminator) try diw.writeUleb128(@intFromEnum(AbbrevCode.null));
+ },
+ .@"packed" => {
+ // TODO: debug info for packed unions
+ try wip_nav.abbrevCode(.numeric_type);
+ try wip_nav.strp(loaded_union.name.toSlice(ip));
+ const backing_int_ty: Type = .fromInterned(loaded_union.packed_backing_int_type);
+ const int_info = backing_int_ty.intInfo(zcu);
+ try diw.writeByte(switch (int_info.signedness) {
+ inline .signed, .unsigned => |signedness| @field(DW.ATE, @tagName(signedness)),
+ });
+ try diw.writeUleb128(int_info.bits);
+ try diw.writeUleb128(backing_int_ty.abiSize(zcu));
+ try diw.writeUleb128(backing_int_ty.abiAlignment(zcu).toByteUnits().?);
+ },
}
- if (need_terminator) try diw.writeUleb128(@intFromEnum(AbbrevCode.null));
},
.enum_type => {
const loaded_enum = ip.loadEnumType(value_index);