From 18362ebe13ece2ea7c4f57303ec4687f55d2dba5 Mon Sep 17 00:00:00 2001 From: mlugg Date: Tue, 17 Dec 2024 00:41:01 +0000 Subject: Zir: refactor `declaration` instruction representation The new representation is often more compact. It is also more straightforward to understand: for instance, `extern` is represented on the `declaration` instruction itself rather than using a special instruction. The same applies to `var`, making both of these far more compact. This commit also separates the type and value bodies of a `declaration` instruction. This is a prerequisite for #131. In general, `declaration` now directly encodes details of the syntax form used, and the embedded ZIR bodies are for actual expressions. The only exception to this is functions, where ZIR is effectively designed as if we had #1717. `extern fn` declarations are modeled as `extern const` with a function type, and normal `fn` definitions are modeled as `const` with a `func{,_fancy,_inferred}` instruction. This may change in the future, but improving on this was out of scope for this commit. --- src/codegen/llvm.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b94ea07995..154a7114cf 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2939,7 +2939,6 @@ pub const Object = struct { const sret = firstParamSRet(fn_info, zcu, target); const is_extern, const lib_name = switch (ip.indexToKey(val.toIntern())) { - .variable => |variable| .{ false, variable.lib_name }, .@"extern" => |@"extern"| .{ true, @"extern".lib_name }, else => .{ false, .none }, }; @@ -4803,7 +4802,7 @@ pub const NavGen = struct { const resolved = nav.status.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, variable.lib_name, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav }, + .variable => |variable| .{ false, .none, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav }, .@"extern" => |@"extern"| .{ true, @"extern".lib_name, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import, @"extern".is_const, .none, @"extern".owner_nav }, else => .{ false, .none, false, false, false, true, resolved.val, nav_index }, }; -- cgit v1.2.3 From 3afda4322c34dedc2319701fdfac3505c8d311e9 Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 23 Dec 2024 20:39:19 +0000 Subject: compiler: analyze type and value of global declaration separately This commit separates semantic analysis of the annotated type vs value of a global declaration, therefore allowing recursive and mutually recursive values to be declared. Every `Nav` which undergoes analysis now has *two* corresponding `AnalUnit`s: `.{ .nav_val = n }` and `.{ .nav_ty = n }`. The `nav_val` unit is responsible for *fully resolving* the `Nav`: determining its value, linksection, addrspace, etc. The `nav_ty` unit, on the other hand, resolves only the information necessary to construct a *pointer* to the `Nav`: its type, addrspace, etc. (It does also analyze its linksection, but that could be moved to `nav_val` I think; it doesn't make any difference). Analyzing a `nav_ty` for a declaration with no type annotation will just mark a dependency on the `nav_val`, analyze it, and finish. Conversely, analyzing a `nav_val` for a declaration *with* a type annotation will first mark a dependency on the `nav_ty` and analyze it, using this as the result type when evaluating the value body. The `nav_val` and `nav_ty` units always have references to one another: so, if a `Nav`'s type is referenced, its value implicitly is too, and vice versa. However, these dependencies are trivial, so, to save memory, are only known implicitly by logic in `resolveReferences`. In general, analyzing ZIR `decl_val` will only analyze `nav_ty` of the corresponding `Nav`. There are two exceptions to this. If the declaration is an `extern` declaration, then we immediately ensure the `Nav` value is resolved (which doesn't actually require any more analysis, since such a declaration has no value body anyway). Additionally, if the resolved type has type tag `.@"fn"`, we again immediately resolve the `Nav` value. The latter restriction is in place for two reasons: * Functions are special, in that their externs are allowed to trivially alias; i.e. with a declaration `extern fn foo(...)`, you can write `const bar = foo;`. This is not allowed for non-function externs, and it means that function types are the only place where it is possible for a declaration `Nav` to have a `.@"extern"` value without actually being declared `extern`. We need to identify this situation immediately so that the `decl_ref` can create a pointer to the *real* extern `Nav`, not this alias. * In certain situations, such as taking a pointer to a `Nav`, Sema needs to queue analysis of a runtime function if the value is a function. To do this, the function value needs to be known, so we need to resolve the value immediately upon `&foo` where `foo` is a function. This restriction is simple to codify into the eventual language specification, and doesn't limit the utility of this feature in practice. A consequence of this commit is that codegen and linking logic needs to be more careful when looking at `Nav`s. In general: * When `updateNav` or `updateFunc` is called, it is safe to assume that the `Nav` being updated (the owner `Nav` for `updateFunc`) is fully resolved. * Any `Nav` whose value is/will be an `@"extern"` or a function is fully resolved; see `Nav.getExtern` for a helper for a common case here. * Any other `Nav` may only have its type resolved. This didn't seem to be too tricky to satisfy in any of the existing codegen/linker backends. Resolves: #131 --- src/Compilation.zig | 14 +- src/InternPool.zig | 255 ++++++++--- src/Sema.zig | 203 +++++++-- src/Sema/comptime_ptr_access.zig | 5 +- src/Value.zig | 7 +- src/Zcu.zig | 81 +++- src/Zcu/PerThread.zig | 476 +++++++++++++++------ src/arch/wasm/CodeGen.zig | 10 +- src/codegen.zig | 17 +- src/codegen/c.zig | 61 +-- src/codegen/llvm.zig | 64 ++- src/codegen/spirv.zig | 29 +- src/link.zig | 2 +- src/link/C.zig | 20 +- src/link/Coff.zig | 17 +- src/link/Dwarf.zig | 10 +- src/link/Elf/ZigObject.zig | 25 +- src/link/MachO/ZigObject.zig | 23 +- src/link/Plan9.zig | 4 +- src/link/Wasm/ZigObject.zig | 13 +- test/behavior/globals.zig | 96 +++++ .../self_reference_missing_const.zig | 11 + 22 files changed, 1033 insertions(+), 410 deletions(-) create mode 100644 test/cases/compile_errors/self_reference_missing_const.zig (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index 6f9b2e18d6..28c5efab6c 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 41019ea9d9..64cf95c7b2 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 a41762f530..ccc9f63a56 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 10e81d7a9e..ceddb9457d 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 59fbdf67d5..25f5b50166 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 d374a0aa0c..8b3125039a 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 0d7ca0eb26..21908f769f 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 ccdf38a474..49961042bc 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 898629d69b..7b607f13f9 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 c3e3c7fbdc..2368f202da 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 154a7114cf..5b36644019 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 16b4a6dfbd..91e2c4f7e7 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 f0f6e9b01d..58c5cf35af 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 f42a467ee8..d84f29eb4b 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 e5b717ce1b..f13863cfb9 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 d132b52329..5af6f80410 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 fd6eceb556..5a2a7a8009 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 e18bc078df..511cb6839d 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 3144b2ac10..8e27e20ec7 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 0166b28743..09d7647730 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 89dc20c5c7..7c5645be19 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 0000000000..72b0ac1561 --- /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 -- cgit v1.2.3