diff --git a/src/Compilation.zig b/src/Compilation.zig index 8b158390b660..6f9b2e18d62b 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -348,12 +348,15 @@ const Job = union(enum) { /// Corresponds to the task in `link.Task`. /// Only needed for backends that haven't yet been updated to not race against Sema. codegen_type: InternPool.Index, - /// The `Cau` must be semantically analyzed (and possibly export itself). + /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed. + /// This may be its first time being analyzed, or it may be outdated. + /// If the unit is a function, a `codegen_func` job will then be queued. + analyze_comptime_unit: InternPool.AnalUnit, + /// This function must be semantically analyzed. /// This may be its first time being analyzed, or it may be outdated. - analyze_cau: InternPool.Cau.Index, - /// Analyze the body of a runtime function. /// After analysis, a `codegen_func` job will be queued. /// These must be separate jobs to ensure any needed type resolution occurs *before* codegen. + /// This job is separate from `analyze_comptime_unit` because it has a different priority. analyze_func: InternPool.Index, /// The main source file for the module needs to be analyzed. analyze_mod: *Package.Module, @@ -3141,8 +3144,10 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { } const file_index = switch (anal_unit.unwrap()) { - .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope, - .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file, + .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index.resolveFile(ip), + .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index.resolveFile(ip), + .type => |ty| Type.fromInterned(ty).typeDeclInst(zcu).?.resolveFile(ip), + .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFile(ip), }; // Skip errors for AnalUnits within files that had a parse failure. @@ -3374,11 +3379,9 @@ pub fn addModuleErrorMsg( const rt_file_path = try src.file_scope.fullPath(gpa); defer gpa.free(rt_file_path); const name = switch (ref.referencer.unwrap()) { - .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { - .nav => |nav| ip.getNav(nav).name.toSlice(ip), - .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), - .none => "comptime", - }, + .@"comptime" => "comptime", + .nav_val => |nav| ip.getNav(nav).name.toSlice(ip), + .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), .func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip), }; try ref_traces.append(gpa, .{ @@ -3641,10 +3644,13 @@ fn performAllTheWorkInner( // If there's no work queued, check if there's anything outdated // which we need to work on, and queue it if so. if (try zcu.findOutdatedToAnalyze()) |outdated| { - switch (outdated.unwrap()) { - .cau => |cau| try comp.queueJob(.{ .analyze_cau = cau }), - .func => |func| try comp.queueJob(.{ .analyze_func = func }), - } + try comp.queueJob(switch (outdated.unwrap()) { + .func => |f| .{ .analyze_func = f }, + .@"comptime", + .nav_val, + .type, + => .{ .analyze_comptime_unit = outdated }, + }); continue; } } @@ -3667,8 +3673,8 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre .codegen_nav => |nav_index| { const zcu = comp.zcu.?; const nav = zcu.intern_pool.getNav(nav_index); - if (nav.analysis_owner.unwrap()) |cau| { - const unit = InternPool.AnalUnit.wrap(.{ .cau = cau }); + if (nav.analysis != null) { + const unit: InternPool.AnalUnit = .wrap(.{ .nav_val = nav_index }); if (zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit)) { return; } @@ -3688,36 +3694,47 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); - pt.ensureFuncBodyAnalyzed(func) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, + + pt.ensureFuncBodyUpToDate(func) catch |err| switch (err) { + error.OutOfMemory => |e| return e, error.AnalysisFail => return, }; }, - .analyze_cau => |cau_index| { + .analyze_comptime_unit => |unit| { + const named_frame = tracy.namedFrame("analyze_comptime_unit"); + defer named_frame.end(); + const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); - pt.ensureCauAnalyzed(cau_index) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, + + const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) { + .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu), + .nav_val => |nav| pt.ensureNavValUpToDate(nav), + .type => |ty| if (pt.ensureTypeUpToDate(ty)) |_| {} else |err| err, + .func => unreachable, + }; + maybe_err catch |err| switch (err) { + error.OutOfMemory => |e| return e, error.AnalysisFail => return, }; + queue_test_analysis: { if (!comp.config.is_test) break :queue_test_analysis; + const nav = switch (unit.unwrap()) { + .nav_val => |nav| nav, + else => break :queue_test_analysis, + }; // Check if this is a test function. const ip = &pt.zcu.intern_pool; - const cau = ip.getCau(cau_index); - const nav_index = switch (cau.owner.unwrap()) { - .none, .type => break :queue_test_analysis, - .nav => |nav| nav, - }; - if (!pt.zcu.test_functions.contains(nav_index)) { + if (!pt.zcu.test_functions.contains(nav)) { break :queue_test_analysis; } // Tests are always emitted in test binaries. The decl_refs are created by // Zcu.populateTestFunctions, but this will not queue body analysis, so do // that now. - try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav_index).status.resolved.val); + try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav).status.resolved.val); } }, .resolve_type_fully => |ty| { diff --git a/src/InternPool.zig b/src/InternPool.zig index 920f0df4ee49..41019ea9d989 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -363,33 +363,53 @@ pub fn rehashTrackedInsts( } /// Analysis Unit. Represents a single entity which undergoes semantic analysis. -/// This is either a `Cau` or a runtime function. -/// The LSB is used as a tag bit. /// This is the "source" of an incremental dependency edge. -pub const AnalUnit = packed struct(u32) { - kind: enum(u1) { cau, func }, - index: u31, - pub const Unwrapped = union(enum) { - cau: Cau.Index, +pub const AnalUnit = packed struct(u64) { + kind: Kind, + id: u32, + + pub const Kind = enum(u32) { + @"comptime", + nav_val, + type, + func, + }; + + pub const Unwrapped = union(Kind) { + /// This `AnalUnit` analyzes the body of the given `comptime` declaration. + @"comptime": ComptimeUnit.Id, + /// This `AnalUnit` resolves the value of the given `Nav`. + nav_val: Nav.Index, + /// This `AnalUnit` resolves the given `struct`/`union`/`enum` type. + /// Generated tag enums are never used here (they do not undergo type resolution). + type: InternPool.Index, + /// This `AnalUnit` analyzes the body of the given runtime function. func: InternPool.Index, }; - pub fn unwrap(as: AnalUnit) Unwrapped { - return switch (as.kind) { - .cau => .{ .cau = @enumFromInt(as.index) }, - .func => .{ .func = @enumFromInt(as.index) }, + + pub fn unwrap(au: AnalUnit) Unwrapped { + return switch (au.kind) { + inline else => |tag| @unionInit( + Unwrapped, + @tagName(tag), + @enumFromInt(au.id), + ), }; } pub fn wrap(raw: Unwrapped) AnalUnit { return switch (raw) { - .cau => |cau| .{ .kind = .cau, .index = @intCast(@intFromEnum(cau)) }, - .func => |func| .{ .kind = .func, .index = @intCast(@intFromEnum(func)) }, + inline else => |id, tag| .{ + .kind = tag, + .id = @intFromEnum(id), + }, }; } + pub fn toOptional(as: AnalUnit) Optional { - return @enumFromInt(@as(u32, @bitCast(as))); + return @enumFromInt(@as(u64, @bitCast(as))); } - pub const Optional = enum(u32) { - none = std.math.maxInt(u32), + pub const Optional = enum(u64) { + none = std.math.maxInt(u64), _, pub fn unwrap(opt: Optional) ?AnalUnit { return switch (opt) { @@ -400,97 +420,30 @@ pub const AnalUnit = packed struct(u32) { }; }; -/// Comptime Analysis Unit. This is the "subject" of semantic analysis where the root context is -/// comptime; every `Sema` is owned by either a `Cau` or a runtime function (see `AnalUnit`). -/// The state stored here is immutable. -/// -/// * Every ZIR `declaration` has a `Cau` (post-instantiation) to analyze the declaration body. -/// * Every `struct`, `union`, and `enum` has a `Cau` for type resolution. -/// -/// The analysis status of a `Cau` is known only from state in `Zcu`. -/// An entry in `Zcu.failed_analysis` indicates an analysis failure with associated error message. -/// An entry in `Zcu.transitive_failed_analysis` indicates a transitive analysis failure. -/// -/// 12 bytes. -pub const Cau = struct { - /// The `declaration`, `struct_decl`, `enum_decl`, or `union_decl` instruction which this `Cau` analyzes. +pub const ComptimeUnit = extern struct { zir_index: TrackedInst.Index, - /// The namespace which this `Cau` should be analyzed within. namespace: NamespaceIndex, - /// This field essentially tells us what to do with the information resulting from - /// semantic analysis. See `Owner.Unwrapped` for details. - owner: Owner, - - /// See `Owner.Unwrapped` for details. In terms of representation, the `InternPool.Index` - /// or `Nav.Index` is cast to a `u31` and stored in `index`. As a special case, if - /// `@as(u32, @bitCast(owner)) == 0xFFFF_FFFF`, then the value is treated as `.none`. - pub const Owner = packed struct(u32) { - kind: enum(u1) { type, nav }, - index: u31, - - pub const Unwrapped = union(enum) { - /// This `Cau` exists in isolation. It is a global `comptime` declaration, or (TODO ANYTHING ELSE?). - /// After semantic analysis completes, the result is discarded. - none, - /// This `Cau` is owned by the given type for type resolution. - /// This is a `struct`, `union`, or `enum` type. - type: InternPool.Index, - /// This `Cau` is owned by the given `Nav` to resolve its value. - /// When analyzing the `Cau`, the resulting value is stored as the value of this `Nav`. - nav: Nav.Index, - }; - pub fn unwrap(owner: Owner) Unwrapped { - if (@as(u32, @bitCast(owner)) == std.math.maxInt(u32)) { - return .none; - } - return switch (owner.kind) { - .type => .{ .type = @enumFromInt(owner.index) }, - .nav => .{ .nav = @enumFromInt(owner.index) }, - }; - } - - fn wrap(raw: Unwrapped) Owner { - return switch (raw) { - .none => @bitCast(@as(u32, std.math.maxInt(u32))), - .type => |ty| .{ .kind = .type, .index = @intCast(@intFromEnum(ty)) }, - .nav => |nav| .{ .kind = .nav, .index = @intCast(@intFromEnum(nav)) }, - }; - } - }; + comptime { + assert(std.meta.hasUniqueRepresentation(ComptimeUnit)); + } - pub const Index = enum(u32) { + pub const Id = enum(u32) { _, - pub const Optional = enum(u32) { - none = std.math.maxInt(u32), - _, - pub fn unwrap(opt: Optional) ?Cau.Index { - return switch (opt) { - .none => null, - _ => @enumFromInt(@intFromEnum(opt)), - }; - } - - const debug_state = InternPool.debug_state; - }; - pub fn toOptional(i: Cau.Index) Optional { - return @enumFromInt(@intFromEnum(i)); - } const Unwrapped = struct { tid: Zcu.PerThread.Id, index: u32, - - fn wrap(unwrapped: Unwrapped, ip: *const InternPool) Cau.Index { + fn wrap(unwrapped: Unwrapped, ip: *const InternPool) ComptimeUnit.Id { assert(@intFromEnum(unwrapped.tid) <= ip.getTidMask()); - assert(unwrapped.index <= ip.getIndexMask(u31)); - return @enumFromInt(@as(u32, @intFromEnum(unwrapped.tid)) << ip.tid_shift_31 | + assert(unwrapped.index <= ip.getIndexMask(u32)); + return @enumFromInt(@as(u32, @intFromEnum(unwrapped.tid)) << ip.tid_shift_32 | unwrapped.index); } }; - fn unwrap(cau_index: Cau.Index, ip: *const InternPool) Unwrapped { + fn unwrap(id: Id, ip: *const InternPool) Unwrapped { return .{ - .tid = @enumFromInt(@intFromEnum(cau_index) >> ip.tid_shift_31 & ip.getTidMask()), - .index = @intFromEnum(cau_index) & ip.getIndexMask(u31), + .tid = @enumFromInt(@intFromEnum(id) >> ip.tid_shift_32 & ip.getTidMask()), + .index = @intFromEnum(id) & ip.getIndexMask(u31), }; } @@ -507,6 +460,11 @@ pub const Cau = struct { /// * Generic instances have a `Nav` corresponding to the instantiated function. /// * `@extern` calls create a `Nav` whose value is a `.@"extern"`. /// +/// This data structure is optimized for the `analysis_info != null` case, because this is much more +/// common in practice; the other case is used only for externs and for generic instances. At the time +/// of writing, in the compiler itself, around 74% of all `Nav`s have `analysis_info != null`. +/// (Specifically, 104225 / 140923) +/// /// `Nav.Repr` is the in-memory representation. pub const Nav = struct { /// The unqualified name of this `Nav`. Namespace lookups use this name, and error messages may use it. @@ -514,13 +472,16 @@ pub const Nav = struct { name: NullTerminatedString, /// The fully-qualified name of this `Nav`. fqn: NullTerminatedString, - /// If the value of this `Nav` is resolved by semantic analysis, it is within this `Cau`. - /// If this is `.none`, then `status == .resolved` always. - analysis_owner: Cau.Index.Optional, + /// This field is populated iff this `Nav` is resolved by semantic analysis. + /// If this is `null`, then `status == .resolved` always. + analysis: ?struct { + namespace: NamespaceIndex, + zir_index: TrackedInst.Index, + }, /// TODO: this is a hack! If #20663 isn't accepted, let's figure out something a bit better. is_usingnamespace: bool, status: union(enum) { - /// This `Nav` is pending semantic analysis through `analysis_owner`. + /// This `Nav` is pending semantic analysis. unresolved, /// The value of this `Nav` is resolved. resolved: struct { @@ -544,17 +505,16 @@ pub const Nav = struct { /// Get the ZIR instruction corresponding to this `Nav`, used to resolve source locations. /// This is a `declaration`. pub fn srcInst(nav: Nav, ip: *const InternPool) TrackedInst.Index { - if (nav.analysis_owner.unwrap()) |cau| { - return ip.getCau(cau).zir_index; + if (nav.analysis) |a| { + return a.zir_index; } - // A `Nav` with no corresponding `Cau` always has a resolved value. + // A `Nav` which does not undergo analysis always has a resolved value. return switch (ip.indexToKey(nav.status.resolved.val)) { .func => |func| { - // Since there was no `analysis_owner`, this must be an instantiation. - // Go up to the generic owner and consult *its* `analysis_owner`. + // Since `analysis` was not populated, this must be an instantiation. + // Go up to the generic owner and consult *its* `analysis` field. const go_nav = ip.getNav(ip.indexToKey(func.generic_owner).func.owner_nav); - const go_cau = ip.getCau(go_nav.analysis_owner.unwrap().?); - return go_cau.zir_index; + return go_nav.analysis.?.zir_index; }, .@"extern" => |@"extern"| @"extern".zir_index, // extern / @extern else => unreachable, @@ -600,11 +560,13 @@ pub const Nav = struct { }; /// The compact in-memory representation of a `Nav`. - /// 18 bytes. + /// 26 bytes. const Repr = struct { name: NullTerminatedString, fqn: NullTerminatedString, - analysis_owner: Cau.Index.Optional, + // The following 1 fields are either both populated, or both `.none`. + analysis_namespace: OptionalNamespaceIndex, + analysis_zir_index: TrackedInst.Index.Optional, /// Populated only if `bits.status == .resolved`. val: InternPool.Index, /// Populated only if `bits.status == .resolved`. @@ -625,7 +587,13 @@ pub const Nav = struct { return .{ .name = repr.name, .fqn = repr.fqn, - .analysis_owner = repr.analysis_owner, + .analysis = if (repr.analysis_namespace.unwrap()) |namespace| .{ + .namespace = namespace, + .zir_index = repr.analysis_zir_index.unwrap().?, + } else a: { + assert(repr.analysis_zir_index == .none); + break :a null; + }, .is_usingnamespace = repr.bits.is_usingnamespace, .status = switch (repr.bits.status) { .unresolved => .unresolved, @@ -646,7 +614,8 @@ pub const Nav = struct { return .{ .name = nav.name, .fqn = nav.fqn, - .analysis_owner = nav.analysis_owner, + .analysis_namespace = if (nav.analysis) |a| a.namespace.toOptional() else .none, + .analysis_zir_index = if (nav.analysis) |a| a.zir_index.toOptional() else .none, .val = switch (nav.status) { .unresolved => .none, .resolved => |r| r.val, @@ -862,8 +831,8 @@ const Local = struct { tracked_insts: ListMutate, files: ListMutate, maps: ListMutate, - caus: ListMutate, navs: ListMutate, + comptime_units: ListMutate, namespaces: BucketListMutate, } align(std.atomic.cache_line), @@ -876,8 +845,8 @@ const Local = struct { tracked_insts: TrackedInsts, files: List(File), maps: Maps, - caus: Caus, navs: Navs, + comptime_units: ComptimeUnits, namespaces: Namespaces, @@ -899,8 +868,8 @@ const Local = struct { const Strings = List(struct { u8 }); const TrackedInsts = List(struct { TrackedInst.MaybeLost }); const Maps = List(struct { FieldMap }); - const Caus = List(struct { Cau }); const Navs = List(Nav.Repr); + const ComptimeUnits = List(struct { ComptimeUnit }); const namespaces_bucket_width = 8; const namespaces_bucket_mask = (1 << namespaces_bucket_width) - 1; @@ -1275,21 +1244,21 @@ const Local = struct { }; } - pub fn getMutableCaus(local: *Local, gpa: Allocator) Caus.Mutable { + pub fn getMutableNavs(local: *Local, gpa: Allocator) Navs.Mutable { return .{ .gpa = gpa, .arena = &local.mutate.arena, - .mutate = &local.mutate.caus, - .list = &local.shared.caus, + .mutate = &local.mutate.navs, + .list = &local.shared.navs, }; } - pub fn getMutableNavs(local: *Local, gpa: Allocator) Navs.Mutable { + pub fn getMutableComptimeUnits(local: *Local, gpa: Allocator) ComptimeUnits.Mutable { return .{ .gpa = gpa, .arena = &local.mutate.arena, - .mutate = &local.mutate.navs, - .list = &local.shared.navs, + .mutate = &local.mutate.comptime_units, + .list = &local.shared.comptime_units, }; } @@ -3052,8 +3021,6 @@ pub const LoadedUnionType = struct { // TODO: the non-fqn will be needed by the new dwarf structure /// The name of this union type. name: NullTerminatedString, - /// The `Cau` within which type resolution occurs. - cau: Cau.Index, /// Represents the declarations inside this union. namespace: NamespaceIndex, /// The enum tag type. @@ -3370,7 +3337,6 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .tid = unwrapped_index.tid, .extra_index = data, .name = type_union.data.name, - .cau = type_union.data.cau, .namespace = type_union.data.namespace, .enum_tag_ty = type_union.data.tag_ty, .field_types = field_types, @@ -3387,8 +3353,6 @@ pub const LoadedStructType = struct { // TODO: the non-fqn will be needed by the new dwarf structure /// The name of this struct type. name: NullTerminatedString, - /// The `Cau` within which type resolution occurs. - cau: Cau.Index, namespace: NamespaceIndex, /// Index of the `struct_decl` or `reify` ZIR instruction. zir_index: TrackedInst.Index, @@ -3979,7 +3943,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { switch (item.tag) { .type_struct => { const name: NullTerminatedString = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "name").?]); - const cau: Cau.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "cau").?]); const namespace: NamespaceIndex = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "namespace").?]); const zir_index: TrackedInst.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "zir_index").?]); const fields_len = extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "fields_len").?]; @@ -4066,7 +4029,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .tid = unwrapped_index.tid, .extra_index = item.data, .name = name, - .cau = cau, .namespace = namespace, .zir_index = zir_index, .layout = if (flags.is_extern) .@"extern" else .auto, @@ -4083,7 +4045,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { }, .type_struct_packed, .type_struct_packed_inits => { const name: NullTerminatedString = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "name").?]); - const cau: Cau.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "cau").?]); const zir_index: TrackedInst.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "zir_index").?]); const fields_len = extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "fields_len").?]; const namespace: NamespaceIndex = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").?]); @@ -4130,7 +4091,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .tid = unwrapped_index.tid, .extra_index = item.data, .name = name, - .cau = cau, .namespace = namespace, .zir_index = zir_index, .layout = .@"packed", @@ -4153,9 +4113,6 @@ pub const LoadedEnumType = struct { // TODO: the non-fqn will be needed by the new dwarf structure /// The name of this enum type. name: NullTerminatedString, - /// The `Cau` within which type resolution occurs. - /// `null` if this is a generated tag type. - cau: Cau.Index.Optional, /// Represents the declarations inside this enum. namespace: NamespaceIndex, /// An integer type which is used for the numerical value of the enum. @@ -4232,21 +4189,15 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType { .type_enum_auto => { const extra = extraDataTrail(extra_list, EnumAuto, item.data); var extra_index: u32 = @intCast(extra.end); - const cau: Cau.Index.Optional = if (extra.data.zir_index == .none) cau: { + if (extra.data.zir_index == .none) { extra_index += 1; // owner_union - break :cau .none; - } else cau: { - const cau: Cau.Index = @enumFromInt(extra_list.view().items(.@"0")[extra_index]); - extra_index += 1; // cau - break :cau cau.toOptional(); - }; + } const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: { extra_index += 2; // type_hash: PackedU64 break :c 0; } else extra.data.captures_len; return .{ .name = extra.data.name, - .cau = cau, .namespace = extra.data.namespace, .tag_ty = extra.data.int_tag_type, .names = .{ @@ -4272,21 +4223,15 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType { }; const extra = extraDataTrail(extra_list, EnumExplicit, item.data); var extra_index: u32 = @intCast(extra.end); - const cau: Cau.Index.Optional = if (extra.data.zir_index == .none) cau: { + if (extra.data.zir_index == .none) { extra_index += 1; // owner_union - break :cau .none; - } else cau: { - const cau: Cau.Index = @enumFromInt(extra_list.view().items(.@"0")[extra_index]); - extra_index += 1; // cau - break :cau cau.toOptional(); - }; + } const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: { extra_index += 2; // type_hash: PackedU64 break :c 0; } else extra.data.captures_len; return .{ .name = extra.data.name, - .cau = cau, .namespace = extra.data.namespace, .tag_ty = extra.data.int_tag_type, .names = .{ @@ -5256,7 +5201,6 @@ pub const Tag = enum(u8) { .payload = EnumExplicit, .trailing = struct { owner_union: Index, - cau: ?Cau.Index, captures: ?[]CaptureValue, type_hash: ?u64, field_names: []NullTerminatedString, @@ -5302,7 +5246,6 @@ pub const Tag = enum(u8) { .payload = EnumAuto, .trailing = struct { owner_union: ?Index, - cau: ?Cau.Index, captures: ?[]CaptureValue, type_hash: ?u64, field_names: []NullTerminatedString, @@ -5679,7 +5622,6 @@ pub const Tag = enum(u8) { size: u32, /// Only valid after .have_layout padding: u32, - cau: Cau.Index, namespace: NamespaceIndex, /// The enum that provides the list of field names and values. tag_ty: Index, @@ -5710,7 +5652,6 @@ pub const Tag = enum(u8) { /// 5. init: Index for each fields_len // if tag is type_struct_packed_inits pub const TypeStructPacked = struct { name: NullTerminatedString, - cau: Cau.Index, zir_index: TrackedInst.Index, fields_len: u32, namespace: NamespaceIndex, @@ -5758,7 +5699,6 @@ pub const Tag = enum(u8) { /// 8. field_offset: u32 // for each field in declared order, undef until layout_resolved pub const TypeStruct = struct { name: NullTerminatedString, - cau: Cau.Index, zir_index: TrackedInst.Index, namespace: NamespaceIndex, fields_len: u32, @@ -6088,11 +6028,10 @@ pub const Array = struct { /// Trailing: /// 0. owner_union: Index // if `zir_index == .none` -/// 1. cau: Cau.Index // if `zir_index != .none` -/// 2. capture: CaptureValue // for each `captures_len` -/// 3. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) -/// 4. field name: NullTerminatedString for each fields_len; declaration order -/// 5. tag value: Index for each fields_len; declaration order +/// 1. capture: CaptureValue // for each `captures_len` +/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) +/// 3. field name: NullTerminatedString for each fields_len; declaration order +/// 4. tag value: Index for each fields_len; declaration order pub const EnumExplicit = struct { name: NullTerminatedString, /// `std.math.maxInt(u32)` indicates this type is reified. @@ -6115,10 +6054,9 @@ pub const EnumExplicit = struct { /// Trailing: /// 0. owner_union: Index // if `zir_index == .none` -/// 1. cau: Cau.Index // if `zir_index != .none` -/// 2. capture: CaptureValue // for each `captures_len` -/// 3. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) -/// 4. field name: NullTerminatedString for each fields_len; declaration order +/// 1. capture: CaptureValue // for each `captures_len` +/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) +/// 3. field name: NullTerminatedString for each fields_len; declaration order pub const EnumAuto = struct { name: NullTerminatedString, /// `std.math.maxInt(u32)` indicates this type is reified. @@ -6408,32 +6346,32 @@ pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void { ip.locals = try gpa.alloc(Local, used_threads); @memset(ip.locals, .{ .shared = .{ - .items = Local.List(Item).empty, - .extra = Local.Extra.empty, - .limbs = Local.Limbs.empty, - .strings = Local.Strings.empty, - .tracked_insts = Local.TrackedInsts.empty, - .files = Local.List(File).empty, - .maps = Local.Maps.empty, - .caus = Local.Caus.empty, - .navs = Local.Navs.empty, - - .namespaces = Local.Namespaces.empty, + .items = .empty, + .extra = .empty, + .limbs = .empty, + .strings = .empty, + .tracked_insts = .empty, + .files = .empty, + .maps = .empty, + .navs = .empty, + .comptime_units = .empty, + + .namespaces = .empty, }, .mutate = .{ .arena = .{}, - .items = Local.ListMutate.empty, - .extra = Local.ListMutate.empty, - .limbs = Local.ListMutate.empty, - .strings = Local.ListMutate.empty, - .tracked_insts = Local.ListMutate.empty, - .files = Local.ListMutate.empty, - .maps = Local.ListMutate.empty, - .caus = Local.ListMutate.empty, - .navs = Local.ListMutate.empty, + .items = .empty, + .extra = .empty, + .limbs = .empty, + .strings = .empty, + .tracked_insts = .empty, + .files = .empty, + .maps = .empty, + .navs = .empty, + .comptime_units = .empty, - .namespaces = Local.BucketListMutate.empty, + .namespaces = .empty, }, }); @@ -6506,7 +6444,8 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { namespace.priv_decls.deinit(gpa); namespace.pub_usingnamespace.deinit(gpa); namespace.priv_usingnamespace.deinit(gpa); - namespace.other_decls.deinit(gpa); + namespace.comptime_decls.deinit(gpa); + namespace.test_decls.deinit(gpa); } }; const maps = local.getMutableMaps(gpa); @@ -6525,8 +6464,6 @@ pub fn activate(ip: *const InternPool) void { _ = OptionalString.debug_state; _ = NullTerminatedString.debug_state; _ = OptionalNullTerminatedString.debug_state; - _ = Cau.Index.debug_state; - _ = Cau.Index.Optional.debug_state; _ = Nav.Index.debug_state; _ = Nav.Index.Optional.debug_state; std.debug.assert(debug_state.intern_pool == null); @@ -6711,14 +6648,14 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { if (extra.data.captures_len == std.math.maxInt(u32)) { break :ns .{ .reified = .{ .zir_index = zir_index, - .type_hash = extraData(extra_list, PackedU64, extra.end + 1).get(), + .type_hash = extraData(extra_list, PackedU64, extra.end).get(), } }; } break :ns .{ .declared = .{ .zir_index = zir_index, .captures = .{ .owned = .{ .tid = unwrapped_index.tid, - .start = extra.end + 1, + .start = extra.end, .len = extra.data.captures_len, } }, } }; @@ -6735,14 +6672,14 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { if (extra.data.captures_len == std.math.maxInt(u32)) { break :ns .{ .reified = .{ .zir_index = zir_index, - .type_hash = extraData(extra_list, PackedU64, extra.end + 1).get(), + .type_hash = extraData(extra_list, PackedU64, extra.end).get(), } }; } break :ns .{ .declared = .{ .zir_index = zir_index, .captures = .{ .owned = .{ .tid = unwrapped_index.tid, - .start = extra.end + 1, + .start = extra.end, .len = extra.data.captures_len, } }, } }; @@ -8323,7 +8260,6 @@ pub fn getUnionType( .size = std.math.maxInt(u32), .padding = std.math.maxInt(u32), .name = undefined, // set by `finish` - .cau = undefined, // set by `finish` .namespace = undefined, // set by `finish` .tag_ty = ini.enum_tag_ty, .zir_index = switch (ini.key) { @@ -8375,7 +8311,6 @@ pub fn getUnionType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "name").?, - .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "cau").?, .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "namespace").?, } }; } @@ -8384,7 +8319,6 @@ pub const WipNamespaceType = struct { tid: Zcu.PerThread.Id, index: Index, type_name_extra_index: u32, - cau_extra_index: ?u32, namespace_extra_index: u32, pub fn setName( @@ -8400,18 +8334,11 @@ pub const WipNamespaceType = struct { pub fn finish( wip: WipNamespaceType, ip: *InternPool, - analysis_owner: Cau.Index.Optional, namespace: NamespaceIndex, ) Index { const extra = ip.getLocalShared(wip.tid).extra.acquire(); const extra_items = extra.view().items(.@"0"); - if (wip.cau_extra_index) |i| { - extra_items[i] = @intFromEnum(analysis_owner.unwrap().?); - } else { - assert(analysis_owner == .none); - } - extra_items[wip.namespace_extra_index] = @intFromEnum(namespace); return wip.index; @@ -8510,7 +8437,6 @@ pub fn getStructType( ini.fields_len); // inits const extra_index = addExtraAssumeCapacity(extra, Tag.TypeStructPacked{ .name = undefined, // set by `finish` - .cau = undefined, // set by `finish` .zir_index = zir_index, .fields_len = ini.fields_len, .namespace = undefined, // set by `finish` @@ -8555,7 +8481,6 @@ pub fn getStructType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "name").?, - .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "cau").?, .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").?, } }; }, @@ -8578,7 +8503,6 @@ pub fn getStructType( 1); // names_map const extra_index = addExtraAssumeCapacity(extra, Tag.TypeStruct{ .name = undefined, // set by `finish` - .cau = undefined, // set by `finish` .zir_index = zir_index, .namespace = undefined, // set by `finish` .fields_len = ini.fields_len, @@ -8647,7 +8571,6 @@ pub fn getStructType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "name").?, - .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "cau").?, .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "namespace").?, } }; } @@ -9383,7 +9306,7 @@ fn finishFuncInstance( func_extra_index: u32, ) Allocator.Error!void { const fn_owner_nav = ip.getNav(ip.funcDeclInfo(generic_owner).owner_nav); - const fn_namespace = ip.getCau(fn_owner_nav.analysis_owner.unwrap().?).namespace; + const fn_namespace = fn_owner_nav.analysis.?.namespace; // TODO: improve this name const nav_name = try ip.getOrPutStringFmt(gpa, tid, "{}__anon_{d}", .{ @@ -9429,7 +9352,6 @@ pub const WipEnumType = struct { index: Index, tag_ty_index: u32, type_name_extra_index: u32, - cau_extra_index: u32, namespace_extra_index: u32, names_map: MapIndex, names_start: u32, @@ -9449,13 +9371,11 @@ pub const WipEnumType = struct { pub fn prepare( wip: WipEnumType, ip: *InternPool, - analysis_owner: Cau.Index, namespace: NamespaceIndex, ) void { const extra = ip.getLocalShared(wip.tid).extra.acquire(); const extra_items = extra.view().items(.@"0"); - extra_items[wip.cau_extra_index] = @intFromEnum(analysis_owner); extra_items[wip.namespace_extra_index] = @intFromEnum(namespace); } @@ -9556,7 +9476,6 @@ pub fn getEnumType( .reified => 2, // type_hash: PackedU64 } + // zig fmt: on - 1 + // cau ini.fields_len); // field types const extra_index = addExtraAssumeCapacity(extra, EnumAuto{ @@ -9577,8 +9496,6 @@ pub fn getEnumType( .tag = .type_enum_auto, .data = extra_index, }); - const cau_extra_index = extra.view().len; - extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish` switch (ini.key) { .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}), .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}), @@ -9591,7 +9508,6 @@ pub fn getEnumType( .index = gop.put(), .tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?, .type_name_extra_index = extra_index + std.meta.fieldIndex(EnumAuto, "name").?, - .cau_extra_index = @intCast(cau_extra_index), .namespace_extra_index = extra_index + std.meta.fieldIndex(EnumAuto, "namespace").?, .names_map = names_map, .names_start = @intCast(names_start), @@ -9616,7 +9532,6 @@ pub fn getEnumType( .reified => 2, // type_hash: PackedU64 } + // zig fmt: on - 1 + // cau ini.fields_len + // field types ini.fields_len * @intFromBool(ini.has_values)); // field values @@ -9643,8 +9558,6 @@ pub fn getEnumType( }, .data = extra_index, }); - const cau_extra_index = extra.view().len; - extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish` switch (ini.key) { .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}), .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}), @@ -9661,7 +9574,6 @@ pub fn getEnumType( .index = gop.put(), .tag_ty_index = extra_index + std.meta.fieldIndex(EnumExplicit, "int_tag_type").?, .type_name_extra_index = extra_index + std.meta.fieldIndex(EnumExplicit, "name").?, - .cau_extra_index = @intCast(cau_extra_index), .namespace_extra_index = extra_index + std.meta.fieldIndex(EnumExplicit, "namespace").?, .names_map = names_map, .names_start = @intCast(names_start), @@ -9858,7 +9770,6 @@ pub fn getOpaqueType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "name").?, - .cau_extra_index = null, // opaques do not undergo type resolution .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "namespace").?, }, }; @@ -9974,7 +9885,6 @@ fn addExtraAssumeCapacity(extra: Local.Extra.Mutable, item: anytype) u32 { inline for (@typeInfo(@TypeOf(item)).@"struct".fields) |field| { extra.appendAssumeCapacity(.{switch (field.type) { Index, - Cau.Index, Nav.Index, NamespaceIndex, OptionalNamespaceIndex, @@ -10037,7 +9947,6 @@ fn extraDataTrail(extra: Local.Extra, comptime T: type, index: u32) struct { dat const extra_item = extra_items[extra_index]; @field(result, field.name) = switch (field.type) { Index, - Cau.Index, Nav.Index, NamespaceIndex, OptionalNamespaceIndex, @@ -11058,12 +10967,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) try bw.flush(); } -pub fn getCau(ip: *const InternPool, index: Cau.Index) Cau { - const unwrapped = index.unwrap(ip); - const caus = ip.getLocalShared(unwrapped.tid).caus.acquire(); - return caus.view().items(.@"0")[unwrapped.index]; -} - pub fn getNav(ip: *const InternPool, index: Nav.Index) Nav { const unwrapped = index.unwrap(ip); const navs = ip.getLocalShared(unwrapped.tid).navs.acquire(); @@ -11077,51 +10980,34 @@ pub fn namespacePtr(ip: *InternPool, namespace_index: NamespaceIndex) *Zcu.Names return &namespaces_bucket[unwrapped_namespace_index.index]; } -/// Create a `Cau` associated with the type at the given `InternPool.Index`. -pub fn createTypeCau( +/// Create a `ComptimeUnit`, forming an `AnalUnit` for a `comptime` declaration. +pub fn createComptimeUnit( ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, zir_index: TrackedInst.Index, namespace: NamespaceIndex, - owner_type: InternPool.Index, -) Allocator.Error!Cau.Index { - const caus = ip.getLocal(tid).getMutableCaus(gpa); - const index_unwrapped: Cau.Index.Unwrapped = .{ +) Allocator.Error!ComptimeUnit.Id { + const comptime_units = ip.getLocal(tid).getMutableComptimeUnits(gpa); + const id_unwrapped: ComptimeUnit.Id.Unwrapped = .{ .tid = tid, - .index = caus.mutate.len, + .index = comptime_units.mutate.len, }; - try caus.append(.{.{ + try comptime_units.append(.{.{ .zir_index = zir_index, .namespace = namespace, - .owner = Cau.Owner.wrap(.{ .type = owner_type }), }}); - return index_unwrapped.wrap(ip); + return id_unwrapped.wrap(ip); } -/// Create a `Cau` for a `comptime` declaration. -pub fn createComptimeCau( - ip: *InternPool, - gpa: Allocator, - tid: Zcu.PerThread.Id, - zir_index: TrackedInst.Index, - namespace: NamespaceIndex, -) Allocator.Error!Cau.Index { - const caus = ip.getLocal(tid).getMutableCaus(gpa); - const index_unwrapped: Cau.Index.Unwrapped = .{ - .tid = tid, - .index = caus.mutate.len, - }; - try caus.append(.{.{ - .zir_index = zir_index, - .namespace = namespace, - .owner = Cau.Owner.wrap(.none), - }}); - return index_unwrapped.wrap(ip); +pub fn getComptimeUnit(ip: *const InternPool, id: ComptimeUnit.Id) ComptimeUnit { + const unwrapped = id.unwrap(ip); + const comptime_units = ip.getLocalShared(unwrapped.tid).comptime_units.acquire(); + return comptime_units.view().items(.@"0")[unwrapped.index]; } -/// Create a `Nav` not associated with any `Cau`. -/// Since there is no analysis owner, the `Nav`'s value must be known at creation time. +/// Create a `Nav` which does not undergo semantic analysis. +/// Since it is never analyzed, the `Nav`'s value must be known at creation time. pub fn createNav( ip: *InternPool, gpa: Allocator, @@ -11143,7 +11029,7 @@ pub fn createNav( try navs.append(Nav.pack(.{ .name = opts.name, .fqn = opts.fqn, - .analysis_owner = .none, + .analysis = null, .status = .{ .resolved = .{ .val = opts.val, .alignment = opts.alignment, @@ -11155,10 +11041,9 @@ pub fn createNav( return index_unwrapped.wrap(ip); } -/// Create a `Cau` and `Nav` which are paired. The value of the `Nav` is -/// determined by semantic analysis of the `Cau`. The value of the `Nav` -/// is initially unresolved. -pub fn createPairedCauNav( +/// Create a `Nav` which undergoes semantic analysis because it corresponds to a source declaration. +/// The value of the `Nav` is initially unresolved. +pub fn createDeclNav( ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, @@ -11168,36 +11053,28 @@ pub fn createPairedCauNav( namespace: NamespaceIndex, /// TODO: this is hacky! See `Nav.is_usingnamespace`. is_usingnamespace: bool, -) Allocator.Error!struct { Cau.Index, Nav.Index } { - const caus = ip.getLocal(tid).getMutableCaus(gpa); +) Allocator.Error!Nav.Index { const navs = ip.getLocal(tid).getMutableNavs(gpa); - try caus.ensureUnusedCapacity(1); try navs.ensureUnusedCapacity(1); - const cau = Cau.Index.Unwrapped.wrap(.{ - .tid = tid, - .index = caus.mutate.len, - }, ip); const nav = Nav.Index.Unwrapped.wrap(.{ .tid = tid, .index = navs.mutate.len, }, ip); - caus.appendAssumeCapacity(.{.{ - .zir_index = zir_index, - .namespace = namespace, - .owner = Cau.Owner.wrap(.{ .nav = nav }), - }}); navs.appendAssumeCapacity(Nav.pack(.{ .name = name, .fqn = fqn, - .analysis_owner = cau.toOptional(), + .analysis = .{ + .namespace = namespace, + .zir_index = zir_index, + }, .status = .unresolved, .is_usingnamespace = is_usingnamespace, })); - return .{ cau, nav }; + return nav; } /// Resolve the value of a `Nav` with an analysis owner. @@ -11220,12 +11097,14 @@ pub fn resolveNavValue( const navs = local.shared.navs.view(); - const nav_analysis_owners = navs.items(.analysis_owner); + const nav_analysis_namespace = navs.items(.analysis_namespace); + const nav_analysis_zir_index = navs.items(.analysis_zir_index); const nav_vals = navs.items(.val); const nav_linksections = navs.items(.@"linksection"); const nav_bits = navs.items(.bits); - assert(nav_analysis_owners[unwrapped.index] != .none); + assert(nav_analysis_namespace[unwrapped.index] != .none); + assert(nav_analysis_zir_index[unwrapped.index] != .none); @atomicStore(InternPool.Index, &nav_vals[unwrapped.index], resolved.val, .release); @atomicStore(OptionalNullTerminatedString, &nav_linksections[unwrapped.index], resolved.@"linksection", .release); diff --git a/src/Sema.zig b/src/Sema.zig index 75d8be47590b..a41762f530ac 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2870,7 +2870,7 @@ fn zirStructDecl( }; const wip_ty = switch (try ip.getStructType(gpa, pt.tid, struct_init, false)) { .existing => |ty| { - const new_ty = try pt.ensureTypeUpToDate(ty, false); + const new_ty = try pt.ensureTypeUpToDate(ty); // Make sure we update the namespace if the declaration is re-analyzed, to pick // up on e.g. changed comptime decls. @@ -2900,12 +2900,10 @@ fn zirStructDecl( }); errdefer pt.destroyNamespace(new_namespace_index); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - if (pt.zcu.comp.incremental) { try ip.addDependency( sema.gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + AnalUnit.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst }, ); } @@ -2922,7 +2920,7 @@ fn zirStructDecl( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn createTypeName( @@ -3100,7 +3098,7 @@ fn zirEnumDecl( }; const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, enum_init, false)) { .existing => |ty| { - const new_ty = try pt.ensureTypeUpToDate(ty, false); + const new_ty = try pt.ensureTypeUpToDate(ty); // Make sure we update the namespace if the declaration is re-analyzed, to pick // up on e.g. changed comptime decls. @@ -3136,16 +3134,14 @@ fn zirEnumDecl( }); errdefer if (!done) pt.destroyNamespace(new_namespace_index); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try pt.scanNamespace(new_namespace_index, decls); try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); // We've finished the initial construction of this type, and are about to perform analysis. - // Set the Cau and namespace appropriately, and don't destroy anything on failure. - wip_ty.prepare(ip, new_cau_index, new_namespace_index); + // Set the namespace appropriately, and don't destroy anything on failure. + wip_ty.prepare(ip, new_namespace_index); done = true; try Sema.resolveDeclaredEnum( @@ -3155,7 +3151,6 @@ fn zirEnumDecl( tracked_inst, new_namespace_index, type_name, - new_cau_index, small, body, tag_type_ref, @@ -3245,7 +3240,7 @@ fn zirUnionDecl( }; const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, union_init, false)) { .existing => |ty| { - const new_ty = try pt.ensureTypeUpToDate(ty, false); + const new_ty = try pt.ensureTypeUpToDate(ty); // Make sure we update the namespace if the declaration is re-analyzed, to pick // up on e.g. changed comptime decls. @@ -3275,12 +3270,10 @@ fn zirUnionDecl( }); errdefer pt.destroyNamespace(new_namespace_index); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - if (pt.zcu.comp.incremental) { try zcu.intern_pool.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + AnalUnit.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst }, ); } @@ -3297,7 +3290,7 @@ fn zirUnionDecl( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn zirOpaqueDecl( @@ -3382,7 +3375,7 @@ fn zirOpaqueDecl( try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn zirErrorSetDecl( @@ -6547,7 +6540,10 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void { const ip = &zcu.intern_pool; const func = switch (sema.owner.unwrap()) { .func => |func| func, - .cau => return, // does nothing outside a function + .@"comptime", + .nav_val, + .type, + => return, // does nothing outside a function }; ip.funcSetDisableInstrumentation(func); sema.allow_memoize = false; @@ -6868,11 +6864,8 @@ fn lookupInNamespace( ignore_self: { const skip_nav = switch (sema.owner.unwrap()) { - .func => break :ignore_self, - .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { - .none, .type => break :ignore_self, - .nav => |nav| nav, - }, + .@"comptime", .type, .func => break :ignore_self, + .nav_val => |nav| nav, }; var i: usize = 0; while (i < candidates.items.len) { @@ -7132,7 +7125,7 @@ fn zirCall( const call_inst = try sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, args_info, call_dbg_node, .call); switch (sema.owner.unwrap()) { - .cau => input_is_error = false, + .@"comptime", .type, .nav_val => input_is_error = false, .func => |owner_func| if (!zcu.intern_pool.funcAnalysisUnordered(owner_func).calls_or_awaits_errorable_fn) { // No errorable fn actually called; we have no error return trace input_is_error = false; @@ -7747,11 +7740,9 @@ fn analyzeCall( // The call site definitely depends on the function's signature. try sema.declareDependency(.{ .src_hash = module_fn.zir_body_inst }); - // This is not a function instance, so the function's `Nav` has a - // `Cau` -- we don't need to check `generic_owner`. + // This is not a function instance, so the function's `Nav` has analysis + // state -- we don't need to check `generic_owner`. const fn_nav = ip.getNav(module_fn.owner_nav); - const fn_cau_index = fn_nav.analysis_owner.unwrap().?; - const fn_cau = ip.getCau(fn_cau_index); // We effectively want a child Sema here, but can't literally do that, because we need AIR // to be shared. InlineCallSema is a wrapper which handles this for us. While `ics` is in @@ -7759,7 +7750,7 @@ fn analyzeCall( // whenever performing an operation where the difference matters. var ics = InlineCallSema.init( sema, - zcu.cauFileScope(fn_cau_index).zir, + zcu.navFileScope(module_fn.owner_nav).zir, module_fn_index, block.error_return_trace_index, ); @@ -7769,7 +7760,7 @@ fn analyzeCall( .parent = null, .sema = sema, // The function body exists in the same namespace as the corresponding function declaration. - .namespace = fn_cau.namespace, + .namespace = fn_nav.analysis.?.namespace, .instructions = .{}, .label = null, .inlining = &inlining, @@ -7780,7 +7771,7 @@ fn analyzeCall( .runtime_cond = block.runtime_cond, .runtime_loop = block.runtime_loop, .runtime_index = block.runtime_index, - .src_base_inst = fn_cau.zir_index, + .src_base_inst = fn_nav.analysis.?.zir_index, .type_name_ctx = fn_nav.fqn, }; @@ -7795,7 +7786,7 @@ fn analyzeCall( // mutate comptime state. // TODO: comptime call memoization is currently not supported under incremental compilation // since dependencies are not marked on callers. If we want to keep this around (we should - // check that it's worthwhile first!), each memoized call needs a `Cau`. + // check that it's worthwhile first!), each memoized call needs an `AnalUnit`. var should_memoize = !zcu.comp.incremental; // If it's a comptime function call, we need to memoize it as long as no external @@ -7904,7 +7895,7 @@ fn analyzeCall( // Since we're doing an inline call, we depend on the source code of the whole // function declaration. - try sema.declareDependency(.{ .src_hash = fn_cau.zir_index }); + try sema.declareDependency(.{ .src_hash = fn_nav.analysis.?.zir_index }); new_fn_info.return_type = sema.fn_ret_ty.toIntern(); if (!is_comptime_call and !block.is_typeof) { @@ -8016,7 +8007,7 @@ fn analyzeCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .cau => {}, + .@"comptime", .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -8268,10 +8259,9 @@ fn instantiateGenericCall( // The actual monomorphization happens via adding `func_instance` to // `InternPool`. - // Since we are looking at the generic owner here, it has a `Cau`. + // Since we are looking at the generic owner here, it has analysis state. const fn_nav = ip.getNav(generic_owner_func.owner_nav); - const fn_cau = ip.getCau(fn_nav.analysis_owner.unwrap().?); - const fn_zir = zcu.namespacePtr(fn_cau.namespace).fileScope(zcu).zir; + const fn_zir = zcu.navFileScope(generic_owner_func.owner_nav).zir; const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count()); @@ -8312,11 +8302,11 @@ fn instantiateGenericCall( var child_block: Block = .{ .parent = null, .sema = &child_sema, - .namespace = fn_cau.namespace, + .namespace = fn_nav.analysis.?.namespace, .instructions = .{}, .inlining = null, .is_comptime = true, - .src_base_inst = fn_cau.zir_index, + .src_base_inst = fn_nav.analysis.?.zir_index, .type_name_ctx = fn_nav.fqn, }; defer child_block.instructions.deinit(gpa); @@ -8481,7 +8471,7 @@ fn instantiateGenericCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .cau => {}, + .@"comptime", .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -9510,14 +9500,11 @@ fn zirFunc( // the callconv based on whether it is exported. Otherwise, the callconv defaults // to `.auto`. const cc: std.builtin.CallingConvention = if (has_body) cc: { - const func_decl_cau = if (sema.generic_owner != .none) cau: { - const generic_owner_fn = zcu.funcInfo(sema.generic_owner); - // The generic owner definitely has a `Cau` for the corresponding function declaration. - const generic_owner_nav = ip.getNav(generic_owner_fn.owner_nav); - break :cau generic_owner_nav.analysis_owner.unwrap().?; - } else sema.owner.unwrap().cau; + const func_decl_nav = if (sema.generic_owner != .none) nav: { + break :nav zcu.funcInfo(sema.generic_owner).owner_nav; + } else sema.owner.unwrap().nav_val; const fn_is_exported = exported: { - const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip) orelse return error.AnalysisFail; + const decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(ip) orelse return error.AnalysisFail; const zir_decl = sema.code.getDeclaration(decl_inst); break :exported zir_decl.linkage == .@"export"; }; @@ -9991,7 +9978,7 @@ fn funcCommon( if (!ret_poison) try sema.validateErrorUnionPayloadType(block, bare_return_type, ret_ty_src); const func_index = try ip.getFuncDeclIes(gpa, pt.tid, .{ - .owner_nav = sema.getOwnerCauNav(), + .owner_nav = sema.owner.unwrap().nav_val, .param_types = param_types, .noalias_bits = noalias_bits, @@ -10040,7 +10027,7 @@ fn funcCommon( if (has_body) { const func_index = try ip.getFuncDecl(gpa, pt.tid, .{ - .owner_nav = sema.getOwnerCauNav(), + .owner_nav = sema.owner.unwrap().nav_val, .ty = func_ty, .cc = cc, .is_noinline = is_noinline, @@ -17664,7 +17651,7 @@ fn zirAsm( if (is_volatile) { return sema.fail(block, src, "volatile keyword is redundant on module-level assembly", .{}); } - try zcu.addGlobalAssembly(sema.owner.unwrap().cau, asm_source); + try zcu.addGlobalAssembly(sema.owner, asm_source); return .void_value; } @@ -18155,7 +18142,7 @@ fn zirThis( _ = extended; const pt = sema.pt; const namespace = pt.zcu.namespacePtr(block.namespace); - const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type, false); + const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type); switch (pt.zcu.intern_pool.indexToKey(new_ty)) { .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }), .opaque_type => {}, @@ -19321,10 +19308,8 @@ fn typeInfoNamespaceDecls( } for (namespace.pub_usingnamespace.items) |nav| { - if (ip.getNav(nav).analysis_owner.unwrap()) |cau| { - if (zcu.analysis_in_progress.contains(AnalUnit.wrap(.{ .cau = cau }))) { - continue; - } + if (zcu.analysis_in_progress.contains(.wrap(.{ .nav_val = nav }))) { + continue; } try sema.ensureNavResolved(src, nav); const namespace_ty = Type.fromInterned(ip.getNav(nav).status.resolved.val); @@ -21187,14 +21172,13 @@ fn structInitAnon( .file_scope = block.getFileScopeIndex(zcu), .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip.index); try zcu.comp.queueJob(.{ .resolve_type_fully = wip.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; try zcu.comp.queueJob(.{ .codegen_type = wip.index }); } - break :ty wip.finish(ip, new_cau_index.toOptional(), new_namespace_index); + break :ty wip.finish(ip, new_namespace_index); }, .existing => |ty| ty, }; @@ -21618,7 +21602,7 @@ fn getErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref { .func => |func| if (ip.funcAnalysisUnordered(func).calls_or_awaits_errorable_fn and block.ownerModule().error_tracing) { return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty); }, - .cau => {}, + .@"comptime", .nav_val, .type => {}, } return Air.internedToRef(try pt.intern(.{ .opt = .{ .ty = opt_ptr_stack_trace_ty.toIntern(), @@ -22296,7 +22280,7 @@ fn zirReify( }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); }, .@"union" => { const struct_type = ip.loadStructType(ip.typeOf(union_val.val)); @@ -22505,11 +22489,9 @@ fn reifyEnum( .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - wip_ty.prepare(ip, new_cau_index, new_namespace_index); + wip_ty.prepare(ip, new_namespace_index); wip_ty.setTagTy(ip, tag_ty.toIntern()); done = true; @@ -22811,8 +22793,6 @@ fn reifyUnion( .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; @@ -22822,7 +22802,7 @@ fn reifyUnion( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn reifyTuple( @@ -23170,8 +23150,6 @@ fn reifyStruct( .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; @@ -23181,7 +23159,7 @@ fn reifyStruct( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref { @@ -26713,15 +26691,13 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A break :blk try sema.analyzeValueAsCallconv(block, cc_src, cc_val); } else cc: { if (has_body) { - const decl_inst = if (sema.generic_owner != .none) decl_inst: { + const func_decl_nav = if (sema.generic_owner != .none) nav: { // Generic instance -- use the original function declaration to // look for the `export` syntax. - const nav = zcu.intern_pool.getNav(zcu.funcInfo(sema.generic_owner).owner_nav); - const cau = zcu.intern_pool.getCau(nav.analysis_owner.unwrap().?); - break :decl_inst cau.zir_index; - } else sema.getOwnerCauDeclInst(); // not an instantiation so we're analyzing a function declaration Cau - - const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&zcu.intern_pool) orelse return error.AnalysisFail); + break :nav zcu.funcInfo(sema.generic_owner).owner_nav; + } else sema.owner.unwrap().nav_val; + const func_decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(&zcu.intern_pool) orelse return error.AnalysisFail; + const zir_decl = sema.code.getDeclaration(func_decl_inst); if (zir_decl.linkage == .@"export") { break :cc target.cCallingConvention() orelse { // This target has no default C calling convention. We sometimes trigger a similar @@ -27108,8 +27084,16 @@ fn zirBuiltinExtern( // `builtin_extern` doesn't provide enough information, and isn't currently tracked. // So, for now, just use our containing `declaration`. .zir_index = switch (sema.owner.unwrap()) { - .cau => sema.getOwnerCauDeclInst(), - .func => sema.getOwnerFuncDeclInst(), + .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index, + .type => |owner_ty| Type.fromInterned(owner_ty).typeDeclInst(zcu).?, + .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index, + .func => |func| zir_index: { + const func_info = zcu.funcInfo(func); + const owner_func_info = if (func_info.generic_owner != .none) owner: { + break :owner zcu.funcInfo(func_info.generic_owner); + } else func_info; + break :zir_index ip.getNav(owner_func_info.owner_nav).analysis.?.zir_index; + }, }, .owner_nav = undefined, // ignored by `getExtern` }); @@ -32670,28 +32654,25 @@ pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - - const cau_index = nav.analysis_owner.unwrap() orelse { + if (nav.analysis == null) { assert(nav.status == .resolved); return; - }; + } - // Note that even if `nav.status == .resolved`, we must still trigger `ensureCauAnalyzed` + // Note that even if `nav.status == .resolved`, we must still trigger `ensureNavValUpToDate` // to make sure the value is up-to-date on incremental updates. - assert(ip.getCau(cau_index).owner.unwrap().nav == nav_index); - - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_index }); try sema.addReferenceEntry(src, anal_unit); if (zcu.analysis_in_progress.contains(anal_unit)) { return sema.failWithOwnedErrorMsg(null, try sema.errMsg(.{ - .base_node_inst = ip.getCau(cau_index).zir_index, + .base_node_inst = nav.analysis.?.zir_index, .offset = LazySrcLoc.Offset.nodeOffset(0), }, "dependency loop detected", .{})); } - return pt.ensureCauAnalyzed(cau_index); + return pt.ensureNavValUpToDate(nav_index); } fn optRefValue(sema: *Sema, opt_val: ?Value) !Value { @@ -35641,7 +35622,7 @@ pub fn resolveStructAlignment( const ip = &zcu.intern_pool; const target = zcu.getTarget(); - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty); assert(struct_type.layout != .@"packed"); assert(struct_type.flagsUnordered(ip).alignment == .none); @@ -35684,7 +35665,7 @@ pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const struct_type = zcu.typeToStruct(ty) orelse return; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); if (struct_type.haveLayout(ip)) return; @@ -35831,15 +35812,13 @@ fn backingIntType( const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const cau_index = struct_type.cau; - var analysis_arena = std.heap.ArenaAllocator.init(gpa); defer analysis_arena.deinit(); var block: Block = .{ .parent = null, .sema = sema, - .namespace = ip.getCau(cau_index).namespace, + .namespace = struct_type.namespace, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -35971,7 +35950,7 @@ pub fn resolveUnionAlignment( const ip = &zcu.intern_pool; const target = zcu.getTarget(); - assert(sema.owner.unwrap().cau == union_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); assert(!union_type.haveLayout(ip)); @@ -36011,7 +35990,7 @@ pub fn resolveUnionLayout(sema: *Sema, ty: Type) SemaError!void { // Load again, since the tag type might have changed due to resolution. const union_type = ip.loadUnionType(ty.ip_index); - assert(sema.owner.unwrap().cau == union_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); const old_flags = union_type.flagsUnordered(ip); switch (old_flags.status) { @@ -36126,7 +36105,7 @@ pub fn resolveStructFully(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const struct_type = zcu.typeToStruct(ty).?; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); if (struct_type.setFullyResolved(ip)) return; errdefer struct_type.clearFullyResolved(ip); @@ -36149,7 +36128,7 @@ pub fn resolveUnionFully(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const union_obj = zcu.typeToUnion(ty).?; - assert(sema.owner.unwrap().cau == union_obj.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); switch (union_obj.flagsUnordered(ip).status) { .none, .have_field_types, .field_types_wip, .layout_wip, .have_layout => {}, @@ -36184,7 +36163,7 @@ pub fn resolveStructFieldTypes( const zcu = pt.zcu; const ip = &zcu.intern_pool; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty); if (struct_type.haveFieldTypes(ip)) return; @@ -36210,7 +36189,7 @@ pub fn resolveStructFieldInits(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const struct_type = zcu.typeToStruct(ty) orelse return; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); // Inits can start as resolved if (struct_type.haveFieldInits(ip)) return; @@ -36239,7 +36218,7 @@ pub fn resolveUnionFieldTypes(sema: *Sema, ty: Type, union_type: InternPool.Load const zcu = pt.zcu; const ip = &zcu.intern_pool; - assert(sema.owner.unwrap().cau == union_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); switch (union_type.flagsUnordered(ip).status) { .none => {}, @@ -36315,7 +36294,7 @@ fn resolveInferredErrorSet( // In this case we are dealing with the actual InferredErrorSet object that // corresponds to the function, not one created to track an inline/comptime call. try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = func_index })); - try pt.ensureFuncBodyAnalyzed(func_index); + try pt.ensureFuncBodyUpToDate(func_index); } // This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody` @@ -36472,8 +36451,7 @@ fn structFields( const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const cau_index = struct_type.cau; - const namespace_index = ip.getCau(cau_index).namespace; + const namespace_index = struct_type.namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail; @@ -36671,8 +36649,7 @@ fn structFieldInits( assert(!struct_type.haveFieldInits(ip)); - const cau_index = struct_type.cau; - const namespace_index = ip.getCau(cau_index).namespace; + const namespace_index = struct_type.namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail; const fields_len, _, var extra_index = structZirInfo(zir, zir_index); @@ -38474,13 +38451,11 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve // the loop. switch (sema.owner.unwrap()) { - .cau => |cau| switch (dependee) { - .nav_val => |nav| if (zcu.intern_pool.getNav(nav).analysis_owner == cau.toOptional()) { - return; - }, + .nav_val => |this_nav| switch (dependee) { + .nav_val => |other_nav| if (this_nav == other_nav) return, else => {}, }, - .func => {}, + else => {}, } try zcu.intern_pool.addDependency(sema.gpa, sema.owner, dependee); @@ -38659,38 +38634,6 @@ pub fn flushExports(sema: *Sema) !void { } } -/// Given that this `Sema` is owned by the `Cau` of a `declaration`, fetches -/// the corresponding `Nav`. -fn getOwnerCauNav(sema: *Sema) InternPool.Nav.Index { - const cau = sema.owner.unwrap().cau; - return sema.pt.zcu.intern_pool.getCau(cau).owner.unwrap().nav; -} - -/// Given that this `Sema` is owned by the `Cau` of a `declaration`, fetches -/// the `TrackedInst` corresponding to this `declaration` instruction. -fn getOwnerCauDeclInst(sema: *Sema) InternPool.TrackedInst.Index { - const ip = &sema.pt.zcu.intern_pool; - const cau = ip.getCau(sema.owner.unwrap().cau); - assert(cau.owner.unwrap() == .nav); - return cau.zir_index; -} - -/// Given that this `Sema` is owned by a runtime function, fetches the -/// `TrackedInst` corresponding to its `declaration` instruction. -fn getOwnerFuncDeclInst(sema: *Sema) InternPool.TrackedInst.Index { - const zcu = sema.pt.zcu; - const ip = &zcu.intern_pool; - const func = sema.owner.unwrap().func; - const func_info = zcu.funcInfo(func); - const cau = if (func_info.generic_owner == .none) cau: { - break :cau ip.getNav(func_info.owner_nav).analysis_owner.unwrap().?; - } else cau: { - const generic_owner = zcu.funcInfo(func_info.generic_owner); - break :cau ip.getNav(generic_owner.owner_nav).analysis_owner.unwrap().?; - }; - return ip.getCau(cau).zir_index; -} - /// Called as soon as a `declared` enum type is created. /// Resolves the tag type and field inits. /// Marks the `src_inst` dependency on the enum's declaration, so call sites need not do this. @@ -38701,7 +38644,6 @@ pub fn resolveDeclaredEnum( tracked_inst: InternPool.TrackedInst.Index, namespace: InternPool.NamespaceIndex, type_name: InternPool.NullTerminatedString, - enum_cau: InternPool.Cau.Index, small: Zir.Inst.EnumDecl.Small, body: []const Zir.Inst.Index, tag_type_ref: Zir.Inst.Ref, @@ -38719,7 +38661,7 @@ pub fn resolveDeclaredEnum( const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) }; const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } }; - const anal_unit = AnalUnit.wrap(.{ .cau = enum_cau }); + const anal_unit = AnalUnit.wrap(.{ .type = wip_ty.index }); var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); @@ -38943,6 +38885,6 @@ fn getBuiltin(sema: *Sema, name: []const u8) SemaError!Air.Inst.Ref { const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = try pt.getBuiltinNav(name); - try pt.ensureCauAnalyzed(ip.getNav(nav).analysis_owner.unwrap().?); + try pt.ensureNavValUpToDate(nav); return Air.internedToRef(ip.getNav(nav).status.resolved.val); } diff --git a/src/Type.zig b/src/Type.zig index 850e50c82ed1..b47a45d1fc30 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -3851,7 +3851,7 @@ fn resolveStructInner( const gpa = zcu.gpa; const struct_obj = zcu.typeToStruct(ty).?; - const owner = InternPool.AnalUnit.wrap(.{ .cau = struct_obj.cau }); + const owner: InternPool.AnalUnit = .wrap(.{ .type = ty.toIntern() }); if (zcu.failed_analysis.contains(owner) or zcu.transitive_failed_analysis.contains(owner)) { return error.AnalysisFail; @@ -3905,7 +3905,7 @@ fn resolveUnionInner( const gpa = zcu.gpa; const union_obj = zcu.typeToUnion(ty).?; - const owner = InternPool.AnalUnit.wrap(.{ .cau = union_obj.cau }); + const owner: InternPool.AnalUnit = .wrap(.{ .type = ty.toIntern() }); if (zcu.failed_analysis.contains(owner) or zcu.transitive_failed_analysis.contains(owner)) { return error.AnalysisFail; diff --git a/src/Zcu.zig b/src/Zcu.zig index bb6a25802e28..d374a0aa0c09 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -192,7 +192,7 @@ compile_log_text: std.ArrayListUnmanaged(u8) = .empty, test_functions: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty, -global_assembly: std.AutoArrayHashMapUnmanaged(InternPool.Cau.Index, []u8) = .empty, +global_assembly: std.AutoArrayHashMapUnmanaged(AnalUnit, []u8) = .empty, /// Key is the `AnalUnit` *performing* the reference. This representation allows /// incremental updates to quickly delete references caused by a specific `AnalUnit`. @@ -344,9 +344,12 @@ pub const Namespace = struct { pub_usingnamespace: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty, /// All `usingnamespace` declarations in this namespace which are *not* marked `pub`. priv_usingnamespace: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty, - /// All `comptime` and `test` declarations in this namespace. We store these purely so that - /// incremental compilation can re-use the existing `Cau`s when a namespace changes. - other_decls: std.ArrayListUnmanaged(InternPool.Cau.Index) = .empty, + /// All `comptime` declarations in this namespace. We store these purely so that incremental + /// compilation can re-use the existing `ComptimeUnit`s when a namespace changes. + comptime_decls: std.ArrayListUnmanaged(InternPool.ComptimeUnit.Id) = .empty, + /// All `test` declarations in this namespace. We store these purely so that incremental + /// compilation can re-use the existing `Nav`s when a namespace changes. + test_decls: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty, pub const Index = InternPool.NamespaceIndex; pub const OptionalIndex = InternPool.OptionalNamespaceIndex; @@ -2436,11 +2439,9 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { // If this is a Decl, we must recursively mark dependencies on its tyval // as no longer PO. switch (depender.unwrap()) { - .cau => |cau| switch (zcu.intern_pool.getCau(cau).owner.unwrap()) { - .nav => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }), - .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }), - .none => {}, - }, + .@"comptime" => {}, + .nav_val => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }), + .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }), .func => |func| try zcu.markPoDependeeUpToDate(.{ .interned = func }), } } @@ -2451,11 +2452,9 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) !void { const ip = &zcu.intern_pool; const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) { - .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { - .nav => |nav| .{ .nav_val = nav }, // TODO: also `nav_ref` deps when introduced - .type => |ty| .{ .interned = ty }, - .none => return, // analysis of this `Cau` can't outdate any dependencies - }, + .@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies + .nav_val => |nav| .{ .nav_val = nav }, // TODO: also `nav_ref` deps when introduced + .type => |ty| .{ .interned = ty }, .func => |func_index| .{ .interned = func_index }, // IES }; log.debug("potentially outdated dependee: {}", .{zcu.fmtDependee(dependee)}); @@ -2512,14 +2511,14 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { } // There is no single AnalUnit which is ready for re-analysis. Instead, we must assume that some - // Cau with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of - // A or B. We should select a Cau, since a Cau is definitely responsible for the loop in the - // dependency graph (since IES dependencies can't have loops). We should also, of course, not - // select a Cau owned by a `comptime` declaration, since you can't depend on those! + // AnalUnit with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of + // A or B. We should definitely not select a function, since a function can't be responsible for the + // loop (IES dependencies can't have loops). We should also, of course, not select a `comptime` + // declaration, since you can't depend on those! - // The choice of this Cau could have a big impact on how much total analysis we perform, since + // The choice of this unit could have a big impact on how much total analysis we perform, since // if analysis concludes any dependencies on its result are up-to-date, then other PO AnalUnit - // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a Decl + // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a unit // which the most things depend on - the idea is that this will resolve a lot of loops (but this // is only a heuristic). @@ -2530,33 +2529,28 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { const ip = &zcu.intern_pool; - var chosen_cau: ?InternPool.Cau.Index = null; - var chosen_cau_dependers: u32 = undefined; + var chosen_unit: ?AnalUnit = null; + var chosen_unit_dependers: u32 = undefined; inline for (.{ zcu.outdated.keys(), zcu.potentially_outdated.keys() }) |outdated_units| { for (outdated_units) |unit| { - const cau = switch (unit.unwrap()) { - .cau => |cau| cau, - .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice - }; - const cau_owner = ip.getCau(cau).owner; - var n: u32 = 0; - var it = ip.dependencyIterator(switch (cau_owner.unwrap()) { - .none => continue, // there can be no dependencies on this `Cau` so it is a terrible choice + var it = ip.dependencyIterator(switch (unit.unwrap()) { + .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice + .@"comptime" => continue, // a `comptime` block can't even be depended on so it is a terrible choice .type => |ty| .{ .interned = ty }, - .nav => |nav| .{ .nav_val = nav }, + .nav_val => |nav| .{ .nav_val = nav }, }); while (it.next()) |_| n += 1; - if (chosen_cau == null or n > chosen_cau_dependers) { - chosen_cau = cau; - chosen_cau_dependers = n; + if (chosen_unit == null or n > chosen_unit_dependers) { + chosen_unit = unit; + chosen_unit_dependers = n; } } } - if (chosen_cau == null) { + if (chosen_unit == null) { for (zcu.outdated.keys(), zcu.outdated.values()) |o, opod| { const func = o.unwrap().func; const nav = zcu.funcInfo(func).owner_nav; @@ -2570,11 +2564,11 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { } log.debug("findOutdatedToAnalyze: heuristic returned '{}' ({d} dependers)", .{ - zcu.fmtAnalUnit(AnalUnit.wrap(.{ .cau = chosen_cau.? })), - chosen_cau_dependers, + zcu.fmtAnalUnit(chosen_unit.?), + chosen_unit_dependers, }); - return AnalUnit.wrap(.{ .cau = chosen_cau.? }); + return chosen_unit.?; } /// During an incremental update, before semantic analysis, call this to flush all values from @@ -3019,9 +3013,9 @@ pub fn handleUpdateExports( }; } -pub fn addGlobalAssembly(zcu: *Zcu, cau: InternPool.Cau.Index, source: []const u8) !void { +pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void { const gpa = zcu.gpa; - const gop = try zcu.global_assembly.getOrPut(gpa, cau); + const gop = try zcu.global_assembly.getOrPut(gpa, unit); if (gop.found_existing) { const new_value = try std.fmt.allocPrint(gpa, "{s}\n{s}", .{ gop.value_ptr.*, source }); gpa.free(gop.value_ptr.*); @@ -3304,23 +3298,22 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv log.debug("handle type '{}'", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}); - // If this type has a `Cau` for resolution, it's automatically referenced. - const resolution_cau: InternPool.Cau.Index.Optional = switch (ip.indexToKey(ty)) { - .struct_type => ip.loadStructType(ty).cau.toOptional(), - .union_type => ip.loadUnionType(ty).cau.toOptional(), - .enum_type => ip.loadEnumType(ty).cau, - .opaque_type => .none, + // If this type undergoes type resolution, the corresponding `AnalUnit` is automatically referenced. + const has_resolution: bool = switch (ip.indexToKey(ty)) { + .struct_type, .union_type => true, + .enum_type => |k| k != .generated_tag, + .opaque_type => false, else => unreachable, }; - if (resolution_cau.unwrap()) |cau| { + if (has_resolution) { // this should only be referenced by the type - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .type = ty }); assert(!result.contains(unit)); try unit_queue.putNoClobber(gpa, unit, referencer); } // If this is a union with a generated tag, its tag type is automatically referenced. - // We don't add this reference for non-generated tags, as those will already be referenced via the union's `Cau`, with a better source location. + // We don't add this reference for non-generated tags, as those will already be referenced via the union's type resolution, with a better source location. if (zcu.typeToUnion(Type.fromInterned(ty))) |union_obj| { const tag_ty = union_obj.enum_tag_ty; if (tag_ty != .none) { @@ -3335,24 +3328,35 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv // Queue any decls within this type which would be automatically analyzed. // Keep in sync with analysis queueing logic in `Zcu.PerThread.ScanDeclIter.scanDecl`. const ns = Type.fromInterned(ty).getNamespace(zcu).unwrap().?; - for (zcu.namespacePtr(ns).other_decls.items) |cau| { - // These are `comptime` and `test` declarations. - // `comptime` decls are always analyzed; `test` declarations are analyzed depending on the test filter. - const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + for (zcu.namespacePtr(ns).comptime_decls.items) |cu| { + // `comptime` decls are always analyzed. + const unit: AnalUnit = .wrap(.{ .@"comptime" = cu }); + if (!result.contains(unit)) { + log.debug("type '{}': ref comptime %{}", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + @intFromEnum(ip.getComptimeUnit(cu).zir_index.resolve(ip) orelse continue), + }); + try unit_queue.put(gpa, unit, referencer); + } + } + for (zcu.namespacePtr(ns).test_decls.items) |nav_id| { + const nav = ip.getNav(nav_id); + // `test` declarations are analyzed depending on the test filter. + const inst_info = nav.analysis.?.zir_index.resolveFull(ip) orelse continue; const file = zcu.fileByIndex(inst_info.file); // If the file failed AstGen, the TrackedInst refers to the old ZIR. const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; const decl = zir.getDeclaration(inst_info.inst); + + if (!comp.config.is_test or file.mod != zcu.main_mod) continue; + const want_analysis = switch (decl.kind) { .@"usingnamespace" => unreachable, .@"const", .@"var" => unreachable, - .@"comptime" => true, - .unnamed_test => comp.config.is_test and file.mod == zcu.main_mod, + .@"comptime" => unreachable, + .unnamed_test => true, .@"test", .decltest => a: { - if (!comp.config.is_test) break :a false; - if (file.mod != zcu.main_mod) break :a false; - const nav = ip.getCau(cau).owner.unwrap().nav; - const fqn_slice = ip.getNav(nav).fqn.toSlice(ip); + const fqn_slice = nav.fqn.toSlice(ip); for (comp.test_filters) |test_filter| { if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; } else break :a false; @@ -3360,28 +3364,25 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv }, }; if (want_analysis) { - const unit = AnalUnit.wrap(.{ .cau = cau }); - if (!result.contains(unit)) { - log.debug("type '{}': ref cau %{}", .{ - Type.fromInterned(ty).containerTypeName(ip).fmt(ip), - @intFromEnum(inst_info.inst), - }); - try unit_queue.put(gpa, unit, referencer); - } + log.debug("type '{}': ref test %{}", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + @intFromEnum(inst_info.inst), + }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + try unit_queue.put(gpa, unit, referencer); } } for (zcu.namespacePtr(ns).pub_decls.keys()) |nav| { // These are named declarations. They are analyzed only if marked `export`. - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue; const file = zcu.fileByIndex(inst_info.file); // If the file failed AstGen, the TrackedInst refers to the old ZIR. const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; const decl = zir.getDeclaration(inst_info.inst); if (decl.linkage == .@"export") { - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) { - log.debug("type '{}': ref cau %{}", .{ + log.debug("type '{}': ref named %{}", .{ Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(inst_info.inst), }); @@ -3391,16 +3392,15 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv } for (zcu.namespacePtr(ns).priv_decls.keys()) |nav| { // These are named declarations. They are analyzed only if marked `export`. - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue; const file = zcu.fileByIndex(inst_info.file); // If the file failed AstGen, the TrackedInst refers to the old ZIR. const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; const decl = zir.getDeclaration(inst_info.inst); if (decl.linkage == .@"export") { - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) { - log.debug("type '{}': ref cau %{}", .{ + log.debug("type '{}': ref named %{}", .{ Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(inst_info.inst), }); @@ -3411,13 +3411,11 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv // Incremental compilation does not support `usingnamespace`. // These are only included to keep good reference traces in non-incremental updates. for (zcu.namespacePtr(ns).pub_usingnamespace.items) |nav| { - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer); } for (zcu.namespacePtr(ns).priv_usingnamespace.items) |nav| { - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer); } continue; @@ -3527,12 +3525,6 @@ pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File { return zcu.fileByIndex(zcu.navFileScopeIndex(nav)); } -pub fn cauFileScope(zcu: *Zcu, cau: InternPool.Cau.Index) *File { - const ip = &zcu.intern_pool; - const file_index = ip.getCau(cau).zir_index.resolveFile(ip); - return zcu.fileByIndex(file_index); -} - pub fn fmtAnalUnit(zcu: *Zcu, unit: AnalUnit) std.fmt.Formatter(formatAnalUnit) { return .{ .data = .{ .unit = unit, .zcu = zcu } }; } @@ -3545,19 +3537,17 @@ fn formatAnalUnit(data: struct { unit: AnalUnit, zcu: *Zcu }, comptime fmt: []co const zcu = data.zcu; const ip = &zcu.intern_pool; switch (data.unit.unwrap()) { - .cau => |cau_index| { - const cau = ip.getCau(cau_index); - switch (cau.owner.unwrap()) { - .nav => |nav| return writer.print("cau(decl='{}')", .{ip.getNav(nav).fqn.fmt(ip)}), - .type => |ty| return writer.print("cau(ty='{}')", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}), - .none => if (cau.zir_index.resolveFull(ip)) |resolved| { - const file_path = zcu.fileByIndex(resolved.file).sub_file_path; - return writer.print("cau(inst=('{s}', %{}))", .{ file_path, @intFromEnum(resolved.inst) }); - } else { - return writer.writeAll("cau(inst=)"); - }, + .@"comptime" => |cu_id| { + const cu = ip.getComptimeUnit(cu_id); + if (cu.zir_index.resolveFull(ip)) |resolved| { + const file_path = zcu.fileByIndex(resolved.file).sub_file_path; + return writer.print("comptime(inst=('{s}', %{}))", .{ file_path, @intFromEnum(resolved.inst) }); + } else { + return writer.writeAll("comptime(inst=)"); } }, + .nav_val => |nav| return writer.print("nav_val('{}')", .{ip.getNav(nav).fqn.fmt(ip)}), + .type => |ty| return writer.print("ty('{}')", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}), .func => |func| { const nav = zcu.funcInfo(func).owner_nav; return writer.print("func('{}')", .{ip.getNav(nav).fqn.fmt(ip)}); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 2cf6e3670c49..0d7ca0eb260a 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -545,144 +545,173 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const file_root_type = pt.zcu.fileRootType(file_index); if (file_root_type != .none) { - _ = try pt.ensureTypeUpToDate(file_root_type, false); + _ = try pt.ensureTypeUpToDate(file_root_type); } else { return pt.semaFile(file_index); } } -/// This ensures that the state of the `Cau`, and of its corresponding `Nav` or type, -/// is fully up-to-date. Note that the type of the `Nav` may not be fully resolved. -/// Returns `error.AnalysisFail` if the `Cau` has an error. -pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu.SemaError!void { +/// Ensures that the state of the given `ComptimeUnit` 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. +pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.SemaError!void { const tracy = trace(@src()); defer tracy.end(); const zcu = pt.zcu; const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); - const cau = ip.getCau(cau_index); + const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id }); - log.debug("ensureCauAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureComptimeUnitUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); assert(!zcu.analysis_in_progress.contains(anal_unit)); - // Determine whether or not this Cau is outdated, i.e. requires re-analysis - // even if `complete`. If a Cau is PO, we pessismistically assume that it - // *does* require re-analysis, to ensure that the Cau is definitely - // up-to-date when this function returns. - - // If analysis occurs in a poor order, this could result in over-analysis. - // We do our best to avoid this by the other dependency logic in this file - // which tries to limit re-analysis to Caus whose previously listed - // dependencies are all up-to-date. + // Determine whether or not this `ComptimeUnit` is outdated. For this kind of `AnalUnit`, that's + // the only indicator as to whether or not analysis is required; when a `ComptimeUnit` is first + // created, it's marked as outdated. + // + // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to + // ensure that the unit is definitely up-to-date when this function returns. This mechanism could + // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by + // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`. - const cau_outdated = zcu.outdated.swapRemove(anal_unit) or + const was_outdated = zcu.outdated.swapRemove(anal_unit) or zcu.potentially_outdated.swapRemove(anal_unit); - const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit); - - if (cau_outdated) { + if (was_outdated) { _ = zcu.outdated_ready.swapRemove(anal_unit); - } else { - // We can trust the current information about this `Cau`. - if (prev_failed) { - return error.AnalysisFail; - } - // If it wasn't failed and wasn't marked outdated, then either... - // * it is a type and is up-to-date, or - // * it is a `comptime` decl and is up-to-date, or - // * it is another decl and is EITHER up-to-date OR never-referenced (so unresolved) - // We just need to check for that last case. - switch (cau.owner.unwrap()) { - .type, .none => return, - .nav => |nav| if (ip.getNav(nav).status == .resolved) return, + // `was_outdated` can be true in the initial update for comptime units, so this isn't a `dev.check`. + if (dev.env.supports(.incremental)) { + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); } + } 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; } - const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result| - // This `Cau` has gone from failed to success, so even if the value of the owner `Nav` didn't actually - // change, we need to invalidate the dependencies anyway. - .{ .{ - .invalidate_decl_val = result.invalidate_decl_val or prev_failed, - .invalidate_decl_ref = result.invalidate_decl_ref or prev_failed, - }, false } - else |err| switch (err) { - error.AnalysisFail => res: { + const unit_prog_node = zcu.sema_prog_node.start("comptime", 0); + defer unit_prog_node.end(); + + return pt.analyzeComptimeUnit(cu_id) catch |err| switch (err) { + error.AnalysisFail => { if (!zcu.failed_analysis.contains(anal_unit)) { - // If this `Cau` caused the error, it would have an entry in `failed_analysis`. + // 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 {}", .{zcu.fmtAnalUnit(anal_unit)}); } - // We consider this `Cau` to be outdated if: - // * Previous analysis succeeded; in this case, we need to re-analyze dependants to ensure - // they hit a transitive error here, rather than reporting a different error later (which - // may now be invalid). - // * The `Cau` is a type; in this case, the declaration site may require re-analysis to - // construct a valid type. - const outdated = !prev_failed or cau.owner.unwrap() == .type; - break :res .{ .{ - .invalidate_decl_val = outdated, - .invalidate_decl_ref = outdated, - }, true }; + return error.AnalysisFail; }, - error.OutOfMemory => res: { - try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - try zcu.retryable_failures.ensureUnusedCapacity(gpa, 1); - const msg = try Zcu.ErrorMsg.create( - gpa, - .{ .base_node_inst = cau.zir_index, .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0) }, - "unable to analyze: OutOfMemory", - .{}, - ); - zcu.retryable_failures.appendAssumeCapacity(anal_unit); - zcu.failed_analysis.putAssumeCapacityNoClobber(anal_unit, msg); - break :res .{ .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }, true }; + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, }; - - if (cau_outdated) { - // TODO: we do not yet have separate dependencies for decl values vs types. - const invalidate = sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref; - const dependee: InternPool.Dependee = switch (cau.owner.unwrap()) { - .none => return, // there are no dependencies on a `comptime` decl! - .nav => |nav_index| .{ .nav_val = nav_index }, - .type => |ty| .{ .interned = ty }, - }; - - if (invalidate) { - // This dependency was marked as PO, meaning dependees were waiting - // on its analysis result, and it has turned out to be outdated. - // Update dependees accordingly. - try zcu.markDependeeOutdated(.marked_po, dependee); - } else { - // This dependency was previously PO, but turned out to be up-to-date. - // We do not need to queue successive analysis. - try zcu.markPoDependeeUpToDate(dependee); - } - } - - if (analysis_fail) return error.AnalysisFail; } -fn ensureCauAnalyzedInner( - pt: Zcu.PerThread, - cau_index: InternPool.Cau.Index, - cau_outdated: bool, -) Zcu.SemaError!SemaCauResult { +/// Re-analyzes a `ComptimeUnit`. The unit has already been determined to be out-of-date, and old +/// side effects (exports/references/etc) have been dropped. If semantic analysis fails, this +/// function will return `error.AnalysisFail`, and it is the caller's reponsibility to add an entry +/// to `transitive_failed_analysis` if necessary. +fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.CompileError!void { const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const cau = ip.getCau(cau_index); - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id }); + const comptime_unit = ip.getComptimeUnit(cu_id); + + log.debug("analyzeComptimeUnit {}", .{zcu.fmtAnalUnit(anal_unit)}); + + const inst_resolved = comptime_unit.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_resolved.file); + // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is + // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends + // in `ensureComptimeUnitUpToDate`. + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + 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.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = 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(); + + // The comptime unit declares on the source of the corresponding `comptime` declaration. + try sema.declareDependency(.{ .src_hash = comptime_unit.zir_index }); + + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = comptime_unit.namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = comptime_unit.zir_index, + .type_name_ctx = try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{ + Type.fromInterned(zcu.namespacePtr(comptime_unit.namespace).owner_type).containerTypeName(ip).fmt(ip), + }, .no_embedded_nulls), + }; + defer block.instructions.deinit(gpa); + + const zir_decl = zir.getDeclaration(inst_resolved.inst); + assert(zir_decl.kind == .@"comptime"); + assert(zir_decl.type_body == null); + assert(zir_decl.align_body == null); + assert(zir_decl.linksection_body == null); + assert(zir_decl.addrspace_body == null); + const value_body = zir_decl.value_body.?; + + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + assert(result_ref == .void_value); // AstGen should always uphold this + + // Nothing else to do -- for a comptime decl, all we care about are the side effects. + // Just make sure to `flushExports`. + try sema.flushExports(); +} - const inst_info = cau.zir_index.resolveFull(ip) orelse 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. +pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void { + const tracy = trace(@src()); + defer tracy.end(); // TODO: document this elsewhere mlugg! // For my own benefit, here's how a namespace update for a normal (non-file-root) type works: @@ -692,821 +721,826 @@ fn ensureCauAnalyzedInner( // * Any change to the `struct` body -- including changing a declaration -- invalidates this // * `S` is re-analyzed, but notes: // * there is an existing struct instance (at this `TrackedInst` with these captures) - // * the struct's `Cau` is up-to-date (because nothing about the fields changed) + // * the struct's resolution is up-to-date (because nothing about the fields changed) // * so, it uses the same `struct` // * but this doesn't stop it from updating the namespace! // * we basically do `scanDecls`, updating the namespace as needed // * so everyone lived happily ever after - if (zcu.fileByIndex(inst_info.file).status != .success_zir) { - return error.AnalysisFail; - } - - // `cau_outdated` can be true in the initial update for `comptime` declarations, - // so this isn't a `dev.check`. - if (cau_outdated and dev.env.supports(.incremental)) { - // The exports this `Cau` performs will be re-discovered, so we remove them here - // prior to re-analysis. - zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(zcu.gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - } - - const decl_prog_node = zcu.sema_prog_node.start(switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).fqn.toSlice(ip), - .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), - .none => "comptime", - }, 0); - defer decl_prog_node.end(); - - return pt.semaCau(cau_index) catch |err| switch (err) { - error.GenericPoison, error.ComptimeBreak, error.ComptimeReturn => unreachable, - error.AnalysisFail, error.OutOfMemory => |e| return e, - }; -} - -pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { - dev.check(.sema); - - const tracy = trace(@src()); - defer tracy.end(); - const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - // We only care about the uncoerced function. - const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index); - const anal_unit = AnalUnit.wrap(.{ .func = func_index }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + const nav = ip.getNav(nav_id); - log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureNavUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); - const func = zcu.funcInfo(maybe_coerced_func_index); + // Determine whether or not this `Nav`'s value is outdated. This also includes checking if the + // status is `.unresolved`, which indicates that the value is outdated because it has *never* + // been analyzed so far. + // + // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to + // ensure that the unit is definitely up-to-date when this function returns. This mechanism could + // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by + // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`. - const func_outdated = zcu.outdated.swapRemove(anal_unit) or + const was_outdated = zcu.outdated.swapRemove(anal_unit) or zcu.potentially_outdated.swapRemove(anal_unit); - const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit); + const prev_failed = zcu.failed_analysis.contains(anal_unit) or + zcu.transitive_failed_analysis.contains(anal_unit); - if (func_outdated) { + if (was_outdated) { + dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(anal_unit); - } else { - // We can trust the current information about this function. - if (prev_failed) { - return error.AnalysisFail; - } - switch (func.analysisUnordered(ip).state) { - .unreferenced => {}, // this is the first reference - .queued => {}, // we're waiting on first-time analysis - .analyzed => return, // up-to-date + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + } else { + // We can trust the current information about this unit. + if (prev_failed) return error.AnalysisFail; + if (nav.status == .resolved) return; } - const ies_outdated, const analysis_fail = if (pt.ensureFuncBodyAnalyzedInner(func_index, func_outdated)) |result| - .{ result.ies_outdated, false } - else |err| switch (err) { + const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); + defer unit_prog_node.end(); + + const sema_result: SemaNavResult, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: { + break :res .{ + .{ + // If the unit has gone from failed to success, we still need to invalidate the dependencies. + .invalidate_nav_val = result.invalidate_nav_val or prev_failed, + .invalidate_nav_ref = result.invalidate_nav_ref or prev_failed, + }, + false, + }; + } else |err| switch (err) { error.AnalysisFail => res: { if (!zcu.failed_analysis.contains(anal_unit)) { - // If this function caused the error, it would have an entry in `failed_analysis`. + // 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 {}", .{zcu.fmtAnalUnit(anal_unit)}); } - // We consider the IES to be outdated if the function previously succeeded analysis; in this case, - // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting - // a different error later (which may now be invalid). - break :res .{ !prev_failed, true }; + break :res .{ .{ + .invalidate_nav_val = !prev_failed, + .invalidate_nav_ref = !prev_failed, + }, true }; }, - error.OutOfMemory => return error.OutOfMemory, // TODO: graceful handling like `ensureCauAnalyzed` + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; + }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, }; - if (func_outdated) { - if (ies_outdated) { - try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); + if (was_outdated) { + // TODO: we do not yet have separate dependencies for Nav values vs types. + const invalidate = sema_result.invalidate_nav_val or sema_result.invalidate_nav_ref; + const dependee: InternPool.Dependee = .{ .nav_val = nav_id }; + if (invalidate) { + // This dependency was marked as PO, meaning dependees were waiting + // on its analysis result, and it has turned out to be outdated. + // Update dependees accordingly. + try zcu.markDependeeOutdated(.marked_po, dependee); } else { - try zcu.markPoDependeeUpToDate(.{ .interned = func_index }); + // This dependency was previously PO, but turned out to be up-to-date. + // We do not need to queue successive analysis. + try zcu.markPoDependeeUpToDate(dependee); } } - if (analysis_fail) return error.AnalysisFail; + if (new_failed) return error.AnalysisFail; } -fn ensureFuncBodyAnalyzedInner( - pt: Zcu.PerThread, - func_index: InternPool.Index, - func_outdated: bool, -) Zcu.SemaError!struct { ies_outdated: bool } { +const SemaNavResult = packed struct { + /// Whether the value of a `decl_val` of the corresponding Nav changed. + invalidate_nav_val: bool, + /// Whether the type of a `decl_ref` of the corresponding Nav changed. + invalidate_nav_ref: bool, +}; + +fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!SemaNavResult { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const func = zcu.funcInfo(func_index); - const anal_unit = AnalUnit.wrap(.{ .func = func_index }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + const old_nav = ip.getNav(nav_id); - // Make sure that this function is still owned by the same `Nav`. Otherwise, analyzing - // it would be a waste of time in the best case, and could cause codegen to give bogus - // results in the worst case. + log.debug("analyzeNavVal {}", .{zcu.fmtAnalUnit(anal_unit)}); - if (func.generic_owner == .none) { - // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?); - if (ip.getNav(func.owner_nav).status.resolved.val != func_index) { - // This function is no longer referenced! There's no point in re-analyzing it. - // Just mark a transitive failure and move on. - return error.AnalysisFail; - } - } else { - const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; - // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?); - if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) { - // The generic owner is no longer referenced, so this function is also unreferenced. - // There's no point in re-analyzing it. Just mark a transitive failure and move on. - return error.AnalysisFail; - } - } + const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_resolved.file); + // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is + // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends + // in `ensureComptimeUnitUpToDate`. + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; - // We'll want to remember what the IES used to be before the update for - // dependency invalidation purposes. - const old_resolved_ies = if (func.analysisUnordered(ip).inferred_error_set) - func.resolvedErrorSetUnordered(ip) - else - .none; + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); - if (func_outdated) { - dev.check(.incremental); - zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - } + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); - if (!func_outdated) { - // We can trust the current information about this function. - if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { - return error.AnalysisFail; - } - switch (func.analysisUnordered(ip).state) { - .unreferenced => {}, // this is the first reference - .queued => {}, // we're waiting on first-time analysis - .analyzed => return .{ .ies_outdated = false }, // up-to-date - } - } + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); - log.debug("analyze and generate fn body {}; reason='{s}'", .{ - zcu.fmtAnalUnit(anal_unit), - if (func_outdated) "outdated" else "never analyzed", - }); + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); - var air = try pt.analyzeFnBody(func_index); - errdefer air.deinit(gpa); + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = 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 ies_outdated = func_outdated and - (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies); + // The comptime unit declares on the source of the corresponding declaration. + try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index }); - const comp = zcu.comp; + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = old_nav.analysis.?.namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = old_nav.analysis.?.zir_index, + .type_name_ctx = old_nav.fqn, + }; + defer block.instructions.deinit(gpa); - const dump_air = build_options.enable_debug_extensions and comp.verbose_air; - const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null); + const zir_decl = zir.getDeclaration(inst_resolved.inst); - if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) { - air.deinit(gpa); - return .{ .ies_outdated = ies_outdated }; - } + assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace")); - // This job depends on any resolve_type_fully jobs queued up before it. - try comp.queueJob(.{ .codegen_func = .{ - .func = func_index, - .air = air, - } }); + const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); + const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); + const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); + const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); + const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); - return .{ .ies_outdated = ies_outdated }; -} + // First, we must resolve the declaration's type. To do this, we analyze the type body if available, + // or otherwise, we analyze the value body, populating `early_val` in the process. -/// Takes ownership of `air`, even on error. -/// If any types referenced by `air` are unresolved, marks the codegen as failed. -pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Air) Allocator.Error!void { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const comp = zcu.comp; + const nav_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { + // We evaluate only the type now; no need for the value yet. + const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_resolved.inst); + const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src); + break :ty .{ .fromInterned(type_ref.toInterned().?), null }; + } else ty: { + // We don't have a type body, so we need to evaluate the value immediately. + const value_body = zir_decl.value_body.?; + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); + break :ty .{ val.typeOf(zcu), val }; + }; - defer { - var air_mut = air; - air_mut.deinit(gpa); + switch (zir_decl.kind) { + .@"comptime" => unreachable, // this is not a Nav + .unnamed_test, .@"test", .decltest => assert(nav_ty.zigTypeTag(zcu) == .@"fn"), + .@"usingnamespace" => {}, + .@"const" => {}, + .@"var" => try sema.validateVarType( + &block, + if (zir_decl.type_body != null) ty_src else init_src, + nav_ty, + zir_decl.linkage == .@"extern", + ), } - const func = zcu.funcInfo(func_index); - const nav_index = func.owner_nav; - const nav = ip.getNav(nav_index); - - var liveness = try Liveness.analyze(gpa, air, ip); - defer liveness.deinit(gpa); + // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine + // the full pointer type of this declaration. - if (build_options.enable_debug_extensions and comp.verbose_air) { - std.debug.print("# Begin Function AIR: {}:\n", .{nav.fqn.fmt(ip)}); - @import("../print_air.zig").dump(pt, air, liveness); - std.debug.print("# End Function AIR: {}\n\n", .{nav.fqn.fmt(ip)}); - } + const alignment: InternPool.Alignment = a: { + const align_body = zir_decl.align_body orelse break :a .none; + const align_ref = try sema.resolveInlineBody(&block, align_body, inst_resolved.inst); + break :a try sema.analyzeAsAlign(&block, align_src, align_ref); + }; - if (std.debug.runtime_safety) { - var verify: Liveness.Verify = .{ - .gpa = gpa, - .air = air, - .liveness = liveness, - .intern_pool = ip, - }; - defer verify.deinit(); + const @"linksection": InternPool.OptionalNullTerminatedString = ls: { + const linksection_body = zir_decl.linksection_body orelse break :ls .none; + const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_resolved.inst); + const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{ + .needed_comptime_reason = "linksection must be comptime-known", + }); + if (std.mem.indexOfScalar(u8, bytes, 0) != null) { + return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{}); + } else if (bytes.len == 0) { + return sema.fail(&block, section_src, "linksection cannot be empty", .{}); + } + break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls); + }; - verify.verify() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( - gpa, - zcu.navSrcLoc(nav_index), - "invalid liveness: {s}", - .{@errorName(err)}, - )); - return; + const @"addrspace": std.builtin.AddressSpace = as: { + const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) { + .@"var" => .variable, + else => switch (nav_ty.zigTypeTag(zcu)) { + .@"fn" => .function, + else => .constant, }, }; + const target = zcu.getTarget(); + const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) { + .function => target_util.defaultAddressSpace(target, .function), + .variable => target_util.defaultAddressSpace(target, .global_mutable), + .constant => target_util.defaultAddressSpace(target, .global_constant), + else => unreachable, + }; + const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_resolved.inst); + break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); + }; + + // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations + // don't have an associated value body. + + const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { + // Put the resolved type into `inst_map` to be used as the result type of the init. + try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_resolved.inst}); + sema.inst_map.putAssumeCapacity(inst_resolved.inst, Air.internedToRef(nav_ty.toIntern())); + const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + assert(sema.inst_map.remove(inst_resolved.inst)); + + const result_ref = try sema.coerce(&block, nav_ty, uncoerced_result_ref, init_src); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } else null; + + const nav_val: Value = switch (zir_decl.linkage) { + .normal, .@"export" => switch (zir_decl.kind) { + .@"var" => .fromInterned(try pt.intern(.{ .variable = .{ + .ty = nav_ty.toIntern(), + .init = final_val.?.toIntern(), + .owner_nav = nav_id, + .is_threadlocal = zir_decl.is_threadlocal, + .is_weak_linkage = false, + } })), + else => final_val.?, + }, + .@"extern" => val: { + assert(final_val == null); // extern decls do not have a value body + const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: { + break :l zir.nullTerminatedString(zir_decl.lib_name); + } else null; + if (lib_name) |l| { + const lib_name_src = block.src(.{ .node_offset_lib_name = 0 }); + try sema.handleExternLibName(&block, lib_name_src, l); + } + break :val .fromInterned(try pt.getExtern(.{ + .name = old_nav.name, + .ty = nav_ty.toIntern(), + .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls), + .is_const = zir_decl.kind == .@"const", + .is_threadlocal = zir_decl.is_threadlocal, + .is_weak_linkage = false, + .is_dll_import = false, + .alignment = alignment, + .@"addrspace" = @"addrspace", + .zir_index = old_nav.analysis.?.zir_index, // `declaration` instruction + .owner_nav = undefined, // ignored by `getExtern` + })); + }, + }; + + switch (nav_val.toIntern()) { + .generic_poison => unreachable, // assertion failure + .unreachable_value => unreachable, // assertion failure + else => {}, } - const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0); - defer codegen_prog_node.end(); + // This resolves the type of the resolved value, not that value itself. If `nav_val` is a struct type, + // this resolves the type `type` (which needs no resolution), not the struct itself. + try nav_ty.resolveLayout(pt); - if (!air.typesFullyResolved(zcu)) { - // A type we depend on failed to resolve. This is a transitive failure. - // Correcting this failure will involve changing a type this function - // depends on, hence triggering re-analysis of this function, so this - // interacts correctly with incremental compilation. - // TODO: do we need to mark this failure anywhere? I don't think so, since compilation - // will fail due to the type error anyway. - } else if (comp.bin_file) |lf| { - lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - assert(zcu.failed_codegen.contains(nav_index)); - }, - else => { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( - gpa, - zcu.navSrcLoc(nav_index), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); - }, - }; - } else if (zcu.llvm_object) |llvm_object| { - llvm_object.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, + // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`. + if (zir_decl.kind == .@"usingnamespace") { + if (nav_ty.toIntern() != .type_type) { + return sema.fail(&block, ty_src, "expected type, found {}", .{nav_ty.fmt(pt)}); + } + if (nav_val.toType().getNamespace(zcu) == .none) { + return sema.fail(&block, ty_src, "type {} has no namespace", .{nav_val.toType().fmt(pt)}); + } + ip.resolveNavValue(nav_id, .{ + .val = nav_val.toIntern(), + .alignment = .none, + .@"linksection" = .none, + .@"addrspace" = .generic, + }); + // TODO: usingnamespace cannot participate in incremental compilation + assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + return .{ + .invalidate_nav_val = true, + .invalidate_nav_ref = true, }; } -} -/// https://github.com/ziglang/zig/issues/14307 -pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void { - dev.check(.sema); - const import_file_result = try pt.importPkg(pkg); - const root_type = pt.zcu.fileRootType(import_file_result.file_index); - if (root_type == .none) { - return pt.semaFile(import_file_result.file_index); - } -} + const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) { + .func => |f| .{ true, f.owner_nav == nav_id }, // note that this lets function aliases reach codegen + .variable => |v| .{ v.owner_nav == nav_id, false }, + .@"extern" => |e| .{ + false, + Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" and zir_decl.linkage == .@"extern", + }, + else => .{ true, false }, + }; -fn createFileRootStruct( - pt: Zcu.PerThread, - file_index: Zcu.File.Index, - namespace_index: Zcu.Namespace.Index, - replace_existing: bool, -) Allocator.Error!InternPool.Index { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const file = zcu.fileByIndex(file_index); - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - assert(extended.opcode == .struct_decl); - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - assert(!small.has_captures_len); - assert(!small.has_backing_int); - assert(small.layout == .auto); - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; - const fields_len = if (small.has_fields_len) blk: { - const fields_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - const decls = file.zir.bodySlice(extra_index, decls_len); - extra_index += decls_len; + if (is_owned_fn) { + // linksection etc are legal, except some targets do not support function alignment. + if (zir_decl.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) { + return sema.fail(&block, align_src, "target does not support function alignment", .{}); + } + } else if (try nav_ty.comptimeOnlySema(pt)) { + // alignment, linksection, addrspace annotations are not allowed for comptime-only types. + const reason: []const u8 = switch (ip.indexToKey(nav_val.toIntern())) { + .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* + else => "comptime-only type", + }; + if (zir_decl.align_body != null) { + return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); + } + if (zir_decl.linksection_body != null) { + return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); + } + if (zir_decl.addrspace_body != null) { + return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); + } + } - const tracked_inst = try ip.trackZir(gpa, pt.tid, .{ - .file = file_index, - .inst = .main_struct_inst, + ip.resolveNavValue(nav_id, .{ + .val = nav_val.toIntern(), + .alignment = alignment, + .@"linksection" = @"linksection", + .@"addrspace" = @"addrspace", }); - const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ - .layout = .auto, - .fields_len = fields_len, - .known_non_opv = small.known_non_opv, - .requires_comptime = if (small.known_comptime_only) .yes else .unknown, - .any_comptime_fields = small.any_comptime_fields, - .any_default_inits = small.any_default_inits, - .inits_resolved = false, - .any_aligned_fields = small.any_aligned_fields, - .key = .{ .declared = .{ - .zir_index = tracked_inst, - .captures = &.{}, - } }, - }, replace_existing)) { - .existing => unreachable, // we wouldn't be analysing the file root if this type existed - .wip => |wip| wip, - }; - errdefer wip_ty.cancel(ip, pt.tid); - - wip_ty.setName(ip, try file.internFullyQualifiedName(pt)); - ip.namespacePtr(namespace_index).owner_type = wip_ty.index; - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, namespace_index, wip_ty.index); - if (zcu.comp.incremental) { - try ip.addDependency( - gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), - .{ .src_hash = tracked_inst }, - ); - } + // Mark the unit as completed before evaluating the export! + assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - try pt.scanNamespace(namespace_index, decls); - try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - codegen_type: { - if (zcu.comp.config.use_llvm) break :codegen_type; - if (file.mod.strip) break :codegen_type; - // This job depends on any resolve_type_fully jobs queued up before it. - try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); + if (zir_decl.linkage == .@"export") { + const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) }); + const name_slice = zir.nullTerminatedString(zir_decl.name); + const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls); + try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_id); } - zcu.setFileRootType(file_index, wip_ty.index); - return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); -} -/// Re-scan the namespace of a file's root struct type on an incremental update. -/// The file must have successfully populated ZIR. -/// If the file's root struct type is not populated (the file is unreferenced), nothing is done. -/// This is called by `updateZirRefs` for all updated files before the main work loop. -/// This function does not perform any semantic analysis. -fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void { - const zcu = pt.zcu; + try sema.flushExports(); - const file = zcu.fileByIndex(file_index); - assert(file.status == .success_zir); - const file_root_type = zcu.fileRootType(file_index); - if (file_root_type == .none) return; + queue_codegen: { + if (!queue_linker_work) break :queue_codegen; - log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{ - file.mod.fully_qualified_name, - file.sub_file_path, - }); + if (!try nav_ty.hasRuntimeBitsSema(pt)) { + if (zcu.comp.config.use_llvm) break :queue_codegen; + if (file.mod.strip) break :queue_codegen; + } - const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu); - const decls = decls: { - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + // This job depends on any resolve_type_fully jobs queued up before it. + try zcu.comp.queueJob(.{ .codegen_nav = nav_id }); + } - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; - extra_index += @intFromBool(small.has_fields_len); - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - break :decls file.zir.bodySlice(extra_index, decls_len); - }; - try pt.scanNamespace(namespace_index, decls); - zcu.namespacePtr(namespace_index).generation = zcu.generation; + switch (old_nav.status) { + .unresolved => return .{ + .invalidate_nav_val = true, + .invalidate_nav_ref = true, + }, + .resolved => |old| { + const new = ip.getNav(nav_id).status.resolved; + return .{ + .invalidate_nav_val = new.val != old.val, + .invalidate_nav_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or + new.alignment != old.alignment or + new.@"linksection" != old.@"linksection" or + new.@"addrspace" != old.@"addrspace", + }; + }, + } } -fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { +pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { + dev.check(.sema); + const tracy = trace(@src()); defer tracy.end(); const zcu = pt.zcu; const gpa = zcu.gpa; - const file = zcu.fileByIndex(file_index); - assert(zcu.fileRootType(file_index) == .none); + const ip = &zcu.intern_pool; - if (file.status != .success_zir) { - return error.AnalysisFail; - } - assert(file.zir_loaded); + // We only care about the uncoerced function. + const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index); + const anal_unit: AnalUnit = .wrap(.{ .func = func_index }); - const new_namespace_index = try pt.createNamespace(.{ - .parent = .none, - .owner_type = undefined, // set in `createFileRootStruct` - .file_scope = file_index, - .generation = zcu.generation, - }); - const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false); - errdefer zcu.intern_pool.remove(pt.tid, struct_ty); + log.debug("ensureFuncBodyUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); - switch (zcu.comp.cache_use) { - .whole => |whole| if (whole.cache_manifest) |man| { - const source = file.getSource(gpa) catch |err| { - try pt.reportRetryableFileError(file_index, "unable to load source: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }; + const func = zcu.funcInfo(maybe_coerced_func_index); - const resolved_path = std.fs.path.resolve(gpa, &.{ - file.mod.root.root_dir.path orelse ".", - file.mod.root.sub_path, - file.sub_file_path, - }) catch |err| { - try pt.reportRetryableFileError(file_index, "unable to resolve path: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }; - errdefer gpa.free(resolved_path); + const was_outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); - whole.cache_manifest_mutex.lock(); - defer whole.cache_manifest_mutex.unlock(); - man.addFilePostContents(resolved_path, source.bytes, source.stat) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - else => { - try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }, - }; + const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit); + + if (was_outdated) { + dev.check(.incremental); + _ = zcu.outdated_ready.swapRemove(anal_unit); + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + } else { + // We can trust the current information about this function. + if (prev_failed) { + return error.AnalysisFail; + } + switch (func.analysisUnordered(ip).state) { + .unreferenced => {}, // this is the first reference + .queued => {}, // we're waiting on first-time analysis + .analyzed => return, // up-to-date + } + } + + const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0); + defer func_prog_node.end(); + + const ies_outdated, const new_failed = if (pt.analyzeFuncBody(func_index)) |result| + .{ prev_failed or result.ies_outdated, false } + else |err| switch (err) { + error.AnalysisFail => res: { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this function 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 {}", .{zcu.fmtAnalUnit(anal_unit)}); + } + // We consider the IES to be outdated if the function previously succeeded analysis; in this case, + // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting + // a different error later (which may now be invalid). + break :res .{ !prev_failed, true }; }, - .incremental => {}, + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; + }, + }; + + if (was_outdated) { + if (ies_outdated) { + try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); + } else { + try zcu.markPoDependeeUpToDate(.{ .interned = func_index }); + } } -} -const SemaCauResult = packed struct { - /// Whether the value of a `decl_val` of the corresponding Nav changed. - invalidate_decl_val: bool, - /// Whether the type of a `decl_ref` of the corresponding Nav changed. - invalidate_decl_ref: bool, -}; + if (new_failed) return error.AnalysisFail; +} -/// Performs semantic analysis on the given `Cau`, storing results to its owner `Nav` if needed. -/// If analysis fails, returns `error.AnalysisFail`, storing an error in `zcu.failed_analysis` unless -/// the error is transitive. -/// On success, returns information about whether the `Nav` value changed. -fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { +fn analyzeFuncBody( + pt: Zcu.PerThread, + func_index: InternPool.Index, +) Zcu.SemaError!struct { ies_outdated: bool } { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); - - const cau = ip.getCau(cau_index); - const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; - const file = zcu.fileByIndex(inst_info.file); - const zir = file.zir; - - if (file.status != .success_zir) { - return error.AnalysisFail; - } + const func = zcu.funcInfo(func_index); + const anal_unit = AnalUnit.wrap(.{ .func = func_index }); - // We are about to re-analyze this `Cau`; drop its depenndencies. - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + // Make sure that this function is still owned by the same `Nav`. Otherwise, analyzing + // it would be a waste of time in the best case, and could cause codegen to give bogus + // results in the worst case. - switch (cau.owner.unwrap()) { - .none => {}, // `comptime` decl -- we will re-analyze its body. - .nav => {}, // Other decl -- we will re-analyze its value. - .type => |ty| { - // This is an incremental update, and this type is being re-analyzed because it is outdated. - // Create a new type in its place, and mark the old one as outdated so that use sites will - // be re-analyzed and discover an up-to-date type. - const new_ty = try pt.ensureTypeUpToDate(ty, true); - assert(new_ty != ty); - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; - }, + if (func.generic_owner == .none) { + // Among another things, this ensures that the function's `zir_body_inst` is correct. + try pt.ensureNavValUpToDate(func.owner_nav); + if (ip.getNav(func.owner_nav).status.resolved.val != func_index) { + // This function is no longer referenced! There's no point in re-analyzing it. + // Just mark a transitive failure and move on. + return error.AnalysisFail; + } + } else { + const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; + // Among another things, this ensures that the function's `zir_body_inst` is correct. + try pt.ensureNavValUpToDate(go_nav); + if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) { + // The generic owner is no longer referenced, so this function is also unreferenced. + // There's no point in re-analyzing it. Just mark a transitive failure and move on. + return error.AnalysisFail; + } } - const is_usingnamespace = switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).is_usingnamespace, - .none, .type => false, - }; + // We'll want to remember what the IES used to be before the update for + // dependency invalidation purposes. + const old_resolved_ies = if (func.analysisUnordered(ip).inferred_error_set) + func.resolvedErrorSetUnordered(ip) + else + .none; - log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("analyze and generate fn body {}", .{zcu.fmtAnalUnit(anal_unit)}); - try zcu.analysis_in_progress.put(gpa, anal_unit, {}); - errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); + var air = try pt.analyzeFnBodyInner(func_index); + errdefer air.deinit(gpa); - var analysis_arena = std.heap.ArenaAllocator.init(gpa); - defer analysis_arena.deinit(); + const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or + func.resolvedErrorSetUnordered(ip) != old_resolved_ies; - var comptime_err_ret_trace = std.ArrayList(Zcu.LazySrcLoc).init(gpa); - defer comptime_err_ret_trace.deinit(); + const comp = zcu.comp; - var sema: Sema = .{ - .pt = pt, - .gpa = gpa, - .arena = analysis_arena.allocator(), - .code = zir, - .owner = anal_unit, - .func_index = .none, - .func_is_naked = false, - .fn_ret_ty = Type.void, - .fn_ret_ty_ies = null, - .comptime_err_ret_trace = &comptime_err_ret_trace, - }; - defer sema.deinit(); + const dump_air = build_options.enable_debug_extensions and comp.verbose_air; + const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null); - // Every `Cau` has a dependency on the source of its own ZIR instruction. - try sema.declareDependency(.{ .src_hash = cau.zir_index }); + if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) { + air.deinit(gpa); + return .{ .ies_outdated = ies_outdated }; + } - var block: Sema.Block = .{ - .parent = null, - .sema = &sema, - .namespace = cau.namespace, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - .src_base_inst = cau.zir_index, - .type_name_ctx = switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).fqn, - .type => |ty| Type.fromInterned(ty).containerTypeName(ip), - .none => try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{ - Type.fromInterned(zcu.namespacePtr(cau.namespace).owner_type).containerTypeName(ip).fmt(ip), - }, .no_embedded_nulls), - }, - }; - defer block.instructions.deinit(gpa); + // This job depends on any resolve_type_fully jobs queued up before it. + try comp.queueJob(.{ .codegen_func = .{ + .func = func_index, + .air = air, + } }); - const zir_decl = zir.getDeclaration(inst_info.inst); + return .{ .ies_outdated = ies_outdated }; +} - // We have to fetch this state before resolving the body because of the `nav_already_populated` - // case below. We might change the language in future so that align/linksection/etc for functions - // work in a way more in line with other declarations, in which case that logic will go away. - const old_nav_info = switch (cau.owner.unwrap()) { - .none, .type => undefined, // we'll never use `old_nav_info` - .nav => |nav| ip.getNav(nav), - }; +/// Takes ownership of `air`, even on error. +/// If any types referenced by `air` are unresolved, marks the codegen as failed. +pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Air) Allocator.Error!void { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const comp = zcu.comp; - const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); - const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); - const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); - const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); - const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); + defer { + var air_mut = air; + air_mut.deinit(gpa); + } - // First, we must resolve the declaration's type. To do this, we analyze the type body if available, - // or otherwise, we analyze the value body, populating `early_val` in the process. + const func = zcu.funcInfo(func_index); + const nav_index = func.owner_nav; + const nav = ip.getNav(nav_index); - const decl_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { - // We evaluate only the type now; no need for the value yet. - const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_info.inst); - const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src); - break :ty .{ .fromInterned(type_ref.toInterned().?), null }; - } else ty: { - // We don't have a type body, so we need to evaluate the value immediately. - const value_body = zir_decl.value_body.?; - const result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); - const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); - break :ty .{ val.typeOf(zcu), val }; - }; + var liveness = try Liveness.analyze(gpa, air, ip); + defer liveness.deinit(gpa); - switch (zir_decl.kind) { - .unnamed_test, .@"test", .decltest => assert(decl_ty.zigTypeTag(zcu) == .@"fn"), - .@"comptime" => assert(decl_ty.toIntern() == .void_type), - .@"usingnamespace" => {}, - .@"const" => {}, - .@"var" => try sema.validateVarType( - &block, - if (zir_decl.type_body != null) ty_src else init_src, - decl_ty, - zir_decl.linkage == .@"extern", - ), + if (build_options.enable_debug_extensions and comp.verbose_air) { + std.debug.print("# Begin Function AIR: {}:\n", .{nav.fqn.fmt(ip)}); + @import("../print_air.zig").dump(pt, air, liveness); + std.debug.print("# End Function AIR: {}\n\n", .{nav.fqn.fmt(ip)}); } - // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine - // the full pointer type of this declaration. + if (std.debug.runtime_safety) { + var verify: Liveness.Verify = .{ + .gpa = gpa, + .air = air, + .liveness = liveness, + .intern_pool = ip, + }; + defer verify.deinit(); - const alignment: InternPool.Alignment = a: { - const align_body = zir_decl.align_body orelse break :a .none; - const align_ref = try sema.resolveInlineBody(&block, align_body, inst_info.inst); - break :a try sema.analyzeAsAlign(&block, align_src, align_ref); - }; + verify.verify() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( + gpa, + zcu.navSrcLoc(nav_index), + "invalid liveness: {s}", + .{@errorName(err)}, + )); + return; + }, + }; + } - const @"linksection": InternPool.OptionalNullTerminatedString = ls: { - const linksection_body = zir_decl.linksection_body orelse break :ls .none; - const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_info.inst); - const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{ - .needed_comptime_reason = "linksection must be comptime-known", - }); - if (std.mem.indexOfScalar(u8, bytes, 0) != null) { - return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{}); - } else if (bytes.len == 0) { - return sema.fail(&block, section_src, "linksection cannot be empty", .{}); - } - break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls); - }; + const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0); + defer codegen_prog_node.end(); - const @"addrspace": std.builtin.AddressSpace = as: { - const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) { - .@"var" => .variable, - else => switch (decl_ty.zigTypeTag(zcu)) { - .@"fn" => .function, - else => .constant, + if (!air.typesFullyResolved(zcu)) { + // A type we depend on failed to resolve. This is a transitive failure. + // Correcting this failure will involve changing a type this function + // depends on, hence triggering re-analysis of this function, so this + // interacts correctly with incremental compilation. + // TODO: do we need to mark this failure anywhere? I don't think so, since compilation + // will fail due to the type error anyway. + } else if (comp.bin_file) |lf| { + lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + assert(zcu.failed_codegen.contains(nav_index)); + }, + else => { + try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( + gpa, + zcu.navSrcLoc(nav_index), + "unable to codegen: {s}", + .{@errorName(err)}, + )); + try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); }, }; - const target = zcu.getTarget(); - const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) { - .function => target_util.defaultAddressSpace(target, .function), - .variable => target_util.defaultAddressSpace(target, .global_mutable), - .constant => target_util.defaultAddressSpace(target, .global_constant), - else => unreachable, + } else if (zcu.llvm_object) |llvm_object| { + llvm_object.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, }; - const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_info.inst); - break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); - }; - - // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations - // don't have an associated value body. - - const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { - // Put the resolved type into `inst_map` to be used as the result type of the init. - try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_info.inst}); - sema.inst_map.putAssumeCapacity(inst_info.inst, Air.internedToRef(decl_ty.toIntern())); - const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); - assert(sema.inst_map.remove(inst_info.inst)); + } +} - const result_ref = try sema.coerce(&block, decl_ty, uncoerced_result_ref, init_src); - break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); - } else null; +/// https://github.com/ziglang/zig/issues/14307 +pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void { + dev.check(.sema); + const import_file_result = try pt.importPkg(pkg); + const root_type = pt.zcu.fileRootType(import_file_result.file_index); + if (root_type == .none) { + return pt.semaFile(import_file_result.file_index); + } +} - // TODO: missing validation? +fn createFileRootStruct( + pt: Zcu.PerThread, + file_index: Zcu.File.Index, + namespace_index: Zcu.Namespace.Index, + replace_existing: bool, +) Allocator.Error!InternPool.Index { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const file = zcu.fileByIndex(file_index); + const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; + assert(extended.opcode == .struct_decl); + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + assert(!small.has_captures_len); + assert(!small.has_backing_int); + assert(small.layout == .auto); + var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; + const fields_len = if (small.has_fields_len) blk: { + const fields_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + const decls_len = if (small.has_decls_len) blk: { + const decls_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + const decls = file.zir.bodySlice(extra_index, decls_len); + extra_index += decls_len; - const decl_val: Value = switch (zir_decl.linkage) { - .normal, .@"export" => switch (zir_decl.kind) { - .@"var" => .fromInterned(try pt.intern(.{ .variable = .{ - .ty = decl_ty.toIntern(), - .init = final_val.?.toIntern(), - .owner_nav = cau.owner.unwrap().nav, - .is_threadlocal = zir_decl.is_threadlocal, - .is_weak_linkage = false, - } })), - else => final_val.?, - }, - .@"extern" => val: { - assert(final_val == null); // extern decls do not have a value body - const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: { - break :l zir.nullTerminatedString(zir_decl.lib_name); - } else null; - if (lib_name) |l| { - const lib_name_src = block.src(.{ .node_offset_lib_name = 0 }); - try sema.handleExternLibName(&block, lib_name_src, l); - } - break :val .fromInterned(try pt.getExtern(.{ - .name = old_nav_info.name, - .ty = decl_ty.toIntern(), - .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls), - .is_const = zir_decl.kind == .@"const", - .is_threadlocal = zir_decl.is_threadlocal, - .is_weak_linkage = false, - .is_dll_import = false, - .alignment = alignment, - .@"addrspace" = @"addrspace", - .zir_index = cau.zir_index, // `declaration` instruction - .owner_nav = undefined, // ignored by `getExtern` - })); - }, + const tracked_inst = try ip.trackZir(gpa, pt.tid, .{ + .file = file_index, + .inst = .main_struct_inst, + }); + const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ + .layout = .auto, + .fields_len = fields_len, + .known_non_opv = small.known_non_opv, + .requires_comptime = if (small.known_comptime_only) .yes else .unknown, + .any_comptime_fields = small.any_comptime_fields, + .any_default_inits = small.any_default_inits, + .inits_resolved = false, + .any_aligned_fields = small.any_aligned_fields, + .key = .{ .declared = .{ + .zir_index = tracked_inst, + .captures = &.{}, + } }, + }, replace_existing)) { + .existing => unreachable, // we wouldn't be analysing the file root if this type existed + .wip => |wip| wip, }; + errdefer wip_ty.cancel(ip, pt.tid); - const nav_index = switch (cau.owner.unwrap()) { - .none => { - // This is a `comptime` decl, so we are done -- the side effects are all we care about. - // Just make sure to `flushExports`. - try sema.flushExports(); - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_decl_val = false, - .invalidate_decl_ref = false, - }; - }, - .nav => |nav| nav, // We will resolve this `Nav` below. - .type => unreachable, // Handled at top of function. - }; + wip_ty.setName(ip, try file.internFullyQualifiedName(pt)); + ip.namespacePtr(namespace_index).owner_type = wip_ty.index; - switch (decl_val.toIntern()) { - .generic_poison => unreachable, // assertion failure - .unreachable_value => unreachable, // assertion failure - else => {}, + if (zcu.comp.incremental) { + try ip.addDependency( + gpa, + .wrap(.{ .type = wip_ty.index }), + .{ .src_hash = tracked_inst }, + ); } - // This resolves the type of the resolved value, not that value itself. If `decl_val` is a struct type, - // this resolves the type `type` (which needs no resolution), not the struct itself. - try decl_ty.resolveLayout(pt); - - // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`. - if (is_usingnamespace) { - if (decl_ty.toIntern() != .type_type) { - return sema.fail(&block, ty_src, "expected type, found {}", .{decl_ty.fmt(pt)}); - } - if (decl_val.toType().getNamespace(zcu) == .none) { - return sema.fail(&block, ty_src, "type {} has no namespace", .{decl_val.toType().fmt(pt)}); - } - ip.resolveNavValue(nav_index, .{ - .val = decl_val.toIntern(), - .alignment = .none, - .@"linksection" = .none, - .@"addrspace" = .generic, - }); - // TODO: usingnamespace cannot participate in incremental compilation - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; + try pt.scanNamespace(namespace_index, decls); + try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); + codegen_type: { + if (zcu.comp.config.use_llvm) break :codegen_type; + if (file.mod.strip) break :codegen_type; + // This job depends on any resolve_type_fully jobs queued up before it. + try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } + zcu.setFileRootType(file_index, wip_ty.index); + return wip_ty.finish(ip, namespace_index); +} - const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(decl_val.toIntern())) { - .func => |f| .{ true, f.owner_nav == nav_index }, // note that this lets function aliases reach codegen - .variable => |v| .{ v.owner_nav == nav_index, false }, - .@"extern" => |e| .{ false, Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" }, - else => .{ true, false }, - }; - - // Keep in sync with logic in `Sema.zirVarExtended`. +/// Re-scan the namespace of a file's root struct type on an incremental update. +/// The file must have successfully populated ZIR. +/// If the file's root struct type is not populated (the file is unreferenced), nothing is done. +/// This is called by `updateZirRefs` for all updated files before the main work loop. +/// This function does not perform any semantic analysis. +fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void { + const zcu = pt.zcu; - if (is_owned_fn) { - // linksection etc are legal, except some targets do not support function alignment. - if (zir_decl.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) { - return sema.fail(&block, align_src, "target does not support function alignment", .{}); - } - } else if (try decl_ty.comptimeOnlySema(pt)) { - // alignment, linksection, addrspace annotations are not allowed for comptime-only types. - const reason: []const u8 = switch (ip.indexToKey(decl_val.toIntern())) { - .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* - else => "comptime-only type", - }; - if (zir_decl.align_body != null) { - return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); - } - if (zir_decl.linksection_body != null) { - return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); - } - if (zir_decl.addrspace_body != null) { - return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); - } - } + const file = zcu.fileByIndex(file_index); + assert(file.status == .success_zir); + const file_root_type = zcu.fileRootType(file_index); + if (file_root_type == .none) return; - ip.resolveNavValue(nav_index, .{ - .val = decl_val.toIntern(), - .alignment = alignment, - .@"linksection" = @"linksection", - .@"addrspace" = @"addrspace", + log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{ + file.mod.fully_qualified_name, + file.sub_file_path, }); - // Mark the `Cau` as completed before evaluating the export! - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - - if (zir_decl.linkage == .@"export") { - const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) }); - const name_slice = zir.nullTerminatedString(zir_decl.name); - const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls); - try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_index); - } + const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu); + const decls = decls: { + const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - try sema.flushExports(); + var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + break :decls file.zir.bodySlice(extra_index, decls_len); + }; + try pt.scanNamespace(namespace_index, decls); + zcu.namespacePtr(namespace_index).generation = zcu.generation; +} - queue_codegen: { - if (!queue_linker_work) break :queue_codegen; +fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { + const tracy = trace(@src()); + defer tracy.end(); - if (!try decl_ty.hasRuntimeBitsSema(pt)) { - if (zcu.comp.config.use_llvm) break :queue_codegen; - if (file.mod.strip) break :queue_codegen; - } + const zcu = pt.zcu; + const gpa = zcu.gpa; + const file = zcu.fileByIndex(file_index); + assert(zcu.fileRootType(file_index) == .none); - // This job depends on any resolve_type_fully jobs queued up before it. - try zcu.comp.queueJob(.{ .codegen_nav = nav_index }); + if (file.status != .success_zir) { + return error.AnalysisFail; } + assert(file.zir_loaded); - switch (old_nav_info.status) { - .unresolved => return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }, - .resolved => |old| { - const new = ip.getNav(nav_index).status.resolved; - return .{ - .invalidate_decl_val = new.val != old.val, - .invalidate_decl_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or - new.alignment != old.alignment or - new.@"linksection" != old.@"linksection" or - new.@"addrspace" != old.@"addrspace", + const new_namespace_index = try pt.createNamespace(.{ + .parent = .none, + .owner_type = undefined, // set in `createFileRootStruct` + .file_scope = file_index, + .generation = zcu.generation, + }); + const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false); + errdefer zcu.intern_pool.remove(pt.tid, struct_ty); + + switch (zcu.comp.cache_use) { + .whole => |whole| if (whole.cache_manifest) |man| { + const source = file.getSource(gpa) catch |err| { + try pt.reportRetryableFileError(file_index, "unable to load source: {s}", .{@errorName(err)}); + return error.AnalysisFail; + }; + + const resolved_path = std.fs.path.resolve(gpa, &.{ + file.mod.root.root_dir.path orelse ".", + file.mod.root.sub_path, + file.sub_file_path, + }) catch |err| { + try pt.reportRetryableFileError(file_index, "unable to resolve path: {s}", .{@errorName(err)}); + return error.AnalysisFail; + }; + errdefer gpa.free(resolved_path); + + whole.cache_manifest_mutex.lock(); + defer whole.cache_manifest_mutex.unlock(); + man.addFilePostContents(resolved_path, source.bytes, source.stat) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => { + try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)}); + return error.AnalysisFail; + }, }; }, + .incremental => {}, } } @@ -1880,45 +1914,42 @@ pub fn scanNamespace( // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather // than their name. We'll build an efficient mapping now, then discard the current `decls`. - // We map to the `Cau`, since not every declaration has a `Nav`. - var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index) = .empty; + // We map to the `AnalUnit`, since not every declaration has a `Nav`. + var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit) = .empty; defer existing_by_inst.deinit(gpa); try existing_by_inst.ensureTotalCapacity(gpa, @intCast( namespace.pub_decls.count() + namespace.priv_decls.count() + namespace.pub_usingnamespace.items.len + namespace.priv_usingnamespace.items.len + - namespace.other_decls.items.len, + namespace.comptime_decls.items.len + + namespace.test_decls.items.len, )); for (namespace.pub_decls.keys()) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.priv_decls.keys()) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.pub_usingnamespace.items) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.priv_usingnamespace.items) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); - } - for (namespace.other_decls.items) |cau_index| { - const cau = ip.getCau(cau_index); - existing_by_inst.putAssumeCapacityNoClobber(cau.zir_index, cau_index); - // If this is a test, it'll be re-added to `test_functions` later on - // if still alive. Remove it for now. - switch (cau.owner.unwrap()) { - .none, .type => {}, - .nav => |nav| _ = zcu.test_functions.swapRemove(nav), - } + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); + } + for (namespace.comptime_decls.items) |cu| { + const zir_index = ip.getComptimeUnit(cu).zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .@"comptime" = cu })); + } + for (namespace.test_decls.items) |nav| { + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); + // This test will be re-added to `test_functions` later on if it's still alive. Remove it for now. + _ = zcu.test_functions.swapRemove(nav); } var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty; @@ -1928,7 +1959,8 @@ pub fn scanNamespace( namespace.priv_decls.clearRetainingCapacity(); namespace.pub_usingnamespace.clearRetainingCapacity(); namespace.priv_usingnamespace.clearRetainingCapacity(); - namespace.other_decls.clearRetainingCapacity(); + namespace.comptime_decls.clearRetainingCapacity(); + namespace.test_decls.clearRetainingCapacity(); var scan_decl_iter: ScanDeclIter = .{ .pt = pt, @@ -1950,7 +1982,7 @@ const ScanDeclIter = struct { pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index, seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), - existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index), + existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit), /// Decl scanning is run in two passes, so that we can detect when a generated /// name would clash with an explicit name and use a different one. pass: enum { named, unnamed }, @@ -1988,48 +2020,30 @@ const ScanDeclIter = struct { const decl = zir.getDeclaration(decl_inst); - const Kind = enum { @"comptime", @"usingnamespace", @"test", named }; - - const maybe_name: InternPool.OptionalNullTerminatedString, const kind: Kind, const is_named_test: bool = switch (decl.kind) { - .@"comptime" => info: { + const maybe_name: InternPool.OptionalNullTerminatedString = switch (decl.kind) { + .@"comptime" => name: { if (iter.pass != .unnamed) return; - break :info .{ - .none, - .@"comptime", - false, - }; + break :name .none; }, - .@"usingnamespace" => info: { + .@"usingnamespace" => name: { if (iter.pass != .unnamed) return; const i = iter.usingnamespace_index; iter.usingnamespace_index += 1; - break :info .{ - (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(), - .@"usingnamespace", - false, - }; + break :name (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(); }, - .unnamed_test => info: { + .unnamed_test => name: { if (iter.pass != .unnamed) return; const i = iter.unnamed_test_index; iter.unnamed_test_index += 1; - break :info .{ - (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(), - .@"test", - false, - }; + break :name (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(); }, - .@"test", .decltest => |kind| info: { + .@"test", .decltest => |kind| name: { // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary. if (iter.pass != .unnamed) return; const prefix = @tagName(kind); - break :info .{ - (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(), - .@"test", - true, - }; + break :name (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(); }, - .@"const", .@"var" => info: { + .@"const", .@"var" => name: { if (iter.pass != .named) return; const name = try ip.getOrPutString( gpa, @@ -2038,11 +2052,7 @@ const ScanDeclIter = struct { .no_embedded_nulls, ); try iter.seen_decls.putNoClobber(gpa, name, {}); - break :info .{ - name.toOptional(), - .named, - false, - }; + break :name name.toOptional(); }, }; @@ -2051,46 +2061,44 @@ const ScanDeclIter = struct { .inst = decl_inst, }); - const existing_cau = iter.existing_by_inst.get(tracked_inst); + const existing_unit = iter.existing_by_inst.get(tracked_inst); - const cau, const want_analysis = switch (kind) { - .@"comptime" => cau: { - const cau = existing_cau orelse try ip.createComptimeCau(gpa, pt.tid, tracked_inst, namespace_index); + const unit, const want_analysis = switch (decl.kind) { + .@"comptime" => unit: { + const cu = if (existing_unit) |eu| + eu.unwrap().@"comptime" + else + try ip.createComptimeUnit(gpa, pt.tid, tracked_inst, namespace_index); - try namespace.other_decls.append(gpa, cau); + const unit: AnalUnit = .wrap(.{ .@"comptime" = cu }); - if (existing_cau == null) { - // For a `comptime` declaration, whether to analyze is based solely on whether the - // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already. - const unit = AnalUnit.wrap(.{ .cau = cau }); - if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value); - if (kv.value == 0) { // no PO deps - zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); - } - } else if (!zcu.outdated.contains(unit)) { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, 0); - zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); - } + try namespace.comptime_decls.append(gpa, cu); + + if (existing_unit == null) { + // For a `comptime` declaration, whether to analyze is based solely on whether the unit + // is outdated. So, add this fresh one to `outdated` and `outdated_ready`. + try zcu.outdated.ensureUnusedCapacity(gpa, 1); + try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); + zcu.outdated.putAssumeCapacityNoClobber(unit, 0); + zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); } - break :cau .{ cau, true }; + break :unit .{ unit, true }; }, - else => cau: { + else => unit: { const name = maybe_name.unwrap().?; const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name); - const cau, const nav = if (existing_cau) |cau_index| cau_nav: { - const nav_index = ip.getCau(cau_index).owner.unwrap().nav; - const nav = ip.getNav(nav_index); - assert(nav.name == name); - assert(nav.fqn == fqn); - break :cau_nav .{ cau_index, nav_index }; - } else try ip.createPairedCauNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, kind == .@"usingnamespace"); - const want_analysis = switch (kind) { + const nav = if (existing_unit) |eu| + eu.unwrap().nav_val + else + try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace"); + + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); + + assert(ip.getNav(nav).name == name); + assert(ip.getNav(nav).fqn == fqn); + + const want_analysis = switch (decl.kind) { .@"comptime" => unreachable, .@"usingnamespace" => a: { if (comp.incremental) { @@ -2103,8 +2111,9 @@ const ScanDeclIter = struct { } break :a true; }, - .@"test" => a: { - try namespace.other_decls.append(gpa, cau); + .unnamed_test, .@"test", .decltest => a: { + const is_named = decl.kind != .unnamed_test; + try namespace.test_decls.append(gpa, nav); // TODO: incremental compilation! // * remove from `test_functions` if no longer matching filter // * add to `test_functions` if newly passing filter @@ -2112,7 +2121,7 @@ const ScanDeclIter = struct { // Perhaps we should add all test indiscriminately and filter at the end of the update. if (!comp.config.is_test) break :a false; if (file.mod != zcu.main_mod) break :a false; - if (is_named_test and comp.test_filters.len > 0) { + if (is_named and comp.test_filters.len > 0) { const fqn_slice = fqn.toSlice(ip); for (comp.test_filters) |test_filter| { if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; @@ -2121,7 +2130,7 @@ const ScanDeclIter = struct { try zcu.test_functions.put(gpa, nav, {}); break :a true; }, - .named => a: { + .@"const", .@"var" => a: { if (decl.is_pub) { try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); } else { @@ -2130,23 +2139,23 @@ const ScanDeclIter = struct { break :a false; }, }; - break :cau .{ cau, want_analysis }; + break :unit .{ unit, want_analysis }; }, }; - if (existing_cau == null and (want_analysis or decl.linkage == .@"export")) { + if (existing_unit == null and (want_analysis or decl.linkage == .@"export")) { log.debug( - "scanDecl queue analyze_cau file='{s}' cau_index={d}", - .{ namespace.fileScope(zcu).sub_file_path, cau }, + "scanDecl queue analyze_comptime_unit file='{s}' unit={}", + .{ namespace.fileScope(zcu).sub_file_path, zcu.fmtAnalUnit(unit) }, ); - try comp.queueJob(.{ .analyze_cau = cau }); + try comp.queueJob(.{ .analyze_comptime_unit = unit }); } // TODO: we used to do line number updates here, but this is an inappropriate place for this logic to live. } }; -fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air { +fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air { const tracy = trace(@src()); defer tracy.end(); @@ -2168,21 +2177,14 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! func.setResolvedErrorSet(ip, .none); } - // This is the `Cau` corresponding to the `declaration` instruction which the function or its generic owner originates from. - const decl_cau = ip.getCau(cau: { - const orig_nav = if (func.generic_owner == .none) - func.owner_nav - else - zcu.funcInfo(func.generic_owner).owner_nav; - - break :cau ip.getNav(orig_nav).analysis_owner.unwrap().?; - }); + // This is the `Nau` corresponding to the `declaration` instruction which the function or its generic owner originates from. + const decl_nav = ip.getNav(if (func.generic_owner == .none) + func.owner_nav + else + zcu.funcInfo(func.generic_owner).owner_nav); const func_nav = ip.getNav(func.owner_nav); - const decl_prog_node = zcu.sema_prog_node.start(func_nav.fqn.toSlice(ip), 0); - defer decl_prog_node.end(); - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); var analysis_arena = std.heap.ArenaAllocator.init(gpa); @@ -2216,7 +2218,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! // Every runtime function has a dependency on the source of the Decl it originates from. // It also depends on the value of its owner Decl. - try sema.declareDependency(.{ .src_hash = decl_cau.zir_index }); + try sema.declareDependency(.{ .src_hash = decl_nav.analysis.?.zir_index }); try sema.declareDependency(.{ .nav_val = func.owner_nav }); if (func.analysisUnordered(ip).inferred_error_set) { @@ -2236,11 +2238,11 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! var inner_block: Sema.Block = .{ .parent = null, .sema = &sema, - .namespace = decl_cau.namespace, + .namespace = decl_nav.analysis.?.namespace, .instructions = .{}, .inlining = null, .is_comptime = false, - .src_base_inst = decl_cau.zir_index, + .src_base_inst = decl_nav.analysis.?.zir_index, .type_name_ctx = func_nav.fqn, }; defer inner_block.instructions.deinit(gpa); @@ -2542,10 +2544,10 @@ fn processExportsInner( .nav => |nav_index| if (failed: { const nav = ip.getNav(nav_index); if (zcu.failed_codegen.contains(nav_index)) break :failed true; - if (nav.analysis_owner.unwrap()) |cau| { - const cau_unit = AnalUnit.wrap(.{ .cau = cau }); - if (zcu.failed_analysis.contains(cau_unit)) break :failed true; - if (zcu.transitive_failed_analysis.contains(cau_unit)) break :failed true; + if (nav.analysis != null) { + const unit: AnalUnit = .wrap(.{ .nav_val = nav_index }); + if (zcu.failed_analysis.contains(unit)) break :failed true; + if (zcu.transitive_failed_analysis.contains(unit)) break :failed true; } const val = switch (nav.status) { .unresolved => break :failed true, @@ -2593,15 +2595,14 @@ pub fn populateTestFunctions( Zcu.Namespace.NameAdapter{ .zcu = zcu }, ).?; { - // We have to call `ensureCauAnalyzed` here in case `builtin.test_functions` + // We have to call `ensureNavValUpToDate` here in case `builtin.test_functions` // was not referenced by start code. zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); defer { zcu.sema_prog_node.end(); zcu.sema_prog_node = std.Progress.Node.none; } - const cau_index = ip.getNav(nav_index).analysis_owner.unwrap().?; - pt.ensureCauAnalyzed(cau_index) catch |err| switch (err) { + pt.ensureNavValUpToDate(nav_index) catch |err| switch (err) { error.AnalysisFail => return, error.OutOfMemory => return error.OutOfMemory, }; @@ -2622,8 +2623,7 @@ pub fn populateTestFunctions( { // The test declaration might have failed; if that's the case, just return, as we'll // be emitting a compile error anyway. - const cau = test_nav.analysis_owner.unwrap().?; - const anal_unit: AnalUnit = .wrap(.{ .cau = cau }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = test_nav_index }); if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { @@ -2748,8 +2748,8 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error "unable to codegen: {s}", .{@errorName(err)}, )); - if (nav.analysis_owner.unwrap()) |cau| { - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .cau = cau })); + if (nav.analysis != null) { + try zcu.retryable_failures.append(zcu.gpa, .wrap(.{ .nav_val = nav_index })); } else { // TODO: we don't have a way to indicate that this failure is retryable! // Since these are really rare, we could as a cop-out retry the whole build next update. @@ -3255,7 +3255,7 @@ pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Intern const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls); const builtin_nav = std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std.zig is corrupt and missing 'builtin'"); - pt.ensureCauAnalyzed(ip.getNav(builtin_nav).analysis_owner.unwrap().?) catch @panic("std.builtin is corrupt"); + pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt"); const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.resolved.val); const builtin_namespace = zcu.namespacePtr(builtin_type.getNamespace(zcu).unwrap() orelse @panic("std.builtin is corrupt")); const name_str = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls); @@ -3307,68 +3307,45 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo /// Given a container type requiring resolution, ensures that it is up-to-date. /// If not, the type is recreated at a new `InternPool.Index`. /// The new index is returned. This is the same as the old index if the fields were up-to-date. -/// If `already_updating` is set, assumes the type is already outdated and undergoing re-analysis rather than checking `zcu.outdated`. -pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index, already_updating: bool) Zcu.SemaError!InternPool.Index { +pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .type = ty }); + const outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + + if (!outdated) return ty; + + // We will recreate the type at a new `InternPool.Index`. + + _ = zcu.outdated_ready.swapRemove(anal_unit); + try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); + + // Delete old state which is no longer in use. Technically, this is not necessary: these exports, + // references, etc, will be ignored because the type itself is unreferenced. However, it allows + // reusing the memory which is currently being used to track this state. + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + switch (ip.indexToKey(ty)) { - .struct_type => |key| { - const struct_obj = ip.loadStructType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = struct_obj.cau }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateStructType(key, struct_obj); - }, - .union_type => |key| { - const union_obj = ip.loadUnionType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = union_obj.cau }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateUnionType(key, union_obj); - }, - .enum_type => |key| { - const enum_obj = ip.loadEnumType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateEnumType(key, enum_obj); - }, - .opaque_type => { - assert(!already_updating); - return ty; - }, + .struct_type => |key| return pt.recreateStructType(ty, key), + .union_type => |key| return pt.recreateUnionType(ty, key), + .enum_type => |key| return pt.recreateEnumType(ty, key), else => unreachable, } } fn recreateStructType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - struct_obj: InternPool.LoadedStructType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3405,8 +3382,7 @@ fn recreateStructType( if (captures_len != key.captures.owned.len) return error.AnalysisFail; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = struct_obj.cau })); + const struct_obj = ip.loadStructType(old_ty); const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ .layout = small.layout, @@ -3428,17 +3404,16 @@ fn recreateStructType( errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, struct_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, struct_obj.namespace, wip_ty.index); try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index }, ); zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive. try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - const new_ty = wip_ty.finish(ip, new_cau_index.toOptional(), struct_obj.namespace); + const new_ty = wip_ty.finish(ip, struct_obj.namespace); if (inst_info.inst == .main_struct_inst) { // This is the root type of a file! Update the reference. zcu.setFileRootType(inst_info.file, new_ty); @@ -3448,8 +3423,8 @@ fn recreateStructType( fn recreateUnionType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - union_obj: InternPool.LoadedUnionType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3488,8 +3463,7 @@ fn recreateUnionType( if (captures_len != key.captures.owned.len) return error.AnalysisFail; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = union_obj.cau })); + const union_obj = ip.loadUnionType(old_ty); const namespace_index = union_obj.namespace; @@ -3526,22 +3500,21 @@ fn recreateUnionType( errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, union_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index }, ); zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive. try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); + return wip_ty.finish(ip, namespace_index); } fn recreateEnumType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - enum_obj: InternPool.LoadedEnumType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3610,8 +3583,7 @@ fn recreateEnumType( if (bag != 0) break true; } else false; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? })); + const enum_obj = ip.loadEnumType(old_ty); const namespace_index = enum_obj.namespace; @@ -3637,12 +3609,10 @@ fn recreateEnumType( wip_ty.setName(ip, enum_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); - zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive. - wip_ty.prepare(ip, new_cau_index, namespace_index); + wip_ty.prepare(ip, namespace_index); done = true; Sema.resolveDeclaredEnum( @@ -3652,7 +3622,6 @@ fn recreateEnumType( key.zir_index, namespace_index, enum_obj.name, - new_cau_index, small, body, tag_type_ref, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 20db035c93f0..d132b5232990 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2261,8 +2261,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In assert(file.zir_loaded); const decl = file.zir.getDeclaration(inst_info.inst); - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, @@ -2292,8 +2292,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In assert(file.zir_loaded); const decl = file.zir.getDeclaration(inst_info.inst); - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, @@ -2321,8 +2321,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In assert(file.zir_loaded); const decl = file.zir.getDeclaration(inst_info.inst); - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, @@ -2563,8 +2563,8 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool return; } - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,