Commit ca02266157

Jacob Young <jacobly0@users.noreply.github.com>
2024-06-16 01:57:47
Zcu: pass `PerThread` to intern pool string functions
1 parent 525f341
src/arch/wasm/CodeGen.zig
@@ -2204,14 +2204,14 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif
         const func_val = (try func.air.value(pl_op.operand, pt)) orelse break :blk null;
 
         if (func_val.getFunction(mod)) |function| {
-            _ = try func.bin_file.getOrCreateAtomForDecl(function.owner_decl);
+            _ = try func.bin_file.getOrCreateAtomForDecl(pt, function.owner_decl);
             break :blk function.owner_decl;
         } else if (func_val.getExternFunc(mod)) |extern_func| {
             const ext_decl = mod.declPtr(extern_func.decl);
             const ext_info = mod.typeToFunc(ext_decl.typeOf(mod)).?;
             var func_type = try genFunctype(func.gpa, ext_info.cc, ext_info.param_types.get(ip), Type.fromInterned(ext_info.return_type), pt);
             defer func_type.deinit(func.gpa);
-            const atom_index = try func.bin_file.getOrCreateAtomForDecl(extern_func.decl);
+            const atom_index = try func.bin_file.getOrCreateAtomForDecl(pt, extern_func.decl);
             const atom = func.bin_file.getAtomPtr(atom_index);
             const type_index = try func.bin_file.storeDeclType(extern_func.decl, func_type);
             try func.bin_file.addOrUpdateImport(
@@ -2224,7 +2224,7 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif
         } else switch (mod.intern_pool.indexToKey(func_val.ip_index)) {
             .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
                 .decl => |decl| {
-                    _ = try func.bin_file.getOrCreateAtomForDecl(decl);
+                    _ = try func.bin_file.getOrCreateAtomForDecl(pt, decl);
                     break :blk decl;
                 },
                 else => {},
@@ -3227,7 +3227,7 @@ fn lowerDeclRefValue(func: *CodeGen, decl_index: InternPool.DeclIndex, offset: u
         return WValue{ .imm32 = 0xaaaaaaaa };
     }
 
-    const atom_index = try func.bin_file.getOrCreateAtomForDecl(decl_index);
+    const atom_index = try func.bin_file.getOrCreateAtomForDecl(pt, decl_index);
     const atom = func.bin_file.getAtom(atom_index);
 
     const target_sym_index = @intFromEnum(atom.sym_index);
@@ -7284,7 +7284,7 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
     defer arena_allocator.deinit();
     const arena = arena_allocator.allocator();
 
-    const fqn = try mod.declPtr(enum_decl_index).fullyQualifiedName(mod);
+    const fqn = try mod.declPtr(enum_decl_index).fullyQualifiedName(pt);
     const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{}", .{fqn.fmt(ip)});
 
     // check if we already generated code for this.
src/codegen/llvm.zig
@@ -1744,7 +1744,7 @@ pub const Object = struct {
         if (export_indices.len != 0) {
             return updateExportedGlobal(self, zcu, global_index, export_indices);
         } else {
-            const fqn = try self.builder.strtabString((try decl.fullyQualifiedName(zcu)).toSlice(ip));
+            const fqn = try self.builder.strtabString((try decl.fullyQualifiedName(pt)).toSlice(ip));
             try global_index.rename(fqn, &self.builder);
             global_index.setLinkage(.internal, &self.builder);
             if (comp.config.dll_export_fns)
@@ -2520,7 +2520,7 @@ pub const Object = struct {
                     const field_offset = ty.structFieldOffset(field_index, pt);
 
                     const field_name = struct_type.fieldName(ip, field_index).unwrap() orelse
-                        try ip.getOrPutStringFmt(gpa, "{d}", .{field_index}, .no_embedded_nulls);
+                        try ip.getOrPutStringFmt(gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
 
                     fields.appendAssumeCapacity(try o.builder.debugMemberType(
                         try o.builder.metadataString(field_name.toSlice(ip)),
@@ -2807,17 +2807,18 @@ pub const Object = struct {
     }
 
     fn getStackTraceType(o: *Object) Allocator.Error!Type {
-        const zcu = o.pt.zcu;
+        const pt = o.pt;
+        const zcu = pt.zcu;
 
         const std_mod = zcu.std_mod;
         const std_file_imported = zcu.importPkg(std_mod) catch unreachable;
 
-        const builtin_str = try zcu.intern_pool.getOrPutString(zcu.gpa, "builtin", .no_embedded_nulls);
+        const builtin_str = try zcu.intern_pool.getOrPutString(zcu.gpa, pt.tid, "builtin", .no_embedded_nulls);
         const std_file_root_decl = zcu.fileRootDecl(std_file_imported.file_index);
         const std_namespace = zcu.namespacePtr(zcu.declPtr(std_file_root_decl.unwrap().?).src_namespace);
         const builtin_decl = std_namespace.decls.getKeyAdapted(builtin_str, Zcu.DeclAdapter{ .zcu = zcu }).?;
 
-        const stack_trace_str = try zcu.intern_pool.getOrPutString(zcu.gpa, "StackTrace", .no_embedded_nulls);
+        const stack_trace_str = try zcu.intern_pool.getOrPutString(zcu.gpa, pt.tid, "StackTrace", .no_embedded_nulls);
         // buffer is only used for int_type, `builtin` is a struct.
         const builtin_ty = zcu.declPtr(builtin_decl).val.toType();
         const builtin_namespace = zcu.namespacePtrUnwrap(builtin_ty.getNamespaceIndex(zcu)).?;
@@ -2865,7 +2866,7 @@ pub const Object = struct {
             try o.builder.strtabString((if (is_extern)
                 decl.name
             else
-                try decl.fullyQualifiedName(zcu)).toSlice(ip)),
+                try decl.fullyQualifiedName(pt)).toSlice(ip)),
             toLlvmAddressSpace(decl.@"addrspace", target),
         );
         gop.value_ptr.* = function_index.ptrConst(&o.builder).global;
@@ -3074,7 +3075,8 @@ pub const Object = struct {
         if (gop.found_existing) return gop.value_ptr.ptr(&o.builder).kind.variable;
         errdefer assert(o.decl_map.remove(decl_index));
 
-        const zcu = o.pt.zcu;
+        const pt = o.pt;
+        const zcu = pt.zcu;
         const decl = zcu.declPtr(decl_index);
         const is_extern = decl.isExtern(zcu);
 
@@ -3082,7 +3084,7 @@ pub const Object = struct {
             try o.builder.strtabString((if (is_extern)
                 decl.name
             else
-                try decl.fullyQualifiedName(zcu)).toSlice(&zcu.intern_pool)),
+                try decl.fullyQualifiedName(pt)).toSlice(&zcu.intern_pool)),
             try o.lowerType(decl.typeOf(zcu)),
             toLlvmGlobalAddressSpace(decl.@"addrspace", zcu.getTarget()),
         );
@@ -3310,7 +3312,7 @@ pub const Object = struct {
                         return int_ty;
                     }
 
-                    const fqn = try mod.declPtr(struct_type.decl.unwrap().?).fullyQualifiedName(mod);
+                    const fqn = try mod.declPtr(struct_type.decl.unwrap().?).fullyQualifiedName(pt);
 
                     var llvm_field_types = std.ArrayListUnmanaged(Builder.Type){};
                     defer llvm_field_types.deinit(o.gpa);
@@ -3464,7 +3466,7 @@ pub const Object = struct {
                         return enum_tag_ty;
                     }
 
-                    const fqn = try mod.declPtr(union_obj.decl).fullyQualifiedName(mod);
+                    const fqn = try mod.declPtr(union_obj.decl).fullyQualifiedName(pt);
 
                     const aligned_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[layout.most_aligned_field]);
                     const aligned_field_llvm_ty = try o.lowerType(aligned_field_ty);
@@ -3525,7 +3527,7 @@ pub const Object = struct {
                     const gop = try o.type_map.getOrPut(o.gpa, t.toIntern());
                     if (!gop.found_existing) {
                         const decl = mod.declPtr(ip.loadOpaqueType(t.toIntern()).decl);
-                        const fqn = try decl.fullyQualifiedName(mod);
+                        const fqn = try decl.fullyQualifiedName(pt);
                         gop.value_ptr.* = try o.builder.opaqueType(try o.builder.string(fqn.toSlice(ip)));
                     }
                     return gop.value_ptr.*;
@@ -4585,7 +4587,7 @@ pub const Object = struct {
 
         const usize_ty = try o.lowerType(Type.usize);
         const ret_ty = try o.lowerType(Type.slice_const_u8_sentinel_0);
-        const fqn = try zcu.declPtr(enum_type.decl).fullyQualifiedName(zcu);
+        const fqn = try zcu.declPtr(enum_type.decl).fullyQualifiedName(pt);
         const target = zcu.root_mod.resolved_target.result;
         const function_index = try o.builder.addFunction(
             try o.builder.fnType(ret_ty, &.{try o.lowerType(Type.fromInterned(enum_type.tag_ty))}, .normal),
@@ -5173,7 +5175,7 @@ pub const FuncGen = struct {
             const line_number = decl.navSrcLine(zcu) + 1;
             self.inlined = self.wip.debug_location;
 
-            const fqn = try decl.fullyQualifiedName(zcu);
+            const fqn = try decl.fullyQualifiedName(pt);
 
             const fn_ty = try pt.funcType(.{
                 .param_types = &.{},
@@ -9707,7 +9709,7 @@ pub const FuncGen = struct {
         if (gop.found_existing) return gop.value_ptr.*;
         errdefer assert(o.named_enum_map.remove(enum_type.decl));
 
-        const fqn = try zcu.declPtr(enum_type.decl).fullyQualifiedName(zcu);
+        const fqn = try zcu.declPtr(enum_type.decl).fullyQualifiedName(pt);
         const target = zcu.root_mod.resolved_target.result;
         const function_index = try o.builder.addFunction(
             try o.builder.fnType(.i1, &.{try o.lowerType(Type.fromInterned(enum_type.tag_ty))}, .normal),
src/codegen/spirv.zig
@@ -1753,7 +1753,7 @@ const DeclGen = struct {
                     }
 
                     const field_name = struct_type.fieldName(ip, field_index).unwrap() orelse
-                        try ip.getOrPutStringFmt(mod.gpa, "{d}", .{field_index}, .no_embedded_nulls);
+                        try ip.getOrPutStringFmt(mod.gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
                     try member_types.append(try self.resolveType(field_ty, .indirect));
                     try member_names.append(field_name.toSlice(ip));
                 }
@@ -3012,7 +3012,7 @@ const DeclGen = struct {
                 // Append the actual code into the functions section.
                 try self.spv.addFunction(spv_decl_index, self.func);
 
-                const fqn = try decl.fullyQualifiedName(self.pt.zcu);
+                const fqn = try decl.fullyQualifiedName(self.pt);
                 try self.spv.debugName(result_id, fqn.toSlice(ip));
 
                 // Temporarily generate a test kernel declaration if this is a test function.
@@ -3041,7 +3041,7 @@ const DeclGen = struct {
                     .storage_class = final_storage_class,
                 });
 
-                const fqn = try decl.fullyQualifiedName(self.pt.zcu);
+                const fqn = try decl.fullyQualifiedName(self.pt);
                 try self.spv.debugName(result_id, fqn.toSlice(ip));
                 try self.spv.declareDeclDeps(spv_decl_index, &.{});
             },
@@ -3086,7 +3086,7 @@ const DeclGen = struct {
                     try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
                     try self.spv.addFunction(spv_decl_index, self.func);
 
-                    const fqn = try decl.fullyQualifiedName(self.pt.zcu);
+                    const fqn = try decl.fullyQualifiedName(self.pt);
                     try self.spv.debugNameFmt(initializer_id, "initializer of {}", .{fqn.fmt(ip)});
 
                     try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpExtInst, .{
src/link/Elf/ZigObject.zig
@@ -908,7 +908,7 @@ fn updateDeclCode(
     const gpa = elf_file.base.comp.gpa;
     const mod = pt.zcu;
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
 
     log.debug("updateDeclCode {}{*}", .{ decl_name.fmt(&mod.intern_pool), decl });
 
@@ -1009,7 +1009,7 @@ fn updateTlv(
     const mod = pt.zcu;
     const gpa = mod.gpa;
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
 
     log.debug("updateTlv {} ({*})", .{ decl_name.fmt(&mod.intern_pool), decl });
 
@@ -1286,7 +1286,7 @@ pub fn lowerUnnamedConst(
     }
     const unnamed_consts = gop.value_ptr;
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
     const index = unnamed_consts.items.len;
     const name = try std.fmt.allocPrint(gpa, "__unnamed_{}_{d}", .{ decl_name.fmt(&mod.intern_pool), index });
     defer gpa.free(name);
@@ -1466,19 +1466,19 @@ pub fn updateExports(
 /// Must be called only after a successful call to `updateDecl`.
 pub fn updateDeclLineNumber(
     self: *ZigObject,
-    mod: *Module,
+    pt: Zcu.PerThread,
     decl_index: InternPool.DeclIndex,
 ) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl = pt.zcu.declPtr(decl_index);
+    const decl_name = try decl.fullyQualifiedName(pt);
 
-    log.debug("updateDeclLineNumber {}{*}", .{ decl_name.fmt(&mod.intern_pool), decl });
+    log.debug("updateDeclLineNumber {}{*}", .{ decl_name.fmt(&pt.zcu.intern_pool), decl });
 
     if (self.dwarf) |*dw| {
-        try dw.updateDeclLineNumber(mod, decl_index);
+        try dw.updateDeclLineNumber(pt.zcu, decl_index);
     }
 }
 
src/link/MachO/ZigObject.zig
@@ -810,7 +810,7 @@ fn updateDeclCode(
     const gpa = macho_file.base.comp.gpa;
     const mod = pt.zcu;
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
 
     log.debug("updateDeclCode {}{*}", .{ decl_name.fmt(&mod.intern_pool), decl });
 
@@ -893,13 +893,12 @@ fn updateTlv(
     sect_index: u8,
     code: []const u8,
 ) !void {
-    const mod = pt.zcu;
-    const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl = pt.zcu.declPtr(decl_index);
+    const decl_name = try decl.fullyQualifiedName(pt);
 
-    log.debug("updateTlv {} ({*})", .{ decl_name.fmt(&mod.intern_pool), decl });
+    log.debug("updateTlv {} ({*})", .{ decl_name.fmt(&pt.zcu.intern_pool), decl });
 
-    const decl_name_slice = decl_name.toSlice(&mod.intern_pool);
+    const decl_name_slice = decl_name.toSlice(&pt.zcu.intern_pool);
     const required_alignment = decl.getAlignment(pt);
 
     // 1. Lower TLV initializer
@@ -1100,7 +1099,7 @@ pub fn lowerUnnamedConst(
     }
     const unnamed_consts = gop.value_ptr;
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
     const index = unnamed_consts.items.len;
     const name = try std.fmt.allocPrint(gpa, "__unnamed_{}_{d}", .{ decl_name.fmt(&mod.intern_pool), index });
     defer gpa.free(name);
@@ -1363,9 +1362,9 @@ fn updateLazySymbol(
 }
 
 /// Must be called only after a successful call to `updateDecl`.
-pub fn updateDeclLineNumber(self: *ZigObject, mod: *Module, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(self: *ZigObject, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !void {
     if (self.dwarf) |*dw| {
-        try dw.updateDeclLineNumber(mod, decl_index);
+        try dw.updateDeclLineNumber(pt.zcu, decl_index);
     }
 }
 
src/link/Wasm/ZigObject.zig
@@ -253,7 +253,7 @@ pub fn updateDecl(
     }
 
     const gpa = wasm_file.base.comp.gpa;
-    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
+    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, pt, decl_index);
     const atom = wasm_file.getAtomPtr(atom_index);
     atom.clear();
 
@@ -302,7 +302,7 @@ pub fn updateFunc(
     const func = pt.zcu.funcInfo(func_index);
     const decl_index = func.owner_decl;
     const decl = pt.zcu.declPtr(decl_index);
-    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
+    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, pt, decl_index);
     const atom = wasm_file.getAtomPtr(atom_index);
     atom.clear();
 
@@ -346,7 +346,7 @@ fn finishUpdateDecl(
     const atom_index = decl_info.atom;
     const atom = wasm_file.getAtomPtr(atom_index);
     const sym = zig_object.symbol(atom.sym_index);
-    const full_name = try decl.fullyQualifiedName(zcu);
+    const full_name = try decl.fullyQualifiedName(pt);
     sym.name = try zig_object.string_table.insert(gpa, full_name.toSlice(ip));
     try atom.code.appendSlice(gpa, code);
     atom.size = @intCast(code.len);
@@ -424,17 +424,21 @@ fn createDataSegment(
 /// For a given `InternPool.DeclIndex` returns its corresponding `Atom.Index`.
 /// When the index was not found, a new `Atom` will be created, and its index will be returned.
 /// The newly created Atom is empty with default fields as specified by `Atom.empty`.
-pub fn getOrCreateAtomForDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) !Atom.Index {
-    const gpa = wasm_file.base.comp.gpa;
+pub fn getOrCreateAtomForDecl(
+    zig_object: *ZigObject,
+    wasm_file: *Wasm,
+    pt: Zcu.PerThread,
+    decl_index: InternPool.DeclIndex,
+) !Atom.Index {
+    const gpa = pt.zcu.gpa;
     const gop = try zig_object.decls_map.getOrPut(gpa, decl_index);
     if (!gop.found_existing) {
         const sym_index = try zig_object.allocateSymbol(gpa);
         gop.value_ptr.* = .{ .atom = try wasm_file.createAtom(sym_index, zig_object.index) };
-        const mod = wasm_file.base.comp.module.?;
-        const decl = mod.declPtr(decl_index);
-        const full_name = try decl.fullyQualifiedName(mod);
+        const decl = pt.zcu.declPtr(decl_index);
+        const full_name = try decl.fullyQualifiedName(pt);
         const sym = zig_object.symbol(sym_index);
-        sym.name = try zig_object.string_table.insert(gpa, full_name.toSlice(&mod.intern_pool));
+        sym.name = try zig_object.string_table.insert(gpa, full_name.toSlice(&pt.zcu.intern_pool));
     }
     return gop.value_ptr.atom;
 }
@@ -487,10 +491,10 @@ pub fn lowerUnnamedConst(
     std.debug.assert(val.typeOf(mod).zigTypeTag(mod) != .Fn); // cannot create local symbols for functions
     const decl = mod.declPtr(decl_index);
 
-    const parent_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
+    const parent_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, pt, decl_index);
     const parent_atom = wasm_file.getAtom(parent_atom_index);
     const local_index = parent_atom.locals.items.len;
-    const fqn = try decl.fullyQualifiedName(mod);
+    const fqn = try decl.fullyQualifiedName(pt);
     const name = try std.fmt.allocPrintZ(gpa, "__unnamed_{}_{d}", .{
         fqn.fmt(&mod.intern_pool), local_index,
     });
@@ -775,22 +779,22 @@ pub fn getGlobalSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator, name: []c
 pub fn getDeclVAddr(
     zig_object: *ZigObject,
     wasm_file: *Wasm,
+    pt: Zcu.PerThread,
     decl_index: InternPool.DeclIndex,
     reloc_info: link.File.RelocInfo,
 ) !u64 {
     const target = wasm_file.base.comp.root_mod.resolved_target.result;
-    const gpa = wasm_file.base.comp.gpa;
-    const mod = wasm_file.base.comp.module.?;
-    const decl = mod.declPtr(decl_index);
+    const gpa = pt.zcu.gpa;
+    const decl = pt.zcu.declPtr(decl_index);
 
-    const target_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
+    const target_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, pt, decl_index);
     const target_symbol_index = @intFromEnum(wasm_file.getAtom(target_atom_index).sym_index);
 
     std.debug.assert(reloc_info.parent_atom_index != 0);
     const atom_index = wasm_file.symbol_atom.get(.{ .file = zig_object.index, .index = @enumFromInt(reloc_info.parent_atom_index) }).?;
     const atom = wasm_file.getAtomPtr(atom_index);
     const is_wasm32 = target.cpu.arch == .wasm32;
-    if (decl.typeOf(mod).zigTypeTag(mod) == .Fn) {
+    if (decl.typeOf(pt.zcu).zigTypeTag(pt.zcu) == .Fn) {
         std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations
         try atom.relocs.append(gpa, .{
             .index = target_symbol_index,
@@ -890,7 +894,7 @@ pub fn updateExports(
         },
     };
     const decl = mod.declPtr(decl_index);
-    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
+    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, pt, decl_index);
     const decl_info = zig_object.decls_map.getPtr(decl_index).?;
     const atom = wasm_file.getAtom(atom_index);
     const atom_sym = atom.symbolLoc().getSymbol(wasm_file).*;
@@ -1116,13 +1120,17 @@ pub fn createDebugSectionForIndex(zig_object: *ZigObject, wasm_file: *Wasm, inde
     return atom_index;
 }
 
-pub fn updateDeclLineNumber(zig_object: *ZigObject, mod: *Zcu, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(
+    zig_object: *ZigObject,
+    pt: Zcu.PerThread,
+    decl_index: InternPool.DeclIndex,
+) !void {
     if (zig_object.dwarf) |*dw| {
-        const decl = mod.declPtr(decl_index);
-        const decl_name = try decl.fullyQualifiedName(mod);
+        const decl = pt.zcu.declPtr(decl_index);
+        const decl_name = try decl.fullyQualifiedName(pt);
 
-        log.debug("updateDeclLineNumber {}{*}", .{ decl_name.fmt(&mod.intern_pool), decl });
-        try dw.updateDeclLineNumber(mod, decl_index);
+        log.debug("updateDeclLineNumber {}{*}", .{ decl_name.fmt(&pt.zcu.intern_pool), decl });
+        try dw.updateDeclLineNumber(pt.zcu, decl_index);
     }
 }
 
src/link/C.zig
@@ -383,11 +383,11 @@ pub fn updateDecl(self: *C, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex)
     gop.value_ptr.fwd_decl = try self.addString(object.dg.fwd_decl.items);
 }
 
-pub fn updateDeclLineNumber(self: *C, zcu: *Zcu, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(self: *C, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !void {
     // The C backend does not have the ability to fix line numbers without re-generating
     // the entire Decl.
     _ = self;
-    _ = zcu;
+    _ = pt;
     _ = decl_index;
 }
 
src/link/Coff.zig
@@ -1176,7 +1176,7 @@ pub fn lowerUnnamedConst(self: *Coff, pt: Zcu.PerThread, val: Value, decl_index:
         gop.value_ptr.* = .{};
     }
     const unnamed_consts = gop.value_ptr;
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
     const index = unnamed_consts.items.len;
     const sym_name = try std.fmt.allocPrint(gpa, "__unnamed_{}_{d}", .{ decl_name.fmt(&mod.intern_pool), index });
     defer gpa.free(sym_name);
@@ -1427,7 +1427,7 @@ fn updateDeclCode(self: *Coff, pt: Zcu.PerThread, decl_index: InternPool.DeclInd
     const mod = pt.zcu;
     const decl = mod.declPtr(decl_index);
 
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
 
     log.debug("updateDeclCode {}{*}", .{ decl_name.fmt(&mod.intern_pool), decl });
     const required_alignment: u32 = @intCast(decl.getAlignment(pt).toByteUnits() orelse 0);
@@ -1855,7 +1855,7 @@ pub fn flushModule(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
     assert(!self.imports_count_dirty);
 }
 
-pub fn getDeclVAddr(self: *Coff, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
+pub fn getDeclVAddr(self: *Coff, _: Zcu.PerThread, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
     assert(self.llvm_object == null);
 
     const this_atom_index = try self.getOrCreateAtomForDecl(decl_index);
@@ -1972,9 +1972,9 @@ pub fn getGlobalSymbol(self: *Coff, name: []const u8, lib_name_name: ?[]const u8
     return global_index;
 }
 
-pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(self: *Coff, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !void {
     _ = self;
-    _ = module;
+    _ = pt;
     _ = decl_index;
     log.debug("TODO implement updateDeclLineNumber", .{});
 }
src/link/Dwarf.zig
@@ -1082,7 +1082,7 @@ pub fn initDeclState(self: *Dwarf, pt: Zcu.PerThread, decl_index: InternPool.Dec
     defer tracy.end();
 
     const decl = pt.zcu.declPtr(decl_index);
-    const decl_linkage_name = try decl.fullyQualifiedName(pt.zcu);
+    const decl_linkage_name = try decl.fullyQualifiedName(pt);
 
     log.debug("initDeclState {}{*}", .{ decl_linkage_name.fmt(&pt.zcu.intern_pool), decl });
 
src/link/Elf.zig
@@ -543,7 +543,7 @@ pub fn deinit(self: *Elf) void {
     self.comdat_group_sections.deinit(gpa);
 }
 
-pub fn getDeclVAddr(self: *Elf, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
+pub fn getDeclVAddr(self: *Elf, _: Zcu.PerThread, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
     assert(self.llvm_object == null);
     return self.zigObjectPtr().?.getDeclVAddr(self, decl_index, reloc_info);
 }
@@ -3021,9 +3021,9 @@ pub fn updateExports(
     return self.zigObjectPtr().?.updateExports(self, pt, exported, export_indices);
 }
 
-pub fn updateDeclLineNumber(self: *Elf, mod: *Module, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(self: *Elf, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !void {
     if (self.llvm_object) |_| return;
-    return self.zigObjectPtr().?.updateDeclLineNumber(mod, decl_index);
+    return self.zigObjectPtr().?.updateDeclLineNumber(pt, decl_index);
 }
 
 pub fn deleteExport(
src/link/MachO.zig
@@ -3198,9 +3198,9 @@ pub fn updateDecl(self: *MachO, pt: Zcu.PerThread, decl_index: InternPool.DeclIn
     return self.getZigObject().?.updateDecl(self, pt, decl_index);
 }
 
-pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(self: *MachO, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !void {
     if (self.llvm_object) |_| return;
-    return self.getZigObject().?.updateDeclLineNumber(module, decl_index);
+    return self.getZigObject().?.updateDeclLineNumber(pt, decl_index);
 }
 
 pub fn updateExports(
@@ -3230,7 +3230,7 @@ pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void {
     return self.getZigObject().?.freeDecl(decl_index);
 }
 
-pub fn getDeclVAddr(self: *MachO, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
+pub fn getDeclVAddr(self: *MachO, _: Zcu.PerThread, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
     assert(self.llvm_object == null);
     return self.getZigObject().?.getDeclVAddr(self, decl_index, reloc_info);
 }
src/link/Plan9.zig
@@ -483,7 +483,7 @@ pub fn lowerUnnamedConst(self: *Plan9, pt: Zcu.PerThread, val: Value, decl_index
     }
     const unnamed_consts = gop.value_ptr;
 
-    const decl_name = try decl.fullyQualifiedName(mod);
+    const decl_name = try decl.fullyQualifiedName(pt);
 
     const index = unnamed_consts.items.len;
     // name is freed when the unnamed const is freed
@@ -1496,22 +1496,22 @@ pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void {
 }
 
 /// Must be called only after a successful call to `updateDecl`.
-pub fn updateDeclLineNumber(self: *Plan9, mod: *Zcu, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(self: *Plan9, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !void {
     _ = self;
-    _ = mod;
+    _ = pt;
     _ = decl_index;
 }
 
 pub fn getDeclVAddr(
     self: *Plan9,
+    pt: Zcu.PerThread,
     decl_index: InternPool.DeclIndex,
     reloc_info: link.File.RelocInfo,
 ) !u64 {
-    const mod = self.base.comp.module.?;
-    const ip = &mod.intern_pool;
-    const decl = mod.declPtr(decl_index);
+    const ip = &pt.zcu.intern_pool;
+    const decl = pt.zcu.declPtr(decl_index);
     log.debug("getDeclVAddr for {}", .{decl.name.fmt(ip)});
-    if (decl.isExtern(mod)) {
+    if (decl.isExtern(pt.zcu)) {
         if (decl.name.eqlSlice("etext", ip)) {
             try self.addReloc(reloc_info.parent_atom_index, .{
                 .target = undefined,
src/link/Wasm.zig
@@ -1457,9 +1457,9 @@ pub fn updateDecl(wasm: *Wasm, pt: Zcu.PerThread, decl_index: InternPool.DeclInd
     try wasm.zigObjectPtr().?.updateDecl(wasm, pt, decl_index);
 }
 
-pub fn updateDeclLineNumber(wasm: *Wasm, mod: *Zcu, decl_index: InternPool.DeclIndex) !void {
+pub fn updateDeclLineNumber(wasm: *Wasm, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !void {
     if (wasm.llvm_object) |_| return;
-    try wasm.zigObjectPtr().?.updateDeclLineNumber(mod, decl_index);
+    try wasm.zigObjectPtr().?.updateDeclLineNumber(pt, decl_index);
 }
 
 /// From a given symbol location, returns its `wasm.GlobalType`.
@@ -1521,10 +1521,11 @@ pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8, lib_name: ?[]const u8) !Sy
 /// Returns the given pointer address
 pub fn getDeclVAddr(
     wasm: *Wasm,
+    pt: Zcu.PerThread,
     decl_index: InternPool.DeclIndex,
     reloc_info: link.File.RelocInfo,
 ) !u64 {
-    return wasm.zigObjectPtr().?.getDeclVAddr(wasm, decl_index, reloc_info);
+    return wasm.zigObjectPtr().?.getDeclVAddr(wasm, pt, decl_index, reloc_info);
 }
 
 pub fn lowerAnonDecl(
@@ -4016,8 +4017,8 @@ pub fn getErrorTableSymbol(wasm_file: *Wasm, pt: Zcu.PerThread) !u32 {
 /// For a given `InternPool.DeclIndex` returns its corresponding `Atom.Index`.
 /// When the index was not found, a new `Atom` will be created, and its index will be returned.
 /// The newly created Atom is empty with default fields as specified by `Atom.empty`.
-pub fn getOrCreateAtomForDecl(wasm_file: *Wasm, decl_index: InternPool.DeclIndex) !Atom.Index {
-    return wasm_file.zigObjectPtr().?.getOrCreateAtomForDecl(wasm_file, decl_index);
+pub fn getOrCreateAtomForDecl(wasm_file: *Wasm, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) !Atom.Index {
+    return wasm_file.zigObjectPtr().?.getOrCreateAtomForDecl(wasm_file, pt, decl_index);
 }
 
 /// Verifies all resolved symbols and checks whether itself needs to be marked alive,
src/Zcu/PerThread.zig
@@ -5,6 +5,411 @@ tid: Id,
 
 pub const Id = if (builtin.single_threaded) enum { main } else enum(usize) { main, _ };
 
+pub fn astGenFile(
+    pt: Zcu.PerThread,
+    file: *Zcu.File,
+    /// This parameter is provided separately from `file` because it is not
+    /// safe to access `import_table` without a lock, and this index is needed
+    /// in the call to `updateZirRefs`.
+    file_index: Zcu.File.Index,
+    path_digest: Cache.BinDigest,
+    opt_root_decl: Zcu.Decl.OptionalIndex,
+) !void {
+    assert(!file.mod.isBuiltin());
+
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const zcu = pt.zcu;
+    const comp = zcu.comp;
+    const gpa = zcu.gpa;
+
+    // In any case we need to examine the stat of the file to determine the course of action.
+    var source_file = try file.mod.root.openFile(file.sub_file_path, .{});
+    defer source_file.close();
+
+    const stat = try source_file.stat();
+
+    const want_local_cache = file.mod == zcu.main_mod;
+    const hex_digest = Cache.binToHex(path_digest);
+    const cache_directory = if (want_local_cache) zcu.local_zir_cache else zcu.global_zir_cache;
+    const zir_dir = cache_directory.handle;
+
+    // Determine whether we need to reload the file from disk and redo parsing and AstGen.
+    var lock: std.fs.File.Lock = switch (file.status) {
+        .never_loaded, .retryable_failure => lock: {
+            // First, load the cached ZIR code, if any.
+            log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{
+                file.sub_file_path, want_local_cache, &hex_digest,
+            });
+
+            break :lock .shared;
+        },
+        .parse_failure, .astgen_failure, .success_zir => lock: {
+            const unchanged_metadata =
+                stat.size == file.stat.size and
+                stat.mtime == file.stat.mtime and
+                stat.inode == file.stat.inode;
+
+            if (unchanged_metadata) {
+                log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
+                return;
+            }
+
+            log.debug("metadata changed: {s}", .{file.sub_file_path});
+
+            break :lock .exclusive;
+        },
+    };
+
+    // We ask for a lock in order to coordinate with other zig processes.
+    // If another process is already working on this file, we will get the cached
+    // version. Likewise if we're working on AstGen and another process asks for
+    // the cached file, they'll get it.
+    const cache_file = while (true) {
+        break zir_dir.createFile(&hex_digest, .{
+            .read = true,
+            .truncate = false,
+            .lock = lock,
+        }) catch |err| switch (err) {
+            error.NotDir => unreachable, // no dir components
+            error.InvalidUtf8 => unreachable, // it's a hex encoded name
+            error.InvalidWtf8 => unreachable, // it's a hex encoded name
+            error.BadPathName => unreachable, // it's a hex encoded name
+            error.NameTooLong => unreachable, // it's a fixed size name
+            error.PipeBusy => unreachable, // it's not a pipe
+            error.WouldBlock => unreachable, // not asking for non-blocking I/O
+            // There are no dir components, so you would think that this was
+            // unreachable, however we have observed on macOS two processes racing
+            // to do openat() with O_CREAT manifest in ENOENT.
+            error.FileNotFound => continue,
+
+            else => |e| return e, // Retryable errors are handled at callsite.
+        };
+    };
+    defer cache_file.close();
+
+    while (true) {
+        update: {
+            // First we read the header to determine the lengths of arrays.
+            const header = cache_file.reader().readStruct(Zir.Header) catch |err| switch (err) {
+                // This can happen if Zig bails out of this function between creating
+                // the cached file and writing it.
+                error.EndOfStream => break :update,
+                else => |e| return e,
+            };
+            const unchanged_metadata =
+                stat.size == header.stat_size and
+                stat.mtime == header.stat_mtime and
+                stat.inode == header.stat_inode;
+
+            if (!unchanged_metadata) {
+                log.debug("AstGen cache stale: {s}", .{file.sub_file_path});
+                break :update;
+            }
+            log.debug("AstGen cache hit: {s} instructions_len={d}", .{
+                file.sub_file_path, header.instructions_len,
+            });
+
+            file.zir = Zcu.loadZirCacheBody(gpa, header, cache_file) catch |err| switch (err) {
+                error.UnexpectedFileSize => {
+                    log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path});
+                    break :update;
+                },
+                else => |e| return e,
+            };
+            file.zir_loaded = true;
+            file.stat = .{
+                .size = header.stat_size,
+                .inode = header.stat_inode,
+                .mtime = header.stat_mtime,
+            };
+            file.status = .success_zir;
+            log.debug("AstGen cached success: {s}", .{file.sub_file_path});
+
+            // TODO don't report compile errors until Sema @importFile
+            if (file.zir.hasCompileErrors()) {
+                {
+                    comp.mutex.lock();
+                    defer comp.mutex.unlock();
+                    try zcu.failed_files.putNoClobber(gpa, file, null);
+                }
+                file.status = .astgen_failure;
+                return error.AnalysisFail;
+            }
+            return;
+        }
+
+        // If we already have the exclusive lock then it is our job to update.
+        if (builtin.os.tag == .wasi or lock == .exclusive) break;
+        // Otherwise, unlock to give someone a chance to get the exclusive lock
+        // and then upgrade to an exclusive lock.
+        cache_file.unlock();
+        lock = .exclusive;
+        try cache_file.lock(lock);
+    }
+
+    // The cache is definitely stale so delete the contents to avoid an underwrite later.
+    cache_file.setEndPos(0) catch |err| switch (err) {
+        error.FileTooBig => unreachable, // 0 is not too big
+
+        else => |e| return e,
+    };
+
+    pt.lockAndClearFileCompileError(file);
+
+    // If the previous ZIR does not have compile errors, keep it around
+    // in case parsing or new ZIR fails. In case of successful ZIR update
+    // at the end of this function we will free it.
+    // We keep the previous ZIR loaded so that we can use it
+    // for the update next time it does not have any compile errors. This avoids
+    // needlessly tossing out semantic analysis work when an error is
+    // temporarily introduced.
+    if (file.zir_loaded and !file.zir.hasCompileErrors()) {
+        assert(file.prev_zir == null);
+        const prev_zir_ptr = try gpa.create(Zir);
+        file.prev_zir = prev_zir_ptr;
+        prev_zir_ptr.* = file.zir;
+        file.zir = undefined;
+        file.zir_loaded = false;
+    }
+    file.unload(gpa);
+
+    if (stat.size > std.math.maxInt(u32))
+        return error.FileTooBig;
+
+    const source = try gpa.allocSentinel(u8, @as(usize, @intCast(stat.size)), 0);
+    defer if (!file.source_loaded) gpa.free(source);
+    const amt = try source_file.readAll(source);
+    if (amt != stat.size)
+        return error.UnexpectedEndOfFile;
+
+    file.stat = .{
+        .size = stat.size,
+        .inode = stat.inode,
+        .mtime = stat.mtime,
+    };
+    file.source = source;
+    file.source_loaded = true;
+
+    file.tree = try Ast.parse(gpa, source, .zig);
+    file.tree_loaded = true;
+
+    // Any potential AST errors are converted to ZIR errors here.
+    file.zir = try AstGen.generate(gpa, file.tree);
+    file.zir_loaded = true;
+    file.status = .success_zir;
+    log.debug("AstGen fresh success: {s}", .{file.sub_file_path});
+
+    const safety_buffer = if (Zcu.data_has_safety_tag)
+        try gpa.alloc([8]u8, file.zir.instructions.len)
+    else
+        undefined;
+    defer if (Zcu.data_has_safety_tag) gpa.free(safety_buffer);
+    const data_ptr = if (Zcu.data_has_safety_tag)
+        if (file.zir.instructions.len == 0)
+            @as([*]const u8, undefined)
+        else
+            @as([*]const u8, @ptrCast(safety_buffer.ptr))
+    else
+        @as([*]const u8, @ptrCast(file.zir.instructions.items(.data).ptr));
+    if (Zcu.data_has_safety_tag) {
+        // The `Data` union has a safety tag but in the file format we store it without.
+        for (file.zir.instructions.items(.data), 0..) |*data, i| {
+            const as_struct: *const Zcu.HackDataLayout = @ptrCast(data);
+            safety_buffer[i] = as_struct.data;
+        }
+    }
+
+    const header: Zir.Header = .{
+        .instructions_len = @as(u32, @intCast(file.zir.instructions.len)),
+        .string_bytes_len = @as(u32, @intCast(file.zir.string_bytes.len)),
+        .extra_len = @as(u32, @intCast(file.zir.extra.len)),
+
+        .stat_size = stat.size,
+        .stat_inode = stat.inode,
+        .stat_mtime = stat.mtime,
+    };
+    var iovecs = [_]std.posix.iovec_const{
+        .{
+            .base = @as([*]const u8, @ptrCast(&header)),
+            .len = @sizeOf(Zir.Header),
+        },
+        .{
+            .base = @as([*]const u8, @ptrCast(file.zir.instructions.items(.tag).ptr)),
+            .len = file.zir.instructions.len,
+        },
+        .{
+            .base = data_ptr,
+            .len = file.zir.instructions.len * 8,
+        },
+        .{
+            .base = file.zir.string_bytes.ptr,
+            .len = file.zir.string_bytes.len,
+        },
+        .{
+            .base = @as([*]const u8, @ptrCast(file.zir.extra.ptr)),
+            .len = file.zir.extra.len * 4,
+        },
+    };
+    cache_file.writevAll(&iovecs) catch |err| {
+        log.warn("unable to write cached ZIR code for {}{s} to {}{s}: {s}", .{
+            file.mod.root, file.sub_file_path, cache_directory, &hex_digest, @errorName(err),
+        });
+    };
+
+    if (file.zir.hasCompileErrors()) {
+        {
+            comp.mutex.lock();
+            defer comp.mutex.unlock();
+            try zcu.failed_files.putNoClobber(gpa, file, null);
+        }
+        file.status = .astgen_failure;
+        return error.AnalysisFail;
+    }
+
+    if (file.prev_zir) |prev_zir| {
+        try pt.updateZirRefs(file, file_index, prev_zir.*);
+        // No need to keep previous ZIR.
+        prev_zir.deinit(gpa);
+        gpa.destroy(prev_zir);
+        file.prev_zir = null;
+    }
+
+    if (opt_root_decl.unwrap()) |root_decl| {
+        // The root of this file must be re-analyzed, since the file has changed.
+        comp.mutex.lock();
+        defer comp.mutex.unlock();
+
+        log.debug("outdated root Decl: {}", .{root_decl});
+        try zcu.outdated_file_root.put(gpa, root_decl, {});
+    }
+}
+
+/// This is called from the AstGen thread pool, so must acquire
+/// the Compilation mutex when acting on shared state.
+fn updateZirRefs(pt: Zcu.PerThread, file: *Zcu.File, file_index: Zcu.File.Index, old_zir: Zir) !void {
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const new_zir = file.zir;
+
+    var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
+    defer inst_map.deinit(gpa);
+
+    try Zcu.mapOldZirToNew(gpa, old_zir, new_zir, &inst_map);
+
+    const old_tag = old_zir.instructions.items(.tag);
+    const old_data = old_zir.instructions.items(.data);
+
+    // TODO: this should be done after all AstGen workers complete, to avoid
+    // iterating over this full set for every updated file.
+    for (zcu.intern_pool.tracked_insts.keys(), 0..) |*ti, idx_raw| {
+        const ti_idx: InternPool.TrackedInst.Index = @enumFromInt(idx_raw);
+        if (ti.file != file_index) continue;
+        const old_inst = ti.inst;
+        ti.inst = inst_map.get(ti.inst) orelse {
+            // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
+            zcu.comp.mutex.lock();
+            defer zcu.comp.mutex.unlock();
+            log.debug("tracking failed for %{d}", .{old_inst});
+            try zcu.markDependeeOutdated(.{ .src_hash = ti_idx });
+            continue;
+        };
+
+        if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: {
+            if (new_zir.getAssociatedSrcHash(ti.inst)) |new_hash| {
+                if (std.zig.srcHashEql(old_hash, new_hash)) {
+                    break :hash_changed;
+                }
+                log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{
+                    old_inst,
+                    ti.inst,
+                    std.fmt.fmtSliceHexLower(&old_hash),
+                    std.fmt.fmtSliceHexLower(&new_hash),
+                });
+            }
+            // The source hash associated with this instruction changed - invalidate relevant dependencies.
+            zcu.comp.mutex.lock();
+            defer zcu.comp.mutex.unlock();
+            try zcu.markDependeeOutdated(.{ .src_hash = ti_idx });
+        }
+
+        // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies.
+        const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {
+            .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {
+                .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,
+                else => false,
+            },
+            else => false,
+        };
+        if (!has_namespace) continue;
+
+        var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
+        defer old_names.deinit(zcu.gpa);
+        {
+            var it = old_zir.declIterator(old_inst);
+            while (it.next()) |decl_inst| {
+                const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
+                switch (decl_name) {
+                    .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
+                    _ => if (decl_name.isNamedTest(old_zir)) continue,
+                }
+                const name_zir = decl_name.toString(old_zir).?;
+                const name_ip = try zcu.intern_pool.getOrPutString(
+                    zcu.gpa,
+                    pt.tid,
+                    old_zir.nullTerminatedString(name_zir),
+                    .no_embedded_nulls,
+                );
+                try old_names.put(zcu.gpa, name_ip, {});
+            }
+        }
+        var any_change = false;
+        {
+            var it = new_zir.declIterator(ti.inst);
+            while (it.next()) |decl_inst| {
+                const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
+                switch (decl_name) {
+                    .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
+                    _ => if (decl_name.isNamedTest(old_zir)) continue,
+                }
+                const name_zir = decl_name.toString(old_zir).?;
+                const name_ip = try zcu.intern_pool.getOrPutString(
+                    zcu.gpa,
+                    pt.tid,
+                    old_zir.nullTerminatedString(name_zir),
+                    .no_embedded_nulls,
+                );
+                if (!old_names.swapRemove(name_ip)) continue;
+                // Name added
+                any_change = true;
+                zcu.comp.mutex.lock();
+                defer zcu.comp.mutex.unlock();
+                try zcu.markDependeeOutdated(.{ .namespace_name = .{
+                    .namespace = ti_idx,
+                    .name = name_ip,
+                } });
+            }
+        }
+        // The only elements remaining in `old_names` now are any names which were removed.
+        for (old_names.keys()) |name_ip| {
+            any_change = true;
+            zcu.comp.mutex.lock();
+            defer zcu.comp.mutex.unlock();
+            try zcu.markDependeeOutdated(.{ .namespace_name = .{
+                .namespace = ti_idx,
+                .name = name_ip,
+            } });
+        }
+
+        if (any_change) {
+            zcu.comp.mutex.lock();
+            defer zcu.comp.mutex.unlock();
+            try zcu.markDependeeOutdated(.{ .namespace = ti_idx });
+        }
+    }
+}
+
 /// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.
 pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
     if (pt.zcu.fileRootDecl(file_index).unwrap()) |existing_root| {
@@ -91,7 +496,7 @@ pub fn ensureDeclAnalyzed(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) Zcu.Sem
             };
         }
 
-        const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(mod)).toSlice(ip), 0);
+        const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(pt)).toSlice(ip), 0);
         defer decl_prog_node.end();
 
         break :blk pt.semaDecl(decl_index) catch |err| switch (err) {
@@ -290,7 +695,7 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
     defer liveness.deinit(gpa);
 
     if (build_options.enable_debug_extensions and comp.verbose_air) {
-        const fqn = try decl.fullyQualifiedName(zcu);
+        const fqn = try decl.fullyQualifiedName(pt);
         std.debug.print("# Begin Function AIR: {}:\n", .{fqn.fmt(ip)});
         @import("../print_air.zig").dump(pt, air, liveness);
         std.debug.print("# End Function AIR: {}\n\n", .{fqn.fmt(ip)});
@@ -324,7 +729,7 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
         };
     }
 
-    const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(zcu)).toSlice(ip), 0);
+    const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(pt)).toSlice(ip), 0);
     defer codegen_prog_node.end();
 
     if (!air.typesFullyResolved(zcu)) {
@@ -434,7 +839,7 @@ fn getFileRootStruct(
     decl.owns_tv = true;
     decl.analysis = .complete;
 
-    try zcu.scanNamespace(namespace_index, decls, decl);
+    try pt.scanNamespace(namespace_index, decls, decl);
     try zcu.comp.work_queue.writeItem(.{ .resolve_type_fully = wip_ty.index });
     return wip_ty.finish(ip, decl_index, namespace_index.toOptional());
 }
@@ -502,7 +907,7 @@ fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated:
     const decls = file.zir.bodySlice(extra_index, decls_len);
 
     if (!type_outdated) {
-        try zcu.scanNamespace(decl.src_namespace, decls, decl);
+        try pt.scanNamespace(decl.src_namespace, decls, decl);
     }
 
     return false;
@@ -539,7 +944,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
     zcu.setFileRootDecl(file_index, new_decl_index.toOptional());
     zcu.namespacePtr(new_namespace_index).decl_index = new_decl_index;
 
-    new_decl.name = try file.fullyQualifiedName(zcu);
+    new_decl.name = try file.fullyQualifiedName(pt);
     new_decl.name_fully_qualified = true;
     new_decl.is_pub = true;
     new_decl.is_exported = false;
@@ -601,9 +1006,9 @@ fn semaDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
     }
 
     log.debug("semaDecl '{d}'", .{@intFromEnum(decl_index)});
-    log.debug("decl name '{}'", .{(try decl.fullyQualifiedName(zcu)).fmt(ip)});
+    log.debug("decl name '{}'", .{(try decl.fullyQualifiedName(pt)).fmt(ip)});
     defer blk: {
-        log.debug("finish decl name '{}'", .{(decl.fullyQualifiedName(zcu) catch break :blk).fmt(ip)});
+        log.debug("finish decl name '{}'", .{(decl.fullyQualifiedName(pt) catch break :blk).fmt(ip)});
     }
 
     const old_has_tv = decl.has_tv;
@@ -631,7 +1036,7 @@ fn semaDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
         const std_file_root_decl_index = zcu.fileRootDecl(std_file_imported.file_index);
         const std_decl = zcu.declPtr(std_file_root_decl_index.unwrap().?);
         const std_namespace = std_decl.getInnerNamespace(zcu).?;
-        const builtin_str = try ip.getOrPutString(gpa, "builtin", .no_embedded_nulls);
+        const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
         const builtin_decl = zcu.declPtr(std_namespace.decls.getKeyAdapted(builtin_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse break :ip_index .none);
         const builtin_namespace = builtin_decl.getInnerNamespaceIndex(zcu).unwrap() orelse break :ip_index .none;
         if (decl.src_namespace != builtin_namespace) break :ip_index .none;
@@ -802,7 +1207,7 @@ fn semaDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
             } else if (bytes.len == 0) {
                 return sema.fail(&block_scope, section_src, "linksection cannot be empty", .{});
             }
-            break :blk try ip.getOrPutStringOpt(gpa, bytes, .no_embedded_nulls);
+            break :blk try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
         };
         decl.@"addrspace" = blk: {
             const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(decl_val.toIntern())) {
@@ -996,7 +1401,7 @@ fn newEmbedFile(
     } });
     const array_val = try pt.intern(.{ .aggregate = .{
         .ty = array_ty,
-        .storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, bytes.len, .maybe_embedded_nulls) },
+        .storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, pt.tid, bytes.len, .maybe_embedded_nulls) },
     } });
 
     const ptr_ty = (try pt.ptrType(.{
@@ -1018,7 +1423,7 @@ fn newEmbedFile(
 
     result.* = new_file;
     new_file.* = .{
-        .sub_file_path = try ip.getOrPutString(gpa, sub_file_path, .no_embedded_nulls),
+        .sub_file_path = try ip.getOrPutString(gpa, pt.tid, sub_file_path, .no_embedded_nulls),
         .owner = pkg,
         .stat = stat,
         .val = ptr_val,
@@ -1027,6 +1432,271 @@ fn newEmbedFile(
     return ptr_val;
 }
 
+pub fn scanNamespace(
+    pt: Zcu.PerThread,
+    namespace_index: Zcu.Namespace.Index,
+    decls: []const Zir.Inst.Index,
+    parent_decl: *Zcu.Decl,
+) Allocator.Error!void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const namespace = zcu.namespacePtr(namespace_index);
+
+    // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather
+    // than their name. We'll build an efficient mapping now, then discard the current `decls`.
+    var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Zcu.Decl.Index) = .{};
+    defer existing_by_inst.deinit(gpa);
+
+    try existing_by_inst.ensureTotalCapacity(gpa, @intCast(namespace.decls.count()));
+
+    for (namespace.decls.keys()) |decl_index| {
+        const decl = zcu.declPtr(decl_index);
+        existing_by_inst.putAssumeCapacityNoClobber(decl.zir_decl_index.unwrap().?, decl_index);
+    }
+
+    var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
+    defer seen_decls.deinit(gpa);
+
+    try zcu.comp.work_queue.ensureUnusedCapacity(decls.len);
+
+    namespace.decls.clearRetainingCapacity();
+    try namespace.decls.ensureTotalCapacity(gpa, decls.len);
+
+    namespace.usingnamespace_set.clearRetainingCapacity();
+
+    var scan_decl_iter: ScanDeclIter = .{
+        .pt = pt,
+        .namespace_index = namespace_index,
+        .parent_decl = parent_decl,
+        .seen_decls = &seen_decls,
+        .existing_by_inst = &existing_by_inst,
+        .pass = .named,
+    };
+    for (decls) |decl_inst| {
+        try scan_decl_iter.scanDecl(decl_inst);
+    }
+    scan_decl_iter.pass = .unnamed;
+    for (decls) |decl_inst| {
+        try scan_decl_iter.scanDecl(decl_inst);
+    }
+
+    if (seen_decls.count() != namespace.decls.count()) {
+        // Do a pass over the namespace contents and remove any decls from the last update
+        // which were removed in this one.
+        var i: usize = 0;
+        while (i < namespace.decls.count()) {
+            const decl_index = namespace.decls.keys()[i];
+            const decl = zcu.declPtr(decl_index);
+            if (!seen_decls.contains(decl.name)) {
+                // We must preserve namespace ordering for @typeInfo.
+                namespace.decls.orderedRemoveAt(i);
+                i -= 1;
+            }
+        }
+    }
+}
+
+const ScanDeclIter = struct {
+    pt: Zcu.PerThread,
+    namespace_index: Zcu.Namespace.Index,
+    parent_decl: *Zcu.Decl,
+    seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
+    existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Zcu.Decl.Index),
+    /// Decl scanning is run in two passes, so that we can detect when a generated
+    /// name would clash with an explicit name and use a different one.
+    pass: enum { named, unnamed },
+    usingnamespace_index: usize = 0,
+    comptime_index: usize = 0,
+    unnamed_test_index: usize = 0,
+
+    fn avoidNameConflict(iter: *ScanDeclIter, comptime fmt: []const u8, args: anytype) !InternPool.NullTerminatedString {
+        const pt = iter.pt;
+        const gpa = pt.zcu.gpa;
+        const ip = &pt.zcu.intern_pool;
+        var name = try ip.getOrPutStringFmt(gpa, pt.tid, fmt, args, .no_embedded_nulls);
+        var gop = try iter.seen_decls.getOrPut(gpa, name);
+        var next_suffix: u32 = 0;
+        while (gop.found_existing) {
+            name = try ip.getOrPutStringFmt(gpa, pt.tid, "{}_{d}", .{ name.fmt(ip), next_suffix }, .no_embedded_nulls);
+            gop = try iter.seen_decls.getOrPut(gpa, name);
+            next_suffix += 1;
+        }
+        return name;
+    }
+
+    fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void {
+        const tracy = trace(@src());
+        defer tracy.end();
+
+        const pt = iter.pt;
+        const zcu = pt.zcu;
+        const namespace_index = iter.namespace_index;
+        const namespace = zcu.namespacePtr(namespace_index);
+        const gpa = zcu.gpa;
+        const zir = namespace.fileScope(zcu).zir;
+        const ip = &zcu.intern_pool;
+
+        const inst_data = zir.instructions.items(.data)[@intFromEnum(decl_inst)].declaration;
+        const extra = zir.extraData(Zir.Inst.Declaration, inst_data.payload_index);
+        const declaration = extra.data;
+
+        // Every Decl needs a name.
+        const decl_name: InternPool.NullTerminatedString, const kind: Zcu.Decl.Kind, const is_named_test: bool = switch (declaration.name) {
+            .@"comptime" => info: {
+                if (iter.pass != .unnamed) return;
+                const i = iter.comptime_index;
+                iter.comptime_index += 1;
+                break :info .{
+                    try iter.avoidNameConflict("comptime_{d}", .{i}),
+                    .@"comptime",
+                    false,
+                };
+            },
+            .@"usingnamespace" => info: {
+                // TODO: this isn't right! These should be considered unnamed. Name conflicts can happen here.
+                // The problem is, we need to preserve the decl ordering for `@typeInfo`.
+                // I'm not bothering to fix this now, since some upcoming changes will change this code significantly anyway.
+                if (iter.pass != .named) return;
+                const i = iter.usingnamespace_index;
+                iter.usingnamespace_index += 1;
+                break :info .{
+                    try iter.avoidNameConflict("usingnamespace_{d}", .{i}),
+                    .@"usingnamespace",
+                    false,
+                };
+            },
+            .unnamed_test => info: {
+                if (iter.pass != .unnamed) return;
+                const i = iter.unnamed_test_index;
+                iter.unnamed_test_index += 1;
+                break :info .{
+                    try iter.avoidNameConflict("test_{d}", .{i}),
+                    .@"test",
+                    false,
+                };
+            },
+            .decltest => info: {
+                // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary.
+                if (iter.pass != .unnamed) return;
+                assert(declaration.flags.has_doc_comment);
+                const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end]));
+                break :info .{
+                    try iter.avoidNameConflict("decltest.{s}", .{name}),
+                    .@"test",
+                    true,
+                };
+            },
+            _ => if (declaration.name.isNamedTest(zir)) info: {
+                // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary.
+                if (iter.pass != .unnamed) return;
+                break :info .{
+                    try iter.avoidNameConflict("test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}),
+                    .@"test",
+                    true,
+                };
+            } else info: {
+                if (iter.pass != .named) return;
+                const name = try ip.getOrPutString(
+                    gpa,
+                    pt.tid,
+                    zir.nullTerminatedString(declaration.name.toString(zir).?),
+                    .no_embedded_nulls,
+                );
+                try iter.seen_decls.putNoClobber(gpa, name, {});
+                break :info .{
+                    name,
+                    .named,
+                    false,
+                };
+            },
+        };
+
+        switch (kind) {
+            .@"usingnamespace" => try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1),
+            .@"test" => try zcu.test_functions.ensureUnusedCapacity(gpa, 1),
+            else => {},
+        }
+
+        const parent_file_scope_index = iter.parent_decl.getFileScopeIndex(zcu);
+        const tracked_inst = try ip.trackZir(gpa, parent_file_scope_index, decl_inst);
+
+        // We create a Decl for it regardless of analysis status.
+
+        const prev_exported, const decl_index = if (iter.existing_by_inst.get(tracked_inst)) |decl_index| decl_index: {
+            // We need only update this existing Decl.
+            const decl = zcu.declPtr(decl_index);
+            const was_exported = decl.is_exported;
+            assert(decl.kind == kind); // ZIR tracking should preserve this
+            decl.name = decl_name;
+            decl.is_pub = declaration.flags.is_pub;
+            decl.is_exported = declaration.flags.is_export;
+            break :decl_index .{ was_exported, decl_index };
+        } else decl_index: {
+            // Create and set up a new Decl.
+            const new_decl_index = try zcu.allocateNewDecl(namespace_index);
+            const new_decl = zcu.declPtr(new_decl_index);
+            new_decl.kind = kind;
+            new_decl.name = decl_name;
+            new_decl.is_pub = declaration.flags.is_pub;
+            new_decl.is_exported = declaration.flags.is_export;
+            new_decl.zir_decl_index = tracked_inst.toOptional();
+            break :decl_index .{ false, new_decl_index };
+        };
+
+        const decl = zcu.declPtr(decl_index);
+
+        namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu });
+
+        const comp = zcu.comp;
+        const decl_mod = namespace.fileScope(zcu).mod;
+        const want_analysis = declaration.flags.is_export or switch (kind) {
+            .anon => unreachable,
+            .@"comptime" => true,
+            .@"usingnamespace" => a: {
+                namespace.usingnamespace_set.putAssumeCapacityNoClobber(decl_index, declaration.flags.is_pub);
+                break :a true;
+            },
+            .named => false,
+            .@"test" => a: {
+                if (!comp.config.is_test) break :a false;
+                if (decl_mod != zcu.main_mod) break :a false;
+                if (is_named_test and comp.test_filters.len > 0) {
+                    const decl_fqn = try namespace.fullyQualifiedName(pt, decl_name);
+                    const decl_fqn_slice = decl_fqn.toSlice(ip);
+                    for (comp.test_filters) |test_filter| {
+                        if (std.mem.indexOf(u8, decl_fqn_slice, test_filter)) |_| break;
+                    } else break :a false;
+                }
+                zcu.test_functions.putAssumeCapacity(decl_index, {}); // may clobber on incremental update
+                break :a true;
+            },
+        };
+
+        if (want_analysis) {
+            // We will not queue analysis if the decl has been analyzed on a previous update and
+            // `is_export` is unchanged. In this case, the incremental update mechanism will handle
+            // re-analysis for us if necessary.
+            if (prev_exported != declaration.flags.is_export or decl.analysis == .unreferenced) {
+                log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{}' decl_index={d}", .{
+                    namespace.fileScope(zcu).sub_file_path, decl_name.fmt(ip), decl_index,
+                });
+                comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = decl_index });
+            }
+        }
+
+        if (decl.getOwnedFunction(zcu) != null) {
+            // TODO this logic is insufficient; namespaces we don't re-scan may still require
+            // updated line numbers. Look into this!
+            // TODO Look into detecting when this would be unnecessary by storing enough state
+            // in `Decl` to notice that the line number did not change.
+            comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index });
+        }
+    }
+};
+
 pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: Allocator) Zcu.SemaError!Air {
     const tracy = trace(@src());
     defer tracy.end();
@@ -1038,12 +1708,12 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
     const decl_index = func.owner_decl;
     const decl = mod.declPtr(decl_index);
 
-    log.debug("func name '{}'", .{(try decl.fullyQualifiedName(mod)).fmt(ip)});
+    log.debug("func name '{}'", .{(try decl.fullyQualifiedName(pt)).fmt(ip)});
     defer blk: {
-        log.debug("finish func name '{}'", .{(decl.fullyQualifiedName(mod) catch break :blk).fmt(ip)});
+        log.debug("finish func name '{}'", .{(decl.fullyQualifiedName(pt) catch break :blk).fmt(ip)});
     }
 
-    const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(mod)).toSlice(ip), 0);
+    const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(pt)).toSlice(ip), 0);
     defer decl_prog_node.end();
 
     mod.intern_pool.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .func = func_index }));
@@ -1273,6 +1943,19 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
     };
 }
 
+fn lockAndClearFileCompileError(pt: Zcu.PerThread, file: *Zcu.File) void {
+    switch (file.status) {
+        .success_zir, .retryable_failure => {},
+        .never_loaded, .parse_failure, .astgen_failure => {
+            pt.zcu.comp.mutex.lock();
+            defer pt.zcu.comp.mutex.unlock();
+            if (pt.zcu.failed_files.fetchSwapRemove(file)) |kv| {
+                if (kv.value) |msg| msg.destroy(pt.zcu.gpa); // Delete previous error message.
+            }
+        },
+    }
+}
+
 /// Called from `Compilation.update`, after everything is done, just before
 /// reporting compile errors. In this function we emit exported symbol collision
 /// errors and communicate exported symbols to the linker backend.
@@ -1397,7 +2080,7 @@ pub fn populateTestFunctions(
     const root_decl_index = zcu.fileRootDecl(builtin_file_index);
     const root_decl = zcu.declPtr(root_decl_index.unwrap().?);
     const builtin_namespace = zcu.namespacePtr(root_decl.src_namespace);
-    const test_functions_str = try ip.getOrPutString(gpa, "test_functions", .no_embedded_nulls);
+    const test_functions_str = try ip.getOrPutString(gpa, pt.tid, "test_functions", .no_embedded_nulls);
     const decl_index = builtin_namespace.decls.getKeyAdapted(
         test_functions_str,
         Zcu.DeclAdapter{ .zcu = zcu },
@@ -1424,7 +2107,7 @@ pub fn populateTestFunctions(
 
         for (test_fn_vals, zcu.test_functions.keys()) |*test_fn_val, test_decl_index| {
             const test_decl = zcu.declPtr(test_decl_index);
-            const test_decl_name = try test_decl.fullyQualifiedName(zcu);
+            const test_decl_name = try test_decl.fullyQualifiedName(pt);
             const test_decl_name_len = test_decl_name.length(ip);
             const test_name_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = n: {
                 const test_name_ty = try pt.arrayType(.{
@@ -1530,7 +2213,7 @@ pub fn linkerUpdateDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !void {
 
     const decl = zcu.declPtr(decl_index);
 
-    const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(zcu)).toSlice(&zcu.intern_pool), 0);
+    const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(pt)).toSlice(&zcu.intern_pool), 0);
     defer codegen_prog_node.end();
 
     if (comp.bin_file) |lf| {
@@ -2064,11 +2747,11 @@ pub fn getBuiltinDecl(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Inter
     const std_file_imported = zcu.importPkg(zcu.std_mod) catch @panic("failed to import lib/std.zig");
     const std_file_root_decl = zcu.fileRootDecl(std_file_imported.file_index).unwrap().?;
     const std_namespace = zcu.declPtr(std_file_root_decl).getOwnedInnerNamespace(zcu).?;
-    const builtin_str = try ip.getOrPutString(gpa, "builtin", .no_embedded_nulls);
+    const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
     const builtin_decl = std_namespace.decls.getKeyAdapted(builtin_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse @panic("lib/std.zig is corrupt and missing 'builtin'");
     pt.ensureDeclAnalyzed(builtin_decl) catch @panic("std.builtin is corrupt");
     const builtin_namespace = zcu.declPtr(builtin_decl).getInnerNamespace(zcu) orelse @panic("std.builtin is corrupt");
-    const name_str = try ip.getOrPutString(gpa, name, .no_embedded_nulls);
+    const name_str = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
     return builtin_namespace.decls.getKeyAdapted(name_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt");
 }
 
@@ -2082,6 +2765,8 @@ pub fn getBuiltinType(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Type
 const Air = @import("../Air.zig");
 const Allocator = std.mem.Allocator;
 const assert = std.debug.assert;
+const Ast = std.zig.Ast;
+const AstGen = std.zig.AstGen;
 const BigIntConst = std.math.big.int.Const;
 const BigIntMutable = std.math.big.int.Mutable;
 const build_options = @import("build_options");
src/codegen.zig
@@ -756,7 +756,7 @@ fn lowerDeclRef(
         return Result.ok;
     }
 
-    const vaddr = try lf.getDeclVAddr(decl_index, .{
+    const vaddr = try lf.getDeclVAddr(pt, decl_index, .{
         .parent_atom_index = reloc_info.parent_atom_index,
         .offset = code.items.len,
         .addend = @intCast(offset),
src/Compilation.zig
@@ -29,8 +29,6 @@ const wasi_libc = @import("wasi_libc.zig");
 const fatal = @import("main.zig").fatal;
 const clangMain = @import("main.zig").clangMain;
 const Zcu = @import("Zcu.zig");
-/// Deprecated; use `Zcu`.
-const Module = Zcu;
 const Sema = @import("Sema.zig");
 const InternPool = @import("InternPool.zig");
 const Cache = std.Build.Cache;
@@ -50,7 +48,7 @@ gpa: Allocator,
 arena: Allocator,
 /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.
 /// TODO: rename to zcu: ?*Zcu
-module: ?*Module,
+module: ?*Zcu,
 /// Contains different state depending on whether the Compilation uses
 /// incremental or whole cache mode.
 cache_use: CacheUse,
@@ -120,7 +118,7 @@ astgen_work_queue: std.fifo.LinearFifo(Zcu.File.Index, .Dynamic),
 /// These jobs are to inspect the file system stat() and if the embedded file has changed
 /// on disk, mark the corresponding Decl outdated and queue up an `analyze_decl`
 /// task for it.
-embed_file_work_queue: std.fifo.LinearFifo(*Module.EmbedFile, .Dynamic),
+embed_file_work_queue: std.fifo.LinearFifo(*Zcu.EmbedFile, .Dynamic),
 
 /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator.
 /// This data is accessed by multiple threads and is protected by `mutex`.
@@ -252,7 +250,7 @@ pub const Emit = struct {
 };
 
 pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size;
-pub const SemaError = Module.SemaError;
+pub const SemaError = Zcu.SemaError;
 
 pub const CRTFile = struct {
     lock: Cache.Lock,
@@ -1138,7 +1136,7 @@ pub const CreateOptions = struct {
     pdb_source_path: ?[]const u8 = null,
     /// (Windows) PDB output path
     pdb_out_path: ?[]const u8 = null,
-    error_limit: ?Compilation.Module.ErrorInt = null,
+    error_limit: ?Zcu.ErrorInt = null,
     global_cc_argv: []const []const u8 = &.{},
 
     pub const Entry = link.File.OpenOptions.Entry;
@@ -1344,7 +1342,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
 
         const main_mod = options.main_mod orelse options.root_mod;
         const comp = try arena.create(Compilation);
-        const opt_zcu: ?*Module = if (have_zcu) blk: {
+        const opt_zcu: ?*Zcu = if (have_zcu) blk: {
             // Pre-open the directory handles for cached ZIR code so that it does not need
             // to redundantly happen for each AstGen operation.
             const zir_sub_dir = "z";
@@ -1362,8 +1360,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
                 .path = try options.global_cache_directory.join(arena, &[_][]const u8{zir_sub_dir}),
             };
 
-            const emit_h: ?*Module.GlobalEmitH = if (options.emit_h) |loc| eh: {
-                const eh = try arena.create(Module.GlobalEmitH);
+            const emit_h: ?*Zcu.GlobalEmitH = if (options.emit_h) |loc| eh: {
+                const eh = try arena.create(Zcu.GlobalEmitH);
                 eh.* = .{ .loc = loc };
                 break :eh eh;
             } else null;
@@ -1386,7 +1384,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
                 .builtin_modules = null, // `builtin_mod` is set
             });
 
-            const zcu = try arena.create(Module);
+            const zcu = try arena.create(Zcu);
             zcu.* = .{
                 .gpa = gpa,
                 .comp = comp,
@@ -1434,7 +1432,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
             .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
             .win32_resource_work_queue = if (build_options.only_core_functionality) {} else std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa),
             .astgen_work_queue = std.fifo.LinearFifo(Zcu.File.Index, .Dynamic).init(gpa),
-            .embed_file_work_queue = std.fifo.LinearFifo(*Module.EmbedFile, .Dynamic).init(gpa),
+            .embed_file_work_queue = std.fifo.LinearFifo(*Zcu.EmbedFile, .Dynamic).init(gpa),
             .c_source_files = options.c_source_files,
             .rc_source_files = options.rc_source_files,
             .cache_parent = cache,
@@ -2626,7 +2624,7 @@ fn reportMultiModuleErrors(zcu: *Zcu) !void {
     var num_errors: u32 = 0;
     const max_errors = 5;
     // Attach the "some omitted" note to the final error message
-    var last_err: ?*Module.ErrorMsg = null;
+    var last_err: ?*Zcu.ErrorMsg = null;
 
     for (zcu.import_table.values(), 0..) |file, file_index_usize| {
         if (!file.multi_pkg) continue;
@@ -2642,13 +2640,13 @@ fn reportMultiModuleErrors(zcu: *Zcu) !void {
             const omitted = file.references.items.len -| max_notes;
             const num_notes = file.references.items.len - omitted;
 
-            const notes = try gpa.alloc(Module.ErrorMsg, if (omitted > 0) num_notes + 1 else num_notes);
+            const notes = try gpa.alloc(Zcu.ErrorMsg, if (omitted > 0) num_notes + 1 else num_notes);
             errdefer gpa.free(notes);
 
             for (notes[0..num_notes], file.references.items[0..num_notes], 0..) |*note, ref, i| {
                 errdefer for (notes[0..i]) |*n| n.deinit(gpa);
                 note.* = switch (ref) {
-                    .import => |import| try Module.ErrorMsg.init(
+                    .import => |import| try Zcu.ErrorMsg.init(
                         gpa,
                         .{
                             .base_node_inst = try ip.trackZir(gpa, import.file, .main_struct_inst),
@@ -2657,7 +2655,7 @@ fn reportMultiModuleErrors(zcu: *Zcu) !void {
                         "imported from module {s}",
                         .{zcu.fileByIndex(import.file).mod.fully_qualified_name},
                     ),
-                    .root => |pkg| try Module.ErrorMsg.init(
+                    .root => |pkg| try Zcu.ErrorMsg.init(
                         gpa,
                         .{
                             .base_node_inst = try ip.trackZir(gpa, file_index, .main_struct_inst),
@@ -2671,7 +2669,7 @@ fn reportMultiModuleErrors(zcu: *Zcu) !void {
             errdefer for (notes[0..num_notes]) |*n| n.deinit(gpa);
 
             if (omitted > 0) {
-                notes[num_notes] = try Module.ErrorMsg.init(
+                notes[num_notes] = try Zcu.ErrorMsg.init(
                     gpa,
                     .{
                         .base_node_inst = try ip.trackZir(gpa, file_index, .main_struct_inst),
@@ -2683,7 +2681,7 @@ fn reportMultiModuleErrors(zcu: *Zcu) !void {
             }
             errdefer if (omitted > 0) notes[num_notes].deinit(gpa);
 
-            const err = try Module.ErrorMsg.create(
+            const err = try Zcu.ErrorMsg.create(
                 gpa,
                 .{
                     .base_node_inst = try ip.trackZir(gpa, file_index, .main_struct_inst),
@@ -2706,7 +2704,7 @@ fn reportMultiModuleErrors(zcu: *Zcu) !void {
 
         // There isn't really any meaningful place to put this note, so just attach it to the
         // last failed file
-        var note = try Module.ErrorMsg.init(
+        var note = try Zcu.ErrorMsg.init(
             gpa,
             err.src_loc,
             "{} more errors omitted",
@@ -3095,10 +3093,10 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
             const values = zcu.compile_log_sources.values();
             // First one will be the error; subsequent ones will be notes.
             const src_loc = values[0].src();
-            const err_msg: Module.ErrorMsg = .{
+            const err_msg: Zcu.ErrorMsg = .{
                 .src_loc = src_loc,
                 .msg = "found compile log statement",
-                .notes = try gpa.alloc(Module.ErrorMsg, zcu.compile_log_sources.count() - 1),
+                .notes = try gpa.alloc(Zcu.ErrorMsg, zcu.compile_log_sources.count() - 1),
             };
             defer gpa.free(err_msg.notes);
 
@@ -3166,9 +3164,9 @@ pub const ErrorNoteHashContext = struct {
 };
 
 pub fn addModuleErrorMsg(
-    mod: *Module,
+    mod: *Zcu,
     eb: *ErrorBundle.Wip,
-    module_err_msg: Module.ErrorMsg,
+    module_err_msg: Zcu.ErrorMsg,
     all_references: *const std.AutoHashMapUnmanaged(InternPool.AnalUnit, Zcu.ResolvedReference),
 ) !void {
     const gpa = eb.gpa;
@@ -3299,7 +3297,7 @@ pub fn addModuleErrorMsg(
     }
 }
 
-pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void {
+pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
     assert(file.zir_loaded);
     assert(file.tree_loaded);
     assert(file.source_loaded);
@@ -3378,7 +3376,7 @@ pub fn performAllTheWork(
                     const path_digest = zcu.filePathDigest(file_index);
                     const root_decl = zcu.fileRootDecl(file_index);
                     const file = zcu.fileByIndex(file_index);
-                    comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerAstGenFile, .{
+                    comp.thread_pool.spawnWgId(&comp.astgen_wait_group, workerAstGenFile, .{
                         comp, file, file_index, path_digest, root_decl, zir_prog_node, &comp.astgen_wait_group, .root,
                     });
                 }
@@ -3587,22 +3585,22 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
             defer named_frame.end();
 
             const gpa = comp.gpa;
-            const zcu = comp.module.?;
-            const decl = zcu.declPtr(decl_index);
+            const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
+            const decl = pt.zcu.declPtr(decl_index);
             const lf = comp.bin_file.?;
-            lf.updateDeclLineNumber(zcu, decl_index) catch |err| {
-                try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
-                zcu.failed_analysis.putAssumeCapacityNoClobber(
+            lf.updateDeclLineNumber(pt, decl_index) catch |err| {
+                try pt.zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
+                pt.zcu.failed_analysis.putAssumeCapacityNoClobber(
                     InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
                     try Zcu.ErrorMsg.create(
                         gpa,
-                        decl.navSrcLoc(zcu),
+                        decl.navSrcLoc(pt.zcu),
                         "unable to update line number: {s}",
                         .{@errorName(err)},
                     ),
                 );
                 decl.analysis = .codegen_failure;
-                try zcu.retryable_failures.append(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
+                try pt.zcu.retryable_failures.append(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
             };
         },
         .analyze_mod => |mod| {
@@ -4049,6 +4047,7 @@ const AstGenSrc = union(enum) {
 };
 
 fn workerAstGenFile(
+    tid: usize,
     comp: *Compilation,
     file: *Zcu.File,
     file_index: Zcu.File.Index,
@@ -4061,8 +4060,8 @@ fn workerAstGenFile(
     const child_prog_node = prog_node.start(file.sub_file_path, 0);
     defer child_prog_node.end();
 
-    const zcu = comp.module.?;
-    zcu.astGenFile(file, file_index, path_digest, root_decl) catch |err| switch (err) {
+    const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
+    pt.astGenFile(file, file_index, path_digest, root_decl) catch |err| switch (err) {
         error.AnalysisFail => return,
         else => {
             file.status = .retryable_failure;
@@ -4097,15 +4096,15 @@ fn workerAstGenFile(
                 comp.mutex.lock();
                 defer comp.mutex.unlock();
 
-                const res = zcu.importFile(file, import_path) catch continue;
+                const res = pt.zcu.importFile(file, import_path) catch continue;
                 if (!res.is_pkg) {
-                    res.file.addReference(zcu.*, .{ .import = .{
+                    res.file.addReference(pt.zcu.*, .{ .import = .{
                         .file = file_index,
                         .token = item.data.token,
                     } }) catch continue;
                 }
-                const imported_path_digest = zcu.filePathDigest(res.file_index);
-                const imported_root_decl = zcu.fileRootDecl(res.file_index);
+                const imported_path_digest = pt.zcu.filePathDigest(res.file_index);
+                const imported_root_decl = pt.zcu.fileRootDecl(res.file_index);
                 break :blk .{ res, imported_path_digest, imported_root_decl };
             };
             if (import_result.is_new) {
@@ -4116,7 +4115,7 @@ fn workerAstGenFile(
                     .importing_file = file_index,
                     .import_tok = item.data.token,
                 } };
-                comp.thread_pool.spawnWg(wg, workerAstGenFile, .{
+                comp.thread_pool.spawnWgId(wg, workerAstGenFile, .{
                     comp, import_result.file, import_result.file_index, imported_path_digest, imported_root_decl, prog_node, wg, sub_src,
                 });
             }
@@ -4127,7 +4126,7 @@ fn workerAstGenFile(
 fn workerUpdateBuiltinZigFile(
     comp: *Compilation,
     mod: *Package.Module,
-    file: *Module.File,
+    file: *Zcu.File,
 ) void {
     Builtin.populateFile(comp, mod, file) catch |err| {
         comp.mutex.lock();
@@ -4139,7 +4138,7 @@ fn workerUpdateBuiltinZigFile(
     };
 }
 
-fn workerCheckEmbedFile(comp: *Compilation, embed_file: *Module.EmbedFile) void {
+fn workerCheckEmbedFile(comp: *Compilation, embed_file: *Zcu.EmbedFile) void {
     comp.detectEmbedFileUpdate(embed_file) catch |err| {
         comp.reportRetryableEmbedFileError(embed_file, err) catch |oom| switch (oom) {
             // Swallowing this error is OK because it's implied to be OOM when
@@ -4150,7 +4149,7 @@ fn workerCheckEmbedFile(comp: *Compilation, embed_file: *Module.EmbedFile) void
     };
 }
 
-fn detectEmbedFileUpdate(comp: *Compilation, embed_file: *Module.EmbedFile) !void {
+fn detectEmbedFileUpdate(comp: *Compilation, embed_file: *Zcu.EmbedFile) !void {
     const mod = comp.module.?;
     const ip = &mod.intern_pool;
     var file = try embed_file.owner.root.openFile(embed_file.sub_file_path.toSlice(ip), .{});
@@ -4477,7 +4476,7 @@ fn reportRetryableAstGenError(
     const file = zcu.fileByIndex(file_index);
     file.status = .retryable_failure;
 
-    const src_loc: Module.LazySrcLoc = switch (src) {
+    const src_loc: Zcu.LazySrcLoc = switch (src) {
         .root => .{
             .base_node_inst = try zcu.intern_pool.trackZir(gpa, file_index, .main_struct_inst),
             .offset = .entire_file,
@@ -4488,7 +4487,7 @@ fn reportRetryableAstGenError(
         },
     };
 
-    const err_msg = try Module.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{
+    const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{
         file.mod.root, file.sub_file_path, @errorName(err),
     });
     errdefer err_msg.destroy(gpa);
@@ -4502,14 +4501,14 @@ fn reportRetryableAstGenError(
 
 fn reportRetryableEmbedFileError(
     comp: *Compilation,
-    embed_file: *Module.EmbedFile,
+    embed_file: *Zcu.EmbedFile,
     err: anyerror,
 ) error{OutOfMemory}!void {
     const mod = comp.module.?;
     const gpa = mod.gpa;
     const src_loc = embed_file.src_loc;
     const ip = &mod.intern_pool;
-    const err_msg = try Module.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{
+    const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}{s}': {s}", .{
         embed_file.owner.root,
         embed_file.sub_file_path.toSlice(ip),
         @errorName(err),
src/InternPool.zig
@@ -4539,7 +4539,7 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void {
     assert(ip.items.len == 0);
 
     // Reserve string index 0 for an empty string.
-    assert((try ip.getOrPutString(gpa, "", .no_embedded_nulls)) == .empty);
+    assert((try ip.getOrPutString(gpa, .main, "", .no_embedded_nulls)) == .empty);
 
     // So that we can use `catch unreachable` below.
     try ip.items.ensureUnusedCapacity(gpa, static_keys.len);
@@ -5986,6 +5986,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                 );
                 const string = try ip.getOrPutTrailingString(
                     gpa,
+                    tid,
                     @intCast(len_including_sentinel),
                     .maybe_embedded_nulls,
                 );
@@ -6865,6 +6866,7 @@ pub fn getFuncInstance(
     return finishFuncInstance(
         ip,
         gpa,
+        tid,
         generic_owner,
         func_index,
         func_extra_index,
@@ -6879,7 +6881,7 @@ pub fn getFuncInstance(
 pub fn getFuncInstanceIes(
     ip: *InternPool,
     gpa: Allocator,
-    _: Zcu.PerThread.Id,
+    tid: Zcu.PerThread.Id,
     arg: GetFuncInstanceKey,
 ) Allocator.Error!Index {
     // Validate input parameters.
@@ -6994,6 +6996,7 @@ pub fn getFuncInstanceIes(
     return finishFuncInstance(
         ip,
         gpa,
+        tid,
         generic_owner,
         func_index,
         func_extra_index,
@@ -7005,6 +7008,7 @@ pub fn getFuncInstanceIes(
 fn finishFuncInstance(
     ip: *InternPool,
     gpa: Allocator,
+    tid: Zcu.PerThread.Id,
     generic_owner: Index,
     func_index: Index,
     func_extra_index: u32,
@@ -7036,7 +7040,7 @@ fn finishFuncInstance(
 
     // TODO: improve this name
     const decl = ip.declPtr(decl_index);
-    decl.name = try ip.getOrPutStringFmt(gpa, "{}__anon_{d}", .{
+    decl.name = try ip.getOrPutStringFmt(gpa, tid, "{}__anon_{d}", .{
         fn_owner_decl.name.fmt(ip), @intFromEnum(decl_index),
     }, .no_embedded_nulls);
 
@@ -8782,18 +8786,20 @@ const EmbeddedNulls = enum {
 pub fn getOrPutString(
     ip: *InternPool,
     gpa: Allocator,
+    tid: Zcu.PerThread.Id,
     slice: []const u8,
     comptime embedded_nulls: EmbeddedNulls,
 ) Allocator.Error!embedded_nulls.StringType() {
     try ip.string_bytes.ensureUnusedCapacity(gpa, slice.len + 1);
     ip.string_bytes.appendSliceAssumeCapacity(slice);
     ip.string_bytes.appendAssumeCapacity(0);
-    return ip.getOrPutTrailingString(gpa, slice.len + 1, embedded_nulls);
+    return ip.getOrPutTrailingString(gpa, tid, slice.len + 1, embedded_nulls);
 }
 
 pub fn getOrPutStringFmt(
     ip: *InternPool,
     gpa: Allocator,
+    tid: Zcu.PerThread.Id,
     comptime format: []const u8,
     args: anytype,
     comptime embedded_nulls: EmbeddedNulls,
@@ -8803,16 +8809,17 @@ pub fn getOrPutStringFmt(
     try ip.string_bytes.ensureUnusedCapacity(gpa, len);
     ip.string_bytes.writer(undefined).print(format, args) catch unreachable;
     ip.string_bytes.appendAssumeCapacity(0);
-    return ip.getOrPutTrailingString(gpa, len, embedded_nulls);
+    return ip.getOrPutTrailingString(gpa, tid, len, embedded_nulls);
 }
 
 pub fn getOrPutStringOpt(
     ip: *InternPool,
     gpa: Allocator,
+    tid: Zcu.PerThread.Id,
     slice: ?[]const u8,
     comptime embedded_nulls: EmbeddedNulls,
 ) Allocator.Error!embedded_nulls.OptionalStringType() {
-    const string = try getOrPutString(ip, gpa, slice orelse return .none, embedded_nulls);
+    const string = try getOrPutString(ip, gpa, tid, slice orelse return .none, embedded_nulls);
     return string.toOptional();
 }
 
@@ -8820,9 +8827,11 @@ pub fn getOrPutStringOpt(
 pub fn getOrPutTrailingString(
     ip: *InternPool,
     gpa: Allocator,
+    tid: Zcu.PerThread.Id,
     len: usize,
     comptime embedded_nulls: EmbeddedNulls,
 ) Allocator.Error!embedded_nulls.StringType() {
+    _ = tid;
     const string_bytes = &ip.string_bytes;
     const str_index: u32 = @intCast(string_bytes.items.len - len);
     if (len > 0 and string_bytes.getLast() == 0) {
src/link.zig
@@ -424,14 +424,14 @@ pub const File = struct {
         }
     }
 
-    pub fn updateDeclLineNumber(base: *File, module: *Zcu, decl_index: InternPool.DeclIndex) UpdateDeclError!void {
-        const decl = module.declPtr(decl_index);
+    pub fn updateDeclLineNumber(base: *File, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex) UpdateDeclError!void {
+        const decl = pt.zcu.declPtr(decl_index);
         assert(decl.has_tv);
         switch (base.tag) {
             .spirv, .nvptx => {},
             inline else => |tag| {
                 if (tag != .c and build_options.only_c) unreachable;
-                return @as(*tag.Type(), @fieldParentPtr("base", base)).updateDeclLineNumber(module, decl_index);
+                return @as(*tag.Type(), @fieldParentPtr("base", base)).updateDeclLineNumber(pt, decl_index);
             },
         }
     }
@@ -626,14 +626,14 @@ pub const File = struct {
     /// `Decl`'s address was not yet resolved, or the containing atom gets moved in virtual memory.
     /// May be called before or after updateFunc/updateDecl therefore it is up to the linker to allocate
     /// the block/atom.
-    pub fn getDeclVAddr(base: *File, decl_index: InternPool.DeclIndex, reloc_info: RelocInfo) !u64 {
+    pub fn getDeclVAddr(base: *File, pt: Zcu.PerThread, decl_index: InternPool.DeclIndex, reloc_info: RelocInfo) !u64 {
         if (build_options.only_c) @compileError("unreachable");
         switch (base.tag) {
             .c => unreachable,
             .spirv => unreachable,
             .nvptx => unreachable,
             inline else => |tag| {
-                return @as(*tag.Type(), @fieldParentPtr("base", base)).getDeclVAddr(decl_index, reloc_info);
+                return @as(*tag.Type(), @fieldParentPtr("base", base)).getDeclVAddr(pt, decl_index, reloc_info);
             },
         }
     }
src/mutable_value.zig
@@ -71,7 +71,7 @@ pub const MutableValue = union(enum) {
             } }),
             .bytes => |b| try pt.intern(.{ .aggregate = .{
                 .ty = b.ty,
-                .storage = .{ .bytes = try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, b.data, .maybe_embedded_nulls) },
+                .storage = .{ .bytes = try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, pt.tid, b.data, .maybe_embedded_nulls) },
             } }),
             .aggregate => |a| {
                 const elems = try arena.alloc(InternPool.Index, a.elems.len);
src/Sema.zig
@@ -2093,12 +2093,12 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize)
     const st_ptr = try err_trace_block.addTy(.alloc, try pt.singleMutPtrType(stack_trace_ty));
 
     // st.instruction_addresses = &addrs;
-    const instruction_addresses_field_name = try ip.getOrPutString(gpa, "instruction_addresses", .no_embedded_nulls);
+    const instruction_addresses_field_name = try ip.getOrPutString(gpa, pt.tid, "instruction_addresses", .no_embedded_nulls);
     const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, instruction_addresses_field_name, src, true);
     try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store);
 
     // st.index = 0;
-    const index_field_name = try ip.getOrPutString(gpa, "index", .no_embedded_nulls);
+    const index_field_name = try ip.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
     const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, index_field_name, src, true);
     try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, .zero_usize, src, .store);
 
@@ -2691,6 +2691,7 @@ fn getCaptures(sema: *Sema, block: *Block, type_src: LazySrcLoc, extra_index: us
             .decl_val => |str| capture: {
                 const decl_name = try ip.getOrPutString(
                     sema.gpa,
+                    pt.tid,
                     sema.code.nullTerminatedString(str),
                     .no_embedded_nulls,
                 );
@@ -2700,6 +2701,7 @@ fn getCaptures(sema: *Sema, block: *Block, type_src: LazySrcLoc, extra_index: us
             .decl_ref => |str| capture: {
                 const decl_name = try ip.getOrPutString(
                     sema.gpa,
+                    pt.tid,
                     sema.code.nullTerminatedString(str),
                     .no_embedded_nulls,
                 );
@@ -2847,7 +2849,7 @@ fn zirStructDecl(
 
     if (new_namespace_index.unwrap()) |ns| {
         const decls = sema.code.bodySlice(extra_index, decls_len);
-        try mod.scanNamespace(ns, decls, mod.declPtr(new_decl_index));
+        try pt.scanNamespace(ns, decls, mod.declPtr(new_decl_index));
     }
 
     try pt.finalizeAnonDecl(new_decl_index);
@@ -2919,7 +2921,7 @@ fn createAnonymousDeclTypeNamed(
             };
 
             try writer.writeByte(')');
-            const name = try ip.getOrPutString(gpa, buf.items, .no_embedded_nulls);
+            const name = try ip.getOrPutString(gpa, pt.tid, buf.items, .no_embedded_nulls);
             try zcu.initNewAnonDecl(new_decl_index, val, name);
             return new_decl_index;
         },
@@ -2931,7 +2933,7 @@ fn createAnonymousDeclTypeNamed(
                 .dbg_var_ptr, .dbg_var_val => {
                     if (zir_data[i].str_op.operand != ref) continue;
 
-                    const name = try ip.getOrPutStringFmt(gpa, "{}.{s}", .{
+                    const name = try ip.getOrPutStringFmt(gpa, pt.tid, "{}.{s}", .{
                         block.type_name_ctx.fmt(ip), zir_data[i].str_op.getStr(sema.code),
                     }, .no_embedded_nulls);
                     try zcu.initNewAnonDecl(new_decl_index, val, name);
@@ -2952,7 +2954,7 @@ fn createAnonymousDeclTypeNamed(
     // This name is also used as the key in the parent namespace so it cannot be
     // renamed.
 
-    const name = ip.getOrPutStringFmt(gpa, "{}__{s}_{d}", .{
+    const name = ip.getOrPutStringFmt(gpa, pt.tid, "{}__{s}_{d}", .{
         block.type_name_ctx.fmt(ip), anon_prefix, @intFromEnum(new_decl_index),
     }, .no_embedded_nulls) catch unreachable;
     try zcu.initNewAnonDecl(new_decl_index, val, name);
@@ -3084,7 +3086,7 @@ fn zirEnumDecl(
     errdefer if (!done) if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns);
 
     if (new_namespace_index.unwrap()) |ns| {
-        try mod.scanNamespace(ns, decls, new_decl);
+        try pt.scanNamespace(ns, decls, new_decl);
     }
 
     // We've finished the initial construction of this type, and are about to perform analysis.
@@ -3169,7 +3171,7 @@ fn zirEnumDecl(
         const field_name_zir = sema.code.nullTerminatedString(field_name_index);
         extra_index += 2; // field name, doc comment
 
-        const field_name = try mod.intern_pool.getOrPutString(gpa, field_name_zir, .no_embedded_nulls);
+        const field_name = try mod.intern_pool.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls);
 
         const value_src: LazySrcLoc = .{
             .base_node_inst = tracked_inst,
@@ -3352,7 +3354,7 @@ fn zirUnionDecl(
 
     if (new_namespace_index.unwrap()) |ns| {
         const decls = sema.code.bodySlice(extra_index, decls_len);
-        try mod.scanNamespace(ns, decls, mod.declPtr(new_decl_index));
+        try pt.scanNamespace(ns, decls, mod.declPtr(new_decl_index));
     }
 
     try pt.finalizeAnonDecl(new_decl_index);
@@ -3441,7 +3443,7 @@ fn zirOpaqueDecl(
 
     if (new_namespace_index.unwrap()) |ns| {
         const decls = sema.code.bodySlice(extra_index, decls_len);
-        try mod.scanNamespace(ns, decls, mod.declPtr(new_decl_index));
+        try pt.scanNamespace(ns, decls, mod.declPtr(new_decl_index));
     }
 
     try pt.finalizeAnonDecl(new_decl_index);
@@ -3470,7 +3472,7 @@ fn zirErrorSetDecl(
     while (extra_index < extra_index_end) : (extra_index += 2) { // +2 to skip over doc_string
         const name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
         const name = sema.code.nullTerminatedString(name_index);
-        const name_ip = try mod.intern_pool.getOrPutString(gpa, name, .no_embedded_nulls);
+        const name_ip = try mod.intern_pool.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
         _ = try mod.getErrorValue(name_ip);
         const result = names.getOrPutAssumeCapacity(name_ip);
         assert(!result.found_existing); // verified in AstGen
@@ -3634,7 +3636,7 @@ fn indexablePtrLen(
     const is_pointer_to = object_ty.isSinglePointer(mod);
     const indexable_ty = if (is_pointer_to) object_ty.childType(mod) else object_ty;
     try checkIndexable(sema, block, src, indexable_ty);
-    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, "len", .no_embedded_nulls);
+    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, pt.tid, "len", .no_embedded_nulls);
     return sema.fieldVal(block, src, object, field_name, src);
 }
 
@@ -3649,7 +3651,7 @@ fn indexablePtrLenOrNone(
     const operand_ty = sema.typeOf(operand);
     try checkMemOperand(sema, block, src, operand_ty);
     if (operand_ty.ptrSize(mod) == .Many) return .none;
-    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, "len", .no_embedded_nulls);
+    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, pt.tid, "len", .no_embedded_nulls);
     return sema.fieldVal(block, src, operand, field_name, src);
 }
 
@@ -4405,7 +4407,7 @@ fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
             }
             if (!object_ty.indexableHasLen(mod)) continue;
 
-            break :l try sema.fieldVal(block, arg_src, object, try ip.getOrPutString(gpa, "len", .no_embedded_nulls), arg_src);
+            break :l try sema.fieldVal(block, arg_src, object, try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls), arg_src);
         };
         const arg_len = try sema.coerce(block, Type.usize, arg_len_uncoerced, arg_src);
         if (len == .none) {
@@ -4797,6 +4799,7 @@ fn validateUnionInit(
     const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
     const field_name = try mod.intern_pool.getOrPutString(
         gpa,
+        pt.tid,
         sema.code.nullTerminatedString(field_ptr_extra.field_name_start),
         .no_embedded_nulls,
     );
@@ -4942,6 +4945,7 @@ fn validateStructInit(
         struct_ptr_zir_ref = field_ptr_extra.lhs;
         const field_name = try ip.getOrPutString(
             gpa,
+            pt.tid,
             sema.code.nullTerminatedString(field_ptr_extra.field_name_start),
             .no_embedded_nulls,
         );
@@ -5518,10 +5522,11 @@ fn failWithBadStructFieldAccess(
     field_src: LazySrcLoc,
     field_name: InternPool.NullTerminatedString,
 ) CompileError {
-    const zcu = sema.pt.zcu;
+    const pt = sema.pt;
+    const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     const decl = zcu.declPtr(struct_type.decl.unwrap().?);
-    const fqn = try decl.fullyQualifiedName(zcu);
+    const fqn = try decl.fullyQualifiedName(pt);
 
     const msg = msg: {
         const msg = try sema.errMsg(
@@ -5544,12 +5549,13 @@ fn failWithBadUnionFieldAccess(
     field_src: LazySrcLoc,
     field_name: InternPool.NullTerminatedString,
 ) CompileError {
-    const zcu = sema.pt.zcu;
+    const pt = sema.pt;
+    const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     const gpa = sema.gpa;
 
     const decl = zcu.declPtr(union_obj.decl);
-    const fqn = try decl.fullyQualifiedName(zcu);
+    const fqn = try decl.fullyQualifiedName(pt);
 
     const msg = msg: {
         const msg = try sema.errMsg(
@@ -5715,7 +5721,7 @@ fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!v
 fn zirStr(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const bytes = sema.code.instructions.items(.data)[@intFromEnum(inst)].str.get(sema.code);
     return sema.addStrLit(
-        try sema.pt.zcu.intern_pool.getOrPutString(sema.gpa, bytes, .maybe_embedded_nulls),
+        try sema.pt.zcu.intern_pool.getOrPutString(sema.gpa, sema.pt.tid, bytes, .maybe_embedded_nulls),
         bytes.len,
     );
 }
@@ -6057,7 +6063,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
 
     const path_digest = zcu.filePathDigest(result.file_index);
     const root_decl = zcu.fileRootDecl(result.file_index);
-    zcu.astGenFile(result.file, result.file_index, path_digest, root_decl) catch |err|
+    pt.astGenFile(result.file, result.file_index, path_digest, root_decl) catch |err|
         return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
 
     try pt.ensureFileAnalyzed(result.file_index);
@@ -6418,6 +6424,7 @@ fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
     const options_src = block.builtinCallArgSrc(inst_data.src_node, 1);
     const decl_name = try mod.intern_pool.getOrPutString(
         mod.gpa,
+        pt.tid,
         sema.code.nullTerminatedString(extra.decl_name),
         .no_embedded_nulls,
     );
@@ -6737,6 +6744,7 @@ fn zirDeclRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const src = block.tokenOffset(inst_data.src_tok);
     const decl_name = try mod.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         inst_data.get(sema.code),
         .no_embedded_nulls,
     );
@@ -6751,6 +6759,7 @@ fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const src = block.tokenOffset(inst_data.src_tok);
     const decl_name = try mod.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         inst_data.get(sema.code),
         .no_embedded_nulls,
     );
@@ -6907,7 +6916,7 @@ pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref
 
     const stack_trace_ty = try pt.getBuiltinType("StackTrace");
     try stack_trace_ty.resolveFields(pt);
-    const field_name = try mod.intern_pool.getOrPutString(gpa, "index", .no_embedded_nulls);
+    const field_name = try mod.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) {
         error.AnalysisFail => @panic("std.builtin.StackTrace is corrupt"),
         error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
@@ -6951,7 +6960,7 @@ fn popErrorReturnTrace(
         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 field_name = try mod.intern_pool.getOrPutString(gpa, "index", .no_embedded_nulls);
+        const field_name = try mod.intern_pool.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
         const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, field_name, src, stack_trace_ty, true);
         try sema.storePtr2(block, src, field_ptr, src, saved_error_trace_index, src, .store);
     } else if (is_non_error == null) {
@@ -6977,7 +6986,7 @@ fn popErrorReturnTrace(
         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);
-        const field_name = try mod.intern_pool.getOrPutString(gpa, "index", .no_embedded_nulls);
+        const field_name = try mod.intern_pool.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
         const field_ptr = try sema.structFieldPtr(&then_block, src, err_return_trace, field_name, src, stack_trace_ty, true);
         try sema.storePtr2(&then_block, src, field_ptr, src, saved_error_trace_index, src, .store);
         _ = try then_block.addBr(cond_block_inst, .void_value);
@@ -7038,6 +7047,7 @@ fn zirCall(
             const object_ptr = try sema.resolveInst(extra.data.obj_ptr);
             const field_name = try mod.intern_pool.getOrPutString(
                 sema.gpa,
+                pt.tid,
                 sema.code.nullTerminatedString(extra.data.field_name_start),
                 .no_embedded_nulls,
             );
@@ -7103,7 +7113,7 @@ fn zirCall(
         if (input_is_error or (pop_error_return_trace and return_ty.isError(mod))) {
             const stack_trace_ty = try pt.getBuiltinType("StackTrace");
             try stack_trace_ty.resolveFields(pt);
-            const field_name = try mod.intern_pool.getOrPutString(sema.gpa, "index", .no_embedded_nulls);
+            const field_name = try mod.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);
 
             // Insert a save instruction before the arg resolution + call instructions we just generated
@@ -8687,6 +8697,7 @@ fn zirErrorValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
     const name = try pt.zcu.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         inst_data.get(sema.code),
         .no_embedded_nulls,
     );
@@ -8849,7 +8860,7 @@ fn zirEnumLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
     const name = inst_data.get(sema.code);
     return Air.internedToRef((try pt.intern(.{
-        .enum_literal = try mod.intern_pool.getOrPutString(sema.gpa, name, .no_embedded_nulls),
+        .enum_literal = try mod.intern_pool.getOrPutString(sema.gpa, pt.tid, name, .no_embedded_nulls),
     })));
 }
 
@@ -9820,7 +9831,7 @@ fn funcCommon(
         const func_index = try ip.getExternFunc(gpa, pt.tid, .{
             .ty = func_ty,
             .decl = sema.owner_decl_index,
-            .lib_name = try mod.intern_pool.getOrPutStringOpt(gpa, opt_lib_name, .no_embedded_nulls),
+            .lib_name = try mod.intern_pool.getOrPutStringOpt(gpa, pt.tid, opt_lib_name, .no_embedded_nulls),
         });
         return finishFunc(
             sema,
@@ -10281,6 +10292,7 @@ fn zirFieldVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
     const field_name = try mod.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         sema.code.nullTerminatedString(extra.field_name_start),
         .no_embedded_nulls,
     );
@@ -10300,6 +10312,7 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
     const field_name = try mod.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         sema.code.nullTerminatedString(extra.field_name_start),
         .no_embedded_nulls,
     );
@@ -10319,6 +10332,7 @@ fn zirStructInitFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi
     const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
     const field_name = try mod.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         sema.code.nullTerminatedString(extra.field_name_start),
         .no_embedded_nulls,
     );
@@ -13983,6 +13997,7 @@ fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.R
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
     const name = try mod.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         inst_data.get(sema.code),
         .no_embedded_nulls,
     );
@@ -17716,7 +17731,7 @@ fn zirBuiltinSrc(
                     .val = try pt.intern(.{ .aggregate = .{
                         .ty = array_ty,
                         .storage = .{
-                            .bytes = try ip.getOrPutString(gpa, file_name, .maybe_embedded_nulls),
+                            .bytes = try ip.getOrPutString(gpa, pt.tid, file_name, .maybe_embedded_nulls),
                         },
                     } }),
                 } },
@@ -17778,7 +17793,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 type_info_ty.getNamespaceIndex(mod),
-                try ip.getOrPutString(gpa, "Fn", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "Fn", .no_embedded_nulls),
             )).?;
             try sema.ensureDeclAnalyzed(fn_info_decl_index);
             const fn_info_decl = mod.declPtr(fn_info_decl_index);
@@ -17788,7 +17803,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 fn_info_ty.getNamespaceIndex(mod),
-                try ip.getOrPutString(gpa, "Param", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "Param", .no_embedded_nulls),
             )).?;
             try sema.ensureDeclAnalyzed(param_info_decl_index);
             const param_info_decl = mod.declPtr(param_info_decl_index);
@@ -17890,7 +17905,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 type_info_ty.getNamespaceIndex(mod),
-                try ip.getOrPutString(gpa, "Int", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "Int", .no_embedded_nulls),
             )).?;
             try sema.ensureDeclAnalyzed(int_info_decl_index);
             const int_info_decl = mod.declPtr(int_info_decl_index);
@@ -17918,7 +17933,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 type_info_ty.getNamespaceIndex(mod),
-                try ip.getOrPutString(gpa, "Float", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "Float", .no_embedded_nulls),
             )).?;
             try sema.ensureDeclAnalyzed(float_info_decl_index);
             const float_info_decl = mod.declPtr(float_info_decl_index);
@@ -17950,7 +17965,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     (try pt.getBuiltinType("Type")).getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Pointer", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Pointer", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(decl_index);
                 const decl = mod.declPtr(decl_index);
@@ -17961,7 +17976,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     pointer_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Size", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Size", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(decl_index);
                 const decl = mod.declPtr(decl_index);
@@ -18004,7 +18019,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Array", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Array", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(array_field_ty_decl_index);
                 const array_field_ty_decl = mod.declPtr(array_field_ty_decl_index);
@@ -18035,7 +18050,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Vector", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Vector", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(vector_field_ty_decl_index);
                 const vector_field_ty_decl = mod.declPtr(vector_field_ty_decl_index);
@@ -18064,7 +18079,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Optional", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Optional", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(optional_field_ty_decl_index);
                 const optional_field_ty_decl = mod.declPtr(optional_field_ty_decl_index);
@@ -18091,7 +18106,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Error", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Error", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(set_field_ty_decl_index);
                 const set_field_ty_decl = mod.declPtr(set_field_ty_decl_index);
@@ -18197,7 +18212,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "ErrorUnion", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "ErrorUnion", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(error_union_field_ty_decl_index);
                 const error_union_field_ty_decl = mod.declPtr(error_union_field_ty_decl_index);
@@ -18227,7 +18242,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "EnumField", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "EnumField", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(enum_field_ty_decl_index);
                 const enum_field_ty_decl = mod.declPtr(enum_field_ty_decl_index);
@@ -18324,7 +18339,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Enum", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Enum", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(type_enum_ty_decl_index);
                 const type_enum_ty_decl = mod.declPtr(type_enum_ty_decl_index);
@@ -18356,7 +18371,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Union", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Union", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(type_union_ty_decl_index);
                 const type_union_ty_decl = mod.declPtr(type_union_ty_decl_index);
@@ -18368,7 +18383,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "UnionField", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "UnionField", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(union_field_ty_decl_index);
                 const union_field_ty_decl = mod.declPtr(union_field_ty_decl_index);
@@ -18473,7 +18488,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     (try pt.getBuiltinType("Type")).getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "ContainerLayout", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "ContainerLayout", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(decl_index);
                 const decl = mod.declPtr(decl_index);
@@ -18506,7 +18521,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Struct", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Struct", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(type_struct_ty_decl_index);
                 const type_struct_ty_decl = mod.declPtr(type_struct_ty_decl_index);
@@ -18518,7 +18533,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "StructField", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "StructField", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(struct_field_ty_decl_index);
                 const struct_field_ty_decl = mod.declPtr(struct_field_ty_decl_index);
@@ -18540,7 +18555,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                                 const field_name = if (anon_struct_type.names.len != 0)
                                     anon_struct_type.names.get(ip)[field_index]
                                 else
-                                    try ip.getOrPutStringFmt(gpa, "{d}", .{field_index}, .no_embedded_nulls);
+                                    try ip.getOrPutStringFmt(gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
                                 const field_name_len = field_name.length(ip);
                                 const new_decl_ty = try pt.arrayType(.{
                                     .len = field_name_len,
@@ -18600,7 +18615,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     const field_name = if (struct_type.fieldName(ip, field_index).unwrap()) |field_name|
                         field_name
                     else
-                        try ip.getOrPutStringFmt(gpa, "{d}", .{field_index}, .no_embedded_nulls);
+                        try ip.getOrPutStringFmt(gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
                     const field_name_len = field_name.length(ip);
                     const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_index]);
                     const field_init = struct_type.fieldInit(ip, field_index);
@@ -18706,7 +18721,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     (try pt.getBuiltinType("Type")).getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "ContainerLayout", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "ContainerLayout", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(decl_index);
                 const decl = mod.declPtr(decl_index);
@@ -18742,7 +18757,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod),
-                    try ip.getOrPutString(gpa, "Opaque", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "Opaque", .no_embedded_nulls),
                 )).?;
                 try sema.ensureDeclAnalyzed(type_opaque_ty_decl_index);
                 const type_opaque_ty_decl = mod.declPtr(type_opaque_ty_decl_index);
@@ -18786,7 +18801,7 @@ fn typeInfoDecls(
             block,
             src,
             type_info_ty.getNamespaceIndex(mod),
-            try mod.intern_pool.getOrPutString(gpa, "Declaration", .no_embedded_nulls),
+            try mod.intern_pool.getOrPutString(gpa, pt.tid, "Declaration", .no_embedded_nulls),
         )).?;
         try sema.ensureDeclAnalyzed(declaration_ty_decl_index);
         const declaration_ty_decl = mod.declPtr(declaration_ty_decl_index);
@@ -19541,6 +19556,7 @@ fn zirRetErrValue(
     const src = block.tokenOffset(inst_data.src_tok);
     const err_name = try mod.intern_pool.getOrPutString(
         sema.gpa,
+        pt.tid,
         inst_data.get(sema.code),
         .no_embedded_nulls,
     );
@@ -20251,6 +20267,7 @@ fn zirStructInit(
             const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
             const field_name = try ip.getOrPutString(
                 gpa,
+                pt.tid,
                 sema.code.nullTerminatedString(field_type_extra.name_start),
                 .no_embedded_nulls,
             );
@@ -20292,6 +20309,7 @@ fn zirStructInit(
         const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
         const field_name = try ip.getOrPutString(
             gpa,
+            pt.tid,
             sema.code.nullTerminatedString(field_type_extra.name_start),
             .no_embedded_nulls,
         );
@@ -20581,7 +20599,7 @@ fn structInitAnon(
                 },
             };
 
-            field_name.* = try mod.intern_pool.getOrPutString(gpa, name, .no_embedded_nulls);
+            field_name.* = try mod.intern_pool.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
 
             const init = try sema.resolveInst(item.data.init);
             field_ty.* = sema.typeOf(init).toIntern();
@@ -20958,7 +20976,7 @@ fn zirStructInitFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
     };
     const aggregate_ty = wrapped_aggregate_ty.optEuBaseType(mod);
     const zir_field_name = sema.code.nullTerminatedString(extra.name_start);
-    const field_name = try ip.getOrPutString(sema.gpa, zir_field_name, .no_embedded_nulls);
+    const field_name = try ip.getOrPutString(sema.gpa, pt.tid, zir_field_name, .no_embedded_nulls);
     return sema.fieldType(block, aggregate_ty, field_name, field_name_src, ty_src);
 }
 
@@ -21344,11 +21362,11 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const signedness_val = try Value.fromInterned(union_val.val).fieldValue(
                 pt,
-                struct_type.nameIndex(ip, try ip.getOrPutString(gpa, "signedness", .no_embedded_nulls)).?,
+                struct_type.nameIndex(ip, try ip.getOrPutString(gpa, pt.tid, "signedness", .no_embedded_nulls)).?,
             );
             const bits_val = try Value.fromInterned(union_val.val).fieldValue(
                 pt,
-                struct_type.nameIndex(ip, try ip.getOrPutString(gpa, "bits", .no_embedded_nulls)).?,
+                struct_type.nameIndex(ip, try ip.getOrPutString(gpa, pt.tid, "bits", .no_embedded_nulls)).?,
             );
 
             const signedness = mod.toEnum(std.builtin.Signedness, signedness_val);
@@ -21360,11 +21378,11 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const len_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "len", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls),
             ).?);
             const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "child", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
             ).?);
 
             const len: u32 = @intCast(try len_val.toUnsignedIntSema(pt));
@@ -21382,7 +21400,7 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const bits_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "bits", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "bits", .no_embedded_nulls),
             ).?);
 
             const bits: u16 = @intCast(try bits_val.toUnsignedIntSema(pt));
@@ -21400,35 +21418,35 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const size_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "size", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "size", .no_embedded_nulls),
             ).?);
             const is_const_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "is_const", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "is_const", .no_embedded_nulls),
             ).?);
             const is_volatile_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "is_volatile", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "is_volatile", .no_embedded_nulls),
             ).?);
             const alignment_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "alignment", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "alignment", .no_embedded_nulls),
             ).?);
             const address_space_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "address_space", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "address_space", .no_embedded_nulls),
             ).?);
             const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "child", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
             ).?);
             const is_allowzero_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "is_allowzero", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "is_allowzero", .no_embedded_nulls),
             ).?);
             const sentinel_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "sentinel", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "sentinel", .no_embedded_nulls),
             ).?);
 
             if (!try sema.intFitsInType(alignment_val, Type.u32, null)) {
@@ -21505,15 +21523,15 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const len_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "len", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls),
             ).?);
             const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "child", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
             ).?);
             const sentinel_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "sentinel", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "sentinel", .no_embedded_nulls),
             ).?);
 
             const len = try len_val.toUnsignedIntSema(pt);
@@ -21534,7 +21552,7 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const child_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "child", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "child", .no_embedded_nulls),
             ).?);
 
             const child_ty = child_val.toType();
@@ -21546,11 +21564,11 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const error_set_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "error_set", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "error_set", .no_embedded_nulls),
             ).?);
             const payload_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "payload", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "payload", .no_embedded_nulls),
             ).?);
 
             const error_set_ty = error_set_val.toType();
@@ -21579,7 +21597,7 @@ fn zirReify(
                 const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern()));
                 const name_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
                     ip,
-                    try ip.getOrPutString(gpa, "name", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "name", .no_embedded_nulls),
                 ).?);
 
                 const name = try sema.sliceToIpString(block, src, name_val, .{
@@ -21601,23 +21619,23 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const layout_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "layout", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "layout", .no_embedded_nulls),
             ).?);
             const backing_integer_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "backing_integer", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "backing_integer", .no_embedded_nulls),
             ).?);
             const fields_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "fields", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "fields", .no_embedded_nulls),
             ).?);
             const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "decls", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
             ).?);
             const is_tuple_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "is_tuple", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "is_tuple", .no_embedded_nulls),
             ).?);
 
             const layout = mod.toEnum(std.builtin.Type.ContainerLayout, layout_val);
@@ -21641,19 +21659,19 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const tag_type_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "tag_type", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "tag_type", .no_embedded_nulls),
             ).?);
             const fields_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "fields", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "fields", .no_embedded_nulls),
             ).?);
             const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "decls", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
             ).?);
             const is_exhaustive_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "is_exhaustive", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "is_exhaustive", .no_embedded_nulls),
             ).?);
 
             if (try decls_val.sliceLen(pt) > 0) {
@@ -21670,7 +21688,7 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "decls", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
             ).?);
 
             // Decls
@@ -21707,19 +21725,19 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const layout_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "layout", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "layout", .no_embedded_nulls),
             ).?);
             const tag_type_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "tag_type", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "tag_type", .no_embedded_nulls),
             ).?);
             const fields_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "fields", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "fields", .no_embedded_nulls),
             ).?);
             const decls_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "decls", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "decls", .no_embedded_nulls),
             ).?);
 
             if (try decls_val.sliceLen(pt) > 0) {
@@ -21737,23 +21755,23 @@ fn zirReify(
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
             const calling_convention_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "calling_convention", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "calling_convention", .no_embedded_nulls),
             ).?);
             const is_generic_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "is_generic", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "is_generic", .no_embedded_nulls),
             ).?);
             const is_var_args_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "is_var_args", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "is_var_args", .no_embedded_nulls),
             ).?);
             const return_type_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "return_type", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "return_type", .no_embedded_nulls),
             ).?);
             const params_slice_val = try Value.fromInterned(union_val.val).fieldValue(pt, struct_type.nameIndex(
                 ip,
-                try ip.getOrPutString(gpa, "params", .no_embedded_nulls),
+                try ip.getOrPutString(gpa, pt.tid, "params", .no_embedded_nulls),
             ).?);
 
             const is_generic = is_generic_val.toBool();
@@ -21783,15 +21801,15 @@ fn zirReify(
                 const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern()));
                 const param_is_generic_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
                     ip,
-                    try ip.getOrPutString(gpa, "is_generic", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "is_generic", .no_embedded_nulls),
                 ).?);
                 const param_is_noalias_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
                     ip,
-                    try ip.getOrPutString(gpa, "is_noalias", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "is_noalias", .no_embedded_nulls),
                 ).?);
                 const opt_param_type_val = try elem_val.fieldValue(pt, elem_struct_type.nameIndex(
                     ip,
-                    try ip.getOrPutString(gpa, "type", .no_embedded_nulls),
+                    try ip.getOrPutString(gpa, pt.tid, "type", .no_embedded_nulls),
                 ).?);
 
                 if (param_is_generic_val.toBool()) {
@@ -22535,7 +22553,7 @@ fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
     const ty = try sema.resolveType(block, ty_src, inst_data.operand);
 
-    const type_name = try ip.getOrPutStringFmt(sema.gpa, "{}", .{ty.fmt(pt)}, .no_embedded_nulls);
+    const type_name = try ip.getOrPutStringFmt(sema.gpa, pt.tid, "{}", .{ty.fmt(pt)}, .no_embedded_nulls);
     return sema.addNullTerminatedStrLit(type_name);
 }
 
@@ -24143,18 +24161,18 @@ fn resolveExportOptions(
     const section_src = block.src(.{ .init_field_section = src.offset.node_offset_builtin_call_arg.builtin_call_node });
     const visibility_src = block.src(.{ .init_field_visibility = src.offset.node_offset_builtin_call_arg.builtin_call_node });
 
-    const name_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "name", .no_embedded_nulls), name_src);
+    const name_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "name", .no_embedded_nulls), name_src);
     const name = try sema.toConstString(block, name_src, name_operand, .{
         .needed_comptime_reason = "name of exported value must be comptime-known",
     });
 
-    const linkage_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "linkage", .no_embedded_nulls), linkage_src);
+    const linkage_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "linkage", .no_embedded_nulls), linkage_src);
     const linkage_val = try sema.resolveConstDefinedValue(block, linkage_src, linkage_operand, .{
         .needed_comptime_reason = "linkage of exported value must be comptime-known",
     });
     const linkage = mod.toEnum(std.builtin.GlobalLinkage, linkage_val);
 
-    const section_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "section", .no_embedded_nulls), section_src);
+    const section_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "section", .no_embedded_nulls), section_src);
     const section_opt_val = try sema.resolveConstDefinedValue(block, section_src, section_operand, .{
         .needed_comptime_reason = "linksection of exported value must be comptime-known",
     });
@@ -24165,7 +24183,7 @@ fn resolveExportOptions(
     else
         null;
 
-    const visibility_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "visibility", .no_embedded_nulls), visibility_src);
+    const visibility_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "visibility", .no_embedded_nulls), visibility_src);
     const visibility_val = try sema.resolveConstDefinedValue(block, visibility_src, visibility_operand, .{
         .needed_comptime_reason = "visibility of exported value must be comptime-known",
     });
@@ -24182,9 +24200,9 @@ fn resolveExportOptions(
     }
 
     return .{
-        .name = try ip.getOrPutString(gpa, name, .no_embedded_nulls),
+        .name = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls),
         .linkage = linkage,
-        .section = try ip.getOrPutStringOpt(gpa, section, .no_embedded_nulls),
+        .section = try ip.getOrPutStringOpt(gpa, pt.tid, section, .no_embedded_nulls),
         .visibility = visibility,
     };
 }
@@ -25821,7 +25839,7 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
 
     const runtime_src = rs: {
         const ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr) orelse break :rs dest_src;
-        const len_air_ref = try sema.fieldVal(block, src, dest_ptr, try ip.getOrPutString(gpa, "len", .no_embedded_nulls), dest_src);
+        const len_air_ref = try sema.fieldVal(block, src, dest_ptr, try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls), dest_src);
         const len_val = (try sema.resolveDefinedValue(block, dest_src, len_air_ref)) orelse break :rs dest_src;
         const len_u64 = (try len_val.getUnsignedIntAdvanced(pt, .sema)).?;
         const len = try sema.usizeCast(block, dest_src, len_u64);
@@ -25952,7 +25970,7 @@ fn zirVarExtended(
         .ty = var_ty.toIntern(),
         .init = init_val,
         .decl = sema.owner_decl_index,
-        .lib_name = try mod.intern_pool.getOrPutStringOpt(sema.gpa, lib_name, .no_embedded_nulls),
+        .lib_name = try mod.intern_pool.getOrPutStringOpt(sema.gpa, pt.tid, lib_name, .no_embedded_nulls),
         .is_extern = small.is_extern,
         .is_const = small.is_const,
         .is_threadlocal = small.is_threadlocal,
@@ -26323,17 +26341,17 @@ fn resolvePrefetchOptions(
     const locality_src = block.src(.{ .init_field_locality = src.offset.node_offset_builtin_call_arg.builtin_call_node });
     const cache_src = block.src(.{ .init_field_cache = src.offset.node_offset_builtin_call_arg.builtin_call_node });
 
-    const rw = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "rw", .no_embedded_nulls), rw_src);
+    const rw = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "rw", .no_embedded_nulls), rw_src);
     const rw_val = try sema.resolveConstDefinedValue(block, rw_src, rw, .{
         .needed_comptime_reason = "prefetch read/write must be comptime-known",
     });
 
-    const locality = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "locality", .no_embedded_nulls), locality_src);
+    const locality = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "locality", .no_embedded_nulls), locality_src);
     const locality_val = try sema.resolveConstDefinedValue(block, locality_src, locality, .{
         .needed_comptime_reason = "prefetch locality must be comptime-known",
     });
 
-    const cache = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "cache", .no_embedded_nulls), cache_src);
+    const cache = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "cache", .no_embedded_nulls), cache_src);
     const cache_val = try sema.resolveConstDefinedValue(block, cache_src, cache, .{
         .needed_comptime_reason = "prefetch cache must be comptime-known",
     });
@@ -26397,23 +26415,23 @@ fn resolveExternOptions(
     const linkage_src = block.src(.{ .init_field_linkage = src.offset.node_offset_builtin_call_arg.builtin_call_node });
     const thread_local_src = block.src(.{ .init_field_thread_local = src.offset.node_offset_builtin_call_arg.builtin_call_node });
 
-    const name_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "name", .no_embedded_nulls), name_src);
+    const name_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "name", .no_embedded_nulls), name_src);
     const name = try sema.toConstString(block, name_src, name_ref, .{
         .needed_comptime_reason = "name of the extern symbol must be comptime-known",
     });
 
-    const library_name_inst = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "library_name", .no_embedded_nulls), library_src);
+    const library_name_inst = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "library_name", .no_embedded_nulls), library_src);
     const library_name_val = try sema.resolveConstDefinedValue(block, library_src, library_name_inst, .{
         .needed_comptime_reason = "library in which extern symbol is must be comptime-known",
     });
 
-    const linkage_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "linkage", .no_embedded_nulls), linkage_src);
+    const linkage_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "linkage", .no_embedded_nulls), linkage_src);
     const linkage_val = try sema.resolveConstDefinedValue(block, linkage_src, linkage_ref, .{
         .needed_comptime_reason = "linkage of the extern symbol must be comptime-known",
     });
     const linkage = mod.toEnum(std.builtin.GlobalLinkage, linkage_val);
 
-    const is_thread_local = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "is_thread_local", .no_embedded_nulls), thread_local_src);
+    const is_thread_local = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "is_thread_local", .no_embedded_nulls), thread_local_src);
     const is_thread_local_val = try sema.resolveConstDefinedValue(block, thread_local_src, is_thread_local, .{
         .needed_comptime_reason = "threadlocality of the extern symbol must be comptime-known",
     });
@@ -26438,8 +26456,8 @@ fn resolveExternOptions(
     }
 
     return .{
-        .name = try ip.getOrPutString(gpa, name, .no_embedded_nulls),
-        .library_name = try ip.getOrPutStringOpt(gpa, library_name, .no_embedded_nulls),
+        .name = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls),
+        .library_name = try ip.getOrPutStringOpt(gpa, pt.tid, library_name, .no_embedded_nulls),
         .linkage = linkage,
         .is_thread_local = is_thread_local_val.toBool(),
     };
@@ -27052,7 +27070,7 @@ fn preparePanicId(sema: *Sema, block: *Block, panic_id: Module.PanicId) !InternP
         block,
         LazySrcLoc.unneeded,
         panic_messages_ty.getNamespaceIndex(mod),
-        try mod.intern_pool.getOrPutString(gpa, @tagName(panic_id), .no_embedded_nulls),
+        try mod.intern_pool.getOrPutString(gpa, pt.tid, @tagName(panic_id), .no_embedded_nulls),
     ) catch |err| switch (err) {
         error.AnalysisFail => @panic("std.builtin.panic_messages is corrupt"),
         error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
@@ -31745,7 +31763,7 @@ fn coerceTupleToStruct(
             .anon_struct_type => |anon_struct_type| if (anon_struct_type.names.len > 0)
                 anon_struct_type.names.get(ip)[tuple_field_index]
             else
-                try ip.getOrPutStringFmt(sema.gpa, "{d}", .{tuple_field_index}, .no_embedded_nulls),
+                try ip.getOrPutStringFmt(sema.gpa, pt.tid, "{d}", .{tuple_field_index}, .no_embedded_nulls),
             .struct_type => ip.loadStructType(inst_ty.toIntern()).field_names.get(ip)[tuple_field_index],
             else => unreachable,
         };
@@ -31858,13 +31876,13 @@ fn coerceTupleToTuple(
             .anon_struct_type => |anon_struct_type| if (anon_struct_type.names.len > 0)
                 anon_struct_type.names.get(ip)[field_i]
             else
-                try ip.getOrPutStringFmt(sema.gpa, "{d}", .{field_i}, .no_embedded_nulls),
+                try ip.getOrPutStringFmt(sema.gpa, pt.tid, "{d}", .{field_i}, .no_embedded_nulls),
             .struct_type => s: {
                 const struct_type = ip.loadStructType(inst_ty.toIntern());
                 if (struct_type.field_names.len > 0) {
                     break :s struct_type.field_names.get(ip)[field_i];
                 } else {
-                    break :s try ip.getOrPutStringFmt(sema.gpa, "{d}", .{field_i}, .no_embedded_nulls);
+                    break :s try ip.getOrPutStringFmt(sema.gpa, pt.tid, "{d}", .{field_i}, .no_embedded_nulls);
                 }
             },
             else => unreachable,
@@ -34849,7 +34867,7 @@ fn resolvePeerTypesInner(
                         const result_buf = try sema.arena.create(PeerResolveResult);
                         result_buf.* = result;
                         const field_name = if (is_tuple)
-                            try ip.getOrPutStringFmt(sema.gpa, "{d}", .{field_index}, .no_embedded_nulls)
+                            try ip.getOrPutStringFmt(sema.gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls)
                         else
                             field_names[field_index];
 
@@ -36066,7 +36084,7 @@ fn semaStructFields(
 
             // This string needs to outlive the ZIR code.
             if (opt_field_name_zir) |field_name_zir| {
-                const field_name = try ip.getOrPutString(gpa, field_name_zir, .no_embedded_nulls);
+                const field_name = try ip.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls);
                 assert(struct_type.addFieldName(ip, field_name) == null);
             }
 
@@ -36567,7 +36585,7 @@ fn semaUnionFields(pt: Zcu.PerThread, arena: Allocator, union_type: InternPool.L
         }
 
         // This string needs to outlive the ZIR code.
-        const field_name = try ip.getOrPutString(gpa, field_name_zir, .no_embedded_nulls);
+        const field_name = try ip.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls);
         if (enum_field_names.len != 0) {
             enum_field_names[field_i] = field_name;
         }
@@ -36716,9 +36734,10 @@ fn generateUnionTagTypeNumbered(
 
     const new_decl_index = try mod.allocateNewDecl(block.namespace);
     errdefer mod.destroyDecl(new_decl_index);
-    const fqn = try union_owner_decl.fullyQualifiedName(mod);
+    const fqn = try union_owner_decl.fullyQualifiedName(pt);
     const name = try ip.getOrPutStringFmt(
         gpa,
+        pt.tid,
         "@typeInfo({}).Union.tag_type.?",
         .{fqn.fmt(ip)},
         .no_embedded_nulls,
@@ -36764,11 +36783,12 @@ fn generateUnionTagTypeSimple(
     const gpa = sema.gpa;
 
     const new_decl_index = new_decl_index: {
-        const fqn = try union_owner_decl.fullyQualifiedName(mod);
+        const fqn = try union_owner_decl.fullyQualifiedName(pt);
         const new_decl_index = try mod.allocateNewDecl(block.namespace);
         errdefer mod.destroyDecl(new_decl_index);
         const name = try ip.getOrPutStringFmt(
             gpa,
+            pt.tid,
             "@typeInfo({}).Union.tag_type.?",
             .{fqn.fmt(ip)},
             .no_embedded_nulls,
src/Value.zig
@@ -67,7 +67,7 @@ pub fn toIpString(val: Value, ty: Type, pt: Zcu.PerThread) !InternPool.NullTermi
             const byte: u8 = @intCast(Value.fromInterned(elem).toUnsignedInt(pt));
             const len: usize = @intCast(ty.arrayLen(mod));
             try ip.string_bytes.appendNTimes(mod.gpa, byte, len);
-            return ip.getOrPutTrailingString(mod.gpa, len, .no_embedded_nulls);
+            return ip.getOrPutTrailingString(mod.gpa, pt.tid, len, .no_embedded_nulls);
         },
     }
 }
@@ -118,7 +118,7 @@ fn arrayToIpString(val: Value, len_u64: u64, pt: Zcu.PerThread) !InternPool.Null
         const byte: u8 = @intCast(elem_val.toUnsignedInt(pt));
         ip.string_bytes.appendAssumeCapacity(byte);
     }
-    return ip.getOrPutTrailingString(gpa, len, .no_embedded_nulls);
+    return ip.getOrPutTrailingString(gpa, pt.tid, len, .no_embedded_nulls);
 }
 
 pub fn fromInterned(i: InternPool.Index) Value {
src/Zcu.zig
@@ -420,11 +420,11 @@ pub const Decl = struct {
         return zcu.namespacePtr(decl.src_namespace).renderFullyQualifiedDebugName(zcu, decl.name, writer);
     }
 
-    pub fn fullyQualifiedName(decl: Decl, zcu: *Zcu) !InternPool.NullTerminatedString {
+    pub fn fullyQualifiedName(decl: Decl, pt: Zcu.PerThread) !InternPool.NullTerminatedString {
         return if (decl.name_fully_qualified)
             decl.name
         else
-            zcu.namespacePtr(decl.src_namespace).fullyQualifiedName(zcu, decl.name);
+            pt.zcu.namespacePtr(decl.src_namespace).fullyQualifiedName(pt, decl.name);
     }
 
     pub fn typeOf(decl: Decl, zcu: *const Zcu) Type {
@@ -688,9 +688,10 @@ pub const Namespace = struct {
 
     pub fn fullyQualifiedName(
         ns: Namespace,
-        zcu: *Zcu,
+        pt: Zcu.PerThread,
         name: InternPool.NullTerminatedString,
     ) !InternPool.NullTerminatedString {
+        const zcu = pt.zcu;
         const ip = &zcu.intern_pool;
         const count = count: {
             var count: usize = name.length(ip) + 1;
@@ -723,7 +724,7 @@ pub const Namespace = struct {
             };
         }
 
-        return ip.getOrPutTrailingString(gpa, ip.string_bytes.items.len - start, .no_embedded_nulls);
+        return ip.getOrPutTrailingString(gpa, pt.tid, ip.string_bytes.items.len - start, .no_embedded_nulls);
     }
 
     pub fn getType(ns: Namespace, zcu: *Zcu) Type {
@@ -875,11 +876,12 @@ pub const File = struct {
         };
     }
 
-    pub fn fullyQualifiedName(file: File, mod: *Module) !InternPool.NullTerminatedString {
-        const ip = &mod.intern_pool;
+    pub fn fullyQualifiedName(file: File, pt: Zcu.PerThread) !InternPool.NullTerminatedString {
+        const gpa = pt.zcu.gpa;
+        const ip = &pt.zcu.intern_pool;
         const start = ip.string_bytes.items.len;
-        try file.renderFullyQualifiedName(ip.string_bytes.writer(mod.gpa));
-        return ip.getOrPutTrailingString(mod.gpa, ip.string_bytes.items.len - start, .no_embedded_nulls);
+        try file.renderFullyQualifiedName(ip.string_bytes.writer(gpa));
+        return ip.getOrPutTrailingString(gpa, pt.tid, ip.string_bytes.items.len - start, .no_embedded_nulls);
     }
 
     pub fn fullPath(file: File, ally: Allocator) ![]u8 {
@@ -2569,8 +2571,8 @@ pub fn declIsRoot(mod: *Module, decl_index: Decl.Index) bool {
 }
 
 // TODO https://github.com/ziglang/zig/issues/8643
-const data_has_safety_tag = @sizeOf(Zir.Inst.Data) != 8;
-const HackDataLayout = extern struct {
+pub const data_has_safety_tag = @sizeOf(Zir.Inst.Data) != 8;
+pub const HackDataLayout = extern struct {
     data: [8]u8 align(@alignOf(Zir.Inst.Data)),
     safety_tag: u8,
 };
@@ -2580,291 +2582,11 @@ comptime {
     }
 }
 
-pub fn astGenFile(
-    zcu: *Zcu,
-    file: *File,
-    /// This parameter is provided separately from `file` because it is not
-    /// safe to access `import_table` without a lock, and this index is needed
-    /// in the call to `updateZirRefs`.
-    file_index: File.Index,
-    path_digest: Cache.BinDigest,
-    opt_root_decl: Zcu.Decl.OptionalIndex,
-) !void {
-    assert(!file.mod.isBuiltin());
-
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const comp = zcu.comp;
-    const gpa = zcu.gpa;
-
-    // In any case we need to examine the stat of the file to determine the course of action.
-    var source_file = try file.mod.root.openFile(file.sub_file_path, .{});
-    defer source_file.close();
-
-    const stat = try source_file.stat();
-
-    const want_local_cache = file.mod == zcu.main_mod;
-    const hex_digest = Cache.binToHex(path_digest);
-    const cache_directory = if (want_local_cache) zcu.local_zir_cache else zcu.global_zir_cache;
-    const zir_dir = cache_directory.handle;
-
-    // Determine whether we need to reload the file from disk and redo parsing and AstGen.
-    var lock: std.fs.File.Lock = switch (file.status) {
-        .never_loaded, .retryable_failure => lock: {
-            // First, load the cached ZIR code, if any.
-            log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{
-                file.sub_file_path, want_local_cache, &hex_digest,
-            });
-
-            break :lock .shared;
-        },
-        .parse_failure, .astgen_failure, .success_zir => lock: {
-            const unchanged_metadata =
-                stat.size == file.stat.size and
-                stat.mtime == file.stat.mtime and
-                stat.inode == file.stat.inode;
-
-            if (unchanged_metadata) {
-                log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
-                return;
-            }
-
-            log.debug("metadata changed: {s}", .{file.sub_file_path});
-
-            break :lock .exclusive;
-        },
-    };
-
-    // We ask for a lock in order to coordinate with other zig processes.
-    // If another process is already working on this file, we will get the cached
-    // version. Likewise if we're working on AstGen and another process asks for
-    // the cached file, they'll get it.
-    const cache_file = while (true) {
-        break zir_dir.createFile(&hex_digest, .{
-            .read = true,
-            .truncate = false,
-            .lock = lock,
-        }) catch |err| switch (err) {
-            error.NotDir => unreachable, // no dir components
-            error.InvalidUtf8 => unreachable, // it's a hex encoded name
-            error.InvalidWtf8 => unreachable, // it's a hex encoded name
-            error.BadPathName => unreachable, // it's a hex encoded name
-            error.NameTooLong => unreachable, // it's a fixed size name
-            error.PipeBusy => unreachable, // it's not a pipe
-            error.WouldBlock => unreachable, // not asking for non-blocking I/O
-            // There are no dir components, so you would think that this was
-            // unreachable, however we have observed on macOS two processes racing
-            // to do openat() with O_CREAT manifest in ENOENT.
-            error.FileNotFound => continue,
-
-            else => |e| return e, // Retryable errors are handled at callsite.
-        };
-    };
-    defer cache_file.close();
-
-    while (true) {
-        update: {
-            // First we read the header to determine the lengths of arrays.
-            const header = cache_file.reader().readStruct(Zir.Header) catch |err| switch (err) {
-                // This can happen if Zig bails out of this function between creating
-                // the cached file and writing it.
-                error.EndOfStream => break :update,
-                else => |e| return e,
-            };
-            const unchanged_metadata =
-                stat.size == header.stat_size and
-                stat.mtime == header.stat_mtime and
-                stat.inode == header.stat_inode;
-
-            if (!unchanged_metadata) {
-                log.debug("AstGen cache stale: {s}", .{file.sub_file_path});
-                break :update;
-            }
-            log.debug("AstGen cache hit: {s} instructions_len={d}", .{
-                file.sub_file_path, header.instructions_len,
-            });
-
-            file.zir = loadZirCacheBody(gpa, header, cache_file) catch |err| switch (err) {
-                error.UnexpectedFileSize => {
-                    log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path});
-                    break :update;
-                },
-                else => |e| return e,
-            };
-            file.zir_loaded = true;
-            file.stat = .{
-                .size = header.stat_size,
-                .inode = header.stat_inode,
-                .mtime = header.stat_mtime,
-            };
-            file.status = .success_zir;
-            log.debug("AstGen cached success: {s}", .{file.sub_file_path});
-
-            // TODO don't report compile errors until Sema @importFile
-            if (file.zir.hasCompileErrors()) {
-                {
-                    comp.mutex.lock();
-                    defer comp.mutex.unlock();
-                    try zcu.failed_files.putNoClobber(gpa, file, null);
-                }
-                file.status = .astgen_failure;
-                return error.AnalysisFail;
-            }
-            return;
-        }
-
-        // If we already have the exclusive lock then it is our job to update.
-        if (builtin.os.tag == .wasi or lock == .exclusive) break;
-        // Otherwise, unlock to give someone a chance to get the exclusive lock
-        // and then upgrade to an exclusive lock.
-        cache_file.unlock();
-        lock = .exclusive;
-        try cache_file.lock(lock);
-    }
-
-    // The cache is definitely stale so delete the contents to avoid an underwrite later.
-    cache_file.setEndPos(0) catch |err| switch (err) {
-        error.FileTooBig => unreachable, // 0 is not too big
-
-        else => |e| return e,
-    };
-
-    zcu.lockAndClearFileCompileError(file);
-
-    // If the previous ZIR does not have compile errors, keep it around
-    // in case parsing or new ZIR fails. In case of successful ZIR update
-    // at the end of this function we will free it.
-    // We keep the previous ZIR loaded so that we can use it
-    // for the update next time it does not have any compile errors. This avoids
-    // needlessly tossing out semantic analysis work when an error is
-    // temporarily introduced.
-    if (file.zir_loaded and !file.zir.hasCompileErrors()) {
-        assert(file.prev_zir == null);
-        const prev_zir_ptr = try gpa.create(Zir);
-        file.prev_zir = prev_zir_ptr;
-        prev_zir_ptr.* = file.zir;
-        file.zir = undefined;
-        file.zir_loaded = false;
-    }
-    file.unload(gpa);
-
-    if (stat.size > std.math.maxInt(u32))
-        return error.FileTooBig;
-
-    const source = try gpa.allocSentinel(u8, @as(usize, @intCast(stat.size)), 0);
-    defer if (!file.source_loaded) gpa.free(source);
-    const amt = try source_file.readAll(source);
-    if (amt != stat.size)
-        return error.UnexpectedEndOfFile;
-
-    file.stat = .{
-        .size = stat.size,
-        .inode = stat.inode,
-        .mtime = stat.mtime,
-    };
-    file.source = source;
-    file.source_loaded = true;
-
-    file.tree = try Ast.parse(gpa, source, .zig);
-    file.tree_loaded = true;
-
-    // Any potential AST errors are converted to ZIR errors here.
-    file.zir = try AstGen.generate(gpa, file.tree);
-    file.zir_loaded = true;
-    file.status = .success_zir;
-    log.debug("AstGen fresh success: {s}", .{file.sub_file_path});
-
-    const safety_buffer = if (data_has_safety_tag)
-        try gpa.alloc([8]u8, file.zir.instructions.len)
-    else
-        undefined;
-    defer if (data_has_safety_tag) gpa.free(safety_buffer);
-    const data_ptr = if (data_has_safety_tag)
-        if (file.zir.instructions.len == 0)
-            @as([*]const u8, undefined)
-        else
-            @as([*]const u8, @ptrCast(safety_buffer.ptr))
-    else
-        @as([*]const u8, @ptrCast(file.zir.instructions.items(.data).ptr));
-    if (data_has_safety_tag) {
-        // The `Data` union has a safety tag but in the file format we store it without.
-        for (file.zir.instructions.items(.data), 0..) |*data, i| {
-            const as_struct = @as(*const HackDataLayout, @ptrCast(data));
-            safety_buffer[i] = as_struct.data;
-        }
-    }
-
-    const header: Zir.Header = .{
-        .instructions_len = @as(u32, @intCast(file.zir.instructions.len)),
-        .string_bytes_len = @as(u32, @intCast(file.zir.string_bytes.len)),
-        .extra_len = @as(u32, @intCast(file.zir.extra.len)),
-
-        .stat_size = stat.size,
-        .stat_inode = stat.inode,
-        .stat_mtime = stat.mtime,
-    };
-    var iovecs = [_]std.posix.iovec_const{
-        .{
-            .base = @as([*]const u8, @ptrCast(&header)),
-            .len = @sizeOf(Zir.Header),
-        },
-        .{
-            .base = @as([*]const u8, @ptrCast(file.zir.instructions.items(.tag).ptr)),
-            .len = file.zir.instructions.len,
-        },
-        .{
-            .base = data_ptr,
-            .len = file.zir.instructions.len * 8,
-        },
-        .{
-            .base = file.zir.string_bytes.ptr,
-            .len = file.zir.string_bytes.len,
-        },
-        .{
-            .base = @as([*]const u8, @ptrCast(file.zir.extra.ptr)),
-            .len = file.zir.extra.len * 4,
-        },
-    };
-    cache_file.writevAll(&iovecs) catch |err| {
-        log.warn("unable to write cached ZIR code for {}{s} to {}{s}: {s}", .{
-            file.mod.root, file.sub_file_path, cache_directory, &hex_digest, @errorName(err),
-        });
-    };
-
-    if (file.zir.hasCompileErrors()) {
-        {
-            comp.mutex.lock();
-            defer comp.mutex.unlock();
-            try zcu.failed_files.putNoClobber(gpa, file, null);
-        }
-        file.status = .astgen_failure;
-        return error.AnalysisFail;
-    }
-
-    if (file.prev_zir) |prev_zir| {
-        try updateZirRefs(zcu, file, file_index, prev_zir.*);
-        // No need to keep previous ZIR.
-        prev_zir.deinit(gpa);
-        gpa.destroy(prev_zir);
-        file.prev_zir = null;
-    }
-
-    if (opt_root_decl.unwrap()) |root_decl| {
-        // The root of this file must be re-analyzed, since the file has changed.
-        comp.mutex.lock();
-        defer comp.mutex.unlock();
-
-        log.debug("outdated root Decl: {}", .{root_decl});
-        try zcu.outdated_file_root.put(gpa, root_decl, {});
-    }
-}
-
 pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {
     return loadZirCacheBody(gpa, try cache_file.reader().readStruct(Zir.Header), cache_file);
 }
 
-fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File) !Zir {
+pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File) !Zir {
     var instructions: std.MultiArrayList(Zir.Inst) = .{};
     errdefer instructions.deinit(gpa);
 
@@ -2930,127 +2652,6 @@ fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File)
     return zir;
 }
 
-/// This is called from the AstGen thread pool, so must acquire
-/// the Compilation mutex when acting on shared state.
-fn updateZirRefs(zcu: *Module, file: *File, file_index: File.Index, old_zir: Zir) !void {
-    const gpa = zcu.gpa;
-    const new_zir = file.zir;
-
-    var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
-    defer inst_map.deinit(gpa);
-
-    try mapOldZirToNew(gpa, old_zir, new_zir, &inst_map);
-
-    const old_tag = old_zir.instructions.items(.tag);
-    const old_data = old_zir.instructions.items(.data);
-
-    // TODO: this should be done after all AstGen workers complete, to avoid
-    // iterating over this full set for every updated file.
-    for (zcu.intern_pool.tracked_insts.keys(), 0..) |*ti, idx_raw| {
-        const ti_idx: InternPool.TrackedInst.Index = @enumFromInt(idx_raw);
-        if (ti.file != file_index) continue;
-        const old_inst = ti.inst;
-        ti.inst = inst_map.get(ti.inst) orelse {
-            // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
-            zcu.comp.mutex.lock();
-            defer zcu.comp.mutex.unlock();
-            log.debug("tracking failed for %{d}", .{old_inst});
-            try zcu.markDependeeOutdated(.{ .src_hash = ti_idx });
-            continue;
-        };
-
-        if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: {
-            if (new_zir.getAssociatedSrcHash(ti.inst)) |new_hash| {
-                if (std.zig.srcHashEql(old_hash, new_hash)) {
-                    break :hash_changed;
-                }
-                log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{
-                    old_inst,
-                    ti.inst,
-                    std.fmt.fmtSliceHexLower(&old_hash),
-                    std.fmt.fmtSliceHexLower(&new_hash),
-                });
-            }
-            // The source hash associated with this instruction changed - invalidate relevant dependencies.
-            zcu.comp.mutex.lock();
-            defer zcu.comp.mutex.unlock();
-            try zcu.markDependeeOutdated(.{ .src_hash = ti_idx });
-        }
-
-        // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies.
-        const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {
-            .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {
-                .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,
-                else => false,
-            },
-            else => false,
-        };
-        if (!has_namespace) continue;
-
-        var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
-        defer old_names.deinit(zcu.gpa);
-        {
-            var it = old_zir.declIterator(old_inst);
-            while (it.next()) |decl_inst| {
-                const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
-                switch (decl_name) {
-                    .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
-                    _ => if (decl_name.isNamedTest(old_zir)) continue,
-                }
-                const name_zir = decl_name.toString(old_zir).?;
-                const name_ip = try zcu.intern_pool.getOrPutString(
-                    zcu.gpa,
-                    old_zir.nullTerminatedString(name_zir),
-                    .no_embedded_nulls,
-                );
-                try old_names.put(zcu.gpa, name_ip, {});
-            }
-        }
-        var any_change = false;
-        {
-            var it = new_zir.declIterator(ti.inst);
-            while (it.next()) |decl_inst| {
-                const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
-                switch (decl_name) {
-                    .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
-                    _ => if (decl_name.isNamedTest(old_zir)) continue,
-                }
-                const name_zir = decl_name.toString(old_zir).?;
-                const name_ip = try zcu.intern_pool.getOrPutString(
-                    zcu.gpa,
-                    old_zir.nullTerminatedString(name_zir),
-                    .no_embedded_nulls,
-                );
-                if (!old_names.swapRemove(name_ip)) continue;
-                // Name added
-                any_change = true;
-                zcu.comp.mutex.lock();
-                defer zcu.comp.mutex.unlock();
-                try zcu.markDependeeOutdated(.{ .namespace_name = .{
-                    .namespace = ti_idx,
-                    .name = name_ip,
-                } });
-            }
-        }
-        // The only elements remaining in `old_names` now are any names which were removed.
-        for (old_names.keys()) |name_ip| {
-            any_change = true;
-            zcu.comp.mutex.lock();
-            defer zcu.comp.mutex.unlock();
-            try zcu.markDependeeOutdated(.{ .namespace_name = .{
-                .namespace = ti_idx,
-                .name = name_ip,
-            } });
-        }
-
-        if (any_change) {
-            zcu.comp.mutex.lock();
-            defer zcu.comp.mutex.unlock();
-            try zcu.markDependeeOutdated(.{ .namespace = ti_idx });
-        }
-    }
-}
-
 pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void {
     log.debug("outdated dependee: {}", .{dependee});
     var it = zcu.intern_pool.dependencyIterator(dependee);
@@ -3695,268 +3296,6 @@ fn computePathDigest(zcu: *Zcu, mod: *Package.Module, sub_file_path: []const u8)
     return bin;
 }
 
-pub fn scanNamespace(
-    zcu: *Zcu,
-    namespace_index: Namespace.Index,
-    decls: []const Zir.Inst.Index,
-    parent_decl: *Decl,
-) Allocator.Error!void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const gpa = zcu.gpa;
-    const namespace = zcu.namespacePtr(namespace_index);
-
-    // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather
-    // than their name. We'll build an efficient mapping now, then discard the current `decls`.
-    var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index) = .{};
-    defer existing_by_inst.deinit(gpa);
-
-    try existing_by_inst.ensureTotalCapacity(gpa, @intCast(namespace.decls.count()));
-
-    for (namespace.decls.keys()) |decl_index| {
-        const decl = zcu.declPtr(decl_index);
-        existing_by_inst.putAssumeCapacityNoClobber(decl.zir_decl_index.unwrap().?, decl_index);
-    }
-
-    var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
-    defer seen_decls.deinit(gpa);
-
-    try zcu.comp.work_queue.ensureUnusedCapacity(decls.len);
-
-    namespace.decls.clearRetainingCapacity();
-    try namespace.decls.ensureTotalCapacity(gpa, decls.len);
-
-    namespace.usingnamespace_set.clearRetainingCapacity();
-
-    var scan_decl_iter: ScanDeclIter = .{
-        .zcu = zcu,
-        .namespace_index = namespace_index,
-        .parent_decl = parent_decl,
-        .seen_decls = &seen_decls,
-        .existing_by_inst = &existing_by_inst,
-        .pass = .named,
-    };
-    for (decls) |decl_inst| {
-        try scanDecl(&scan_decl_iter, decl_inst);
-    }
-    scan_decl_iter.pass = .unnamed;
-    for (decls) |decl_inst| {
-        try scanDecl(&scan_decl_iter, decl_inst);
-    }
-
-    if (seen_decls.count() != namespace.decls.count()) {
-        // Do a pass over the namespace contents and remove any decls from the last update
-        // which were removed in this one.
-        var i: usize = 0;
-        while (i < namespace.decls.count()) {
-            const decl_index = namespace.decls.keys()[i];
-            const decl = zcu.declPtr(decl_index);
-            if (!seen_decls.contains(decl.name)) {
-                // We must preserve namespace ordering for @typeInfo.
-                namespace.decls.orderedRemoveAt(i);
-                i -= 1;
-            }
-        }
-    }
-}
-
-const ScanDeclIter = struct {
-    zcu: *Zcu,
-    namespace_index: Namespace.Index,
-    parent_decl: *Decl,
-    seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
-    existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index),
-    /// Decl scanning is run in two passes, so that we can detect when a generated
-    /// name would clash with an explicit name and use a different one.
-    pass: enum { named, unnamed },
-    usingnamespace_index: usize = 0,
-    comptime_index: usize = 0,
-    unnamed_test_index: usize = 0,
-
-    fn avoidNameConflict(iter: *ScanDeclIter, comptime fmt: []const u8, args: anytype) !InternPool.NullTerminatedString {
-        const zcu = iter.zcu;
-        const gpa = zcu.gpa;
-        const ip = &zcu.intern_pool;
-        var name = try ip.getOrPutStringFmt(gpa, fmt, args, .no_embedded_nulls);
-        var gop = try iter.seen_decls.getOrPut(gpa, name);
-        var next_suffix: u32 = 0;
-        while (gop.found_existing) {
-            name = try ip.getOrPutStringFmt(gpa, "{}_{d}", .{ name.fmt(ip), next_suffix }, .no_embedded_nulls);
-            gop = try iter.seen_decls.getOrPut(gpa, name);
-            next_suffix += 1;
-        }
-        return name;
-    }
-};
-
-fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const zcu = iter.zcu;
-    const namespace_index = iter.namespace_index;
-    const namespace = zcu.namespacePtr(namespace_index);
-    const gpa = zcu.gpa;
-    const zir = namespace.fileScope(zcu).zir;
-    const ip = &zcu.intern_pool;
-
-    const inst_data = zir.instructions.items(.data)[@intFromEnum(decl_inst)].declaration;
-    const extra = zir.extraData(Zir.Inst.Declaration, inst_data.payload_index);
-    const declaration = extra.data;
-
-    // Every Decl needs a name.
-    const decl_name: InternPool.NullTerminatedString, const kind: Decl.Kind, const is_named_test: bool = switch (declaration.name) {
-        .@"comptime" => info: {
-            if (iter.pass != .unnamed) return;
-            const i = iter.comptime_index;
-            iter.comptime_index += 1;
-            break :info .{
-                try iter.avoidNameConflict("comptime_{d}", .{i}),
-                .@"comptime",
-                false,
-            };
-        },
-        .@"usingnamespace" => info: {
-            // TODO: this isn't right! These should be considered unnamed. Name conflicts can happen here.
-            // The problem is, we need to preserve the decl ordering for `@typeInfo`.
-            // I'm not bothering to fix this now, since some upcoming changes will change this code significantly anyway.
-            if (iter.pass != .named) return;
-            const i = iter.usingnamespace_index;
-            iter.usingnamespace_index += 1;
-            break :info .{
-                try iter.avoidNameConflict("usingnamespace_{d}", .{i}),
-                .@"usingnamespace",
-                false,
-            };
-        },
-        .unnamed_test => info: {
-            if (iter.pass != .unnamed) return;
-            const i = iter.unnamed_test_index;
-            iter.unnamed_test_index += 1;
-            break :info .{
-                try iter.avoidNameConflict("test_{d}", .{i}),
-                .@"test",
-                false,
-            };
-        },
-        .decltest => info: {
-            // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary.
-            if (iter.pass != .unnamed) return;
-            assert(declaration.flags.has_doc_comment);
-            const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end]));
-            break :info .{
-                try iter.avoidNameConflict("decltest.{s}", .{name}),
-                .@"test",
-                true,
-            };
-        },
-        _ => if (declaration.name.isNamedTest(zir)) info: {
-            // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary.
-            if (iter.pass != .unnamed) return;
-            break :info .{
-                try iter.avoidNameConflict("test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}),
-                .@"test",
-                true,
-            };
-        } else info: {
-            if (iter.pass != .named) return;
-            const name = try ip.getOrPutString(
-                gpa,
-                zir.nullTerminatedString(declaration.name.toString(zir).?),
-                .no_embedded_nulls,
-            );
-            try iter.seen_decls.putNoClobber(gpa, name, {});
-            break :info .{
-                name,
-                .named,
-                false,
-            };
-        },
-    };
-
-    switch (kind) {
-        .@"usingnamespace" => try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1),
-        .@"test" => try zcu.test_functions.ensureUnusedCapacity(gpa, 1),
-        else => {},
-    }
-
-    const parent_file_scope_index = iter.parent_decl.getFileScopeIndex(zcu);
-    const tracked_inst = try ip.trackZir(gpa, parent_file_scope_index, decl_inst);
-
-    // We create a Decl for it regardless of analysis status.
-
-    const prev_exported, const decl_index = if (iter.existing_by_inst.get(tracked_inst)) |decl_index| decl_index: {
-        // We need only update this existing Decl.
-        const decl = zcu.declPtr(decl_index);
-        const was_exported = decl.is_exported;
-        assert(decl.kind == kind); // ZIR tracking should preserve this
-        decl.name = decl_name;
-        decl.is_pub = declaration.flags.is_pub;
-        decl.is_exported = declaration.flags.is_export;
-        break :decl_index .{ was_exported, decl_index };
-    } else decl_index: {
-        // Create and set up a new Decl.
-        const new_decl_index = try zcu.allocateNewDecl(namespace_index);
-        const new_decl = zcu.declPtr(new_decl_index);
-        new_decl.kind = kind;
-        new_decl.name = decl_name;
-        new_decl.is_pub = declaration.flags.is_pub;
-        new_decl.is_exported = declaration.flags.is_export;
-        new_decl.zir_decl_index = tracked_inst.toOptional();
-        break :decl_index .{ false, new_decl_index };
-    };
-
-    const decl = zcu.declPtr(decl_index);
-
-    namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu });
-
-    const comp = zcu.comp;
-    const decl_mod = namespace.fileScope(zcu).mod;
-    const want_analysis = declaration.flags.is_export or switch (kind) {
-        .anon => unreachable,
-        .@"comptime" => true,
-        .@"usingnamespace" => a: {
-            namespace.usingnamespace_set.putAssumeCapacityNoClobber(decl_index, declaration.flags.is_pub);
-            break :a true;
-        },
-        .named => false,
-        .@"test" => a: {
-            if (!comp.config.is_test) break :a false;
-            if (decl_mod != zcu.main_mod) break :a false;
-            if (is_named_test and comp.test_filters.len > 0) {
-                const decl_fqn = try namespace.fullyQualifiedName(zcu, decl_name);
-                const decl_fqn_slice = decl_fqn.toSlice(ip);
-                for (comp.test_filters) |test_filter| {
-                    if (mem.indexOf(u8, decl_fqn_slice, test_filter)) |_| break;
-                } else break :a false;
-            }
-            zcu.test_functions.putAssumeCapacity(decl_index, {}); // may clobber on incremental update
-            break :a true;
-        },
-    };
-
-    if (want_analysis) {
-        // We will not queue analysis if the decl has been analyzed on a previous update and
-        // `is_export` is unchanged. In this case, the incremental update mechanism will handle
-        // re-analysis for us if necessary.
-        if (prev_exported != declaration.flags.is_export or decl.analysis == .unreferenced) {
-            log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{}' decl_index={d}", .{
-                namespace.fileScope(zcu).sub_file_path, decl_name.fmt(ip), decl_index,
-            });
-            comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = decl_index });
-        }
-    }
-
-    if (decl.getOwnedFunction(zcu) != null) {
-        // TODO this logic is insufficient; namespaces we don't re-scan may still require
-        // updated line numbers. Look into this!
-        // TODO Look into detecting when this would be unnecessary by storing enough state
-        // in `Decl` to notice that the line number did not change.
-        comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index });
-    }
-}
-
 /// Cancel the creation of an anon decl and delete any references to it.
 /// If other decls depend on this decl, they must be aborted first.
 pub fn abortAnonDecl(mod: *Module, decl_index: Decl.Index) void {