From 3a89324450754bc5da85e964f88161193c2f661d Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 10 Oct 2023 02:20:31 -0400 Subject: [PATCH] sema: Improvements to union and struct layout resolution - Add resolveUnionAlignment, to resolve a union's alignment only, without triggering layout resolution - Update resolveUnionLayout to cache size, alignment, and padding - Resolve a false-positive circular dependency error when a union or struct uses @typeInfo on itself (such as std.mem.zeroes) --- src/InternPool.zig | 40 +++- src/Module.zig | 50 ++--- src/Sema.zig | 185 ++++++++++++++++-- src/type.zig | 74 ++----- .../container_circular_dependency.zig | 41 ++++ 5 files changed, 279 insertions(+), 111 deletions(-) create mode 100644 test/behavior/container_circular_dependency.zig diff --git a/src/InternPool.zig b/src/InternPool.zig index 14490c21dbb5..32ad3f09f7a9 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -492,7 +492,7 @@ pub const Key = union(enum) { /// The returned pointer expires with any addition to the `InternPool`. /// Asserts the struct is not packed. - pub fn flagsPtr(self: @This(), ip: *InternPool) *Tag.TypeStruct.Flags { + pub fn flagsPtr(self: @This(), ip: *const InternPool) *Tag.TypeStruct.Flags { assert(self.layout != .Packed); const flags_field_index = std.meta.fieldIndex(Tag.TypeStruct, "flags").?; return @ptrCast(&ip.extra.items[self.extra_index + flags_field_index]); @@ -688,6 +688,18 @@ pub const Key = union(enum) { return @ptrCast(&ip.extra.items[self.extra_index + flags_field_index]); } + /// The returned pointer expires with any addition to the `InternPool`. + pub fn size(self: @This(), ip: *InternPool) *u32 { + const size_field_index = std.meta.fieldIndex(Tag.TypeUnion, "size").?; + return @ptrCast(&ip.extra.items[self.extra_index + size_field_index]); + } + + /// The returned pointer expires with any addition to the `InternPool`. + pub fn padding(self: @This(), ip: *InternPool) *u32 { + const padding_field_index = std.meta.fieldIndex(Tag.TypeUnion, "padding").?; + return @ptrCast(&ip.extra.items[self.extra_index + padding_field_index]); + } + pub fn haveFieldTypes(self: @This(), ip: *const InternPool) bool { return self.flagsPtr(ip).status.haveFieldTypes(); } @@ -1737,6 +1749,10 @@ pub const UnionType = struct { enum_tag_ty: Index, /// The integer tag type of the enum. int_tag_ty: Index, + /// ABI size of the union, including padding + size: u64, + /// Trailing padding bytes + padding: u32, /// List of field names in declaration order. field_names: NullTerminatedString.Slice, /// List of field types in declaration order. @@ -1823,6 +1839,10 @@ pub const UnionType = struct { return self.flagsPtr(ip).runtime_tag.hasTag(); } + pub fn haveFieldTypes(self: UnionType, ip: *const InternPool) bool { + return self.flagsPtr(ip).status.haveFieldTypes(); + } + pub fn haveLayout(self: UnionType, ip: *const InternPool) bool { return self.flagsPtr(ip).status.haveLayout(); } @@ -1860,6 +1880,8 @@ pub fn loadUnionType(ip: *InternPool, key: Key.UnionType) UnionType { .namespace = type_union.data.namespace, .enum_tag_ty = enum_ty, .int_tag_ty = enum_info.tag_ty, + .size = type_union.data.padding, + .padding = type_union.data.padding, .field_names = enum_info.names, .names_map = enum_info.names_map, .field_types = .{ @@ -2925,6 +2947,10 @@ pub const Tag = enum(u8) { /// 1. field align: Alignment for each field; declaration order pub const TypeUnion = struct { flags: Flags, + // Only valid after .have_layout + size: u32, + // Only valid after .have_layout + padding: u32, decl: Module.Decl.Index, namespace: Module.Namespace.Index, /// The enum that provides the list of field names and values. @@ -2939,7 +2965,9 @@ pub const Tag = enum(u8) { status: UnionType.Status, requires_comptime: RequiresComptime, assumed_runtime_bits: bool, - _: u21 = 0, + field_runtime_bits_wip: bool, + alignment: Alignment, + _: u14 = 0, }; }; @@ -2999,11 +3027,12 @@ pub const Tag = enum(u8) { requires_comptime: RequiresComptime, is_tuple: bool, assumed_runtime_bits: bool, + field_runtime_bits_wip: bool, has_namespace: bool, any_comptime_fields: bool, any_default_inits: bool, any_aligned_fields: bool, - /// `undefined` until the layout_resolved + /// `.none` until layout_resolved alignment: Alignment, /// Dependency loop detection when resolving struct alignment. alignment_wip: bool, @@ -3018,7 +3047,7 @@ pub const Tag = enum(u8) { // which `layout_resolved` does not ensure. fully_resolved: bool, - _: u11 = 0, + _: u10 = 0, }; }; }; @@ -5213,6 +5242,8 @@ pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocat const union_type_extra_index = ip.addExtraAssumeCapacity(Tag.TypeUnion{ .flags = ini.flags, + .size = std.math.maxInt(u32), + .padding = std.math.maxInt(u32), .decl = ini.decl, .namespace = ini.namespace, .tag_ty = ini.enum_tag_ty, @@ -5352,6 +5383,7 @@ pub fn getStructType( .requires_comptime = ini.requires_comptime, .is_tuple = ini.is_tuple, .assumed_runtime_bits = false, + .field_runtime_bits_wip = false, .has_namespace = ini.namespace != .none, .any_comptime_fields = ini.any_comptime_fields, .any_default_inits = ini.any_default_inits, diff --git a/src/Module.zig b/src/Module.zig index 41f4ec2b4101..eaa5aa968c89 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2504,6 +2504,8 @@ pub const CompileError = error{ /// In a comptime scope, a break instruction was encountered. This error is only seen when /// evaluating a comptime block. ComptimeBreak, + /// A dependency loop was detected when resolving if a field type has runtime bits + CircularComptimeRequirementCheck, }; pub fn init(mod: *Module) !void { @@ -4791,6 +4793,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato error.GenericPoison => unreachable, error.ComptimeReturn => unreachable, error.ComptimeBreak => unreachable, + error.CircularComptimeRequirementCheck => unreachable, else => |e| return e, }; if (opt_opv) |opv| { @@ -4820,10 +4823,10 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato inner_block.error_return_trace_index = error_return_trace_index; sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { - // TODO make these unreachable instead of @panic - error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"), - error.GenericPoison => @panic("zig compiler bug: GenericPoison"), - error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"), + error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.CircularComptimeRequirementCheck => unreachable, else => |e| return e, }; @@ -4845,11 +4848,11 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato !sema.fn_ret_ty.isError(mod)) { sema.setupErrorReturnTrace(&inner_block, last_arg_index) catch |err| switch (err) { - // TODO make these unreachable instead of @panic - error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"), - error.GenericPoison => @panic("zig compiler bug: GenericPoison"), - error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"), - error.ComptimeBreak => @panic("zig compiler bug: ComptimeBreak"), + error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, + error.CircularComptimeRequirementCheck => unreachable, else => |e| return e, }; } @@ -4877,6 +4880,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato error.GenericPoison => unreachable, error.ComptimeReturn => unreachable, error.ComptimeBreak => unreachable, + error.CircularComptimeRequirementCheck => unreachable, error.AnalysisFail => { // In this case our function depends on a type that had a compile error. // We should not try to lower this function. @@ -4901,6 +4905,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato error.GenericPoison => unreachable, error.ComptimeReturn => unreachable, error.ComptimeBreak => unreachable, + error.CircularComptimeRequirementCheck => unreachable, error.AnalysisFail => { // In this case our function depends on a type that had a compile error. // We should not try to lower this function. @@ -4918,6 +4923,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato error.GenericPoison => unreachable, error.ComptimeReturn => unreachable, error.ComptimeBreak => unreachable, + error.CircularComptimeRequirementCheck => unreachable, error.AnalysisFail => { // In this case our function depends on a type that had a compile error. // We should not try to lower this function. @@ -6516,31 +6522,11 @@ pub fn getUnionLayout(mod: *Module, u: InternPool.UnionType) UnionLayout { .padding = 0, }; } - // Put the tag before or after the payload depending on which one's - // alignment is greater. + const tag_size = u.enum_tag_ty.toType().abiSize(mod); const tag_align = u.enum_tag_ty.toType().abiAlignment(mod).max(.@"1"); - var size: u64 = 0; - var padding: u32 = undefined; - if (tag_align.compare(.gte, payload_align)) { - // {Tag, Payload} - size += tag_size; - size = payload_align.forward(size); - size += payload_size; - const prev_size = size; - size = tag_align.forward(size); - padding = @intCast(size - prev_size); - } else { - // {Payload, Tag} - size += payload_size; - size = tag_align.forward(size); - size += tag_size; - const prev_size = size; - size = payload_align.forward(size); - padding = @intCast(size - prev_size); - } return .{ - .abi_size = size, + .abi_size = u.size, .abi_align = tag_align.max(payload_align), .most_aligned_field = most_aligned_field, .most_aligned_field_size = most_aligned_field_size, @@ -6549,7 +6535,7 @@ pub fn getUnionLayout(mod: *Module, u: InternPool.UnionType) UnionLayout { .payload_align = payload_align, .tag_align = tag_align, .tag_size = tag_size, - .padding = padding, + .padding = u.padding, }; } diff --git a/src/Sema.zig b/src/Sema.zig index c9dacd118589..9f0aa48970e9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3257,6 +3257,8 @@ fn zirUnionDecl( .any_aligned_fields = small.any_aligned_fields, .requires_comptime = .unknown, .assumed_runtime_bits = false, + .field_runtime_bits_wip = false, + .alignment = .none, }, .decl = new_decl_index, .namespace = new_namespace_index, @@ -6557,16 +6559,31 @@ pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref return .none; const stack_trace_ty = sema.getBuiltinType("StackTrace") catch |err| switch (err) { - error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable, + error.NeededSourceLocation, + error.GenericPoison, + error.ComptimeReturn, + error.ComptimeBreak, + error.CircularComptimeRequirementCheck, + => unreachable, else => |e| return e, }; sema.resolveTypeFields(stack_trace_ty) catch |err| switch (err) { - error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable, + error.NeededSourceLocation, + error.GenericPoison, + error.ComptimeReturn, + error.ComptimeBreak, + error.CircularComptimeRequirementCheck, + => unreachable, else => |e| return e, }; const field_name = try mod.intern_pool.getOrPutString(gpa, "index"); const field_index = sema.structFieldIndex(block, stack_trace_ty, field_name, src) catch |err| switch (err) { - error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable, + error.NeededSourceLocation, + error.GenericPoison, + error.ComptimeReturn, + error.ComptimeBreak, + error.CircularComptimeRequirementCheck, + => unreachable, else => |e| return e, }; @@ -21051,6 +21068,8 @@ fn zirReify( .any_aligned_fields = any_aligned_fields, .requires_comptime = .unknown, .assumed_runtime_bits = false, + .field_runtime_bits_wip = false, + .alignment = .none, }, .field_types = union_fields.items(.type), .field_aligns = if (any_aligned_fields) union_fields.items(.alignment) else &.{}, @@ -34706,6 +34725,27 @@ pub fn resolveTypeLayout(sema: *Sema, ty: Type) CompileError!void { } } +fn structFieldRequiresComptime( + sema: *Sema, + struct_type: InternPool.Key.StructType, + field_ty: Type, +) CompileError!bool { + const ip = &sema.mod.intern_pool; + + if (struct_type.flagsPtr(ip).field_runtime_bits_wip) return error.CircularComptimeRequirementCheck; + + struct_type.flagsPtr(ip).field_runtime_bits_wip = true; + defer struct_type.flagsPtr(ip).field_runtime_bits_wip = false; + + return sema.typeRequiresComptime(field_ty) catch |err| switch (err) { + error.CircularComptimeRequirementCheck => b: { + struct_type.flagsPtr(ip).assumed_runtime_bits = true; + break :b false; + }, + else => return err, + }; +} + /// Resolve a struct's alignment only without triggering resolution of its layout. /// Asserts that the alignment is not yet resolved and the layout is non-packed. pub fn resolveStructAlignment( @@ -34749,7 +34789,7 @@ pub fn resolveStructAlignment( for (0..struct_type.field_types.len) |i| { const field_ty = struct_type.field_types.get(ip)[i].toType(); - if (struct_type.fieldIsComptime(ip, i) or try sema.typeRequiresComptime(field_ty)) + if (struct_type.fieldIsComptime(ip, i) or try structFieldRequiresComptime(sema, struct_type, field_ty)) continue; const field_align = try sema.structFieldAlignment( struct_type.fieldAlign(ip, i), @@ -34778,6 +34818,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { return; } + if (struct_type.flagsPtr(ip).field_runtime_bits_wip) return error.CircularComptimeRequirementCheck; if (struct_type.setLayoutWip(ip)) { const msg = try Module.ErrorMsg.create( sema.gpa, @@ -34793,10 +34834,9 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { const sizes = try sema.arena.alloc(u64, struct_type.field_types.len); var big_align: Alignment = .@"1"; - for (aligns, sizes, 0..) |*field_align, *field_size, i| { const field_ty = struct_type.field_types.get(ip)[i].toType(); - if (struct_type.fieldIsComptime(ip, i) or try sema.typeRequiresComptime(field_ty)) { + if (struct_type.fieldIsComptime(ip, i) or try structFieldRequiresComptime(sema, struct_type, field_ty)) { struct_type.offsets.get(ip)[i] = 0; field_size.* = 0; field_align.* = .none; @@ -35037,11 +35077,78 @@ fn checkMemOperand(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void return sema.failWithOwnedErrorMsg(block, msg); } +fn unionFieldHasRuntimeBits( + sema: *Sema, + union_type: InternPool.Key.UnionType, + field_ty: Type, +) CompileError!bool { + const ip = &sema.mod.intern_pool; + + if (union_type.flagsPtr(ip).field_runtime_bits_wip) return error.CircularComptimeRequirementCheck; + + union_type.flagsPtr(ip).field_runtime_bits_wip = true; + defer union_type.flagsPtr(ip).field_runtime_bits_wip = false; + + return (sema.typeHasRuntimeBits(field_ty) catch |err| switch (err) { + error.CircularComptimeRequirementCheck => b: { + union_type.flagsPtr(ip).assumed_runtime_bits = true; + break :b true; + }, + else => return err, + }); +} + +/// Resolve a unions's alignment only without triggering resolution of its layout. +/// Asserts that the alignment is not yet resolved. +pub fn resolveUnionAlignment( + sema: *Sema, + ty: Type, + union_type: InternPool.Key.UnionType, +) CompileError!Alignment { + const mod = sema.mod; + const ip = &mod.intern_pool; + const target = mod.getTarget(); + + assert(!union_type.haveLayout(ip)); + + if (union_type.flagsPtr(ip).status == .field_types_wip) { + // We'll guess "pointer-aligned", if the union has an + // underaligned pointer field then some allocations + // might require explicit alignment. + return Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8)); + } + + try sema.resolveTypeFieldsUnion(ty, union_type); + + const union_obj = ip.loadUnionType(union_type); + var max_align: Alignment = .@"1"; + for (0..union_obj.field_names.len) |field_index| { + const field_ty = union_obj.field_types.get(ip)[field_index].toType(); + if (!(try unionFieldHasRuntimeBits(sema, union_type, field_ty))) continue; + + const explicit_align = union_obj.fieldAlign(ip, @intCast(field_index)); + const field_align = if (explicit_align != .none) + explicit_align + else + try sema.typeAbiAlignment(field_ty); + + max_align = max_align.max(field_align); + } + + union_type.flagsPtr(ip).alignment = max_align; + return max_align; +} + +/// This logic must be kept in sync with `Module.getUnionLayout`. fn resolveUnionLayout(sema: *Sema, ty: Type) CompileError!void { const mod = sema.mod; const ip = &mod.intern_pool; - try sema.resolveTypeFields(ty); - const union_obj = mod.typeToUnion(ty).?; + + const union_type = ip.indexToKey(ty.ip_index).union_type; + try sema.resolveTypeFieldsUnion(ty, union_type); + + const union_obj = ip.loadUnionType(union_type); + if (union_obj.flagsPtr(ip).field_runtime_bits_wip) return error.CircularComptimeRequirementCheck; switch (union_obj.flagsPtr(ip).status) { .none, .have_field_types => {}, .field_types_wip, .layout_wip => { @@ -35055,25 +35162,74 @@ fn resolveUnionLayout(sema: *Sema, ty: Type) CompileError!void { }, .have_layout, .fully_resolved_wip, .fully_resolved => return, } + const prev_status = union_obj.flagsPtr(ip).status; errdefer if (union_obj.flagsPtr(ip).status == .layout_wip) { union_obj.flagsPtr(ip).status = prev_status; }; union_obj.flagsPtr(ip).status = .layout_wip; - for (0..union_obj.field_types.len) |field_index| { + + var max_size: u64 = 0; + var max_align: Alignment = .@"1"; + for (0..union_obj.field_names.len) |field_index| { const field_ty = union_obj.field_types.get(ip)[field_index].toType(); - sema.resolveTypeLayout(field_ty) catch |err| switch (err) { + if (!(try unionFieldHasRuntimeBits(sema, union_type, field_ty))) continue; + + max_size = @max(max_size, sema.typeAbiSize(field_ty) catch |err| switch (err) { error.AnalysisFail => { const msg = sema.err orelse return err; try sema.addFieldErrNote(ty, field_index, msg, "while checking this field", .{}); return err; }, else => return err, - }; - } - union_obj.flagsPtr(ip).status = .have_layout; - _ = try sema.typeRequiresComptime(ty); + }); + + const explicit_align = union_obj.fieldAlign(ip, @intCast(field_index)); + const field_align = if (explicit_align != .none) + explicit_align + else + try sema.typeAbiAlignment(field_ty); + + max_align = max_align.max(field_align); + } + + const flags = union_obj.flagsPtr(ip); + const has_runtime_tag = flags.runtime_tag.hasTag() and try sema.typeHasRuntimeBits(union_obj.enum_tag_ty.toType()); + const size, const alignment, const padding = if (has_runtime_tag) layout: { + const enum_tag_type = union_obj.enum_tag_ty.toType(); + const tag_align = try sema.typeAbiAlignment(enum_tag_type); + const tag_size = try sema.typeAbiSize(enum_tag_type); + + // Put the tag before or after the payload depending on which one's + // alignment is greater. + var size: u64 = 0; + var padding: u32 = 0; + if (tag_align.compare(.gte, max_align)) { + // {Tag, Payload} + size += tag_size; + size = max_align.forward(size); + size += max_size; + const prev_size = size; + size = tag_align.forward(size); + padding = @intCast(size - prev_size); + } else { + // {Payload, Tag} + size += max_size; + size = tag_align.forward(size); + size += tag_size; + const prev_size = size; + size = max_align.forward(size); + padding = @intCast(size - prev_size); + } + + break :layout .{ size, max_align.max(tag_align), padding }; + } else .{ max_align.forward(max_size), max_align, 0 }; + + union_type.size(ip).* = @intCast(size); + union_type.padding(ip).* = padding; + flags.alignment = alignment; + flags.status = .have_layout; if (union_obj.flagsPtr(ip).assumed_runtime_bits and !(try sema.typeHasRuntimeBits(ty))) { const msg = try Module.ErrorMsg.create( @@ -35150,7 +35306,6 @@ fn resolveStructFully(sema: *Sema, ty: Type) CompileError!void { fn resolveUnionFully(sema: *Sema, ty: Type) CompileError!void { try sema.resolveUnionLayout(ty); - try sema.resolveTypeFields(ty); const mod = sema.mod; const ip = &mod.intern_pool; diff --git a/src/type.zig b/src/type.zig index 5bdca2766782..b6250e1d3405 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1031,66 +1031,20 @@ pub const Type = struct { } return .{ .scalar = big_align }; }, - .union_type => |union_type| { - if (opt_sema) |sema| { - if (union_type.flagsPtr(ip).status == .field_types_wip) { - // We'll guess "pointer-aligned", if the union has an - // underaligned pointer field then some allocations - // might require explicit alignment. - return .{ .scalar = Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8)) }; - } - _ = try sema.resolveTypeFields(ty); - } - if (!union_type.haveFieldTypes(ip)) switch (strat) { + const flags = union_type.flagsPtr(ip).*; + if (flags.alignment != .none) return .{ .scalar = flags.alignment }; + + if (!union_type.haveLayout(ip)) switch (strat) { .eager => unreachable, // union layout not resolved - .sema => unreachable, // handled above + .sema => |sema| return .{ .scalar = try sema.resolveUnionAlignment(ty, union_type) }, .lazy => return .{ .val = (try mod.intern(.{ .int = .{ .ty = .comptime_int_type, .storage = .{ .lazy_align = ty.toIntern() }, } })).toValue() }, }; - const union_obj = ip.loadUnionType(union_type); - if (union_obj.field_names.len == 0) { - if (union_obj.hasTag(ip)) { - return abiAlignmentAdvanced(union_obj.enum_tag_ty.toType(), mod, strat); - } else { - return .{ .scalar = .@"1" }; - } - } - var max_align: Alignment = .@"1"; - if (union_obj.hasTag(ip)) max_align = union_obj.enum_tag_ty.toType().abiAlignment(mod); - for (0..union_obj.field_names.len) |field_index| { - const field_ty = union_obj.field_types.get(ip)[field_index].toType(); - const field_align = if (union_obj.field_aligns.len == 0) - .none - else - union_obj.field_aligns.get(ip)[field_index]; - if (!(field_ty.hasRuntimeBitsAdvanced(mod, false, strat) catch |err| switch (err) { - error.NeedLazy => return .{ .val = (try mod.intern(.{ .int = .{ - .ty = .comptime_int_type, - .storage = .{ .lazy_align = ty.toIntern() }, - } })).toValue() }, - else => |e| return e, - })) continue; - - const field_align_bytes: Alignment = if (field_align != .none) - field_align - else switch (try field_ty.abiAlignmentAdvanced(mod, strat)) { - .scalar => |a| a, - .val => switch (strat) { - .eager => unreachable, // struct layout not resolved - .sema => unreachable, // handled above - .lazy => return .{ .val = (try mod.intern(.{ .int = .{ - .ty = .comptime_int_type, - .storage = .{ .lazy_align = ty.toIntern() }, - } })).toValue() }, - }, - }; - max_align = max_align.max(field_align_bytes); - } - return .{ .scalar = max_align }; + return .{ .scalar = union_type.flagsPtr(ip).alignment }; }, .opaque_type => return .{ .scalar = .@"1" }, .enum_type => |enum_type| return .{ @@ -1445,8 +1399,8 @@ pub const Type = struct { }, .eager => {}, } - const union_obj = ip.loadUnionType(union_type); - return AbiSizeAdvanced{ .scalar = mod.unionAbiSize(union_obj) }; + + return .{ .scalar = union_type.size(ip).* }; }, .opaque_type => unreachable, // no size available .enum_type => |enum_type| return AbiSizeAdvanced{ .scalar = enum_type.tag_ty.toType().abiSize(mod) }, @@ -2680,11 +2634,11 @@ pub const Type = struct { if (struct_type.flagsPtr(ip).field_types_wip) return false; - try sema.resolveTypeFieldsStruct(ty.toIntern(), struct_type); - struct_type.flagsPtr(ip).requires_comptime = .wip; errdefer struct_type.flagsPtr(ip).requires_comptime = .unknown; + try sema.resolveTypeFieldsStruct(ty.toIntern(), struct_type); + for (0..struct_type.field_types.len) |i_usize| { const i: u32 = @intCast(i_usize); if (struct_type.fieldIsComptime(ip, i)) continue; @@ -2723,12 +2677,12 @@ pub const Type = struct { if (union_type.flagsPtr(ip).status == .field_types_wip) return false; - try sema.resolveTypeFieldsUnion(ty, union_type); - const union_obj = ip.loadUnionType(union_type); + union_type.flagsPtr(ip).requires_comptime = .wip; + errdefer union_type.flagsPtr(ip).requires_comptime = .unknown; - union_obj.flagsPtr(ip).requires_comptime = .wip; - errdefer union_obj.flagsPtr(ip).requires_comptime = .unknown; + try sema.resolveTypeFieldsUnion(ty, union_type); + const union_obj = ip.loadUnionType(union_type); for (0..union_obj.field_types.len) |field_idx| { const field_ty = union_obj.field_types.get(ip)[field_idx]; if (try field_ty.toType().comptimeOnlyAdvanced(mod, opt_sema)) { diff --git a/test/behavior/container_circular_dependency.zig b/test/behavior/container_circular_dependency.zig new file mode 100644 index 000000000000..4b412978d131 --- /dev/null +++ b/test/behavior/container_circular_dependency.zig @@ -0,0 +1,41 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; + +const UnionInner = extern struct { + outer: UnionOuter = std.mem.zeroes(UnionOuter), +}; + +const Union = extern union { + outer: ?*UnionOuter, + inner: ?*UnionInner, +}; + +const UnionOuter = extern struct { + u: Union = std.mem.zeroes(Union), +}; + +test "circular dependency through pointer field of a union" { + var outer: UnionOuter = .{}; + try expect(outer.u.outer == null); + try expect(outer.u.inner == null); +} + +const StructInner = extern struct { + outer: StructOuter = std.mem.zeroes(StructOuter), +}; + +const StructMiddle = extern struct { + outer: ?*StructInner, + inner: ?*StructOuter, +}; + +const StructOuter = extern struct { + middle: StructMiddle = std.mem.zeroes(StructMiddle), +}; + +test "circular dependency through pointer field of a struct" { + var outer: StructOuter = .{}; + try expect(outer.middle.outer == null); + try expect(outer.middle.inner == null); +}