Commit 30ec43a6c7

Andrew Kelley <andrew@ziglang.org>
2024-07-04 23:16:42
Zcu: extract permanent state from File
Primarily, this commit removes 2 fields from File, relying on the data being stored in the `files` field, with the key as the path digest, and the value as the struct decl corresponding to the File. This table is serialized into the compiler state that survives between incremental updates. Meanwhile, the File struct remains ephemeral data that can be reconstructed the first time it is needed by the compiler process, as well as operated on by independent worker threads. A key outcome of this commit is that there is now a stable index that can be used to refer to a File. This will be needed when serializing error messages to survive incremental compilation updates.
1 parent 7ed2fbd
src/arch/aarch64/CodeGen.zig
@@ -345,7 +345,7 @@ pub fn generate(
     assert(fn_owner_decl.has_tv);
     const fn_type = fn_owner_decl.typeOf(zcu);
     const namespace = zcu.namespacePtr(fn_owner_decl.src_namespace);
-    const target = &namespace.file_scope.mod.resolved_target.result;
+    const target = &namespace.fileScope(zcu).mod.resolved_target.result;
 
     var branch_stack = std.ArrayList(Branch).init(gpa);
     defer {
src/arch/arm/CodeGen.zig
@@ -352,7 +352,7 @@ pub fn generate(
     assert(fn_owner_decl.has_tv);
     const fn_type = fn_owner_decl.typeOf(zcu);
     const namespace = zcu.namespacePtr(fn_owner_decl.src_namespace);
-    const target = &namespace.file_scope.mod.resolved_target.result;
+    const target = &namespace.fileScope(zcu).mod.resolved_target.result;
 
     var branch_stack = std.ArrayList(Branch).init(gpa);
     defer {
src/arch/riscv64/CodeGen.zig
@@ -712,8 +712,8 @@ pub fn generate(
     assert(fn_owner_decl.has_tv);
     const fn_type = fn_owner_decl.typeOf(zcu);
     const namespace = zcu.namespacePtr(fn_owner_decl.src_namespace);
-    const target = &namespace.file_scope.mod.resolved_target.result;
-    const mod = namespace.file_scope.mod;
+    const target = &namespace.fileScope(zcu).mod.resolved_target.result;
+    const mod = namespace.fileScope(zcu).mod;
 
     var branch_stack = std.ArrayList(Branch).init(gpa);
     defer {
src/arch/sparc64/CodeGen.zig
@@ -277,7 +277,7 @@ pub fn generate(
     assert(fn_owner_decl.has_tv);
     const fn_type = fn_owner_decl.typeOf(zcu);
     const namespace = zcu.namespacePtr(fn_owner_decl.src_namespace);
-    const target = &namespace.file_scope.mod.resolved_target.result;
+    const target = &namespace.fileScope(zcu).mod.resolved_target.result;
 
     var branch_stack = std.ArrayList(Branch).init(gpa);
     defer {
src/arch/wasm/CodeGen.zig
@@ -1212,11 +1212,11 @@ pub fn generate(
     _ = src_loc;
     const comp = bin_file.comp;
     const gpa = comp.gpa;
-    const mod = comp.module.?;
-    const func = mod.funcInfo(func_index);
-    const decl = mod.declPtr(func.owner_decl);
-    const namespace = mod.namespacePtr(decl.src_namespace);
-    const target = namespace.file_scope.mod.resolved_target.result;
+    const zcu = comp.module.?;
+    const func = zcu.funcInfo(func_index);
+    const decl = zcu.declPtr(func.owner_decl);
+    const namespace = zcu.namespacePtr(decl.src_namespace);
+    const target = namespace.fileScope(zcu).mod.resolved_target.result;
     var code_gen: CodeGen = .{
         .gpa = gpa,
         .air = air,
@@ -7706,7 +7706,7 @@ fn airFence(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     // for a single-threaded build, can we emit the `fence` instruction.
     // In all other cases, we emit no instructions for a fence.
     const func_namespace = zcu.namespacePtr(func.decl.src_namespace);
-    const single_threaded = func_namespace.file_scope.mod.single_threaded;
+    const single_threaded = func_namespace.fileScope(zcu).mod.single_threaded;
     if (func.useAtomicFeature() and !single_threaded) {
         try func.addAtomicTag(.atomic_fence);
     }
src/arch/x86_64/CodeGen.zig
@@ -810,7 +810,7 @@ pub fn generate(
     assert(fn_owner_decl.has_tv);
     const fn_type = fn_owner_decl.typeOf(zcu);
     const namespace = zcu.namespacePtr(fn_owner_decl.src_namespace);
-    const mod = namespace.file_scope.mod;
+    const mod = namespace.fileScope(zcu).mod;
 
     var function = Self{
         .gpa = gpa,
src/codegen/c.zig
@@ -2581,7 +2581,7 @@ pub fn genTypeDecl(
                 _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{});
                 try writer.writeByte(';');
                 const owner_decl = zcu.declPtr(owner_decl_index);
-                const owner_mod = zcu.namespacePtr(owner_decl.src_namespace).file_scope.mod;
+                const owner_mod = zcu.namespacePtr(owner_decl.src_namespace).fileScope(zcu).mod;
                 if (!owner_mod.strip) {
                     try writer.writeAll(" /* ");
                     try owner_decl.renderFullyQualifiedName(zcu, writer);
src/codegen/llvm.zig
@@ -1362,7 +1362,8 @@ pub const Object = struct {
         const decl_index = func.owner_decl;
         const decl = zcu.declPtr(decl_index);
         const namespace = zcu.namespacePtr(decl.src_namespace);
-        const owner_mod = namespace.file_scope.mod;
+        const file_scope = namespace.fileScope(zcu);
+        const owner_mod = file_scope.mod;
         const fn_info = zcu.typeToFunc(decl.typeOf(zcu)).?;
         const target = owner_mod.resolved_target.result;
         const ip = &zcu.intern_pool;
@@ -1633,7 +1634,7 @@ pub const Object = struct {
         function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
 
         const file, const subprogram = if (!wip.strip) debug_info: {
-            const file = try o.getDebugFile(namespace.file_scope);
+            const file = try o.getDebugFile(file_scope);
 
             const line_number = decl.navSrcLine(zcu) + 1;
             const is_internal_linkage = decl.val.getExternFunc(zcu) == null;
@@ -1720,23 +1721,23 @@ pub const Object = struct {
 
     pub fn updateExports(
         self: *Object,
-        mod: *Module,
+        zcu: *Zcu,
         exported: Module.Exported,
         export_indices: []const u32,
     ) link.File.UpdateExportsError!void {
         const decl_index = switch (exported) {
             .decl_index => |i| i,
-            .value => |val| return updateExportedValue(self, mod, val, export_indices),
+            .value => |val| return updateExportedValue(self, zcu, val, export_indices),
         };
-        const ip = &mod.intern_pool;
+        const ip = &zcu.intern_pool;
         const global_index = self.decl_map.get(decl_index).?;
-        const decl = mod.declPtr(decl_index);
-        const comp = mod.comp;
+        const decl = zcu.declPtr(decl_index);
+        const comp = zcu.comp;
 
         if (export_indices.len != 0) {
-            return updateExportedGlobal(self, mod, global_index, export_indices);
+            return updateExportedGlobal(self, zcu, global_index, export_indices);
         } else {
-            const fqn = try self.builder.strtabString((try decl.fullyQualifiedName(mod)).toSlice(ip));
+            const fqn = try self.builder.strtabString((try decl.fullyQualifiedName(zcu)).toSlice(ip));
             try global_index.rename(fqn, &self.builder);
             global_index.setLinkage(.internal, &self.builder);
             if (comp.config.dll_export_fns)
@@ -1908,12 +1909,12 @@ pub const Object = struct {
 
         const gpa = o.gpa;
         const target = o.target;
-        const mod = o.module;
-        const ip = &mod.intern_pool;
+        const zcu = o.module;
+        const ip = &zcu.intern_pool;
 
         if (o.debug_type_map.get(ty)) |debug_type| return debug_type;
 
-        switch (ty.zigTypeTag(mod)) {
+        switch (ty.zigTypeTag(zcu)) {
             .Void,
             .NoReturn,
             => {
@@ -1925,12 +1926,12 @@ pub const Object = struct {
                 return debug_void_type;
             },
             .Int => {
-                const info = ty.intInfo(mod);
+                const info = ty.intInfo(zcu);
                 assert(info.bits != 0);
                 const name = try o.allocTypeName(ty);
                 defer gpa.free(name);
                 const builder_name = try o.builder.metadataString(name);
-                const debug_bits = ty.abiSize(mod) * 8; // lldb cannot handle non-byte sized types
+                const debug_bits = ty.abiSize(zcu) * 8; // lldb cannot handle non-byte sized types
                 const debug_int_type = switch (info.signedness) {
                     .signed => try o.builder.debugSignedType(builder_name, debug_bits),
                     .unsigned => try o.builder.debugUnsignedType(builder_name, debug_bits),
@@ -1939,10 +1940,10 @@ pub const Object = struct {
                 return debug_int_type;
             },
             .Enum => {
-                const owner_decl_index = ty.getOwnerDecl(mod);
+                const owner_decl_index = ty.getOwnerDecl(zcu);
                 const owner_decl = o.module.declPtr(owner_decl_index);
 
-                if (!ty.hasRuntimeBitsIgnoreComptime(mod)) {
+                if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) {
                     const debug_enum_type = try o.makeEmptyNamespaceDebugType(owner_decl_index);
                     try o.debug_type_map.put(gpa, ty, debug_enum_type);
                     return debug_enum_type;
@@ -1954,13 +1955,13 @@ pub const Object = struct {
                 defer gpa.free(enumerators);
 
                 const int_ty = Type.fromInterned(enum_type.tag_ty);
-                const int_info = ty.intInfo(mod);
+                const int_info = ty.intInfo(zcu);
                 assert(int_info.bits != 0);
 
                 for (enum_type.names.get(ip), 0..) |field_name_ip, i| {
                     var bigint_space: Value.BigIntSpace = undefined;
                     const bigint = if (enum_type.values.len != 0)
-                        Value.fromInterned(enum_type.values.get(ip)[i]).toBigInt(&bigint_space, mod)
+                        Value.fromInterned(enum_type.values.get(ip)[i]).toBigInt(&bigint_space, zcu)
                     else
                         std.math.big.int.Mutable.init(&bigint_space.limbs, i).toConst();
 
@@ -1972,7 +1973,8 @@ pub const Object = struct {
                     );
                 }
 
-                const file = try o.getDebugFile(mod.namespacePtr(owner_decl.src_namespace).file_scope);
+                const file_scope = zcu.namespacePtr(owner_decl.src_namespace).fileScope(zcu);
+                const file = try o.getDebugFile(file_scope);
                 const scope = try o.namespaceToDebugScope(owner_decl.src_namespace);
 
                 const name = try o.allocTypeName(ty);
@@ -1982,10 +1984,10 @@ pub const Object = struct {
                     try o.builder.metadataString(name),
                     file,
                     scope,
-                    owner_decl.typeSrcLine(mod) + 1, // Line
+                    owner_decl.typeSrcLine(zcu) + 1, // Line
                     try o.lowerDebugType(int_ty),
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(enumerators),
                 );
 
@@ -2014,7 +2016,7 @@ pub const Object = struct {
             },
             .Pointer => {
                 // Normalize everything that the debug info does not represent.
-                const ptr_info = ty.ptrInfo(mod);
+                const ptr_info = ty.ptrInfo(zcu);
 
                 if (ptr_info.sentinel != .none or
                     ptr_info.flags.address_space != .generic or
@@ -2025,10 +2027,10 @@ pub const Object = struct {
                     ptr_info.flags.is_const or
                     ptr_info.flags.is_volatile or
                     ptr_info.flags.size == .Many or ptr_info.flags.size == .C or
-                    !Type.fromInterned(ptr_info.child).hasRuntimeBitsIgnoreComptime(mod))
+                    !Type.fromInterned(ptr_info.child).hasRuntimeBitsIgnoreComptime(zcu))
                 {
-                    const bland_ptr_ty = try mod.ptrType(.{
-                        .child = if (!Type.fromInterned(ptr_info.child).hasRuntimeBitsIgnoreComptime(mod))
+                    const bland_ptr_ty = try zcu.ptrType(.{
+                        .child = if (!Type.fromInterned(ptr_info.child).hasRuntimeBitsIgnoreComptime(zcu))
                             .anyopaque_type
                         else
                             ptr_info.child,
@@ -2050,18 +2052,18 @@ pub const Object = struct {
                 // Set as forward reference while the type is lowered in case it references itself
                 try o.debug_type_map.put(gpa, ty, debug_fwd_ref);
 
-                if (ty.isSlice(mod)) {
-                    const ptr_ty = ty.slicePtrFieldType(mod);
+                if (ty.isSlice(zcu)) {
+                    const ptr_ty = ty.slicePtrFieldType(zcu);
                     const len_ty = Type.usize;
 
                     const name = try o.allocTypeName(ty);
                     defer gpa.free(name);
                     const line = 0;
 
-                    const ptr_size = ptr_ty.abiSize(mod);
-                    const ptr_align = ptr_ty.abiAlignment(mod);
-                    const len_size = len_ty.abiSize(mod);
-                    const len_align = len_ty.abiAlignment(mod);
+                    const ptr_size = ptr_ty.abiSize(zcu);
+                    const ptr_align = ptr_ty.abiAlignment(zcu);
+                    const len_size = len_ty.abiSize(zcu);
+                    const len_align = len_ty.abiAlignment(zcu);
 
                     const len_offset = len_align.forward(ptr_size);
 
@@ -2093,8 +2095,8 @@ pub const Object = struct {
                         o.debug_compile_unit, // Scope
                         line,
                         .none, // Underlying type
-                        ty.abiSize(mod) * 8,
-                        (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                        ty.abiSize(zcu) * 8,
+                        (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                         try o.builder.debugTuple(&.{
                             debug_ptr_type,
                             debug_len_type,
@@ -2122,7 +2124,7 @@ pub const Object = struct {
                     0, // Line
                     debug_elem_ty,
                     target.ptrBitWidth(),
-                    (ty.ptrAlignment(mod).toByteUnits() orelse 0) * 8,
+                    (ty.ptrAlignment(zcu).toByteUnits() orelse 0) * 8,
                     0, // Offset
                 );
 
@@ -2146,13 +2148,14 @@ pub const Object = struct {
 
                 const name = try o.allocTypeName(ty);
                 defer gpa.free(name);
-                const owner_decl_index = ty.getOwnerDecl(mod);
+                const owner_decl_index = ty.getOwnerDecl(zcu);
                 const owner_decl = o.module.declPtr(owner_decl_index);
+                const file_scope = zcu.namespacePtr(owner_decl.src_namespace).fileScope(zcu);
                 const debug_opaque_type = try o.builder.debugStructType(
                     try o.builder.metadataString(name),
-                    try o.getDebugFile(mod.namespacePtr(owner_decl.src_namespace).file_scope),
+                    try o.getDebugFile(file_scope),
                     try o.namespaceToDebugScope(owner_decl.src_namespace),
-                    owner_decl.typeSrcLine(mod) + 1, // Line
+                    owner_decl.typeSrcLine(zcu) + 1, // Line
                     .none, // Underlying type
                     0, // Size
                     0, // Align
@@ -2167,13 +2170,13 @@ pub const Object = struct {
                     .none, // File
                     .none, // Scope
                     0, // Line
-                    try o.lowerDebugType(ty.childType(mod)),
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    try o.lowerDebugType(ty.childType(zcu)),
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(&.{
                         try o.builder.debugSubrange(
                             try o.builder.debugConstant(try o.builder.intConst(.i64, 0)),
-                            try o.builder.debugConstant(try o.builder.intConst(.i64, ty.arrayLen(mod))),
+                            try o.builder.debugConstant(try o.builder.intConst(.i64, ty.arrayLen(zcu))),
                         ),
                     }),
                 );
@@ -2181,14 +2184,14 @@ pub const Object = struct {
                 return debug_array_type;
             },
             .Vector => {
-                const elem_ty = ty.elemType2(mod);
+                const elem_ty = ty.elemType2(zcu);
                 // Vector elements cannot be padded since that would make
                 // @bitSizOf(elem) * len > @bitSizOf(vec).
                 // Neither gdb nor lldb seem to be able to display non-byte sized
                 // vectors properly.
-                const debug_elem_type = switch (elem_ty.zigTypeTag(mod)) {
+                const debug_elem_type = switch (elem_ty.zigTypeTag(zcu)) {
                     .Int => blk: {
-                        const info = elem_ty.intInfo(mod);
+                        const info = elem_ty.intInfo(zcu);
                         assert(info.bits != 0);
                         const name = try o.allocTypeName(ty);
                         defer gpa.free(name);
@@ -2202,7 +2205,7 @@ pub const Object = struct {
                         try o.builder.metadataString("bool"),
                         1,
                     ),
-                    else => try o.lowerDebugType(ty.childType(mod)),
+                    else => try o.lowerDebugType(ty.childType(zcu)),
                 };
 
                 const debug_vector_type = try o.builder.debugVectorType(
@@ -2211,12 +2214,12 @@ pub const Object = struct {
                     .none, // Scope
                     0, // Line
                     debug_elem_type,
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(&.{
                         try o.builder.debugSubrange(
                             try o.builder.debugConstant(try o.builder.intConst(.i64, 0)),
-                            try o.builder.debugConstant(try o.builder.intConst(.i64, ty.vectorLen(mod))),
+                            try o.builder.debugConstant(try o.builder.intConst(.i64, ty.vectorLen(zcu))),
                         ),
                     }),
                 );
@@ -2227,8 +2230,8 @@ pub const Object = struct {
             .Optional => {
                 const name = try o.allocTypeName(ty);
                 defer gpa.free(name);
-                const child_ty = ty.optionalChild(mod);
-                if (!child_ty.hasRuntimeBitsIgnoreComptime(mod)) {
+                const child_ty = ty.optionalChild(zcu);
+                if (!child_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
                     const debug_bool_type = try o.builder.debugBoolType(
                         try o.builder.metadataString(name),
                         8,
@@ -2242,7 +2245,7 @@ pub const Object = struct {
                 // Set as forward reference while the type is lowered in case it references itself
                 try o.debug_type_map.put(gpa, ty, debug_fwd_ref);
 
-                if (ty.optionalReprIsPayload(mod)) {
+                if (ty.optionalReprIsPayload(zcu)) {
                     const debug_optional_type = try o.lowerDebugType(child_ty);
 
                     o.builder.debugForwardReferenceSetType(debug_fwd_ref, debug_optional_type);
@@ -2255,10 +2258,10 @@ pub const Object = struct {
                 }
 
                 const non_null_ty = Type.u8;
-                const payload_size = child_ty.abiSize(mod);
-                const payload_align = child_ty.abiAlignment(mod);
-                const non_null_size = non_null_ty.abiSize(mod);
-                const non_null_align = non_null_ty.abiAlignment(mod);
+                const payload_size = child_ty.abiSize(zcu);
+                const payload_align = child_ty.abiAlignment(zcu);
+                const non_null_size = non_null_ty.abiSize(zcu);
+                const non_null_align = non_null_ty.abiAlignment(zcu);
                 const non_null_offset = non_null_align.forward(payload_size);
 
                 const debug_data_type = try o.builder.debugMemberType(
@@ -2289,8 +2292,8 @@ pub const Object = struct {
                     o.debug_compile_unit, // Scope
                     0, // Line
                     .none, // Underlying type
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(&.{
                         debug_data_type,
                         debug_some_type,
@@ -2306,8 +2309,8 @@ pub const Object = struct {
                 return debug_optional_type;
             },
             .ErrorUnion => {
-                const payload_ty = ty.errorUnionPayload(mod);
-                if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
+                const payload_ty = ty.errorUnionPayload(zcu);
+                if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
                     // TODO: Maybe remove?
                     const debug_error_union_type = try o.lowerDebugType(Type.anyerror);
                     try o.debug_type_map.put(gpa, ty, debug_error_union_type);
@@ -2317,10 +2320,10 @@ pub const Object = struct {
                 const name = try o.allocTypeName(ty);
                 defer gpa.free(name);
 
-                const error_size = Type.anyerror.abiSize(mod);
-                const error_align = Type.anyerror.abiAlignment(mod);
-                const payload_size = payload_ty.abiSize(mod);
-                const payload_align = payload_ty.abiAlignment(mod);
+                const error_size = Type.anyerror.abiSize(zcu);
+                const error_align = Type.anyerror.abiAlignment(zcu);
+                const payload_size = payload_ty.abiSize(zcu);
+                const payload_align = payload_ty.abiAlignment(zcu);
 
                 var error_index: u32 = undefined;
                 var payload_index: u32 = undefined;
@@ -2368,8 +2371,8 @@ pub const Object = struct {
                     o.debug_compile_unit, // Sope
                     0, // Line
                     .none, // Underlying type
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(&fields),
                 );
 
@@ -2390,14 +2393,14 @@ pub const Object = struct {
                 const name = try o.allocTypeName(ty);
                 defer gpa.free(name);
 
-                if (mod.typeToPackedStruct(ty)) |struct_type| {
+                if (zcu.typeToPackedStruct(ty)) |struct_type| {
                     const backing_int_ty = struct_type.backingIntType(ip).*;
                     if (backing_int_ty != .none) {
-                        const info = Type.fromInterned(backing_int_ty).intInfo(mod);
+                        const info = Type.fromInterned(backing_int_ty).intInfo(zcu);
                         const builder_name = try o.builder.metadataString(name);
                         const debug_int_type = switch (info.signedness) {
-                            .signed => try o.builder.debugSignedType(builder_name, ty.abiSize(mod) * 8),
-                            .unsigned => try o.builder.debugUnsignedType(builder_name, ty.abiSize(mod) * 8),
+                            .signed => try o.builder.debugSignedType(builder_name, ty.abiSize(zcu) * 8),
+                            .unsigned => try o.builder.debugUnsignedType(builder_name, ty.abiSize(zcu) * 8),
                         };
                         try o.debug_type_map.put(gpa, ty, debug_int_type);
                         return debug_int_type;
@@ -2417,10 +2420,10 @@ pub const Object = struct {
                         const debug_fwd_ref = try o.builder.debugForwardReference();
 
                         for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, i| {
-                            if (field_val != .none or !Type.fromInterned(field_ty).hasRuntimeBits(mod)) continue;
+                            if (field_val != .none or !Type.fromInterned(field_ty).hasRuntimeBits(zcu)) continue;
 
-                            const field_size = Type.fromInterned(field_ty).abiSize(mod);
-                            const field_align = Type.fromInterned(field_ty).abiAlignment(mod);
+                            const field_size = Type.fromInterned(field_ty).abiSize(zcu);
+                            const field_align = Type.fromInterned(field_ty).abiAlignment(zcu);
                             const field_offset = field_align.forward(offset);
                             offset = field_offset + field_size;
 
@@ -2448,8 +2451,8 @@ pub const Object = struct {
                             o.debug_compile_unit, // Scope
                             0, // Line
                             .none, // Underlying type
-                            ty.abiSize(mod) * 8,
-                            (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                            ty.abiSize(zcu) * 8,
+                            (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                             try o.builder.debugTuple(fields.items),
                         );
 
@@ -2467,7 +2470,7 @@ pub const Object = struct {
                             // into. Therefore we can satisfy this by making an empty namespace,
                             // rather than changing the frontend to unnecessarily resolve the
                             // struct field types.
-                            const owner_decl_index = ty.getOwnerDecl(mod);
+                            const owner_decl_index = ty.getOwnerDecl(zcu);
                             const debug_struct_type = try o.makeEmptyNamespaceDebugType(owner_decl_index);
                             try o.debug_type_map.put(gpa, ty, debug_struct_type);
                             return debug_struct_type;
@@ -2476,14 +2479,14 @@ pub const Object = struct {
                     else => {},
                 }
 
-                if (!ty.hasRuntimeBitsIgnoreComptime(mod)) {
-                    const owner_decl_index = ty.getOwnerDecl(mod);
+                if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) {
+                    const owner_decl_index = ty.getOwnerDecl(zcu);
                     const debug_struct_type = try o.makeEmptyNamespaceDebugType(owner_decl_index);
                     try o.debug_type_map.put(gpa, ty, debug_struct_type);
                     return debug_struct_type;
                 }
 
-                const struct_type = mod.typeToStruct(ty).?;
+                const struct_type = zcu.typeToStruct(ty).?;
 
                 var fields: std.ArrayListUnmanaged(Builder.Metadata) = .{};
                 defer fields.deinit(gpa);
@@ -2499,14 +2502,14 @@ pub const Object = struct {
                 var it = struct_type.iterateRuntimeOrder(ip);
                 while (it.next()) |field_index| {
                     const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_index]);
-                    if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
-                    const field_size = field_ty.abiSize(mod);
-                    const field_align = mod.structFieldAlignment(
+                    if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
+                    const field_size = field_ty.abiSize(zcu);
+                    const field_align = zcu.structFieldAlignment(
                         struct_type.fieldAlign(ip, field_index),
                         field_ty,
                         struct_type.layout,
                     );
-                    const field_offset = ty.structFieldOffset(field_index, mod);
+                    const field_offset = ty.structFieldOffset(field_index, zcu);
 
                     const field_name = struct_type.fieldName(ip, field_index).unwrap() orelse
                         try ip.getOrPutStringFmt(gpa, "{d}", .{field_index}, .no_embedded_nulls);
@@ -2529,8 +2532,8 @@ pub const Object = struct {
                     o.debug_compile_unit, // Scope
                     0, // Line
                     .none, // Underlying type
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(fields.items),
                 );
 
@@ -2543,14 +2546,14 @@ pub const Object = struct {
                 return debug_struct_type;
             },
             .Union => {
-                const owner_decl_index = ty.getOwnerDecl(mod);
+                const owner_decl_index = ty.getOwnerDecl(zcu);
 
                 const name = try o.allocTypeName(ty);
                 defer gpa.free(name);
 
                 const union_type = ip.loadUnionType(ty.toIntern());
                 if (!union_type.haveFieldTypes(ip) or
-                    !ty.hasRuntimeBitsIgnoreComptime(mod) or
+                    !ty.hasRuntimeBitsIgnoreComptime(zcu) or
                     !union_type.haveLayout(ip))
                 {
                     const debug_union_type = try o.makeEmptyNamespaceDebugType(owner_decl_index);
@@ -2558,7 +2561,7 @@ pub const Object = struct {
                     return debug_union_type;
                 }
 
-                const layout = mod.getUnionLayout(union_type);
+                const layout = zcu.getUnionLayout(union_type);
 
                 const debug_fwd_ref = try o.builder.debugForwardReference();
 
@@ -2572,8 +2575,8 @@ pub const Object = struct {
                         o.debug_compile_unit, // Scope
                         0, // Line
                         .none, // Underlying type
-                        ty.abiSize(mod) * 8,
-                        (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                        ty.abiSize(zcu) * 8,
+                        (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                         try o.builder.debugTuple(
                             &.{try o.lowerDebugType(Type.fromInterned(union_type.enum_tag_ty))},
                         ),
@@ -2600,12 +2603,12 @@ pub const Object = struct {
 
                 for (0..tag_type.names.len) |field_index| {
                     const field_ty = union_type.field_types.get(ip)[field_index];
-                    if (!Type.fromInterned(field_ty).hasRuntimeBitsIgnoreComptime(mod)) continue;
+                    if (!Type.fromInterned(field_ty).hasRuntimeBitsIgnoreComptime(zcu)) continue;
 
-                    const field_size = Type.fromInterned(field_ty).abiSize(mod);
+                    const field_size = Type.fromInterned(field_ty).abiSize(zcu);
                     const field_align: InternPool.Alignment = switch (union_type.flagsPtr(ip).layout) {
                         .@"packed" => .none,
-                        .auto, .@"extern" => mod.unionFieldNormalAlignment(union_type, @intCast(field_index)),
+                        .auto, .@"extern" => zcu.unionFieldNormalAlignment(union_type, @intCast(field_index)),
                     };
 
                     const field_name = tag_type.names.get(ip)[field_index];
@@ -2634,8 +2637,8 @@ pub const Object = struct {
                     o.debug_compile_unit, // Scope
                     0, // Line
                     .none, // Underlying type
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(fields.items),
                 );
 
@@ -2693,8 +2696,8 @@ pub const Object = struct {
                     o.debug_compile_unit, // Scope
                     0, // Line
                     .none, // Underlying type
-                    ty.abiSize(mod) * 8,
-                    (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
+                    ty.abiSize(zcu) * 8,
+                    (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
                     try o.builder.debugTuple(&full_fields),
                 );
 
@@ -2707,7 +2710,7 @@ pub const Object = struct {
                 return debug_tagged_union_type;
             },
             .Fn => {
-                const fn_info = mod.typeToFunc(ty).?;
+                const fn_info = zcu.typeToFunc(ty).?;
 
                 var debug_param_types = std.ArrayList(Builder.Metadata).init(gpa);
                 defer debug_param_types.deinit();
@@ -2715,32 +2718,32 @@ pub const Object = struct {
                 try debug_param_types.ensureUnusedCapacity(3 + fn_info.param_types.len);
 
                 // Return type goes first.
-                if (Type.fromInterned(fn_info.return_type).hasRuntimeBitsIgnoreComptime(mod)) {
-                    const sret = firstParamSRet(fn_info, mod, target);
+                if (Type.fromInterned(fn_info.return_type).hasRuntimeBitsIgnoreComptime(zcu)) {
+                    const sret = firstParamSRet(fn_info, zcu, target);
                     const ret_ty = if (sret) Type.void else Type.fromInterned(fn_info.return_type);
                     debug_param_types.appendAssumeCapacity(try o.lowerDebugType(ret_ty));
 
                     if (sret) {
-                        const ptr_ty = try mod.singleMutPtrType(Type.fromInterned(fn_info.return_type));
+                        const ptr_ty = try zcu.singleMutPtrType(Type.fromInterned(fn_info.return_type));
                         debug_param_types.appendAssumeCapacity(try o.lowerDebugType(ptr_ty));
                     }
                 } else {
                     debug_param_types.appendAssumeCapacity(try o.lowerDebugType(Type.void));
                 }
 
-                if (Type.fromInterned(fn_info.return_type).isError(mod) and
+                if (Type.fromInterned(fn_info.return_type).isError(zcu) and
                     o.module.comp.config.any_error_tracing)
                 {
-                    const ptr_ty = try mod.singleMutPtrType(try o.getStackTraceType());
+                    const ptr_ty = try zcu.singleMutPtrType(try o.getStackTraceType());
                     debug_param_types.appendAssumeCapacity(try o.lowerDebugType(ptr_ty));
                 }
 
                 for (0..fn_info.param_types.len) |i| {
                     const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[i]);
-                    if (!param_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
+                    if (!param_ty.hasRuntimeBitsIgnoreComptime(zcu)) continue;
 
-                    if (isByRef(param_ty, mod)) {
-                        const ptr_ty = try mod.singleMutPtrType(param_ty);
+                    if (isByRef(param_ty, zcu)) {
+                        const ptr_ty = try zcu.singleMutPtrType(param_ty);
                         debug_param_types.appendAssumeCapacity(try o.lowerDebugType(ptr_ty));
                     } else {
                         debug_param_types.appendAssumeCapacity(try o.lowerDebugType(param_ty));
@@ -2767,9 +2770,10 @@ pub const Object = struct {
     }
 
     fn namespaceToDebugScope(o: *Object, namespace_index: InternPool.NamespaceIndex) !Builder.Metadata {
-        const mod = o.module;
-        const namespace = mod.namespacePtr(namespace_index);
-        if (namespace.parent == .none) return try o.getDebugFile(namespace.file_scope);
+        const zcu = o.module;
+        const namespace = zcu.namespacePtr(namespace_index);
+        const file_scope = namespace.fileScope(zcu);
+        if (namespace.parent == .none) return try o.getDebugFile(file_scope);
 
         const gop = try o.debug_unresolved_namespace_scopes.getOrPut(o.gpa, namespace_index);
 
@@ -2779,13 +2783,14 @@ pub const Object = struct {
     }
 
     fn makeEmptyNamespaceDebugType(o: *Object, decl_index: InternPool.DeclIndex) !Builder.Metadata {
-        const mod = o.module;
-        const decl = mod.declPtr(decl_index);
+        const zcu = o.module;
+        const decl = zcu.declPtr(decl_index);
+        const file_scope = zcu.namespacePtr(decl.src_namespace).fileScope(zcu);
         return o.builder.debugStructType(
-            try o.builder.metadataString(decl.name.toSlice(&mod.intern_pool)), // TODO use fully qualified name
-            try o.getDebugFile(mod.namespacePtr(decl.src_namespace).file_scope),
+            try o.builder.metadataString(decl.name.toSlice(&zcu.intern_pool)), // TODO use fully qualified name
+            try o.getDebugFile(file_scope),
             try o.namespaceToDebugScope(decl.src_namespace),
-            decl.typeSrcLine(mod) + 1,
+            decl.typeSrcLine(zcu) + 1,
             .none,
             0,
             0,
@@ -2794,21 +2799,22 @@ pub const Object = struct {
     }
 
     fn getStackTraceType(o: *Object) Allocator.Error!Type {
-        const mod = o.module;
+        const zcu = o.module;
 
-        const std_mod = mod.std_mod;
-        const std_file = (mod.importPkg(std_mod) catch unreachable).file;
+        const std_mod = zcu.std_mod;
+        const std_file_imported = zcu.importPkg(std_mod) catch unreachable;
 
-        const builtin_str = try mod.intern_pool.getOrPutString(mod.gpa, "builtin", .no_embedded_nulls);
-        const std_namespace = mod.namespacePtr(mod.declPtr(std_file.root_decl.unwrap().?).src_namespace);
-        const builtin_decl = std_namespace.decls.getKeyAdapted(builtin_str, Module.DeclAdapter{ .zcu = mod }).?;
+        const builtin_str = try zcu.intern_pool.getOrPutString(zcu.gpa, "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, Module.DeclAdapter{ .zcu = zcu }).?;
 
-        const stack_trace_str = try mod.intern_pool.getOrPutString(mod.gpa, "StackTrace", .no_embedded_nulls);
+        const stack_trace_str = try zcu.intern_pool.getOrPutString(zcu.gpa, "StackTrace", .no_embedded_nulls);
         // buffer is only used for int_type, `builtin` is a struct.
-        const builtin_ty = mod.declPtr(builtin_decl).val.toType();
-        const builtin_namespace = mod.namespacePtrUnwrap(builtin_ty.getNamespaceIndex(mod)).?;
-        const stack_trace_decl_index = builtin_namespace.decls.getKeyAdapted(stack_trace_str, Module.DeclAdapter{ .zcu = mod }).?;
-        const stack_trace_decl = mod.declPtr(stack_trace_decl_index);
+        const builtin_ty = zcu.declPtr(builtin_decl).val.toType();
+        const builtin_namespace = zcu.namespacePtrUnwrap(builtin_ty.getNamespaceIndex(zcu)).?;
+        const stack_trace_decl_index = builtin_namespace.decls.getKeyAdapted(stack_trace_str, Module.DeclAdapter{ .zcu = zcu }).?;
+        const stack_trace_decl = zcu.declPtr(stack_trace_decl_index);
 
         // Sema should have ensured that StackTrace was analyzed.
         assert(stack_trace_decl.has_tv);
@@ -2834,7 +2840,7 @@ pub const Object = struct {
         const gpa = o.gpa;
         const decl = zcu.declPtr(decl_index);
         const namespace = zcu.namespacePtr(decl.src_namespace);
-        const owner_mod = namespace.file_scope.mod;
+        const owner_mod = namespace.fileScope(zcu).mod;
         const zig_fn_type = decl.typeOf(zcu);
         const gop = try o.decl_map.getOrPut(gpa, decl_index);
         if (gop.found_existing) return gop.value_ptr.ptr(&o.builder).kind.function;
@@ -3059,17 +3065,17 @@ 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 mod = o.module;
-        const decl = mod.declPtr(decl_index);
-        const is_extern = decl.isExtern(mod);
+        const zcu = o.module;
+        const decl = zcu.declPtr(decl_index);
+        const is_extern = decl.isExtern(zcu);
 
         const variable_index = try o.builder.addVariable(
             try o.builder.strtabString((if (is_extern)
                 decl.name
             else
-                try decl.fullyQualifiedName(mod)).toSlice(&mod.intern_pool)),
-            try o.lowerType(decl.typeOf(mod)),
-            toLlvmGlobalAddressSpace(decl.@"addrspace", mod.getTarget()),
+                try decl.fullyQualifiedName(zcu)).toSlice(&zcu.intern_pool)),
+            try o.lowerType(decl.typeOf(zcu)),
+            toLlvmGlobalAddressSpace(decl.@"addrspace", zcu.getTarget()),
         );
         gop.value_ptr.* = variable_index.ptrConst(&o.builder).global;
 
@@ -3077,9 +3083,9 @@ pub const Object = struct {
         if (is_extern) {
             variable_index.setLinkage(.external, &o.builder);
             variable_index.setUnnamedAddr(.default, &o.builder);
-            if (decl.val.getVariable(mod)) |decl_var| {
-                const decl_namespace = mod.namespacePtr(decl.src_namespace);
-                const single_threaded = decl_namespace.file_scope.mod.single_threaded;
+            if (decl.val.getVariable(zcu)) |decl_var| {
+                const decl_namespace = zcu.namespacePtr(decl.src_namespace);
+                const single_threaded = decl_namespace.fileScope(zcu).mod.single_threaded;
                 variable_index.setThreadLocal(
                     if (decl_var.is_threadlocal and !single_threaded) .generaldynamic else .default,
                     &o.builder,
@@ -4638,7 +4644,8 @@ pub const DeclGen = struct {
         const o = dg.object;
         const zcu = o.module;
         const namespace = zcu.namespacePtr(dg.decl.src_namespace);
-        return namespace.file_scope.mod;
+        const file_scope = namespace.fileScope(zcu);
+        return file_scope.mod;
     }
 
     fn todo(dg: *DeclGen, comptime format: []const u8, args: anytype) Error {
@@ -4682,7 +4689,7 @@ pub const DeclGen = struct {
 
             if (decl.val.getVariable(zcu)) |decl_var| {
                 const decl_namespace = zcu.namespacePtr(decl.src_namespace);
-                const single_threaded = decl_namespace.file_scope.mod.single_threaded;
+                const single_threaded = decl_namespace.fileScope(zcu).mod.single_threaded;
                 variable_index.setThreadLocal(
                     if (decl_var.is_threadlocal and !single_threaded) .generaldynamic else .default,
                     &o.builder,
@@ -4692,10 +4699,11 @@ pub const DeclGen = struct {
             const line_number = decl.navSrcLine(zcu) + 1;
 
             const namespace = zcu.namespacePtr(decl.src_namespace);
-            const owner_mod = namespace.file_scope.mod;
+            const file_scope = namespace.fileScope(zcu);
+            const owner_mod = file_scope.mod;
 
             if (!owner_mod.strip) {
-                const debug_file = try o.getDebugFile(namespace.file_scope);
+                const debug_file = try o.getDebugFile(file_scope);
 
                 const debug_global_var = try o.builder.debugGlobalVar(
                     try o.builder.metadataString(decl.name.toSlice(ip)), // Name
@@ -5143,9 +5151,10 @@ pub const FuncGen = struct {
             const decl_index = func.owner_decl;
             const decl = zcu.declPtr(decl_index);
             const namespace = zcu.namespacePtr(decl.src_namespace);
-            const owner_mod = namespace.file_scope.mod;
+            const file_scope = namespace.fileScope(zcu);
+            const owner_mod = file_scope.mod;
 
-            self.file = try o.getDebugFile(namespace.file_scope);
+            self.file = try o.getDebugFile(file_scope);
 
             const line_number = decl.navSrcLine(zcu) + 1;
             self.inlined = self.wip.debug_location;
src/codegen/spirv.zig
@@ -188,19 +188,20 @@ pub const Object = struct {
 
     fn genDecl(
         self: *Object,
-        mod: *Module,
+        zcu: *Zcu,
         decl_index: InternPool.DeclIndex,
         air: Air,
         liveness: Liveness,
     ) !void {
-        const decl = mod.declPtr(decl_index);
-        const namespace = mod.namespacePtr(decl.src_namespace);
-        const structured_cfg = namespace.file_scope.mod.structured_cfg;
+        const gpa = self.gpa;
+        const decl = zcu.declPtr(decl_index);
+        const namespace = zcu.namespacePtr(decl.src_namespace);
+        const structured_cfg = namespace.fileScope(zcu).mod.structured_cfg;
 
         var decl_gen = DeclGen{
-            .gpa = self.gpa,
+            .gpa = gpa,
             .object = self,
-            .module = mod,
+            .module = zcu,
             .spv = &self.spv,
             .decl_index = decl_index,
             .air = air,
@@ -212,19 +213,19 @@ pub const Object = struct {
                 false => .{ .unstructured = .{} },
             },
             .current_block_label = undefined,
-            .base_line = decl.navSrcLine(mod),
+            .base_line = decl.navSrcLine(zcu),
         };
         defer decl_gen.deinit();
 
         decl_gen.genDecl() catch |err| switch (err) {
             error.CodegenFail => {
-                try mod.failed_analysis.put(mod.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }), decl_gen.error_msg.?);
+                try zcu.failed_analysis.put(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }), decl_gen.error_msg.?);
             },
             else => |other| {
                 // There might be an error that happened *after* self.error_msg
                 // was already allocated, so be sure to free it.
                 if (decl_gen.error_msg) |error_msg| {
-                    error_msg.deinit(mod.gpa);
+                    error_msg.deinit(gpa);
                 }
 
                 return other;
src/link/Wasm/ZigObject.zig
@@ -335,29 +335,29 @@ fn finishUpdateDecl(
     code: []const u8,
 ) !void {
     const gpa = wasm_file.base.comp.gpa;
-    const mod = wasm_file.base.comp.module.?;
-    const decl = mod.declPtr(decl_index);
+    const zcu = wasm_file.base.comp.module.?;
+    const decl = zcu.declPtr(decl_index);
     const decl_info = zig_object.decls_map.get(decl_index).?;
     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(mod);
-    sym.name = try zig_object.string_table.insert(gpa, full_name.toSlice(&mod.intern_pool));
+    const full_name = try decl.fullyQualifiedName(zcu);
+    sym.name = try zig_object.string_table.insert(gpa, full_name.toSlice(&zcu.intern_pool));
     try atom.code.appendSlice(gpa, code);
     atom.size = @intCast(code.len);
 
-    switch (decl.typeOf(mod).zigTypeTag(mod)) {
+    switch (decl.typeOf(zcu).zigTypeTag(zcu)) {
         .Fn => {
             sym.index = try zig_object.appendFunction(gpa, .{ .type_index = zig_object.atom_types.get(atom_index).? });
             sym.tag = .function;
         },
         else => {
-            const segment_name: []const u8 = if (decl.getOwnedVariable(mod)) |variable| name: {
+            const segment_name: []const u8 = if (decl.getOwnedVariable(zcu)) |variable| name: {
                 if (variable.is_const) {
                     break :name ".rodata.";
-                } else if (Value.fromInterned(variable.init).isUndefDeep(mod)) {
-                    const decl_namespace = mod.namespacePtr(decl.src_namespace);
-                    const optimize_mode = decl_namespace.file_scope.mod.optimize_mode;
+                } else if (Value.fromInterned(variable.init).isUndefDeep(zcu)) {
+                    const decl_namespace = zcu.namespacePtr(decl.src_namespace);
+                    const optimize_mode = decl_namespace.fileScope(zcu).mod.optimize_mode;
                     const is_initialized = switch (optimize_mode) {
                         .Debug, .ReleaseSafe => true,
                         .ReleaseFast, .ReleaseSmall => false,
@@ -382,7 +382,7 @@ fn finishUpdateDecl(
             // Will be freed upon freeing of decl or after cleanup of Wasm binary.
             const full_segment_name = try std.mem.concat(gpa, u8, &.{
                 segment_name,
-                full_name.toSlice(&mod.intern_pool),
+                full_name.toSlice(&zcu.intern_pool),
             });
             errdefer gpa.free(full_segment_name);
             sym.tag = .data;
@@ -390,7 +390,7 @@ fn finishUpdateDecl(
         },
     }
     if (code.len == 0) return;
-    atom.alignment = decl.getAlignment(mod);
+    atom.alignment = decl.getAlignment(zcu);
 }
 
 /// Creates and initializes a new segment in the 'Data' section.
src/link/C.zig
@@ -208,6 +208,8 @@ pub fn updateFunc(
     fwd_decl.clearRetainingCapacity();
     code.clearRetainingCapacity();
 
+    const file_scope = zcu.namespacePtr(decl.src_namespace).fileScope(zcu);
+
     var function: codegen.Function = .{
         .value_map = codegen.CValueMap.init(gpa),
         .air = air,
@@ -217,7 +219,7 @@ pub fn updateFunc(
             .dg = .{
                 .gpa = gpa,
                 .zcu = zcu,
-                .mod = zcu.namespacePtr(decl.src_namespace).file_scope.mod,
+                .mod = file_scope.mod,
                 .error_msg = null,
                 .pass = .{ .decl = decl_index },
                 .is_naked_fn = decl.typeOf(zcu).fnCallingConvention(zcu) == .Naked,
@@ -335,11 +337,13 @@ pub fn updateDecl(self: *C, zcu: *Zcu, decl_index: InternPool.DeclIndex) !void {
     fwd_decl.clearRetainingCapacity();
     code.clearRetainingCapacity();
 
+    const file_scope = zcu.namespacePtr(decl.src_namespace).fileScope(zcu);
+
     var object: codegen.Object = .{
         .dg = .{
             .gpa = gpa,
             .zcu = zcu,
-            .mod = zcu.namespacePtr(decl.src_namespace).file_scope.mod,
+            .mod = file_scope.mod,
             .error_msg = null,
             .pass = .{ .decl = decl_index },
             .is_naked_fn = false,
@@ -491,7 +495,7 @@ pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !vo
         for (self.decl_table.keys(), self.decl_table.values()) |decl_index, *decl_block| {
             const decl = zcu.declPtr(decl_index);
             const extern_name = if (decl.isExtern(zcu)) decl.name.toOptional() else .none;
-            const mod = zcu.namespacePtr(decl.src_namespace).file_scope.mod;
+            const mod = zcu.namespacePtr(decl.src_namespace).fileScope(zcu).mod;
             try self.flushDeclBlock(
                 zcu,
                 mod,
@@ -848,7 +852,7 @@ pub fn updateExports(
     const gpa = self.base.comp.gpa;
     const mod, const pass: codegen.DeclGen.Pass, const decl_block, const exported_block = switch (exported) {
         .decl_index => |decl_index| .{
-            zcu.namespacePtr(zcu.declPtr(decl_index).src_namespace).file_scope.mod,
+            zcu.namespacePtr(zcu.declPtr(decl_index).src_namespace).fileScope(zcu).mod,
             .{ .decl = decl_index },
             self.decl_table.getPtr(decl_index).?,
             (try self.exported_decls.getOrPut(gpa, decl_index)).value_ptr,
src/link/Dwarf.zig
@@ -1204,7 +1204,7 @@ pub fn commitDeclState(
     const decl = zcu.declPtr(decl_index);
     const ip = &zcu.intern_pool;
     const namespace = zcu.namespacePtr(decl.src_namespace);
-    const target = namespace.file_scope.mod.resolved_target.result;
+    const target = namespace.fileScope(zcu).mod.resolved_target.result;
     const target_endian = target.cpu.arch.endian();
 
     var dbg_line_buffer = &decl_state.dbg_line;
src/Package/Module.zig
@@ -379,7 +379,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
 
         const new_file = try arena.create(File);
 
-        const bin_digest, const hex_digest = digest: {
+        const hex_digest = digest: {
             var hasher: Cache.Hasher = Cache.hasher_init;
             hasher.update(generated_builtin_source);
 
@@ -393,7 +393,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
                 .{std.fmt.fmtSliceHexLower(&bin_digest)},
             ) catch unreachable;
 
-            break :digest .{ bin_digest, hex_digest };
+            break :digest hex_digest;
         };
 
         const builtin_sub_path = try arena.dupe(u8, "b" ++ std.fs.path.sep_str ++ hex_digest);
@@ -443,10 +443,6 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
             .zir = undefined,
             .status = .never_loaded,
             .mod = new,
-            .root_decl = .none,
-            // We might as well use this digest for the File `path digest`, since there's a
-            // one-to-one correspondence here between distinct paths and distinct contents.
-            .path_digest = bin_digest,
         };
         break :b new;
     };
src/codegen.zig
@@ -58,7 +58,7 @@ pub fn generateFunction(
     const func = zcu.funcInfo(func_index);
     const decl = zcu.declPtr(func.owner_decl);
     const namespace = zcu.namespacePtr(decl.src_namespace);
-    const target = namespace.file_scope.mod.resolved_target.result;
+    const target = namespace.fileScope(zcu).mod.resolved_target.result;
     switch (target.cpu.arch) {
         .arm,
         .armeb,
@@ -88,7 +88,7 @@ pub fn generateLazyFunction(
     const decl_index = lazy_sym.ty.getOwnerDecl(zcu);
     const decl = zcu.declPtr(decl_index);
     const namespace = zcu.namespacePtr(decl.src_namespace);
-    const target = namespace.file_scope.mod.resolved_target.result;
+    const target = namespace.fileScope(zcu).mod.resolved_target.result;
     switch (target.cpu.arch) {
         .x86_64 => return @import("arch/x86_64/CodeGen.zig").generateLazy(lf, src_loc, lazy_sym, code, debug_output),
         else => unreachable,
@@ -742,7 +742,7 @@ fn lowerDeclRef(
     const zcu = lf.comp.module.?;
     const decl = zcu.declPtr(decl_index);
     const namespace = zcu.namespacePtr(decl.src_namespace);
-    const target = namespace.file_scope.mod.resolved_target.result;
+    const target = namespace.fileScope(zcu).mod.resolved_target.result;
 
     const ptr_width = target.ptrBitWidth();
     const is_fn_body = decl.typeOf(zcu).zigTypeTag(zcu) == .Fn;
@@ -836,7 +836,7 @@ fn genDeclRef(
 
     const ptr_decl = zcu.declPtr(ptr_decl_index);
     const namespace = zcu.namespacePtr(ptr_decl.src_namespace);
-    const target = namespace.file_scope.mod.resolved_target.result;
+    const target = namespace.fileScope(zcu).mod.resolved_target.result;
 
     const ptr_bits = target.ptrBitWidth();
     const ptr_bytes: u64 = @divExact(ptr_bits, 8);
@@ -875,7 +875,7 @@ fn genDeclRef(
     }
 
     const decl_namespace = zcu.namespacePtr(decl.src_namespace);
-    const single_threaded = decl_namespace.file_scope.mod.single_threaded;
+    const single_threaded = decl_namespace.fileScope(zcu).mod.single_threaded;
     const is_threadlocal = val.isPtrToThreadLocal(zcu) and !single_threaded;
     const is_extern = decl.isExtern(zcu);
 
@@ -985,7 +985,7 @@ pub fn genTypedValue(
 
     const owner_decl = zcu.declPtr(owner_decl_index);
     const namespace = zcu.namespacePtr(owner_decl.src_namespace);
-    const target = namespace.file_scope.mod.resolved_target.result;
+    const target = namespace.fileScope(zcu).mod.resolved_target.result;
     const ptr_bits = target.ptrBitWidth();
 
     if (!ty.isSlice(zcu)) switch (ip.indexToKey(val.toIntern())) {
src/Compilation.zig
@@ -116,7 +116,7 @@ win32_resource_work_queue: if (build_options.only_core_functionality) void else
 /// These jobs are to tokenize, parse, and astgen files, which may be outdated
 /// since the last compilation, as well as scan for `@import` and queue up
 /// additional jobs corresponding to those new files.
-astgen_work_queue: std.fifo.LinearFifo(*Module.File, .Dynamic),
+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.
@@ -1433,7 +1433,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
             .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
             .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(*Module.File, .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),
             .c_source_files = options.c_source_files,
             .rc_source_files = options.rc_source_files,
@@ -2095,13 +2095,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
         }
     }
 
-    if (comp.module) |module| {
-        module.compile_log_text.shrinkAndFree(gpa, 0);
+    if (comp.module) |zcu| {
+        zcu.compile_log_text.shrinkAndFree(gpa, 0);
 
         // Make sure std.zig is inside the import_table. We unconditionally need
         // it for start.zig.
-        const std_mod = module.std_mod;
-        _ = try module.importPkg(std_mod);
+        const std_mod = zcu.std_mod;
+        _ = try zcu.importPkg(std_mod);
 
         // Normally we rely on importing std to in turn import the root source file
         // in the start code, but when using the stage1 backend that won't happen,
@@ -2110,64 +2110,65 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
         // Likewise, in the case of `zig test`, the test runner is the root source file,
         // and so there is nothing to import the main file.
         if (comp.config.is_test) {
-            _ = try module.importPkg(module.main_mod);
+            _ = try zcu.importPkg(zcu.main_mod);
         }
 
-        if (module.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
-            _ = try module.importPkg(compiler_rt_mod);
+        if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
+            _ = try zcu.importPkg(compiler_rt_mod);
         }
 
         // Put a work item in for every known source file to detect if
         // it changed, and, if so, re-compute ZIR and then queue the job
         // to update it.
-        try comp.astgen_work_queue.ensureUnusedCapacity(module.import_table.count());
-        for (module.import_table.values()) |file| {
+        try comp.astgen_work_queue.ensureUnusedCapacity(zcu.import_table.count());
+        for (zcu.import_table.values(), 0..) |file, file_index_usize| {
+            const file_index: Zcu.File.Index = @enumFromInt(file_index_usize);
             if (file.mod.isBuiltin()) continue;
-            comp.astgen_work_queue.writeItemAssumeCapacity(file);
+            comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
         }
 
         // Put a work item in for checking if any files used with `@embedFile` changed.
-        try comp.embed_file_work_queue.ensureUnusedCapacity(module.embed_table.count());
-        for (module.embed_table.values()) |embed_file| {
+        try comp.embed_file_work_queue.ensureUnusedCapacity(zcu.embed_table.count());
+        for (zcu.embed_table.values()) |embed_file| {
             comp.embed_file_work_queue.writeItemAssumeCapacity(embed_file);
         }
 
         try comp.work_queue.writeItem(.{ .analyze_mod = std_mod });
         if (comp.config.is_test) {
-            try comp.work_queue.writeItem(.{ .analyze_mod = module.main_mod });
+            try comp.work_queue.writeItem(.{ .analyze_mod = zcu.main_mod });
         }
 
-        if (module.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
+        if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
             try comp.work_queue.writeItem(.{ .analyze_mod = compiler_rt_mod });
         }
     }
 
     try comp.performAllTheWork(main_progress_node);
 
-    if (comp.module) |module| {
+    if (comp.module) |zcu| {
         if (build_options.enable_debug_extensions and comp.verbose_intern_pool) {
             std.debug.print("intern pool stats for '{s}':\n", .{
                 comp.root_name,
             });
-            module.intern_pool.dump();
+            zcu.intern_pool.dump();
         }
 
         if (build_options.enable_debug_extensions and comp.verbose_generic_instances) {
             std.debug.print("generic instances for '{s}:0x{x}':\n", .{
                 comp.root_name,
-                @as(usize, @intFromPtr(module)),
+                @as(usize, @intFromPtr(zcu)),
             });
-            module.intern_pool.dumpGenericInstances(gpa);
+            zcu.intern_pool.dumpGenericInstances(gpa);
         }
 
         if (comp.config.is_test and comp.totalErrorCount() == 0) {
             // The `test_functions` decl has been intentionally postponed until now,
             // at which point we must populate it with the list of test functions that
             // have been discovered and not filtered out.
-            try module.populateTestFunctions(main_progress_node);
+            try zcu.populateTestFunctions(main_progress_node);
         }
 
-        try module.processExports();
+        try zcu.processExports();
     }
 
     if (comp.totalErrorCount() != 0) {
@@ -2615,7 +2616,9 @@ fn resolveEmitLoc(
     return slice.ptr;
 }
 
-fn reportMultiModuleErrors(mod: *Module) !void {
+fn reportMultiModuleErrors(zcu: *Zcu) !void {
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
     // Some cases can give you a whole bunch of multi-module errors, which it's not helpful to
     // print all of, so we'll cap the number of these to emit.
     var num_errors: u32 = 0;
@@ -2623,37 +2626,39 @@ fn reportMultiModuleErrors(mod: *Module) !void {
     // Attach the "some omitted" note to the final error message
     var last_err: ?*Module.ErrorMsg = null;
 
-    for (mod.import_table.values()) |file| {
+    for (zcu.import_table.values(), 0..) |file, file_index_usize| {
         if (!file.multi_pkg) continue;
 
         num_errors += 1;
         if (num_errors > max_errors) continue;
 
+        const file_index: Zcu.File.Index = @enumFromInt(file_index_usize);
+
         const err = err_blk: {
             // Like with errors, let's cap the number of notes to prevent a huge error spew.
             const max_notes = 5;
             const omitted = file.references.items.len -| max_notes;
             const num_notes = file.references.items.len - omitted;
 
-            const notes = try mod.gpa.alloc(Module.ErrorMsg, if (omitted > 0) num_notes + 1 else num_notes);
-            errdefer mod.gpa.free(notes);
+            const notes = try gpa.alloc(Module.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(mod.gpa);
+                errdefer for (notes[0..i]) |*n| n.deinit(gpa);
                 note.* = switch (ref) {
                     .import => |import| try Module.ErrorMsg.init(
-                        mod.gpa,
+                        gpa,
                         .{
-                            .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, import.file, .main_struct_inst),
+                            .base_node_inst = try ip.trackZir(gpa, zcu.filePathDigest(import.file), .main_struct_inst),
                             .offset = .{ .token_abs = import.token },
                         },
                         "imported from module {s}",
-                        .{import.file.mod.fully_qualified_name},
+                        .{zcu.fileByIndex(import.file).mod.fully_qualified_name},
                     ),
                     .root => |pkg| try Module.ErrorMsg.init(
-                        mod.gpa,
+                        gpa,
                         .{
-                            .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+                            .base_node_inst = try ip.trackZir(gpa, zcu.filePathDigest(file_index), .main_struct_inst),
                             .offset = .entire_file,
                         },
                         "root of module {s}",
@@ -2661,25 +2666,25 @@ fn reportMultiModuleErrors(mod: *Module) !void {
                     ),
                 };
             }
-            errdefer for (notes[0..num_notes]) |*n| n.deinit(mod.gpa);
+            errdefer for (notes[0..num_notes]) |*n| n.deinit(gpa);
 
             if (omitted > 0) {
                 notes[num_notes] = try Module.ErrorMsg.init(
-                    mod.gpa,
+                    gpa,
                     .{
-                        .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+                        .base_node_inst = try ip.trackZir(gpa, zcu.filePathDigest(file_index), .main_struct_inst),
                         .offset = .entire_file,
                     },
                     "{} more references omitted",
                     .{omitted},
                 );
             }
-            errdefer if (omitted > 0) notes[num_notes].deinit(mod.gpa);
+            errdefer if (omitted > 0) notes[num_notes].deinit(gpa);
 
             const err = try Module.ErrorMsg.create(
-                mod.gpa,
+                gpa,
                 .{
-                    .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+                    .base_node_inst = try ip.trackZir(gpa, zcu.filePathDigest(file_index), .main_struct_inst),
                     .offset = .entire_file,
                 },
                 "file exists in multiple modules",
@@ -2688,8 +2693,8 @@ fn reportMultiModuleErrors(mod: *Module) !void {
             err.notes = notes;
             break :err_blk err;
         };
-        errdefer err.destroy(mod.gpa);
-        try mod.failed_files.putNoClobber(mod.gpa, file, err);
+        errdefer err.destroy(gpa);
+        try zcu.failed_files.putNoClobber(gpa, file, err);
         last_err = err;
     }
 
@@ -2700,15 +2705,15 @@ fn reportMultiModuleErrors(mod: *Module) !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(
-            mod.gpa,
+            gpa,
             err.src_loc,
             "{} more errors omitted",
             .{num_errors - max_errors},
         );
-        errdefer note.deinit(mod.gpa);
+        errdefer note.deinit(gpa);
 
         const i = err.notes.len;
-        err.notes = try mod.gpa.realloc(err.notes, i + 1);
+        err.notes = try gpa.realloc(err.notes, i + 1);
         err.notes[i] = note;
     }
 
@@ -2719,8 +2724,8 @@ fn reportMultiModuleErrors(mod: *Module) !void {
     // to add this flag after reporting the errors however, as otherwise
     // we'd get an error for every single downstream file, which wouldn't be
     // very useful.
-    for (mod.import_table.values()) |file| {
-        if (file.multi_pkg) file.recursiveMarkMultiPkg(mod);
+    for (zcu.import_table.values()) |file| {
+        if (file.multi_pkg) file.recursiveMarkMultiPkg(zcu);
     }
 }
 
@@ -2752,6 +2757,7 @@ const Header = extern struct {
         first_dependency_len: u32,
         dep_entries_len: u32,
         free_dep_entries_len: u32,
+        files_len: u32,
     },
 };
 
@@ -2759,7 +2765,7 @@ const Header = extern struct {
 /// saved, such as the target and most CLI flags. A cache hit will only occur
 /// when subsequent compiler invocations use the same set of flags.
 pub fn saveState(comp: *Compilation) !void {
-    var bufs_list: [19]std.posix.iovec_const = undefined;
+    var bufs_list: [21]std.posix.iovec_const = undefined;
     var bufs_len: usize = 0;
 
     const lf = comp.bin_file orelse return;
@@ -2780,6 +2786,7 @@ pub fn saveState(comp: *Compilation) !void {
                 .first_dependency_len = @intCast(ip.first_dependency.count()),
                 .dep_entries_len = @intCast(ip.dep_entries.items.len),
                 .free_dep_entries_len = @intCast(ip.free_dep_entries.items.len),
+                .files_len = @intCast(zcu.files.entries.len),
             },
         };
         addBuf(&bufs_list, &bufs_len, mem.asBytes(&header));
@@ -2804,8 +2811,10 @@ pub fn saveState(comp: *Compilation) !void {
         addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.dep_entries.items));
         addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.free_dep_entries.items));
 
+        addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(zcu.files.keys()));
+        addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(zcu.files.values()));
+
         // TODO: compilation errors
-        // TODO: files
         // TODO: namespaces
         // TODO: decls
         // TODO: linker state
@@ -3353,16 +3362,31 @@ pub fn performAllTheWork(
             }
         }
 
-        while (comp.astgen_work_queue.readItem()) |file| {
-            comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerAstGenFile, .{
-                comp, file, zir_prog_node, &comp.astgen_wait_group, .root,
-            });
-        }
+        if (comp.module) |zcu| {
+            {
+                // Worker threads may append to zcu.files and zcu.import_table
+                // so we must hold the lock while spawning those tasks, since
+                // we access those tables in this loop.
+                comp.mutex.lock();
+                defer comp.mutex.unlock();
 
-        while (comp.embed_file_work_queue.readItem()) |embed_file| {
-            comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerCheckEmbedFile, .{
-                comp, embed_file,
-            });
+                while (comp.astgen_work_queue.readItem()) |file_index| {
+                    // Pre-load these things from our single-threaded context since they
+                    // will be needed by the worker threads.
+                    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, file, file_index, path_digest, root_decl, zir_prog_node, &comp.astgen_wait_group, .root,
+                    });
+                }
+            }
+
+            while (comp.embed_file_work_queue.readItem()) |embed_file| {
+                comp.thread_pool.spawnWg(&comp.astgen_wait_group, workerCheckEmbedFile, .{
+                    comp, embed_file,
+                });
+            }
         }
 
         while (comp.c_object_work_queue.readItem()) |c_object| {
@@ -3426,8 +3450,8 @@ pub fn performAllTheWork(
 fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !void {
     switch (job) {
         .codegen_decl => |decl_index| {
-            const module = comp.module.?;
-            const decl = module.declPtr(decl_index);
+            const zcu = comp.module.?;
+            const decl = zcu.declPtr(decl_index);
 
             switch (decl.analysis) {
                 .unreferenced => unreachable,
@@ -3445,7 +3469,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
 
                     assert(decl.has_tv);
 
-                    try module.linkerUpdateDecl(decl_index);
+                    try zcu.linkerUpdateDecl(decl_index);
                     return;
                 },
             }
@@ -3454,16 +3478,16 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
             const named_frame = tracy.namedFrame("codegen_func");
             defer named_frame.end();
 
-            const module = comp.module.?;
+            const zcu = comp.module.?;
             // This call takes ownership of `func.air`.
-            try module.linkerUpdateFunc(func.func, func.air);
+            try zcu.linkerUpdateFunc(func.func, func.air);
         },
         .analyze_func => |func| {
             const named_frame = tracy.namedFrame("analyze_func");
             defer named_frame.end();
 
-            const module = comp.module.?;
-            module.ensureFuncBodyAnalyzed(func) catch |err| switch (err) {
+            const zcu = comp.module.?;
+            zcu.ensureFuncBodyAnalyzed(func) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
                 error.AnalysisFail => return,
             };
@@ -3472,8 +3496,8 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
             if (true) @panic("regressed compiler feature: emit-h should hook into updateExports, " ++
                 "not decl analysis, which is too early to know about @export calls");
 
-            const module = comp.module.?;
-            const decl = module.declPtr(decl_index);
+            const zcu = comp.module.?;
+            const decl = zcu.declPtr(decl_index);
 
             switch (decl.analysis) {
                 .unreferenced => unreachable,
@@ -3491,7 +3515,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
                     defer named_frame.end();
 
                     const gpa = comp.gpa;
-                    const emit_h = module.emit_h.?;
+                    const emit_h = zcu.emit_h.?;
                     _ = try emit_h.decl_table.getOrPut(gpa, decl_index);
                     const decl_emit_h = emit_h.declPtr(decl_index);
                     const fwd_decl = &decl_emit_h.fwd_decl;
@@ -3499,10 +3523,12 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
                     var ctypes_arena = std.heap.ArenaAllocator.init(gpa);
                     defer ctypes_arena.deinit();
 
+                    const file_scope = zcu.namespacePtr(decl.src_namespace).fileScope(zcu);
+
                     var dg: c_codegen.DeclGen = .{
                         .gpa = gpa,
-                        .zcu = module,
-                        .mod = module.namespacePtr(decl.src_namespace).file_scope.mod,
+                        .zcu = zcu,
+                        .mod = file_scope.mod,
                         .error_msg = null,
                         .pass = .{ .decl = decl_index },
                         .is_naked_fn = false,
@@ -3531,17 +3557,17 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
             }
         },
         .analyze_decl => |decl_index| {
-            const module = comp.module.?;
-            module.ensureDeclAnalyzed(decl_index) catch |err| switch (err) {
+            const zcu = comp.module.?;
+            zcu.ensureDeclAnalyzed(decl_index) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
                 error.AnalysisFail => return,
             };
-            const decl = module.declPtr(decl_index);
+            const decl = zcu.declPtr(decl_index);
             if (decl.kind == .@"test" and comp.config.is_test) {
                 // Tests are always emitted in test binaries. The decl_refs are created by
-                // Module.populateTestFunctions, but this will not queue body analysis, so do
+                // Zcu.populateTestFunctions, but this will not queue body analysis, so do
                 // that now.
-                try module.ensureFuncBodyAnalysisQueued(decl.val.toIntern());
+                try zcu.ensureFuncBodyAnalysisQueued(decl.val.toIntern());
             }
         },
         .resolve_type_fully => |ty| {
@@ -3559,30 +3585,30 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
             defer named_frame.end();
 
             const gpa = comp.gpa;
-            const module = comp.module.?;
-            const decl = module.declPtr(decl_index);
+            const zcu = comp.module.?;
+            const decl = zcu.declPtr(decl_index);
             const lf = comp.bin_file.?;
-            lf.updateDeclLineNumber(module, decl_index) catch |err| {
-                try module.failed_analysis.ensureUnusedCapacity(gpa, 1);
-                module.failed_analysis.putAssumeCapacityNoClobber(
+            lf.updateDeclLineNumber(zcu, decl_index) catch |err| {
+                try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
+                zcu.failed_analysis.putAssumeCapacityNoClobber(
                     InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
-                    try Module.ErrorMsg.create(
+                    try Zcu.ErrorMsg.create(
                         gpa,
-                        decl.navSrcLoc(module),
+                        decl.navSrcLoc(zcu),
                         "unable to update line number: {s}",
                         .{@errorName(err)},
                     ),
                 );
                 decl.analysis = .codegen_failure;
-                try module.retryable_failures.append(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
+                try zcu.retryable_failures.append(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
             };
         },
         .analyze_mod => |pkg| {
             const named_frame = tracy.namedFrame("analyze_mod");
             defer named_frame.end();
 
-            const module = comp.module.?;
-            module.semaPkg(pkg) catch |err| switch (err) {
+            const zcu = comp.module.?;
+            zcu.semaPkg(pkg) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
                 error.AnalysisFail => return,
             };
@@ -4015,14 +4041,17 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye
 const AstGenSrc = union(enum) {
     root,
     import: struct {
-        importing_file: *Module.File,
+        importing_file: Zcu.File.Index,
         import_tok: std.zig.Ast.TokenIndex,
     },
 };
 
 fn workerAstGenFile(
     comp: *Compilation,
-    file: *Module.File,
+    file: *Zcu.File,
+    file_index: Zcu.File.Index,
+    path_digest: Cache.BinDigest,
+    root_decl: Zcu.Decl.OptionalIndex,
     prog_node: std.Progress.Node,
     wg: *WaitGroup,
     src: AstGenSrc,
@@ -4030,12 +4059,12 @@ fn workerAstGenFile(
     const child_prog_node = prog_node.start(file.sub_file_path, 0);
     defer child_prog_node.end();
 
-    const mod = comp.module.?;
-    mod.astGenFile(file) catch |err| switch (err) {
+    const zcu = comp.module.?;
+    zcu.astGenFile(file, path_digest, root_decl) catch |err| switch (err) {
         error.AnalysisFail => return,
         else => {
             file.status = .retryable_failure;
-            comp.reportRetryableAstGenError(src, file, err) catch |oom| switch (oom) {
+            comp.reportRetryableAstGenError(src, file_index, err) catch |oom| switch (oom) {
                 // Swallowing this error is OK because it's implied to be OOM when
                 // there is a missing `failed_files` error message.
                 error.OutOfMemory => {},
@@ -4062,29 +4091,31 @@ fn workerAstGenFile(
             // `@import("builtin")` is handled specially.
             if (mem.eql(u8, import_path, "builtin")) continue;
 
-            const import_result = blk: {
+            const import_result, const imported_path_digest, const imported_root_decl = blk: {
                 comp.mutex.lock();
                 defer comp.mutex.unlock();
 
-                const res = mod.importFile(file, import_path) catch continue;
+                const res = zcu.importFile(file, import_path) catch continue;
                 if (!res.is_pkg) {
-                    res.file.addReference(mod.*, .{ .import = .{
-                        .file = file,
+                    res.file.addReference(zcu.*, .{ .import = .{
+                        .file = file_index,
                         .token = item.data.token,
                     } }) catch continue;
                 }
-                break :blk res;
+                const imported_path_digest = zcu.filePathDigest(res.file_index);
+                const imported_root_decl = zcu.fileRootDecl(res.file_index);
+                break :blk .{ res, imported_path_digest, imported_root_decl };
             };
             if (import_result.is_new) {
                 log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{
                     file.sub_file_path, import_path, import_result.file.sub_file_path,
                 });
                 const sub_src: AstGenSrc = .{ .import = .{
-                    .importing_file = file,
+                    .importing_file = file_index,
                     .import_tok = item.data.token,
                 } };
                 comp.thread_pool.spawnWg(wg, workerAstGenFile, .{
-                    comp, import_result.file, prog_node, wg, sub_src,
+                    comp, import_result.file, import_result.file_index, imported_path_digest, imported_root_decl, prog_node, wg, sub_src,
                 });
             }
         }
@@ -4435,21 +4466,22 @@ fn reportRetryableWin32ResourceError(
 fn reportRetryableAstGenError(
     comp: *Compilation,
     src: AstGenSrc,
-    file: *Module.File,
+    file_index: Zcu.File.Index,
     err: anyerror,
 ) error{OutOfMemory}!void {
-    const mod = comp.module.?;
-    const gpa = mod.gpa;
+    const zcu = comp.module.?;
+    const gpa = zcu.gpa;
 
+    const file = zcu.fileByIndex(file_index);
     file.status = .retryable_failure;
 
     const src_loc: Module.LazySrcLoc = switch (src) {
         .root => .{
-            .base_node_inst = try mod.intern_pool.trackZir(gpa, file, .main_struct_inst),
+            .base_node_inst = try zcu.intern_pool.trackZir(gpa, zcu.filePathDigest(file_index), .main_struct_inst),
             .offset = .entire_file,
         },
         .import => |info| .{
-            .base_node_inst = try mod.intern_pool.trackZir(gpa, info.importing_file, .main_struct_inst),
+            .base_node_inst = try zcu.intern_pool.trackZir(gpa, zcu.filePathDigest(info.importing_file), .main_struct_inst),
             .offset = .{ .token_abs = info.import_tok },
         },
     };
@@ -4462,7 +4494,7 @@ fn reportRetryableAstGenError(
     {
         comp.mutex.lock();
         defer comp.mutex.unlock();
-        try mod.failed_files.putNoClobber(gpa, file, err_msg);
+        try zcu.failed_files.putNoClobber(gpa, file, err_msg);
     }
 }
 
src/InternPool.zig
@@ -123,9 +123,14 @@ pub const TrackedInst = extern struct {
     };
 };
 
-pub fn trackZir(ip: *InternPool, gpa: Allocator, file: *Module.File, inst: Zir.Inst.Index) Allocator.Error!TrackedInst.Index {
+pub fn trackZir(
+    ip: *InternPool,
+    gpa: Allocator,
+    path_digest: Cache.BinDigest,
+    inst: Zir.Inst.Index,
+) Allocator.Error!TrackedInst.Index {
     const key: TrackedInst = .{
-        .path_digest = file.path_digest,
+        .path_digest = path_digest,
         .inst = inst,
     };
     const gop = try ip.tracked_insts.getOrPut(gpa, key);
src/main.zig
@@ -27,8 +27,6 @@ const Cache = std.Build.Cache;
 const target_util = @import("target.zig");
 const crash_report = @import("crash_report.zig");
 const Zcu = @import("Zcu.zig");
-/// Deprecated.
-const Module = Zcu;
 const AstGen = std.zig.AstGen;
 const mingw = @import("mingw.zig");
 const Server = std.zig.Server;
@@ -919,7 +917,7 @@ fn buildOutputType(
     var contains_res_file: bool = false;
     var reference_trace: ?u32 = null;
     var pdb_out_path: ?[]const u8 = null;
-    var error_limit: ?Module.ErrorInt = null;
+    var error_limit: ?Zcu.ErrorInt = null;
     // These are before resolving sysroot.
     var extra_cflags: std.ArrayListUnmanaged([]const u8) = .{};
     var extra_rcflags: std.ArrayListUnmanaged([]const u8) = .{};
@@ -1107,7 +1105,7 @@ fn buildOutputType(
                         );
                     } else if (mem.eql(u8, arg, "--error-limit")) {
                         const next_arg = args_iter.nextOrFatal();
-                        error_limit = std.fmt.parseUnsigned(Module.ErrorInt, next_arg, 0) catch |err| {
+                        error_limit = std.fmt.parseUnsigned(Zcu.ErrorInt, next_arg, 0) catch |err| {
                             fatal("unable to parse error limit '{s}': {s}", .{ next_arg, @errorName(err) });
                         };
                     } else if (mem.eql(u8, arg, "-cflags")) {
@@ -5956,7 +5954,7 @@ fn cmdAstCheck(
         }
     }
 
-    var file: Module.File = .{
+    var file: Zcu.File = .{
         .status = .never_loaded,
         .source_loaded = false,
         .tree_loaded = false,
@@ -5967,8 +5965,6 @@ fn cmdAstCheck(
         .tree = undefined,
         .zir = undefined,
         .mod = undefined,
-        .root_decl = .none,
-        .path_digest = undefined,
     };
     if (zig_source_file) |file_name| {
         var f = fs.cwd().openFile(file_name, .{}) catch |err| {
@@ -6275,7 +6271,7 @@ fn cmdDumpZir(
     };
     defer f.close();
 
-    var file: Module.File = .{
+    var file: Zcu.File = .{
         .status = .never_loaded,
         .source_loaded = false,
         .tree_loaded = false,
@@ -6284,10 +6280,8 @@ fn cmdDumpZir(
         .source = undefined,
         .stat = undefined,
         .tree = undefined,
-        .zir = try Module.loadZirCache(gpa, f),
+        .zir = try Zcu.loadZirCache(gpa, f),
         .mod = undefined,
-        .root_decl = .none,
-        .path_digest = undefined,
     };
     defer file.zir.deinit(gpa);
 
@@ -6342,7 +6336,7 @@ fn cmdChangelist(
     if (stat.size > std.zig.max_src_size)
         return error.FileTooBig;
 
-    var file: Module.File = .{
+    var file: Zcu.File = .{
         .status = .never_loaded,
         .source_loaded = false,
         .tree_loaded = false,
@@ -6357,8 +6351,6 @@ fn cmdChangelist(
         .tree = undefined,
         .zir = undefined,
         .mod = undefined,
-        .root_decl = .none,
-        .path_digest = undefined,
     };
 
     file.mod = try Package.Module.createLimited(arena, .{
@@ -6431,7 +6423,7 @@ fn cmdChangelist(
     var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
     defer inst_map.deinit(gpa);
 
-    try Module.mapOldZirToNew(gpa, old_zir, file.zir, &inst_map);
+    try Zcu.mapOldZirToNew(gpa, old_zir, file.zir, &inst_map);
 
     var bw = io.bufferedWriter(io.getStdOut().writer());
     const stdout = bw.writer();
src/Sema.zig
@@ -546,8 +546,12 @@ pub const Block = struct {
         };
     }
 
-    pub fn getFileScope(block: *Block, mod: *Module) *Module.File {
-        return mod.namespacePtr(block.namespace).file_scope;
+    pub fn getFileScope(block: *Block, zcu: *Zcu) *Zcu.File {
+        return zcu.fileByIndex(getFileScopeIndex(block, zcu));
+    }
+
+    pub fn getFileScopeIndex(block: *Block, zcu: *Zcu) Zcu.File.Index {
+        return zcu.namespacePtr(block.namespace).file_scope;
     }
 
     fn addTy(
@@ -826,7 +830,17 @@ pub const Block = struct {
 
     pub fn ownerModule(block: Block) *Package.Module {
         const zcu = block.sema.mod;
-        return zcu.namespacePtr(block.namespace).file_scope.mod;
+        return zcu.namespacePtr(block.namespace).fileScope(zcu).mod;
+    }
+
+    fn trackZir(block: *Block, inst: Zir.Inst.Index) Allocator.Error!InternPool.TrackedInst.Index {
+        const sema = block.sema;
+        const gpa = sema.gpa;
+        const zcu = sema.mod;
+        const ip = &zcu.intern_pool;
+        const file_index = block.getFileScopeIndex(zcu);
+        const path_digest = zcu.filePathDigest(file_index);
+        return ip.trackZir(gpa, path_digest, inst);
     }
 };
 
@@ -1000,7 +1014,7 @@ fn analyzeBodyInner(
         if (build_options.enable_logging) {
             std.log.scoped(.sema_zir).debug("sema ZIR {s} %{d}", .{ sub_file_path: {
                 const path_digest = block.src_base_inst.resolveFull(&mod.intern_pool).path_digest;
-                const index = mod.path_digest_map.getIndex(path_digest).?;
+                const index = mod.files.getIndex(path_digest).?;
                 break :sub_file_path mod.import_table.values()[index].sub_file_path;
             }, inst });
         }
@@ -2730,7 +2744,7 @@ fn zirStructDecl(
     const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
     const extra = sema.code.extraData(Zir.Inst.StructDecl, extended.operand);
 
-    const tracked_inst = try ip.trackZir(gpa, block.getFileScope(mod), inst);
+    const tracked_inst = try block.trackZir(inst);
     const src: LazySrcLoc = .{
         .base_node_inst = tracked_inst,
         .offset = LazySrcLoc.Offset.nodeOffset(0),
@@ -2806,7 +2820,7 @@ fn zirStructDecl(
         try ip.addDependency(
             sema.gpa,
             AnalUnit.wrap(.{ .decl = new_decl_index }),
-            .{ .src_hash = try ip.trackZir(sema.gpa, block.getFileScope(mod), inst) },
+            .{ .src_hash = try block.trackZir(inst) },
         );
     }
 
@@ -2814,7 +2828,7 @@ fn zirStructDecl(
     const new_namespace_index: InternPool.OptionalNamespaceIndex = if (true or decls_len > 0) (try mod.createNamespace(.{
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
-        .file_scope = block.getFileScope(mod),
+        .file_scope = block.getFileScopeIndex(mod),
     })).toOptional() else .none;
     errdefer if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns);
 
@@ -2947,7 +2961,7 @@ fn zirEnumDecl(
     const extra = sema.code.extraData(Zir.Inst.EnumDecl, extended.operand);
     var extra_index: usize = extra.end;
 
-    const tracked_inst = try ip.trackZir(gpa, block.getFileScope(mod), inst);
+    const tracked_inst = try block.trackZir(inst);
     const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) };
     const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } };
 
@@ -3040,9 +3054,9 @@ fn zirEnumDecl(
 
     if (sema.mod.comp.debug_incremental) {
         try mod.intern_pool.addDependency(
-            sema.gpa,
+            gpa,
             AnalUnit.wrap(.{ .decl = new_decl_index }),
-            .{ .src_hash = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst) },
+            .{ .src_hash = try block.trackZir(inst) },
         );
     }
 
@@ -3050,7 +3064,7 @@ fn zirEnumDecl(
     const new_namespace_index: InternPool.OptionalNamespaceIndex = if (true or decls_len > 0) (try mod.createNamespace(.{
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
-        .file_scope = block.getFileScope(mod),
+        .file_scope = block.getFileScopeIndex(mod),
     })).toOptional() else .none;
     errdefer if (!done) if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns);
 
@@ -3232,7 +3246,7 @@ fn zirUnionDecl(
     const extra = sema.code.extraData(Zir.Inst.UnionDecl, extended.operand);
     var extra_index: usize = extra.end;
 
-    const tracked_inst = try ip.trackZir(gpa, block.getFileScope(mod), inst);
+    const tracked_inst = try block.trackZir(inst);
     const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) };
 
     extra_index += @intFromBool(small.has_tag_type);
@@ -3306,9 +3320,9 @@ fn zirUnionDecl(
 
     if (sema.mod.comp.debug_incremental) {
         try mod.intern_pool.addDependency(
-            sema.gpa,
+            gpa,
             AnalUnit.wrap(.{ .decl = new_decl_index }),
-            .{ .src_hash = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst) },
+            .{ .src_hash = try block.trackZir(inst) },
         );
     }
 
@@ -3316,7 +3330,7 @@ fn zirUnionDecl(
     const new_namespace_index: InternPool.OptionalNamespaceIndex = if (true or decls_len > 0) (try mod.createNamespace(.{
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
-        .file_scope = block.getFileScope(mod),
+        .file_scope = block.getFileScopeIndex(mod),
     })).toOptional() else .none;
     errdefer if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns);
 
@@ -3348,7 +3362,7 @@ fn zirOpaqueDecl(
     const extra = sema.code.extraData(Zir.Inst.OpaqueDecl, extended.operand);
     var extra_index: usize = extra.end;
 
-    const tracked_inst = try ip.trackZir(gpa, block.getFileScope(mod), inst);
+    const tracked_inst = try block.trackZir(inst);
     const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) };
 
     const captures_len = if (small.has_captures_len) blk: {
@@ -3397,14 +3411,14 @@ fn zirOpaqueDecl(
         try ip.addDependency(
             gpa,
             AnalUnit.wrap(.{ .decl = new_decl_index }),
-            .{ .src_hash = try ip.trackZir(gpa, block.getFileScope(mod), inst) },
+            .{ .src_hash = try block.trackZir(inst) },
         );
     }
 
     const new_namespace_index: InternPool.OptionalNamespaceIndex = if (decls_len > 0) (try mod.createNamespace(.{
         .parent = block.namespace.toOptional(),
         .decl_index = new_decl_index,
-        .file_scope = block.getFileScope(mod),
+        .file_scope = block.getFileScopeIndex(mod),
     })).toOptional() else .none;
     errdefer if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns);
 
@@ -5893,8 +5907,8 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
     const tracy = trace(@src());
     defer tracy.end();
 
-    const mod = sema.mod;
-    const comp = mod.comp;
+    const zcu = sema.mod;
+    const comp = zcu.comp;
     const gpa = sema.gpa;
     const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
     const src = parent_block.nodeOffset(pl_node.src_node);
@@ -5940,7 +5954,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
             if (!comp.config.link_libc)
                 try sema.errNote(src, msg, "libc headers not available; compilation does not link against libc", .{});
 
-            const gop = try mod.cimport_errors.getOrPut(gpa, sema.ownerUnit());
+            const gop = try zcu.cimport_errors.getOrPut(gpa, sema.ownerUnit());
             if (!gop.found_existing) {
                 gop.value_ptr.* = c_import_res.errors;
                 c_import_res.errors = std.zig.ErrorBundle.empty;
@@ -5984,14 +5998,16 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
         else => |e| return e,
     };
 
-    const result = mod.importPkg(c_import_mod) catch |err|
+    const result = zcu.importPkg(c_import_mod) catch |err|
         return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
 
-    mod.astGenFile(result.file) catch |err|
+    const path_digest = zcu.filePathDigest(result.file_index);
+    const root_decl = zcu.fileRootDecl(result.file_index);
+    zcu.astGenFile(result.file, path_digest, root_decl) catch |err|
         return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
 
-    try mod.ensureFileAnalyzed(result.file);
-    const file_root_decl_index = result.file.root_decl.unwrap().?;
+    try zcu.ensureFileAnalyzed(result.file_index);
+    const file_root_decl_index = zcu.fileRootDecl(result.file_index).unwrap().?;
     return sema.analyzeDeclVal(parent_block, src, file_root_decl_index);
 }
 
@@ -6730,7 +6746,9 @@ fn lookupInNamespace(
                 // Skip decls which are not marked pub, which are in a different
                 // file than the `a.b`/`@hasDecl` syntax.
                 const decl = mod.declPtr(decl_index);
-                if (decl.is_pub or (src_file == decl.getFileScope(mod) and checked_namespaces.values()[check_i])) {
+                if (decl.is_pub or (src_file == decl.getFileScopeIndex(mod) and
+                    checked_namespaces.values()[check_i]))
+                {
                     try candidates.append(gpa, decl_index);
                 }
             }
@@ -6741,7 +6759,7 @@ fn lookupInNamespace(
                 if (sub_usingnamespace_decl_index == sema.owner_decl_index) continue;
                 const sub_usingnamespace_decl = mod.declPtr(sub_usingnamespace_decl_index);
                 const sub_is_pub = entry.value_ptr.*;
-                if (!sub_is_pub and src_file != sub_usingnamespace_decl.getFileScope(mod)) {
+                if (!sub_is_pub and src_file != sub_usingnamespace_decl.getFileScopeIndex(mod)) {
                     // Skip usingnamespace decls which are not marked pub, which are in
                     // a different file than the `a.b`/`@hasDecl` syntax.
                     continue;
@@ -6749,7 +6767,7 @@ fn lookupInNamespace(
                 try sema.ensureDeclAnalyzed(sub_usingnamespace_decl_index);
                 const ns_ty = sub_usingnamespace_decl.val.toType();
                 const sub_ns = mod.namespacePtrUnwrap(ns_ty.getNamespaceIndex(mod)) orelse continue;
-                try checked_namespaces.put(gpa, sub_ns, src_file == sub_usingnamespace_decl.getFileScope(mod));
+                try checked_namespaces.put(gpa, sub_ns, src_file == sub_usingnamespace_decl.getFileScopeIndex(mod));
             }
         }
 
@@ -8067,20 +8085,20 @@ fn instantiateGenericCall(
     call_tag: Air.Inst.Tag,
     call_dbg_node: ?Zir.Inst.Index,
 ) CompileError!Air.Inst.Ref {
-    const mod = sema.mod;
+    const zcu = sema.mod;
     const gpa = sema.gpa;
-    const ip = &mod.intern_pool;
+    const ip = &zcu.intern_pool;
 
     const func_val = try sema.resolveConstDefinedValue(block, func_src, func, .{
         .needed_comptime_reason = "generic function being called must be comptime-known",
     });
-    const generic_owner = switch (mod.intern_pool.indexToKey(func_val.toIntern())) {
+    const generic_owner = switch (zcu.intern_pool.indexToKey(func_val.toIntern())) {
         .func => func_val.toIntern(),
-        .ptr => |ptr| mod.declPtr(ptr.base_addr.decl).val.toIntern(),
+        .ptr => |ptr| zcu.declPtr(ptr.base_addr.decl).val.toIntern(),
         else => unreachable,
     };
-    const generic_owner_func = mod.intern_pool.indexToKey(generic_owner).func;
-    const generic_owner_ty_info = mod.typeToFunc(Type.fromInterned(generic_owner_func.ty)).?;
+    const generic_owner_func = zcu.intern_pool.indexToKey(generic_owner).func;
+    const generic_owner_ty_info = zcu.typeToFunc(Type.fromInterned(generic_owner_func.ty)).?;
 
     try sema.declareDependency(.{ .src_hash = generic_owner_func.zir_body_inst });
 
@@ -8092,10 +8110,10 @@ fn instantiateGenericCall(
     // The actual monomorphization happens via adding `func_instance` to
     // `InternPool`.
 
-    const fn_owner_decl = mod.declPtr(generic_owner_func.owner_decl);
+    const fn_owner_decl = zcu.declPtr(generic_owner_func.owner_decl);
     const namespace_index = fn_owner_decl.src_namespace;
-    const namespace = mod.namespacePtr(namespace_index);
-    const fn_zir = namespace.file_scope.zir;
+    const namespace = zcu.namespacePtr(namespace_index);
+    const fn_zir = namespace.fileScope(zcu).zir;
     const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip));
 
     const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count());
@@ -8110,7 +8128,7 @@ fn instantiateGenericCall(
     // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a
     // new, monomorphized function, with the comptime parameters elided.
     var child_sema: Sema = .{
-        .mod = mod,
+        .mod = zcu,
         .gpa = gpa,
         .arena = sema.arena,
         .code = fn_zir,
@@ -8199,7 +8217,7 @@ fn instantiateGenericCall(
         const arg_ref = try args_info.analyzeArg(sema, block, arg_index, param_ty, generic_owner_ty_info, func);
         try sema.validateRuntimeValue(block, args_info.argSrc(block, arg_index), arg_ref);
         const arg_ty = sema.typeOf(arg_ref);
-        if (arg_ty.zigTypeTag(mod) == .NoReturn) {
+        if (arg_ty.zigTypeTag(zcu) == .NoReturn) {
             // This terminates argument analysis.
             return arg_ref;
         }
@@ -8283,12 +8301,12 @@ fn instantiateGenericCall(
     const new_func_inst = try child_sema.resolveInlineBody(&child_block, fn_info.param_body[args_info.count()..], fn_info.param_body_inst);
     const callee_index = (child_sema.resolveConstDefinedValue(&child_block, LazySrcLoc.unneeded, new_func_inst, undefined) catch unreachable).toIntern();
 
-    const callee = mod.funcInfo(callee_index);
+    const callee = zcu.funcInfo(callee_index);
     callee.branchQuota(ip).* = @max(callee.branchQuota(ip).*, sema.branch_quota);
 
     // Make a runtime call to the new function, making sure to omit the comptime args.
     const func_ty = Type.fromInterned(callee.ty);
-    const func_ty_info = mod.typeToFunc(func_ty).?;
+    const func_ty_info = zcu.typeToFunc(func_ty).?;
 
     // If the call evaluated to a return type that requires comptime, never mind
     // our generic instantiation. Instead we need to perform a comptime call.
@@ -8304,13 +8322,13 @@ fn instantiateGenericCall(
     if (call_dbg_node) |some| try sema.zirDbgStmt(block, some);
 
     if (sema.owner_func_index != .none and
-        Type.fromInterned(func_ty_info.return_type).isError(mod))
+        Type.fromInterned(func_ty_info.return_type).isError(zcu))
     {
         ip.funcAnalysis(sema.owner_func_index).calls_or_awaits_errorable_fn = true;
     }
 
     try sema.addReferenceEntry(call_src, AnalUnit.wrap(.{ .func = callee_index }));
-    try mod.ensureFuncBodyAnalysisQueued(callee_index);
+    try zcu.ensureFuncBodyAnalysisQueued(callee_index);
 
     try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + runtime_args.items.len);
     const result = try block.addInst(.{
@@ -8333,7 +8351,7 @@ fn instantiateGenericCall(
     if (call_tag == .call_always_tail) {
         return sema.handleTailCall(block, call_src, func_ty, result);
     }
-    if (func_ty.fnReturnType(mod).isNoReturn(mod)) {
+    if (func_ty.fnReturnType(zcu).isNoReturn(zcu)) {
         _ = try block.addNoOp(.unreach);
         return .unreachable_value;
     }
@@ -9653,7 +9671,7 @@ fn funcCommon(
             .is_generic = final_is_generic,
             .is_noinline = is_noinline,
 
-            .zir_body_inst = try ip.trackZir(gpa, block.getFileScope(mod), func_inst),
+            .zir_body_inst = try block.trackZir(func_inst),
             .lbrace_line = src_locs.lbrace_line,
             .rbrace_line = src_locs.rbrace_line,
             .lbrace_column = @as(u16, @truncate(src_locs.columns)),
@@ -9731,7 +9749,7 @@ fn funcCommon(
             .ty = func_ty,
             .cc = cc,
             .is_noinline = is_noinline,
-            .zir_body_inst = try ip.trackZir(gpa, block.getFileScope(mod), func_inst),
+            .zir_body_inst = try block.trackZir(func_inst),
             .lbrace_line = src_locs.lbrace_line,
             .rbrace_line = src_locs.rbrace_line,
             .lbrace_column = @as(u16, @truncate(src_locs.columns)),
@@ -13787,18 +13805,18 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
     const tracy = trace(@src());
     defer tracy.end();
 
-    const mod = sema.mod;
+    const zcu = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
     const operand_src = block.tokenOffset(inst_data.src_tok);
     const operand = inst_data.get(sema.code);
 
-    const result = mod.importFile(block.getFileScope(mod), operand) catch |err| switch (err) {
+    const result = zcu.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) {
         error.ImportOutsideModulePath => {
             return sema.fail(block, operand_src, "import of file outside module path: '{s}'", .{operand});
         },
         error.ModuleNotFound => {
             return sema.fail(block, operand_src, "no module named '{s}' available within module {s}", .{
-                operand, block.getFileScope(mod).mod.fully_qualified_name,
+                operand, block.getFileScope(zcu).mod.fully_qualified_name,
             });
         },
         else => {
@@ -13807,8 +13825,8 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
             return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
         },
     };
-    try mod.ensureFileAnalyzed(result.file);
-    const file_root_decl_index = result.file.root_decl.unwrap().?;
+    try zcu.ensureFileAnalyzed(result.file_index);
+    const file_root_decl_index = zcu.fileRootDecl(result.file_index).unwrap().?;
     return sema.analyzeDeclVal(block, operand_src, file_root_decl_index);
 }
 
@@ -21089,7 +21107,7 @@ fn zirReify(
     const ip = &mod.intern_pool;
     const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
     const extra = sema.code.extraData(Zir.Inst.Reify, extended.operand).data;
-    const tracked_inst = try ip.trackZir(gpa, block.getFileScope(mod), inst);
+    const tracked_inst = try block.trackZir(inst);
     const src: LazySrcLoc = .{
         .base_node_inst = tracked_inst,
         .offset = LazySrcLoc.Offset.nodeOffset(0),
@@ -21466,7 +21484,7 @@ fn zirReify(
             const wip_ty = switch (try ip.getOpaqueType(gpa, .{
                 .has_namespace = false,
                 .key = .{ .reified = .{
-                    .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst),
+                    .zir_index = try block.trackZir(inst),
                 } },
             })) {
                 .existing => |ty| return Air.internedToRef(ty),
@@ -21660,7 +21678,7 @@ fn reifyEnum(
         .tag_mode = if (is_exhaustive) .explicit else .nonexhaustive,
         .fields_len = fields_len,
         .key = .{ .reified = .{
-            .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst),
+            .zir_index = try block.trackZir(inst),
             .type_hash = hasher.final(),
         } },
     })) {
@@ -21810,7 +21828,7 @@ fn reifyUnion(
         .field_types = &.{}, // set later
         .field_aligns = &.{}, // set later
         .key = .{ .reified = .{
-            .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst),
+            .zir_index = try block.trackZir(inst),
             .type_hash = hasher.final(),
         } },
     })) {
@@ -22062,7 +22080,7 @@ fn reifyStruct(
         .inits_resolved = true,
         .has_namespace = false,
         .key = .{ .reified = .{
-            .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst),
+            .zir_index = try block.trackZir(inst),
             .type_hash = hasher.final(),
         } },
     })) {
@@ -34894,14 +34912,14 @@ pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void {
     _ = try sema.typeRequiresComptime(ty);
 }
 
-fn semaBackingIntType(mod: *Module, struct_type: InternPool.LoadedStructType) CompileError!void {
-    const gpa = mod.gpa;
-    const ip = &mod.intern_pool;
+fn semaBackingIntType(zcu: *Zcu, struct_type: InternPool.LoadedStructType) CompileError!void {
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
 
     const decl_index = struct_type.decl.unwrap().?;
-    const decl = mod.declPtr(decl_index);
+    const decl = zcu.declPtr(decl_index);
 
-    const zir = mod.namespacePtr(struct_type.namespace.unwrap().?).file_scope.zir;
+    const zir = zcu.namespacePtr(struct_type.namespace.unwrap().?).fileScope(zcu).zir;
 
     var analysis_arena = std.heap.ArenaAllocator.init(gpa);
     defer analysis_arena.deinit();
@@ -34910,7 +34928,7 @@ fn semaBackingIntType(mod: *Module, struct_type: InternPool.LoadedStructType) Co
     defer comptime_err_ret_trace.deinit();
 
     var sema: Sema = .{
-        .mod = mod,
+        .mod = zcu,
         .gpa = gpa,
         .arena = analysis_arena.allocator(),
         .code = zir,
@@ -34941,7 +34959,7 @@ fn semaBackingIntType(mod: *Module, struct_type: InternPool.LoadedStructType) Co
         var accumulator: u64 = 0;
         for (0..struct_type.field_types.len) |i| {
             const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[i]);
-            accumulator += try field_ty.bitSizeAdvanced(mod, .sema);
+            accumulator += try field_ty.bitSizeAdvanced(zcu, .sema);
         }
         break :blk accumulator;
     };
@@ -34987,7 +35005,7 @@ fn semaBackingIntType(mod: *Module, struct_type: InternPool.LoadedStructType) Co
         if (fields_bit_sum > std.math.maxInt(u16)) {
             return sema.fail(&block, block.nodeOffset(0), "size of packed struct '{d}' exceeds maximum bit width of 65535", .{fields_bit_sum});
         }
-        const backing_int_ty = try mod.intType(.unsigned, @intCast(fields_bit_sum));
+        const backing_int_ty = try zcu.intType(.unsigned, @intCast(fields_bit_sum));
         struct_type.backingIntType(ip).* = backing_int_ty.toIntern();
     }
 
@@ -35597,23 +35615,23 @@ fn structZirInfo(zir: Zir, zir_index: Zir.Inst.Index) struct {
 }
 
 fn semaStructFields(
-    mod: *Module,
+    zcu: *Zcu,
     arena: Allocator,
     struct_type: InternPool.LoadedStructType,
 ) CompileError!void {
-    const gpa = mod.gpa;
-    const ip = &mod.intern_pool;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
     const decl_index = struct_type.decl.unwrap() orelse return;
-    const decl = mod.declPtr(decl_index);
+    const decl = zcu.declPtr(decl_index);
     const namespace_index = struct_type.namespace.unwrap() orelse decl.src_namespace;
-    const zir = mod.namespacePtr(namespace_index).file_scope.zir;
+    const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir;
     const zir_index = struct_type.zir_index.unwrap().?.resolve(ip);
 
     const fields_len, const small, var extra_index = structZirInfo(zir, zir_index);
 
     if (fields_len == 0) switch (struct_type.layout) {
         .@"packed" => {
-            try semaBackingIntType(mod, struct_type);
+            try semaBackingIntType(zcu, struct_type);
             return;
         },
         .auto, .@"extern" => {
@@ -35627,7 +35645,7 @@ fn semaStructFields(
     defer comptime_err_ret_trace.deinit();
 
     var sema: Sema = .{
-        .mod = mod,
+        .mod = zcu,
         .gpa = gpa,
         .arena = arena,
         .code = zir,
@@ -35749,7 +35767,7 @@ fn semaStructFields(
 
         struct_type.field_types.get(ip)[field_i] = field_ty.toIntern();
 
-        if (field_ty.zigTypeTag(mod) == .Opaque) {
+        if (field_ty.zigTypeTag(zcu) == .Opaque) {
             const msg = msg: {
                 const msg = try sema.errMsg(ty_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
                 errdefer msg.destroy(sema.gpa);
@@ -35759,7 +35777,7 @@ fn semaStructFields(
             };
             return sema.failWithOwnedErrorMsg(&block_scope, msg);
         }
-        if (field_ty.zigTypeTag(mod) == .NoReturn) {
+        if (field_ty.zigTypeTag(zcu) == .NoReturn) {
             const msg = msg: {
                 const msg = try sema.errMsg(ty_src, "struct fields cannot be 'noreturn'", .{});
                 errdefer msg.destroy(sema.gpa);
@@ -35772,7 +35790,7 @@ fn semaStructFields(
         switch (struct_type.layout) {
             .@"extern" => if (!try sema.validateExternType(field_ty, .struct_field)) {
                 const msg = msg: {
-                    const msg = try sema.errMsg(ty_src, "extern structs cannot contain fields of type '{}'", .{field_ty.fmt(mod)});
+                    const msg = try sema.errMsg(ty_src, "extern structs cannot contain fields of type '{}'", .{field_ty.fmt(zcu)});
                     errdefer msg.destroy(sema.gpa);
 
                     try sema.explainWhyTypeIsNotExtern(msg, ty_src, field_ty, .struct_field);
@@ -35784,7 +35802,7 @@ fn semaStructFields(
             },
             .@"packed" => if (!try sema.validatePackedType(field_ty)) {
                 const msg = msg: {
-                    const msg = try sema.errMsg(ty_src, "packed structs cannot contain fields of type '{}'", .{field_ty.fmt(mod)});
+                    const msg = try sema.errMsg(ty_src, "packed structs cannot contain fields of type '{}'", .{field_ty.fmt(zcu)});
                     errdefer msg.destroy(sema.gpa);
 
                     try sema.explainWhyTypeIsNotPacked(msg, ty_src, field_ty);
@@ -35820,19 +35838,19 @@ fn semaStructFields(
 
 // This logic must be kept in sync with `semaStructFields`
 fn semaStructFieldInits(
-    mod: *Module,
+    zcu: *Zcu,
     arena: Allocator,
     struct_type: InternPool.LoadedStructType,
 ) CompileError!void {
-    const gpa = mod.gpa;
-    const ip = &mod.intern_pool;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
 
     assert(!struct_type.haveFieldInits(ip));
 
     const decl_index = struct_type.decl.unwrap() orelse return;
-    const decl = mod.declPtr(decl_index);
+    const decl = zcu.declPtr(decl_index);
     const namespace_index = struct_type.namespace.unwrap() orelse decl.src_namespace;
-    const zir = mod.namespacePtr(namespace_index).file_scope.zir;
+    const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir;
     const zir_index = struct_type.zir_index.unwrap().?.resolve(ip);
     const fields_len, const small, var extra_index = structZirInfo(zir, zir_index);
 
@@ -35840,7 +35858,7 @@ fn semaStructFieldInits(
     defer comptime_err_ret_trace.deinit();
 
     var sema: Sema = .{
-        .mod = mod,
+        .mod = zcu,
         .gpa = gpa,
         .arena = arena,
         .code = zir,
@@ -35950,7 +35968,7 @@ fn semaStructFieldInits(
                 });
             };
 
-            if (default_val.canMutateComptimeVarState(mod)) {
+            if (default_val.canMutateComptimeVarState(zcu)) {
                 return sema.fail(&block_scope, init_src, "field default value contains reference to comptime-mutable memory", .{});
             }
             struct_type.field_inits.get(ip)[field_i] = default_val.toIntern();
@@ -35960,14 +35978,14 @@ fn semaStructFieldInits(
     try sema.flushExports();
 }
 
-fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.LoadedUnionType) CompileError!void {
+fn semaUnionFields(zcu: *Zcu, arena: Allocator, union_type: InternPool.LoadedUnionType) CompileError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = mod.gpa;
-    const ip = &mod.intern_pool;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
     const decl_index = union_type.decl;
-    const zir = mod.namespacePtr(union_type.namespace.unwrap().?).file_scope.zir;
+    const zir = zcu.namespacePtr(union_type.namespace.unwrap().?).fileScope(zcu).zir;
     const zir_index = union_type.zir_index.resolve(ip);
     const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended;
     assert(extended.opcode == .union_decl);
@@ -36011,13 +36029,13 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
     const body = zir.bodySlice(extra_index, body_len);
     extra_index += body.len;
 
-    const decl = mod.declPtr(decl_index);
+    const decl = zcu.declPtr(decl_index);
 
     var comptime_err_ret_trace = std.ArrayList(LazySrcLoc).init(gpa);
     defer comptime_err_ret_trace.deinit();
 
     var sema: Sema = .{
-        .mod = mod,
+        .mod = zcu,
         .gpa = gpa,
         .arena = arena,
         .code = zir,
@@ -36063,18 +36081,18 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
         if (small.auto_enum_tag) {
             // The provided type is an integer type and we must construct the enum tag type here.
             int_tag_ty = provided_ty;
-            if (int_tag_ty.zigTypeTag(mod) != .Int and int_tag_ty.zigTypeTag(mod) != .ComptimeInt) {
-                return sema.fail(&block_scope, tag_ty_src, "expected integer tag type, found '{}'", .{int_tag_ty.fmt(mod)});
+            if (int_tag_ty.zigTypeTag(zcu) != .Int and int_tag_ty.zigTypeTag(zcu) != .ComptimeInt) {
+                return sema.fail(&block_scope, tag_ty_src, "expected integer tag type, found '{}'", .{int_tag_ty.fmt(zcu)});
             }
 
             if (fields_len > 0) {
-                const field_count_val = try mod.intValue(Type.comptime_int, fields_len - 1);
+                const field_count_val = try zcu.intValue(Type.comptime_int, fields_len - 1);
                 if (!(try sema.intFitsInType(field_count_val, int_tag_ty, null))) {
                     const msg = msg: {
                         const msg = try sema.errMsg(tag_ty_src, "specified integer tag type cannot represent every field", .{});
                         errdefer msg.destroy(sema.gpa);
                         try sema.errNote(tag_ty_src, msg, "type '{}' cannot fit values in range 0...{d}", .{
-                            int_tag_ty.fmt(mod),
+                            int_tag_ty.fmt(zcu),
                             fields_len - 1,
                         });
                         break :msg msg;
@@ -36089,7 +36107,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
             union_type.tagTypePtr(ip).* = provided_ty.toIntern();
             const enum_type = switch (ip.indexToKey(provided_ty.toIntern())) {
                 .enum_type => ip.loadEnumType(provided_ty.toIntern()),
-                else => return sema.fail(&block_scope, tag_ty_src, "expected enum tag type, found '{}'", .{provided_ty.fmt(mod)}),
+                else => return sema.fail(&block_scope, tag_ty_src, "expected enum tag type, found '{}'", .{provided_ty.fmt(zcu)}),
             };
             // The fields of the union must match the enum exactly.
             // A flag per field is used to check for missing and extraneous fields.
@@ -36185,7 +36203,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
                 const val = if (last_tag_val) |val|
                     try sema.intAdd(val, Value.one_comptime_int, int_tag_ty, undefined)
                 else
-                    try mod.intValue(int_tag_ty, 0);
+                    try zcu.intValue(int_tag_ty, 0);
                 last_tag_val = val;
 
                 break :blk val;
@@ -36197,7 +36215,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
                     .offset = .{ .container_field_value = @intCast(gop.index) },
                 };
                 const msg = msg: {
-                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{enum_tag_val.fmtValue(mod, &sema)});
+                    const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{enum_tag_val.fmtValue(zcu, &sema)});
                     errdefer msg.destroy(gpa);
                     try sema.errNote(other_value_src, msg, "other occurrence here", .{});
                     break :msg msg;
@@ -36227,7 +36245,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
             const tag_info = ip.loadEnumType(union_type.tagTypePtr(ip).*);
             const enum_index = tag_info.nameIndex(ip, field_name) orelse {
                 return sema.fail(&block_scope, name_src, "no field named '{}' in enum '{}'", .{
-                    field_name.fmt(ip), Type.fromInterned(union_type.tagTypePtr(ip).*).fmt(mod),
+                    field_name.fmt(ip), Type.fromInterned(union_type.tagTypePtr(ip).*).fmt(zcu),
                 });
             };
 
@@ -36254,7 +36272,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
             }
         }
 
-        if (field_ty.zigTypeTag(mod) == .Opaque) {
+        if (field_ty.zigTypeTag(zcu) == .Opaque) {
             const msg = msg: {
                 const msg = try sema.errMsg(type_src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{});
                 errdefer msg.destroy(sema.gpa);
@@ -36269,7 +36287,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
             !try sema.validateExternType(field_ty, .union_field))
         {
             const msg = msg: {
-                const msg = try sema.errMsg(type_src, "extern unions cannot contain fields of type '{}'", .{field_ty.fmt(mod)});
+                const msg = try sema.errMsg(type_src, "extern unions cannot contain fields of type '{}'", .{field_ty.fmt(zcu)});
                 errdefer msg.destroy(sema.gpa);
 
                 try sema.explainWhyTypeIsNotExtern(msg, type_src, field_ty, .union_field);
@@ -36280,7 +36298,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
             return sema.failWithOwnedErrorMsg(&block_scope, msg);
         } else if (layout == .@"packed" and !try sema.validatePackedType(field_ty)) {
             const msg = msg: {
-                const msg = try sema.errMsg(type_src, "packed unions cannot contain fields of type '{}'", .{field_ty.fmt(mod)});
+                const msg = try sema.errMsg(type_src, "packed unions cannot contain fields of type '{}'", .{field_ty.fmt(zcu)});
                 errdefer msg.destroy(sema.gpa);
 
                 try sema.explainWhyTypeIsNotPacked(msg, type_src, field_ty);
@@ -36325,10 +36343,10 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
             return sema.failWithOwnedErrorMsg(&block_scope, msg);
         }
     } else if (enum_field_vals.count() > 0) {
-        const enum_ty = try sema.generateUnionTagTypeNumbered(&block_scope, enum_field_names, enum_field_vals.keys(), mod.declPtr(union_type.decl));
+        const enum_ty = try sema.generateUnionTagTypeNumbered(&block_scope, enum_field_names, enum_field_vals.keys(), zcu.declPtr(union_type.decl));
         union_type.tagTypePtr(ip).* = enum_ty;
     } else {
-        const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, mod.declPtr(union_type.decl));
+        const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, zcu.declPtr(union_type.decl));
         union_type.tagTypePtr(ip).* = enum_ty;
     }
 
src/Type.zig
@@ -3455,7 +3455,7 @@ pub fn typeDeclSrcLine(ty: Type, zcu: *const Zcu) ?u32 {
         else => return null,
     };
     const info = tracked.resolveFull(&zcu.intern_pool);
-    const file = zcu.import_table.values()[zcu.path_digest_map.getIndex(info.path_digest).?];
+    const file = zcu.import_table.values()[zcu.files.getIndex(info.path_digest).?];
     assert(file.zir_loaded);
     const zir = file.zir;
     const inst = zir.instructions.get(@intFromEnum(info.inst));
src/Zcu.zig
@@ -72,6 +72,7 @@ codegen_prog_node: std.Progress.Node = undefined,
 global_zir_cache: Compilation.Directory,
 /// Used by AstGen worker to load and store ZIR cache.
 local_zir_cache: Compilation.Directory,
+
 /// This is where all `Export` values are stored. Not all values here are necessarily valid exports;
 /// to enumerate all exports, `single_exports` and `multi_exports` must be consulted.
 all_exports: ArrayListUnmanaged(Export) = .{},
@@ -88,14 +89,35 @@ multi_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
     index: u32,
     len: u32,
 }) = .{},
-/// The set of all the Zig source files in the Module. We keep track of this in order
-/// to iterate over it and check which source files have been modified on the file system when
-/// an update is requested, as well as to cache `@import` results.
+
+/// The set of all the Zig source files in the Zig Compilation Unit. Tracked in
+/// order to iterate over it and check which source files have been modified on
+/// the file system when an update is requested, as well as to cache `@import`
+/// results.
+///
 /// Keys are fully resolved file paths. This table owns the keys and values.
+///
+/// Protected by Compilation's mutex.
+///
+/// Not serialized. This state is reconstructed during the first call to
+/// `Compilation.update` of the process for a given `Compilation`.
+///
+/// Indexes correspond 1:1 to `files`.
 import_table: std.StringArrayHashMapUnmanaged(*File) = .{},
-/// This acts as a map from `path_digest` to the corresponding `File`.
-/// The value is omitted, as keys are ordered identically to `import_table`.
-path_digest_map: std.AutoArrayHashMapUnmanaged(Cache.BinDigest, void) = .{},
+
+/// Elements are ordered identically to `import_table`.
+///
+/// Unlike `import_table`, this data is serialized as part of incremental
+/// compilation state.
+///
+/// Key is the hash of the path to this file, used to store
+/// `InternPool.TrackedInst`.
+///
+/// Value is the `Decl` of the struct that represents this `File`.
+///
+/// Protected by Compilation's mutex.
+files: std.AutoArrayHashMapUnmanaged(Cache.BinDigest, Decl.OptionalIndex) = .{},
+
 /// The set of all the files which have been loaded with `@embedFile` in the Module.
 /// We keep track of this in order to iterate over it and check which files have been
 /// modified on the file system when an update is requested, as well as to cache
@@ -387,8 +409,8 @@ pub const Decl = struct {
         anon,
     };
 
-    const Index = InternPool.DeclIndex;
-    const OptionalIndex = InternPool.OptionalDeclIndex;
+    pub const Index = InternPool.DeclIndex;
+    pub const OptionalIndex = InternPool.OptionalDeclIndex;
 
     pub fn zirBodies(decl: Decl, zcu: *Zcu) Zir.Inst.Declaration.Bodies {
         const zir = decl.getFileScope(zcu).zir;
@@ -490,6 +512,10 @@ pub const Decl = struct {
     }
 
     pub fn getFileScope(decl: Decl, zcu: *Zcu) *File {
+        return zcu.fileByIndex(getFileScopeIndex(decl, zcu));
+    }
+
+    pub fn getFileScopeIndex(decl: Decl, zcu: *Zcu) File.Index {
         return zcu.namespacePtr(decl.src_namespace).file_scope;
     }
 
@@ -558,7 +584,7 @@ pub const Decl = struct {
             break :inst generic_owner_decl.zir_decl_index.unwrap().?;
         };
         const info = tracked.resolveFull(&zcu.intern_pool);
-        const file = zcu.import_table.values()[zcu.path_digest_map.getIndex(info.path_digest).?];
+        const file = zcu.import_table.values()[zcu.files.getIndex(info.path_digest).?];
         assert(file.zir_loaded);
         const zir = file.zir;
         const inst = zir.instructions.get(@intFromEnum(info.inst));
@@ -595,7 +621,7 @@ pub const DeclAdapter = struct {
 /// The container that structs, enums, unions, and opaques have.
 pub const Namespace = struct {
     parent: OptionalIndex,
-    file_scope: *File,
+    file_scope: File.Index,
     /// Will be a struct, enum, union, or opaque.
     decl_index: Decl.Index,
     /// Direct children of the namespace.
@@ -627,6 +653,10 @@ pub const Namespace = struct {
         }
     };
 
+    pub fn fileScope(ns: Namespace, zcu: *Zcu) *File {
+        return zcu.fileByIndex(ns.file_scope);
+    }
+
     // This renders e.g. "std.fs.Dir.OpenOptions"
     pub fn renderFullyQualifiedName(
         ns: Namespace,
@@ -641,7 +671,7 @@ pub const Namespace = struct {
                 writer,
             );
         } else {
-            try ns.file_scope.renderFullyQualifiedName(writer);
+            try ns.fileScope(zcu).renderFullyQualifiedName(writer);
         }
         if (name != .empty) try writer.print(".{}", .{name.fmt(&zcu.intern_pool)});
     }
@@ -661,7 +691,7 @@ pub const Namespace = struct {
             );
             break :sep '.';
         } else sep: {
-            try ns.file_scope.renderFullyQualifiedDebugName(writer);
+            try ns.fileScope(zcu).renderFullyQualifiedDebugName(writer);
             break :sep ':';
         };
         if (name != .empty) try writer.print("{c}{}", .{ sep, name.fmt(&zcu.intern_pool) });
@@ -680,7 +710,7 @@ pub const Namespace = struct {
                 const decl = zcu.declPtr(cur_ns.decl_index);
                 count += decl.name.length(ip) + 1;
                 cur_ns = zcu.namespacePtr(cur_ns.parent.unwrap() orelse {
-                    count += ns.file_scope.sub_file_path.len;
+                    count += ns.fileScope(zcu).sub_file_path.len;
                     break :count count;
                 });
             }
@@ -715,8 +745,6 @@ pub const Namespace = struct {
 };
 
 pub const File = struct {
-    /// The Decl of the struct that represents this File.
-    root_decl: Decl.OptionalIndex,
     status: enum {
         never_loaded,
         retryable_failure,
@@ -744,8 +772,6 @@ pub const File = struct {
     multi_pkg: bool = false,
     /// List of references to this file, used for multi-package errors.
     references: std.ArrayListUnmanaged(File.Reference) = .{},
-    /// The hash of the path to this file, used to store `InternPool.TrackedInst`.
-    path_digest: Cache.BinDigest,
 
     /// The most recent successful ZIR for this file, with no errors.
     /// This is only populated when a previously successful ZIR
@@ -757,7 +783,7 @@ pub const File = struct {
     pub const Reference = union(enum) {
         /// The file is imported directly (i.e. not as a package) with @import.
         import: struct {
-            file: *File,
+            file: File.Index,
             token: Ast.TokenIndex,
         },
         /// The file is the root of a module.
@@ -791,28 +817,6 @@ pub const File = struct {
         }
     }
 
-    pub fn deinit(file: *File, mod: *Module) void {
-        const gpa = mod.gpa;
-        const is_builtin = file.mod.isBuiltin();
-        log.debug("deinit File {s}", .{file.sub_file_path});
-        if (is_builtin) {
-            file.unloadTree(gpa);
-            file.unloadZir(gpa);
-        } else {
-            gpa.free(file.sub_file_path);
-            file.unload(gpa);
-        }
-        file.references.deinit(gpa);
-        if (file.root_decl.unwrap()) |root_decl| {
-            mod.destroyDecl(root_decl);
-        }
-        if (file.prev_zir) |prev_zir| {
-            prev_zir.deinit(gpa);
-            gpa.destroy(prev_zir);
-        }
-        file.* = undefined;
-    }
-
     pub const Source = struct {
         bytes: [:0]const u8,
         stat: Cache.File.Stat,
@@ -865,13 +869,6 @@ pub const File = struct {
         return &file.tree;
     }
 
-    pub fn destroy(file: *File, mod: *Module) void {
-        const gpa = mod.gpa;
-        const is_builtin = file.mod.isBuiltin();
-        file.deinit(mod);
-        if (!is_builtin) gpa.destroy(file);
-    }
-
     pub fn renderFullyQualifiedName(file: File, writer: anytype) !void {
         // Convert all the slashes into dots and truncate the extension.
         const ext = std.fs.path.extension(file.sub_file_path);
@@ -937,7 +934,7 @@ pub const File = struct {
         }
 
         const mod = switch (ref) {
-            .import => |import| import.file.mod,
+            .import => |import| zcu.fileByIndex(import.file).mod,
             .root => |mod| mod,
         };
         if (mod != file.mod) file.multi_pkg = true;
@@ -971,6 +968,10 @@ pub const File = struct {
             }
         }
     }
+
+    pub const Index = enum(u32) {
+        _,
+    };
 };
 
 pub const EmbedFile = struct {
@@ -2355,7 +2356,7 @@ pub const LazySrcLoc = struct {
             break :inst .{ info.path_digest, info.inst };
         };
         const file = file: {
-            const index = zcu.path_digest_map.getIndex(want_path_digest).?;
+            const index = zcu.files.getIndex(want_path_digest).?;
             break :file zcu.import_table.values()[index];
         };
         assert(file.zir_loaded);
@@ -2423,11 +2424,12 @@ pub fn deinit(zcu: *Zcu) void {
     for (zcu.import_table.keys()) |key| {
         gpa.free(key);
     }
-    for (zcu.import_table.values()) |value| {
-        value.destroy(zcu);
+    for (0..zcu.import_table.entries.len) |file_index_usize| {
+        const file_index: File.Index = @enumFromInt(file_index_usize);
+        zcu.destroyFile(file_index);
     }
     zcu.import_table.deinit(gpa);
-    zcu.path_digest_map.deinit(gpa);
+    zcu.files.deinit(gpa);
 
     for (zcu.embed_table.keys(), zcu.embed_table.values()) |path, embed_file| {
         gpa.free(path);
@@ -2531,6 +2533,37 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {
     }
 }
 
+fn deinitFile(zcu: *Zcu, file_index: File.Index) void {
+    const gpa = zcu.gpa;
+    const file = zcu.fileByIndex(file_index);
+    const is_builtin = file.mod.isBuiltin();
+    log.debug("deinit File {s}", .{file.sub_file_path});
+    if (is_builtin) {
+        file.unloadTree(gpa);
+        file.unloadZir(gpa);
+    } else {
+        gpa.free(file.sub_file_path);
+        file.unload(gpa);
+    }
+    file.references.deinit(gpa);
+    if (zcu.fileRootDecl(file_index).unwrap()) |root_decl| {
+        zcu.destroyDecl(root_decl);
+    }
+    if (file.prev_zir) |prev_zir| {
+        prev_zir.deinit(gpa);
+        gpa.destroy(prev_zir);
+    }
+    file.* = undefined;
+}
+
+pub fn destroyFile(zcu: *Zcu, file_index: File.Index) void {
+    const gpa = zcu.gpa;
+    const file = zcu.fileByIndex(file_index);
+    const is_builtin = file.mod.isBuiltin();
+    zcu.deinitFile(file_index);
+    if (!is_builtin) gpa.destroy(file);
+}
+
 pub fn declPtr(mod: *Module, index: Decl.Index) *Decl {
     return mod.intern_pool.declPtr(index);
 }
@@ -2563,14 +2596,14 @@ comptime {
     }
 }
 
-pub fn astGenFile(mod: *Module, file: *File) !void {
+pub fn astGenFile(zcu: *Zcu, file: *File, path_digest: Cache.BinDigest, opt_root_decl: Zcu.Decl.OptionalIndex) !void {
     assert(!file.mod.isBuiltin());
 
     const tracy = trace(@src());
     defer tracy.end();
 
-    const comp = mod.comp;
-    const gpa = mod.gpa;
+    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, .{});
@@ -2578,17 +2611,9 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
 
     const stat = try source_file.stat();
 
-    const want_local_cache = file.mod == mod.main_mod;
-    const hex_digest = hex: {
-        var hex: Cache.HexDigest = undefined;
-        _ = std.fmt.bufPrint(
-            &hex,
-            "{s}",
-            .{std.fmt.fmtSliceHexLower(&file.path_digest)},
-        ) catch unreachable;
-        break :hex hex;
-    };
-    const cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache;
+    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.
@@ -2688,7 +2713,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
                 {
                     comp.mutex.lock();
                     defer comp.mutex.unlock();
-                    try mod.failed_files.putNoClobber(gpa, file, null);
+                    try zcu.failed_files.putNoClobber(gpa, file, null);
                 }
                 file.status = .astgen_failure;
                 return error.AnalysisFail;
@@ -2712,7 +2737,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
         else => |e| return e,
     };
 
-    mod.lockAndClearFileCompileError(file);
+    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
@@ -2818,27 +2843,27 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
         {
             comp.mutex.lock();
             defer comp.mutex.unlock();
-            try mod.failed_files.putNoClobber(gpa, file, null);
+            try zcu.failed_files.putNoClobber(gpa, file, null);
         }
         file.status = .astgen_failure;
         return error.AnalysisFail;
     }
 
     if (file.prev_zir) |prev_zir| {
-        try updateZirRefs(mod, file, prev_zir.*);
+        try updateZirRefs(zcu, file, prev_zir.*, path_digest);
         // No need to keep previous ZIR.
         prev_zir.deinit(gpa);
         gpa.destroy(prev_zir);
         file.prev_zir = null;
     }
 
-    if (file.root_decl.unwrap()) |root_decl| {
+    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 mod.outdated_file_root.put(gpa, root_decl, {});
+        try zcu.outdated_file_root.put(gpa, root_decl, {});
     }
 }
 
@@ -2914,7 +2939,7 @@ fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File)
 
 /// 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, old_zir: Zir) !void {
+fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir, path_digest: Cache.BinDigest) !void {
     const gpa = zcu.gpa;
     const new_zir = file.zir;
 
@@ -2930,7 +2955,7 @@ fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {
     // 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 (!std.mem.eql(u8, &ti.path_digest, &file.path_digest)) continue;
+        if (!std.mem.eql(u8, &ti.path_digest, &path_digest)) continue;
         const old_inst = ti.inst;
         ti.inst = inst_map.get(ti.inst) orelse {
             // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
@@ -3378,11 +3403,11 @@ pub fn mapOldZirToNew(
 }
 
 /// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.
-pub fn ensureFileAnalyzed(zcu: *Zcu, file: *File) SemaError!void {
-    if (file.root_decl.unwrap()) |existing_root| {
+pub fn ensureFileAnalyzed(zcu: *Zcu, file_index: File.Index) SemaError!void {
+    if (zcu.fileRootDecl(file_index).unwrap()) |existing_root| {
         return zcu.ensureDeclAnalyzed(existing_root);
     } else {
-        return zcu.semaFile(file);
+        return zcu.semaFile(file_index);
     }
 }
 
@@ -3455,7 +3480,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
         }
 
         if (mod.declIsRoot(decl_index)) {
-            const changed = try mod.semaFileUpdate(decl.getFileScope(mod), decl_was_outdated);
+            const changed = try mod.semaFileUpdate(decl.getFileScopeIndex(mod), decl_was_outdated);
             break :blk .{
                 .invalidate_decl_val = changed,
                 .invalidate_decl_ref = changed,
@@ -3787,17 +3812,23 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index)
     func.analysis(ip).state = .queued;
 }
 
-/// https://github.com/ziglang/zig/issues/14307
-pub fn semaPkg(mod: *Module, pkg: *Package.Module) !void {
-    const file = (try mod.importPkg(pkg)).file;
-    if (file.root_decl == .none) {
-        return mod.semaFile(file);
+pub fn semaPkg(zcu: *Zcu, pkg: *Package.Module) !void {
+    const import_file_result = try zcu.importPkg(pkg);
+    const root_decl_index = zcu.fileRootDecl(import_file_result.file_index);
+    if (root_decl_index == .none) {
+        return zcu.semaFile(import_file_result.file_index);
     }
 }
 
-fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespace.Index, file: *File) Allocator.Error!InternPool.Index {
+fn getFileRootStruct(
+    zcu: *Zcu,
+    decl_index: Decl.Index,
+    namespace_index: Namespace.Index,
+    file_index: File.Index,
+) Allocator.Error!InternPool.Index {
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
+    const file = zcu.fileByIndex(file_index);
     const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
     assert(extended.opcode == .struct_decl);
     const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
@@ -3818,7 +3849,7 @@ fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespa
     const decls = file.zir.bodySlice(extra_index, decls_len);
     extra_index += decls_len;
 
-    const tracked_inst = try ip.trackZir(gpa, file, .main_struct_inst);
+    const tracked_inst = try ip.trackZir(gpa, zcu.filePathDigest(file_index), .main_struct_inst);
     const wip_ty = switch (try ip.getStructType(gpa, .{
         .layout = .auto,
         .fields_len = fields_len,
@@ -3863,8 +3894,9 @@ fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespa
 /// If `type_outdated`, the struct type itself is considered outdated and is
 /// reconstructed at a new InternPool index. Otherwise, the namespace is just
 /// re-analyzed. Returns whether the decl's tyval was invalidated.
-fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
-    const decl = zcu.declPtr(file.root_decl.unwrap().?);
+fn semaFileUpdate(zcu: *Zcu, file_index: File.Index, type_outdated: bool) SemaError!bool {
+    const file = zcu.fileByIndex(file_index);
+    const decl = zcu.declPtr(zcu.fileRootDecl(file_index).unwrap().?);
 
     log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{
         file.mod.fully_qualified_name,
@@ -3883,7 +3915,8 @@ fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
 
     if (decl.analysis == .file_failure) {
         // No struct type currently exists. Create one!
-        _ = try zcu.getFileRootStruct(file.root_decl.unwrap().?, decl.src_namespace, file);
+        const root_decl = zcu.fileRootDecl(file_index);
+        _ = try zcu.getFileRootStruct(root_decl.unwrap().?, decl.src_namespace, file_index);
         return true;
     }
 
@@ -3892,10 +3925,13 @@ fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
 
     if (type_outdated) {
         // Invalidate the existing type, reusing the decl and namespace.
-        zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{ .decl = file.root_decl.unwrap().? }));
+        const file_root_decl = zcu.fileRootDecl(file_index).unwrap().?;
+        zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{
+            .decl = file_root_decl,
+        }));
         zcu.intern_pool.remove(decl.val.toIntern());
         decl.val = undefined;
-        _ = try zcu.getFileRootStruct(file.root_decl.unwrap().?, decl.src_namespace, file);
+        _ = try zcu.getFileRootStruct(file_root_decl, decl.src_namespace, file_index);
         return true;
     }
 
@@ -3923,35 +3959,36 @@ fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
 
 /// Regardless of the file status, will create a `Decl` if none exists so that we can track
 /// dependencies and re-analyze when the file becomes outdated.
-fn semaFile(mod: *Module, file: *File) SemaError!void {
+fn semaFile(zcu: *Zcu, file_index: File.Index) SemaError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    assert(file.root_decl == .none);
+    const file = zcu.fileByIndex(file_index);
+    assert(zcu.fileRootDecl(file_index) == .none);
 
-    const gpa = mod.gpa;
-    log.debug("semaFile mod={s} sub_file_path={s}", .{
+    const gpa = zcu.gpa;
+    log.debug("semaFile zcu={s} sub_file_path={s}", .{
         file.mod.fully_qualified_name, file.sub_file_path,
     });
 
     // Because these three things each reference each other, `undefined`
     // placeholders are used before being set after the struct type gains an
     // InternPool index.
-    const new_namespace_index = try mod.createNamespace(.{
+    const new_namespace_index = try zcu.createNamespace(.{
         .parent = .none,
         .decl_index = undefined,
-        .file_scope = file,
+        .file_scope = file_index,
     });
-    errdefer mod.destroyNamespace(new_namespace_index);
+    errdefer zcu.destroyNamespace(new_namespace_index);
 
-    const new_decl_index = try mod.allocateNewDecl(new_namespace_index);
-    const new_decl = mod.declPtr(new_decl_index);
+    const new_decl_index = try zcu.allocateNewDecl(new_namespace_index);
+    const new_decl = zcu.declPtr(new_decl_index);
     errdefer @panic("TODO error handling");
 
-    file.root_decl = new_decl_index.toOptional();
-    mod.namespacePtr(new_namespace_index).decl_index = new_decl_index;
+    zcu.setFileRootDecl(file_index, new_decl_index.toOptional());
+    zcu.namespacePtr(new_namespace_index).decl_index = new_decl_index;
 
-    new_decl.name = try file.fullyQualifiedName(mod);
+    new_decl.name = try file.fullyQualifiedName(zcu);
     new_decl.name_fully_qualified = true;
     new_decl.is_pub = true;
     new_decl.is_exported = false;
@@ -3965,13 +4002,13 @@ fn semaFile(mod: *Module, file: *File) SemaError!void {
     }
     assert(file.zir_loaded);
 
-    const struct_ty = try mod.getFileRootStruct(new_decl_index, new_namespace_index, file);
-    errdefer mod.intern_pool.remove(struct_ty);
+    const struct_ty = try zcu.getFileRootStruct(new_decl_index, new_namespace_index, file_index);
+    errdefer zcu.intern_pool.remove(struct_ty);
 
-    switch (mod.comp.cache_use) {
+    switch (zcu.comp.cache_use) {
         .whole => |whole| if (whole.cache_manifest) |man| {
             const source = file.getSource(gpa) catch |err| {
-                try reportRetryableFileError(mod, file, "unable to load source: {s}", .{@errorName(err)});
+                try reportRetryableFileError(zcu, file_index, "unable to load source: {s}", .{@errorName(err)});
                 return error.AnalysisFail;
             };
 
@@ -3980,7 +4017,7 @@ fn semaFile(mod: *Module, file: *File) SemaError!void {
                 file.mod.root.sub_path,
                 file.sub_file_path,
             }) catch |err| {
-                try reportRetryableFileError(mod, file, "unable to resolve path: {s}", .{@errorName(err)});
+                try reportRetryableFileError(zcu, file_index, "unable to resolve path: {s}", .{@errorName(err)});
                 return error.AnalysisFail;
             };
             errdefer gpa.free(resolved_path);
@@ -4000,57 +4037,58 @@ const SemaDeclResult = packed struct {
     invalidate_decl_ref: bool,
 };
 
-fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
+fn semaDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const decl = mod.declPtr(decl_index);
-    const ip = &mod.intern_pool;
+    const decl = zcu.declPtr(decl_index);
+    const ip = &zcu.intern_pool;
 
-    if (decl.getFileScope(mod).status != .success_zir) {
+    if (decl.getFileScope(zcu).status != .success_zir) {
         return error.AnalysisFail;
     }
 
-    assert(!mod.declIsRoot(decl_index));
+    assert(!zcu.declIsRoot(decl_index));
 
     if (decl.zir_decl_index == .none and decl.owns_tv) {
         // We are re-analyzing an anonymous owner Decl (for a function or a namespace type).
-        return mod.semaAnonOwnerDecl(decl_index);
+        return zcu.semaAnonOwnerDecl(decl_index);
     }
 
     log.debug("semaDecl '{d}'", .{@intFromEnum(decl_index)});
-    log.debug("decl name '{}'", .{(try decl.fullyQualifiedName(mod)).fmt(ip)});
+    log.debug("decl name '{}'", .{(try decl.fullyQualifiedName(zcu)).fmt(ip)});
     defer blk: {
-        log.debug("finish decl name '{}'", .{(decl.fullyQualifiedName(mod) catch break :blk).fmt(ip)});
+        log.debug("finish decl name '{}'", .{(decl.fullyQualifiedName(zcu) catch break :blk).fmt(ip)});
     }
 
     const old_has_tv = decl.has_tv;
     // The following values are ignored if `!old_has_tv`
-    const old_ty = if (old_has_tv) decl.typeOf(mod) else undefined;
+    const old_ty = if (old_has_tv) decl.typeOf(zcu) else undefined;
     const old_val = decl.val;
     const old_align = decl.alignment;
     const old_linksection = decl.@"linksection";
     const old_addrspace = decl.@"addrspace";
-    const old_is_inline = if (decl.getOwnedFunction(mod)) |prev_func|
+    const old_is_inline = if (decl.getOwnedFunction(zcu)) |prev_func|
         prev_func.analysis(ip).state == .inline_only
     else
         false;
 
     const decl_inst = decl.zir_decl_index.unwrap().?.resolve(ip);
 
-    const gpa = mod.gpa;
-    const zir = decl.getFileScope(mod).zir;
+    const gpa = zcu.gpa;
+    const zir = decl.getFileScope(zcu).zir;
 
     const builtin_type_target_index: InternPool.Index = ip_index: {
-        const std_mod = mod.std_mod;
-        if (decl.getFileScope(mod).mod != std_mod) break :ip_index .none;
+        const std_mod = zcu.std_mod;
+        if (decl.getFileScope(zcu).mod != std_mod) break :ip_index .none;
         // We're in the std module.
-        const std_file = (try mod.importPkg(std_mod)).file;
-        const std_decl = mod.declPtr(std_file.root_decl.unwrap().?);
-        const std_namespace = std_decl.getInnerNamespace(mod).?;
+        const std_file_imported = try zcu.importPkg(std_mod);
+        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_decl = mod.declPtr(std_namespace.decls.getKeyAdapted(builtin_str, DeclAdapter{ .zcu = mod }) orelse break :ip_index .none);
-        const builtin_namespace = builtin_decl.getInnerNamespaceIndex(mod).unwrap() orelse break :ip_index .none;
+        const builtin_decl = zcu.declPtr(std_namespace.decls.getKeyAdapted(builtin_str, 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;
         // We're in builtin.zig. This could be a builtin we need to add to a specific InternPool index.
         for ([_][]const u8{
@@ -4083,7 +4121,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
         break :ip_index .none;
     };
 
-    mod.intern_pool.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .decl = decl_index }));
+    zcu.intern_pool.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .decl = decl_index }));
 
     decl.analysis = .in_progress;
 
@@ -4094,7 +4132,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
     defer comptime_err_ret_trace.deinit();
 
     var sema: Sema = .{
-        .mod = mod,
+        .mod = zcu,
         .gpa = gpa,
         .arena = analysis_arena.allocator(),
         .code = zir,
@@ -4112,8 +4150,8 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
 
     // Every Decl (other than file root Decls, which do not have a ZIR index) has a dependency on its own source.
     try sema.declareDependency(.{ .src_hash = try ip.trackZir(
-        sema.gpa,
-        decl.getFileScope(mod),
+        gpa,
+        zcu.filePathDigest(decl.getFileScopeIndex(zcu)),
         decl_inst,
     ) });
 
@@ -4129,7 +4167,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
     };
     defer block_scope.instructions.deinit(gpa);
 
-    const decl_bodies = decl.zirBodies(mod);
+    const decl_bodies = decl.zirBodies(zcu);
 
     const result_ref = try sema.resolveInlineBody(&block_scope, decl_bodies.value_body, decl_inst);
     // We'll do some other bits with the Sema. Clear the type target index just
@@ -4141,22 +4179,22 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
     const ty_src: LazySrcLoc = block_scope.src(.{ .node_offset_var_decl_ty = 0 });
     const init_src: LazySrcLoc = block_scope.src(.{ .node_offset_var_decl_init = 0 });
     const decl_val = try sema.resolveFinalDeclValue(&block_scope, init_src, result_ref);
-    const decl_ty = decl_val.typeOf(mod);
+    const decl_ty = decl_val.typeOf(zcu);
 
     // Note this resolves the type of the Decl, not the value; if this Decl
     // is a struct, for example, this resolves `type` (which needs no resolution),
     // not the struct itself.
-    try decl_ty.resolveLayout(mod);
+    try decl_ty.resolveLayout(zcu);
 
     if (decl.kind == .@"usingnamespace") {
-        if (!decl_ty.eql(Type.type, mod)) {
+        if (!decl_ty.eql(Type.type, zcu)) {
             return sema.fail(&block_scope, ty_src, "expected type, found {}", .{
-                decl_ty.fmt(mod),
+                decl_ty.fmt(zcu),
             });
         }
         const ty = decl_val.toType();
-        if (ty.getNamespace(mod) == null) {
-            return sema.fail(&block_scope, ty_src, "type {} has no namespace", .{ty.fmt(mod)});
+        if (ty.getNamespace(zcu) == null) {
+            return sema.fail(&block_scope, ty_src, "type {} has no namespace", .{ty.fmt(zcu)});
         }
 
         decl.val = ty.toValue();
@@ -4194,7 +4232,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
             .func => |func| {
                 decl.owns_tv = func.owner_decl == decl_index;
                 queue_linker_work = false;
-                is_inline = decl.owns_tv and decl_ty.fnCallingConvention(mod) == .Inline;
+                is_inline = decl.owns_tv and decl_ty.fnCallingConvention(zcu) == .Inline;
                 is_func = decl.owns_tv;
             },
 
@@ -4246,10 +4284,10 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
     decl.analysis = .complete;
 
     const result: SemaDeclResult = if (old_has_tv) .{
-        .invalidate_decl_val = !decl_ty.eql(old_ty, mod) or
-            !decl.val.eql(old_val, decl_ty, mod) or
+        .invalidate_decl_val = !decl_ty.eql(old_ty, zcu) or
+            !decl.val.eql(old_val, decl_ty, zcu) or
             is_inline != old_is_inline,
-        .invalidate_decl_ref = !decl_ty.eql(old_ty, mod) or
+        .invalidate_decl_ref = !decl_ty.eql(old_ty, zcu) or
             decl.alignment != old_align or
             decl.@"linksection" != old_linksection or
             decl.@"addrspace" != old_addrspace or
@@ -4263,12 +4301,12 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
     if (has_runtime_bits) {
         // Needed for codegen_decl which will call updateDecl and then the
         // codegen backend wants full access to the Decl Type.
-        try decl_ty.resolveFully(mod);
+        try decl_ty.resolveFully(zcu);
 
-        try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
+        try zcu.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
 
-        if (result.invalidate_decl_ref and mod.emit_h != null) {
-            try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });
+        if (result.invalidate_decl_ref and zcu.emit_h != null) {
+            try zcu.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });
         }
     }
 
@@ -4322,6 +4360,7 @@ fn semaAnonOwnerDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult {
 
 pub const ImportFileResult = struct {
     file: *File,
+    file_index: File.Index,
     is_new: bool,
     is_pkg: bool,
 };
@@ -4344,20 +4383,25 @@ pub fn importPkg(zcu: *Zcu, mod: *Package.Module) !ImportFileResult {
     errdefer _ = zcu.import_table.pop();
     if (gop.found_existing) {
         try gop.value_ptr.*.addReference(zcu.*, .{ .root = mod });
-        return ImportFileResult{
+        return .{
             .file = gop.value_ptr.*,
+            .file_index = @enumFromInt(gop.index),
             .is_new = false,
             .is_pkg = true,
         };
     }
 
+    try zcu.files.ensureUnusedCapacity(gpa, 1);
+
     if (mod.builtin_file) |builtin_file| {
         keep_resolved_path = true; // It's now owned by import_table.
         gop.value_ptr.* = builtin_file;
         try builtin_file.addReference(zcu.*, .{ .root = mod });
-        try zcu.path_digest_map.put(gpa, builtin_file.path_digest, {});
+        const path_digest = computePathDigest(zcu, mod, builtin_file.sub_file_path);
+        zcu.files.putAssumeCapacityNoClobber(path_digest, .none);
         return .{
             .file = builtin_file,
+            .file_index = @enumFromInt(zcu.files.entries.len - 1),
             .is_new = false,
             .is_pkg = true,
         };
@@ -4382,43 +4426,36 @@ pub fn importPkg(zcu: *Zcu, mod: *Package.Module) !ImportFileResult {
         .zir = undefined,
         .status = .never_loaded,
         .mod = mod,
-        .root_decl = .none,
-        .path_digest = digest: {
-            const want_local_cache = mod == zcu.main_mod;
-            var path_hash: Cache.HashHelper = .{};
-            path_hash.addBytes(build_options.version);
-            path_hash.add(builtin.zig_backend);
-            if (!want_local_cache) {
-                path_hash.addOptionalBytes(mod.root.root_dir.path);
-                path_hash.addBytes(mod.root.sub_path);
-            }
-            path_hash.addBytes(sub_file_path);
-            var bin: Cache.BinDigest = undefined;
-            path_hash.hasher.final(&bin);
-            break :digest bin;
-        },
     };
+
+    const path_digest = computePathDigest(zcu, mod, sub_file_path);
+
     try new_file.addReference(zcu.*, .{ .root = mod });
-    try zcu.path_digest_map.put(gpa, new_file.path_digest, {});
-    return ImportFileResult{
+    zcu.files.putAssumeCapacityNoClobber(path_digest, .none);
+    return .{
         .file = new_file,
+        .file_index = @enumFromInt(zcu.files.entries.len - 1),
         .is_new = true,
         .is_pkg = true,
     };
 }
 
+/// Called from a worker thread during AstGen.
+/// Also called from Sema during semantic analysis.
 pub fn importFile(
     zcu: *Zcu,
     cur_file: *File,
     import_string: []const u8,
 ) !ImportFileResult {
+    const mod = cur_file.mod;
+
     if (std.mem.eql(u8, import_string, "std")) {
         return zcu.importPkg(zcu.std_mod);
     }
     if (std.mem.eql(u8, import_string, "root")) {
         return zcu.importPkg(zcu.root_mod);
     }
-    if (cur_file.mod.deps.get(import_string)) |pkg| {
+    if (mod.deps.get(import_string)) |pkg| {
         return zcu.importPkg(pkg);
     }
     if (!mem.endsWith(u8, import_string, ".zig")) {
@@ -4430,8 +4467,8 @@ pub fn importFile(
     // an import refers to the same as another, despite different relative paths
     // or differently mapped package names.
     const resolved_path = try std.fs.path.resolve(gpa, &.{
-        cur_file.mod.root.root_dir.path orelse ".",
-        cur_file.mod.root.sub_path,
+        mod.root.root_dir.path orelse ".",
+        mod.root.sub_path,
         cur_file.sub_file_path,
         "..",
         import_string,
@@ -4442,18 +4479,21 @@ pub fn importFile(
 
     const gop = try zcu.import_table.getOrPut(gpa, resolved_path);
     errdefer _ = zcu.import_table.pop();
-    if (gop.found_existing) return ImportFileResult{
+    if (gop.found_existing) return .{
         .file = gop.value_ptr.*,
+        .file_index = @enumFromInt(gop.index),
         .is_new = false,
         .is_pkg = false,
     };
 
+    try zcu.files.ensureUnusedCapacity(gpa, 1);
+
     const new_file = try gpa.create(File);
     errdefer gpa.destroy(new_file);
 
     const resolved_root_path = try std.fs.path.resolve(gpa, &.{
-        cur_file.mod.root.root_dir.path orelse ".",
-        cur_file.mod.root.sub_path,
+        mod.root.root_dir.path orelse ".",
+        mod.root.sub_path,
     });
     defer gpa.free(resolved_root_path);
 
@@ -4484,26 +4524,14 @@ pub fn importFile(
         .tree = undefined,
         .zir = undefined,
         .status = .never_loaded,
-        .mod = cur_file.mod,
-        .root_decl = .none,
-        .path_digest = digest: {
-            const want_local_cache = cur_file.mod == zcu.main_mod;
-            var path_hash: Cache.HashHelper = .{};
-            path_hash.addBytes(build_options.version);
-            path_hash.add(builtin.zig_backend);
-            if (!want_local_cache) {
-                path_hash.addOptionalBytes(cur_file.mod.root.root_dir.path);
-                path_hash.addBytes(cur_file.mod.root.sub_path);
-            }
-            path_hash.addBytes(sub_file_path);
-            var bin: Cache.BinDigest = undefined;
-            path_hash.hasher.final(&bin);
-            break :digest bin;
-        },
+        .mod = mod,
     };
-    try zcu.path_digest_map.put(gpa, new_file.path_digest, {});
-    return ImportFileResult{
+
+    const path_digest = computePathDigest(zcu, mod, sub_file_path);
+    zcu.files.putAssumeCapacityNoClobber(path_digest, .none);
+    return .{
         .file = new_file,
+        .file_index = @enumFromInt(zcu.files.entries.len - 1),
         .is_new = true,
         .is_pkg = false,
     };
@@ -4581,6 +4609,21 @@ pub fn embedFile(
     return newEmbedFile(mod, cur_file.mod, sub_file_path, resolved_path, gop.value_ptr, src_loc);
 }
 
+fn computePathDigest(zcu: *Zcu, mod: *Package.Module, sub_file_path: []const u8) Cache.BinDigest {
+    const want_local_cache = mod == zcu.main_mod;
+    var path_hash: Cache.HashHelper = .{};
+    path_hash.addBytes(build_options.version);
+    path_hash.add(builtin.zig_backend);
+    if (!want_local_cache) {
+        path_hash.addOptionalBytes(mod.root.root_dir.path);
+        path_hash.addBytes(mod.root.sub_path);
+    }
+    path_hash.addBytes(sub_file_path);
+    var bin: Cache.BinDigest = undefined;
+    path_hash.hasher.final(&bin);
+    return bin;
+}
+
 /// https://github.com/ziglang/zig/issues/14307
 fn newEmbedFile(
     mod: *Module,
@@ -4765,7 +4808,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
     const namespace_index = iter.namespace_index;
     const namespace = zcu.namespacePtr(namespace_index);
     const gpa = zcu.gpa;
-    const zir = namespace.file_scope.zir;
+    const zir = namespace.fileScope(zcu).zir;
     const ip = &zcu.intern_pool;
 
     const inst_data = zir.instructions.items(.data)[@intFromEnum(decl_inst)].declaration;
@@ -4848,7 +4891,8 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
         else => {},
     }
 
-    const tracked_inst = try ip.trackZir(gpa, iter.parent_decl.getFileScope(zcu), decl_inst);
+    const parent_file_scope_index = iter.parent_decl.getFileScopeIndex(zcu);
+    const tracked_inst = try ip.trackZir(gpa, zcu.filePathDigest(parent_file_scope_index), decl_inst);
 
     // We create a Decl for it regardless of analysis status.
 
@@ -4878,7 +4922,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
     namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu });
 
     const comp = zcu.comp;
-    const decl_mod = namespace.file_scope.mod;
+    const decl_mod = namespace.fileScope(zcu).mod;
     const want_analysis = declaration.flags.is_export or switch (kind) {
         .anon => unreachable,
         .@"comptime" => true,
@@ -4908,7 +4952,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
         // 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.file_scope.sub_file_path, decl_name.fmt(ip), decl_index,
+                namespace.fileScope(zcu).sub_file_path, decl_name.fmt(ip), decl_index,
             });
             comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = decl_index });
         }
@@ -5512,77 +5556,78 @@ fn handleUpdateExports(
 }
 
 pub fn populateTestFunctions(
-    mod: *Module,
+    zcu: *Zcu,
     main_progress_node: std.Progress.Node,
 ) !void {
-    const gpa = mod.gpa;
-    const ip = &mod.intern_pool;
-    const builtin_mod = mod.root_mod.getBuiltinDependency();
-    const builtin_file = (mod.importPkg(builtin_mod) catch unreachable).file;
-    const root_decl = mod.declPtr(builtin_file.root_decl.unwrap().?);
-    const builtin_namespace = mod.namespacePtr(root_decl.src_namespace);
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
+    const builtin_mod = zcu.root_mod.getBuiltinDependency();
+    const builtin_file_index = (zcu.importPkg(builtin_mod) catch unreachable).file_index;
+    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 decl_index = builtin_namespace.decls.getKeyAdapted(
         test_functions_str,
-        DeclAdapter{ .zcu = mod },
+        DeclAdapter{ .zcu = zcu },
     ).?;
     {
         // We have to call `ensureDeclAnalyzed` here in case `builtin.test_functions`
         // was not referenced by start code.
-        mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
+        zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
         defer {
-            mod.sema_prog_node.end();
-            mod.sema_prog_node = undefined;
+            zcu.sema_prog_node.end();
+            zcu.sema_prog_node = undefined;
         }
-        try mod.ensureDeclAnalyzed(decl_index);
+        try zcu.ensureDeclAnalyzed(decl_index);
     }
 
-    const decl = mod.declPtr(decl_index);
-    const test_fn_ty = decl.typeOf(mod).slicePtrFieldType(mod).childType(mod);
+    const decl = zcu.declPtr(decl_index);
+    const test_fn_ty = decl.typeOf(zcu).slicePtrFieldType(zcu).childType(zcu);
 
     const array_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = array: {
-        // Add mod.test_functions to an array decl then make the test_functions
+        // Add zcu.test_functions to an array decl then make the test_functions
         // decl reference it as a slice.
-        const test_fn_vals = try gpa.alloc(InternPool.Index, mod.test_functions.count());
+        const test_fn_vals = try gpa.alloc(InternPool.Index, zcu.test_functions.count());
         defer gpa.free(test_fn_vals);
 
-        for (test_fn_vals, mod.test_functions.keys()) |*test_fn_val, test_decl_index| {
-            const test_decl = mod.declPtr(test_decl_index);
-            const test_decl_name = try test_decl.fullyQualifiedName(mod);
+        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_len = test_decl_name.length(ip);
             const test_name_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = n: {
-                const test_name_ty = try mod.arrayType(.{
+                const test_name_ty = try zcu.arrayType(.{
                     .len = test_decl_name_len,
                     .child = .u8_type,
                 });
-                const test_name_val = try mod.intern(.{ .aggregate = .{
+                const test_name_val = try zcu.intern(.{ .aggregate = .{
                     .ty = test_name_ty.toIntern(),
                     .storage = .{ .bytes = test_decl_name.toString() },
                 } });
                 break :n .{
-                    .orig_ty = (try mod.singleConstPtrType(test_name_ty)).toIntern(),
+                    .orig_ty = (try zcu.singleConstPtrType(test_name_ty)).toIntern(),
                     .val = test_name_val,
                 };
             };
 
             const test_fn_fields = .{
                 // name
-                try mod.intern(.{ .slice = .{
+                try zcu.intern(.{ .slice = .{
                     .ty = .slice_const_u8_type,
-                    .ptr = try mod.intern(.{ .ptr = .{
+                    .ptr = try zcu.intern(.{ .ptr = .{
                         .ty = .manyptr_const_u8_type,
                         .base_addr = .{ .anon_decl = test_name_anon_decl },
                         .byte_offset = 0,
                     } }),
-                    .len = try mod.intern(.{ .int = .{
+                    .len = try zcu.intern(.{ .int = .{
                         .ty = .usize_type,
                         .storage = .{ .u64 = test_decl_name_len },
                     } }),
                 } }),
                 // func
-                try mod.intern(.{ .ptr = .{
-                    .ty = try mod.intern(.{ .ptr_type = .{
-                        .child = test_decl.typeOf(mod).toIntern(),
+                try zcu.intern(.{ .ptr = .{
+                    .ty = try zcu.intern(.{ .ptr_type = .{
+                        .child = test_decl.typeOf(zcu).toIntern(),
                         .flags = .{
                             .is_const = true,
                         },
@@ -5591,29 +5636,29 @@ pub fn populateTestFunctions(
                     .byte_offset = 0,
                 } }),
             };
-            test_fn_val.* = try mod.intern(.{ .aggregate = .{
+            test_fn_val.* = try zcu.intern(.{ .aggregate = .{
                 .ty = test_fn_ty.toIntern(),
                 .storage = .{ .elems = &test_fn_fields },
             } });
         }
 
-        const array_ty = try mod.arrayType(.{
+        const array_ty = try zcu.arrayType(.{
             .len = test_fn_vals.len,
             .child = test_fn_ty.toIntern(),
             .sentinel = .none,
         });
-        const array_val = try mod.intern(.{ .aggregate = .{
+        const array_val = try zcu.intern(.{ .aggregate = .{
             .ty = array_ty.toIntern(),
             .storage = .{ .elems = test_fn_vals },
         } });
         break :array .{
-            .orig_ty = (try mod.singleConstPtrType(array_ty)).toIntern(),
+            .orig_ty = (try zcu.singleConstPtrType(array_ty)).toIntern(),
             .val = array_val,
         };
     };
 
     {
-        const new_ty = try mod.ptrType(.{
+        const new_ty = try zcu.ptrType(.{
             .child = test_fn_ty.toIntern(),
             .flags = .{
                 .is_const = true,
@@ -5621,14 +5666,14 @@ pub fn populateTestFunctions(
             },
         });
         const new_val = decl.val;
-        const new_init = try mod.intern(.{ .slice = .{
+        const new_init = try zcu.intern(.{ .slice = .{
             .ty = new_ty.toIntern(),
-            .ptr = try mod.intern(.{ .ptr = .{
-                .ty = new_ty.slicePtrFieldType(mod).toIntern(),
+            .ptr = try zcu.intern(.{ .ptr = .{
+                .ty = new_ty.slicePtrFieldType(zcu).toIntern(),
                 .base_addr = .{ .anon_decl = array_anon_decl },
                 .byte_offset = 0,
             } }),
-            .len = (try mod.intValue(Type.usize, mod.test_functions.count())).toIntern(),
+            .len = (try zcu.intValue(Type.usize, zcu.test_functions.count())).toIntern(),
         } });
         ip.mutateVarInit(decl.val.toIntern(), new_init);
 
@@ -5638,13 +5683,13 @@ pub fn populateTestFunctions(
         decl.has_tv = true;
     }
     {
-        mod.codegen_prog_node = main_progress_node.start("Code Generation", 0);
+        zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
         defer {
-            mod.codegen_prog_node.end();
-            mod.codegen_prog_node = undefined;
+            zcu.codegen_prog_node.end();
+            zcu.codegen_prog_node = undefined;
         }
 
-        try mod.linkerUpdateDecl(decl_index);
+        try zcu.linkerUpdateDecl(decl_index);
     }
 }
 
@@ -5684,31 +5729,35 @@ pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void {
 }
 
 fn reportRetryableFileError(
-    mod: *Module,
-    file: *File,
+    zcu: *Zcu,
+    file_index: File.Index,
     comptime format: []const u8,
     args: anytype,
 ) error{OutOfMemory}!void {
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
+
+    const file = zcu.fileByIndex(file_index);
     file.status = .retryable_failure;
 
     const err_msg = try ErrorMsg.create(
-        mod.gpa,
+        gpa,
         .{
-            .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+            .base_node_inst = try ip.trackZir(gpa, zcu.filePathDigest(file_index), .main_struct_inst),
             .offset = .entire_file,
         },
         format,
         args,
     );
-    errdefer err_msg.destroy(mod.gpa);
+    errdefer err_msg.destroy(gpa);
 
-    mod.comp.mutex.lock();
-    defer mod.comp.mutex.unlock();
+    zcu.comp.mutex.lock();
+    defer zcu.comp.mutex.unlock();
 
-    const gop = try mod.failed_files.getOrPut(mod.gpa, file);
+    const gop = try zcu.failed_files.getOrPut(gpa, file);
     if (gop.found_existing) {
         if (gop.value_ptr.*) |old_err_msg| {
-            old_err_msg.destroy(mod.gpa);
+            old_err_msg.destroy(gpa);
         }
     }
     gop.value_ptr.* = err_msg;
@@ -6528,8 +6577,9 @@ pub fn getBuiltin(zcu: *Zcu, name: []const u8) Allocator.Error!Air.Inst.Ref {
 pub fn getBuiltinDecl(zcu: *Zcu, name: []const u8) Allocator.Error!InternPool.DeclIndex {
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
-    const std_file = (zcu.importPkg(zcu.std_mod) catch @panic("failed to import lib/std.zig")).file;
-    const std_namespace = zcu.declPtr(std_file.root_decl.unwrap().?).getOwnedInnerNamespace(zcu).?;
+    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_decl = std_namespace.decls.getKeyAdapted(builtin_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse @panic("lib/std.zig is corrupt and missing 'builtin'");
     zcu.ensureDeclAnalyzed(builtin_decl) catch @panic("std.builtin is corrupt");
@@ -6544,3 +6594,20 @@ pub fn getBuiltinType(zcu: *Zcu, name: []const u8) Allocator.Error!Type {
     ty.resolveFully(zcu) catch @panic("std.builtin is corrupt");
     return ty;
 }
+
+pub fn fileByIndex(zcu: *const Zcu, i: File.Index) *File {
+    return zcu.import_table.values()[@intFromEnum(i)];
+}
+
+/// Returns the `Decl` of the struct that represents this `File`.
+pub fn fileRootDecl(zcu: *const Zcu, i: File.Index) Decl.OptionalIndex {
+    return zcu.files.values()[@intFromEnum(i)];
+}
+
+pub fn setFileRootDecl(zcu: *Zcu, i: File.Index, root_decl: Decl.OptionalIndex) void {
+    zcu.files.values()[@intFromEnum(i)] = root_decl;
+}
+
+pub fn filePathDigest(zcu: *const Zcu, i: File.Index) Cache.BinDigest {
+    return zcu.files.keys()[@intFromEnum(i)];
+}