diff --git a/src/Compilation.zig b/src/Compilation.zig index a4e712826d4a04f3ca0e6f15475def928a3562ad..dad807fcbef769a4f92cc38848f2292481866e95 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3647,6 +3647,7 @@ const Header = extern struct { nav_val_deps_len: u32, nav_ty_deps_len: u32, type_layout_deps_len: u32, + struct_defaults_deps_len: u32, func_ies_deps_len: u32, zon_file_deps_len: u32, embed_file_deps_len: u32, @@ -3696,6 +3697,7 @@ pub fn saveState(comp: *Compilation) !void { .nav_val_deps_len = @intCast(ip.nav_val_deps.count()), .nav_ty_deps_len = @intCast(ip.nav_ty_deps.count()), .type_layout_deps_len = @intCast(ip.type_layout_deps.count()), + .struct_defaults_deps_len = @intCast(ip.struct_defaults_deps.count()), .func_ies_deps_len = @intCast(ip.func_ies_deps.count()), .zon_file_deps_len = @intCast(ip.zon_file_deps.count()), .embed_file_deps_len = @intCast(ip.embed_file_deps.count()), @@ -3720,7 +3722,7 @@ pub fn saveState(comp: *Compilation) !void { }, }); - try bufs.ensureTotalCapacityPrecise(24 + 9 * pt_headers.items.len); + try bufs.ensureTotalCapacityPrecise(26 + 9 * pt_headers.items.len); addBuf(&bufs, mem.asBytes(&header)); addBuf(&bufs, @ptrCast(pt_headers.items)); @@ -3732,6 +3734,8 @@ pub fn saveState(comp: *Compilation) !void { addBuf(&bufs, @ptrCast(ip.nav_ty_deps.values())); addBuf(&bufs, @ptrCast(ip.type_layout_deps.keys())); addBuf(&bufs, @ptrCast(ip.type_layout_deps.values())); + addBuf(&bufs, @ptrCast(ip.struct_defaults_deps.keys())); + addBuf(&bufs, @ptrCast(ip.struct_defaults_deps.values())); addBuf(&bufs, @ptrCast(ip.func_ies_deps.keys())); addBuf(&bufs, @ptrCast(ip.func_ies_deps.values())); addBuf(&bufs, @ptrCast(ip.zon_file_deps.keys())); diff --git a/src/IncrementalDebugServer.zig b/src/IncrementalDebugServer.zig index 782b34320586299b259336e70cca63d9fba32376..7d0dc8e89b6fd9fd257733839e924de1ce92a25f 100644 --- a/src/IncrementalDebugServer.zig +++ b/src/IncrementalDebugServer.zig @@ -307,7 +307,7 @@ fn handleCommand(zcu: *Zcu, w: *Io.Writer, cmd_str: []const u8, arg_str: []const switch (dependee) { .src_hash, .namespace, .namespace_name, .zon_file, .embed_file => try w.print("{f}", .{zcu.fmtDependee(dependee)}), .nav_val, .nav_ty => |nav| try w.print("{t} {d}", .{ dependee, @intFromEnum(nav) }), - .type_layout, .func_ies => |ip_index| try w.print("{t} {d}", .{ dependee, @intFromEnum(ip_index) }), + .type_layout, .struct_defaults, .func_ies => |ip_index| try w.print("{t} {d}", .{ dependee, @intFromEnum(ip_index) }), .memoized_state => |stage| try w.print("memoized_state {s}", .{@tagName(stage)}), } try w.writeByte('\n'); @@ -374,6 +374,8 @@ fn parseAnalUnit(str: []const u8) ?AnalUnit { return .wrap(.{ .nav_ty = @enumFromInt(parseIndex(idx_str) orelse return null) }); } else if (std.mem.eql(u8, kind, "type_layout")) { return .wrap(.{ .type_layout = @enumFromInt(parseIndex(idx_str) orelse return null) }); + } else if (std.mem.eql(u8, kind, "struct_defaults")) { + return .wrap(.{ .struct_defaults = @enumFromInt(parseIndex(idx_str) orelse return null) }); } else if (std.mem.eql(u8, kind, "func")) { return .wrap(.{ .func = @enumFromInt(parseIndex(idx_str) orelse return null) }); } else if (std.mem.eql(u8, kind, "memoized_state")) { diff --git a/src/InternPool.zig b/src/InternPool.zig index ffabc7371b0c9d7221475b966fa059eccf275a48..da95ed6cc7a40f96b08a7e3a589afb5f93d568df 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -54,6 +54,9 @@ func_ies_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index), /// Dependencies on the resolved layout of a `struct`, `union`, or `enum` type. /// Value is index into `dep_entries` of the first dependency on this type's layout. type_layout_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index), +/// Dependencies on the resolved default field values of a `struct` type. +/// Value is index into `dep_entries` of the first dependency on this type's inits. +struct_defaults_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index), /// Dependencies on a ZON file. Triggered by `@import` of ZON. /// Value is index into `dep_entries` of the first dependency on this ZON file. zon_file_deps: std.AutoArrayHashMapUnmanaged(FileIndex, DepEntry.Index), @@ -108,6 +111,7 @@ pub const empty: InternPool = .{ .nav_ty_deps = .empty, .func_ies_deps = .empty, .type_layout_deps = .empty, + .struct_defaults_deps = .empty, .zon_file_deps = .empty, .embed_file_deps = .empty, .namespace_deps = .empty, @@ -419,6 +423,7 @@ pub const AnalUnit = packed struct(u64) { nav_val, nav_ty, type_layout, + struct_defaults, func, memoized_state, }; @@ -432,6 +437,8 @@ pub const AnalUnit = packed struct(u64) { nav_ty: Nav.Index, /// This `AnalUnit` resolves the layout of the given `struct`, `union`, or `enum` type. type_layout: InternPool.Index, + /// This `AnalUnit` resolves the default field values of the given `struct` type. + struct_defaults: InternPool.Index, /// This `AnalUnit` analyzes the body of the given runtime function. func: InternPool.Index, /// This `AnalUnit` resolves all state which is memoized in fields on `Zcu`. @@ -851,6 +858,7 @@ pub const Dependee = union(enum) { /// Index is the function, not its IES. func_ies: Index, type_layout: Index, + struct_defaults: Index, zon_file: FileIndex, embed_file: Zcu.EmbedFile.Index, namespace: TrackedInst.Index, @@ -904,6 +912,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI .nav_ty => |x| ip.nav_ty_deps.get(x), .func_ies => |x| ip.func_ies_deps.get(x), .type_layout => |x| ip.type_layout_deps.get(x), + .struct_defaults => |x| ip.struct_defaults_deps.get(x), .zon_file => |x| ip.zon_file_deps.get(x), .embed_file => |x| ip.embed_file_deps.get(x), .namespace => |x| ip.namespace_deps.get(x), @@ -978,6 +987,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend .nav_ty => ip.nav_ty_deps, .func_ies => ip.func_ies_deps, .type_layout => ip.type_layout_deps, + .struct_defaults => ip.struct_defaults_deps, .zon_file => ip.zon_file_deps, .embed_file => ip.embed_file_deps, .namespace => ip.namespace_deps, @@ -6454,6 +6464,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator, io: Io) void { ip.nav_ty_deps.deinit(gpa); ip.func_ies_deps.deinit(gpa); ip.type_layout_deps.deinit(gpa); + ip.struct_defaults_deps.deinit(gpa); ip.zon_file_deps.deinit(gpa); ip.embed_file_deps.deinit(gpa); ip.namespace_deps.deinit(gpa); @@ -10619,6 +10630,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void { const nav_ty_deps_len = ip.nav_ty_deps.count(); const func_ies_deps_len = ip.func_ies_deps.count(); const type_layout_deps_len = ip.type_layout_deps.count(); + const struct_defaults_deps_len = ip.struct_defaults_deps.count(); const zon_file_deps_len = ip.zon_file_deps.count(); const embed_file_deps_len = ip.embed_file_deps.count(); const namespace_deps_len = ip.namespace_deps.count(); @@ -10629,6 +10641,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void { const nav_ty_deps_size = nav_ty_deps_len * 8; const func_ies_deps_size = func_ies_deps_len * 8; const type_layout_deps_size = type_layout_deps_len * 8; + const struct_defaults_deps_size = struct_defaults_deps_len * 8; const zon_file_deps_size = zon_file_deps_len * 8; const embed_file_deps_size = embed_file_deps_len * 8; const namespace_deps_size = namespace_deps_len * 8; @@ -10642,6 +10655,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void { \\ {d} nav_ty: {d} bytes \\ {d} func_ies: {d} bytes \\ {d} type_layout: {d} bytes + \\ {d} struct_defaults: {d} bytes \\ {d} zon_file: {d} bytes \\ {d} embed_file: {d} bytes \\ {d} namespace: {d} bytes @@ -10649,7 +10663,7 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void { \\ , .{ dep_entries_size + src_hash_deps_size + nav_val_deps_size + nav_ty_deps_size + - func_ies_deps_size + type_layout_deps_size + zon_file_deps_size + + func_ies_deps_size + type_layout_deps_size + struct_defaults_deps_size + zon_file_deps_size + embed_file_deps_size + namespace_deps_size + namespace_name_deps_size, dep_entries_len, dep_entries_size, @@ -10663,6 +10677,8 @@ fn dumpDependencyStatsFallible(ip: *const InternPool, w: *Io.Writer) !void { func_ies_deps_size, type_layout_deps_len, type_layout_deps_size, + struct_defaults_deps_len, + struct_defaults_deps_size, zon_file_deps_len, zon_file_deps_size, embed_file_deps_len, diff --git a/src/Sema.zig b/src/Sema.zig index fc5b147759df31eead062d8980961059ab7e5a2c..337f0d07cda987e326bdf5869949734578fcb9d7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4533,6 +4533,10 @@ fn validateStructInit( if (explicit) continue; if (struct_ty.structFieldIsComptime(i, zcu)) continue; + if (!struct_ty.isTuple(zcu)) { + try sema.ensureStructDefaultsResolved(struct_ty, init_src); + } + const default_val = struct_ty.structFieldDefaultValue(i, zcu) orelse { const field_name = struct_ty.structFieldName(i, zcu).unwrap() orelse { const template = "missing tuple field with index {d}"; @@ -5850,6 +5854,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void { .nav_val, .nav_ty, .type_layout, + .struct_defaults, .memoized_state, => return, // does nothing outside a function }; @@ -5868,6 +5873,7 @@ fn zirDisableIntrinsics(sema: *Sema) CompileError!void { .nav_val, .nav_ty, .type_layout, + .struct_defaults, .memoized_state, => return, // does nothing outside a function }; @@ -7091,7 +7097,14 @@ fn analyzeCall( }); if (func_ty_info.cc == .auto) { switch (sema.owner.unwrap()) { - .@"comptime", .nav_ty, .nav_val, .type_layout, .memoized_state => {}, + .@"comptime", + .nav_ty, + .nav_val, + .type_layout, + .struct_defaults, + .memoized_state, + => {}, + .func => |owner_func| ip.funcSetHasErrorTrace(io, owner_func, true), } } @@ -16907,6 +16920,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .struct_type => ip.loadStructType(ty.toIntern()), else => unreachable, }; + try sema.ensureStructDefaultsResolved(ty, src); // can't do this sooner, since it's not allowed on tuples struct_field_vals = try gpa.alloc(InternPool.Index, struct_type.field_types.len); for (struct_field_vals, 0..) |*field_val, field_index| { @@ -18788,6 +18802,8 @@ fn finishStructInit( continue; } + try sema.ensureStructDefaultsResolved(struct_ty, init_src); + const field_default: InternPool.Index = d: { if (struct_type.field_defaults.len == 0) break :d .none; break :d struct_type.field_defaults.get(ip)[i]; @@ -19454,7 +19470,14 @@ fn getErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref { .func => |func| if (ip.funcAnalysisUnordered(func).has_error_trace and block.ownerModule().error_tracing) { return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty); }, - .@"comptime", .nav_ty, .nav_val, .type_layout, .memoized_state => {}, + + .@"comptime", + .nav_ty, + .nav_val, + .type_layout, + .struct_defaults, + .memoized_state, + => {}, } return Air.internedToRef(try pt.intern(.{ .opt = .{ .ty = opt_ptr_stack_trace_ty.toIntern(), @@ -24784,7 +24807,7 @@ fn zirBuiltinExtern( // So, for now, just use our containing `declaration`. .zir_index = switch (sema.owner.unwrap()) { .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index, - .type_layout => |owner_ty| Type.fromInterned(owner_ty).typeDeclInstAllowGeneratedTag(zcu).?, + .type_layout, .struct_defaults => |owner_ty| Type.fromInterned(owner_ty).typeDeclInstAllowGeneratedTag(zcu).?, .memoized_state => unreachable, .nav_ty, .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index, .func => |func| zir_index: { @@ -25276,7 +25299,14 @@ fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !In try sema.ensureMemoizedStateResolved(src, .panic); const panic_fn_index = zcu.builtin_decl_values.get(panic_id.toBuiltin()); switch (sema.owner.unwrap()) { - .@"comptime", .nav_ty, .nav_val, .type_layout, .memoized_state => {}, + .@"comptime", + .nav_ty, + .nav_val, + .type_layout, + .struct_defaults, + .memoized_state, + => {}, + .func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(io, owner_func, true), } return panic_fn_index; @@ -33541,23 +33571,6 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { const gop = try sema.dependencies.getOrPut(sema.gpa, dependee); if (gop.found_existing) return; - // Avoid creating dependencies on ourselves. This situation can arise when we analyze the fields - // of a type and they use `@This()`. This dependency would be unnecessary, and in fact would - // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve - // the loop. - // Note that this also disallows a `nav_val` - switch (sema.owner.unwrap()) { - .nav_val => |this_nav| switch (dependee) { - .nav_val => |other_nav| if (this_nav == other_nav) return, - else => {}, - }, - .nav_ty => |this_nav| switch (dependee) { - .nav_ty => |other_nav| if (this_nav == other_nav) return, - else => {}, - }, - else => {}, - } - try pt.addDependency(sema.owner, dependee); } @@ -33923,6 +33936,7 @@ const ComptimeStoreResult = @import("Sema/comptime_ptr_access.zig").ComptimeStor pub const type_resolution = @import("Sema/type_resolution.zig"); pub const ensureLayoutResolved = type_resolution.ensureLayoutResolved; +pub const ensureStructDefaultsResolved = type_resolution.ensureStructDefaultsResolved; pub fn getBuiltinType(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl) SemaError!Type { assert(decl.kind() == .type); diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 06218806a44b24cf4433ed9b7738b27515a55154..2012d5167c2111f133aac6916d7aa6e697395f2a 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -758,6 +758,7 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const ip = &pt.zcu.intern_pool; try self.sema.ensureLayoutResolved(res_ty, self.import_loc, .init); + try self.sema.ensureStructDefaultsResolved(res_ty, self.import_loc); const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?; const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig index 5e4e8a4ccd84e8080f71162262560a934977b34f..717d5b8b96e34893007f4f9e80fd100c3ce7b241 100644 --- a/src/Sema/type_resolution.zig +++ b/src/Sema/type_resolution.zig @@ -134,6 +134,33 @@ fn ensureLayoutResolvedInner(sema: *Sema, ty: Type, orig_ty: Type, reason: *cons } } +/// Asserts that `ty` is a non-tuple `struct` type, and ensures that its fields' default values +/// are resolved. Adds incremental dependencies tracking the required type resolution. +/// +/// It is not necessary to call this function to query the values of comptime fields: those values +/// are available from type *layout* resolution, see `ensureLayoutResolved`. +/// +/// Asserts that the *layout* of `ty` has already been resolved---see `ensureLayoutResolved`. +pub fn ensureStructDefaultsResolved(sema: *Sema, ty: Type, src: LazySrcLoc) SemaError!void { + const pt = sema.pt; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + assert(ip.indexToKey(ty.toIntern()) == .struct_type); + if (zcu.comp.config.incremental) assert(sema.dependencies.contains(.{ .type_layout = ty.toIntern() })); + + try sema.declareDependency(.{ .struct_defaults = ty.toIntern() }); + try sema.addReferenceEntry(null, src, .wrap(.{ .struct_defaults = ty.toIntern() })); + + const reason: Zcu.DependencyReason = .{ .src = src, .type_layout_reason = undefined }; + + if (zcu.analysis_in_progress.contains(.wrap(.{ .struct_defaults = ty.toIntern() }))) { + return sema.failWithDependencyLoop(.wrap(.{ .struct_defaults = ty.toIntern() }), &reason); + } + + try pt.ensureStructDefaultsUpToDate(ty, &reason); +} + /// Asserts that `struct_ty` is a non-packed non-tuple struct, and that `sema.owner` is that type. /// This function *does* register the `src_hash` dependency on the struct. pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { @@ -193,14 +220,8 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { @memset(struct_obj.field_is_comptime_bits.getAll(ip), 0); const zir_struct = sema.code.getStructDecl(zir_index); - - // If we have any default values to resolve, we'll need to map the struct decl instruction - // to the result type. - if (zir_struct.field_default_body_lens != null) { - try sema.inst_map.ensureSpaceForInstructions(gpa, &.{zir_index}); - } - var field_it = zir_struct.iterateFields(); + var any_comptime_fields = false; while (field_it.next()) |zir_field| { { const name_slice = sema.code.nullTerminatedString(zir_field.name); @@ -212,18 +233,21 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { const bit_bag_index = zir_field.idx / 32; const mask = @as(u32, 1) << @intCast(zir_field.idx % 32); struct_obj.field_is_comptime_bits.getAll(ip)[bit_bag_index] |= mask; + any_comptime_fields = true; } - const field_ty: Type = field_ty: { + { const field_ty_src = block.src(.{ .container_field_type = zir_field.idx }); - block.comptime_reason = .{ .reason = .{ - .src = field_ty_src, - .r = .{ .simple = .struct_field_types }, - } }; - const type_ref = try sema.resolveInlineBody(&block, zir_field.type_body, zir_index); - break :field_ty try sema.analyzeAsType(&block, field_ty_src, .struct_field_types, type_ref); - }; - struct_obj.field_types.get(ip)[zir_field.idx] = field_ty.toIntern(); + const field_ty: Type = field_ty: { + block.comptime_reason = .{ .reason = .{ + .src = field_ty_src, + .r = .{ .simple = .struct_field_types }, + } }; + const type_ref = try sema.resolveInlineBody(&block, zir_field.type_body, zir_index); + break :field_ty try sema.analyzeAsType(&block, field_ty_src, .struct_field_types, type_ref); + }; + struct_obj.field_types.get(ip)[zir_field.idx] = field_ty.toIntern(); + } if (struct_obj.field_aligns.len == 0) { assert(zir_field.align_body == null); @@ -240,31 +264,13 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { }; struct_obj.field_aligns.get(ip)[zir_field.idx] = field_align; } + } - if (struct_obj.field_defaults.len == 0) { - assert(zir_field.default_body == null); - } else { - const field_default_src = block.src(.{ .container_field_value = zir_field.idx }); - const field_default: InternPool.Index = d: { - block.comptime_reason = .{ .reason = .{ - .src = field_default_src, - .r = .{ .simple = .struct_field_default_value }, - } }; - const default_body = zir_field.default_body orelse break :d .none; - // Provide the result type - sema.inst_map.putAssumeCapacity(zir_index, .fromType(field_ty)); - defer assert(sema.inst_map.remove(zir_index)); - const uncoerced_default_val = try sema.resolveInlineBody(&block, default_body, zir_index); - const coerced_default_val = try sema.coerce(&block, field_ty, uncoerced_default_val, field_default_src); - const default_val = try sema.resolveConstValue(&block, field_default_src, coerced_default_val, null); - if (default_val.canMutateComptimeVarState(zcu)) { - const field_name = struct_obj.field_names.get(ip)[zir_field.idx]; - return sema.failWithContainsReferenceToComptimeVar(&block, field_default_src, field_name, "field default value", default_val); - } - break :d default_val.toIntern(); - }; - struct_obj.field_defaults.get(ip)[zir_field.idx] = field_default; - } + // We also resolve the default values of any `comptime` fields now. This is not necessary in + // the case of a reified struct because the the default values were already poulated and + // validated by `Sema.zirReifyStruct`. + if (any_comptime_fields) { + try resolveStructDefaultsInner(sema, &block, &struct_obj, .comptime_fields); } } @@ -523,6 +529,118 @@ fn resolvePackedStructLayout( ); } +/// Asserts that `struct_ty` is a non-tuple struct, and that `sema.owner` is that type. +/// +/// Also asserts that the layout of `struct_ty` has *already* been resolved (though it is okay for +/// that resolution to have failed). This requirement exists to ensure better error messages in the +/// event of a dependency loop. +/// +/// This function *does* register the `src_hash` dependency on the struct. +pub fn resolveStructDefaults(sema: *Sema, struct_ty: Type) CompileError!void { + const pt = sema.pt; + const zcu = pt.zcu; + const comp = zcu.comp; + const gpa = comp.gpa; + const ip = &zcu.intern_pool; + + assert(sema.owner.unwrap().struct_defaults == struct_ty.toIntern()); + + // We always depend on the layout of `struct_ty`. However, we don't actually need to resolve it + // now, because the caller has done so for us. Just mark the dependency so that the incremental + // compilation handling understands the dependency graph. + try sema.declareDependency(.{ .type_layout = struct_ty.toIntern() }); + struct_ty.assertHasLayout(zcu); + const layout_unit: InternPool.AnalUnit = .wrap(.{ .type_layout = struct_ty.toIntern() }); + if (zcu.failed_analysis.contains(layout_unit) or zcu.transitive_failed_analysis.contains(layout_unit)) { + return error.AnalysisFail; + } + + const struct_obj = ip.loadStructType(struct_ty.toIntern()); + assert(struct_obj.want_layout); + + if (struct_obj.is_reified) { + // `Sema.zirReifyStruct` has already populated the default field values *and* (by loading + // the default values from pointers) validated their types, so we have nothing to do. + return; + } + + try sema.declareDependency(.{ .src_hash = struct_obj.zir_index }); + + if (struct_obj.field_defaults.len == 0) { + // The struct has no default field values, so the slice has been omitted. + return; + } + + var block: Block = .{ + .parent = null, + .sema = sema, + .namespace = struct_obj.namespace, + .instructions = .empty, + .inlining = null, + .comptime_reason = undefined, // always set before using `block` + .src_base_inst = struct_obj.zir_index, + .type_name_ctx = struct_obj.name, + }; + defer block.instructions.deinit(gpa); + + return resolveStructDefaultsInner(sema, &block, &struct_obj, .normal_fields); +} + +/// Asserts that the struct is not reified, and that `struct_obj.field_defaults.len` is non-zero. +fn resolveStructDefaultsInner( + sema: *Sema, + block: *Block, + struct_obj: *const InternPool.LoadedStructType, + mode: enum { comptime_fields, normal_fields }, +) CompileError!void { + const pt = sema.pt; + const zcu = pt.zcu; + const comp = zcu.comp; + const gpa = comp.gpa; + const ip = &zcu.intern_pool; + + assert(struct_obj.field_defaults.len > 0); + + // We'll need to map the struct decl instruction to provide result types + const zir_index = struct_obj.zir_index.resolve(ip) orelse return error.AnalysisFail; + try sema.inst_map.ensureSpaceForInstructions(gpa, &.{zir_index}); + + const field_types = struct_obj.field_types.get(ip); + + const zir_struct = sema.code.getStructDecl(zir_index); + var field_it = zir_struct.iterateFields(); + while (field_it.next()) |zir_field| { + switch (mode) { + .comptime_fields => if (!zir_field.is_comptime) continue, + .normal_fields => if (zir_field.is_comptime) continue, + } + + const default_val_src = block.src(.{ .container_field_value = zir_field.idx }); + block.comptime_reason = .{ .reason = .{ + .src = default_val_src, + .r = .{ .simple = .struct_field_default_value }, + } }; + const default_body = zir_field.default_body orelse { + struct_obj.field_defaults.get(ip)[zir_field.idx] = .none; + continue; + }; + const field_ty: Type = .fromInterned(field_types[zir_field.idx]); + const uncoerced = ref: { + // Provide the result type + sema.inst_map.putAssumeCapacity(zir_index, .fromIntern(field_ty.toIntern())); + defer assert(sema.inst_map.remove(zir_index)); + break :ref try sema.resolveInlineBody(block, default_body, zir_index); + }; + const coerced = try sema.coerce(block, field_ty, uncoerced, default_val_src); + const default_val = try sema.resolveConstValue(block, default_val_src, coerced, null); + if (default_val.canMutateComptimeVarState(zcu)) { + const field_name = struct_obj.field_names.get(ip)[zir_field.idx]; + return sema.failWithContainsReferenceToComptimeVar(block, default_val_src, field_name, "field default value", default_val); + } + struct_obj.field_defaults.get(ip)[zir_field.idx] = default_val.toIntern(); + } +} + /// This logic must be kept in sync with `Type.getUnionLayout`. pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { const pt = sema.pt; diff --git a/src/Zcu.zig b/src/Zcu.zig index faa535da5d6e12ae12c1ba6aaed1341fde2147d6..c623c1ec49f110d79676431c59481e6339613948 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3175,6 +3175,7 @@ fn markPoDependeeUpToDateInner(zcu: *Zcu, dependee: InternPool.Dependee) !void { .nav_val => |nav| try zcu.markPoDependeeUpToDateInner(.{ .nav_val = nav }), .nav_ty => |nav| try zcu.markPoDependeeUpToDateInner(.{ .nav_ty = nav }), .type_layout => |ty| try zcu.markPoDependeeUpToDateInner(.{ .type_layout = ty }), + .struct_defaults => |ty| try zcu.markPoDependeeUpToDateInner(.{ .struct_defaults = ty }), .func => |func| try zcu.markPoDependeeUpToDateInner(.{ .func_ies = func }), .memoized_state => |stage| try zcu.markPoDependeeUpToDateInner(.{ .memoized_state = stage }), } @@ -3193,6 +3194,7 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni .nav_val => |nav| .{ .nav_val = nav }, .nav_ty => |nav| .{ .nav_ty = nav }, .type_layout => |ty| .{ .type_layout = ty }, + .struct_defaults => |ty| .{ .struct_defaults = ty }, .func => |func_index| .{ .func_ies = func_index }, .memoized_state => |stage| .{ .memoized_state = stage }, }; @@ -4249,11 +4251,18 @@ fn resolveReferencesInner(zcu: *Zcu) Allocator.Error!std.AutoArrayHashMapUnmanag unit_idx += 1; // `nav_val` and `nav_ty` reference each other *implicitly* to save memory. + // Likewise for `type_layout` and `struct_defaults` of a struct type. queue_paired: { const other: AnalUnit = .wrap(switch (unit.unwrap()) { .nav_val => |n| .{ .nav_ty = n }, .nav_ty => |n| .{ .nav_val = n }, - .@"comptime", .type_layout, .func, .memoized_state => break :queue_paired, + .struct_defaults => |ty| .{ .type_layout = ty }, + .type_layout => |ty| switch (ip.indexToKey(ty)) { + .struct_type => .{ .struct_defaults = ty }, + .union_type, .enum_type, .opaque_type => break :queue_paired, + else => unreachable, + }, + .@"comptime", .func, .memoized_state => break :queue_paired, }); const gop = try units.getOrPut(gpa, other); if (gop.found_existing) break :queue_paired; @@ -4406,7 +4415,7 @@ fn formatAnalUnit(data: FormatAnalUnit, writer: *Io.Writer) Io.Writer.Error!void } }, .nav_val, .nav_ty => |nav, tag| return writer.print("{t}('{f}' [{}])", .{ tag, ip.getNav(nav).fqn.fmt(ip), @intFromEnum(nav) }), - .type_layout => |ty, tag| return writer.print("{t}('{f}' [{}])", .{ tag, Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(ty) }), + .type_layout, .struct_defaults => |ty, tag| return writer.print("{t}('{f}' [{}])", .{ tag, Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(ty) }), .func => |func| { const nav = zcu.funcInfo(func).owner_nav; 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 const fqn = ip.getNav(nav).fqn; return writer.print("{t}('{f}')", .{ tag, fqn.fmt(ip) }); }, - .type_layout => |ip_index, tag| { + .type_layout, .struct_defaults => |ip_index, tag| { const name = Type.fromInterned(ip_index).containerTypeName(ip); return writer.print("{t}('{f}')", .{ tag, name.fmt(ip) }); }, @@ -4920,6 +4929,10 @@ fn addDependencyLoopErrorLine( fmt_source, dep_node.reason.type_layout_reason.msg(), }), + .struct_defaults => |ty| try eb.printString( + "default field values of '{f}' depend on themselves for initialization here", + .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}, + ), } else switch (dep_node.unit.unwrap()) { .@"comptime" => unreachable, // cannot be involved in a dependency loop .nav_val => |nav| try eb.printString("{f} uses value of declaration '{f}' here", .{ @@ -4940,6 +4953,10 @@ fn addDependencyLoopErrorLine( Type.fromInterned(ty).containerTypeName(ip).fmt(ip), dep_node.reason.type_layout_reason.msg(), }), + .struct_defaults => |ty| try eb.printString( + "{f} uses default field values of '{f}' here", + .{ fmt_source, Type.fromInterned(ty).containerTypeName(ip).fmt(ip) }, + ), }; const src_loc = dep_node.reason.src.upgrade(zcu); @@ -4982,6 +4999,9 @@ fn formatDependencyLoopSourceUnit(data: FormatAnalUnit, w: *Io.Writer) Io.Writer .type_layout => |ty| try w.print("type '{f}'", .{ Type.fromInterned(ty).containerTypeName(ip).fmt(ip), }), + .struct_defaults => |ty| try w.print("default field value of '{f}'", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + }), .func => |func| try w.print("function '{f}'", .{ ip.getNav(zcu.funcInfo(func).owner_nav).fqn.fmt(ip), }), @@ -5030,7 +5050,7 @@ pub fn populateReferenceTrace( const root_name: ?[]const u8 = switch (ref.referencer.unwrap()) { .@"comptime" => "comptime", .nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip), - .type_layout => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), + .type_layout, .struct_defaults => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), .func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip), .memoized_state => null, }; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index ee788c884e327fffe1fe5a1e6f09854ba6932803..8a786edb4903b031464efdadf108966bd8f63af0 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -324,6 +324,17 @@ pub fn update( .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav, null), .nav_val => |nav| pt.ensureNavValUpToDate(nav, null), .type_layout => |ty| pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null), + .struct_defaults => |ty| res: { + // Unlike the other functions, this one requires that the type layout is resolved first. + pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null) catch |err| switch (err) { + error.OutOfMemory, + error.Canceled, + => |e| return e, + + error.AnalysisFail => {}, // already reported + }; + break :res pt.ensureStructDefaultsUpToDate(.fromInterned(ty), null); + }, .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage, null), .func => |func| pt.ensureFuncBodyUpToDate(func, null), }; @@ -1326,6 +1337,7 @@ pub fn ensureTypeLayoutUpToDate( defer tracy_trace.end(); const zcu = pt.zcu; + const ip = &zcu.intern_pool; const comp = zcu.comp; const gpa = comp.gpa; @@ -1335,8 +1347,23 @@ pub fn ensureTypeLayoutUpToDate( assert(!zcu.analysis_in_progress.contains(anal_unit)); - const was_outdated = zcu.clearOutdatedState(anal_unit) or - zcu.intern_pool.setWantTypeLayout(comp.io, ty.toIntern()); + const was_outdated: bool = outdated: { + if (zcu.clearOutdatedState(anal_unit)) break :outdated true; + if (ip.setWantTypeLayout(comp.io, ty.toIntern())) { + // We'll analyze the layout for the first time, but if this is a struct type then its + // default field values also need to be analyzed. + if (ip.indexToKey(ty.toIntern()) == .struct_type) { + if (std.debug.runtime_safety) zcu.outdated_lock.lockUncancelable(zcu.comp.io); + defer if (std.debug.runtime_safety) zcu.outdated_lock.unlock(zcu.comp.io); + try zcu.outdated.ensureUnusedCapacity(gpa, 1); + try zcu.outdated_ready.other.ensureUnusedCapacity(gpa, 1); + zcu.outdated.putAssumeCapacityNoClobber(.wrap(.{ .struct_defaults = ty.toIntern() }), 0); + zcu.outdated_ready.other.putAssumeCapacityNoClobber(.wrap(.{ .struct_defaults = ty.toIntern() }), {}); + } + break :outdated true; + } + break :outdated false; + }; if (was_outdated) { // `was_outdated` is true in the initial update, so this isn't a `dev.check`. @@ -1359,7 +1386,7 @@ pub fn ensureTypeLayoutUpToDate( info.deps.clearRetainingCapacity(); } - const unit_tracking = zcu.trackUnitSema(ty.containerTypeName(&zcu.intern_pool).toSlice(&zcu.intern_pool), null); + const unit_tracking = zcu.trackUnitSema(ty.containerTypeName(ip).toSlice(ip), null); defer unit_tracking.end(zcu); try zcu.analysis_in_progress.put(gpa, anal_unit, reason); @@ -1428,6 +1455,120 @@ pub fn ensureTypeLayoutUpToDate( if (new_failed) return error.AnalysisFail; } +/// Ensures that the default field values of the given `struct` type are fully up-to-date, +/// performing re-analysis if necessary. Asserts that `ty` is a struct (not a tuple!) type. Unlike +/// the other "ensure X up to date" functions, this particular function also asserts that the +/// *layout* of `ty` is *already* up-to-date (though it is okay for that resolution to have failed). +/// Returns `error.AnalysisFail` if an analysis error is encountered while resolving the default +/// field values; the caller is free to ignore this, since the error is already registered. +pub fn ensureStructDefaultsUpToDate( + pt: Zcu.PerThread, + ty: Type, + /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. + reason: ?*const Zcu.DependencyReason, +) Zcu.SemaError!void { + const tracy_trace = trace(@src()); + defer tracy_trace.end(); + + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const comp = zcu.comp; + const gpa = comp.gpa; + + assert(ip.indexToKey(ty.toIntern()) == .struct_type); + + const anal_unit: AnalUnit = .wrap(.{ .struct_defaults = ty.toIntern() }); + + log.debug("ensureStructDefaultsUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)}); + + assert(!zcu.analysis_in_progress.contains(anal_unit)); + + const was_outdated: bool = outdated: { + if (zcu.clearOutdatedState(anal_unit)) break :outdated true; + // The type layout should already be marked as "wanted" by this point, because a struct's + // layout must always be analyzed before its default values are. + assert(!ip.setWantTypeLayout(comp.io, ty.toIntern())); + break :outdated false; + }; + + if (was_outdated) { + // `was_outdated` is true in the initial update, so this isn't a `dev.check`. + if (dev.env.supports(.incremental)) { + zcu.resetUnit(anal_unit); + } + // For types, we already know that we have to invalidate all dependees. + // TODO: we actually *could* detect whether everything was the same. should we bother? + try zcu.markDependeeOutdated(.marked_po, .{ .struct_defaults = ty.toIntern() }); + } else { + // We can trust the current information about this unit. + if (zcu.failed_analysis.contains(anal_unit)) return error.AnalysisFail; + if (zcu.transitive_failed_analysis.contains(anal_unit)) return error.AnalysisFail; + return; + } + + if (zcu.comp.debugIncremental()) { + const info = try zcu.incremental_debug_state.getUnitInfo(gpa, anal_unit); + info.last_update_gen = zcu.generation; + info.deps.clearRetainingCapacity(); + } + + const unit_tracking = zcu.trackUnitSema(ty.containerTypeName(ip).toSlice(ip), null); + defer unit_tracking.end(zcu); + + try zcu.analysis_in_progress.put(gpa, anal_unit, reason); + defer assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); + + var comptime_err_ret_trace: std.array_list.Managed(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + const file = zcu.namespacePtr(ty.getNamespaceIndex(zcu)).fileScope(zcu); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = file.zir.?, + .owner = anal_unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = .void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, + }; + defer sema.deinit(); + + const new_failed: bool = if (Sema.type_resolution.resolveStructDefaults(&sema, ty)) failed: { + break :failed false; + } else |err| switch (err) { + error.AnalysisFail => failed: { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this unit caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); + log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)}); + } + break :failed true; + }, + error.OutOfMemory, + error.Canceled, + => |e| return e, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, + }; + + sema.flushExports() catch |err| switch (err) { + error.OutOfMemory => |e| return e, + }; + + // We don't need to `markDependeeOutdated`/`markPoDependeeUpToDate` here, because we already + // marked the struct defaults as outdated at the top of this function. + + if (new_failed) return error.AnalysisFail; +} + /// Ensures that the resolved value of the given `Nav` is fully up-to-date, performing re-analysis /// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is /// free to ignore this, since the error is already registered. diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 806a85ff23cb3a0b8ead4e6716b24de3b70995f5..d09012fe948ba1a70b1b93fb99a8966abf89c8bc 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -3827,7 +3827,15 @@ fn updateConstInner(dwarf: *Dwarf, pt: Zcu.PerThread, debug_const_index: link.Co try diw.writeUleb128(ty.abiAlignment(zcu).toByteUnits().?); for (0..loaded_struct.field_types.len) |field_index| { const is_comptime = loaded_struct.field_is_comptime_bits.get(ip, field_index); - const field_init = loaded_struct.field_defaults.getOrNone(ip, field_index); + // TODO: we currently don't emit information about default values for + // non-`comptime` fields, because these default values are resolved at a + // separate time in the compiler frontend. To emit this information, the + // frontend needs to tell us when the default values are available: like + // how `Zcu.PerThread.ensureTypeLayoutUpToDate` enqueues a link task to + // indicate completion of the type's layout, a task should be enqueued + // by `Zcu.PerThread.ensureStructDefaultsUpToDate`, and upon receiving + // it we should patch the correct default field values in. + const field_init: InternPool.Index = if (is_comptime) loaded_struct.field_defaults.getOrNone(ip, field_index) else .none; assert(!(is_comptime and field_init == .none)); const field_type: Type = .fromInterned(loaded_struct.field_types.get(ip)[field_index]); const has_runtime_bits, const has_comptime_state = switch (field_init) {