Commit 3d2dfbe828

Jacob Young <jacobly0@users.noreply.github.com>
2024-07-10 16:04:33
InternPool: add `FileIndex` to `*File` mapping
1 parent f93a10f
src/codegen/llvm.zig
@@ -2811,7 +2811,7 @@ pub const Object = struct {
         const zcu = pt.zcu;
 
         const std_mod = zcu.std_mod;
-        const std_file_imported = zcu.importPkg(std_mod) catch unreachable;
+        const std_file_imported = pt.importPkg(std_mod) catch unreachable;
 
         const builtin_str = try zcu.intern_pool.getOrPutString(zcu.gpa, pt.tid, "builtin", .no_embedded_nulls);
         const std_file_root_decl = zcu.fileRootDecl(std_file_imported.file_index);
src/Zcu/PerThread.zig
@@ -817,7 +817,7 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
 
 /// https://github.com/ziglang/zig/issues/14307
 pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void {
-    const import_file_result = try pt.zcu.importPkg(pkg);
+    const import_file_result = try pt.importPkg(pkg);
     const root_decl_index = pt.zcu.fileRootDecl(import_file_result.file_index);
     if (root_decl_index == .none) {
         return pt.semaFile(import_file_result.file_index);
@@ -1081,7 +1081,7 @@ fn semaDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
         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_imported = try zcu.importPkg(std_mod);
+        const std_file_imported = try pt.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).?;
@@ -1356,6 +1356,191 @@ pub fn semaAnonOwnerDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.Sem
     };
 }
 
+pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult {
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+
+    // The resolved path is used as the key in the import table, to detect if
+    // 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, &.{
+        mod.root.root_dir.path orelse ".",
+        mod.root.sub_path,
+        mod.root_src_path,
+    });
+    var keep_resolved_path = false;
+    defer if (!keep_resolved_path) gpa.free(resolved_path);
+
+    const gop = try zcu.import_table.getOrPut(gpa, resolved_path);
+    errdefer _ = zcu.import_table.pop();
+    if (gop.found_existing) {
+        const file_index = gop.value_ptr.*;
+        const file = zcu.fileByIndex(file_index);
+        try file.addReference(zcu, .{ .root = mod });
+        return .{
+            .file = file,
+            .file_index = file_index,
+            .is_new = false,
+            .is_pkg = true,
+        };
+    }
+
+    const ip = &zcu.intern_pool;
+    try ip.files.ensureUnusedCapacity(gpa, 1);
+
+    if (mod.builtin_file) |builtin_file| {
+        const file_index = try ip.createFile(gpa, pt.tid, builtin_file);
+        keep_resolved_path = true; // It's now owned by import_table.
+        gop.value_ptr.* = file_index;
+        try builtin_file.addReference(zcu, .{ .root = mod });
+        const path_digest = Zcu.computePathDigest(zcu, mod, builtin_file.sub_file_path);
+        ip.files.putAssumeCapacityNoClobber(path_digest, .none);
+        return .{
+            .file = builtin_file,
+            .file_index = file_index,
+            .is_new = false,
+            .is_pkg = true,
+        };
+    }
+
+    const sub_file_path = try gpa.dupe(u8, mod.root_src_path);
+    errdefer gpa.free(sub_file_path);
+
+    const new_file = try gpa.create(Zcu.File);
+    errdefer gpa.destroy(new_file);
+
+    const new_file_index = try ip.createFile(gpa, pt.tid, new_file);
+    keep_resolved_path = true; // It's now owned by import_table.
+    gop.value_ptr.* = new_file_index;
+    new_file.* = .{
+        .sub_file_path = sub_file_path,
+        .source = undefined,
+        .source_loaded = false,
+        .tree_loaded = false,
+        .zir_loaded = false,
+        .stat = undefined,
+        .tree = undefined,
+        .zir = undefined,
+        .status = .never_loaded,
+        .mod = mod,
+    };
+
+    const path_digest = zcu.computePathDigest(mod, sub_file_path);
+
+    try new_file.addReference(zcu, .{ .root = mod });
+    ip.files.putAssumeCapacityNoClobber(path_digest, .none);
+    return .{
+        .file = new_file,
+        .file_index = new_file_index,
+        .is_new = true,
+        .is_pkg = true,
+    };
+}
+
+/// Called from a worker thread during AstGen.
+/// Also called from Sema during semantic analysis.
+pub fn importFile(
+    pt: Zcu.PerThread,
+    cur_file: *Zcu.File,
+    import_string: []const u8,
+) !Zcu.ImportFileResult {
+    const zcu = pt.zcu;
+    const mod = cur_file.mod;
+
+    if (std.mem.eql(u8, import_string, "std")) {
+        return pt.importPkg(zcu.std_mod);
+    }
+    if (std.mem.eql(u8, import_string, "root")) {
+        return pt.importPkg(zcu.root_mod);
+    }
+    if (mod.deps.get(import_string)) |pkg| {
+        return pt.importPkg(pkg);
+    }
+    if (!std.mem.endsWith(u8, import_string, ".zig")) {
+        return error.ModuleNotFound;
+    }
+    const gpa = zcu.gpa;
+
+    // The resolved path is used as the key in the import table, to detect if
+    // 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, &.{
+        mod.root.root_dir.path orelse ".",
+        mod.root.sub_path,
+        cur_file.sub_file_path,
+        "..",
+        import_string,
+    });
+
+    var keep_resolved_path = false;
+    defer if (!keep_resolved_path) gpa.free(resolved_path);
+
+    const gop = try zcu.import_table.getOrPut(gpa, resolved_path);
+    errdefer _ = zcu.import_table.pop();
+    if (gop.found_existing) {
+        const file_index = gop.value_ptr.*;
+        return .{
+            .file = zcu.fileByIndex(file_index),
+            .file_index = file_index,
+            .is_new = false,
+            .is_pkg = false,
+        };
+    }
+
+    const ip = &zcu.intern_pool;
+
+    try ip.files.ensureUnusedCapacity(gpa, 1);
+
+    const new_file = try gpa.create(Zcu.File);
+    errdefer gpa.destroy(new_file);
+
+    const resolved_root_path = try std.fs.path.resolve(gpa, &.{
+        mod.root.root_dir.path orelse ".",
+        mod.root.sub_path,
+    });
+    defer gpa.free(resolved_root_path);
+
+    const sub_file_path = p: {
+        const relative = try std.fs.path.relative(gpa, resolved_root_path, resolved_path);
+        errdefer gpa.free(relative);
+
+        if (!isUpDir(relative) and !std.fs.path.isAbsolute(relative)) {
+            break :p relative;
+        }
+        return error.ImportOutsideModulePath;
+    };
+    errdefer gpa.free(sub_file_path);
+
+    log.debug("new importFile. resolved_root_path={s}, resolved_path={s}, sub_file_path={s}, import_string={s}", .{
+        resolved_root_path, resolved_path, sub_file_path, import_string,
+    });
+
+    const new_file_index = try ip.createFile(gpa, pt.tid, new_file);
+    keep_resolved_path = true; // It's now owned by import_table.
+    gop.value_ptr.* = new_file_index;
+    new_file.* = .{
+        .sub_file_path = sub_file_path,
+        .source = undefined,
+        .source_loaded = false,
+        .tree_loaded = false,
+        .zir_loaded = false,
+        .stat = undefined,
+        .tree = undefined,
+        .zir = undefined,
+        .status = .never_loaded,
+        .mod = mod,
+    };
+
+    const path_digest = zcu.computePathDigest(mod, sub_file_path);
+    ip.files.putAssumeCapacityNoClobber(path_digest, .none);
+    return .{
+        .file = new_file,
+        .file_index = new_file_index,
+        .is_new = true,
+        .is_pkg = false,
+    };
+}
+
 pub fn embedFile(
     pt: Zcu.PerThread,
     cur_file: *Zcu.File,
@@ -1429,20 +1614,6 @@ pub fn embedFile(
     return pt.newEmbedFile(cur_file.mod, sub_file_path, resolved_path, gop.value_ptr, src_loc);
 }
 
-/// Cancel the creation of an anon decl and delete any references to it.
-/// If other decls depend on this decl, they must be aborted first.
-pub fn abortAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) void {
-    assert(!pt.zcu.declIsRoot(decl_index));
-    pt.destroyDecl(decl_index);
-}
-
-/// Finalize the creation of an anon decl.
-pub fn finalizeAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) Allocator.Error!void {
-    if (pt.zcu.declPtr(decl_index).typeOf(pt.zcu).isFnOrHasRuntimeBits(pt)) {
-        try pt.zcu.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
-    }
-}
-
 /// https://github.com/ziglang/zig/issues/14307
 fn newEmbedFile(
     pt: Zcu.PerThread,
@@ -1792,6 +1963,20 @@ const ScanDeclIter = struct {
     }
 };
 
+/// Cancel the creation of an anon decl and delete any references to it.
+/// If other decls depend on this decl, they must be aborted first.
+pub fn abortAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) void {
+    assert(!pt.zcu.declIsRoot(decl_index));
+    pt.destroyDecl(decl_index);
+}
+
+/// Finalize the creation of an anon decl.
+pub fn finalizeAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) Allocator.Error!void {
+    if (pt.zcu.declPtr(decl_index).typeOf(pt.zcu).isFnOrHasRuntimeBits(pt)) {
+        try pt.zcu.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
+    }
+}
+
 pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: Allocator) Zcu.SemaError!Air {
     const tracy = trace(@src());
     defer tracy.end();
@@ -2255,7 +2440,7 @@ pub fn populateTestFunctions(
     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 builtin_file_index = (pt.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);
@@ -2923,7 +3108,7 @@ pub fn getBuiltinDecl(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Inter
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
-    const std_file_imported = zcu.importPkg(zcu.std_mod) catch @panic("failed to import lib/std.zig");
+    const std_file_imported = pt.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, pt.tid, "builtin", .no_embedded_nulls);
src/Compilation.zig
@@ -2119,12 +2119,14 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
     }
 
     if (comp.module) |zcu| {
+        const pt: Zcu.PerThread = .{ .zcu = zcu, .tid = .main };
+
         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 = zcu.std_mod;
-        _ = try zcu.importPkg(std_mod);
+        _ = try pt.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,
@@ -2133,20 +2135,19 @@ 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 zcu.importPkg(zcu.main_mod);
+            _ = try pt.importPkg(zcu.main_mod);
         }
 
         if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
-            _ = try zcu.importPkg(compiler_rt_mod);
+            _ = try pt.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(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;
+        for (zcu.import_table.values()) |file_index| {
+            if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue;
             comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
         }
 
@@ -2641,7 +2642,8 @@ fn resolveEmitLoc(
     return slice.ptr;
 }
 
-fn reportMultiModuleErrors(zcu: *Zcu) !void {
+fn reportMultiModuleErrors(pt: Zcu.PerThread) !void {
+    const zcu = pt.zcu;
     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
@@ -2651,14 +2653,13 @@ fn reportMultiModuleErrors(zcu: *Zcu) !void {
     // Attach the "some omitted" note to the final error message
     var last_err: ?*Zcu.ErrorMsg = null;
 
-    for (zcu.import_table.values(), 0..) |file, file_index_usize| {
+    for (zcu.import_table.values()) |file_index| {
+        const file = zcu.fileByIndex(file_index);
         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;
@@ -2749,8 +2750,9 @@ fn reportMultiModuleErrors(zcu: *Zcu) !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 (zcu.import_table.values()) |file| {
-        if (file.multi_pkg) file.recursiveMarkMultiPkg(zcu);
+    for (zcu.import_table.values()) |file_index| {
+        const file = zcu.fileByIndex(file_index);
+        if (file.multi_pkg) file.recursiveMarkMultiPkg(pt);
     }
 }
 
@@ -3443,11 +3445,12 @@ fn performAllTheWorkInner(
         }
     }
 
-    if (comp.module) |mod| {
-        try reportMultiModuleErrors(mod);
-        try mod.flushRetryableFailures();
-        mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
-        mod.codegen_prog_node = main_progress_node.start("Code Generation", 0);
+    if (comp.module) |zcu| {
+        const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = .main };
+        try reportMultiModuleErrors(pt);
+        try zcu.flushRetryableFailures();
+        zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
+        zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
     }
 
     if (!InternPool.single_threaded) comp.thread_pool.spawnWgId(&comp.work_queue_wait_group, codegenThread, .{comp});
@@ -4189,9 +4192,9 @@ fn workerAstGenFile(
                 comp.mutex.lock();
                 defer comp.mutex.unlock();
 
-                const res = pt.zcu.importFile(file, import_path) catch continue;
+                const res = pt.importFile(file, import_path) catch continue;
                 if (!res.is_pkg) {
-                    res.file.addReference(pt.zcu.*, .{ .import = .{
+                    res.file.addReference(pt.zcu, .{ .import = .{
                         .file = file_index,
                         .token = item.data.token,
                     } }) catch continue;
src/InternPool.zig
@@ -1,6 +1,5 @@
 //! All interned objects have both a value and a type.
-//! This data structure is self-contained, with the following exceptions:
-//! * Module.Namespace has a pointer to Module.File
+//! This data structure is self-contained.
 
 /// One item per thread, indexed by `tid`, which is dense and unique per thread.
 locals: []Local = &.{},
@@ -79,10 +78,6 @@ const want_multi_threaded = false;
 /// Whether a single-threaded intern pool impl is in use.
 pub const single_threaded = builtin.single_threaded or !want_multi_threaded;
 
-pub const FileIndex = enum(u32) {
-    _,
-};
-
 pub const TrackedInst = extern struct {
     file: FileIndex,
     inst: Zir.Inst.Index,
@@ -340,6 +335,7 @@ const Local = struct {
         extra: ListMutate,
         limbs: ListMutate,
         strings: ListMutate,
+        files: ListMutate,
 
         decls: BucketListMutate,
         namespaces: BucketListMutate,
@@ -350,6 +346,7 @@ const Local = struct {
         extra: Extra,
         limbs: Limbs,
         strings: Strings,
+        files: Files,
 
         decls: Decls,
         namespaces: Namespaces,
@@ -370,16 +367,17 @@ const Local = struct {
         else => @compileError("unsupported host"),
     };
     const Strings = List(struct { u8 });
+    const Files = List(struct { *Zcu.File });
 
     const decls_bucket_width = 8;
     const decls_bucket_mask = (1 << decls_bucket_width) - 1;
     const decl_next_free_field = "src_namespace";
-    const Decls = List(struct { *[1 << decls_bucket_width]Module.Decl });
+    const Decls = List(struct { *[1 << decls_bucket_width]Zcu.Decl });
 
     const namespaces_bucket_width = 8;
     const namespaces_bucket_mask = (1 << namespaces_bucket_width) - 1;
     const namespace_next_free_field = "decl_index";
-    const Namespaces = List(struct { *[1 << namespaces_bucket_width]Module.Namespace });
+    const Namespaces = List(struct { *[1 << namespaces_bucket_width]Zcu.Namespace });
 
     const ListMutate = struct {
         len: u32,
@@ -677,6 +675,15 @@ const Local = struct {
         };
     }
 
+    pub fn getMutableFiles(local: *Local, gpa: std.mem.Allocator) Files.Mutable {
+        return .{
+            .gpa = gpa,
+            .arena = &local.mutate.arena,
+            .mutate = &local.mutate.files,
+            .list = &local.shared.files,
+        };
+    }
+
     /// Rather than allocating Decl objects with an Allocator, we instead allocate
     /// them with this BucketList. This provides four advantages:
     ///  * Stable memory so that one thread can access a Decl object while another
@@ -812,8 +819,6 @@ const Hash = std.hash.Wyhash;
 
 const InternPool = @This();
 const Zcu = @import("Zcu.zig");
-/// Deprecated.
-const Module = Zcu;
 const Zir = std.zig.Zir;
 
 /// An index into `maps` which might be `none`.
@@ -938,6 +943,28 @@ pub const OptionalNamespaceIndex = enum(u32) {
     }
 };
 
+pub const FileIndex = enum(u32) {
+    _,
+
+    const Unwrapped = struct {
+        tid: Zcu.PerThread.Id,
+        index: u32,
+
+        fn wrap(unwrapped: Unwrapped, ip: *const InternPool) FileIndex {
+            assert(@intFromEnum(unwrapped.tid) <= ip.getTidMask());
+            assert(unwrapped.index <= ip.getIndexMask(u32));
+            return @enumFromInt(@as(u32, @intFromEnum(unwrapped.tid)) << ip.tid_shift_32 |
+                unwrapped.index);
+        }
+    };
+    fn unwrap(file_index: FileIndex, ip: *const InternPool) Unwrapped {
+        return .{
+            .tid = @enumFromInt(@intFromEnum(file_index) >> ip.tid_shift_32 & ip.getTidMask()),
+            .index = @intFromEnum(file_index) & ip.getIndexMask(u32),
+        };
+    }
+};
+
 /// An index into `strings`.
 pub const String = enum(u32) {
     /// An empty string.
@@ -4608,12 +4635,12 @@ pub const FuncAnalysis = packed struct(u32) {
         /// inline, which means no runtime version of the function will be generated.
         inline_only,
         in_progress,
-        /// There will be a corresponding ErrorMsg in Module.failed_decls
+        /// There will be a corresponding ErrorMsg in Zcu.failed_decls
         sema_failure,
         /// This function might be OK but it depends on another Decl which did not
         /// successfully complete semantic analysis.
         dependency_failure,
-        /// There will be a corresponding ErrorMsg in Module.failed_decls.
+        /// There will be a corresponding ErrorMsg in Zcu.failed_decls.
         /// Indicates that semantic analysis succeeded, but code generation for
         /// this function failed.
         codegen_failure,
@@ -5210,6 +5237,7 @@ pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void {
             .extra = Local.Extra.empty,
             .limbs = Local.Limbs.empty,
             .strings = Local.Strings.empty,
+            .files = Local.Files.empty,
 
             .decls = Local.Decls.empty,
             .namespaces = Local.Namespaces.empty,
@@ -5221,6 +5249,7 @@ pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void {
             .extra = Local.ListMutate.empty,
             .limbs = Local.ListMutate.empty,
             .strings = Local.ListMutate.empty,
+            .files = Local.ListMutate.empty,
 
             .decls = Local.BucketListMutate.empty,
             .namespaces = Local.BucketListMutate.empty,
@@ -9213,7 +9242,7 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
     const items_size = (1 + 4) * items_len;
     const extra_size = 4 * extra_len;
     const limbs_size = 8 * limbs_len;
-    const decls_size = @sizeOf(Module.Decl) * decls_len;
+    const decls_size = @sizeOf(Zcu.Decl) * decls_len;
 
     // TODO: map overhead size is not taken into account
     const total_size = @sizeOf(InternPool) + items_size + extra_size + limbs_size + decls_size;
@@ -9640,29 +9669,22 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)
     try bw.flush();
 }
 
-pub fn declPtr(ip: *InternPool, decl_index: DeclIndex) *Module.Decl {
+pub fn declPtr(ip: *InternPool, decl_index: DeclIndex) *Zcu.Decl {
     return @constCast(ip.declPtrConst(decl_index));
 }
 
-pub fn declPtrConst(ip: *const InternPool, decl_index: DeclIndex) *const Module.Decl {
+pub fn declPtrConst(ip: *const InternPool, decl_index: DeclIndex) *const Zcu.Decl {
     const unwrapped_decl_index = decl_index.unwrap(ip);
     const decls = ip.getLocalShared(unwrapped_decl_index.tid).decls.acquire();
     const decls_bucket = decls.view().items(.@"0")[unwrapped_decl_index.bucket_index];
     return &decls_bucket[unwrapped_decl_index.index];
 }
 
-pub fn namespacePtr(ip: *InternPool, namespace_index: NamespaceIndex) *Module.Namespace {
-    const unwrapped_namespace_index = namespace_index.unwrap(ip);
-    const namespaces = ip.getLocalShared(unwrapped_namespace_index.tid).namespaces.acquire();
-    const namespaces_bucket = namespaces.view().items(.@"0")[unwrapped_namespace_index.bucket_index];
-    return &namespaces_bucket[unwrapped_namespace_index.index];
-}
-
 pub fn createDecl(
     ip: *InternPool,
     gpa: Allocator,
     tid: Zcu.PerThread.Id,
-    initialization: Module.Decl,
+    initialization: Zcu.Decl,
 ) Allocator.Error!DeclIndex {
     const local = ip.getLocal(tid);
     const free_list_next = local.mutate.decls.free_list;
@@ -9679,7 +9701,7 @@ pub fn createDecl(
         var arena = decls.arena.promote(decls.gpa);
         defer decls.arena.* = arena.state;
         decls.appendAssumeCapacity(.{try arena.allocator().create(
-            [1 << Local.decls_bucket_width]Module.Decl,
+            [1 << Local.decls_bucket_width]Zcu.Decl,
         )});
     }
     const unwrapped_decl_index: DeclIndex.Unwrapped = .{
@@ -9702,11 +9724,18 @@ pub fn destroyDecl(ip: *InternPool, tid: Zcu.PerThread.Id, decl_index: DeclIndex
     local.mutate.decls.free_list = @intFromEnum(decl_index);
 }
 
+pub fn namespacePtr(ip: *InternPool, namespace_index: NamespaceIndex) *Zcu.Namespace {
+    const unwrapped_namespace_index = namespace_index.unwrap(ip);
+    const namespaces = ip.getLocalShared(unwrapped_namespace_index.tid).namespaces.acquire();
+    const namespaces_bucket = namespaces.view().items(.@"0")[unwrapped_namespace_index.bucket_index];
+    return &namespaces_bucket[unwrapped_namespace_index.index];
+}
+
 pub fn createNamespace(
     ip: *InternPool,
     gpa: Allocator,
     tid: Zcu.PerThread.Id,
-    initialization: Module.Namespace,
+    initialization: Zcu.Namespace,
 ) Allocator.Error!NamespaceIndex {
     const local = ip.getLocal(tid);
     const free_list_next = local.mutate.namespaces.free_list;
@@ -9724,7 +9753,7 @@ pub fn createNamespace(
         var arena = namespaces.arena.promote(namespaces.gpa);
         defer namespaces.arena.* = arena.state;
         namespaces.appendAssumeCapacity(.{try arena.allocator().create(
-            [1 << Local.namespaces_bucket_width]Module.Namespace,
+            [1 << Local.namespaces_bucket_width]Zcu.Namespace,
         )});
     }
     const unwrapped_namespace_index: NamespaceIndex.Unwrapped = .{
@@ -9756,6 +9785,27 @@ pub fn destroyNamespace(
     local.mutate.namespaces.free_list = @intFromEnum(namespace_index);
 }
 
+pub fn filePtr(ip: *InternPool, file_index: FileIndex) *Zcu.File {
+    const file_index_unwrapped = file_index.unwrap(ip);
+    const files = ip.getLocalShared(file_index_unwrapped.tid).files.acquire();
+    return files.view().items(.@"0")[file_index_unwrapped.index];
+}
+
+pub fn createFile(
+    ip: *InternPool,
+    gpa: Allocator,
+    tid: Zcu.PerThread.Id,
+    file: *Zcu.File,
+) Allocator.Error!FileIndex {
+    const files = ip.getLocal(tid).getMutableFiles(gpa);
+    const file_index_unwrapped: FileIndex.Unwrapped = .{
+        .tid = tid,
+        .index = files.mutate.len,
+    };
+    try files.append(.{file});
+    return file_index_unwrapped.wrap(ip);
+}
+
 const EmbeddedNulls = enum {
     no_embedded_nulls,
     maybe_embedded_nulls,
src/Sema.zig
@@ -6056,7 +6056,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
         else => |e| return e,
     };
 
-    const result = zcu.importPkg(c_import_mod) catch |err|
+    const result = pt.importPkg(c_import_mod) catch |err|
         return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
 
     const path_digest = zcu.filePathDigest(result.file_index);
@@ -13950,7 +13950,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
     const operand_src = block.tokenOffset(inst_data.src_tok);
     const operand = inst_data.get(sema.code);
 
-    const result = zcu.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) {
+    const result = pt.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});
         },
src/Type.zig
@@ -3451,7 +3451,7 @@ pub fn typeDeclInst(ty: Type, zcu: *const Zcu) ?InternPool.TrackedInst.Index {
     };
 }
 
-pub fn typeDeclSrcLine(ty: Type, zcu: *const Zcu) ?u32 {
+pub fn typeDeclSrcLine(ty: Type, zcu: *Zcu) ?u32 {
     const ip = &zcu.intern_pool;
     const tracked = switch (ip.indexToKey(ty.toIntern())) {
         .struct_type, .union_type, .opaque_type, .enum_type => |info| switch (info) {
src/Zcu.zig
@@ -102,7 +102,7 @@ multi_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
 /// `Compilation.update` of the process for a given `Compilation`.
 ///
 /// Indexes correspond 1:1 to `files`.
-import_table: std.StringArrayHashMapUnmanaged(*File) = .{},
+import_table: std.StringArrayHashMapUnmanaged(File.Index) = .{},
 
 /// 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
@@ -892,7 +892,7 @@ pub const File = struct {
     }
 
     /// Add a reference to this file during AstGen.
-    pub fn addReference(file: *File, zcu: Zcu, ref: File.Reference) !void {
+    pub fn addReference(file: *File, zcu: *Zcu, ref: File.Reference) !void {
         // Don't add the same module root twice. Note that since we always add module roots at the
         // front of the references array (see below), this loop is actually O(1) on valid code.
         if (ref == .root) {
@@ -924,7 +924,7 @@ pub const File = struct {
 
     /// Mark this file and every file referenced by it as multi_pkg and report an
     /// astgen_failure error for them. AstGen must have completed in its entirety.
-    pub fn recursiveMarkMultiPkg(file: *File, mod: *Module) void {
+    pub fn recursiveMarkMultiPkg(file: *File, pt: Zcu.PerThread) void {
         file.multi_pkg = true;
         file.status = .astgen_failure;
 
@@ -944,9 +944,9 @@ pub const File = struct {
             const import_path = file.zir.nullTerminatedString(item.data.name);
             if (mem.eql(u8, import_path, "builtin")) continue;
 
-            const res = mod.importFile(file, import_path) catch continue;
+            const res = pt.importFile(file, import_path) catch continue;
             if (!res.is_pkg and !res.file.multi_pkg) {
-                res.file.recursiveMarkMultiPkg(mod);
+                res.file.recursiveMarkMultiPkg(pt);
             }
         }
     }
@@ -3002,183 +3002,7 @@ pub const ImportFileResult = struct {
     is_pkg: bool,
 };
 
-pub fn importPkg(zcu: *Zcu, mod: *Package.Module) !ImportFileResult {
-    const gpa = zcu.gpa;
-
-    // The resolved path is used as the key in the import table, to detect if
-    // 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, &.{
-        mod.root.root_dir.path orelse ".",
-        mod.root.sub_path,
-        mod.root_src_path,
-    });
-    var keep_resolved_path = false;
-    defer if (!keep_resolved_path) gpa.free(resolved_path);
-
-    const gop = try zcu.import_table.getOrPut(gpa, resolved_path);
-    errdefer _ = zcu.import_table.pop();
-    if (gop.found_existing) {
-        try gop.value_ptr.*.addReference(zcu.*, .{ .root = mod });
-        return .{
-            .file = gop.value_ptr.*,
-            .file_index = @enumFromInt(gop.index),
-            .is_new = false,
-            .is_pkg = true,
-        };
-    }
-
-    const ip = &zcu.intern_pool;
-
-    try ip.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 });
-        const path_digest = computePathDigest(zcu, mod, builtin_file.sub_file_path);
-        ip.files.putAssumeCapacityNoClobber(path_digest, .none);
-        return .{
-            .file = builtin_file,
-            .file_index = @enumFromInt(ip.files.entries.len - 1),
-            .is_new = false,
-            .is_pkg = true,
-        };
-    }
-
-    const sub_file_path = try gpa.dupe(u8, mod.root_src_path);
-    errdefer gpa.free(sub_file_path);
-
-    const new_file = try gpa.create(File);
-    errdefer gpa.destroy(new_file);
-
-    keep_resolved_path = true; // It's now owned by import_table.
-    gop.value_ptr.* = new_file;
-    new_file.* = .{
-        .sub_file_path = sub_file_path,
-        .source = undefined,
-        .source_loaded = false,
-        .tree_loaded = false,
-        .zir_loaded = false,
-        .stat = undefined,
-        .tree = undefined,
-        .zir = undefined,
-        .status = .never_loaded,
-        .mod = mod,
-    };
-
-    const path_digest = computePathDigest(zcu, mod, sub_file_path);
-
-    try new_file.addReference(zcu.*, .{ .root = mod });
-    ip.files.putAssumeCapacityNoClobber(path_digest, .none);
-    return .{
-        .file = new_file,
-        .file_index = @enumFromInt(ip.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 (mod.deps.get(import_string)) |pkg| {
-        return zcu.importPkg(pkg);
-    }
-    if (!mem.endsWith(u8, import_string, ".zig")) {
-        return error.ModuleNotFound;
-    }
-    const gpa = zcu.gpa;
-
-    // The resolved path is used as the key in the import table, to detect if
-    // 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, &.{
-        mod.root.root_dir.path orelse ".",
-        mod.root.sub_path,
-        cur_file.sub_file_path,
-        "..",
-        import_string,
-    });
-
-    var keep_resolved_path = false;
-    defer if (!keep_resolved_path) gpa.free(resolved_path);
-
-    const gop = try zcu.import_table.getOrPut(gpa, resolved_path);
-    errdefer _ = zcu.import_table.pop();
-    if (gop.found_existing) return .{
-        .file = gop.value_ptr.*,
-        .file_index = @enumFromInt(gop.index),
-        .is_new = false,
-        .is_pkg = false,
-    };
-
-    const ip = &zcu.intern_pool;
-
-    try ip.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, &.{
-        mod.root.root_dir.path orelse ".",
-        mod.root.sub_path,
-    });
-    defer gpa.free(resolved_root_path);
-
-    const sub_file_path = p: {
-        const relative = try std.fs.path.relative(gpa, resolved_root_path, resolved_path);
-        errdefer gpa.free(relative);
-
-        if (!isUpDir(relative) and !std.fs.path.isAbsolute(relative)) {
-            break :p relative;
-        }
-        return error.ImportOutsideModulePath;
-    };
-    errdefer gpa.free(sub_file_path);
-
-    log.debug("new importFile. resolved_root_path={s}, resolved_path={s}, sub_file_path={s}, import_string={s}", .{
-        resolved_root_path, resolved_path, sub_file_path, import_string,
-    });
-
-    keep_resolved_path = true; // It's now owned by import_table.
-    gop.value_ptr.* = new_file;
-    new_file.* = .{
-        .sub_file_path = sub_file_path,
-        .source = undefined,
-        .source_loaded = false,
-        .tree_loaded = false,
-        .zir_loaded = false,
-        .stat = undefined,
-        .tree = undefined,
-        .zir = undefined,
-        .status = .never_loaded,
-        .mod = mod,
-    };
-
-    const path_digest = computePathDigest(zcu, mod, sub_file_path);
-    ip.files.putAssumeCapacityNoClobber(path_digest, .none);
-    return .{
-        .file = new_file,
-        .file_index = @enumFromInt(ip.files.entries.len - 1),
-        .is_new = true,
-        .is_pkg = false,
-    };
-}
-
-fn computePathDigest(zcu: *Zcu, mod: *Package.Module, sub_file_path: []const u8) Cache.BinDigest {
+pub 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);
@@ -3710,8 +3534,9 @@ pub fn resolveReferences(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, Resolved
     return result;
 }
 
-pub fn fileByIndex(zcu: *const Zcu, i: File.Index) *File {
-    return zcu.import_table.values()[@intFromEnum(i)];
+pub fn fileByIndex(zcu: *Zcu, i: File.Index) *File {
+    const ip = &zcu.intern_pool;
+    return ip.filePtr(i);
 }
 
 /// Returns the `Decl` of the struct that represents this `File`.