Commit f01029c4af

mlugg <mlugg@mlugg.co.uk>
2025-01-04 06:09:02
incremental: new `AnalUnit` to group dependencies on `std.builtin` decls
This commit reworks how values like the panic handler function are memoized during a compiler invocation. Previously, the value was resolved by whichever analysis requested it first, and cached on `Zcu`. This is problematic for incremental compilation, as after the initial resolution, no dependencies are marked by users of this memoized state. This is arguably acceptable for `std.builtin`, but it's definitely not acceptable for the panic handler/messages, because those can be set by the user (`std.builtin.Panic` checks `@import("root").Panic`). So, here we introduce a new kind of `AnalUnit`, called `memoized_state`. There are 3 such units: * `.{ .memoized_state = .va_list }` resolves the type `std.builtin.VaList` * `.{ .memoized_state = .panic }` resolves `std.Panic` * `.{ .memoized_state = .main }` resolves everything else we want These units essentially "bundle" the resolution of their corresponding declarations, storing the results into fields on `Zcu`. This way, when, for instance, a function wants to call the panic handler, it simply runs `ensureMemoizedStateResolved`, registering one dependency, and pulls the values from the `Zcu`. This "bundling" minimizes dependency edges. The 3 units are separated to allow them to act independently: for instance, the panic handler can use `std.builtin.Type` without triggering a dependency loop.
1 parent fd62912
src/codegen/llvm.zig
@@ -5754,10 +5754,12 @@ pub const FuncGen = struct {
         const o = fg.ng.object;
         const zcu = o.pt.zcu;
         const ip = &zcu.intern_pool;
-        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.fully_resolved.val);
+        const panic_msg_val: InternPool.Index = switch (panic_id) {
+            inline else => |ct_panic_id| @field(zcu.builtin_decl_values, "Panic.messages." ++ @tagName(ct_panic_id)),
+        };
+        assert(panic_msg_val != .none);
+        const msg_len = Value.fromInterned(panic_msg_val).typeOf(zcu).childType(zcu).arrayLen(zcu);
+        const msg_ptr = try o.lowerValue(panic_msg_val);
         const null_opt_addr_global = try fg.resolveNullOptUsize();
         const target = zcu.getTarget();
         const llvm_usize = try o.lowerType(Type.usize);
@@ -5768,7 +5770,7 @@ pub const FuncGen = struct {
         //   ptr null,                                               ; stack trace
         //   ptr @2,                                                 ; addr (null ?usize)
         // )
-        const panic_func = zcu.funcInfo(zcu.panic_func_index);
+        const panic_func = zcu.funcInfo(zcu.builtin_decl_values.@"Panic.call");
         const panic_nav = ip.getNav(panic_func.owner_nav);
         const fn_info = zcu.typeToFunc(Type.fromInterned(panic_nav.typeOf(ip))).?;
         const panic_global = try o.resolveLlvmFunction(panic_func.owner_nav);
src/Zcu/PerThread.zig
@@ -560,6 +560,147 @@ pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.Sem
     return pt.semaFile(file_index);
 }
 
+/// Ensures that all memoized state on `Zcu` is up-to-date, performing re-analysis if necessary.
+/// Returns `error.AnalysisFail` if an analysis error is encountered; the caller is free to ignore
+/// this, since the error is already registered, but it must not use the value of memoized fields.
+pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.MemoizedStateStage) Zcu.SemaError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+
+    const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
+
+    log.debug("ensureMemoizedStateUpToDate", .{});
+
+    assert(!zcu.analysis_in_progress.contains(unit));
+
+    const was_outdated = zcu.outdated.swapRemove(unit) or zcu.potentially_outdated.swapRemove(unit);
+    const prev_failed = zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit);
+
+    if (was_outdated) {
+        dev.check(.incremental);
+        _ = zcu.outdated_ready.swapRemove(unit);
+        // No need for `deleteUnitExports` because we never export anything.
+        zcu.deleteUnitReferences(unit);
+        if (zcu.failed_analysis.fetchSwapRemove(unit)) |kv| {
+            kv.value.destroy(gpa);
+        }
+        _ = zcu.transitive_failed_analysis.swapRemove(unit);
+    } else {
+        if (prev_failed) return error.AnalysisFail;
+        // We use an arbitrary field to check if the state has been resolved yet.
+        const val = switch (stage) {
+            .main => zcu.builtin_decl_values.Type,
+            .panic => zcu.builtin_decl_values.Panic,
+            .va_list => zcu.builtin_decl_values.VaList,
+        };
+        if (val != .none) return;
+    }
+
+    const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed|
+        .{ any_changed or prev_failed, false }
+    else |err| switch (err) {
+        error.AnalysisFail => res: {
+            if (!zcu.failed_analysis.contains(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, unit, {});
+                log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(unit)});
+            }
+            break :res .{ !prev_failed, true };
+        },
+        error.OutOfMemory => {
+            // TODO: same as for `ensureComptimeUnitUpToDate` etc
+            return error.OutOfMemory;
+        },
+        error.GenericPoison => unreachable,
+        error.ComptimeReturn => unreachable,
+        error.ComptimeBreak => unreachable,
+    };
+
+    if (was_outdated) {
+        const dependee: InternPool.Dependee = .{ .memoized_state = stage };
+        if (any_changed) {
+            try zcu.markDependeeOutdated(.marked_po, dependee);
+        } else {
+            try zcu.markPoDependeeUpToDate(dependee);
+        }
+    }
+
+    if (new_failed) return error.AnalysisFail;
+}
+
+fn analyzeMemoizedState(pt: Zcu.PerThread, stage: InternPool.MemoizedStateStage) Zcu.CompileError!bool {
+    const zcu = pt.zcu;
+    const ip = &zcu.intern_pool;
+    const gpa = zcu.gpa;
+
+    const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
+
+    try zcu.analysis_in_progress.put(gpa, unit, {});
+    defer assert(zcu.analysis_in_progress.swapRemove(unit));
+
+    // Before we begin, collect:
+    // * The type `std`, and its namespace
+    // * The type `std.builtin`, and its namespace
+    // * A semi-reasonable source location
+    const std_file_imported = pt.importPkg(zcu.std_mod) catch return error.AnalysisFail;
+    try pt.ensureFileAnalyzed(std_file_imported.file_index);
+    const std_type: Type = .fromInterned(zcu.fileRootType(std_file_imported.file_index));
+    const std_namespace = std_type.getNamespaceIndex(zcu);
+    try pt.ensureNamespaceUpToDate(std_namespace);
+    const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
+    const builtin_nav = zcu.namespacePtr(std_namespace).pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse
+        @panic("lib/std.zig is corrupt and missing 'builtin'");
+    try pt.ensureNavValUpToDate(builtin_nav);
+    const builtin_type: Type = .fromInterned(ip.getNav(builtin_nav).status.fully_resolved.val);
+    const builtin_namespace = builtin_type.getNamespaceIndex(zcu);
+    try pt.ensureNamespaceUpToDate(builtin_namespace);
+    const src: Zcu.LazySrcLoc = .{
+        .base_node_inst = builtin_type.typeDeclInst(zcu).?,
+        .offset = .entire_file,
+    };
+
+    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 = .{ .instructions = .empty, .string_bytes = &.{}, .extra = &.{} },
+        .owner = 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();
+
+    var block: Sema.Block = .{
+        .parent = null,
+        .sema = &sema,
+        .namespace = std_namespace,
+        .instructions = .{},
+        .inlining = null,
+        .comptime_reason = .{ .reason = .{
+            .src = src,
+            .r = .{ .simple = .type },
+        } },
+        .src_base_inst = src.base_node_inst,
+        .type_name_ctx = .empty,
+    };
+    defer block.instructions.deinit(gpa);
+
+    return sema.analyzeMemoizedState(&block, src, builtin_namespace, stage);
+}
+
 /// Ensures that the state of the given `ComptimeUnit` is fully up-to-date, performing re-analysis
 /// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is
 /// free to ignore this, since the error is already registered.
@@ -2615,7 +2756,7 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE
     // result in circular dependency errors.
     // TODO: this can go away once we fix backends having to resolve `StackTrace`.
     // The codegen timing guarantees that the parameter types will be populated.
-    sema.resolveFnTypes(fn_ty) catch |err| switch (err) {
+    sema.resolveFnTypes(fn_ty, inner_block.nodeOffset(0)) catch |err| switch (err) {
         error.GenericPoison => unreachable,
         error.ComptimeReturn => unreachable,
         error.ComptimeBreak => unreachable,
@@ -3471,23 +3612,6 @@ pub fn structPackedFieldBitOffset(
     unreachable; // index out of bounds
 }
 
-pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!InternPool.Nav.Index {
-    const zcu = pt.zcu;
-    const gpa = zcu.gpa;
-    const ip = &zcu.intern_pool;
-    const std_file_imported = pt.importPkg(zcu.std_mod) catch @panic("failed to import lib/std.zig");
-    const std_type = Type.fromInterned(zcu.fileRootType(std_file_imported.file_index));
-    const std_namespace = zcu.namespacePtr(std_type.getNamespace(zcu).unwrap().?);
-    const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
-    const builtin_nav = std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse
-        @panic("lib/std.zig is corrupt and missing 'builtin'");
-    pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt");
-    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_id: InternPool.Nav.Index) Allocator.Error!Type {
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
src/Compilation.zig
@@ -3158,16 +3158,19 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
                 if (!refs.contains(anal_unit)) continue;
             }
 
-            const file_index = switch (anal_unit.unwrap()) {
-                .@"comptime" => |cu| ip.getComptimeUnit(cu).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),
-            };
+            report_ok: {
+                const file_index = switch (anal_unit.unwrap()) {
+                    .@"comptime" => |cu| ip.getComptimeUnit(cu).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),
+                    .memoized_state => break :report_ok, // always report std.builtin errors
+                };
 
-            // Skip errors for AnalUnits within files that had a parse failure.
-            // We'll try again once parsing succeeds.
-            if (!zcu.fileByIndex(file_index).okToReportErrors()) continue;
+                // Skip errors for AnalUnits within files that had a parse failure.
+                // We'll try again once parsing succeeds.
+                if (!zcu.fileByIndex(file_index).okToReportErrors()) continue;
+            }
 
             std.log.scoped(.zcu).debug("analysis error '{s}' reported from unit '{}'", .{
                 error_msg.msg,
@@ -3391,7 +3394,7 @@ pub fn addModuleErrorMsg(
             const ref = maybe_ref orelse break;
             const gop = try seen.getOrPut(gpa, ref.referencer);
             if (gop.found_existing) break;
-            if (ref_traces.items.len < max_references) {
+            if (ref_traces.items.len < max_references) skip: {
                 const src = ref.src.upgrade(zcu);
                 const source = try src.file_scope.getSource(gpa);
                 const span = try src.span(gpa);
@@ -3403,6 +3406,7 @@ pub fn addModuleErrorMsg(
                     .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),
+                    .memoized_state => break :skip,
                 };
                 try ref_traces.append(gpa, .{
                     .decl_name = try eb.addString(name),
@@ -3670,6 +3674,7 @@ fn performAllTheWorkInner(
             if (try zcu.findOutdatedToAnalyze()) |outdated| {
                 try comp.queueJob(switch (outdated.unwrap()) {
                     .func => |f| .{ .analyze_func = f },
+                    .memoized_state,
                     .@"comptime",
                     .nav_ty,
                     .nav_val,
@@ -3737,6 +3742,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
                 .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav),
                 .nav_val => |nav| pt.ensureNavValUpToDate(nav),
                 .type => |ty| if (pt.ensureTypeUpToDate(ty)) |_| {} else |err| err,
+                .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage),
                 .func => unreachable,
             };
             maybe_err catch |err| switch (err) {
src/InternPool.zig
@@ -49,6 +49,11 @@ namespace_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index)
 /// Dependencies on the (non-)existence of some name in a namespace.
 /// Value is index into `dep_entries` of the first dependency on this name.
 namespace_name_deps: std.AutoArrayHashMapUnmanaged(NamespaceNameKey, DepEntry.Index),
+// Dependencies on the value of fields memoized on `Zcu` (`panic_messages` etc).
+// If set, these are indices into `dep_entries` of the first dependency on this state.
+memoized_state_main_deps: DepEntry.Index.Optional,
+memoized_state_panic_deps: DepEntry.Index.Optional,
+memoized_state_va_list_deps: DepEntry.Index.Optional,
 
 /// Given a `Depender`, points to an entry in `dep_entries` whose `depender`
 /// matches. The `next_dependee` field can be used to iterate all such entries
@@ -87,6 +92,9 @@ pub const empty: InternPool = .{
     .interned_deps = .empty,
     .namespace_deps = .empty,
     .namespace_name_deps = .empty,
+    .memoized_state_main_deps = .none,
+    .memoized_state_panic_deps = .none,
+    .memoized_state_va_list_deps = .none,
     .first_dependency = .empty,
     .dep_entries = .empty,
     .free_dep_entries = .empty,
@@ -385,6 +393,7 @@ pub const AnalUnit = packed struct(u64) {
         nav_ty,
         type,
         func,
+        memoized_state,
     };
 
     pub const Unwrapped = union(Kind) {
@@ -399,6 +408,8 @@ pub const AnalUnit = packed struct(u64) {
         type: InternPool.Index,
         /// This `AnalUnit` analyzes the body of the given runtime function.
         func: InternPool.Index,
+        /// This `AnalUnit` resolves all state which is memoized in fields on `Zcu`.
+        memoized_state: MemoizedStateStage,
     };
 
     pub fn unwrap(au: AnalUnit) Unwrapped {
@@ -434,6 +445,16 @@ pub const AnalUnit = packed struct(u64) {
     };
 };
 
+pub const MemoizedStateStage = enum(u32) {
+    /// Everything other than panics and `VaList`.
+    main,
+    /// Everything within `std.builtin.Panic`.
+    /// Since the panic handler is user-provided, this must be able to reference the other memoized state.
+    panic,
+    /// Specifically `std.builtin.VaList`. See `Zcu.BuiltinDecl.stage`.
+    va_list,
+};
+
 pub const ComptimeUnit = extern struct {
     zir_index: TrackedInst.Index,
     namespace: NamespaceIndex,
@@ -769,6 +790,7 @@ pub const Dependee = union(enum) {
     interned: Index,
     namespace: TrackedInst.Index,
     namespace_name: NamespaceNameKey,
+    memoized_state: MemoizedStateStage,
 };
 
 pub fn removeDependenciesForDepender(ip: *InternPool, gpa: Allocator, depender: AnalUnit) void {
@@ -819,6 +841,11 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
         .interned => |x| ip.interned_deps.get(x),
         .namespace => |x| ip.namespace_deps.get(x),
         .namespace_name => |x| ip.namespace_name_deps.get(x),
+        .memoized_state => |stage| switch (stage) {
+            .main => ip.memoized_state_main_deps.unwrap(),
+            .panic => ip.memoized_state_panic_deps.unwrap(),
+            .va_list => ip.memoized_state_va_list_deps.unwrap(),
+        },
     } orelse return .{
         .ip = ip,
         .next_entry = .none,
@@ -848,6 +875,33 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
     // This block should allocate an entry and prepend it to the relevant `*_deps` list.
     // The `next` field should be correctly initialized; all other fields may be undefined.
     const new_index: DepEntry.Index = switch (dependee) {
+        .memoized_state => |stage| new_index: {
+            const deps = switch (stage) {
+                .main => &ip.memoized_state_main_deps,
+                .panic => &ip.memoized_state_panic_deps,
+                .va_list => &ip.memoized_state_va_list_deps,
+            };
+
+            if (deps.unwrap()) |first| {
+                if (ip.dep_entries.items[@intFromEnum(first)].depender == .none) {
+                    // Dummy entry, so we can reuse it rather than allocating a new one!
+                    break :new_index first;
+                }
+            }
+
+            // Prepend a new dependency.
+            const new_index: DepEntry.Index, const ptr = if (ip.free_dep_entries.popOrNull()) |new_index| new: {
+                break :new .{ new_index, &ip.dep_entries.items[@intFromEnum(new_index)] };
+            } else .{ @enumFromInt(ip.dep_entries.items.len), ip.dep_entries.addOneAssumeCapacity() };
+            if (deps.unwrap()) |old_first| {
+                ptr.next = old_first.toOptional();
+                ip.dep_entries.items[@intFromEnum(old_first)].prev = new_index.toOptional();
+            } else {
+                ptr.next = .none;
+            }
+            deps.* = new_index.toOptional();
+            break :new_index new_index;
+        },
         inline else => |dependee_payload, tag| new_index: {
             const gop = try switch (tag) {
                 .file => ip.file_deps,
@@ -857,6 +911,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
                 .interned => ip.interned_deps,
                 .namespace => ip.namespace_deps,
                 .namespace_name => ip.namespace_name_deps,
+                .memoized_state => comptime unreachable,
             }.getOrPut(gpa, dependee_payload);
 
             if (gop.found_existing and ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].depender == .none) {
src/Sema.zig
@@ -428,7 +428,7 @@ pub const Block = struct {
         } });
     }
 
-    fn nodeOffset(block: Block, node_offset: i32) LazySrcLoc {
+    pub fn nodeOffset(block: Block, node_offset: i32) LazySrcLoc {
         return block.src(LazySrcLoc.Offset.nodeOffset(node_offset));
     }
 
@@ -2149,7 +2149,7 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize)
     const addrs_ptr = try err_trace_block.addTy(.alloc, try pt.singleMutPtrType(addr_arr_ty));
 
     // var st: StackTrace = undefined;
-    const stack_trace_ty = try sema.getBuiltinType("StackTrace");
+    const stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(0), .StackTrace);
     try stack_trace_ty.resolveFields(pt);
     const st_ptr = try err_trace_block.addTy(.alloc, try pt.singleMutPtrType(stack_trace_ty));
 
@@ -6600,6 +6600,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void {
         .nav_val,
         .nav_ty,
         .type,
+        .memoized_state,
         => return, // does nothing outside a function
     };
     ip.funcSetDisableInstrumentation(func);
@@ -6609,7 +6610,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void {
 fn zirSetFloatMode(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
     const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
     const src = block.builtinCallArgSrc(extra.node, 0);
-    block.float_mode = try sema.resolveBuiltinEnum(block, src, extra.operand, "FloatMode", .{ .simple = .operand_setFloatMode });
+    block.float_mode = try sema.resolveBuiltinEnum(block, src, extra.operand, .FloatMode, .{ .simple = .operand_setFloatMode });
 }
 
 fn zirSetRuntimeSafety(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
@@ -6917,7 +6918,7 @@ fn lookupInNamespace(
 
         ignore_self: {
             const skip_nav = switch (sema.owner.unwrap()) {
-                .@"comptime", .type, .func => break :ignore_self,
+                .@"comptime", .type, .func, .memoized_state => break :ignore_self,
                 .nav_ty, .nav_val => |nav| nav,
             };
             var i: usize = 0;
@@ -6990,7 +6991,7 @@ pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref
 
     if (!block.ownerModule().error_tracing) return .none;
 
-    const stack_trace_ty = try sema.getBuiltinType("StackTrace");
+    const stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(0), .StackTrace);
     try stack_trace_ty.resolveFields(pt);
     const field_name = try zcu.intern_pool.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
     const field_index = sema.structFieldIndex(block, stack_trace_ty, field_name, LazySrcLoc.unneeded) catch |err| switch (err) {
@@ -7032,7 +7033,7 @@ fn popErrorReturnTrace(
         // AstGen determined this result does not go to an error-handling expr (try/catch/return etc.), or
         // the result is comptime-known to be a non-error. Either way, pop unconditionally.
 
-        const stack_trace_ty = try sema.getBuiltinType("StackTrace");
+        const stack_trace_ty = try sema.getBuiltinType(src, .StackTrace);
         try stack_trace_ty.resolveFields(pt);
         const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
         const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
@@ -7058,7 +7059,7 @@ fn popErrorReturnTrace(
         defer then_block.instructions.deinit(gpa);
 
         // If non-error, then pop the error return trace by restoring the index.
-        const stack_trace_ty = try sema.getBuiltinType("StackTrace");
+        const stack_trace_ty = try sema.getBuiltinType(src, .StackTrace);
         try stack_trace_ty.resolveFields(pt);
         const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
         const err_return_trace = try then_block.addTy(.err_return_trace, ptr_stack_trace_ty);
@@ -7178,7 +7179,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_ty, .nav_val => input_is_error = false,
+        .@"comptime", .type, .memoized_state, .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;
@@ -7201,7 +7202,7 @@ fn zirCall(
         // If any input is an error-type, we might need to pop any trace it generated. Otherwise, we only
         // need to clean-up our own trace if we were passed to a non-error-handling expression.
         if (input_is_error or (pop_error_return_trace and return_ty.isError(zcu))) {
-            const stack_trace_ty = try sema.getBuiltinType("StackTrace");
+            const stack_trace_ty = try sema.getBuiltinType(call_src, .StackTrace);
             try stack_trace_ty.resolveFields(pt);
             const field_name = try zcu.intern_pool.getOrPutString(sema.gpa, pt.tid, "index", .no_embedded_nulls);
             const field_index = try sema.structFieldIndex(block, stack_trace_ty, field_name, call_src);
@@ -8091,7 +8092,7 @@ fn analyzeCall(
         if (call_dbg_node) |some| try sema.zirDbgStmt(block, some);
 
         switch (sema.owner.unwrap()) {
-            .@"comptime", .nav_ty, .nav_val, .type => {},
+            .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
             .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) {
                 ip.funcSetCallsOrAwaitsErrorableFn(owner_func);
             },
@@ -8557,7 +8558,7 @@ fn instantiateGenericCall(
     if (call_dbg_node) |some| try sema.zirDbgStmt(block, some);
 
     switch (sema.owner.unwrap()) {
-        .@"comptime", .nav_ty, .nav_val, .type => {},
+        .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
         .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) {
             ip.funcSetCallsOrAwaitsErrorableFn(owner_func);
         },
@@ -9537,6 +9538,7 @@ fn zirFunc(
     const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index);
     const target = zcu.getTarget();
     const ret_ty_src = block.src(.{ .node_offset_fn_type_ret_ty = inst_data.src_node });
+    const src = block.nodeOffset(inst_data.src_node);
 
     var extra_index = extra.end;
 
@@ -9588,7 +9590,7 @@ fn zirFunc(
                 // error by trying to evaluate `std.builtin.CallingConvention.c`, so for consistency,
                 // let's eval that now and just get the transitive error. (It's guaranteed to error
                 // because it does the exact `cCallingConvention` call we just did.)
-                const cc_type = try sema.getBuiltinType("CallingConvention");
+                const cc_type = try sema.getBuiltinType(src, .CallingConvention);
                 _ = try sema.namespaceLookupVal(
                     block,
                     LazySrcLoc.unneeded,
@@ -10302,7 +10304,7 @@ fn finishFunc(
     if (!final_is_generic and sema.wantErrorReturnTracing(return_type)) {
         // Make sure that StackTrace's fields are resolved so that the backend can
         // lower this fn type.
-        const unresolved_stack_trace_ty = try sema.getBuiltinType("StackTrace");
+        const unresolved_stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(0), .StackTrace);
         try unresolved_stack_trace_ty.resolveFields(pt);
     }
 
@@ -14283,7 +14285,7 @@ fn maybeErrorUnwrap(
                 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
                 const msg_inst = try sema.resolveInst(inst_data.operand);
 
-                const panic_fn = try getPanicInnerFn(sema, block, operand_src, "call");
+                const panic_fn = try getBuiltin(sema, operand_src, .@"Panic.call");
                 const err_return_trace = try sema.getErrorReturnTrace(block);
                 const args: [3]Air.Inst.Ref = .{ msg_inst, err_return_trace, .null_value };
                 try sema.callBuiltin(block, operand_src, Air.internedToRef(panic_fn), .auto, &args, .@"safety check");
@@ -17477,7 +17479,7 @@ fn analyzeArithmetic(
     if (block.wantSafety() and want_safety and scalar_tag == .int) {
         if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
             if (air_tag != air_tag_safe) {
-                _ = try sema.preparePanicId(block, src, .integer_overflow);
+                _ = try sema.preparePanicId(src, .integer_overflow);
             }
             return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
         } else {
@@ -18381,7 +18383,7 @@ fn zirBuiltinSrc(
         } });
     };
 
-    const src_loc_ty = try sema.getBuiltinType("SourceLocation");
+    const src_loc_ty = try sema.getBuiltinType(block.nodeOffset(0), .SourceLocation);
     const fields = .{
         // module: [:0]const u8,
         module_name_val,
@@ -18408,7 +18410,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
     const src = block.nodeOffset(inst_data.src_node);
     const ty = try sema.resolveType(block, src, inst_data.operand);
-    const type_info_ty = try sema.getBuiltinType("Type");
+    const type_info_ty = try sema.getBuiltinType(src, .Type);
     const type_info_tag_ty = type_info_ty.unionTagType(zcu).?;
 
     if (ty.typeDeclInst(zcu)) |type_decl_inst| {
@@ -18428,8 +18430,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         => |type_info_tag| return unionInitFromEnumTag(sema, block, src, type_info_ty, @intFromEnum(type_info_tag), .void_value),
 
         .@"fn" => {
-            const fn_info_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Fn");
-            const param_info_ty = try getBuiltinInnerType(sema, block, src, fn_info_ty, "Type.Fn", "Param");
+            const fn_info_ty = try sema.getBuiltinType(src, .@"Type.Fn");
+            const param_info_ty = try sema.getBuiltinType(src, .@"Type.Fn.Param");
 
             const func_ty_info = zcu.typeToFunc(ty).?;
             const param_vals = try sema.arena.alloc(InternPool.Index, func_ty_info.param_types.len);
@@ -18499,7 +18501,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     func_ty_info.return_type,
             } });
 
-            const callconv_ty = try sema.getBuiltinType("CallingConvention");
+            const callconv_ty = try sema.getBuiltinType(src, .CallingConvention);
             const callconv_val = Value.uninterpret(func_ty_info.cc, callconv_ty, pt) catch |err| switch (err) {
                 error.TypeMismatch => @panic("std.builtin is corrupt"),
                 error.OutOfMemory => |e| return e,
@@ -18527,8 +18529,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .int => {
-            const int_info_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Int");
-            const signedness_ty = try sema.getBuiltinType("Signedness");
+            const int_info_ty = try sema.getBuiltinType(src, .@"Type.Int");
+            const signedness_ty = try sema.getBuiltinType(src, .Signedness);
             const info = ty.intInfo(zcu);
             const field_values = .{
                 // signedness: Signedness,
@@ -18546,7 +18548,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .float => {
-            const float_info_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Float");
+            const float_info_ty = try sema.getBuiltinType(src, .@"Type.Float");
 
             const field_vals = .{
                 // bits: u16,
@@ -18568,9 +18570,9 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             else
                 try Type.fromInterned(info.child).lazyAbiAlignment(pt);
 
-            const addrspace_ty = try sema.getBuiltinType("AddressSpace");
-            const pointer_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Pointer");
-            const ptr_size_ty = try getBuiltinInnerType(sema, block, src, pointer_ty, "Type.Pointer", "Size");
+            const addrspace_ty = try sema.getBuiltinType(src, .AddressSpace);
+            const pointer_ty = try sema.getBuiltinType(src, .@"Type.Pointer");
+            const ptr_size_ty = try sema.getBuiltinType(src, .@"Type.Pointer.Size");
 
             const field_values = .{
                 // size: Size,
@@ -18603,7 +18605,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .array => {
-            const array_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Array");
+            const array_field_ty = try sema.getBuiltinType(src, .@"Type.Array");
 
             const info = ty.arrayInfo(zcu);
             const field_values = .{
@@ -18624,7 +18626,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .vector => {
-            const vector_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Vector");
+            const vector_field_ty = try sema.getBuiltinType(src, .@"Type.Vector");
 
             const info = ty.arrayInfo(zcu);
             const field_values = .{
@@ -18643,7 +18645,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .optional => {
-            const optional_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Optional");
+            const optional_field_ty = try sema.getBuiltinType(src, .@"Type.Optional");
 
             const field_values = .{
                 // child: type,
@@ -18660,7 +18662,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         },
         .error_set => {
             // Get the Error type
-            const error_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Error");
+            const error_field_ty = try sema.getBuiltinType(src, .@"Type.Error");
 
             // Build our list of Error values
             // Optional value is only null if anyerror
@@ -18756,7 +18758,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .error_union => {
-            const error_union_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "ErrorUnion");
+            const error_union_field_ty = try sema.getBuiltinType(src, .@"Type.ErrorUnion");
 
             const field_values = .{
                 // error_set: type,
@@ -18776,7 +18778,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         .@"enum" => {
             const is_exhaustive = Value.makeBool(ip.loadEnumType(ty.toIntern()).tag_mode != .nonexhaustive);
 
-            const enum_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "EnumField");
+            const enum_field_ty = try sema.getBuiltinType(src, .@"Type.EnumField");
 
             const enum_field_vals = try sema.arena.alloc(InternPool.Index, ip.loadEnumType(ty.toIntern()).names.len);
             for (enum_field_vals, 0..) |*field_val, tag_index| {
@@ -18861,9 +18863,9 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 } });
             };
 
-            const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, ip.loadEnumType(ty.toIntern()).namespace.toOptional());
+            const decls_val = try sema.typeInfoDecls(block, src, ip.loadEnumType(ty.toIntern()).namespace.toOptional());
 
-            const type_enum_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Enum");
+            const type_enum_ty = try sema.getBuiltinType(src, .@"Type.Enum");
 
             const field_values = .{
                 // tag_type: type,
@@ -18885,8 +18887,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .@"union" => {
-            const type_union_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Union");
-            const union_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "UnionField");
+            const type_union_ty = try sema.getBuiltinType(src, .@"Type.Union");
+            const union_field_ty = try sema.getBuiltinType(src, .@"Type.UnionField");
 
             try ty.resolveLayout(pt); // Getting alignment requires type layout
             const union_obj = zcu.typeToUnion(ty).?;
@@ -18974,14 +18976,14 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 } });
             };
 
-            const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, ty.getNamespaceIndex(zcu).toOptional());
+            const decls_val = try sema.typeInfoDecls(block, src, ty.getNamespaceIndex(zcu).toOptional());
 
             const enum_tag_ty_val = try pt.intern(.{ .opt = .{
                 .ty = (try pt.optionalType(.type_type)).toIntern(),
                 .val = if (ty.unionTagType(zcu)) |tag_ty| tag_ty.toIntern() else .none,
             } });
 
-            const container_layout_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "ContainerLayout");
+            const container_layout_ty = try sema.getBuiltinType(src, .@"Type.ContainerLayout");
 
             const field_values = .{
                 // layout: ContainerLayout,
@@ -19004,8 +19006,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .@"struct" => {
-            const type_struct_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Struct");
-            const struct_field_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "StructField");
+            const type_struct_ty = try sema.getBuiltinType(src, .@"Type.Struct");
+            const struct_field_ty = try sema.getBuiltinType(src, .@"Type.StructField");
 
             try ty.resolveLayout(pt); // Getting alignment requires type layout
 
@@ -19169,7 +19171,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 } });
             };
 
-            const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, ty.getNamespace(zcu));
+            const decls_val = try sema.typeInfoDecls(block, src, ty.getNamespace(zcu));
 
             const backing_integer_val = try pt.intern(.{ .opt = .{
                 .ty = (try pt.optionalType(.type_type)).toIntern(),
@@ -19179,7 +19181,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 } else .none,
             } });
 
-            const container_layout_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "ContainerLayout");
+            const container_layout_ty = try sema.getBuiltinType(src, .@"Type.ContainerLayout");
 
             const layout = ty.containerLayout(zcu);
 
@@ -19205,10 +19207,10 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             })));
         },
         .@"opaque" => {
-            const type_opaque_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Opaque");
+            const type_opaque_ty = try sema.getBuiltinType(src, .@"Type.Opaque");
 
             try ty.resolveFields(pt);
-            const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, ty.getNamespace(zcu));
+            const decls_val = try sema.typeInfoDecls(block, src, ty.getNamespace(zcu));
 
             const field_values = .{
                 // decls: []const Declaration,
@@ -19232,14 +19234,13 @@ fn typeInfoDecls(
     sema: *Sema,
     block: *Block,
     src: LazySrcLoc,
-    type_info_ty: Type,
     opt_namespace: InternPool.OptionalNamespaceIndex,
 ) CompileError!InternPool.Index {
     const pt = sema.pt;
     const zcu = pt.zcu;
     const gpa = sema.gpa;
 
-    const declaration_ty = try getBuiltinInnerType(sema, block, src, type_info_ty, "Type", "Declaration");
+    const declaration_ty = try sema.getBuiltinType(src, .@"Type.Declaration");
 
     var decl_vals = std.ArrayList(InternPool.Index).init(gpa);
     defer decl_vals.deinit();
@@ -20181,11 +20182,11 @@ fn retWithErrTracing(
         else => true,
     };
     const gpa = sema.gpa;
-    const stack_trace_ty = try sema.getBuiltinType("StackTrace");
+    const stack_trace_ty = try sema.getBuiltinType(src, .StackTrace);
     try stack_trace_ty.resolveFields(pt);
     const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
     const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
-    const return_err_fn = try sema.getBuiltin("returnError");
+    const return_err_fn = Air.internedToRef(try sema.getBuiltin(src, .returnError));
     const args: [1]Air.Inst.Ref = .{err_return_trace};
 
     if (!need_check) {
@@ -21607,7 +21608,7 @@ fn getErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
-    const stack_trace_ty = try sema.getBuiltinType("StackTrace");
+    const stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(0), .StackTrace);
     try stack_trace_ty.resolveFields(pt);
     const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
     const opt_ptr_stack_trace_ty = try pt.optionalType(ptr_stack_trace_ty.toIntern());
@@ -21616,7 +21617,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_ty, .nav_val, .type => {},
+        .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
     }
     return Air.internedToRef(try pt.intern(.{ .opt = .{
         .ty = opt_ptr_stack_trace_ty.toIntern(),
@@ -21896,7 +21897,7 @@ fn zirReify(
             },
         },
     };
-    const type_info_ty = try sema.getBuiltinType("Type");
+    const type_info_ty = try sema.getBuiltinType(src, .Type);
     const uncasted_operand = try sema.resolveInst(extra.operand);
     const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src);
     const val = try sema.resolveConstDefinedValue(block, operand_src, type_info, .{ .simple = .operand_Type });
@@ -23156,7 +23157,7 @@ fn reifyStruct(
 
 fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
-    const va_list_ty = try sema.getBuiltinType("VaList");
+    const va_list_ty = try sema.getBuiltinType(src, .VaList);
     const va_list_ptr = try pt.singleMutPtrType(va_list_ty);
 
     const inst = try sema.resolveInst(zir_ref);
@@ -23195,7 +23196,7 @@ fn zirCVaCopy(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData)
     const va_list_src = block.builtinCallArgSrc(extra.node, 0);
 
     const va_list_ref = try sema.resolveVaListRef(block, va_list_src, extra.operand);
-    const va_list_ty = try sema.getBuiltinType("VaList");
+    const va_list_ty = try sema.getBuiltinType(src, .VaList);
 
     try sema.requireRuntimeBlock(block, src, null);
     return block.addTyOp(.c_va_copy, va_list_ty, va_list_ref);
@@ -23215,7 +23216,7 @@ fn zirCVaEnd(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) C
 fn zirCVaStart(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
     const src = block.nodeOffset(@bitCast(extended.operand));
 
-    const va_list_ty = try sema.getBuiltinType("VaList");
+    const va_list_ty = try sema.getBuiltinType(src, .VaList);
     try sema.requireRuntimeBlock(block, src, null);
     return block.addInst(.{
         .tag = .c_va_start,
@@ -24821,7 +24822,7 @@ fn resolveExportOptions(
     const zcu = pt.zcu;
     const gpa = sema.gpa;
     const ip = &zcu.intern_pool;
-    const export_options_ty = try sema.getBuiltinType("ExportOptions");
+    const export_options_ty = try sema.getBuiltinType(src, .ExportOptions);
     const air_ref = try sema.resolveInst(zir_ref);
     const options = try sema.coerce(block, export_options_ty, air_ref, src);
 
@@ -24871,15 +24872,15 @@ fn resolveBuiltinEnum(
     block: *Block,
     src: LazySrcLoc,
     zir_ref: Zir.Inst.Ref,
-    comptime name: []const u8,
+    comptime name: Zcu.BuiltinDecl,
     reason: ComptimeReason,
-) CompileError!@field(std.builtin, name) {
+) CompileError!@field(std.builtin, @tagName(name)) {
     const pt = sema.pt;
-    const ty = try sema.getBuiltinType(name);
+    const ty = try sema.getBuiltinType(src, name);
     const air_ref = try sema.resolveInst(zir_ref);
     const coerced = try sema.coerce(block, ty, air_ref, src);
     const val = try sema.resolveConstDefinedValue(block, src, coerced, reason);
-    return pt.zcu.toEnum(@field(std.builtin, name), val);
+    return pt.zcu.toEnum(@field(std.builtin, @tagName(name)), val);
 }
 
 fn resolveAtomicOrder(
@@ -24889,7 +24890,7 @@ fn resolveAtomicOrder(
     zir_ref: Zir.Inst.Ref,
     reason: ComptimeReason,
 ) CompileError!std.builtin.AtomicOrder {
-    return sema.resolveBuiltinEnum(block, src, zir_ref, "AtomicOrder", reason);
+    return sema.resolveBuiltinEnum(block, src, zir_ref, .AtomicOrder, reason);
 }
 
 fn resolveAtomicRmwOp(
@@ -24898,7 +24899,7 @@ fn resolveAtomicRmwOp(
     src: LazySrcLoc,
     zir_ref: Zir.Inst.Ref,
 ) CompileError!std.builtin.AtomicRmwOp {
-    return sema.resolveBuiltinEnum(block, src, zir_ref, "AtomicRmwOp", .{ .simple = .operand_atomicRmw_operation });
+    return sema.resolveBuiltinEnum(block, src, zir_ref, .AtomicRmwOp, .{ .simple = .operand_atomicRmw_operation });
 }
 
 fn zirCmpxchg(
@@ -25078,7 +25079,7 @@ fn zirReduce(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
     const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
     const op_src = block.builtinCallArgSrc(inst_data.src_node, 0);
     const operand_src = block.builtinCallArgSrc(inst_data.src_node, 1);
-    const operation = try sema.resolveBuiltinEnum(block, op_src, extra.lhs, "ReduceOp", .{ .simple = .operand_reduce_operation });
+    const operation = try sema.resolveBuiltinEnum(block, op_src, extra.lhs, .ReduceOp, .{ .simple = .operand_reduce_operation });
     const operand = try sema.resolveInst(extra.rhs);
     const operand_ty = sema.typeOf(operand);
     const pt = sema.pt;
@@ -25668,7 +25669,7 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
     const extra = sema.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data;
     const func = try sema.resolveInst(extra.callee);
 
-    const modifier_ty = try sema.getBuiltinType("CallModifier");
+    const modifier_ty = try sema.getBuiltinType(call_src, .CallModifier);
     const air_ref = try sema.resolveInst(extra.modifier);
     const modifier_ref = try sema.coerce(block, modifier_ty, air_ref, modifier_src);
     const modifier_val = try sema.resolveConstDefinedValue(block, modifier_src, modifier_ref, .{ .simple = .call_modifier });
@@ -26630,13 +26631,13 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         const body = sema.code.bodySlice(extra_index, body_len);
         extra_index += body.len;
 
-        const cc_ty = try sema.getBuiltinType("CallingConvention");
+        const cc_ty = try sema.getBuiltinType(cc_src, .CallingConvention);
         const val = try sema.resolveGenericBody(block, cc_src, body, inst, cc_ty, .{ .simple = .@"callconv" });
         break :blk try sema.analyzeValueAsCallconv(block, cc_src, val);
     } else if (extra.data.bits.has_cc_ref) blk: {
         const cc_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
         extra_index += 1;
-        const cc_ty = try sema.getBuiltinType("CallingConvention");
+        const cc_ty = try sema.getBuiltinType(cc_src, .CallingConvention);
         const uncoerced_cc = try sema.resolveInst(cc_ref);
         const coerced_cc = try sema.coerce(block, cc_ty, uncoerced_cc, cc_src);
         const cc_val = try sema.resolveConstDefinedValue(block, cc_src, coerced_cc, .{ .simple = .@"callconv" });
@@ -26656,7 +26657,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
                     // error by trying to evaluate `std.builtin.CallingConvention.c`, so for consistency,
                     // let's eval that now and just get the transitive error. (It's guaranteed to error
                     // because it does the exact `cCallingConvention` call we just did.)
-                    const cc_type = try sema.getBuiltinType("CallingConvention");
+                    const cc_type = try sema.getBuiltinType(cc_src, .CallingConvention);
                     _ = try sema.namespaceLookupVal(
                         block,
                         LazySrcLoc.unneeded,
@@ -26834,7 +26835,7 @@ fn resolvePrefetchOptions(
     const zcu = pt.zcu;
     const gpa = sema.gpa;
     const ip = &zcu.intern_pool;
-    const options_ty = try sema.getBuiltinType("PrefetchOptions");
+    const options_ty = try sema.getBuiltinType(src, .PrefetchOptions);
     const options = try sema.coerce(block, options_ty, try sema.resolveInst(zir_ref), src);
 
     const rw_src = block.src(.{ .init_field_rw = src.offset.node_offset_builtin_call_arg.builtin_call_node });
@@ -26902,7 +26903,7 @@ fn resolveExternOptions(
     const gpa = sema.gpa;
     const ip = &zcu.intern_pool;
     const options_inst = try sema.resolveInst(zir_ref);
-    const extern_options_ty = try sema.getBuiltinType("ExternOptions");
+    const extern_options_ty = try sema.getBuiltinType(src, .ExternOptions);
     const options = try sema.coerce(block, extern_options_ty, options_inst, src);
 
     const name_src = block.src(.{ .init_field_name = src.offset.node_offset_builtin_call_arg.builtin_call_node });
@@ -27004,6 +27005,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).?,
+            .memoized_state => unreachable,
             .nav_ty, .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index,
             .func => |func| zir_index: {
                 const func_info = zcu.funcInfo(func);
@@ -27081,23 +27083,25 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
     const src = block.nodeOffset(@bitCast(extended.operand));
     const value: Zir.Inst.BuiltinValue = @enumFromInt(extended.small);
 
-    const type_name = switch (value) {
-        .atomic_order => "AtomicOrder",
-        .atomic_rmw_op => "AtomicRmwOp",
-        .calling_convention => "CallingConvention",
-        .address_space => "AddressSpace",
-        .float_mode => "FloatMode",
-        .reduce_op => "ReduceOp",
-        .call_modifier => "CallModifier",
-        .prefetch_options => "PrefetchOptions",
-        .export_options => "ExportOptions",
-        .extern_options => "ExternOptions",
-        .type_info => "Type",
-        .branch_hint => "BranchHint",
+    const ty = switch (value) {
+        // zig fmt: off
+        .atomic_order       => try sema.getBuiltinType(src, .AtomicOrder),
+        .atomic_rmw_op      => try sema.getBuiltinType(src, .AtomicRmwOp),
+        .calling_convention => try sema.getBuiltinType(src, .CallingConvention),
+        .address_space      => try sema.getBuiltinType(src, .AddressSpace),
+        .float_mode         => try sema.getBuiltinType(src, .FloatMode),
+        .reduce_op          => try sema.getBuiltinType(src, .ReduceOp),
+        .call_modifier      => try sema.getBuiltinType(src, .CallModifier),
+        .prefetch_options   => try sema.getBuiltinType(src, .PrefetchOptions),
+        .export_options     => try sema.getBuiltinType(src, .ExportOptions),
+        .extern_options     => try sema.getBuiltinType(src, .ExternOptions),
+        .type_info          => try sema.getBuiltinType(src, .Type),
+        .branch_hint        => try sema.getBuiltinType(src, .BranchHint),
+        // zig fmt: on
 
         // Values are handled here.
         .calling_convention_c => {
-            const callconv_ty = try sema.getBuiltinType("CallingConvention");
+            const callconv_ty = try sema.getBuiltinType(src, .CallingConvention);
             return try sema.namespaceLookupVal(
                 block,
                 src,
@@ -27107,7 +27111,7 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
         },
         .calling_convention_inline => {
             comptime assert(@typeInfo(std.builtin.CallingConvention.Tag).@"enum".tag_type == u8);
-            const callconv_ty = try sema.getBuiltinType("CallingConvention");
+            const callconv_ty = try sema.getBuiltinType(src, .CallingConvention);
             const callconv_tag_ty = callconv_ty.unionTagType(zcu) orelse @panic("std.builtin is corrupt");
             const inline_tag_val = try pt.enumValue(
                 callconv_tag_ty,
@@ -27119,7 +27123,6 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
             return sema.coerce(block, callconv_ty, Air.internedToRef(inline_tag_val.toIntern()), src);
         },
     };
-    const ty = try sema.getBuiltinType(type_name);
     return Air.internedToRef(ty.toIntern());
 }
 
@@ -27158,7 +27161,7 @@ fn zirBranchHint(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
     const uncoerced_hint = try sema.resolveInst(extra.operand);
     const operand_src = block.builtinCallArgSrc(extra.node, 0);
 
-    const hint_ty = try sema.getBuiltinType("BranchHint");
+    const hint_ty = try sema.getBuiltinType(operand_src, .BranchHint);
     const coerced_hint = try sema.coerce(block, hint_ty, uncoerced_hint, operand_src);
     const hint_val = try sema.resolveConstDefinedValue(block, operand_src, coerced_hint, .{ .simple = .operand_branchHint });
 
@@ -27603,61 +27606,19 @@ fn explainWhyTypeIsNotPacked(
     }
 }
 
-fn prepareSimplePanic(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-
-    if (zcu.panic_func_index == .none) {
-        zcu.panic_func_index = try sema.getPanicInnerFn(block, src, "call");
-        // Here, function body analysis must be queued up so that backends can
-        // make calls to this function.
-        try zcu.ensureFuncBodyAnalysisQueued(zcu.panic_func_index);
-    }
-
-    if (zcu.null_stack_trace == .none) {
-        const stack_trace_ty = try sema.getBuiltinType("StackTrace");
-        try stack_trace_ty.resolveFields(pt);
-        const target = zcu.getTarget();
-        const ptr_stack_trace_ty = try pt.ptrTypeSema(.{
-            .child = stack_trace_ty.toIntern(),
-            .flags = .{
-                .address_space = target_util.defaultAddressSpace(target, .global_constant),
-            },
-        });
-        const opt_ptr_stack_trace_ty = try pt.optionalType(ptr_stack_trace_ty.toIntern());
-        zcu.null_stack_trace = try pt.intern(.{ .opt = .{
-            .ty = opt_ptr_stack_trace_ty.toIntern(),
-            .val = .none,
-        } });
-    }
-}
-
 /// Backends depend on panic decls being available when lowering safety-checked
 /// instructions. This function ensures the panic function will be available to
 /// be called during that time.
-fn preparePanicId(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.PanicId) !InternPool.Nav.Index {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const gpa = sema.gpa;
-    if (zcu.panic_messages[@intFromEnum(panic_id)].unwrap()) |x| return x;
-
-    try sema.prepareSimplePanic(block, src);
-
-    const panic_ty = try sema.getBuiltinType("Panic");
-    const panic_messages_ty = try sema.getBuiltinInnerType(block, src, panic_ty, "Panic", "messages");
-    const msg_nav_index = (sema.namespaceLookup(
-        block,
-        LazySrcLoc.unneeded,
-        panic_messages_ty.getNamespaceIndex(zcu),
-        try zcu.intern_pool.getOrPutString(gpa, pt.tid, @tagName(panic_id), .no_embedded_nulls),
-    ) catch |err| switch (err) {
-        error.AnalysisFail => return error.AnalysisFail,
-        error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
-        error.OutOfMemory => |e| return e,
-    }).?;
-    try sema.ensureNavResolved(src, msg_nav_index, .fully);
-    zcu.panic_messages[@intFromEnum(panic_id)] = msg_nav_index.toOptional();
-    return msg_nav_index;
+fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.PanicId) !InternPool.Index {
+    const zcu = sema.pt.zcu;
+    try sema.ensureMemoizedStateResolved(src, .panic);
+    try zcu.ensureFuncBodyAnalysisQueued(zcu.builtin_decl_values.@"Panic.call");
+    switch (panic_id) {
+        inline else => |ct_panic_id| {
+            const name = "Panic.messages." ++ @tagName(ct_panic_id);
+            return @field(zcu.builtin_decl_values, name);
+        },
+    }
 }
 
 fn addSafetyCheck(
@@ -27761,10 +27722,10 @@ fn panicWithMsg(sema: *Sema, block: *Block, src: LazySrcLoc, msg_inst: Air.Inst.
         return;
     }
 
-    try sema.prepareSimplePanic(block, src);
+    try sema.ensureMemoizedStateResolved(src, .panic);
+    try zcu.ensureFuncBodyAnalysisQueued(zcu.builtin_decl_values.@"Panic.call");
 
-    const panic_func = zcu.funcInfo(zcu.panic_func_index);
-    const panic_fn = try sema.analyzeNavVal(block, src, panic_func.owner_nav);
+    const panic_fn = Air.internedToRef(zcu.builtin_decl_values.@"Panic.call");
     const null_stack_trace = Air.internedToRef(zcu.null_stack_trace);
 
     const opt_usize_ty = try pt.optionalType(.usize_type);
@@ -27812,7 +27773,7 @@ fn safetyPanicUnwrapError(sema: *Sema, block: *Block, src: LazySrcLoc, err: Air.
     if (!zcu.backendSupportsFeature(.panic_fn)) {
         _ = try block.addNoOp(.trap);
     } else {
-        const panic_fn = try getPanicInnerFn(sema, block, src, "unwrapError");
+        const panic_fn = try getBuiltin(sema, src, .@"Panic.unwrapError");
         const err_return_trace = try sema.getErrorReturnTrace(block);
         const args: [2]Air.Inst.Ref = .{ err_return_trace, err };
         try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &args, .@"safety check");
@@ -27829,7 +27790,7 @@ fn addSafetyCheckIndexOob(
 ) !void {
     assert(!parent_block.isComptime());
     const ok = try parent_block.addBinOp(cmp_op, index, len);
-    return addSafetyCheckCall(sema, parent_block, src, ok, "outOfBounds", &.{ index, len });
+    return addSafetyCheckCall(sema, parent_block, src, ok, .@"Panic.outOfBounds", &.{ index, len });
 }
 
 fn addSafetyCheckInactiveUnionField(
@@ -27841,7 +27802,7 @@ fn addSafetyCheckInactiveUnionField(
 ) !void {
     assert(!parent_block.isComptime());
     const ok = try parent_block.addBinOp(.cmp_eq, active_tag, wanted_tag);
-    return addSafetyCheckCall(sema, parent_block, src, ok, "inactiveUnionField", &.{ active_tag, wanted_tag });
+    return addSafetyCheckCall(sema, parent_block, src, ok, .@"Panic.inactiveUnionField", &.{ active_tag, wanted_tag });
 }
 
 fn addSafetyCheckSentinelMismatch(
@@ -27882,7 +27843,7 @@ fn addSafetyCheckSentinelMismatch(
         break :ok try parent_block.addBinOp(.cmp_eq, expected_sentinel, actual_sentinel);
     };
 
-    return addSafetyCheckCall(sema, parent_block, src, ok, "sentinelMismatch", &.{
+    return addSafetyCheckCall(sema, parent_block, src, ok, .@"Panic.sentinelMismatch", &.{
         expected_sentinel, actual_sentinel,
     });
 }
@@ -27892,7 +27853,7 @@ fn addSafetyCheckCall(
     parent_block: *Block,
     src: LazySrcLoc,
     ok: Air.Inst.Ref,
-    func_name: []const u8,
+    comptime func_decl: Zcu.BuiltinDecl,
     args: []const Air.Inst.Ref,
 ) !void {
     assert(!parent_block.isComptime());
@@ -27916,7 +27877,7 @@ fn addSafetyCheckCall(
     if (!zcu.backendSupportsFeature(.panic_fn)) {
         _ = try fail_block.addNoOp(.trap);
     } else {
-        const panic_fn = try getPanicInnerFn(sema, &fail_block, src, func_name);
+        const panic_fn = try getBuiltin(sema, src, func_decl);
         try sema.callBuiltin(&fail_block, src, Air.internedToRef(panic_fn), .auto, args, .@"safety check");
     }
 
@@ -27925,9 +27886,8 @@ fn addSafetyCheckCall(
 
 /// This does not set `sema.branch_hint`.
 fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.PanicId) CompileError!void {
-    const msg_nav_index = try sema.preparePanicId(block, src, panic_id);
-    const msg_inst = try sema.analyzeNavVal(block, src, msg_nav_index);
-    try sema.panicWithMsg(block, src, msg_inst, .@"safety check");
+    const msg_val = try sema.preparePanicId(src, panic_id);
+    try sema.panicWithMsg(block, src, Air.internedToRef(msg_val), .@"safety check");
 }
 
 fn emitBackwardBranch(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
@@ -32524,6 +32484,19 @@ fn addTypeReferenceEntry(
     try zcu.addTypeReference(sema.owner, referenced_type, src);
 }
 
+fn ensureMemoizedStateResolved(sema: *Sema, src: LazySrcLoc, stage: InternPool.MemoizedStateStage) SemaError!void {
+    const pt = sema.pt;
+
+    const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
+    try sema.addReferenceEntry(src, unit);
+    try sema.declareDependency(.{ .memoized_state = stage });
+
+    if (pt.zcu.analysis_in_progress.contains(unit)) {
+        return sema.failWithOwnedErrorMsg(null, try sema.errMsg(src, "dependency loop detected", .{}));
+    }
+    try pt.ensureMemoizedStateUpToDate(stage);
+}
+
 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;
@@ -33373,7 +33346,7 @@ fn analyzeSlice(
         assert(!block.isComptime());
         try sema.requireRuntimeBlock(block, src, runtime_src.?);
         const ok = try block.addBinOp(.cmp_lte, start, end);
-        try sema.addSafetyCheckCall(block, src, ok, "startGreaterThanEnd", &.{ start, end });
+        try sema.addSafetyCheckCall(block, src, ok, .@"Panic.startGreaterThanEnd", &.{ start, end });
     }
     const new_len = if (by_length)
         try sema.coerce(block, Type.usize, uncasted_end_opt, end_src)
@@ -35493,7 +35466,7 @@ pub fn resolveIes(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError!void
     }
 }
 
-pub fn resolveFnTypes(sema: *Sema, fn_ty: Type) CompileError!void {
+pub fn resolveFnTypes(sema: *Sema, fn_ty: Type, src: LazySrcLoc) CompileError!void {
     const pt = sema.pt;
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
@@ -35505,7 +35478,7 @@ pub fn resolveFnTypes(sema: *Sema, fn_ty: Type) CompileError!void {
         Type.fromInterned(fn_ty_info.return_type).isError(zcu))
     {
         // Ensure the type exists so that backends can assume that.
-        _ = try sema.getBuiltinType("StackTrace");
+        _ = try sema.getBuiltinType(src, .StackTrace);
     }
 
     for (0..fn_ty_info.param_types.len) |i| {
@@ -37550,7 +37523,7 @@ pub fn analyzeAsAddressSpace(
 ) !std.builtin.AddressSpace {
     const pt = sema.pt;
     const zcu = pt.zcu;
-    const addrspace_ty = try sema.getBuiltinType("AddressSpace");
+    const addrspace_ty = try sema.getBuiltinType(src, .AddressSpace);
     const coerced = try sema.coerce(block, addrspace_ty, air_ref, src);
     const addrspace_val = try sema.resolveConstDefinedValue(block, src, coerced, .{ .simple = .@"addrspace" });
     const address_space = zcu.toEnum(std.builtin.AddressSpace, addrspace_val);
@@ -38747,69 +38720,15 @@ const ComptimeLoadResult = @import("Sema/comptime_ptr_access.zig").ComptimeLoadR
 const storeComptimePtr = @import("Sema/comptime_ptr_access.zig").storeComptimePtr;
 const ComptimeStoreResult = @import("Sema/comptime_ptr_access.zig").ComptimeStoreResult;
 
-fn getPanicInnerFn(
-    sema: *Sema,
-    block: *Block,
-    src: LazySrcLoc,
-    inner_name: []const u8,
-) !InternPool.Index {
-    const gpa = sema.gpa;
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const outer_ty = try sema.getBuiltinType("Panic");
-    const inner_name_ip = try ip.getOrPutString(gpa, pt.tid, inner_name, .no_embedded_nulls);
-    const opt_fn_ref = try namespaceLookupVal(sema, block, src, outer_ty.getNamespaceIndex(zcu), inner_name_ip);
-    const fn_ref = opt_fn_ref orelse return sema.fail(block, src, "std.builtin.Panic missing {s}", .{inner_name});
-    const fn_val = try sema.resolveConstValue(block, src, fn_ref, .{ .simple = .panic_handler });
-    if (fn_val.typeOf(zcu).zigTypeTag(zcu) != .@"fn") {
-        return sema.fail(block, src, "std.builtin.Panic.{s} is not a function", .{inner_name});
-    }
-    // Better not to queue up function body analysis because the function might be generic, and
-    // the semantic analysis for the call will already queue if necessary.
-    return fn_val.toIntern();
-}
-
-fn getBuiltinType(sema: *Sema, name: []const u8) SemaError!Type {
-    const pt = sema.pt;
-    const ty_inst = try sema.getBuiltin(name);
-    const ty = Type.fromInterned(ty_inst.toInterned() orelse @panic("std.builtin is corrupt"));
-    try ty.resolveFully(pt);
-    return ty;
-}
-
-fn getBuiltinInnerType(
-    sema: *Sema,
-    block: *Block,
-    src: LazySrcLoc,
-    outer_ty: Type,
-    /// Relative to "std.builtin".
-    compile_error_parent_name: []const u8,
-    inner_name: []const u8,
-) !Type {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const gpa = sema.gpa;
-    const inner_name_ip = try ip.getOrPutString(gpa, pt.tid, inner_name, .no_embedded_nulls);
-    const opt_nav = try sema.namespaceLookup(block, src, outer_ty.getNamespaceIndex(zcu), inner_name_ip);
-    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, .fully);
-    const val = Value.fromInterned(ip.getNav(nav).status.fully_resolved.val);
-    const ty = val.toType();
-    try ty.resolveFully(pt);
-    return ty;
+pub fn getBuiltinType(sema: *Sema, src: LazySrcLoc, comptime decl: Zcu.BuiltinDecl) SemaError!Type {
+    comptime assert(decl.kind() == .type);
+    try sema.ensureMemoizedStateResolved(src, decl.stage());
+    return .fromInterned(@field(sema.pt.zcu.builtin_decl_values, @tagName(decl)));
 }
-
-fn getBuiltin(sema: *Sema, name: []const u8) SemaError!Air.Inst.Ref {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const nav = try pt.getBuiltinNav(name);
-    try pt.ensureNavValUpToDate(nav);
-    return Air.internedToRef(ip.getNav(nav).status.fully_resolved.val);
+pub fn getBuiltin(sema: *Sema, src: LazySrcLoc, comptime decl: Zcu.BuiltinDecl) SemaError!InternPool.Index {
+    comptime assert(decl.kind() != .type);
+    try sema.ensureMemoizedStateResolved(src, decl.stage());
+    return @field(sema.pt.zcu.builtin_decl_values, @tagName(decl));
 }
 
 pub const NavPtrModifiers = struct {
@@ -38877,3 +38796,77 @@ pub fn resolveNavPtrModifiers(
         .@"addrspace" = @"addrspace",
     };
 }
+
+pub fn analyzeMemoizedState(sema: *Sema, block: *Block, src: LazySrcLoc, builtin_namespace: InternPool.NamespaceIndex, stage: InternPool.MemoizedStateStage) CompileError!bool {
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const ip = &zcu.intern_pool;
+    const gpa = zcu.gpa;
+
+    var any_changed = false;
+
+    inline for (comptime std.enums.values(Zcu.BuiltinDecl)) |builtin_decl| {
+        if (stage == comptime builtin_decl.stage()) {
+            const parent_ns: Zcu.Namespace.Index, const parent_name: []const u8, const name: []const u8 = switch (comptime builtin_decl.access()) {
+                .direct => |name| .{ builtin_namespace, "std.builtin", name },
+                .nested => |nested| access: {
+                    const parent_ty: Type = .fromInterned(@field(zcu.builtin_decl_values, @tagName(nested[0])));
+                    const parent_ns = parent_ty.getNamespace(zcu).unwrap() orelse {
+                        return sema.fail(block, src, "std.builtin.{s} is not a container type", .{@tagName(nested[0])});
+                    };
+                    break :access .{ parent_ns, "std.builtin." ++ @tagName(nested[0]), nested[1] };
+                },
+            };
+
+            const name_nts = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
+            const result = try sema.namespaceLookupVal(block, src, parent_ns, name_nts) orelse
+                return sema.fail(block, src, "{s} missing {s}", .{ parent_name, name });
+
+            const val = try sema.resolveConstDefinedValue(block, src, result, null);
+
+            switch (builtin_decl.kind()) {
+                .type => if (val.typeOf(zcu).zigTypeTag(zcu) != .type) {
+                    return sema.fail(block, src, "{s}.{s} is not a type", .{ parent_name, name });
+                } else {
+                    try val.toType().resolveFully(pt);
+                },
+                .func => if (val.typeOf(zcu).zigTypeTag(zcu) != .@"fn") {
+                    return sema.fail(block, src, "{s}.{s} is not a function", .{ parent_name, name });
+                },
+                .string => {
+                    const ty = val.typeOf(zcu);
+                    if (!ty.isSinglePointer(zcu) or
+                        !ty.isConstPtr(zcu) or
+                        ty.childType(zcu).zigTypeTag(zcu) != .array or
+                        ty.childType(zcu).childType(zcu).toIntern() != .u8_type)
+                    {
+                        return sema.fail(block, src, "{s}.{s} is not a valid string", .{ parent_name, name });
+                    }
+                },
+            }
+
+            const prev = @field(zcu.builtin_decl_values, @tagName(builtin_decl));
+            if (val.toIntern() != prev) {
+                @field(zcu.builtin_decl_values, @tagName(builtin_decl)) = val.toIntern();
+                any_changed = true;
+            }
+        }
+    }
+
+    if (stage == .panic) {
+        // We use `getBuiltinType` because this is from an earlier stage.
+        const stack_trace_ty = try sema.getBuiltinType(src, .StackTrace);
+        const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
+        const opt_ptr_stack_trace_ty = try pt.optionalType(ptr_stack_trace_ty.toIntern());
+        const null_stack_trace = try pt.intern(.{ .opt = .{
+            .ty = opt_ptr_stack_trace_ty.toIntern(),
+            .val = .none,
+        } });
+        if (null_stack_trace != zcu.null_stack_trace) {
+            zcu.null_stack_trace = null_stack_trace;
+            any_changed = true;
+        }
+    }
+
+    return any_changed;
+}
src/Zcu.zig
@@ -217,15 +217,212 @@ all_type_references: std.ArrayListUnmanaged(TypeReference) = .empty,
 /// Freelist of indices in `all_type_references`.
 free_type_references: std.ArrayListUnmanaged(u32) = .empty,
 
-panic_messages: [PanicId.len]InternPool.Nav.Index.Optional = .{.none} ** PanicId.len,
-/// The panic function body.
-panic_func_index: InternPool.Index = .none,
+/// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the field.
+builtin_decl_values: BuiltinDecl.Memoized = .{},
+/// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = .panic })`.
 null_stack_trace: InternPool.Index = .none,
 
 generation: u32 = 0,
 
 pub const PerThread = @import("Zcu/PerThread.zig");
 
+/// Names of declarations in `std.builtin` whose values are memoized in a `BuiltinDecl.Memoized`.
+/// The name must exactly match the declaration name, as comptime logic is used to compute the namespace accesses.
+/// Parent namespaces must be before their children in this enum. For instance, `.Type` must be before `.@"Type.Fn"`.
+/// Additionally, parent namespaces must be resolved in the same stage as their children; see `BuiltinDecl.stage`.
+pub const BuiltinDecl = enum {
+    Signedness,
+    AddressSpace,
+    CallingConvention,
+    returnError,
+    StackTrace,
+    SourceLocation,
+    CallModifier,
+    AtomicOrder,
+    AtomicRmwOp,
+    ReduceOp,
+    FloatMode,
+    PrefetchOptions,
+    ExportOptions,
+    ExternOptions,
+    BranchHint,
+
+    Type,
+    @"Type.Fn",
+    @"Type.Fn.Param",
+    @"Type.Int",
+    @"Type.Float",
+    @"Type.Pointer",
+    @"Type.Pointer.Size",
+    @"Type.Array",
+    @"Type.Vector",
+    @"Type.Optional",
+    @"Type.Error",
+    @"Type.ErrorUnion",
+    @"Type.EnumField",
+    @"Type.Enum",
+    @"Type.Union",
+    @"Type.UnionField",
+    @"Type.Struct",
+    @"Type.StructField",
+    @"Type.ContainerLayout",
+    @"Type.Opaque",
+    @"Type.Declaration",
+
+    Panic,
+    @"Panic.call",
+    @"Panic.sentinelMismatch",
+    @"Panic.unwrapError",
+    @"Panic.outOfBounds",
+    @"Panic.startGreaterThanEnd",
+    @"Panic.inactiveUnionField",
+    @"Panic.messages",
+    @"Panic.messages.reached_unreachable",
+    @"Panic.messages.unwrap_null",
+    @"Panic.messages.cast_to_null",
+    @"Panic.messages.incorrect_alignment",
+    @"Panic.messages.invalid_error_code",
+    @"Panic.messages.cast_truncated_data",
+    @"Panic.messages.negative_to_unsigned",
+    @"Panic.messages.integer_overflow",
+    @"Panic.messages.shl_overflow",
+    @"Panic.messages.shr_overflow",
+    @"Panic.messages.divide_by_zero",
+    @"Panic.messages.exact_division_remainder",
+    @"Panic.messages.integer_part_out_of_bounds",
+    @"Panic.messages.corrupt_switch",
+    @"Panic.messages.shift_rhs_too_big",
+    @"Panic.messages.invalid_enum_value",
+    @"Panic.messages.for_len_mismatch",
+    @"Panic.messages.memcpy_len_mismatch",
+    @"Panic.messages.memcpy_alias",
+    @"Panic.messages.noreturn_returned",
+
+    VaList,
+
+    /// Determines what kind of validation will be done to the decl's value.
+    pub fn kind(decl: BuiltinDecl) enum { type, func, string } {
+        return switch (decl) {
+            .returnError => .func,
+
+            .StackTrace,
+            .CallingConvention,
+            .SourceLocation,
+            .Signedness,
+            .AddressSpace,
+            .VaList,
+            .CallModifier,
+            .AtomicOrder,
+            .AtomicRmwOp,
+            .ReduceOp,
+            .FloatMode,
+            .PrefetchOptions,
+            .ExportOptions,
+            .ExternOptions,
+            .BranchHint,
+            => .type,
+
+            .Type,
+            .@"Type.Fn",
+            .@"Type.Fn.Param",
+            .@"Type.Int",
+            .@"Type.Float",
+            .@"Type.Pointer",
+            .@"Type.Pointer.Size",
+            .@"Type.Array",
+            .@"Type.Vector",
+            .@"Type.Optional",
+            .@"Type.Error",
+            .@"Type.ErrorUnion",
+            .@"Type.EnumField",
+            .@"Type.Enum",
+            .@"Type.Union",
+            .@"Type.UnionField",
+            .@"Type.Struct",
+            .@"Type.StructField",
+            .@"Type.ContainerLayout",
+            .@"Type.Opaque",
+            .@"Type.Declaration",
+            => .type,
+
+            .Panic => .type,
+
+            .@"Panic.call",
+            .@"Panic.sentinelMismatch",
+            .@"Panic.unwrapError",
+            .@"Panic.outOfBounds",
+            .@"Panic.startGreaterThanEnd",
+            .@"Panic.inactiveUnionField",
+            => .func,
+
+            .@"Panic.messages" => .type,
+
+            .@"Panic.messages.reached_unreachable",
+            .@"Panic.messages.unwrap_null",
+            .@"Panic.messages.cast_to_null",
+            .@"Panic.messages.incorrect_alignment",
+            .@"Panic.messages.invalid_error_code",
+            .@"Panic.messages.cast_truncated_data",
+            .@"Panic.messages.negative_to_unsigned",
+            .@"Panic.messages.integer_overflow",
+            .@"Panic.messages.shl_overflow",
+            .@"Panic.messages.shr_overflow",
+            .@"Panic.messages.divide_by_zero",
+            .@"Panic.messages.exact_division_remainder",
+            .@"Panic.messages.integer_part_out_of_bounds",
+            .@"Panic.messages.corrupt_switch",
+            .@"Panic.messages.shift_rhs_too_big",
+            .@"Panic.messages.invalid_enum_value",
+            .@"Panic.messages.for_len_mismatch",
+            .@"Panic.messages.memcpy_len_mismatch",
+            .@"Panic.messages.memcpy_alias",
+            .@"Panic.messages.noreturn_returned",
+            => .string,
+        };
+    }
+
+    /// Resolution of these values is done in three distinct stages:
+    /// * Resolution of `std.builtin.Panic` and everything under it
+    /// * Resolution of `VaList`
+    /// * Everything else
+    ///
+    /// Panics are separated because they are provided by the user, so must be able to use
+    /// things like reification.
+    ///
+    /// `VaList` is separate because its value depends on the target, so it needs some reflection
+    /// machinery to work; additionally, it is `@compileError` on some targets, so must be referenced
+    /// by itself.
+    pub fn stage(decl: BuiltinDecl) InternPool.MemoizedStateStage {
+        if (decl == .VaList) return .va_list;
+
+        if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.@"Type.Declaration")) {
+            return .main;
+        } else {
+            return .panic;
+        }
+    }
+
+    /// Based on the tag name, determines how to access this decl; either as a direct child of the
+    /// `std.builtin` namespace, or as a child of some preceding `BuiltinDecl` value.
+    pub fn access(decl: BuiltinDecl) union(enum) {
+        direct: []const u8,
+        nested: struct { BuiltinDecl, []const u8 },
+    } {
+        @setEvalBranchQuota(2000);
+        return switch (decl) {
+            inline else => |tag| {
+                const name = @tagName(tag);
+                const split = (comptime std.mem.lastIndexOfScalar(u8, name, '.')) orelse return .{ .direct = name };
+                const parent = @field(BuiltinDecl, name[0..split]);
+                comptime assert(@intFromEnum(parent) < @intFromEnum(tag)); // dependencies ordered correctly
+                return .{ .nested = .{ parent, name[split + 1 ..] } };
+            },
+        };
+    }
+
+    const Memoized = std.enums.EnumFieldStruct(BuiltinDecl, InternPool.Index, .none);
+};
+
 pub const PanicId = enum {
     reached_unreachable,
     unwrap_null,
@@ -247,8 +444,6 @@ pub const PanicId = enum {
     memcpy_len_mismatch,
     memcpy_alias,
     noreturn_returned,
-
-    pub const len = @typeInfo(PanicId).@"enum".fields.len;
 };
 
 pub const GlobalErrorSet = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void);
@@ -2454,6 +2649,7 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
             .nav_ty => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_ty = nav }),
             .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }),
             .func => |func| try zcu.markPoDependeeUpToDate(.{ .interned = func }),
+            .memoized_state => |stage| try zcu.markPoDependeeUpToDate(.{ .memoized_state = stage }),
         }
     }
 }
@@ -2468,6 +2664,7 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni
         .nav_ty => |nav| .{ .nav_ty = nav },
         .type => |ty| .{ .interned = ty },
         .func => |func_index| .{ .interned = func_index }, // IES
+        .memoized_state => |stage| .{ .memoized_state = stage },
     };
     log.debug("potentially outdated dependee: {}", .{zcu.fmtDependee(dependee)});
     var it = ip.dependencyIterator(dependee);
@@ -2553,6 +2750,12 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
                 .type => |ty| .{ .interned = ty },
                 .nav_val => |nav| .{ .nav_val = nav },
                 .nav_ty => |nav| .{ .nav_ty = nav },
+                .memoized_state => {
+                    // If we've hit a loop and some `.memoized_state` is outdated, we should make that choice eagerly.
+                    // In general, it's good to resolve this early on, since -- for instance -- almost every function
+                    // references the panic handler.
+                    return unit;
+                },
             });
             while (it.next()) |_| n += 1;
 
@@ -3462,7 +3665,7 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
                 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,
+                    .@"comptime", .type, .func, .memoized_state => break :queue_paired,
                 });
                 if (result.contains(other)) break :queue_paired;
                 try unit_queue.put(gpa, other, kv.value); // same reference location
@@ -3597,6 +3800,7 @@ fn formatAnalUnit(data: struct { unit: AnalUnit, zcu: *Zcu }, comptime fmt: []co
             const nav = zcu.funcInfo(func).owner_nav;
             return writer.print("func('{}' [{}])", .{ ip.getNav(nav).fqn.fmt(ip), @intFromEnum(func) });
         },
+        .memoized_state => return writer.writeAll("memoized_state"),
     }
 }
 fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
@@ -3642,6 +3846,7 @@ fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, com
             const file_path = zcu.fileByIndex(info.file).sub_file_path;
             return writer.print("namespace('{s}', %{d}, '{}')", .{ file_path, @intFromEnum(info.inst), k.name.fmt(ip) });
         },
+        .memoized_state => return writer.writeAll("memoized_state"),
     }
 }