diff --git a/src/Compilation.zig b/src/Compilation.zig index 6f9b2e18d62b..28c5efab6c9a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2906,6 +2906,7 @@ const Header = extern struct { file_deps_len: u32, src_hash_deps_len: u32, nav_val_deps_len: u32, + nav_ty_deps_len: u32, namespace_deps_len: u32, namespace_name_deps_len: u32, first_dependency_len: u32, @@ -2949,6 +2950,7 @@ pub fn saveState(comp: *Compilation) !void { .file_deps_len = @intCast(ip.file_deps.count()), .src_hash_deps_len = @intCast(ip.src_hash_deps.count()), .nav_val_deps_len = @intCast(ip.nav_val_deps.count()), + .nav_ty_deps_len = @intCast(ip.nav_ty_deps.count()), .namespace_deps_len = @intCast(ip.namespace_deps.count()), .namespace_name_deps_len = @intCast(ip.namespace_name_deps.count()), .first_dependency_len = @intCast(ip.first_dependency.count()), @@ -2979,6 +2981,8 @@ pub fn saveState(comp: *Compilation) !void { addBuf(&bufs, mem.sliceAsBytes(ip.src_hash_deps.values())); addBuf(&bufs, mem.sliceAsBytes(ip.nav_val_deps.keys())); addBuf(&bufs, mem.sliceAsBytes(ip.nav_val_deps.values())); + addBuf(&bufs, mem.sliceAsBytes(ip.nav_ty_deps.keys())); + addBuf(&bufs, mem.sliceAsBytes(ip.nav_ty_deps.values())); addBuf(&bufs, mem.sliceAsBytes(ip.namespace_deps.keys())); addBuf(&bufs, mem.sliceAsBytes(ip.namespace_deps.values())); addBuf(&bufs, mem.sliceAsBytes(ip.namespace_name_deps.keys())); @@ -3145,7 +3149,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { const file_index = switch (anal_unit.unwrap()) { .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index.resolveFile(ip), - .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index.resolveFile(ip), + .nav_val, .nav_ty => |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), }; @@ -3380,7 +3384,7 @@ pub fn addModuleErrorMsg( defer gpa.free(rt_file_path); const name = switch (ref.referencer.unwrap()) { .@"comptime" => "comptime", - .nav_val => |nav| ip.getNav(nav).name.toSlice(ip), + .nav_val, .nav_ty => |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), }; @@ -3647,6 +3651,7 @@ fn performAllTheWorkInner( try comp.queueJob(switch (outdated.unwrap()) { .func => |f| .{ .analyze_func = f }, .@"comptime", + .nav_ty, .nav_val, .type, => .{ .analyze_comptime_unit = outdated }, @@ -3679,7 +3684,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre return; } } - assert(nav.status == .resolved); + assert(nav.status == .fully_resolved); comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index }); }, .codegen_func => |func| { @@ -3709,6 +3714,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) { .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu), + .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav), .nav_val => |nav| pt.ensureNavValUpToDate(nav), .type => |ty| if (pt.ensureTypeUpToDate(ty)) |_| {} else |err| err, .func => unreachable, @@ -3734,7 +3740,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre // 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).status.resolved.val); + try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav).status.fully_resolved.val); } }, .resolve_type_fully => |ty| { diff --git a/src/InternPool.zig b/src/InternPool.zig index 41019ea9d989..64cf95c7b24f 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -34,6 +34,9 @@ src_hash_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index), /// Dependencies on the value of a Nav. /// Value is index into `dep_entries` of the first dependency on this Nav value. nav_val_deps: std.AutoArrayHashMapUnmanaged(Nav.Index, DepEntry.Index), +/// Dependencies on the type of a Nav. +/// Value is index into `dep_entries` of the first dependency on this Nav value. +nav_ty_deps: std.AutoArrayHashMapUnmanaged(Nav.Index, DepEntry.Index), /// Dependencies on an interned value, either: /// * a runtime function (invalidated when its IES changes) /// * a container type requiring resolution (invalidated when the type must be recreated at a new index) @@ -80,6 +83,7 @@ pub const empty: InternPool = .{ .file_deps = .empty, .src_hash_deps = .empty, .nav_val_deps = .empty, + .nav_ty_deps = .empty, .interned_deps = .empty, .namespace_deps = .empty, .namespace_name_deps = .empty, @@ -371,6 +375,7 @@ pub const AnalUnit = packed struct(u64) { pub const Kind = enum(u32) { @"comptime", nav_val, + nav_ty, type, func, }; @@ -380,6 +385,8 @@ pub const AnalUnit = packed struct(u64) { @"comptime": ComptimeUnit.Id, /// This `AnalUnit` resolves the value of the given `Nav`. nav_val: Nav.Index, + /// This `AnalUnit` resolves the type of the given `Nav`. + nav_ty: 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, @@ -483,8 +490,20 @@ pub const Nav = struct { status: union(enum) { /// This `Nav` is pending semantic analysis. unresolved, + /// The type of this `Nav` is resolved; the value is queued for resolution. + type_resolved: struct { + type: InternPool.Index, + alignment: Alignment, + @"linksection": OptionalNullTerminatedString, + @"addrspace": std.builtin.AddressSpace, + is_const: bool, + is_threadlocal: bool, + /// This field is whether this `Nav` is a literal `extern` definition. + /// It does *not* tell you whether this might alias an extern fn (see #21027). + is_extern_decl: bool, + }, /// The value of this `Nav` is resolved. - resolved: struct { + fully_resolved: struct { val: InternPool.Index, alignment: Alignment, @"linksection": OptionalNullTerminatedString, @@ -492,14 +511,81 @@ pub const Nav = struct { }, }, - /// Asserts that `status == .resolved`. + /// Asserts that `status != .unresolved`. pub fn typeOf(nav: Nav, ip: *const InternPool) InternPool.Index { - return ip.typeOf(nav.status.resolved.val); + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.type, + .fully_resolved => |r| ip.typeOf(r.val), + }; } - /// Asserts that `status == .resolved`. - pub fn isExtern(nav: Nav, ip: *const InternPool) bool { - return ip.indexToKey(nav.status.resolved.val) == .@"extern"; + /// Always returns `null` for `status == .type_resolved`. This function is inteded + /// to be used by code generation, since semantic analysis will ensure that any `Nav` + /// which is potentially `extern` is fully resolved. + /// Asserts that `status != .unresolved`. + pub fn getExtern(nav: Nav, ip: *const InternPool) ?Key.Extern { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => null, + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .@"extern" => |e| e, + else => null, + }, + }; + } + + /// Asserts that `status != .unresolved`. + pub fn getAddrspace(nav: Nav) std.builtin.AddressSpace { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.@"addrspace", + .fully_resolved => |r| r.@"addrspace", + }; + } + + /// Asserts that `status != .unresolved`. + pub fn getAlignment(nav: Nav) Alignment { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.alignment, + .fully_resolved => |r| r.alignment, + }; + } + + /// Asserts that `status != .unresolved`. + pub fn isThreadlocal(nav: Nav, ip: *const InternPool) bool { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.is_threadlocal, + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .@"extern" => |e| e.is_threadlocal, + .variable => |v| v.is_threadlocal, + else => false, + }, + }; + } + + /// If this returns `true`, then a pointer to this `Nav` might actually be encoded as a pointer + /// to some other `Nav` due to an extern definition or extern alias (see #21027). + /// This query is valid on `Nav`s for whom only the type is resolved. + /// Asserts that `status != .unresolved`. + pub fn isExternOrFn(nav: Nav, ip: *const InternPool) bool { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| { + if (r.is_extern_decl) return true; + const tag = ip.zigTypeTagOrPoison(r.type) catch unreachable; + if (tag == .@"fn") return true; + return false; + }, + .fully_resolved => |r| { + if (ip.indexToKey(r.val) == .@"extern") return true; + const tag = ip.zigTypeTagOrPoison(ip.typeOf(r.val)) catch unreachable; + if (tag == .@"fn") return true; + return false; + }, + }; } /// Get the ZIR instruction corresponding to this `Nav`, used to resolve source locations. @@ -509,7 +595,7 @@ pub const Nav = struct { return a.zir_index; } // A `Nav` which does not undergo analysis always has a resolved value. - return switch (ip.indexToKey(nav.status.resolved.val)) { + return switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => |func| { // Since `analysis` was not populated, this must be an instantiation. // Go up to the generic owner and consult *its* `analysis` field. @@ -567,19 +653,22 @@ pub const Nav = struct { // 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`. + /// Populated only if `bits.status != .unresolved`. + type_or_val: InternPool.Index, + /// Populated only if `bits.status != .unresolved`. @"linksection": OptionalNullTerminatedString, bits: Bits, const Bits = packed struct(u16) { - status: enum(u1) { unresolved, resolved }, - /// Populated only if `bits.status == .resolved`. + status: enum(u2) { unresolved, type_resolved, fully_resolved, type_resolved_extern_decl }, + /// Populated only if `bits.status != .unresolved`. alignment: Alignment, - /// Populated only if `bits.status == .resolved`. + /// Populated only if `bits.status != .unresolved`. @"addrspace": std.builtin.AddressSpace, - _: u3 = 0, + /// Populated only if `bits.status == .type_resolved`. + is_const: bool, + /// Populated only if `bits.status == .type_resolved`. + is_threadlocal: bool, is_usingnamespace: bool, }; @@ -597,8 +686,17 @@ pub const Nav = struct { .is_usingnamespace = repr.bits.is_usingnamespace, .status = switch (repr.bits.status) { .unresolved => .unresolved, - .resolved => .{ .resolved = .{ - .val = repr.val, + .type_resolved, .type_resolved_extern_decl => .{ .type_resolved = .{ + .type = repr.type_or_val, + .alignment = repr.bits.alignment, + .@"linksection" = repr.@"linksection", + .@"addrspace" = repr.bits.@"addrspace", + .is_const = repr.bits.is_const, + .is_threadlocal = repr.bits.is_threadlocal, + .is_extern_decl = repr.bits.status == .type_resolved_extern_decl, + } }, + .fully_resolved => .{ .fully_resolved = .{ + .val = repr.type_or_val, .alignment = repr.bits.alignment, .@"linksection" = repr.@"linksection", .@"addrspace" = repr.bits.@"addrspace", @@ -616,13 +714,15 @@ pub const Nav = struct { .fqn = nav.fqn, .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) { + .type_or_val = switch (nav.status) { .unresolved => .none, - .resolved => |r| r.val, + .type_resolved => |r| r.type, + .fully_resolved => |r| r.val, }, .@"linksection" = switch (nav.status) { .unresolved => .none, - .resolved => |r| r.@"linksection", + .type_resolved => |r| r.@"linksection", + .fully_resolved => |r| r.@"linksection", }, .bits = switch (nav.status) { .unresolved => .{ @@ -630,12 +730,24 @@ pub const Nav = struct { .alignment = .none, .@"addrspace" = .generic, .is_usingnamespace = nav.is_usingnamespace, + .is_const = false, + .is_threadlocal = false, + }, + .type_resolved => |r| .{ + .status = if (r.is_extern_decl) .type_resolved_extern_decl else .type_resolved, + .alignment = r.alignment, + .@"addrspace" = r.@"addrspace", + .is_usingnamespace = nav.is_usingnamespace, + .is_const = r.is_const, + .is_threadlocal = r.is_threadlocal, }, - .resolved => |r| .{ - .status = .resolved, + .fully_resolved => |r| .{ + .status = .fully_resolved, .alignment = r.alignment, .@"addrspace" = r.@"addrspace", .is_usingnamespace = nav.is_usingnamespace, + .is_const = false, + .is_threadlocal = false, }, }, }; @@ -646,6 +758,7 @@ pub const Dependee = union(enum) { file: FileIndex, src_hash: TrackedInst.Index, nav_val: Nav.Index, + nav_ty: Nav.Index, interned: Index, namespace: TrackedInst.Index, namespace_name: NamespaceNameKey, @@ -695,6 +808,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI .file => |x| ip.file_deps.get(x), .src_hash => |x| ip.src_hash_deps.get(x), .nav_val => |x| ip.nav_val_deps.get(x), + .nav_ty => |x| ip.nav_ty_deps.get(x), .interned => |x| ip.interned_deps.get(x), .namespace => |x| ip.namespace_deps.get(x), .namespace_name => |x| ip.namespace_name_deps.get(x), @@ -732,6 +846,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend .file => ip.file_deps, .src_hash => ip.src_hash_deps, .nav_val => ip.nav_val_deps, + .nav_ty => ip.nav_ty_deps, .interned => ip.interned_deps, .namespace => ip.namespace_deps, .namespace_name => ip.namespace_name_deps, @@ -2079,36 +2194,36 @@ pub const Key = union(enum) { return @atomicLoad(FuncAnalysis, func.analysisPtr(ip), .unordered); } - pub fn setAnalysisState(func: Func, ip: *InternPool, state: FuncAnalysis.State) void { + pub fn setCallsOrAwaitsErrorableFn(func: Func, ip: *InternPool, value: bool) void { const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex; extra_mutex.lock(); defer extra_mutex.unlock(); const analysis_ptr = func.analysisPtr(ip); var analysis = analysis_ptr.*; - analysis.state = state; + analysis.calls_or_awaits_errorable_fn = value; @atomicStore(FuncAnalysis, analysis_ptr, analysis, .release); } - pub fn setCallsOrAwaitsErrorableFn(func: Func, ip: *InternPool, value: bool) void { + pub fn setBranchHint(func: Func, ip: *InternPool, hint: std.builtin.BranchHint) void { const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex; extra_mutex.lock(); defer extra_mutex.unlock(); const analysis_ptr = func.analysisPtr(ip); var analysis = analysis_ptr.*; - analysis.calls_or_awaits_errorable_fn = value; + analysis.branch_hint = hint; @atomicStore(FuncAnalysis, analysis_ptr, analysis, .release); } - pub fn setBranchHint(func: Func, ip: *InternPool, hint: std.builtin.BranchHint) void { + pub fn setAnalyzed(func: Func, ip: *InternPool) void { const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex; extra_mutex.lock(); defer extra_mutex.unlock(); const analysis_ptr = func.analysisPtr(ip); var analysis = analysis_ptr.*; - analysis.branch_hint = hint; + analysis.is_analyzed = true; @atomicStore(FuncAnalysis, analysis_ptr, analysis, .release); } @@ -5755,7 +5870,7 @@ pub const Tag = enum(u8) { /// equality or hashing, except for `inferred_error_set` which is considered /// to be part of the type of the function. pub const FuncAnalysis = packed struct(u32) { - state: State, + is_analyzed: bool, branch_hint: std.builtin.BranchHint, is_noinline: bool, calls_or_awaits_errorable_fn: bool, @@ -5763,20 +5878,7 @@ pub const FuncAnalysis = packed struct(u32) { inferred_error_set: bool, disable_instrumentation: bool, - _: u23 = 0, - - pub const State = enum(u2) { - /// The runtime function has never been referenced. - /// As such, it has never been analyzed, nor is it queued for analysis. - unreferenced, - /// The runtime function has been referenced, but has not yet been analyzed. - /// Its semantic analysis is queued. - queued, - /// The runtime function has been (or is currently being) semantically analyzed. - /// To know if analysis succeeded, consult `zcu.[transitive_]failed_analysis`. - /// To know if analysis is up-to-date, consult `zcu.[potentially_]outdated`. - analyzed, - }; + _: u24 = 0, }; pub const Bytes = struct { @@ -6419,6 +6521,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.file_deps.deinit(gpa); ip.src_hash_deps.deinit(gpa); ip.nav_val_deps.deinit(gpa); + ip.nav_ty_deps.deinit(gpa); ip.interned_deps.deinit(gpa); ip.namespace_deps.deinit(gpa); ip.namespace_name_deps.deinit(gpa); @@ -6875,8 +6978,8 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .is_threadlocal = extra.flags.is_threadlocal, .is_weak_linkage = extra.flags.is_weak_linkage, .is_dll_import = extra.flags.is_dll_import, - .alignment = nav.status.resolved.alignment, - .@"addrspace" = nav.status.resolved.@"addrspace", + .alignment = nav.status.fully_resolved.alignment, + .@"addrspace" = nav.status.fully_resolved.@"addrspace", .zir_index = extra.zir_index, .owner_nav = extra.owner_nav, } }; @@ -8794,7 +8897,7 @@ pub fn getFuncDecl( const func_decl_extra_index = addExtraAssumeCapacity(extra, Tag.FuncDecl{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = key.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -8903,7 +9006,7 @@ pub fn getFuncDeclIes( const func_decl_extra_index = addExtraAssumeCapacity(extra, Tag.FuncDecl{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = key.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -9099,7 +9202,7 @@ pub fn getFuncInstance( const func_extra_index = addExtraAssumeCapacity(extra, Tag.FuncInstance{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = arg.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -9197,7 +9300,7 @@ pub fn getFuncInstanceIes( const func_extra_index = addExtraAssumeCapacity(extra, Tag.FuncInstance{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = arg.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -9316,9 +9419,9 @@ fn finishFuncInstance( .name = nav_name, .fqn = try ip.namespacePtr(fn_namespace).internFullyQualifiedName(ip, gpa, tid, nav_name), .val = func_index, - .alignment = fn_owner_nav.status.resolved.alignment, - .@"linksection" = fn_owner_nav.status.resolved.@"linksection", - .@"addrspace" = fn_owner_nav.status.resolved.@"addrspace", + .alignment = fn_owner_nav.status.fully_resolved.alignment, + .@"linksection" = fn_owner_nav.status.fully_resolved.@"linksection", + .@"addrspace" = fn_owner_nav.status.fully_resolved.@"addrspace", }); // Populate the owner_nav field which was left undefined until now. @@ -11030,7 +11133,7 @@ pub fn createNav( .name = opts.name, .fqn = opts.fqn, .analysis = null, - .status = .{ .resolved = .{ + .status = .{ .fully_resolved = .{ .val = opts.val, .alignment = opts.alignment, .@"linksection" = opts.@"linksection", @@ -11077,6 +11180,50 @@ pub fn createDeclNav( return nav; } +/// Resolve the type of a `Nav` with an analysis owner. +/// If its status is already `resolved`, the old value is discarded. +pub fn resolveNavType( + ip: *InternPool, + nav: Nav.Index, + resolved: struct { + type: InternPool.Index, + alignment: Alignment, + @"linksection": OptionalNullTerminatedString, + @"addrspace": std.builtin.AddressSpace, + is_const: bool, + is_threadlocal: bool, + is_extern_decl: bool, + }, +) void { + const unwrapped = nav.unwrap(ip); + + const local = ip.getLocal(unwrapped.tid); + local.mutate.extra.mutex.lock(); + defer local.mutate.extra.mutex.unlock(); + + const navs = local.shared.navs.view(); + + const nav_analysis_namespace = navs.items(.analysis_namespace); + const nav_analysis_zir_index = navs.items(.analysis_zir_index); + const nav_types = navs.items(.type_or_val); + const nav_linksections = navs.items(.@"linksection"); + const nav_bits = navs.items(.bits); + + assert(nav_analysis_namespace[unwrapped.index] != .none); + assert(nav_analysis_zir_index[unwrapped.index] != .none); + + @atomicStore(InternPool.Index, &nav_types[unwrapped.index], resolved.type, .release); + @atomicStore(OptionalNullTerminatedString, &nav_linksections[unwrapped.index], resolved.@"linksection", .release); + + var bits = nav_bits[unwrapped.index]; + bits.status = if (resolved.is_extern_decl) .type_resolved_extern_decl else .type_resolved; + bits.alignment = resolved.alignment; + bits.@"addrspace" = resolved.@"addrspace"; + bits.is_const = resolved.is_const; + bits.is_threadlocal = resolved.is_threadlocal; + @atomicStore(Nav.Repr.Bits, &nav_bits[unwrapped.index], bits, .release); +} + /// Resolve the value of a `Nav` with an analysis owner. /// If its status is already `resolved`, the old value is discarded. pub fn resolveNavValue( @@ -11099,7 +11246,7 @@ pub fn resolveNavValue( 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_vals = navs.items(.type_or_val); const nav_linksections = navs.items(.@"linksection"); const nav_bits = navs.items(.bits); @@ -11110,7 +11257,7 @@ pub fn resolveNavValue( @atomicStore(OptionalNullTerminatedString, &nav_linksections[unwrapped.index], resolved.@"linksection", .release); var bits = nav_bits[unwrapped.index]; - bits.status = .resolved; + bits.status = .fully_resolved; bits.alignment = resolved.alignment; bits.@"addrspace" = resolved.@"addrspace"; @atomicStore(Nav.Repr.Bits, &nav_bits[unwrapped.index], bits, .release); diff --git a/src/Sema.zig b/src/Sema.zig index a41762f530ac..ccc9f63a5699 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6495,9 +6495,9 @@ pub fn analyzeExport( if (options.linkage == .internal) return; - try sema.ensureNavResolved(src, orig_nav_index); + try sema.ensureNavResolved(src, orig_nav_index, .fully); - const exported_nav_index = switch (ip.indexToKey(ip.getNav(orig_nav_index).status.resolved.val)) { + const exported_nav_index = switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) { .variable => |v| v.owner_nav, .@"extern" => |e| e.owner_nav, .func => |f| f.owner_nav, @@ -6520,7 +6520,7 @@ pub fn analyzeExport( } // TODO: some backends might support re-exporting extern decls - if (exported_nav.isExtern(ip)) { + if (exported_nav.getExtern(ip) != null) { return sema.fail(block, src, "export target cannot be extern", .{}); } @@ -6542,6 +6542,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void { .func => |func| func, .@"comptime", .nav_val, + .nav_ty, .type, => return, // does nothing outside a function }; @@ -6854,8 +6855,8 @@ fn lookupInNamespace( } for (usingnamespaces.items) |sub_ns_nav| { - try sema.ensureNavResolved(src, sub_ns_nav); - const sub_ns_ty = Type.fromInterned(ip.getNav(sub_ns_nav).status.resolved.val); + try sema.ensureNavResolved(src, sub_ns_nav, .fully); + const sub_ns_ty = Type.fromInterned(ip.getNav(sub_ns_nav).status.fully_resolved.val); const sub_ns = zcu.namespacePtr(sub_ns_ty.getNamespaceIndex(zcu)); try checked_namespaces.put(gpa, sub_ns, {}); } @@ -6865,7 +6866,7 @@ fn lookupInNamespace( ignore_self: { const skip_nav = switch (sema.owner.unwrap()) { .@"comptime", .type, .func => break :ignore_self, - .nav_val => |nav| nav, + .nav_ty, .nav_val => |nav| nav, }; var i: usize = 0; while (i < candidates.items.len) { @@ -7125,7 +7126,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()) { - .@"comptime", .type, .nav_val => input_is_error = false, + .@"comptime", .type, .nav_ty, .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; @@ -7686,12 +7687,13 @@ fn analyzeCall( .ptr => |ptr| blk: { switch (ptr.base_addr) { .nav => |nav_index| if (ptr.byte_offset == 0) { + try sema.ensureNavResolved(call_src, nav_index, .fully); const nav = ip.getNav(nav_index); - if (nav.isExtern(ip)) + if (nav.getExtern(ip) != null) return sema.fail(block, call_src, "{s} call of extern function pointer", .{ if (is_comptime_call) "comptime" else "inline", }); - break :blk nav.status.resolved.val; + break :blk nav.status.fully_resolved.val; }, else => {}, } @@ -8007,7 +8009,7 @@ fn analyzeCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .@"comptime", .nav_val, .type => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -8046,7 +8048,10 @@ fn analyzeCall( switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { .func => break :skip_safety, .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { - .nav => |nav| if (!ip.getNav(nav).isExtern(ip)) break :skip_safety, + .nav => |nav| { + try sema.ensureNavResolved(call_src, nav, .fully); + if (ip.getNav(nav).getExtern(ip) == null) break :skip_safety; + }, else => {}, }, else => {}, @@ -8243,7 +8248,7 @@ fn instantiateGenericCall( }); const generic_owner = switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { .func => func_val.toIntern(), - .ptr => |ptr| ip.getNav(ptr.base_addr.nav).status.resolved.val, + .ptr => |ptr| ip.getNav(ptr.base_addr.nav).status.fully_resolved.val, else => unreachable, }; const generic_owner_func = zcu.intern_pool.indexToKey(generic_owner).func; @@ -8471,7 +8476,7 @@ fn instantiateGenericCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .@"comptime", .nav_val, .type => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -19311,8 +19316,8 @@ fn typeInfoNamespaceDecls( 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); + try sema.ensureNavResolved(src, nav, .fully); + const namespace_ty = Type.fromInterned(ip.getNav(nav).status.fully_resolved.val); try sema.typeInfoNamespaceDecls(block, src, namespace_ty.getNamespaceIndex(zcu).toOptional(), declaration_ty, decl_vals, seen_namespaces); } } @@ -21602,7 +21607,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); }, - .@"comptime", .nav_val, .type => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, } return Air.internedToRef(try pt.intern(.{ .opt = .{ .ty = opt_ptr_stack_trace_ty.toIntern(), @@ -27086,7 +27091,7 @@ fn zirBuiltinExtern( .zir_index = switch (sema.owner.unwrap()) { .@"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, + .nav_ty, .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: { @@ -27741,7 +27746,7 @@ fn preparePanicId(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.Pan error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable, error.OutOfMemory => |e| return e, }).?; - try sema.ensureNavResolved(src, msg_nav_index); + try sema.ensureNavResolved(src, msg_nav_index, .fully); zcu.panic_messages[@intFromEnum(panic_id)] = msg_nav_index.toOptional(); return msg_nav_index; } @@ -32648,21 +32653,29 @@ fn addTypeReferenceEntry( try zcu.addTypeReference(sema.owner, referenced_type, src); } -pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) CompileError!void { +pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index, kind: enum { type, fully }) CompileError!void { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); if (nav.analysis == null) { - assert(nav.status == .resolved); + assert(nav.status == .fully_resolved); return; } + try sema.declareDependency(switch (kind) { + .type => .{ .nav_ty = nav_index }, + .fully => .{ .nav_val = nav_index }, + }); + // Note that even if `nav.status == .resolved`, we must still trigger `ensureNavValUpToDate` // to make sure the value is up-to-date on incremental updates. - const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_index }); + const anal_unit: AnalUnit = .wrap(switch (kind) { + .type => .{ .nav_ty = nav_index }, + .fully => .{ .nav_val = nav_index }, + }); try sema.addReferenceEntry(src, anal_unit); if (zcu.analysis_in_progress.contains(anal_unit)) { @@ -32672,7 +32685,13 @@ pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav }, "dependency loop detected", .{})); } - return pt.ensureNavValUpToDate(nav_index); + switch (kind) { + .type => { + try zcu.ensureNavValAnalysisQueued(nav_index); + return pt.ensureNavTypeUpToDate(nav_index); + }, + .fully => return pt.ensureNavValUpToDate(nav_index), + } } fn optRefValue(sema: *Sema, opt_val: ?Value) !Value { @@ -32691,36 +32710,44 @@ fn analyzeNavRef(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) return sema.analyzeNavRefInner(src, nav_index, true); } -/// Analyze a reference to the `Nav` at the given index. Ensures the underlying `Nav` is analyzed, but -/// only triggers analysis for function bodies if `analyze_fn_body` is true. If it's possible for a -/// decl_ref to end up in runtime code, the function body must be analyzed: `analyzeNavRef` wraps -/// this function with `analyze_fn_body` set to true. -fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.Nav.Index, analyze_fn_body: bool) CompileError!Air.Inst.Ref { +/// Analyze a reference to the `Nav` at the given index. Ensures the underlying `Nav` is analyzed. +/// If this pointer will be used directly, `is_ref` must be `true`. +/// If this pointer will be immediately loaded (i.e. a `decl_val` instruction), `is_ref` must be `false`. +fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.Nav.Index, is_ref: bool) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; - // TODO: if this is a `decl_ref` of a non-variable Nav, only depend on Nav type - try sema.declareDependency(.{ .nav_val = orig_nav_index }); - try sema.ensureNavResolved(src, orig_nav_index); + try sema.ensureNavResolved(src, orig_nav_index, if (is_ref) .type else .fully); - const nav_val = zcu.navValue(orig_nav_index); - const nav_index, const is_const = switch (ip.indexToKey(nav_val.toIntern())) { - .variable => |v| .{ v.owner_nav, false }, - .func => |f| .{ f.owner_nav, true }, - .@"extern" => |e| .{ e.owner_nav, e.is_const }, - else => .{ orig_nav_index, true }, + const nav_index = nav: { + if (ip.getNav(orig_nav_index).isExternOrFn(ip)) { + // Getting a pointer to this `Nav` might mean we actually get a pointer to something else! + // We need to resolve the value to know for sure. + if (is_ref) try sema.ensureNavResolved(src, orig_nav_index, .fully); + switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) { + .func => |f| break :nav f.owner_nav, + .@"extern" => |e| break :nav e.owner_nav, + else => {}, + } + } + break :nav orig_nav_index; + }; + + const ty, const alignment, const @"addrspace", const is_const = switch (ip.getNav(nav_index).status) { + .unresolved => unreachable, + .type_resolved => |r| .{ r.type, r.alignment, r.@"addrspace", r.is_const }, + .fully_resolved => |r| .{ ip.typeOf(r.val), r.alignment, r.@"addrspace", zcu.navValIsConst(r.val) }, }; - const nav_info = ip.getNav(nav_index).status.resolved; const ptr_ty = try pt.ptrTypeSema(.{ - .child = nav_val.typeOf(zcu).toIntern(), + .child = ty, .flags = .{ - .alignment = nav_info.alignment, + .alignment = alignment, .is_const = is_const, - .address_space = nav_info.@"addrspace", + .address_space = @"addrspace", }, }); - if (analyze_fn_body) { + if (is_ref) { try sema.maybeQueueFuncBodyAnalysis(src, nav_index); } return Air.internedToRef((try pt.intern(.{ .ptr = .{ @@ -32731,11 +32758,22 @@ fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.N } fn maybeQueueFuncBodyAnalysis(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) !void { - const zcu = sema.pt.zcu; + const pt = sema.pt; + const zcu = pt.zcu; const ip = &zcu.intern_pool; + + // To avoid forcing too much resolution, let's first resolve the type, and check if it's a function. + // If it is, we can resolve the *value*, and queue analysis as needed. + + try sema.ensureNavResolved(src, nav_index, .type); + const nav_ty: Type = .fromInterned(ip.getNav(nav_index).typeOf(ip)); + if (nav_ty.zigTypeTag(zcu) != .@"fn") return; + if (!try nav_ty.fnHasRuntimeBitsSema(pt)) return; + + try sema.ensureNavResolved(src, nav_index, .fully); const nav_val = zcu.navValue(nav_index); if (!ip.isFuncBody(nav_val.toIntern())) return; - if (!try nav_val.typeOf(zcu).fnHasRuntimeBitsSema(sema.pt)) return; + try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = nav_val.toIntern() })); try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern()); } @@ -38450,11 +38488,16 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { // of a type and they use `@This()`. This dependency would be unnecessary, and in fact would // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve // the loop. + // Note that this also disallows a `nav_val` switch (sema.owner.unwrap()) { .nav_val => |this_nav| switch (dependee) { .nav_val => |other_nav| if (this_nav == other_nav) return, else => {}, }, + .nav_ty => |this_nav| switch (dependee) { + .nav_ty => |other_nav| if (this_nav == other_nav) return, + else => {}, + }, else => {}, } @@ -38873,8 +38916,8 @@ fn getBuiltinInnerType( const nav = opt_nav orelse return sema.fail(block, src, "std.builtin.{s} missing {s}", .{ compile_error_parent_name, inner_name, }); - try sema.ensureNavResolved(src, nav); - const val = Value.fromInterned(ip.getNav(nav).status.resolved.val); + try sema.ensureNavResolved(src, nav, .fully); + const val = Value.fromInterned(ip.getNav(nav).status.fully_resolved.val); const ty = val.toType(); try ty.resolveFully(pt); return ty; @@ -38886,5 +38929,73 @@ fn getBuiltin(sema: *Sema, name: []const u8) SemaError!Air.Inst.Ref { const ip = &zcu.intern_pool; const nav = try pt.getBuiltinNav(name); try pt.ensureNavValUpToDate(nav); - return Air.internedToRef(ip.getNav(nav).status.resolved.val); + return Air.internedToRef(ip.getNav(nav).status.fully_resolved.val); +} + +pub const NavPtrModifiers = struct { + alignment: Alignment, + @"linksection": InternPool.OptionalNullTerminatedString, + @"addrspace": std.builtin.AddressSpace, +}; + +pub fn resolveNavPtrModifiers( + sema: *Sema, + block: *Block, + zir_decl: Zir.Inst.Declaration.Unwrapped, + decl_inst: Zir.Inst.Index, + nav_ty: Type, +) CompileError!NavPtrModifiers { + const pt = sema.pt; + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + 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 alignment: InternPool.Alignment = a: { + const align_body = zir_decl.align_body orelse break :a .none; + const align_ref = try sema.resolveInlineBody(block, align_body, decl_inst); + break :a try sema.analyzeAsAlign(block, align_src, align_ref); + }; + + 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, decl_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 @"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, decl_inst); + break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx); + }; + + return .{ + .alignment = alignment, + .@"linksection" = @"linksection", + .@"addrspace" = @"addrspace", + }; } diff --git a/src/Sema/comptime_ptr_access.zig b/src/Sema/comptime_ptr_access.zig index 10e81d7a9efc..ceddb9457df9 100644 --- a/src/Sema/comptime_ptr_access.zig +++ b/src/Sema/comptime_ptr_access.zig @@ -219,9 +219,8 @@ fn loadComptimePtrInner( const base_val: MutableValue = switch (ptr.base_addr) { .nav => |nav| val: { - try sema.declareDependency(.{ .nav_val = nav }); - try sema.ensureNavResolved(src, nav); - const val = ip.getNav(nav).status.resolved.val; + try sema.ensureNavResolved(src, nav, .fully); + const val = ip.getNav(nav).status.fully_resolved.val; switch (ip.indexToKey(val)) { .variable => return .runtime_load, // We let `.@"extern"` through here if it's a function. diff --git a/src/Value.zig b/src/Value.zig index 59fbdf67d5bc..25f5b50166d0 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -1343,7 +1343,12 @@ pub fn isLazySize(val: Value, zcu: *Zcu) bool { pub fn isPtrRuntimeValue(val: Value, zcu: *Zcu) bool { const ip = &zcu.intern_pool; const nav = ip.getBackingNav(val.toIntern()).unwrap() orelse return false; - return switch (ip.indexToKey(ip.getNav(nav).status.resolved.val)) { + const nav_val = switch (ip.getNav(nav).status) { + .unresolved => unreachable, + .type_resolved => |r| return r.is_threadlocal, + .fully_resolved => |r| r.val, + }; + return switch (ip.indexToKey(nav_val)) { .@"extern" => |e| e.is_threadlocal or e.is_dll_import, .variable => |v| v.is_threadlocal, else => false, diff --git a/src/Zcu.zig b/src/Zcu.zig index d374a0aa0c09..8b3125039ae2 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -170,6 +170,9 @@ outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty, /// it as outdated. retryable_failures: std.ArrayListUnmanaged(AnalUnit) = .empty, +func_body_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .empty, +nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty, + /// These are the modules which we initially queue for analysis in `Compilation.update`. /// `resolveReferences` will use these as the root of its reachability traversal. analysis_roots: std.BoundedArray(*Package.Module, 3) = .{}, @@ -282,7 +285,11 @@ pub const Exported = union(enum) { pub fn getAlign(exported: Exported, zcu: *Zcu) Alignment { return switch (exported) { - .nav => |nav| zcu.intern_pool.getNav(nav).status.resolved.alignment, + .nav => |nav| switch (zcu.intern_pool.getNav(nav).status) { + .unresolved => unreachable, + .type_resolved => |r| r.alignment, + .fully_resolved => |r| r.alignment, + }, .uav => .none, }; } @@ -2241,6 +2248,9 @@ pub fn deinit(zcu: *Zcu) void { zcu.outdated_ready.deinit(gpa); zcu.retryable_failures.deinit(gpa); + zcu.func_body_analysis_queued.deinit(gpa); + zcu.nav_val_analysis_queued.deinit(gpa); + zcu.test_functions.deinit(gpa); for (zcu.global_assembly.values()) |s| { @@ -2441,6 +2451,7 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { switch (depender.unwrap()) { .@"comptime" => {}, .nav_val => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }), + .nav_ty => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_ty = nav }), .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }), .func => |func| try zcu.markPoDependeeUpToDate(.{ .interned = func }), } @@ -2453,7 +2464,8 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni const ip = &zcu.intern_pool; const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) { .@"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 + .nav_val => |nav| .{ .nav_val = nav }, + .nav_ty => |nav| .{ .nav_ty = nav }, .type => |ty| .{ .interned = ty }, .func => |func_index| .{ .interned = func_index }, // IES }; @@ -2540,6 +2552,7 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { .@"comptime" => continue, // a `comptime` block can't even be depended on so it is a terrible choice .type => |ty| .{ .interned = ty }, .nav_val => |nav| .{ .nav_val = nav }, + .nav_ty => |nav| .{ .nav_ty = nav }, }); while (it.next()) |_| n += 1; @@ -2780,14 +2793,39 @@ pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func_index: InternPool.Index) !vo const ip = &zcu.intern_pool; const func = zcu.funcInfo(func_index); - switch (func.analysisUnordered(ip).state) { - .unreferenced => {}, // We're the first reference! - .queued => return, // Analysis is already queued. - .analyzed => return, // Analysis is complete; if it's out-of-date, it'll be re-analyzed later this update. + if (zcu.func_body_analysis_queued.contains(func_index)) return; + + if (func.analysisUnordered(ip).is_analyzed) { + if (!zcu.outdated.contains(.wrap(.{ .func = func_index })) and + !zcu.potentially_outdated.contains(.wrap(.{ .func = func_index }))) + { + // This function has been analyzed before and is definitely up-to-date. + return; + } } + try zcu.func_body_analysis_queued.ensureUnusedCapacity(zcu.gpa, 1); try zcu.comp.queueJob(.{ .analyze_func = func_index }); - func.setAnalysisState(ip, .queued); + zcu.func_body_analysis_queued.putAssumeCapacityNoClobber(func_index, {}); +} + +pub fn ensureNavValAnalysisQueued(zcu: *Zcu, nav_id: InternPool.Nav.Index) !void { + const ip = &zcu.intern_pool; + + if (zcu.nav_val_analysis_queued.contains(nav_id)) return; + + if (ip.getNav(nav_id).status == .fully_resolved) { + if (!zcu.outdated.contains(.wrap(.{ .nav_val = nav_id })) and + !zcu.potentially_outdated.contains(.wrap(.{ .nav_val = nav_id }))) + { + // This `Nav` has been analyzed before and is definitely up-to-date. + return; + } + } + + try zcu.nav_val_analysis_queued.ensureUnusedCapacity(zcu.gpa, 1); + try zcu.comp.queueJob(.{ .analyze_comptime_unit = .wrap(.{ .nav_val = nav_id }) }); + zcu.nav_val_analysis_queued.putAssumeCapacityNoClobber(nav_id, {}); } pub const ImportFileResult = struct { @@ -3424,6 +3462,17 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv const unit = kv.key; try result.putNoClobber(gpa, unit, kv.value); + // `nav_val` and `nav_ty` reference each other *implicitly* to save memory. + queue_paired: { + const other: AnalUnit = .wrap(switch (unit.unwrap()) { + .nav_val => |n| .{ .nav_ty = n }, + .nav_ty => |n| .{ .nav_val = n }, + .@"comptime", .type, .func => break :queue_paired, + }); + if (result.contains(other)) break :queue_paired; + try unit_queue.put(gpa, other, kv.value); // same reference location + } + log.debug("handle unit '{}'", .{zcu.fmtAnalUnit(unit)}); if (zcu.reference_table.get(unit)) |first_ref_idx| { @@ -3513,7 +3562,7 @@ pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 { } pub fn navValue(zcu: *const Zcu, nav_index: InternPool.Nav.Index) Value { - return Value.fromInterned(zcu.intern_pool.getNav(nav_index).status.resolved.val); + return Value.fromInterned(zcu.intern_pool.getNav(nav_index).status.fully_resolved.val); } pub fn navFileScopeIndex(zcu: *Zcu, nav: InternPool.Nav.Index) File.Index { @@ -3547,6 +3596,7 @@ fn formatAnalUnit(data: struct { unit: AnalUnit, zcu: *Zcu }, comptime fmt: []co } }, .nav_val => |nav| return writer.print("nav_val('{}')", .{ip.getNav(nav).fqn.fmt(ip)}), + .nav_ty => |nav| return writer.print("nav_ty('{}')", .{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; @@ -3572,7 +3622,11 @@ fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, com }, .nav_val => |nav| { const fqn = ip.getNav(nav).fqn; - return writer.print("nav('{}')", .{fqn.fmt(ip)}); + return writer.print("nav_val('{}')", .{fqn.fmt(ip)}); + }, + .nav_ty => |nav| { + const fqn = ip.getNav(nav).fqn; + return writer.print("nav_ty('{}')", .{fqn.fmt(ip)}); }, .interned => |ip_index| switch (ip.indexToKey(ip_index)) { .struct_type, .union_type, .enum_type => return writer.print("type('{}')", .{Type.fromInterned(ip_index).containerTypeName(ip).fmt(ip)}), @@ -3749,3 +3803,12 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enu if (!backend_ok) return .{ .bad_backend = backend }; return .ok; } + +/// Given that a `Nav` has value `val`, determine if a ref of that `Nav` gives a `const` pointer. +pub fn navValIsConst(zcu: *const Zcu, val: InternPool.Index) bool { + return switch (zcu.intern_pool.indexToKey(val)) { + .variable => false, + .@"extern" => |e| e.is_const, + else => true, + }; +} diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 0d7ca0eb260a..21908f769f50 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -731,10 +731,12 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu const gpa = zcu.gpa; const ip = &zcu.intern_pool; + _ = zcu.nav_val_analysis_queued.swapRemove(nav_id); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); const nav = ip.getNav(nav_id); - log.debug("ensureNavUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureNavValUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); // 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* @@ -763,19 +765,19 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu } else { // We can trust the current information about this unit. if (prev_failed) return error.AnalysisFail; - if (nav.status == .resolved) return; + switch (nav.status) { + .unresolved, .type_resolved => {}, + .fully_resolved => return, + } } 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: { + const invalidate_value: bool, 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, - }, + // If the unit has gone from failed to success, we still need to invalidate the dependencies. + result.val_changed or prev_failed, false, }; } else |err| switch (err) { @@ -786,10 +788,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); } - break :res .{ .{ - .invalidate_nav_val = !prev_failed, - .invalidate_nav_ref = !prev_failed, - }, true }; + break :res .{ !prev_failed, true }; }, error.OutOfMemory => { // TODO: it's unclear how to gracefully handle this. @@ -806,10 +805,8 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu }; 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) { + if (invalidate_value) { // 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. @@ -824,14 +821,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu if (new_failed) return error.AnalysisFail; } -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 { +fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { val_changed: bool } { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; @@ -875,9 +865,13 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr }; defer sema.deinit(); - // The comptime unit declares on the source of the corresponding declaration. + // Every `Nav` declares a dependency on the source of the corresponding declaration. try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index }); + // In theory, we would also add a reference to the corresponding `nav_val` unit here: there are + // always references in both directions between a `nav_val` and `nav_ty`. However, to save memory, + // these references are known implicitly. See logic in `Zcu.resolveReferences`. + var block: Sema.Block = .{ .parent = null, .sema = &sema, @@ -891,31 +885,44 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr defer block.instructions.deinit(gpa); const zir_decl = zir.getDeclaration(inst_resolved.inst); - assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace")); + const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); + const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); 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 }); + + const maybe_ty: ?Type = if (zir_decl.type_body != null) ty: { + // Since we have a type body, the type is resolved separately! + // Of course, we need to make sure we depend on it properly. + try sema.declareDependency(.{ .nav_ty = nav_id }); + try pt.ensureNavTypeUpToDate(nav_id); + break :ty .fromInterned(ip.getNav(nav_id).status.type_resolved.type); + } else null; + + const final_val: ?Value = if (zir_decl.value_body) |value_body| val: { + if (maybe_ty) |ty| { + // 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(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, ty, uncoerced_result_ref, init_src); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } else { + // Just analyze the value; we have no type to offer. + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } + } else null; + + const nav_ty: Type = maybe_ty orelse final_val.?.typeOf(zcu); // 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 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 }; - }; - switch (zir_decl.kind) { .@"comptime" => unreachable, // this is not a Nav .unnamed_test, .@"test", .decltest => assert(nav_ty.zigTypeTag(zcu) == .@"fn"), @@ -932,58 +939,24 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine // the full pointer type of this declaration. - 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); - }; - - 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); - }; - - 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 modifiers: Sema.NavPtrModifiers = if (zir_decl.type_body != null) m: { + // `analyzeNavType` (from the `ensureNavTypeUpToDate` call above) has already populated this data into + // the `Nav`. Load the new one, and pull the modifiers out. + switch (ip.getNav(nav_id).status) { + .unresolved => unreachable, // `analyzeNavType` will never leave us in this state + inline .type_resolved, .fully_resolved => |r| break :m .{ + .alignment = r.alignment, + .@"linksection" = r.@"linksection", + .@"addrspace" = r.@"addrspace", }, - }; - 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); + } + } else m: { + // `analyzeNavType` is essentially a stub which calls us. We are responsible for resolving this data. + break :m try sema.resolveNavPtrModifiers(&block, zir_decl, inst_resolved.inst, nav_ty); }; - // 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; + // Lastly, we must figure out the actual interned value to store to the `Nav`. + // This isn't necessarily the same as `final_val`! const nav_val: Value = switch (zir_decl.linkage) { .normal, .@"export" => switch (zir_decl.kind) { @@ -1013,8 +986,8 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr .is_threadlocal = zir_decl.is_threadlocal, .is_weak_linkage = false, .is_dll_import = false, - .alignment = alignment, - .@"addrspace" = @"addrspace", + .alignment = modifiers.alignment, + .@"addrspace" = modifiers.@"addrspace", .zir_index = old_nav.analysis.?.zir_index, // `declaration` instruction .owner_nav = undefined, // ignored by `getExtern` })); @@ -1047,10 +1020,7 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr }); // TODO: usingnamespace cannot participate in incremental compilation assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_nav_val = true, - .invalidate_nav_ref = true, - }; + return .{ .val_changed = true }; } const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) { @@ -1087,14 +1057,22 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr ip.resolveNavValue(nav_id, .{ .val = nav_val.toIntern(), - .alignment = alignment, - .@"linksection" = @"linksection", - .@"addrspace" = @"addrspace", + .alignment = modifiers.alignment, + .@"linksection" = modifiers.@"linksection", + .@"addrspace" = modifiers.@"addrspace", }); // Mark the unit as completed before evaluating the export! assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + if (zir_decl.type_body == null) { + // In this situation, it's possible that we were triggered by `analyzeNavType` up the stack. In that + // case, we must also signal that the *type* is now populated to make this export behave correctly. + // An alternative strategy would be to just put something on the job queue to perform the export, but + // this is a little more straightforward, if perhaps less elegant. + _ = zcu.analysis_in_progress.swapRemove(.wrap(.{ .nav_ty = nav_id })); + } + 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); @@ -1117,21 +1095,246 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr } switch (old_nav.status) { - .unresolved => return .{ - .invalidate_nav_val = true, - .invalidate_nav_ref = true, + .unresolved, .type_resolved => return .{ .val_changed = true }, + .fully_resolved => |old| return .{ .val_changed = old.val != nav_val.toIntern() }, + } +} + +pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) 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(.{ .nav_ty = nav_id }); + const nav = ip.getNav(nav_id); + + log.debug("ensureNavTypeUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); + + // Determine whether or not this `Nav`'s type 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 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 (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 unit. + if (prev_failed) return error.AnalysisFail; + switch (nav.status) { + .unresolved => {}, + .type_resolved, .fully_resolved => return, + } + } + + const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); + defer unit_prog_node.end(); + + const invalidate_type: bool, const new_failed: bool = if (pt.analyzeNavType(nav_id)) |result| res: { + break :res .{ + // If the unit has gone from failed to success, we still need to invalidate the dependencies. + result.type_changed or prev_failed, + false, + }; + } else |err| switch (err) { + error.AnalysisFail => res: { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this unit caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); + log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); + } + break :res .{ !prev_failed, 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", - }; + 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 (was_outdated) { + const dependee: InternPool.Dependee = .{ .nav_ty = nav_id }; + if (invalidate_type) { + // 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 (new_failed) return error.AnalysisFail; +} + +fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { type_changed: bool } { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id }); + const old_nav = ip.getNav(nav_id); + + log.debug("analyzeNavType {}", .{zcu.fmtAnalUnit(anal_unit)}); + + 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 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 _ = 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(); + + // Every `Nav` declares a dependency on the source of the corresponding declaration. + try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index }); + + // In theory, we would also add a reference to the corresponding `nav_val` unit here: there are + // always references in both directions between a `nav_val` and `nav_ty`. However, to save memory, + // these references are known implicitly. See logic in `Zcu.resolveReferences`. + + 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 zir_decl = zir.getDeclaration(inst_resolved.inst); + assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace")); + + const type_body = zir_decl.type_body orelse { + // The type of this `Nav` is inferred from the value. + // In other words, this `nav_ty` depends on the corresponding `nav_val`. + try sema.declareDependency(.{ .nav_val = nav_id }); + try pt.ensureNavValUpToDate(nav_id); + // Note that the above call, if it did any work, has removed our `analysis_in_progress` entry for us. + // (Our `defer` will run anyway, but it does nothing in this case.) + + // There's not a great way for us to know whether the type actually changed. + // For instance, perhaps the `nav_val` was already up-to-date, but this `nav_ty` is being + // analyzed because this declaration had a type annotation on the *previous* update. + // However, such cases are rare, and it's not unreasonable to re-analyze in them; and in + // other cases where we get here, it's because the `nav_val` was already re-analyzed and + // is outdated. + return .{ .type_changed = true }; + }; + + const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); + + const resolved_ty: Type = ty: { + 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().?); + }; + + // In the case where the type is specified, this function is also responsible for resolving + // the pointer modifiers, i.e. alignment, linksection, addrspace. + const modifiers = try sema.resolveNavPtrModifiers(&block, zir_decl, inst_resolved.inst, resolved_ty); + + // Usually, we can infer this information from the resolved `Nav` value; see `Zcu.navValIsConst`. + // However, since we don't have one, we need to quickly check the ZIR to figure this out. + const is_const = switch (zir_decl.kind) { + .@"comptime" => unreachable, + .unnamed_test, .@"test", .decltest, .@"usingnamespace", .@"const" => true, + .@"var" => false, + }; + + const is_extern_decl = zir_decl.linkage == .@"extern"; + + // Now for the question of the day: are the type and modifiers the same as before? + // If they are, then we should actually keep the `Nav` as `fully_resolved` if it currently is. + // That's because `analyzeNavVal` will later want to look at the resolved value to figure out + // whether it's changed: if we threw that data away now, it would have to assume that the value + // had changed, potentially spinning off loads of unnecessary re-analysis! + const changed = switch (old_nav.status) { + .unresolved => true, + .type_resolved => |r| r.type != resolved_ty.toIntern() or + r.alignment != modifiers.alignment or + r.@"linksection" != modifiers.@"linksection" or + r.@"addrspace" != modifiers.@"addrspace" or + r.is_const != is_const or + r.is_extern_decl != is_extern_decl, + .fully_resolved => |r| ip.typeOf(r.val) != resolved_ty.toIntern() or + r.alignment != modifiers.alignment or + r.@"linksection" != modifiers.@"linksection" or + r.@"addrspace" != modifiers.@"addrspace" or + zcu.navValIsConst(r.val) != is_const or + (old_nav.getExtern(ip) != null) != is_extern_decl, + }; + + if (!changed) return .{ .type_changed = false }; + + ip.resolveNavType(nav_id, .{ + .type = resolved_ty.toIntern(), + .alignment = modifiers.alignment, + .@"linksection" = modifiers.@"linksection", + .@"addrspace" = modifiers.@"addrspace", + .is_const = is_const, + .is_threadlocal = zir_decl.is_threadlocal, + .is_extern_decl = is_extern_decl, + }); + + return .{ .type_changed = true }; } pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { @@ -1144,6 +1347,8 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter const gpa = zcu.gpa; const ip = &zcu.intern_pool; + _ = zcu.func_body_analysis_queued.swapRemove(maybe_coerced_func_index); + // We only care about the uncoerced function. const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index); const anal_unit: AnalUnit = .wrap(.{ .func = func_index }); @@ -1171,11 +1376,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter 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 - } + if (func.analysisUnordered(ip).is_analyzed) return; } const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0); @@ -1236,7 +1437,7 @@ fn analyzeFuncBody( 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) { + if (ip.getNav(func.owner_nav).status.fully_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; @@ -1245,7 +1446,7 @@ fn analyzeFuncBody( 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) { + if (ip.getNav(go_nav).status.fully_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; @@ -2172,7 +2373,7 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE try zcu.analysis_in_progress.put(gpa, anal_unit, {}); errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); - func.setAnalysisState(ip, .analyzed); + func.setAnalyzed(ip); if (func.analysisUnordered(ip).inferred_error_set) { func.setResolvedErrorSet(ip, .none); } @@ -2550,8 +2751,8 @@ fn processExportsInner( if (zcu.transitive_failed_analysis.contains(unit)) break :failed true; } const val = switch (nav.status) { - .unresolved => break :failed true, - .resolved => |r| Value.fromInterned(r.val), + .unresolved, .type_resolved => break :failed true, + .fully_resolved => |r| Value.fromInterned(r.val), }; // If the value is a function, we also need to check if that function succeeded analysis. if (val.typeOf(zcu).zigTypeTag(zcu) == .@"fn") { @@ -3256,30 +3457,29 @@ pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Intern 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.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt"); - const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.resolved.val); + const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.fully_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); return builtin_namespace.pub_decls.getKeyAdapted(name_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt"); } -pub fn navPtrType(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) Allocator.Error!Type { +pub fn navPtrType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Allocator.Error!Type { const zcu = pt.zcu; const ip = &zcu.intern_pool; - const r = ip.getNav(nav_index).status.resolved; - const ty = Value.fromInterned(r.val).typeOf(zcu); + const ty, const alignment, const @"addrspace", const is_const = switch (ip.getNav(nav_id).status) { + .unresolved => unreachable, + .type_resolved => |r| .{ r.type, r.alignment, r.@"addrspace", r.is_const }, + .fully_resolved => |r| .{ ip.typeOf(r.val), r.alignment, r.@"addrspace", zcu.navValIsConst(r.val) }, + }; return pt.ptrType(.{ - .child = ty.toIntern(), + .child = ty, .flags = .{ - .alignment = if (r.alignment == ty.abiAlignment(zcu)) + .alignment = if (alignment == Type.fromInterned(ty).abiAlignment(zcu)) .none else - r.alignment, - .address_space = r.@"addrspace", - .is_const = switch (ip.indexToKey(r.val)) { - .variable => false, - .@"extern" => |e| e.is_const, - else => true, - }, + alignment, + .address_space = @"addrspace", + .is_const = is_const, }, }); } @@ -3299,9 +3499,13 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error! // TODO: this shouldn't need a `PerThread`! Fix the signature of `Type.abiAlignment`. pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPool.Alignment { const zcu = pt.zcu; - const r = zcu.intern_pool.getNav(nav_index).status.resolved; - if (r.alignment != .none) return r.alignment; - return Value.fromInterned(r.val).typeOf(zcu).abiAlignment(zcu); + const ty: Type, const alignment = switch (zcu.intern_pool.getNav(nav_index).status) { + .unresolved => unreachable, + .type_resolved => |r| .{ .fromInterned(r.type), r.alignment }, + .fully_resolved => |r| .{ Value.fromInterned(r.val).typeOf(zcu), r.alignment }, + }; + if (alignment != .none) return alignment; + return ty.abiAlignment(zcu); } /// Given a container type requiring resolution, ensures that it is up-to-date. diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index ccdf38a4741e..49961042bc82 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -3218,15 +3218,7 @@ fn lowerNavRef(func: *CodeGen, nav_index: InternPool.Nav.Index, offset: u32) Inn const zcu = pt.zcu; const ip = &zcu.intern_pool; - // check if decl is an alias to a function, in which case we - // want to lower the actual decl, rather than the alias itself. - const owner_nav = switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .func => |function| function.owner_nav, - .variable => |variable| variable.owner_nav, - .@"extern" => |@"extern"| @"extern".owner_nav, - else => nav_index, - }; - const nav_ty = ip.getNav(owner_nav).typeOf(ip); + const nav_ty = ip.getNav(nav_index).typeOf(ip); if (!ip.isFunctionType(nav_ty) and !Type.fromInterned(nav_ty).hasRuntimeBitsIgnoreComptime(zcu)) { return .{ .imm32 = 0xaaaaaaaa }; } diff --git a/src/codegen.zig b/src/codegen.zig index 898629d69b22..7b607f13f935 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -817,7 +817,7 @@ fn genNavRef( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, val: Value, - ref_nav_index: InternPool.Nav.Index, + nav_index: InternPool.Nav.Index, target: std.Target, ) CodeGenError!GenResult { const zcu = pt.zcu; @@ -851,14 +851,15 @@ fn genNavRef( } } - const nav_index, const is_extern, const lib_name, const is_threadlocal = switch (ip.indexToKey(zcu.navValue(ref_nav_index).toIntern())) { - .func => |func| .{ func.owner_nav, false, .none, false }, - .variable => |variable| .{ variable.owner_nav, false, .none, variable.is_threadlocal }, - .@"extern" => |@"extern"| .{ @"extern".owner_nav, true, @"extern".lib_name, @"extern".is_threadlocal }, - else => .{ ref_nav_index, false, .none, false }, - }; + const nav = ip.getNav(nav_index); + + const is_extern, const lib_name, const is_threadlocal = if (nav.getExtern(ip)) |e| + .{ true, e.lib_name, e.is_threadlocal } + else + .{ false, .none, nav.isThreadlocal(ip) }; + const single_threaded = zcu.navFileScope(nav_index).mod.single_threaded; - const name = ip.getNav(nav_index).name; + const name = nav.name; if (lf.cast(.elf)) |elf_file| { const zo = elf_file.zigObjectPtr().?; if (is_extern) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c3e3c7fbdc11..2368f202da3f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -770,11 +770,14 @@ pub const DeclGen = struct { const ctype_pool = &dg.ctype_pool; // Chase function values in order to be able to reference the original function. - const owner_nav = switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .variable => |variable| variable.owner_nav, - .func => |func| func.owner_nav, - .@"extern" => |@"extern"| @"extern".owner_nav, - else => nav_index, + const owner_nav = switch (ip.getNav(nav_index).status) { + .unresolved => unreachable, + .type_resolved => nav_index, // this can't be an extern or a function + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .func => |f| f.owner_nav, + .@"extern" => |e| e.owner_nav, + else => nav_index, + }, }; // Render an undefined pointer if we have a pointer to a zero-bit or comptime type. @@ -2237,7 +2240,7 @@ pub const DeclGen = struct { Type.fromInterned(nav.typeOf(ip)), .{ .nav = nav_index }, CQualifiers.init(.{ .@"const" = flags.is_const }), - nav.status.resolved.alignment, + nav.getAlignment(), .complete, ); try fwd.writeAll(";\n"); @@ -2246,19 +2249,19 @@ pub const DeclGen = struct { fn renderNavName(dg: *DeclGen, writer: anytype, nav_index: InternPool.Nav.Index) !void { const zcu = dg.pt.zcu; const ip = &zcu.intern_pool; - switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .@"extern" => |@"extern"| try writer.print("{ }", .{ + const nav = ip.getNav(nav_index); + if (nav.getExtern(ip)) |@"extern"| { + try writer.print("{ }", .{ fmtIdent(ip.getNav(@"extern".owner_nav).name.toSlice(ip)), - }), - else => { - // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case), - // expand to 3x the length of its input, but let's cut it off at a much shorter limit. - const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip); - try writer.print("{}__{d}", .{ - fmtIdent(fqn_slice[0..@min(fqn_slice.len, 100)]), - @intFromEnum(nav_index), - }); - }, + }); + } else { + // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case), + // expand to 3x the length of its input, but let's cut it off at a much shorter limit. + const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip); + try writer.print("{}__{d}", .{ + fmtIdent(fqn_slice[0..@min(fqn_slice.len, 100)]), + @intFromEnum(nav_index), + }); } } @@ -2826,7 +2829,7 @@ pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFn const fwd = o.dg.fwdDeclWriter(); try fwd.print("static zig_{s} ", .{@tagName(key)}); - try o.dg.renderFunctionSignature(fwd, fn_val, ip.getNav(fn_nav_index).status.resolved.alignment, .forward, .{ + try o.dg.renderFunctionSignature(fwd, fn_val, ip.getNav(fn_nav_index).getAlignment(), .forward, .{ .fmt_ctype_pool_string = fn_name, }); try fwd.writeAll(";\n"); @@ -2867,13 +2870,13 @@ pub fn genFunc(f: *Function) !void { try o.dg.renderFunctionSignature( fwd, nav_val, - nav.status.resolved.alignment, + nav.status.fully_resolved.alignment, .forward, .{ .nav = nav_index }, ); try fwd.writeAll(";\n"); - if (nav.status.resolved.@"linksection".toSlice(ip)) |s| + if (nav.status.fully_resolved.@"linksection".toSlice(ip)) |s| try o.writer().print("zig_linksection_fn({s}) ", .{fmtStringLiteral(s, null)}); try o.dg.renderFunctionSignature( o.writer(), @@ -2952,7 +2955,7 @@ pub fn genDecl(o: *Object) !void { const nav_ty = Type.fromInterned(nav.typeOf(ip)); if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) return; - switch (ip.indexToKey(nav.status.resolved.val)) { + switch (ip.indexToKey(nav.status.fully_resolved.val)) { .@"extern" => |@"extern"| { if (!ip.isFunctionType(nav_ty.toIntern())) return o.dg.renderFwdDecl(o.dg.pass.nav, .{ .is_extern = true, @@ -2965,8 +2968,8 @@ pub fn genDecl(o: *Object) !void { try fwd.writeAll("zig_extern "); try o.dg.renderFunctionSignature( fwd, - Value.fromInterned(nav.status.resolved.val), - nav.status.resolved.alignment, + Value.fromInterned(nav.status.fully_resolved.val), + nav.status.fully_resolved.alignment, .forward, .{ .@"export" = .{ .main_name = nav.name, @@ -2985,14 +2988,14 @@ pub fn genDecl(o: *Object) !void { const w = o.writer(); if (variable.is_weak_linkage) try w.writeAll("zig_weak_linkage "); if (variable.is_threadlocal and !o.dg.mod.single_threaded) try w.writeAll("zig_threadlocal "); - if (nav.status.resolved.@"linksection".toSlice(&zcu.intern_pool)) |s| + if (nav.status.fully_resolved.@"linksection".toSlice(&zcu.intern_pool)) |s| try w.print("zig_linksection({s}) ", .{fmtStringLiteral(s, null)}); try o.dg.renderTypeAndName( w, nav_ty, .{ .nav = o.dg.pass.nav }, .{}, - nav.status.resolved.alignment, + nav.status.fully_resolved.alignment, .complete, ); try w.writeAll(" = "); @@ -3002,10 +3005,10 @@ pub fn genDecl(o: *Object) !void { }, else => try genDeclValue( o, - Value.fromInterned(nav.status.resolved.val), + Value.fromInterned(nav.status.fully_resolved.val), .{ .nav = o.dg.pass.nav }, - nav.status.resolved.alignment, - nav.status.resolved.@"linksection", + nav.status.fully_resolved.alignment, + nav.status.fully_resolved.@"linksection", ), } } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 154a7114cf76..5b36644019c8 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1476,7 +1476,7 @@ pub const Object = struct { } }, &o.builder); } - if (nav.status.resolved.@"linksection".toSlice(ip)) |section| + if (nav.status.fully_resolved.@"linksection".toSlice(ip)) |section| function_index.setSection(try o.builder.string(section), &o.builder); var deinit_wip = true; @@ -1684,7 +1684,7 @@ pub const Object = struct { const file = try o.getDebugFile(file_scope); const line_number = zcu.navSrcLine(func.owner_nav) + 1; - const is_internal_linkage = ip.indexToKey(nav.status.resolved.val) != .@"extern"; + const is_internal_linkage = ip.indexToKey(nav.status.fully_resolved.val) != .@"extern"; const debug_decl_type = try o.lowerDebugType(fn_ty); const subprogram = try o.builder.debugSubprogram( @@ -2928,9 +2928,7 @@ pub const Object = struct { const gpa = o.gpa; const nav = ip.getNav(nav_index); const owner_mod = zcu.navFileScope(nav_index).mod; - const resolved = nav.status.resolved; - const val = Value.fromInterned(resolved.val); - const ty = val.typeOf(zcu); + const ty: Type = .fromInterned(nav.typeOf(ip)); const gop = try o.nav_map.getOrPut(gpa, nav_index); if (gop.found_existing) return gop.value_ptr.ptr(&o.builder).kind.function; @@ -2938,14 +2936,14 @@ pub const Object = struct { const target = owner_mod.resolved_target.result; const sret = firstParamSRet(fn_info, zcu, target); - const is_extern, const lib_name = switch (ip.indexToKey(val.toIntern())) { - .@"extern" => |@"extern"| .{ true, @"extern".lib_name }, - else => .{ false, .none }, - }; + const is_extern, const lib_name = if (nav.getExtern(ip)) |@"extern"| + .{ true, @"extern".lib_name } + else + .{ false, .none }; const function_index = try o.builder.addFunction( try o.lowerType(ty), try o.builder.strtabString((if (is_extern) nav.name else nav.fqn).toSlice(ip)), - toLlvmAddressSpace(resolved.@"addrspace", target), + toLlvmAddressSpace(nav.getAddrspace(), target), ); gop.value_ptr.* = function_index.ptrConst(&o.builder).global; @@ -3063,8 +3061,8 @@ pub const Object = struct { } } - if (resolved.alignment != .none) - function_index.setAlignment(resolved.alignment.toLlvm(), &o.builder); + if (nav.getAlignment() != .none) + function_index.setAlignment(nav.getAlignment().toLlvm(), &o.builder); // Function attributes that are independent of analysis results of the function body. try o.addCommonFnAttributes( @@ -3249,17 +3247,21 @@ pub const Object = struct { const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const resolved = nav.status.resolved; - const is_extern, const is_threadlocal, const is_weak_linkage, const is_dll_import = switch (ip.indexToKey(resolved.val)) { - .variable => |variable| .{ false, variable.is_threadlocal, variable.is_weak_linkage, false }, - .@"extern" => |@"extern"| .{ true, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import }, - else => .{ false, false, false, false }, + const is_extern, const is_threadlocal, const is_weak_linkage, const is_dll_import = switch (nav.status) { + .unresolved => unreachable, + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .variable => |variable| .{ false, variable.is_threadlocal, variable.is_weak_linkage, false }, + .@"extern" => |@"extern"| .{ true, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import }, + else => .{ false, false, false, false }, + }, + // This means it's a source declaration which is not `extern`! + .type_resolved => |r| .{ false, r.is_threadlocal, false, false }, }; const variable_index = try o.builder.addVariable( try o.builder.strtabString((if (is_extern) nav.name else nav.fqn).toSlice(ip)), try o.lowerType(Type.fromInterned(nav.typeOf(ip))), - toLlvmGlobalAddressSpace(resolved.@"addrspace", zcu.getTarget()), + toLlvmGlobalAddressSpace(nav.getAddrspace(), zcu.getTarget()), ); gop.value_ptr.* = variable_index.ptrConst(&o.builder).global; @@ -4528,20 +4530,10 @@ pub const Object = struct { const zcu = pt.zcu; const ip = &zcu.intern_pool; - // In the case of something like: - // fn foo() void {} - // const bar = foo; - // ... &bar; - // `bar` is just an alias and we actually want to lower a reference to `foo`. - const owner_nav_index = switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .func => |func| func.owner_nav, - .@"extern" => |@"extern"| @"extern".owner_nav, - else => nav_index, - }; - const owner_nav = ip.getNav(owner_nav_index); + const nav = ip.getNav(nav_index); - const nav_ty = Type.fromInterned(owner_nav.typeOf(ip)); - const ptr_ty = try pt.navPtrType(owner_nav_index); + const nav_ty = Type.fromInterned(nav.typeOf(ip)); + const ptr_ty = try pt.navPtrType(nav_index); const is_fn_body = nav_ty.zigTypeTag(zcu) == .@"fn"; if ((!is_fn_body and !nav_ty.hasRuntimeBits(zcu)) or @@ -4551,13 +4543,13 @@ pub const Object = struct { } const llvm_global = if (is_fn_body) - (try o.resolveLlvmFunction(owner_nav_index)).ptrConst(&o.builder).global + (try o.resolveLlvmFunction(nav_index)).ptrConst(&o.builder).global else - (try o.resolveGlobalNav(owner_nav_index)).ptrConst(&o.builder).global; + (try o.resolveGlobalNav(nav_index)).ptrConst(&o.builder).global; const llvm_val = try o.builder.convConst( llvm_global.toConst(), - try o.builder.ptrType(toLlvmAddressSpace(owner_nav.status.resolved.@"addrspace", zcu.getTarget())), + try o.builder.ptrType(toLlvmAddressSpace(nav.getAddrspace(), zcu.getTarget())), ); return o.builder.convConst(llvm_val, try o.lowerType(ptr_ty)); @@ -4799,7 +4791,7 @@ pub const NavGen = struct { const ip = &zcu.intern_pool; const nav_index = ng.nav_index; const nav = ip.getNav(nav_index); - const resolved = nav.status.resolved; + const resolved = nav.status.fully_resolved; const is_extern, const lib_name, const is_threadlocal, const is_weak_linkage, const is_dll_import, const is_const, const init_val, const owner_nav = switch (ip.indexToKey(resolved.val)) { .variable => |variable| .{ false, .none, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav }, @@ -5765,7 +5757,7 @@ pub const FuncGen = struct { const msg_nav_index = zcu.panic_messages[@intFromEnum(panic_id)].unwrap().?; const msg_nav = ip.getNav(msg_nav_index); const msg_len = Type.fromInterned(msg_nav.typeOf(ip)).childType(zcu).arrayLen(zcu); - const msg_ptr = try o.lowerValue(msg_nav.status.resolved.val); + const msg_ptr = try o.lowerValue(msg_nav.status.fully_resolved.val); const null_opt_addr_global = try fg.resolveNullOptUsize(); const target = zcu.getTarget(); const llvm_usize = try o.lowerType(Type.usize); diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 16b4a6dfbdb5..91e2c4f7e7ca 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -268,7 +268,7 @@ pub const Object = struct { // TODO: Extern fn? const kind: SpvModule.Decl.Kind = if (ip.isFunctionType(nav.typeOf(ip))) .func - else switch (nav.status.resolved.@"addrspace") { + else switch (nav.getAddrspace()) { .generic => .invocation_global, else => .global, }; @@ -1279,17 +1279,20 @@ const NavGen = struct { const ip = &zcu.intern_pool; const ty_id = try self.resolveType(ty, .direct); const nav = ip.getNav(nav_index); - const nav_val = zcu.navValue(nav_index); - const nav_ty = nav_val.typeOf(zcu); - - switch (ip.indexToKey(nav_val.toIntern())) { - .func => { - // TODO: Properly lower function pointers. For now we are going to hack around it and - // just generate an empty pointer. Function pointers are represented by a pointer to usize. - return try self.spv.constUndef(ty_id); + const nav_ty: Type = .fromInterned(nav.typeOf(ip)); + + switch (nav.status) { + .unresolved => unreachable, + .type_resolved => {}, // this is not a function or extern + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .func => { + // TODO: Properly lower function pointers. For now we are going to hack around it and + // just generate an empty pointer. Function pointers are represented by a pointer to usize. + return try self.spv.constUndef(ty_id); + }, + .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) @panic("TODO"), + else => {}, }, - .@"extern" => assert(!ip.isFunctionType(nav_ty.toIntern())), // TODO - else => {}, } if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) { @@ -1305,7 +1308,7 @@ const NavGen = struct { .global, .invocation_global => spv_decl.result_id, }; - const storage_class = self.spvStorageClass(nav.status.resolved.@"addrspace"); + const storage_class = self.spvStorageClass(nav.getAddrspace()); try self.addFunctionDep(spv_decl_index, storage_class); const decl_ptr_ty_id = try self.ptrType(nav_ty, storage_class); @@ -3182,7 +3185,7 @@ const NavGen = struct { }; assert(maybe_init_val == null); // TODO - const storage_class = self.spvStorageClass(nav.status.resolved.@"addrspace"); + const storage_class = self.spvStorageClass(nav.getAddrspace()); assert(storage_class != .Generic); // These should be instance globals const ptr_ty_id = try self.ptrType(ty, storage_class); diff --git a/src/link.zig b/src/link.zig index f0f6e9b01df9..58c5cf35af09 100644 --- a/src/link.zig +++ b/src/link.zig @@ -692,7 +692,7 @@ pub const File = struct { /// May be called before or after updateExports for any given Nav. pub fn updateNav(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) UpdateNavError!void { const nav = pt.zcu.intern_pool.getNav(nav_index); - assert(nav.status == .resolved); + assert(nav.status == .fully_resolved); switch (base.tag) { inline else => |tag| { dev.check(tag.devFeature()); diff --git a/src/link/C.zig b/src/link/C.zig index f42a467ee8e6..d84f29eb4b5b 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -217,7 +217,7 @@ pub fn updateFunc( .mod = zcu.navFileScope(func.owner_nav).mod, .error_msg = null, .pass = .{ .nav = func.owner_nav }, - .is_naked_fn = zcu.navValue(func.owner_nav).typeOf(zcu).fnCallingConvention(zcu) == .naked, + .is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked, .fwd_decl = fwd_decl.toManaged(gpa), .ctype_pool = ctype_pool.*, .scratch = .{}, @@ -320,11 +320,11 @@ pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ! const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + const nav_init = switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => return, .@"extern" => .none, .variable => |variable| variable.init, - else => nav.status.resolved.val, + else => nav.status.fully_resolved.val, }; if (nav_init != .none and !Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(zcu)) return; @@ -499,7 +499,7 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: av_block, self.exported_navs.getPtr(nav), export_names, - if (ip.indexToKey(zcu.navValue(nav).toIntern()) == .@"extern") + if (ip.getNav(nav).getExtern(ip) != null) ip.getNav(nav).name.toOptional() else .none, @@ -544,13 +544,11 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: }, self.getString(av_block.code), ); - for (self.navs.keys(), self.navs.values()) |nav, av_block| f.appendCodeAssumeCapacity( - if (self.exported_navs.contains(nav)) .default else switch (ip.indexToKey(zcu.navValue(nav).toIntern())) { - .@"extern" => .zig_extern, - else => .static, - }, - self.getString(av_block.code), - ); + for (self.navs.keys(), self.navs.values()) |nav, av_block| f.appendCodeAssumeCapacity(storage: { + if (self.exported_navs.contains(nav)) break :storage .default; + if (ip.getNav(nav).getExtern(ip) != null) break :storage .zig_extern; + break :storage .static; + }, self.getString(av_block.code)); const file = self.base.file.?; try file.setEndPos(f.file_size); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e5b717ce1bcc..f13863cfb9b1 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1110,6 +1110,8 @@ pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index, const atom_index = try coff.getOrCreateAtomForNav(func.owner_nav); coff.freeRelocations(atom_index); + coff.navs.getPtr(func.owner_nav).?.section = coff.text_section_index.?; + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); @@ -1223,6 +1225,8 @@ pub fn updateNav( coff.freeRelocations(atom_index); const atom = coff.getAtom(atom_index); + coff.navs.getPtr(nav_index).?.section = coff.getNavOutputSection(nav_index); + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); @@ -1342,7 +1346,8 @@ pub fn getOrCreateAtomForNav(coff: *Coff, nav_index: InternPool.Nav.Index) !Atom if (!gop.found_existing) { gop.value_ptr.* = .{ .atom = try coff.createAtom(), - .section = coff.getNavOutputSection(nav_index), + // If necessary, this will be modified by `updateNav` or `updateFunc`. + .section = coff.rdata_section_index.?, .exports = .{}, }; } @@ -1355,7 +1360,7 @@ fn getNavOutputSection(coff: *Coff, nav_index: InternPool.Nav.Index) u16 { const nav = ip.getNav(nav_index); const ty = Type.fromInterned(nav.typeOf(ip)); const zig_ty = ty.zigTypeTag(zcu); - const val = Value.fromInterned(nav.status.resolved.val); + const val = Value.fromInterned(nav.status.fully_resolved.val); const index: u16 = blk: { if (val.isUndefDeep(zcu)) { // TODO in release-fast and release-small, we should put undef in .bss @@ -2348,10 +2353,10 @@ pub fn getNavVAddr( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getNavVAddr {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const sym_index = switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try coff.getGlobalSymbol(nav.name.toSlice(ip), @"extern".lib_name.toSlice(ip)), - else => coff.getAtom(try coff.getOrCreateAtomForNav(nav_index)).getSymbolIndex().?, - }; + const sym_index = if (nav.getExtern(ip)) |e| + try coff.getGlobalSymbol(nav.name.toSlice(ip), e.lib_name.toSlice(ip)) + else + coff.getAtom(try coff.getOrCreateAtomForNav(nav_index)).getSymbolIndex().?; const atom_index = coff.getAtomIndexForSymbol(.{ .sym_index = reloc_info.parent.atom_index, .file = null, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index d132b5232990..5af6f804107b 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2281,7 +2281,7 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In const nav_ty = nav_val.typeOf(zcu); const nav_ty_reloc_index = try wip_nav.refForward(); try wip_nav.infoExprloc(.{ .addr = .{ .sym = sym_index } }); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse nav_ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); wip_nav.finishForward(nav_ty_reloc_index); @@ -2313,7 +2313,7 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In try wip_nav.refType(ty); const addr: Loc = .{ .addr = .{ .sym = sym_index } }; try wip_nav.infoExprloc(if (variable.is_threadlocal) .{ .form_tls_address = &addr } else addr); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); }, @@ -2388,7 +2388,7 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In wip_nav.func_high_pc = @intCast(wip_nav.debug_info.items.len); try diw.writeInt(u32, 0, dwarf.endian); const target = file.mod.resolved_target.result; - try uleb128(diw, switch (nav.status.resolved.alignment) { + try uleb128(diw, switch (nav.status.fully_resolved.alignment) { .none => target_info.defaultFunctionAlignment(target), else => |a| a.maxStrict(target_info.minFunctionAlignment(target)), }.toByteUnits().?); @@ -2952,7 +2952,7 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool const nav_ty = nav_val.typeOf(zcu); try wip_nav.refType(nav_ty); try wip_nav.blockValue(nav_src_loc, nav_val); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse nav_ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); }, @@ -2977,7 +2977,7 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool try wip_nav.strp(nav.name.toSlice(ip)); try wip_nav.strp(nav.fqn.toSlice(ip)); const nav_ty_reloc_index = try wip_nav.refForward(); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse nav_ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); if (has_runtime_bits) try wip_nav.blockValue(nav_src_loc, nav_val); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index fd6eceb556d7..5a2a7a8009b7 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -925,14 +925,11 @@ pub fn getNavVAddr( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getNavVAddr {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const this_sym_index = switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try self.getGlobalSymbol( - elf_file, - nav.name.toSlice(ip), - @"extern".lib_name.toSlice(ip), - ), - else => try self.getOrCreateMetadataForNav(zcu, nav_index), - }; + const this_sym_index = if (nav.getExtern(ip)) |@"extern"| try self.getGlobalSymbol( + elf_file, + nav.name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ) else try self.getOrCreateMetadataForNav(zcu, nav_index); const this_sym = self.symbol(this_sym_index); const vaddr = this_sym.address(.{}, elf_file); switch (reloc_info.parent) { @@ -1107,15 +1104,13 @@ pub fn freeNav(self: *ZigObject, elf_file: *Elf, nav_index: InternPool.Nav.Index pub fn getOrCreateMetadataForNav(self: *ZigObject, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol.Index { const gpa = zcu.gpa; + const ip = &zcu.intern_pool; const gop = try self.navs.getOrPut(gpa, nav_index); if (!gop.found_existing) { const symbol_index = try self.newSymbolWithAtom(gpa, 0); - const nav_val = Value.fromInterned(zcu.intern_pool.getNav(nav_index).status.resolved.val); const sym = self.symbol(symbol_index); - if (nav_val.getVariable(zcu)) |variable| { - if (variable.is_threadlocal and zcu.comp.config.any_non_single_threaded) { - sym.flags.is_tls = true; - } + if (ip.getNav(nav_index).isThreadlocal(ip) and zcu.comp.config.any_non_single_threaded) { + sym.flags.is_tls = true; } gop.value_ptr.* = .{ .symbol_index = symbol_index }; } @@ -1547,7 +1542,7 @@ pub fn updateNav( log.debug("updateNav {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + const nav_init = switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => .none, .variable => |variable| variable.init, .@"extern" => |@"extern"| { @@ -1560,7 +1555,7 @@ pub fn updateNav( self.symbol(sym_index).flags.is_extern_ptr = true; return; }, - else => nav.status.resolved.val, + else => nav.status.fully_resolved.val, }; if (nav_init != .none and Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(zcu)) { diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index e18bc078dff4..511cb6839de7 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -608,14 +608,11 @@ pub fn getNavVAddr( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getNavVAddr {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const sym_index = switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try self.getGlobalSymbol( - macho_file, - nav.name.toSlice(ip), - @"extern".lib_name.toSlice(ip), - ), - else => try self.getOrCreateMetadataForNav(macho_file, nav_index), - }; + const sym_index = if (nav.getExtern(ip)) |@"extern"| try self.getGlobalSymbol( + macho_file, + nav.name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ) else try self.getOrCreateMetadataForNav(macho_file, nav_index); const sym = self.symbols.items[sym_index]; const vaddr = sym.getAddress(.{}, macho_file); switch (reloc_info.parent) { @@ -882,7 +879,7 @@ pub fn updateNav( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + const nav_init = switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => .none, .variable => |variable| variable.init, .@"extern" => |@"extern"| { @@ -895,7 +892,7 @@ pub fn updateNav( sym.flags.is_extern_ptr = true; return; }, - else => nav.status.resolved.val, + else => nav.status.fully_resolved.val, }; if (nav_init != .none and Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(zcu)) { @@ -1561,11 +1558,7 @@ fn isThreadlocal(macho_file: *MachO, nav_index: InternPool.Nav.Index) bool { if (!macho_file.base.comp.config.any_non_single_threaded) return false; const ip = &macho_file.base.comp.zcu.?.intern_pool; - return switch (ip.indexToKey(ip.getNav(nav_index).status.resolved.val)) { - .variable => |variable| variable.is_threadlocal, - .@"extern" => |@"extern"| @"extern".is_threadlocal, - else => false, - }; + return ip.getNav(nav_index).isThreadlocal(ip); } fn addAtom(self: *ZigObject, allocator: Allocator) !Atom.Index { diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 3144b2ac1083..8e27e20ec775 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -1021,7 +1021,7 @@ pub fn seeNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) const atom_idx = gop.value_ptr.index; // handle externs here because they might not get updateDecl called on them const nav = ip.getNav(nav_index); - if (ip.indexToKey(nav.status.resolved.val) == .@"extern") { + if (nav.getExtern(ip) != null) { // this is a "phantom atom" - it is never actually written to disk, just convenient for us to store stuff about externs if (nav.name.eqlSlice("etext", ip)) { self.etext_edata_end_atom_indices[0] = atom_idx; @@ -1370,7 +1370,7 @@ pub fn getNavVAddr( const ip = &pt.zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getDeclVAddr for {}", .{nav.name.fmt(ip)}); - if (ip.indexToKey(nav.status.resolved.val) == .@"extern") { + if (nav.getExtern(ip) != null) { if (nav.name.eqlSlice("etext", ip)) { try self.addReloc(reloc_info.parent.atom_index, .{ .target = undefined, diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index 0166b28743f8..09d764773018 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -734,15 +734,14 @@ pub fn getNavVAddr( const target_atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index); const target_atom = wasm.getAtom(target_atom_index); const target_symbol_index = @intFromEnum(target_atom.sym_index); - switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try zig_object.addOrUpdateImport( + if (nav.getExtern(ip)) |@"extern"| { + try zig_object.addOrUpdateImport( wasm, nav.name.toSlice(ip), target_atom.sym_index, @"extern".lib_name.toSlice(ip), null, - ), - else => {}, + ); } std.debug.assert(reloc_info.parent.atom_index != 0); @@ -945,8 +944,8 @@ pub fn freeNav(zig_object: *ZigObject, wasm: *Wasm, nav_index: InternPool.Nav.In segment.name = &.{}; // Ensure no accidental double free } - const nav_val = zcu.navValue(nav_index).toIntern(); - if (ip.indexToKey(nav_val) == .@"extern") { + const nav = ip.getNav(nav_index); + if (nav.getExtern(ip) != null) { std.debug.assert(zig_object.imports.remove(atom.sym_index)); } std.debug.assert(wasm.symbol_atom.remove(atom.symbolLoc())); @@ -960,7 +959,7 @@ pub fn freeNav(zig_object: *ZigObject, wasm: *Wasm, nav_index: InternPool.Nav.In if (sym.isGlobal()) { std.debug.assert(zig_object.global_syms.remove(atom.sym_index)); } - if (ip.isFunctionType(ip.typeOf(nav_val))) { + if (ip.isFunctionType(nav.typeOf(ip))) { zig_object.functions_free_list.append(gpa, sym.index) catch {}; std.debug.assert(zig_object.atom_types.remove(atom_index)); } else { diff --git a/test/behavior/globals.zig b/test/behavior/globals.zig index 89dc20c5c786..7c5645be1932 100644 --- a/test/behavior/globals.zig +++ b/test/behavior/globals.zig @@ -66,3 +66,99 @@ test "global loads can affect liveness" { S.f(); try std.testing.expect(y.a == 1); } + +test "global const can be self-referential" { + const S = struct { + self: *const @This(), + x: u32, + + const foo: @This() = .{ .self = &foo, .x = 123 }; + }; + + try std.testing.expect(S.foo.x == 123); + try std.testing.expect(S.foo.self.x == 123); + try std.testing.expect(S.foo.self.self.x == 123); + try std.testing.expect(S.foo.self == &S.foo); + try std.testing.expect(S.foo.self.self == &S.foo); +} + +test "global var can be self-referential" { + const S = struct { + self: *@This(), + x: u32, + + var foo: @This() = .{ .self = &foo, .x = undefined }; + }; + + S.foo.x = 123; + + try std.testing.expect(S.foo.x == 123); + try std.testing.expect(S.foo.self.x == 123); + try std.testing.expect(S.foo.self == &S.foo); + + S.foo.self.x = 456; + + try std.testing.expect(S.foo.x == 456); + try std.testing.expect(S.foo.self.x == 456); + try std.testing.expect(S.foo.self == &S.foo); + + S.foo.self.self.x = 789; + + try std.testing.expect(S.foo.x == 789); + try std.testing.expect(S.foo.self.x == 789); + try std.testing.expect(S.foo.self == &S.foo); +} + +test "global const can be indirectly self-referential" { + const S = struct { + other: *const @This(), + x: u32, + + const foo: @This() = .{ .other = &bar, .x = 123 }; + const bar: @This() = .{ .other = &foo, .x = 456 }; + }; + + try std.testing.expect(S.foo.x == 123); + try std.testing.expect(S.foo.other.x == 456); + try std.testing.expect(S.foo.other.other.x == 123); + try std.testing.expect(S.foo.other.other.other.x == 456); + try std.testing.expect(S.foo.other == &S.bar); + try std.testing.expect(S.foo.other.other == &S.foo); + + try std.testing.expect(S.bar.x == 456); + try std.testing.expect(S.bar.other.x == 123); + try std.testing.expect(S.bar.other.other.x == 456); + try std.testing.expect(S.bar.other.other.other.x == 123); + try std.testing.expect(S.bar.other == &S.foo); + try std.testing.expect(S.bar.other.other == &S.bar); +} + +test "global var can be indirectly self-referential" { + const S = struct { + other: *@This(), + x: u32, + + var foo: @This() = .{ .other = &bar, .x = undefined }; + var bar: @This() = .{ .other = &foo, .x = undefined }; + }; + + S.foo.other.x = 123; // bar.x + S.foo.other.other.x = 456; // foo.x + + try std.testing.expect(S.foo.x == 456); + try std.testing.expect(S.foo.other.x == 123); + try std.testing.expect(S.foo.other.other.x == 456); + try std.testing.expect(S.foo.other.other.other.x == 123); + try std.testing.expect(S.foo.other == &S.bar); + try std.testing.expect(S.foo.other.other == &S.foo); + + S.bar.other.x = 111; // foo.x + S.bar.other.other.x = 222; // bar.x + + try std.testing.expect(S.bar.x == 222); + try std.testing.expect(S.bar.other.x == 111); + try std.testing.expect(S.bar.other.other.x == 222); + try std.testing.expect(S.bar.other.other.other.x == 111); + try std.testing.expect(S.bar.other == &S.foo); + try std.testing.expect(S.bar.other.other == &S.bar); +} diff --git a/test/cases/compile_errors/self_reference_missing_const.zig b/test/cases/compile_errors/self_reference_missing_const.zig new file mode 100644 index 000000000000..72b0ac1561be --- /dev/null +++ b/test/cases/compile_errors/self_reference_missing_const.zig @@ -0,0 +1,11 @@ +const S = struct { self: *S, x: u32 }; +const s: S = .{ .self = &s, .x = 123 }; + +comptime { + _ = s; +} + +// error +// +// :2:18: error: expected type '*tmp.S', found '*const tmp.S' +// :2:18: note: cast discards const qualifier