Commit d5c1e7f7b1

Andrew Kelley <andrew@ziglang.org>
2024-01-02 03:11:57
link: accept the update arena in flush
This branch introduced an arena allocator for temporary allocations in Compilation.update. Almost every implementation of flush() inside the linker code was already creating a local arena that had the lifetime of the function call. This commit passes the update arena so that all those local ones can be deleted, resulting in slightly more efficient memory usage with every compilation update. While at it, this commit also removes the Compilation parameter from the linker flush function API since a reference to the Compilation is now already stored in `link.File`.
1 parent eae6d45
src/link/Coff/lld.zig
@@ -17,14 +17,12 @@ const Allocator = mem.Allocator;
 const Coff = @import("../Coff.zig");
 const Compilation = @import("../../Compilation.zig");
 
-pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void {
+pub fn linkWithLLD(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const comp = self.base.comp;
     const gpa = comp.gpa;
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
 
     const directory = self.base.emit.directory; // Just an alias to make it shorter to type.
     const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
@@ -32,7 +30,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
     const module_obj_path: ?[]const u8 = if (comp.module != null) blk: {
-        try self.flushModule(comp, prog_node);
+        try self.flushModule(arena, prog_node);
 
         if (fs.path.dirname(full_out_path)) |dirname| {
             break :blk try fs.path.join(arena, &.{ dirname, self.base.zcu_object_sub_path.? });
src/link/MachO/zld.zig
@@ -1,27 +1,24 @@
 pub fn linkWithZld(
     macho_file: *MachO,
-    comp: *Compilation,
+    arena: Allocator,
     prog_node: *std.Progress.Node,
 ) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = macho_file.base.comp.gpa;
-    const target = macho_file.base.comp.root_mod.resolved_target.result;
+    const comp = macho_file.base.comp;
+    const gpa = comp.gpa;
+    const target = comp.root_mod.resolved_target.result;
     const emit = macho_file.base.emit;
 
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
-
     const directory = emit.directory; // Just an alias to make it shorter to type.
     const full_out_path = try directory.join(arena, &[_][]const u8{emit.sub_path});
-    const opt_zcu = macho_file.base.comp.module;
+    const opt_zcu = comp.module;
 
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
     const module_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
-        try macho_file.flushModule(comp, prog_node);
+        try macho_file.flushModule(arena, prog_node);
 
         if (fs.path.dirname(full_out_path)) |dirname| {
             break :blk try fs.path.join(arena, &.{ dirname, macho_file.base.zcu_object_sub_path.? });
@@ -35,8 +32,8 @@ pub fn linkWithZld(
     sub_prog_node.context.refresh();
     defer sub_prog_node.end();
 
-    const output_mode = macho_file.base.comp.config.output_mode;
-    const link_mode = macho_file.base.comp.config.link_mode;
+    const output_mode = comp.config.output_mode;
+    const link_mode = comp.config.link_mode;
     const cpu_arch = target.cpu.arch;
     const is_lib = output_mode == .Lib;
     const is_dyn_lib = link_mode == .Dynamic and is_lib;
@@ -50,7 +47,7 @@ pub fn linkWithZld(
 
     var digest: [Cache.hex_digest_len]u8 = undefined;
 
-    const objects = macho_file.base.comp.objects;
+    const objects = comp.objects;
 
     if (!macho_file.base.disable_lld_caching) {
         man = comp.cache_parent.obtain();
@@ -76,7 +73,7 @@ pub fn linkWithZld(
         man.hash.add(macho_file.headerpad_max_install_names);
         man.hash.add(macho_file.base.gc_sections);
         man.hash.add(macho_file.dead_strip_dylibs);
-        man.hash.add(macho_file.base.comp.root_mod.strip);
+        man.hash.add(comp.root_mod.strip);
         try MachO.hashAddFrameworks(&man, macho_file.frameworks);
         man.hash.addListOfBytes(macho_file.base.rpath_list);
         if (is_dyn_lib) {
@@ -406,7 +403,7 @@ pub fn linkWithZld(
         try macho_file.createDyldPrivateAtom();
         try macho_file.createTentativeDefAtoms();
 
-        if (macho_file.base.comp.config.output_mode == .Exe) {
+        if (comp.config.output_mode == .Exe) {
             const global = macho_file.getEntryPoint().?;
             if (macho_file.getSymbol(global).undf()) {
                 // We do one additional check here in case the entry point was found in one of the dylibs.
src/link/C.zig
@@ -376,8 +376,8 @@ pub fn updateDeclLineNumber(self: *C, module: *Module, decl_index: InternPool.De
     _ = decl_index;
 }
 
-pub fn flush(self: *C, comp: *Compilation, prog_node: *std.Progress.Node) !void {
-    return self.flushModule(comp, prog_node);
+pub fn flush(self: *C, arena: Allocator, prog_node: *std.Progress.Node) !void {
+    return self.flushModule(arena, prog_node);
 }
 
 fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
@@ -393,7 +393,9 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
     return defines;
 }
 
-pub fn flushModule(self: *C, _: *Compilation, prog_node: *std.Progress.Node) !void {
+pub fn flushModule(self: *C, arena: Allocator, prog_node: *std.Progress.Node) !void {
+    _ = arena; // Has the same lifetime as the call to Compilation.update.
+
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -401,7 +403,8 @@ pub fn flushModule(self: *C, _: *Compilation, prog_node: *std.Progress.Node) !vo
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    const gpa = self.base.comp.gpa;
+    const comp = self.base.comp;
+    const gpa = comp.gpa;
     const module = self.base.comp.module.?;
 
     {
src/link/Coff.zig
@@ -1706,28 +1706,26 @@ fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void {
     gop.value_ptr.* = current;
 }
 
-pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
-    const use_lld = build_options.have_llvm and self.base.comp.config.use_lld;
+pub fn flush(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
+    const comp = self.base.comp;
+    const use_lld = build_options.have_llvm and comp.config.use_lld;
     if (use_lld) {
-        return lld.linkWithLLD(self, comp, prog_node);
+        return lld.linkWithLLD(self, arena, prog_node);
     }
-    switch (self.base.comp.config.output_mode) {
-        .Exe, .Obj => return self.flushModule(comp, prog_node),
+    switch (comp.config.output_mode) {
+        .Exe, .Obj => return self.flushModule(arena, prog_node),
         .Lib => return error.TODOImplementWritingLibFiles,
     }
 }
 
-pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flushModule(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const comp = self.base.comp;
     const gpa = comp.gpa;
 
     if (self.llvm_object) |llvm_object| {
-        var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-        defer arena_allocator.deinit();
-        const arena = arena_allocator.allocator();
-
         try self.base.emitLlvmObject(arena, llvm_object, prog_node);
         return;
     }
src/link/Elf.zig
@@ -1026,22 +1026,20 @@ pub fn markDirty(self: *Elf, shdr_index: u16) void {
     }
 }
 
-pub fn flush(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flush(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     const use_lld = build_options.have_llvm and self.base.comp.config.use_lld;
     if (use_lld) {
-        return self.linkWithLLD(comp, prog_node);
+        return self.linkWithLLD(arena, prog_node);
     }
-    try self.flushModule(comp, prog_node);
+    try self.flushModule(arena, prog_node);
 }
 
-pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flushModule(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const comp = self.base.comp;
     const gpa = comp.gpa;
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
 
     if (self.llvm_object) |llvm_object| {
         try self.base.emitLlvmObject(arena, llvm_object, prog_node);
@@ -2349,22 +2347,20 @@ fn scanRelocs(self: *Elf) !void {
     }
 }
 
-fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !void {
+fn linkWithLLD(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = self.base.comp.gpa;
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
+    const comp = self.base.comp;
+    const gpa = comp.gpa;
 
     const directory = self.base.emit.directory; // Just an alias to make it shorter to type.
     const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
 
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
-    const module_obj_path: ?[]const u8 = if (self.base.comp.module != null) blk: {
-        try self.flushModule(comp, prog_node);
+    const module_obj_path: ?[]const u8 = if (comp.module != null) blk: {
+        try self.flushModule(arena, prog_node);
 
         if (fs.path.dirname(full_out_path)) |dirname| {
             break :blk try fs.path.join(arena, &.{ dirname, self.base.zcu_object_sub_path.? });
@@ -2378,15 +2374,15 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
     sub_prog_node.context.refresh();
     defer sub_prog_node.end();
 
-    const output_mode = self.base.comp.config.output_mode;
+    const output_mode = comp.config.output_mode;
     const is_obj = output_mode == .Obj;
     const is_lib = output_mode == .Lib;
-    const link_mode = self.base.comp.config.link_mode;
+    const link_mode = comp.config.link_mode;
     const is_dyn_lib = link_mode == .Dynamic and is_lib;
     const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe;
     const have_dynamic_linker = comp.config.link_libc and
         link_mode == .Dynamic and is_exe_or_dyn_lib;
-    const target = self.base.comp.root_mod.resolved_target.result;
+    const target = comp.root_mod.resolved_target.result;
     const compiler_rt_path: ?[]const u8 = blk: {
         if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
         if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
@@ -2459,8 +2455,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
         man.hash.add(self.hash_style);
         // strip does not need to go into the linker hash because it is part of the hash namespace
         if (comp.config.link_libc) {
-            man.hash.add(self.base.comp.libc_installation != null);
-            if (self.base.comp.libc_installation) |libc_installation| {
+            man.hash.add(comp.libc_installation != null);
+            if (comp.libc_installation) |libc_installation| {
                 man.hash.addBytes(libc_installation.crt_dir.?);
             }
             if (have_dynamic_linker) {
@@ -2469,7 +2465,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
         }
         man.hash.addOptionalBytes(self.soname);
         man.hash.addOptional(comp.version);
-        try link.hashAddSystemLibs(&man, self.base.comp.system_libs);
+        try link.hashAddSystemLibs(&man, comp.system_libs);
         man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
         man.hash.add(self.base.allow_shlib_undefined);
         man.hash.add(self.bind_global_refs_locally);
@@ -2743,7 +2739,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
         if (self.each_lib_rpath) {
             var test_path = std.ArrayList(u8).init(arena);
             for (self.lib_dirs) |lib_dir_path| {
-                for (self.base.comp.system_libs.keys()) |link_lib| {
+                for (comp.system_libs.keys()) |link_lib| {
                     if (!(try self.accessLibPath(&test_path, null, lib_dir_path, link_lib, .Dynamic)))
                         continue;
                     if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) {
@@ -2771,7 +2767,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
         }
 
         if (comp.config.link_libc) {
-            if (self.base.comp.libc_installation) |libc_installation| {
+            if (comp.libc_installation) |libc_installation| {
                 try argv.append("-L");
                 try argv.append(libc_installation.crt_dir.?);
             }
@@ -2841,8 +2837,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
 
         // Shared libraries.
         if (is_exe_or_dyn_lib) {
-            const system_libs = self.base.comp.system_libs.keys();
-            const system_libs_values = self.base.comp.system_libs.values();
+            const system_libs = comp.system_libs.keys();
+            const system_libs_values = comp.system_libs.values();
 
             // Worst-case, we need an --as-needed argument for every lib, as well
             // as one before and one after.
@@ -2890,7 +2886,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
             // libc dep
             comp.link_error_flags.missing_libc = false;
             if (comp.config.link_libc) {
-                if (self.base.comp.libc_installation != null) {
+                if (comp.libc_installation != null) {
                     const needs_grouping = link_mode == .Static;
                     if (needs_grouping) try argv.append("--start-group");
                     try argv.appendSlice(target_util.libcFullLinkFlags(target));
@@ -2939,7 +2935,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
             try argv.append("-Bsymbolic");
         }
 
-        if (self.base.comp.verbose_link) {
+        if (comp.verbose_link) {
             // Skip over our own name so that the LLD linker name is the first argv item.
             Compilation.dump_argv(argv.items[1..]);
         }
src/link/MachO.zig
@@ -315,13 +315,14 @@ pub fn open(
     return createEmpty(arena, comp, emit, options);
 }
 
-pub fn flush(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
-    const gpa = self.base.comp.gpa;
-    const output_mode = self.base.comp.config.output_mode;
+pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
+    const comp = self.base.comp;
+    const gpa = comp.gpa;
+    const output_mode = comp.config.output_mode;
 
-    if (output_mode == .Lib and self.base.comp.config.link_mode == .Static) {
+    if (output_mode == .Lib and comp.config.link_mode == .Static) {
         if (build_options.have_llvm) {
-            return self.base.linkAsArchive(comp, prog_node);
+            return self.base.linkAsArchive(arena, prog_node);
         } else {
             try comp.link_errors.ensureUnusedCapacity(gpa, 1);
             comp.link_errors.appendAssumeCapacity(.{
@@ -332,19 +333,17 @@ pub fn flush(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) li
     }
 
     switch (self.mode) {
-        .zld => return zld.linkWithZld(self, comp, prog_node),
-        .incremental => return self.flushModule(comp, prog_node),
+        .zld => return zld.linkWithZld(self, arena, prog_node),
+        .incremental => return self.flushModule(arena, prog_node),
     }
 }
 
-pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const comp = self.base.comp;
     const gpa = comp.gpa;
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
 
     if (self.llvm_object) |llvm_object| {
         try self.base.emitLlvmObject(arena, llvm_object, prog_node);
src/link/NvPtx.zig
@@ -106,18 +106,18 @@ pub fn freeDecl(self: *NvPtx, decl_index: InternPool.DeclIndex) void {
     return self.llvm_object.freeDecl(decl_index);
 }
 
-pub fn flush(self: *NvPtx, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
-    return self.flushModule(comp, prog_node);
+pub fn flush(self: *NvPtx, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
+    return self.flushModule(arena, prog_node);
 }
 
-pub fn flushModule(self: *NvPtx, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flushModule(self: *NvPtx, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     if (build_options.skip_non_native)
         @panic("Attempted to compile for architecture that was disabled by build configuration");
 
     // The code that was here before mutated the Compilation's file emission mechanism.
     // That's not supposed to happen in flushModule, so I deleted the code.
+    _ = arena;
     _ = self;
-    _ = comp;
     _ = prog_node;
     @panic("TODO: rewrite the NvPtx.flushModule function");
 }
src/link/Plan9.zig
@@ -608,8 +608,9 @@ fn allocateGotIndex(self: *Plan9) usize {
     }
 }
 
-pub fn flush(self: *Plan9, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
-    const use_lld = build_options.have_llvm and self.base.comp.config.use_lld;
+pub fn flush(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
+    const comp = self.base.comp;
+    const use_lld = build_options.have_llvm and comp.config.use_lld;
     assert(!use_lld);
 
     switch (link.File.effectiveOutputMode(use_lld, comp.config.output_mode)) {
@@ -618,7 +619,7 @@ pub fn flush(self: *Plan9, comp: *Compilation, prog_node: *std.Progress.Node) li
         .Obj => return error.TODOImplementPlan9Objs,
         .Lib => return error.TODOImplementWritingLibFiles,
     }
-    return self.flushModule(comp, prog_node);
+    return self.flushModule(arena, prog_node);
 }
 
 pub fn changeLine(l: *std.ArrayList(u8), delta_line: i32) !void {
@@ -666,11 +667,14 @@ fn atomCount(self: *Plan9) usize {
     return data_decl_count + fn_decl_count + unnamed_const_count + lazy_atom_count + extern_atom_count + anon_atom_count;
 }
 
-pub fn flushModule(self: *Plan9, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     if (build_options.skip_non_native and builtin.object_format != .plan9) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
 
+    _ = arena; // Has the same lifetime as the call to Compilation.update.
+
+    const comp = self.base.comp;
     const gpa = comp.gpa;
     const target = comp.root_mod.resolved_target.result;
 
src/link/SpirV.zig
@@ -173,15 +173,17 @@ pub fn freeDecl(self: *SpirV, decl_index: InternPool.DeclIndex) void {
     _ = decl_index;
 }
 
-pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
-    return self.flushModule(comp, prog_node);
+pub fn flush(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
+    return self.flushModule(arena, prog_node);
 }
 
-pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     if (build_options.skip_non_native) {
         @panic("Attempted to compile for architecture that was disabled by build configuration");
     }
 
+    _ = arena; // Has the same lifetime as the call to Compilation.update.
+
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -191,6 +193,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No
 
     const spv = &self.object.spv;
 
+    const comp = self.base.comp;
     const gpa = comp.gpa;
     const target = comp.getTarget();
 
src/link/Wasm.zig
@@ -3480,33 +3480,29 @@ fn resetState(wasm: *Wasm) void {
     wasm.debug_pubtypes_index = null;
 }
 
-pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flush(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
+    const comp = wasm.base.comp;
     const use_lld = build_options.have_llvm and comp.config.use_lld;
     const use_llvm = comp.config.use_llvm;
 
     if (use_lld) {
-        return wasm.linkWithLLD(comp, prog_node);
+        return wasm.linkWithLLD(arena, prog_node);
     } else if (use_llvm) {
-        return wasm.linkWithZld(comp, prog_node);
+        return wasm.linkWithZld(arena, prog_node);
     } else {
-        return wasm.flushModule(comp, prog_node);
+        return wasm.flushModule(arena, prog_node);
     }
 }
 
 /// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary.
-fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+fn linkWithZld(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = comp.gpa;
+    const comp = wasm.base.comp;
     const shared_memory = comp.config.shared_memory;
     const import_memory = comp.config.import_memory;
 
-    // Used for all temporary memory allocated during flushin
-    var arena_instance = std.heap.ArenaAllocator.init(gpa);
-    defer arena_instance.deinit();
-    const arena = arena_instance.allocator();
-
     const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type.
     const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path});
     const opt_zcu = comp.module;
@@ -3516,7 +3512,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
     // will not be part of the linker line anyway.
     const module_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
         assert(use_llvm); // `linkWithZld` should never be called when the Wasm backend is used
-        try wasm.flushModule(comp, prog_node);
+        try wasm.flushModule(arena, prog_node);
 
         if (fs.path.dirname(full_out_path)) |dirname| {
             break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? });
@@ -3708,15 +3704,11 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
     }
 }
 
-pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = comp.gpa;
-    // Used for all temporary memory allocated during flushin
-    var arena_instance = std.heap.ArenaAllocator.init(gpa);
-    defer arena_instance.deinit();
-    const arena = arena_instance.allocator();
+    const comp = wasm.base.comp;
 
     if (wasm.llvm_object) |llvm_object| {
         try wasm.base.emitLlvmObject(arena, llvm_object, prog_node);
@@ -4589,19 +4581,17 @@ fn emitImport(wasm: *Wasm, writer: anytype, import: types.Import) !void {
     }
 }
 
-fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !void {
+fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const comp = wasm.base.comp;
     const shared_memory = comp.config.shared_memory;
     const export_memory = comp.config.export_memory;
     const import_memory = comp.config.import_memory;
     const target = comp.root_mod.resolved_target.result;
 
     const gpa = comp.gpa;
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
 
     const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type.
     const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path});
@@ -4609,7 +4599,7 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
     const module_obj_path: ?[]const u8 = if (comp.module != null) blk: {
-        try wasm.flushModule(comp, prog_node);
+        try wasm.flushModule(arena, prog_node);
 
         if (fs.path.dirname(full_out_path)) |dirname| {
             break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? });
src/Compilation.zig
@@ -2311,7 +2311,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
 fn flush(comp: *Compilation, arena: Allocator, prog_node: *std.Progress.Node) !void {
     if (comp.bin_file) |lf| {
         // This is needed before reading the error flags.
-        lf.flush(comp, prog_node) catch |err| switch (err) {
+        lf.flush(arena, prog_node) catch |err| switch (err) {
             error.FlushFailure => {}, // error reported through link_error_flags
             error.LLDReportedFailure => {}, // error reported via lockAndParseLldStderr
             else => |e| return e,
src/link.zig
@@ -547,19 +547,22 @@ pub const File = struct {
 
     /// Commit pending changes and write headers. Takes into account final output mode
     /// and `use_lld`, not only `effectiveOutputMode`.
-    pub fn flush(base: *File, comp: *Compilation, prog_node: *std.Progress.Node) FlushError!void {
+    /// `arena` has the lifetime of the call to `Compilation.update`.
+    pub fn flush(base: *File, arena: Allocator, prog_node: *std.Progress.Node) FlushError!void {
         if (build_options.only_c) {
             assert(base.tag == .c);
-            return @fieldParentPtr(C, "base", base).flush(comp, prog_node);
+            return @fieldParentPtr(C, "base", base).flush(arena, prog_node);
         }
+        const comp = base.comp;
         if (comp.clang_preprocessor_mode == .yes) {
+            const gpa = comp.gpa;
             const emit = base.emit;
             // TODO: avoid extra link step when it's just 1 object file (the `zig cc -c` case)
             // Until then, we do `lld -r -o output.o input.o` even though the output is the same
             // as the input. For the preprocessing case (`zig cc -E -o foo`) we copy the file
             // to the final location. See also the corresponding TODO in Coff linking.
-            const full_out_path = try emit.directory.join(comp.gpa, &[_][]const u8{emit.sub_path});
-            defer comp.gpa.free(full_out_path);
+            const full_out_path = try emit.directory.join(gpa, &[_][]const u8{emit.sub_path});
+            defer gpa.free(full_out_path);
             assert(comp.c_object_table.count() == 1);
             const the_key = comp.c_object_table.keys()[0];
             const cached_pp_file_path = the_key.status.success.object_path;
@@ -571,25 +574,25 @@ pub const File = struct {
         const output_mode = comp.config.output_mode;
         const link_mode = comp.config.link_mode;
         if (use_lld and output_mode == .Lib and link_mode == .Static) {
-            return base.linkAsArchive(comp, prog_node);
+            return base.linkAsArchive(arena, prog_node);
         }
         switch (base.tag) {
             inline else => |tag| {
-                return @fieldParentPtr(tag.Type(), "base", base).flush(comp, prog_node);
+                return @fieldParentPtr(tag.Type(), "base", base).flush(arena, prog_node);
             },
         }
     }
 
     /// Commit pending changes and write headers. Works based on `effectiveOutputMode`
     /// rather than final output mode.
-    pub fn flushModule(base: *File, comp: *Compilation, prog_node: *std.Progress.Node) FlushError!void {
+    pub fn flushModule(base: *File, arena: Allocator, prog_node: *std.Progress.Node) FlushError!void {
         switch (base.tag) {
             .c => {
-                return @fieldParentPtr(C, "base", base).flushModule(comp, prog_node);
+                return @fieldParentPtr(C, "base", base).flushModule(arena, prog_node);
             },
             inline else => |tag| {
                 if (build_options.only_c) unreachable;
-                return @fieldParentPtr(tag.Type(), "base", base).flushModule(comp, prog_node);
+                return @fieldParentPtr(tag.Type(), "base", base).flushModule(arena, prog_node);
             },
         }
     }
@@ -707,14 +710,12 @@ pub const File = struct {
         }
     }
 
-    pub fn linkAsArchive(base: *File, comp: *Compilation, prog_node: *std.Progress.Node) FlushError!void {
+    pub fn linkAsArchive(base: *File, arena: Allocator, prog_node: *std.Progress.Node) FlushError!void {
         const tracy = trace(@src());
         defer tracy.end();
 
+        const comp = base.comp;
         const gpa = comp.gpa;
-        var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-        defer arena_allocator.deinit();
-        const arena = arena_allocator.allocator();
 
         const directory = base.emit.directory; // Just an alias to make it shorter to type.
         const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
@@ -724,7 +725,7 @@ pub const File = struct {
         // If there is no Zig code to compile, then we should skip flushing the output file
         // because it will not be part of the linker line anyway.
         const zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
-            try base.flushModule(comp, prog_node);
+            try base.flushModule(arena, prog_node);
 
             const dirname = fs.path.dirname(full_out_path_z) orelse ".";
             break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });