authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-02-27 10:40:12+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-10 10:26:13+00:00
log51c23f7ba4f09f26f53b21299a456f2ed9d7839e
tree48ef4833fe4b90f8dd3866c9060cfeec3b227be3
parent0075c5a1d5ad1198b16e6a1c390c985a8e4354de
signaturelock-open Commit is signed but in an unrecognized format.

compiler: split default field values back out from layout resolution

I was trying out combining struct layout resolution with resolution of default field values, but it broke a few cases which it's not clear we want to break. The simplest such case was a struct with a field which was a slice of itself, with a default value of `&.{}`. So, at least for now, I'm accepting defeat and splitting this back out. This allows a couple of behavior tests which were removed to be re-introduced---I will do that in the commit following this one. I have *not* made this separate phase of resolution "lazy": instead, it is tied to layout resolution, in the sense that if a struct's layout is referenced, then its default field values are also referenced. I chose this approach for simplicity---not of the implementation (it's actually slightly *more* code to do it this way!), but in terms of the language specification. I think this behavior is easier to understand and keep in your head. It can be easily changed in future if we decide we want to. This partially reverts the commit titled "compiler: merge struct default value resolution into layout resolution".

9 files changed, 396 insertions(+), 72 deletions(-)

src/Compilation.zig+5-1
......@@ -3647,6 +3647,7 @@ const Header = extern struct {
36473647 nav_val_deps_len: u32,
36483648 nav_ty_deps_len: u32,
36493649 type_layout_deps_len: u32,
3650 struct_defaults_deps_len: u32,
36503651 func_ies_deps_len: u32,
36513652 zon_file_deps_len: u32,
36523653 embed_file_deps_len: u32,
......@@ -3696,6 +3697,7 @@ pub fn saveState(comp: *Compilation) !void {
36963697 .nav_val_deps_len = @intCast(ip.nav_val_deps.count()),
36973698 .nav_ty_deps_len = @intCast(ip.nav_ty_deps.count()),
36983699 .type_layout_deps_len = @intCast(ip.type_layout_deps.count()),
3700 .struct_defaults_deps_len = @intCast(ip.struct_defaults_deps.count()),
36993701 .func_ies_deps_len = @intCast(ip.func_ies_deps.count()),
37003702 .zon_file_deps_len = @intCast(ip.zon_file_deps.count()),
37013703 .embed_file_deps_len = @intCast(ip.embed_file_deps.count()),
......@@ -3720,7 +3722,7 @@ pub fn saveState(comp: *Compilation) !void {
37203722 },
37213723 });
37223724
3723 try bufs.ensureTotalCapacityPrecise(24 + 9 * pt_headers.items.len);
3725 try bufs.ensureTotalCapacityPrecise(26 + 9 * pt_headers.items.len);
37243726 addBuf(&bufs, mem.asBytes(&header));
37253727 addBuf(&bufs, @ptrCast(pt_headers.items));
37263728
......@@ -3732,6 +3734,8 @@ pub fn saveState(comp: *Compilation) !void {
37323734 addBuf(&bufs, @ptrCast(ip.nav_ty_deps.values()));
37333735 addBuf(&bufs, @ptrCast(ip.type_layout_deps.keys()));
37343736 addBuf(&bufs, @ptrCast(ip.type_layout_deps.values()));
3737 addBuf(&bufs, @ptrCast(ip.struct_defaults_deps.keys()));
3738 addBuf(&bufs, @ptrCast(ip.struct_defaults_deps.values()));
37353739 addBuf(&bufs, @ptrCast(ip.func_ies_deps.keys()));
37363740 addBuf(&bufs, @ptrCast(ip.func_ies_deps.values()));
37373741 addBuf(&bufs, @ptrCast(ip.zon_file_deps.keys()));
src/IncrementalDebugServer.zig+3-1
......@@ -307,7 +307,7 @@ fn handleCommand(zcu: *Zcu, w: *Io.Writer, cmd_str: []const u8, arg_str: []const
307307 switch (dependee) {
308308 .src_hash, .namespace, .namespace_name, .zon_file, .embed_file => try w.print("{f}", .{zcu.fmtDependee(dependee)}),
309309 .nav_val, .nav_ty => |nav| try w.print("{t} {d}", .{ dependee, @intFromEnum(nav) }),
310 .type_layout, .func_ies => |ip_index| try w.print("{t} {d}", .{ dependee, @intFromEnum(ip_index) }),
310 .type_layout, .struct_defaults, .func_ies => |ip_index| try w.print("{t} {d}", .{ dependee, @intFromEnum(ip_index) }),
311311 .memoized_state => |stage| try w.print("memoized_state {s}", .{@tagName(stage)}),
312312 }
313313 try w.writeByte('\n');
......@@ -374,6 +374,8 @@ fn parseAnalUnit(str: []const u8) ?AnalUnit {
374374 return .wrap(.{ .nav_ty = @enumFromInt(parseIndex(idx_str) orelse return null) });
375375 } else if (std.mem.eql(u8, kind, "type_layout")) {
376376 return .wrap(.{ .type_layout = @enumFromInt(parseIndex(idx_str) orelse return null) });
377 } else if (std.mem.eql(u8, kind, "struct_defaults")) {
378 return .wrap(.{ .struct_defaults = @enumFromInt(parseIndex(idx_str) orelse return null) });
377379 } else if (std.mem.eql(u8, kind, "func")) {
378380 return .wrap(.{ .func = @enumFromInt(parseIndex(idx_str) orelse return null) });
379381 } else if (std.mem.eql(u8, kind, "memoized_state")) {
src/InternPool.zig+17-1
......@@ -54,6 +54,9 @@ func_ies_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),
5454/// Dependencies on the resolved layout of a `struct`, `union`, or `enum` type.
5555/// Value is index into `dep_entries` of the first dependency on this type's layout.
5656type_layout_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),
57/// Dependencies on the resolved default field values of a `struct` type.
58/// Value is index into `dep_entries` of the first dependency on this type's inits.
59struct_defaults_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),
5760/// Dependencies on a ZON file. Triggered by `@import` of ZON.
5861/// Value is index into `dep_entries` of the first dependency on this ZON file.
5962zon_file_deps: std.AutoArrayHashMapUnmanaged(FileIndex, DepEntry.Index),
......@@ -108,6 +111,7 @@ pub const empty: InternPool = .{
108111 .nav_ty_deps = .empty,
109112 .func_ies_deps = .empty,
110113 .type_layout_deps = .empty,
114 .struct_defaults_deps = .empty,
111115 .zon_file_deps = .empty,
112116 .embed_file_deps = .empty,
113117 .namespace_deps = .empty,
......@@ -419,6 +423,7 @@ pub const AnalUnit = packed struct(u64) {
419423 nav_val,
420424 nav_ty,
421425 type_layout,
426 struct_defaults,
422427 func,
423428 memoized_state,
424429 };
......@@ -432,6 +437,8 @@ pub const AnalUnit = packed struct(u64) {
432437 nav_ty: Nav.Index,
433438 /// This `AnalUnit` resolves the layout of the given `struct`, `union`, or `enum` type.
434439 type_layout: InternPool.Index,
440 /// This `AnalUnit` resolves the default field values of the given `struct` type.
441 struct_defaults: InternPool.Index,
435442 /// This `AnalUnit` analyzes the body of the given runtime function.
436443 func: InternPool.Index,
437444 /// This `AnalUnit` resolves all state which is memoized in fields on `Zcu`.
......@@ -851,6 +858,7 @@ pub const Dependee = union(enum) {
851858 /// Index is the function, not its IES.
852859 func_ies: Index,
853860 type_layout: Index,
861 struct_defaults: Index,
854862 zon_file: FileIndex,
855863 embed_file: Zcu.EmbedFile.Index,
856864 namespace: TrackedInst.Index,
......@@ -904,6 +912,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
904912 .nav_ty => |x| ip.nav_ty_deps.get(x),
905913 .func_ies => |x| ip.func_ies_deps.get(x),
906914 .type_layout => |x| ip.type_layout_deps.get(x),
915 .struct_defaults => |x| ip.struct_defaults_deps.get(x),
907916 .zon_file => |x| ip.zon_file_deps.get(x),
908917 .embed_file => |x| ip.embed_file_deps.get(x),
909918 .namespace => |x| ip.namespace_deps.get(x),
......@@ -978,6 +987,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
978987 .nav_ty => ip.nav_ty_deps,
979988 .func_ies => ip.func_ies_deps,
980989 .type_layout => ip.type_layout_deps,
990 .struct_defaults => ip.struct_defaults_deps,
981991 .zon_file => ip.zon_file_deps,
982992 .embed_file => ip.embed_file_deps,
983993 .namespace => ip.namespace_deps,
......@@ -6454,6 +6464,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator, io: Io) void {
64546464 ip.nav_ty_deps.deinit(gpa);
64556465 ip.func_ies_deps.deinit(gpa);
64566466 ip.type_layout_deps.deinit(gpa);
6467 ip.struct_defaults_deps.deinit(gpa);
64576468 ip.zon_file_deps.deinit(gpa);
64586469 ip.embed_file_deps.deinit(gpa);
64596470 ip.namespace_deps.deinit(gpa);
......@@ -10619,6 +10630,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1061910630 const nav_ty_deps_len = ip.nav_ty_deps.count();
1062010631 const func_ies_deps_len = ip.func_ies_deps.count();
1062110632 const type_layout_deps_len = ip.type_layout_deps.count();
10633 const struct_defaults_deps_len = ip.struct_defaults_deps.count();
1062210634 const zon_file_deps_len = ip.zon_file_deps.count();
1062310635 const embed_file_deps_len = ip.embed_file_deps.count();
1062410636 const namespace_deps_len = ip.namespace_deps.count();
......@@ -10629,6 +10641,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1062910641 const nav_ty_deps_size = nav_ty_deps_len * 8;
1063010642 const func_ies_deps_size = func_ies_deps_len * 8;
1063110643 const type_layout_deps_size = type_layout_deps_len * 8;
10644 const struct_defaults_deps_size = struct_defaults_deps_len * 8;
1063210645 const zon_file_deps_size = zon_file_deps_len * 8;
1063310646 const embed_file_deps_size = embed_file_deps_len * 8;
1063410647 const namespace_deps_size = namespace_deps_len * 8;
......@@ -10642,6 +10655,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1064210655 \\ {d} nav_ty: {d} bytes
1064310656 \\ {d} func_ies: {d} bytes
1064410657 \\ {d} type_layout: {d} bytes
10658 \\ {d} struct_defaults: {d} bytes
1064510659 \\ {d} zon_file: {d} bytes
1064610660 \\ {d} embed_file: {d} bytes
1064710661 \\ {d} namespace: {d} bytes
......@@ -10649,7 +10663,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1064910663 \\
1065010664 , .{
1065110665 dep_entries_size + src_hash_deps_size + nav_val_deps_size + nav_ty_deps_size +
10652 func_ies_deps_size + type_layout_deps_size + zon_file_deps_size +
10666 func_ies_deps_size + type_layout_deps_size + struct_defaults_deps_size + zon_file_deps_size +
1065310667 embed_file_deps_size + namespace_deps_size + namespace_name_deps_size,
1065410668 dep_entries_len,
1065510669 dep_entries_size,
......@@ -10663,6 +10677,8 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void {
1066310677 func_ies_deps_size,
1066410678 type_layout_deps_len,
1066510679 type_layout_deps_size,
10680 struct_defaults_deps_len,
10681 struct_defaults_deps_size,
1066610682 zon_file_deps_len,
1066710683 zon_file_deps_size,
1066810684 embed_file_deps_len,
src/Sema.zig+35-21
......@@ -4533,6 +4533,10 @@ fn validateStructInit(
45334533 if (explicit) continue;
45344534 if (struct_ty.structFieldIsComptime(i, zcu)) continue;
45354535
4536 if (!struct_ty.isTuple(zcu)) {
4537 try sema.ensureStructDefaultsResolved(struct_ty, init_src);
4538 }
4539
45364540 const default_val = struct_ty.structFieldDefaultValue(i, zcu) orelse {
45374541 const field_name = struct_ty.structFieldName(i, zcu).unwrap() orelse {
45384542 const template = "missing tuple field with index {d}";
......@@ -5850,6 +5854,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void {
58505854 .nav_val,
58515855 .nav_ty,
58525856 .type_layout,
5857 .struct_defaults,
58535858 .memoized_state,
58545859 => return, // does nothing outside a function
58555860 };
......@@ -5868,6 +5873,7 @@ fn zirDisableIntrinsics(sema: *Sema) CompileError!void {
58685873 .nav_val,
58695874 .nav_ty,
58705875 .type_layout,
5876 .struct_defaults,
58715877 .memoized_state,
58725878 => return, // does nothing outside a function
58735879 };
......@@ -7091,7 +7097,14 @@ fn analyzeCall(
70917097 });
70927098 if (func_ty_info.cc == .auto) {
70937099 switch (sema.owner.unwrap()) {
7094 .@"comptime", .nav_ty, .nav_val, .type_layout, .memoized_state => {},
7100 .@"comptime",
7101 .nav_ty,
7102 .nav_val,
7103 .type_layout,
7104 .struct_defaults,
7105 .memoized_state,
7106 => {},
7107
70957108 .func => |owner_func| ip.funcSetHasErrorTrace(io, owner_func, true),
70967109 }
70977110 }
......@@ -16907,6 +16920,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
1690716920 .struct_type => ip.loadStructType(ty.toIntern()),
1690816921 else => unreachable,
1690916922 };
16923 try sema.ensureStructDefaultsResolved(ty, src); // can't do this sooner, since it's not allowed on tuples
1691016924 struct_field_vals = try gpa.alloc(InternPool.Index, struct_type.field_types.len);
1691116925
1691216926 for (struct_field_vals, 0..) |*field_val, field_index| {
......@@ -18788,6 +18802,8 @@ fn finishStructInit(
1878818802 continue;
1878918803 }
1879018804
18805 try sema.ensureStructDefaultsResolved(struct_ty, init_src);
18806
1879118807 const field_default: InternPool.Index = d: {
1879218808 if (struct_type.field_defaults.len == 0) break :d .none;
1879318809 break :d struct_type.field_defaults.get(ip)[i];
......@@ -19454,7 +19470,14 @@ fn getErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref {
1945419470 .func => |func| if (ip.funcAnalysisUnordered(func).has_error_trace and block.ownerModule().error_tracing) {
1945519471 return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty);
1945619472 },
19457 .@"comptime", .nav_ty, .nav_val, .type_layout, .memoized_state => {},
19473
19474 .@"comptime",
19475 .nav_ty,
19476 .nav_val,
19477 .type_layout,
19478 .struct_defaults,
19479 .memoized_state,
19480 => {},
1945819481 }
1945919482 return Air.internedToRef(try pt.intern(.{ .opt = .{
1946019483 .ty = opt_ptr_stack_trace_ty.toIntern(),
......@@ -24784,7 +24807,7 @@ fn zirBuiltinExtern(
2478424807 // So, for now, just use our containing `declaration`.
2478524808 .zir_index = switch (sema.owner.unwrap()) {
2478624809 .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index,
24787 .type_layout => |owner_ty| Type.fromInterned(owner_ty).typeDeclInstAllowGeneratedTag(zcu).?,
24810 .type_layout, .struct_defaults => |owner_ty| Type.fromInterned(owner_ty).typeDeclInstAllowGeneratedTag(zcu).?,
2478824811 .memoized_state => unreachable,
2478924812 .nav_ty, .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index,
2479024813 .func => |func| zir_index: {
......@@ -25276,7 +25299,14 @@ fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !In
2527625299 try sema.ensureMemoizedStateResolved(src, .panic);
2527725300 const panic_fn_index = zcu.builtin_decl_values.get(panic_id.toBuiltin());
2527825301 switch (sema.owner.unwrap()) {
25279 .@"comptime", .nav_ty, .nav_val, .type_layout, .memoized_state => {},
25302 .@"comptime",
25303 .nav_ty,
25304 .nav_val,
25305 .type_layout,
25306 .struct_defaults,
25307 .memoized_state,
25308 => {},
25309
2528025310 .func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(io, owner_func, true),
2528125311 }
2528225312 return panic_fn_index;
......@@ -33541,23 +33571,6 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
3354133571 const gop = try sema.dependencies.getOrPut(sema.gpa, dependee);
3354233572 if (gop.found_existing) return;
3354333573
33544 // Avoid creating dependencies on ourselves. This situation can arise when we analyze the fields
33545 // of a type and they use `@This()`. This dependency would be unnecessary, and in fact would
33546 // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve
33547 // the loop.
33548 // Note that this also disallows a `nav_val`
33549 switch (sema.owner.unwrap()) {
33550 .nav_val => |this_nav| switch (dependee) {
33551 .nav_val => |other_nav| if (this_nav == other_nav) return,
33552 else => {},
33553 },
33554 .nav_ty => |this_nav| switch (dependee) {
33555 .nav_ty => |other_nav| if (this_nav == other_nav) return,
33556 else => {},
33557 },
33558 else => {},
33559 }
33560
3356133574 try pt.addDependency(sema.owner, dependee);
3356233575}
3356333576
......@@ -33923,6 +33936,7 @@ const ComptimeStoreResult = @import("Sema/comptime_ptr_access.zig").ComptimeStor
3392333936
3392433937pub const type_resolution = @import("Sema/type_resolution.zig");
3392533938pub const ensureLayoutResolved = type_resolution.ensureLayoutResolved;
33939pub const ensureStructDefaultsResolved = type_resolution.ensureStructDefaultsResolved;
3392633940
3392733941pub fn getBuiltinType(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl) SemaError!Type {
3392833942 assert(decl.kind() == .type);
src/Sema/LowerZon.zig+1
......@@ -758,6 +758,7 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool
758758 const ip = &pt.zcu.intern_pool;
759759
760760 try self.sema.ensureLayoutResolved(res_ty, self.import_loc, .init);
761 try self.sema.ensureStructDefaultsResolved(res_ty, self.import_loc);
761762 const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?;
762763
763764 const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) {
src/Sema/type_resolution.zig+158-40
......@@ -134,6 +134,33 @@ fn ensureLayoutResolvedInner(sema: *Sema, ty: Type, orig_ty: Type, reason: *cons
134134 }
135135}
136136
137/// Asserts that `ty` is a non-tuple `struct` type, and ensures that its fields' default values
138/// are resolved. Adds incremental dependencies tracking the required type resolution.
139///
140/// It is not necessary to call this function to query the values of comptime fields: those values
141/// are available from type *layout* resolution, see `ensureLayoutResolved`.
142///
143/// Asserts that the *layout* of `ty` has already been resolved---see `ensureLayoutResolved`.
144pub fn ensureStructDefaultsResolved(sema: *Sema, ty: Type, src: LazySrcLoc) SemaError!void {
145 const pt = sema.pt;
146 const zcu = pt.zcu;
147 const ip = &zcu.intern_pool;
148
149 assert(ip.indexToKey(ty.toIntern()) == .struct_type);
150 if (zcu.comp.config.incremental) assert(sema.dependencies.contains(.{ .type_layout = ty.toIntern() }));
151
152 try sema.declareDependency(.{ .struct_defaults = ty.toIntern() });
153 try sema.addReferenceEntry(null, src, .wrap(.{ .struct_defaults = ty.toIntern() }));
154
155 const reason: Zcu.DependencyReason = .{ .src = src, .type_layout_reason = undefined };
156
157 if (zcu.analysis_in_progress.contains(.wrap(.{ .struct_defaults = ty.toIntern() }))) {
158 return sema.failWithDependencyLoop(.wrap(.{ .struct_defaults = ty.toIntern() }), &reason);
159 }
160
161 try pt.ensureStructDefaultsUpToDate(ty, &reason);
162}
163
137164/// Asserts that `struct_ty` is a non-packed non-tuple struct, and that `sema.owner` is that type.
138165/// This function *does* register the `src_hash` dependency on the struct.
139166pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void {
......@@ -193,14 +220,8 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void {
193220 @memset(struct_obj.field_is_comptime_bits.getAll(ip), 0);
194221
195222 const zir_struct = sema.code.getStructDecl(zir_index);
196
197 // If we have any default values to resolve, we'll need to map the struct decl instruction
198 // to the result type.
199 if (zir_struct.field_default_body_lens != null) {
200 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{zir_index});
201 }
202
203223 var field_it = zir_struct.iterateFields();
224 var any_comptime_fields = false;
204225 while (field_it.next()) |zir_field| {
205226 {
206227 const name_slice = sema.code.nullTerminatedString(zir_field.name);
......@@ -212,18 +233,21 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void {
212233 const bit_bag_index = zir_field.idx / 32;
213234 const mask = @as(u32, 1) << @intCast(zir_field.idx % 32);
214235 struct_obj.field_is_comptime_bits.getAll(ip)[bit_bag_index] |= mask;
236 any_comptime_fields = true;
215237 }
216238
217 const field_ty: Type = field_ty: {
239 {
218240 const field_ty_src = block.src(.{ .container_field_type = zir_field.idx });
219 block.comptime_reason = .{ .reason = .{
220 .src = field_ty_src,
221 .r = .{ .simple = .struct_field_types },
222 } };
223 const type_ref = try sema.resolveInlineBody(&block, zir_field.type_body, zir_index);
224 break :field_ty try sema.analyzeAsType(&block, field_ty_src, .struct_field_types, type_ref);
225 };
226 struct_obj.field_types.get(ip)[zir_field.idx] = field_ty.toIntern();
241 const field_ty: Type = field_ty: {
242 block.comptime_reason = .{ .reason = .{
243 .src = field_ty_src,
244 .r = .{ .simple = .struct_field_types },
245 } };
246 const type_ref = try sema.resolveInlineBody(&block, zir_field.type_body, zir_index);
247 break :field_ty try sema.analyzeAsType(&block, field_ty_src, .struct_field_types, type_ref);
248 };
249 struct_obj.field_types.get(ip)[zir_field.idx] = field_ty.toIntern();
250 }
227251
228252 if (struct_obj.field_aligns.len == 0) {
229253 assert(zir_field.align_body == null);
......@@ -240,31 +264,13 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void {
240264 };
241265 struct_obj.field_aligns.get(ip)[zir_field.idx] = field_align;
242266 }
267 }
243268
244 if (struct_obj.field_defaults.len == 0) {
245 assert(zir_field.default_body == null);
246 } else {
247 const field_default_src = block.src(.{ .container_field_value = zir_field.idx });
248 const field_default: InternPool.Index = d: {
249 block.comptime_reason = .{ .reason = .{
250 .src = field_default_src,
251 .r = .{ .simple = .struct_field_default_value },
252 } };
253 const default_body = zir_field.default_body orelse break :d .none;
254 // Provide the result type
255 sema.inst_map.putAssumeCapacity(zir_index, .fromType(field_ty));
256 defer assert(sema.inst_map.remove(zir_index));
257 const uncoerced_default_val = try sema.resolveInlineBody(&block, default_body, zir_index);
258 const coerced_default_val = try sema.coerce(&block, field_ty, uncoerced_default_val, field_default_src);
259 const default_val = try sema.resolveConstValue(&block, field_default_src, coerced_default_val, null);
260 if (default_val.canMutateComptimeVarState(zcu)) {
261 const field_name = struct_obj.field_names.get(ip)[zir_field.idx];
262 return sema.failWithContainsReferenceToComptimeVar(&block, field_default_src, field_name, "field default value", default_val);
263 }
264 break :d default_val.toIntern();
265 };
266 struct_obj.field_defaults.get(ip)[zir_field.idx] = field_default;
267 }
269 // We also resolve the default values of any `comptime` fields now. This is not necessary in
270 // the case of a reified struct because the the default values were already poulated and
271 // validated by `Sema.zirReifyStruct`.
272 if (any_comptime_fields) {
273 try resolveStructDefaultsInner(sema, &block, &struct_obj, .comptime_fields);
268274 }
269275 }
270276
......@@ -523,6 +529,118 @@ fn resolvePackedStructLayout(
523529 );
524530}
525531
532/// Asserts that `struct_ty` is a non-tuple struct, and that `sema.owner` is that type.
533///
534/// Also asserts that the layout of `struct_ty` has *already* been resolved (though it is okay for
535/// that resolution to have failed). This requirement exists to ensure better error messages in the
536/// event of a dependency loop.
537///
538/// This function *does* register the `src_hash` dependency on the struct.
539pub fn resolveStructDefaults(sema: *Sema, struct_ty: Type) CompileError!void {
540 const pt = sema.pt;
541 const zcu = pt.zcu;
542 const comp = zcu.comp;
543 const gpa = comp.gpa;
544 const ip = &zcu.intern_pool;
545
546 assert(sema.owner.unwrap().struct_defaults == struct_ty.toIntern());
547
548 // We always depend on the layout of `struct_ty`. However, we don't actually need to resolve it
549 // now, because the caller has done so for us. Just mark the dependency so that the incremental
550 // compilation handling understands the dependency graph.
551 try sema.declareDependency(.{ .type_layout = struct_ty.toIntern() });
552 struct_ty.assertHasLayout(zcu);
553 const layout_unit: InternPool.AnalUnit = .wrap(.{ .type_layout = struct_ty.toIntern() });
554 if (zcu.failed_analysis.contains(layout_unit) or zcu.transitive_failed_analysis.contains(layout_unit)) {
555 return error.AnalysisFail;
556 }
557
558 const struct_obj = ip.loadStructType(struct_ty.toIntern());
559 assert(struct_obj.want_layout);
560
561 if (struct_obj.is_reified) {
562 // `Sema.zirReifyStruct` has already populated the default field values *and* (by loading
563 // the default values from pointers) validated their types, so we have nothing to do.
564 return;
565 }
566
567 try sema.declareDependency(.{ .src_hash = struct_obj.zir_index });
568
569 if (struct_obj.field_defaults.len == 0) {
570 // The struct has no default field values, so the slice has been omitted.
571 return;
572 }
573
574 var block: Block = .{
575 .parent = null,
576 .sema = sema,
577 .namespace = struct_obj.namespace,
578 .instructions = .empty,
579 .inlining = null,
580 .comptime_reason = undefined, // always set before using `block`
581 .src_base_inst = struct_obj.zir_index,
582 .type_name_ctx = struct_obj.name,
583 };
584 defer block.instructions.deinit(gpa);
585
586 return resolveStructDefaultsInner(sema, &block, &struct_obj, .normal_fields);
587}
588
589/// Asserts that the struct is not reified, and that `struct_obj.field_defaults.len` is non-zero.
590fn resolveStructDefaultsInner(
591 sema: *Sema,
592 block: *Block,
593 struct_obj: *const InternPool.LoadedStructType,
594 mode: enum { comptime_fields, normal_fields },
595) CompileError!void {
596 const pt = sema.pt;
597 const zcu = pt.zcu;
598 const comp = zcu.comp;
599 const gpa = comp.gpa;
600 const ip = &zcu.intern_pool;
601
602 assert(struct_obj.field_defaults.len > 0);
603
604 // We'll need to map the struct decl instruction to provide result types
605 const zir_index = struct_obj.zir_index.resolve(ip) orelse return error.AnalysisFail;
606 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{zir_index});
607
608 const field_types = struct_obj.field_types.get(ip);
609
610 const zir_struct = sema.code.getStructDecl(zir_index);
611 var field_it = zir_struct.iterateFields();
612 while (field_it.next()) |zir_field| {
613 switch (mode) {
614 .comptime_fields => if (!zir_field.is_comptime) continue,
615 .normal_fields => if (zir_field.is_comptime) continue,
616 }
617
618 const default_val_src = block.src(.{ .container_field_value = zir_field.idx });
619 block.comptime_reason = .{ .reason = .{
620 .src = default_val_src,
621 .r = .{ .simple = .struct_field_default_value },
622 } };
623 const default_body = zir_field.default_body orelse {
624 struct_obj.field_defaults.get(ip)[zir_field.idx] = .none;
625 continue;
626 };
627 const field_ty: Type = .fromInterned(field_types[zir_field.idx]);
628 const uncoerced = ref: {
629 // Provide the result type
630 sema.inst_map.putAssumeCapacity(zir_index, .fromIntern(field_ty.toIntern()));
631 defer assert(sema.inst_map.remove(zir_index));
632 break :ref try sema.resolveInlineBody(block, default_body, zir_index);
633 };
634 const coerced = try sema.coerce(block, field_ty, uncoerced, default_val_src);
635 const default_val = try sema.resolveConstValue(block, default_val_src, coerced, null);
636 if (default_val.canMutateComptimeVarState(zcu)) {
637 const field_name = struct_obj.field_names.get(ip)[zir_field.idx];
638 return sema.failWithContainsReferenceToComptimeVar(block, default_val_src, field_name, "field default value", default_val);
639 }
640 struct_obj.field_defaults.get(ip)[zir_field.idx] = default_val.toIntern();
641 }
642}
643
526644/// This logic must be kept in sync with `Type.getUnionLayout`.
527645pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void {
528646 const pt = sema.pt;
src/Zcu.zig+24-4
......@@ -3175,6 +3175,7 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void {
31753175 .nav_val => |nav| try zcu.markPoDependeeUpToDateInner(.{ .nav_val = nav }),
31763176 .nav_ty => |nav| try zcu.markPoDependeeUpToDateInner(.{ .nav_ty = nav }),
31773177 .type_layout => |ty| try zcu.markPoDependeeUpToDateInner(.{ .type_layout = ty }),
3178 .struct_defaults => |ty| try zcu.markPoDependeeUpToDateInner(.{ .struct_defaults = ty }),
31783179 .func => |func| try zcu.markPoDependeeUpToDateInner(.{ .func_ies = func }),
31793180 .memoized_state => |stage| try zcu.markPoDependeeUpToDateInner(.{ .memoized_state = stage }),
31803181 }
......@@ -3193,6 +3194,7 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
31933194 .nav_val => |nav| .{ .nav_val = nav },
31943195 .nav_ty => |nav| .{ .nav_ty = nav },
31953196 .type_layout => |ty| .{ .type_layout = ty },
3197 .struct_defaults => |ty| .{ .struct_defaults = ty },
31963198 .func => |func_index| .{ .func_ies = func_index },
31973199 .memoized_state => |stage| .{ .memoized_state = stage },
31983200 };
......@@ -4249,11 +4251,18 @@ fn resolveReferencesInner(zcu: *Zcu) Allocator.Error!std.AutoArrayHashMapUnmanag
42494251 unit_idx += 1;
42504252
42514253 // `nav_val` and `nav_ty` reference each other *implicitly* to save memory.
4254 // Likewise for `type_layout` and `struct_defaults` of a struct type.
42524255 queue_paired: {
42534256 const other: AnalUnit = .wrap(switch (unit.unwrap()) {
42544257 .nav_val => |n| .{ .nav_ty = n },
42554258 .nav_ty => |n| .{ .nav_val = n },
4256 .@"comptime", .type_layout, .func, .memoized_state => break :queue_paired,
4259 .struct_defaults => |ty| .{ .type_layout = ty },
4260 .type_layout => |ty| switch (ip.indexToKey(ty)) {
4261 .struct_type => .{ .struct_defaults = ty },
4262 .union_type, .enum_type, .opaque_type => break :queue_paired,
4263 else => unreachable,
4264 },
4265 .@"comptime", .func, .memoized_state => break :queue_paired,
42574266 });
42584267 const gop = try units.getOrPut(gpa, other);
42594268 if (gop.found_existing) break :queue_paired;
......@@ -4406,7 +4415,7 @@ fn formatAnalUnit(data: FormatAnalUnit, writer: *Io.Writer) Io.Writer.Error!void
44064415 }
44074416 },
44084417 .nav_val, .nav_ty => |nav, tag| return writer.print("{t}('{f}' [{}])", .{ tag, ip.getNav(nav).fqn.fmt(ip), @intFromEnum(nav) }),
4409 .type_layout => |ty, tag| return writer.print("{t}('{f}' [{}])", .{ tag, Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(ty) }),
4418 .type_layout, .struct_defaults => |ty, tag| return writer.print("{t}('{f}' [{}])", .{ tag, Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(ty) }),
44104419 .func => |func| {
44114420 const nav = zcu.funcInfo(func).owner_nav;
44124421 return writer.print("func('{f}' [{}])", .{ ip.getNav(nav).fqn.fmt(ip), @intFromEnum(func) });
......@@ -4431,7 +4440,7 @@ fn formatDependee(data: FormatDependee, writer: *Io.Writer) Io.Writer.Error!void
44314440 const fqn = ip.getNav(nav).fqn;
44324441 return writer.print("{t}('{f}')", .{ tag, fqn.fmt(ip) });
44334442 },
4434 .type_layout => |ip_index, tag| {
4443 .type_layout, .struct_defaults => |ip_index, tag| {
44354444 const name = Type.fromInterned(ip_index).containerTypeName(ip);
44364445 return writer.print("{t}('{f}')", .{ tag, name.fmt(ip) });
44374446 },
......@@ -4920,6 +4929,10 @@ fn addDependencyLoopErrorLine(
49204929 fmt_source,
49214930 dep_node.reason.type_layout_reason.msg(),
49224931 }),
4932 .struct_defaults => |ty| try eb.printString(
4933 "default field values of '{f}' depend on themselves for initialization here",
4934 .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)},
4935 ),
49234936 } else switch (dep_node.unit.unwrap()) {
49244937 .@"comptime" => unreachable, // cannot be involved in a dependency loop
49254938 .nav_val => |nav| try eb.printString("{f} uses value of declaration '{f}' here", .{
......@@ -4940,6 +4953,10 @@ fn addDependencyLoopErrorLine(
49404953 Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
49414954 dep_node.reason.type_layout_reason.msg(),
49424955 }),
4956 .struct_defaults => |ty| try eb.printString(
4957 "{f} uses default field values of '{f}' here",
4958 .{ fmt_source, Type.fromInterned(ty).containerTypeName(ip).fmt(ip) },
4959 ),
49434960 };
49444961
49454962 const src_loc = dep_node.reason.src.upgrade(zcu);
......@@ -4982,6 +4999,9 @@ fn formatDependencyLoopSourceUnit(data: FormatAnalUnit, w: *Io.Writer) Io.Writer
49824999 .type_layout => |ty| try w.print("type '{f}'", .{
49835000 Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
49845001 }),
5002 .struct_defaults => |ty| try w.print("default field value of '{f}'", .{
5003 Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
5004 }),
49855005 .func => |func| try w.print("function '{f}'", .{
49865006 ip.getNav(zcu.funcInfo(func).owner_nav).fqn.fmt(ip),
49875007 }),
......@@ -5030,7 +5050,7 @@ pub fn populateReferenceTrace(
50305050 const root_name: ?[]const u8 = switch (ref.referencer.unwrap()) {
50315051 .@"comptime" => "comptime",
50325052 .nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip),
5033 .type_layout => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
5053 .type_layout, .struct_defaults => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
50345054 .func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip),
50355055 .memoized_state => null,
50365056 };
src/Zcu/PerThread.zig+144-3
......@@ -324,6 +324,17 @@ pub fn update(
324324 .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav, null),
325325 .nav_val => |nav| pt.ensureNavValUpToDate(nav, null),
326326 .type_layout => |ty| pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null),
327 .struct_defaults => |ty| res: {
328 // Unlike the other functions, this one requires that the type layout is resolved first.
329 pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null) catch |err| switch (err) {
330 error.OutOfMemory,
331 error.Canceled,
332 => |e| return e,
333
334 error.AnalysisFail => {}, // already reported
335 };
336 break :res pt.ensureStructDefaultsUpToDate(.fromInterned(ty), null);
337 },
327338 .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage, null),
328339 .func => |func| pt.ensureFuncBodyUpToDate(func, null),
329340 };
......@@ -1326,6 +1337,7 @@ pub fn ensureTypeLayoutUpToDate(
13261337 defer tracy_trace.end();
13271338
13281339 const zcu = pt.zcu;
1340 const ip = &zcu.intern_pool;
13291341 const comp = zcu.comp;
13301342 const gpa = comp.gpa;
13311343
......@@ -1335,8 +1347,23 @@ pub fn ensureTypeLayoutUpToDate(
13351347
13361348 assert(!zcu.analysis_in_progress.contains(anal_unit));
13371349
1338 const was_outdated = zcu.clearOutdatedState(anal_unit) or
1339 zcu.intern_pool.setWantTypeLayout(comp.io, ty.toIntern());
1350 const was_outdated: bool = outdated: {
1351 if (zcu.clearOutdatedState(anal_unit)) break :outdated true;
1352 if (ip.setWantTypeLayout(comp.io, ty.toIntern())) {
1353 // We'll analyze the layout for the first time, but if this is a struct type then its
1354 // default field values also need to be analyzed.
1355 if (ip.indexToKey(ty.toIntern()) == .struct_type) {
1356 if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io);
1357 defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io);
1358 try zcu.outdated.ensureUnusedCapacity(gpa, 1);
1359 try zcu.outdated_ready.other.ensureUnusedCapacity(gpa, 1);
1360 zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .struct_defaults = ty.toIntern() }), 0);
1361 zcu.outdated_ready.other.putAssumeCapacityNoClobber(.wrap(.{ .struct_defaults = ty.toIntern() }), {});
1362 }
1363 break :outdated true;
1364 }
1365 break :outdated false;
1366 };
13401367
13411368 if (was_outdated) {
13421369 // `was_outdated` is true in the initial update, so this isn't a `dev.check`.
......@@ -1359,7 +1386,7 @@ pub fn ensureTypeLayoutUpToDate(
13591386 info.deps.clearRetainingCapacity();
13601387 }
13611388
1362 const unit_tracking = zcu.trackUnitSema(ty.containerTypeName(&zcu.intern_pool).toSlice(&zcu.intern_pool), null);
1389 const unit_tracking = zcu.trackUnitSema(ty.containerTypeName(ip).toSlice(ip), null);
13631390 defer unit_tracking.end(zcu);
13641391
13651392 try zcu.analysis_in_progress.put(gpa, anal_unit, reason);
......@@ -1428,6 +1455,120 @@ pub fn ensureTypeLayoutUpToDate(
14281455 if (new_failed) return error.AnalysisFail;
14291456}
14301457
1458/// Ensures that the default field values of the given `struct` type are fully up-to-date,
1459/// performing re-analysis if necessary. Asserts that `ty` is a struct (not a tuple!) type. Unlike
1460/// the other "ensure X up to date" functions, this particular function also asserts that the
1461/// *layout* of `ty` is *already* up-to-date (though it is okay for that resolution to have failed).
1462/// Returns `error.AnalysisFail` if an analysis error is encountered while resolving the default
1463/// field values; the caller is free to ignore this, since the error is already registered.
1464pub fn ensureStructDefaultsUpToDate(
1465 pt: Zcu.PerThread,
1466 ty: Type,
1467 /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`.
1468 reason: ?*const Zcu.DependencyReason,
1469) Zcu.SemaError!void {
1470 const tracy_trace = trace(@src());
1471 defer tracy_trace.end();
1472
1473 const zcu = pt.zcu;
1474 const ip = &zcu.intern_pool;
1475 const comp = zcu.comp;
1476 const gpa = comp.gpa;
1477
1478 assert(ip.indexToKey(ty.toIntern()) == .struct_type);
1479
1480 const anal_unit: AnalUnit = .wrap(.{ .struct_defaults = ty.toIntern() });
1481
1482 log.debug("ensureStructDefaultsUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
1483
1484 assert(!zcu.analysis_in_progress.contains(anal_unit));
1485
1486 const was_outdated: bool = outdated: {
1487 if (zcu.clearOutdatedState(anal_unit)) break :outdated true;
1488 // The type layout should already be marked as "wanted" by this point, because a struct's
1489 // layout must always be analyzed before its default values are.
1490 assert(!ip.setWantTypeLayout(comp.io, ty.toIntern()));
1491 break :outdated false;
1492 };
1493
1494 if (was_outdated) {
1495 // `was_outdated` is true in the initial update, so this isn't a `dev.check`.
1496 if (dev.env.supports(.incremental)) {
1497 zcu.resetUnit(anal_unit);
1498 }
1499 // For types, we already know that we have to invalidate all dependees.
1500 // TODO: we actually *could* detect whether everything was the same. should we bother?
1501 try zcu.markDependeeOutdated(.marked_po, .{ .struct_defaults = ty.toIntern() });
1502 } else {
1503 // We can trust the current information about this unit.
1504 if (zcu.failed_analysis.contains(anal_unit)) return error.AnalysisFail;
1505 if (zcu.transitive_failed_analysis.contains(anal_unit)) return error.AnalysisFail;
1506 return;
1507 }
1508
1509 if (zcu.comp.debugIncremental()) {
1510 const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit);
1511 info.last_update_gen = zcu.generation;
1512 info.deps.clearRetainingCapacity();
1513 }
1514
1515 const unit_tracking = zcu.trackUnitSema(ty.containerTypeName(ip).toSlice(ip), null);
1516 defer unit_tracking.end(zcu);
1517
1518 try zcu.analysis_in_progress.put(gpa, anal_unit, reason);
1519 defer assert(zcu.analysis_in_progress.swapRemove(anal_unit));
1520
1521 var analysis_arena: std.heap.ArenaAllocator = .init(gpa);
1522 defer analysis_arena.deinit();
1523
1524 var comptime_err_ret_trace: std.array_list.Managed(Zcu.LazySrcLoc) = .init(gpa);
1525 defer comptime_err_ret_trace.deinit();
1526
1527 const file = zcu.namespacePtr(ty.getNamespaceIndex(zcu)).fileScope(zcu);
1528
1529 var sema: Sema = .{
1530 .pt = pt,
1531 .gpa = gpa,
1532 .arena = analysis_arena.allocator(),
1533 .code = file.zir.?,
1534 .owner = anal_unit,
1535 .func_index = .none,
1536 .func_is_naked = false,
1537 .fn_ret_ty = .void,
1538 .fn_ret_ty_ies = null,
1539 .comptime_err_ret_trace = &comptime_err_ret_trace,
1540 };
1541 defer sema.deinit();
1542
1543 const new_failed: bool = if (Sema.type_resolution.resolveStructDefaults(&sema, ty)) failed: {
1544 break :failed false;
1545 } else |err| switch (err) {
1546 error.AnalysisFail => failed: {
1547 if (!zcu.failed_analysis.contains(anal_unit)) {
1548 // If this unit caused the error, it would have an entry in `failed_analysis`.
1549 // Since it does not, this must be a transitive failure.
1550 try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
1551 log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)});
1552 }
1553 break :failed true;
1554 },
1555 error.OutOfMemory,
1556 error.Canceled,
1557 => |e| return e,
1558 error.ComptimeReturn => unreachable,
1559 error.ComptimeBreak => unreachable,
1560 };
1561
1562 sema.flushExports() catch |err| switch (err) {
1563 error.OutOfMemory => |e| return e,
1564 };
1565
1566 // We don't need to `markDependeeOutdated`/`markPoDependeeUpToDate` here, because we already
1567 // marked the struct defaults as outdated at the top of this function.
1568
1569 if (new_failed) return error.AnalysisFail;
1570}
1571
14311572/// Ensures that the resolved value of the given `Nav` is fully up-to-date, performing re-analysis
14321573/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is
14331574/// free to ignore this, since the error is already registered.
src/link/Dwarf.zig+9-1
......@@ -3827,7 +3827,15 @@ fn updateConstInner(dwarf: *Dwarf, pt: Zcu.PerThread, debug_const_index: link.Co
38273827 try diw.writeUleb128(ty.abiAlignment(zcu).toByteUnits().?);
38283828 for (0..loaded_struct.field_types.len) |field_index| {
38293829 const is_comptime = loaded_struct.field_is_comptime_bits.get(ip, field_index);
3830 const field_init = loaded_struct.field_defaults.getOrNone(ip, field_index);
3830 // TODO: we currently don't emit information about default values for
3831 // non-`comptime` fields, because these default values are resolved at a
3832 // separate time in the compiler frontend. To emit this information, the
3833 // frontend needs to tell us when the default values are available: like
3834 // how `Zcu.PerThread.ensureTypeLayoutUpToDate` enqueues a link task to
3835 // indicate completion of the type's layout, a task should be enqueued
3836 // by `Zcu.PerThread.ensureStructDefaultsUpToDate`, and upon receiving
3837 // it we should patch the correct default field values in.
3838 const field_init: InternPool.Index = if (is_comptime) loaded_struct.field_defaults.getOrNone(ip, field_index) else .none;
38313839 assert(!(is_comptime and field_init == .none));
38323840 const field_type: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
38333841 const has_runtime_bits, const has_comptime_state = switch (field_init) {