Commit b54ad93175

Andrew Kelley <andrew@ziglang.org>
2023-12-16 03:12:06
update codegen.llvm references to bin_file.options
1 parent 92b54e5
src/codegen/llvm.zig
@@ -822,7 +822,7 @@ pub const Object = struct {
     type_map: TypeMap,
     di_type_map: DITypeMap,
     /// The LLVM global table which holds the names corresponding to Zig errors.
-    /// Note that the values are not added until flushModule, when all errors in
+    /// Note that the values are not added until `emit`, when all errors in
     /// the compilation are known.
     error_name_table: Builder.Variable.Index,
     /// This map is usually very close to empty. It tracks only the cases when a
@@ -850,7 +850,7 @@ pub const Object = struct {
 
     pub const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, Builder.Type);
 
-    /// This is an ArrayHashMap as opposed to a HashMap because in `flushModule` we
+    /// This is an ArrayHashMap as opposed to a HashMap because in `emit` we
     /// want to iterate over it while adding entries to it.
     pub const DITypeMap = std.AutoArrayHashMapUnmanaged(InternPool.Index, AnnotatedDITypePtr);
 
@@ -1026,17 +1026,6 @@ pub const Object = struct {
         self.* = undefined;
     }
 
-    fn locPath(
-        arena: Allocator,
-        opt_loc: ?Compilation.EmitLoc,
-        cache_directory: Compilation.Directory,
-    ) !?[*:0]u8 {
-        const loc = opt_loc orelse return null;
-        const directory = loc.directory orelse cache_directory;
-        const slice = try directory.joinZ(arena, &[_][]const u8{loc.basename});
-        return slice.ptr;
-    }
-
     fn genErrorNameTable(o: *Object) Allocator.Error!void {
         // If o.error_name_table is null, then it was not referenced by any instructions.
         if (o.error_name_table == .none) return;
@@ -1175,12 +1164,22 @@ pub const Object = struct {
         }
     }
 
-    pub fn flushModule(self: *Object, comp: *Compilation, prog_node: *std.Progress.Node) !void {
-        var sub_prog_node = prog_node.start("LLVM Emit Object", 0);
-        sub_prog_node.activate();
-        sub_prog_node.context.refresh();
-        defer sub_prog_node.end();
+    pub const EmitOptions = struct {
+        pre_ir_path: ?[]const u8,
+        pre_bc_path: ?[]const u8,
+        bin_path: ?[*:0]const u8,
+        emit_asm: ?[*:0]const u8,
+        post_ir_path: ?[*:0]const u8,
+        post_bc_path: ?[*:0]const u8,
+
+        is_debug: bool,
+        is_small: bool,
+        time_report: bool,
+        sanitize_thread: bool,
+        lto: bool,
+    };
 
+    pub fn emit(self: *Object, options: EmitOptions) !void {
         try self.resolveExportExternCollisions();
         try self.genErrorNameTable();
         try self.genCmpLtErrorsLenFunction();
@@ -1206,7 +1205,7 @@ pub const Object = struct {
             dib.finalize();
         }
 
-        if (comp.verbose_llvm_ir) |path| {
+        if (options.pre_ir_path) |path| {
             if (std.mem.eql(u8, path, "-")) {
                 self.builder.dump();
             } else {
@@ -1214,91 +1213,72 @@ pub const Object = struct {
             }
         }
 
-        if (comp.verbose_llvm_bc) |path| _ = try self.builder.writeBitcodeToFile(path);
-
-        var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
-        defer arena_allocator.deinit();
-        const arena = arena_allocator.allocator();
-
-        const mod = comp.module.?;
-        const cache_dir = mod.zig_cache_artifact_directory;
+        if (options.pre_bc_path) |path| _ = try self.builder.writeBitcodeToFile(path);
 
         if (std.debug.runtime_safety and !try self.builder.verify()) {
-            if (try locPath(arena, comp.emit_llvm_ir, cache_dir)) |emit_llvm_ir_path|
-                _ = self.builder.printToFileZ(emit_llvm_ir_path);
             @panic("LLVM module verification failed");
         }
 
-        var emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit) |emit|
-            try emit.basenamePath(arena, try arena.dupeZ(u8, comp.bin_file.intermediary_basename.?))
-        else
-            null;
-
-        const emit_asm_path = try locPath(arena, comp.emit_asm, cache_dir);
-        var emit_llvm_ir_path = try locPath(arena, comp.emit_llvm_ir, cache_dir);
-        const emit_llvm_bc_path = try locPath(arena, comp.emit_llvm_bc, cache_dir);
-
-        const emit_asm_msg = emit_asm_path orelse "(none)";
-        const emit_bin_msg = emit_bin_path orelse "(none)";
-        const emit_llvm_ir_msg = emit_llvm_ir_path orelse "(none)";
-        const emit_llvm_bc_msg = emit_llvm_bc_path orelse "(none)";
+        const emit_asm_msg = options.asm_path orelse "(none)";
+        const emit_bin_msg = options.bin_path orelse "(none)";
+        const post_llvm_ir_msg = options.post_ir_path orelse "(none)";
+        const post_llvm_bc_msg = options.post_bc_path orelse "(none)";
         log.debug("emit LLVM object asm={s} bin={s} ir={s} bc={s}", .{
-            emit_asm_msg, emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg,
+            emit_asm_msg, emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg,
         });
 
-        if (emit_asm_path == null and emit_bin_path == null and
-            emit_llvm_ir_path == null and emit_llvm_bc_path == null) return;
+        if (options.asm_path == null and options.bin_path == null and
+            options.post_ir_path == null and options.post_bc_path == null) return;
 
-        if (!self.builder.useLibLlvm()) {
-            log.err("emitting without libllvm not implemented", .{});
-            return error.FailedToEmit;
-        }
+        if (!self.builder.useLibLlvm()) unreachable; // caught in Compilation.Config.resolve
 
         // Unfortunately, LLVM shits the bed when we ask for both binary and assembly.
         // So we call the entire pipeline multiple times if this is requested.
         var error_message: [*:0]const u8 = undefined;
-        if (emit_asm_path != null and emit_bin_path != null) {
+        var emit_bin_path = options.bin_path;
+        var post_ir_path = options.post_ir_path;
+        if (options.asm_path != null and options.bin_path != null) {
             if (self.target_machine.emitToFile(
                 self.builder.llvm.module.?,
                 &error_message,
-                comp.bin_file.options.optimize_mode == .Debug,
-                comp.bin_file.options.optimize_mode == .ReleaseSmall,
-                comp.time_report,
-                comp.bin_file.options.tsan,
-                comp.bin_file.options.lto,
+                options.is_debug,
+                options.is_small,
+                options.time_report,
+                options.sanitize_thread,
+                options.lto,
                 null,
                 emit_bin_path,
-                emit_llvm_ir_path,
+                post_ir_path,
                 null,
             )) {
                 defer llvm.disposeMessage(error_message);
 
                 log.err("LLVM failed to emit bin={s} ir={s}: {s}", .{
-                    emit_bin_msg, emit_llvm_ir_msg, error_message,
+                    emit_bin_msg, post_llvm_ir_msg, error_message,
                 });
                 return error.FailedToEmit;
             }
             emit_bin_path = null;
-            emit_llvm_ir_path = null;
+            post_ir_path = null;
         }
 
         if (self.target_machine.emitToFile(
             self.builder.llvm.module.?,
             &error_message,
-            comp.bin_file.options.optimize_mode == .Debug,
-            comp.bin_file.options.optimize_mode == .ReleaseSmall,
-            comp.time_report,
-            comp.bin_file.options.tsan,
-            comp.bin_file.options.lto,
-            emit_asm_path,
+            options.is_debug,
+            options.is_small,
+            options.time_report,
+            options.sanitize_thread,
+            options.lto,
+            options.asm_path,
             emit_bin_path,
-            emit_llvm_ir_path,
-            emit_llvm_bc_path,
+            post_ir_path,
+            options.post_bc_path,
         )) {
             defer llvm.disposeMessage(error_message);
 
             log.err("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
-                emit_asm_msg,  emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg,
+                emit_asm_msg,  emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg,
                 error_message,
             });
             return error.FailedToEmit;
@@ -1307,17 +1287,19 @@ pub const Object = struct {
 
     pub fn updateFunc(
         o: *Object,
-        mod: *Module,
+        zcu: *Module,
         func_index: InternPool.Index,
         air: Air,
         liveness: Liveness,
     ) !void {
-        const func = mod.funcInfo(func_index);
+        const func = zcu.funcInfo(func_index);
         const decl_index = func.owner_decl;
-        const decl = mod.declPtr(decl_index);
-        const fn_info = mod.typeToFunc(decl.ty).?;
-        const target = mod.getTarget();
-        const ip = &mod.intern_pool;
+        const decl = zcu.declPtr(decl_index);
+        const namespace = zcu.namespacePtr(decl.src_namespace);
+        const owner_mod = namespace.file_scope.mod;
+        const fn_info = zcu.typeToFunc(decl.ty).?;
+        const target = zcu.getTarget();
+        const ip = &zcu.intern_pool;
 
         var dg: DeclGen = .{
             .object = o,
@@ -1352,7 +1334,7 @@ pub const Object = struct {
         }
 
         // TODO: disable this if safety is off for the function scope
-        const ssp_buf_size = mod.comp.bin_file.options.stack_protector;
+        const ssp_buf_size = owner_mod.stack_protector;
         if (ssp_buf_size != 0) {
             try attributes.addFnAttr(.sspstrong, &o.builder);
             try attributes.addFnAttr(.{ .string = .{
@@ -1362,7 +1344,7 @@ pub const Object = struct {
         }
 
         // TODO: disable this if safety is off for the function scope
-        if (mod.comp.bin_file.options.stack_check) {
+        if (owner_mod.stack_check) {
             try attributes.addFnAttr(.{ .string = .{
                 .kind = try o.builder.string("probe-stack"),
                 .value = try o.builder.string("__zig_probe_stack"),
@@ -1385,20 +1367,22 @@ pub const Object = struct {
         var llvm_arg_i: u32 = 0;
 
         // This gets the LLVM values from the function and stores them in `dg.args`.
-        const sret = firstParamSRet(fn_info, mod);
+        const sret = firstParamSRet(fn_info, zcu);
         const ret_ptr: Builder.Value = if (sret) param: {
             const param = wip.arg(llvm_arg_i);
             llvm_arg_i += 1;
             break :param param;
         } else .none;
 
-        if (ccAbiPromoteInt(fn_info.cc, mod, Type.fromInterned(fn_info.return_type))) |s| switch (s) {
+        if (ccAbiPromoteInt(fn_info.cc, zcu, Type.fromInterned(fn_info.return_type))) |s| switch (s) {
             .signed => try attributes.addRetAttr(.signext, &o.builder),
             .unsigned => try attributes.addRetAttr(.zeroext, &o.builder),
         };
 
-        const err_return_tracing = Type.fromInterned(fn_info.return_type).isError(mod) and
-            mod.comp.config.any_error_tracing;
+        const comp = zcu.comp;
+
+        const err_return_tracing = Type.fromInterned(fn_info.return_type).isError(zcu) and
+            comp.config.any_error_tracing;
 
         const err_ret_trace: Builder.Value = if (err_return_tracing) param: {
             const param = wip.arg(llvm_arg_i);
@@ -1426,8 +1410,8 @@ pub const Object = struct {
                         const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[param_index]);
                         const param = wip.arg(llvm_arg_i);
 
-                        if (isByRef(param_ty, mod)) {
-                            const alignment = param_ty.abiAlignment(mod).toLlvm();
+                        if (isByRef(param_ty, zcu)) {
+                            const alignment = param_ty.abiAlignment(zcu).toLlvm();
                             const param_llvm_ty = param.typeOfWip(&wip);
                             const arg_ptr = try buildAllocaInner(&wip, false, param_llvm_ty, alignment, target);
                             _ = try wip.store(.normal, param, arg_ptr, alignment);
@@ -1443,12 +1427,12 @@ pub const Object = struct {
                         const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
                         const param_llvm_ty = try o.lowerType(param_ty);
                         const param = wip.arg(llvm_arg_i);
-                        const alignment = param_ty.abiAlignment(mod).toLlvm();
+                        const alignment = param_ty.abiAlignment(zcu).toLlvm();
 
                         try o.addByRefParamAttrs(&attributes, llvm_arg_i, alignment, it.byval_attr, param_llvm_ty);
                         llvm_arg_i += 1;
 
-                        if (isByRef(param_ty, mod)) {
+                        if (isByRef(param_ty, zcu)) {
                             args.appendAssumeCapacity(param);
                         } else {
                             args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, param, alignment, ""));
@@ -1458,12 +1442,12 @@ pub const Object = struct {
                         const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
                         const param_llvm_ty = try o.lowerType(param_ty);
                         const param = wip.arg(llvm_arg_i);
-                        const alignment = param_ty.abiAlignment(mod).toLlvm();
+                        const alignment = param_ty.abiAlignment(zcu).toLlvm();
 
                         try attributes.addParamAttr(llvm_arg_i, .noundef, &o.builder);
                         llvm_arg_i += 1;
 
-                        if (isByRef(param_ty, mod)) {
+                        if (isByRef(param_ty, zcu)) {
                             args.appendAssumeCapacity(param);
                         } else {
                             args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, param, alignment, ""));
@@ -1476,11 +1460,11 @@ pub const Object = struct {
                         llvm_arg_i += 1;
 
                         const param_llvm_ty = try o.lowerType(param_ty);
-                        const alignment = param_ty.abiAlignment(mod).toLlvm();
+                        const alignment = param_ty.abiAlignment(zcu).toLlvm();
                         const arg_ptr = try buildAllocaInner(&wip, false, param_llvm_ty, alignment, target);
                         _ = try wip.store(.normal, param, arg_ptr, alignment);
 
-                        args.appendAssumeCapacity(if (isByRef(param_ty, mod))
+                        args.appendAssumeCapacity(if (isByRef(param_ty, zcu))
                             arg_ptr
                         else
                             try wip.load(.normal, param_llvm_ty, arg_ptr, alignment, ""));
@@ -1488,14 +1472,14 @@ pub const Object = struct {
                     .slice => {
                         assert(!it.byval_attr);
                         const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
-                        const ptr_info = param_ty.ptrInfo(mod);
+                        const ptr_info = param_ty.ptrInfo(zcu);
 
                         if (math.cast(u5, it.zig_index - 1)) |i| {
                             if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
                                 try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
                             }
                         }
-                        if (param_ty.zigTypeTag(mod) != .Optional) {
+                        if (param_ty.zigTypeTag(zcu) != .Optional) {
                             try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
                         }
                         if (ptr_info.flags.is_const) {
@@ -1504,7 +1488,7 @@ pub const Object = struct {
                         const elem_align = (if (ptr_info.flags.alignment != .none)
                             @as(InternPool.Alignment, ptr_info.flags.alignment)
                         else
-                            Type.fromInterned(ptr_info.child).abiAlignment(mod).max(.@"1")).toLlvm();
+                            Type.fromInterned(ptr_info.child).abiAlignment(zcu).max(.@"1")).toLlvm();
                         try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder);
                         const ptr_param = wip.arg(llvm_arg_i);
                         llvm_arg_i += 1;
@@ -1521,7 +1505,7 @@ pub const Object = struct {
                         const field_types = it.types_buffer[0..it.types_len];
                         const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
                         const param_llvm_ty = try o.lowerType(param_ty);
-                        const param_alignment = param_ty.abiAlignment(mod).toLlvm();
+                        const param_alignment = param_ty.abiAlignment(zcu).toLlvm();
                         const arg_ptr = try buildAllocaInner(&wip, false, param_llvm_ty, param_alignment, target);
                         const llvm_ty = try o.builder.structType(.normal, field_types);
                         for (0..field_types.len) |field_i| {
@@ -1533,7 +1517,7 @@ pub const Object = struct {
                             _ = try wip.store(.normal, param, field_ptr, alignment);
                         }
 
-                        const is_by_ref = isByRef(param_ty, mod);
+                        const is_by_ref = isByRef(param_ty, zcu);
                         args.appendAssumeCapacity(if (is_by_ref)
                             arg_ptr
                         else
@@ -1551,11 +1535,11 @@ pub const Object = struct {
                         const param = wip.arg(llvm_arg_i);
                         llvm_arg_i += 1;
 
-                        const alignment = param_ty.abiAlignment(mod).toLlvm();
+                        const alignment = param_ty.abiAlignment(zcu).toLlvm();
                         const arg_ptr = try buildAllocaInner(&wip, false, param_llvm_ty, alignment, target);
                         _ = try wip.store(.normal, param, arg_ptr, alignment);
 
-                        args.appendAssumeCapacity(if (isByRef(param_ty, mod))
+                        args.appendAssumeCapacity(if (isByRef(param_ty, zcu))
                             arg_ptr
                         else
                             try wip.load(.normal, param_llvm_ty, arg_ptr, alignment, ""));
@@ -1566,11 +1550,11 @@ pub const Object = struct {
                         const param = wip.arg(llvm_arg_i);
                         llvm_arg_i += 1;
 
-                        const alignment = param_ty.abiAlignment(mod).toLlvm();
+                        const alignment = param_ty.abiAlignment(zcu).toLlvm();
                         const arg_ptr = try buildAllocaInner(&wip, false, param_llvm_ty, alignment, target);
                         _ = try wip.store(.normal, param, arg_ptr, alignment);
 
-                        args.appendAssumeCapacity(if (isByRef(param_ty, mod))
+                        args.appendAssumeCapacity(if (isByRef(param_ty, zcu))
                             arg_ptr
                         else
                             try wip.load(.normal, param_llvm_ty, arg_ptr, alignment, ""));
@@ -1584,14 +1568,12 @@ pub const Object = struct {
         var di_file: ?if (build_options.have_llvm) *llvm.DIFile else noreturn = null;
         var di_scope: ?if (build_options.have_llvm) *llvm.DIScope else noreturn = null;
 
-        const namespace = mod.namespacePtr(decl.src_namespace);
-
         if (o.di_builder) |dib| {
             di_file = try o.getDIFile(gpa, namespace.file_scope);
 
             const line_number = decl.src_line + 1;
-            const is_internal_linkage = decl.val.getExternFunc(mod) == null and
-                !mod.decl_exports.contains(decl_index);
+            const is_internal_linkage = decl.val.getExternFunc(zcu) == null and
+                !zcu.decl_exports.contains(decl_index);
             const noret_bit: c_uint = if (fn_info.return_type == .noreturn_type)
                 llvm.DIFlags.NoReturn
             else
@@ -1608,7 +1590,7 @@ pub const Object = struct {
                 true, // is definition
                 line_number + func.lbrace_line, // scope line
                 llvm.DIFlags.StaticMember | noret_bit,
-                mod.comp.bin_file.options.optimize_mode != .Debug,
+                owner_mod.optimize_mode != .Debug,
                 null, // decl_subprogram
             );
             try o.di_map.put(gpa, decl, subprogram.toNode());
@@ -1618,8 +1600,6 @@ pub const Object = struct {
             di_scope = subprogram.toScope();
         }
 
-        const single_threaded = namespace.file_scope.mod.single_threaded;
-
         var fg: FuncGen = .{
             .gpa = gpa,
             .air = air,
@@ -1631,7 +1611,7 @@ pub const Object = struct {
             .arg_index = 0,
             .func_inst_table = .{},
             .blocks = .{},
-            .sync_scope = if (single_threaded) .singlethread else .system,
+            .sync_scope = if (owner_mod.single_threaded) .singlethread else .system,
             .di_scope = di_scope,
             .di_file = di_file,
             .base_line = dg.decl.src_line,
@@ -1645,7 +1625,7 @@ pub const Object = struct {
         fg.genBody(air.getMainBody()) catch |err| switch (err) {
             error.CodegenFail => {
                 decl.analysis = .codegen_failure;
-                try mod.failed_decls.put(mod.gpa, decl_index, dg.err_msg.?);
+                try zcu.failed_decls.put(zcu.gpa, decl_index, dg.err_msg.?);
                 dg.err_msg = null;
                 return;
             },
@@ -1654,7 +1634,7 @@ pub const Object = struct {
 
         try fg.wip.finish();
 
-        try o.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
+        try o.updateExports(zcu, .{ .decl_index = decl_index }, zcu.getDeclExports(decl_index));
     }
 
     pub fn updateDecl(self: *Object, module: *Module, decl_index: InternPool.DeclIndex) !void {
@@ -2933,22 +2913,24 @@ pub const Object = struct {
         o: *Object,
         decl_index: InternPool.DeclIndex,
     ) Allocator.Error!Builder.Function.Index {
-        const mod = o.module;
-        const ip = &mod.intern_pool;
+        const zcu = o.module;
+        const ip = &zcu.intern_pool;
         const gpa = o.gpa;
-        const decl = mod.declPtr(decl_index);
+        const decl = zcu.declPtr(decl_index);
+        const namespace = zcu.namespacePtr(decl.src_namespace);
+        const owner_mod = namespace.file_scope.mod;
         const zig_fn_type = decl.ty;
         const gop = try o.decl_map.getOrPut(gpa, decl_index);
         if (gop.found_existing) return gop.value_ptr.ptr(&o.builder).kind.function;
 
         assert(decl.has_tv);
-        const fn_info = mod.typeToFunc(zig_fn_type).?;
-        const target = mod.getTarget();
-        const sret = firstParamSRet(fn_info, mod);
+        const fn_info = zcu.typeToFunc(zig_fn_type).?;
+        const target = owner_mod.resolved_target.result;
+        const sret = firstParamSRet(fn_info, zcu);
 
         const function_index = try o.builder.addFunction(
             try o.lowerType(zig_fn_type),
-            try o.builder.string(ip.stringToSlice(try decl.getFullyQualifiedName(mod))),
+            try o.builder.string(ip.stringToSlice(try decl.getFullyQualifiedName(zcu))),
             toLlvmAddressSpace(decl.@"addrspace", target),
         );
         gop.value_ptr.* = function_index.ptrConst(&o.builder).global;
@@ -2956,7 +2938,7 @@ pub const Object = struct {
         var attributes: Builder.FunctionAttributes.Wip = .{};
         defer attributes.deinit(&o.builder);
 
-        const is_extern = decl.isExtern(mod);
+        const is_extern = decl.isExtern(zcu);
         if (!is_extern) {
             function_index.setLinkage(.internal, &o.builder);
             function_index.setUnnamedAddr(.unnamed_addr, &o.builder);
@@ -2966,7 +2948,7 @@ pub const Object = struct {
                     .kind = try o.builder.string("wasm-import-name"),
                     .value = try o.builder.string(ip.stringToSlice(decl.name)),
                 } }, &o.builder);
-                if (ip.stringToSliceUnwrap(decl.getOwnedExternFunc(mod).?.lib_name)) |lib_name| {
+                if (ip.stringToSliceUnwrap(decl.getOwnedExternFunc(zcu).?.lib_name)) |lib_name| {
                     if (!std.mem.eql(u8, lib_name, "c")) try attributes.addFnAttr(.{ .string = .{
                         .kind = try o.builder.string("wasm-import-module"),
                         .value = try o.builder.string(lib_name),
@@ -2987,8 +2969,8 @@ pub const Object = struct {
             llvm_arg_i += 1;
         }
 
-        const err_return_tracing = Type.fromInterned(fn_info.return_type).isError(mod) and
-            mod.comp.config.any_error_tracing;
+        const err_return_tracing = Type.fromInterned(fn_info.return_type).isError(zcu) and
+            zcu.comp.config.any_error_tracing;
 
         if (err_return_tracing) {
             try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
@@ -3009,7 +2991,7 @@ pub const Object = struct {
             function_index.setAlignment(fn_info.alignment.toLlvm(), &o.builder);
 
         // Function attributes that are independent of analysis results of the function body.
-        try o.addCommonFnAttributes(&attributes);
+        try o.addCommonFnAttributes(&attributes, owner_mod);
 
         if (fn_info.return_type == .noreturn_type) try attributes.addFnAttr(.noreturn, &o.builder);
 
@@ -3022,14 +3004,14 @@ pub const Object = struct {
                 .byval => {
                     const param_index = it.zig_index - 1;
                     const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[param_index]);
-                    if (!isByRef(param_ty, mod)) {
+                    if (!isByRef(param_ty, zcu)) {
                         try o.addByValParamAttrs(&attributes, param_ty, param_index, fn_info, it.llvm_index - 1);
                     }
                 },
                 .byref => {
                     const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
                     const param_llvm_ty = try o.lowerType(param_ty);
-                    const alignment = param_ty.abiAlignment(mod);
+                    const alignment = param_ty.abiAlignment(zcu);
                     try o.addByRefParamAttrs(&attributes, it.llvm_index - 1, alignment.toLlvm(), it.byval_attr, param_llvm_ty);
                 },
                 .byref_mut => try attributes.addParamAttr(it.llvm_index - 1, .noundef, &o.builder),
@@ -3055,13 +3037,14 @@ pub const Object = struct {
     fn addCommonFnAttributes(
         o: *Object,
         attributes: *Builder.FunctionAttributes.Wip,
+        owner_mod: *Package.Module,
     ) Allocator.Error!void {
         const comp = o.module.comp;
 
-        if (!comp.bin_file.options.red_zone) {
+        if (!owner_mod.red_zone) {
             try attributes.addFnAttr(.noredzone, &o.builder);
         }
-        if (comp.bin_file.options.omit_frame_pointer) {
+        if (owner_mod.omit_frame_pointer) {
             try attributes.addFnAttr(.{ .string = .{
                 .kind = try o.builder.string("frame-pointer"),
                 .value = try o.builder.string("none"),
@@ -3073,7 +3056,7 @@ pub const Object = struct {
             } }, &o.builder);
         }
         try attributes.addFnAttr(.nounwind, &o.builder);
-        if (comp.unwind_tables) {
+        if (owner_mod.unwind_tables) {
             try attributes.addFnAttr(.{ .uwtable = Builder.Attribute.UwTable.default }, &o.builder);
         }
         if (comp.skip_linker_dependencies or comp.no_builtin) {
@@ -3084,26 +3067,27 @@ pub const Object = struct {
             // overflow instead of performing memcpy.
             try attributes.addFnAttr(.nobuiltin, &o.builder);
         }
-        if (comp.bin_file.options.optimize_mode == .ReleaseSmall) {
+        if (owner_mod.optimize_mode == .ReleaseSmall) {
             try attributes.addFnAttr(.minsize, &o.builder);
             try attributes.addFnAttr(.optsize, &o.builder);
         }
-        if (comp.bin_file.options.tsan) {
+        if (owner_mod.sanitize_thread) {
             try attributes.addFnAttr(.sanitize_thread, &o.builder);
         }
-        if (comp.getTarget().cpu.model.llvm_name) |s| {
+        const target = owner_mod.resolved_target.result;
+        if (target.cpu.model.llvm_name) |s| {
             try attributes.addFnAttr(.{ .string = .{
                 .kind = try o.builder.string("target-cpu"),
                 .value = try o.builder.string(s),
             } }, &o.builder);
         }
-        if (comp.bin_file.options.llvm_cpu_features) |s| {
+        if (owner_mod.resolved_target.llvm_cpu_features) |s| {
             try attributes.addFnAttr(.{ .string = .{
                 .kind = try o.builder.string("target-features"),
                 .value = try o.builder.string(std.mem.span(s)),
             } }, &o.builder);
         }
-        if (comp.getTarget().cpu.arch.isBpf()) {
+        if (target.cpu.arch.isBpf()) {
             try attributes.addFnAttr(.{ .string = .{
                 .kind = try o.builder.string("no-builtins"),
                 .value = .empty,
@@ -4646,6 +4630,100 @@ pub const Object = struct {
             .field_index = @intCast(field_index),
         });
     }
+
+    fn getCmpLtErrorsLenFunction(o: *Object) !Builder.Function.Index {
+        const name = try o.builder.string(lt_errors_fn_name);
+        if (o.builder.getGlobal(name)) |llvm_fn| return llvm_fn.ptrConst(&o.builder).kind.function;
+
+        const zcu = o.module;
+        const target = zcu.root_mod.resolved_target.result;
+        const function_index = try o.builder.addFunction(
+            try o.builder.fnType(.i1, &.{try o.errorIntType()}, .normal),
+            name,
+            toLlvmAddressSpace(.generic, target),
+        );
+
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+        try o.addCommonFnAttributes(&attributes, zcu.root_mod);
+
+        function_index.setLinkage(.internal, &o.builder);
+        function_index.setCallConv(.fastcc, &o.builder);
+        function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
+        return function_index;
+    }
+
+    fn getEnumTagNameFunction(o: *Object, enum_ty: Type) !Builder.Function.Index {
+        const zcu = o.module;
+        const ip = &zcu.intern_pool;
+        const enum_type = ip.indexToKey(enum_ty.toIntern()).enum_type;
+
+        // TODO: detect when the type changes and re-emit this function.
+        const gop = try o.decl_map.getOrPut(o.gpa, enum_type.decl);
+        if (gop.found_existing) return gop.value_ptr.ptrConst(&o.builder).kind.function;
+        errdefer assert(o.decl_map.remove(enum_type.decl));
+
+        const usize_ty = try o.lowerType(Type.usize);
+        const ret_ty = try o.lowerType(Type.slice_const_u8_sentinel_0);
+        const fqn = try zcu.declPtr(enum_type.decl).getFullyQualifiedName(zcu);
+        const target = zcu.root_mod.resolved_target.result;
+        const function_index = try o.builder.addFunction(
+            try o.builder.fnType(ret_ty, &.{try o.lowerType(Type.fromInterned(enum_type.tag_ty))}, .normal),
+            try o.builder.fmt("__zig_tag_name_{}", .{fqn.fmt(ip)}),
+            toLlvmAddressSpace(.generic, target),
+        );
+
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+        try o.addCommonFnAttributes(&attributes, zcu.root_mod);
+
+        function_index.setLinkage(.internal, &o.builder);
+        function_index.setCallConv(.fastcc, &o.builder);
+        function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
+        gop.value_ptr.* = function_index.ptrConst(&o.builder).global;
+
+        var wip = try Builder.WipFunction.init(&o.builder, function_index);
+        defer wip.deinit();
+        wip.cursor = .{ .block = try wip.block(0, "Entry") };
+
+        const bad_value_block = try wip.block(1, "BadValue");
+        const tag_int_value = wip.arg(0);
+        var wip_switch =
+            try wip.@"switch"(tag_int_value, bad_value_block, @intCast(enum_type.names.len));
+        defer wip_switch.finish(&wip);
+
+        for (0..enum_type.names.len) |field_index| {
+            const name = try o.builder.string(ip.stringToSlice(enum_type.names.get(ip)[field_index]));
+            const name_init = try o.builder.stringNullConst(name);
+            const name_variable_index =
+                try o.builder.addVariable(.empty, name_init.typeOf(&o.builder), .default);
+            try name_variable_index.setInitializer(name_init, &o.builder);
+            name_variable_index.setLinkage(.private, &o.builder);
+            name_variable_index.setMutability(.constant, &o.builder);
+            name_variable_index.setUnnamedAddr(.unnamed_addr, &o.builder);
+            name_variable_index.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
+
+            const name_val = try o.builder.structValue(ret_ty, &.{
+                name_variable_index.toConst(&o.builder),
+                try o.builder.intConst(usize_ty, name.slice(&o.builder).?.len),
+            });
+
+            const return_block = try wip.block(1, "Name");
+            const this_tag_int_value = try o.lowerValue(
+                (try zcu.enumValueFieldIndex(enum_ty, @intCast(field_index))).toIntern(),
+            );
+            try wip_switch.addCase(this_tag_int_value, return_block, &wip);
+
+            wip.cursor = .{ .block = return_block };
+            _ = try wip.ret(name_val);
+        }
+
+        wip.cursor = .{ .block = bad_value_block };
+        _ = try wip.@"unreachable"();
+
+        try wip.finish();
+        return function_index;
+    }
 };
 
 pub const DeclGen = struct {
@@ -4654,6 +4732,13 @@ pub const DeclGen = struct {
     decl_index: InternPool.DeclIndex,
     err_msg: ?*Module.ErrorMsg,
 
+    fn ownerModule(dg: DeclGen) *Package.Module {
+        const o = dg.object;
+        const zcu = o.module;
+        const namespace = zcu.namespacePtr(dg.decl.src_namespace);
+        return namespace.file_scope.mod;
+    }
+
     fn todo(dg: *DeclGen, comptime format: []const u8, args: anytype) Error {
         @setCold(true);
         assert(dg.err_msg == null);
@@ -5614,7 +5699,7 @@ pub const FuncGen = struct {
         const o = self.dg.object;
         const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
         const operand = try self.resolveInst(un_op);
-        const llvm_fn = try self.getCmpLtErrorsLenFunction();
+        const llvm_fn = try o.getCmpLtErrorsLenFunction();
         return self.wip.call(
             .normal,
             .fastcc,
@@ -6547,11 +6632,13 @@ pub const FuncGen = struct {
         const dib = o.di_builder orelse return .none;
         const ty_fn = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_fn;
 
-        const mod = o.module;
-        const func = mod.funcInfo(ty_fn.func);
+        const zcu = o.module;
+        const func = zcu.funcInfo(ty_fn.func);
         const decl_index = func.owner_decl;
-        const decl = mod.declPtr(decl_index);
-        const di_file = try o.getDIFile(self.gpa, mod.namespacePtr(decl.src_namespace).file_scope);
+        const decl = zcu.declPtr(decl_index);
+        const namespace = zcu.namespacePtr(decl.src_namespace);
+        const owner_mod = namespace.file_scope.mod;
+        const di_file = try o.getDIFile(self.gpa, zcu.namespacePtr(decl.src_namespace).file_scope);
         self.di_file = di_file;
         const line_number = decl.src_line + 1;
         const cur_debug_location = self.wip.llvm.builder.getCurrentDebugLocation2();
@@ -6562,18 +6649,18 @@ pub const FuncGen = struct {
             .base_line = self.base_line,
         });
 
-        const fqn = try decl.getFullyQualifiedName(mod);
+        const fqn = try decl.getFullyQualifiedName(zcu);
 
-        const is_internal_linkage = !mod.decl_exports.contains(decl_index);
-        const fn_ty = try mod.funcType(.{
+        const is_internal_linkage = !zcu.decl_exports.contains(decl_index);
+        const fn_ty = try zcu.funcType(.{
             .param_types = &.{},
             .return_type = .void_type,
         });
         const fn_di_ty = try o.lowerDebugType(fn_ty, .full);
         const subprogram = dib.createFunction(
             di_file.toScope(),
-            mod.intern_pool.stringToSlice(decl.name),
-            mod.intern_pool.stringToSlice(fqn),
+            zcu.intern_pool.stringToSlice(decl.name),
+            zcu.intern_pool.stringToSlice(fqn),
             di_file,
             line_number,
             fn_di_ty,
@@ -6581,7 +6668,7 @@ pub const FuncGen = struct {
             true, // is definition
             line_number + func.lbrace_line, // scope line
             llvm.DIFlags.StaticMember,
-            mod.comp.bin_file.options.optimize_mode != .Debug,
+            owner_mod.optimize_mode != .Debug,
             null, // decl_subprogram
         );
 
@@ -6676,11 +6763,12 @@ pub const FuncGen = struct {
             null;
         const debug_loc = llvm.getDebugLoc(self.prev_dbg_line, self.prev_dbg_column, self.di_scope.?, inlined_at);
         const insert_block = self.wip.cursor.block.toLlvm(&self.wip);
-        const mod = o.module;
-        if (isByRef(operand_ty, mod)) {
+        const zcu = o.module;
+        const owner_mod = self.dg.ownerModule();
+        if (isByRef(operand_ty, zcu)) {
             _ = dib.insertDeclareAtEnd(operand.toLlvm(&self.wip), di_local_var, debug_loc, insert_block);
-        } else if (o.module.comp.bin_file.options.optimize_mode == .Debug) {
-            const alignment = operand_ty.abiAlignment(mod).toLlvm();
+        } else if (owner_mod.optimize_mode == .Debug) {
+            const alignment = operand_ty.abiAlignment(zcu).toLlvm();
             const alloca = try self.buildAlloca(operand.typeOfWip(&self.wip), alignment);
             _ = try self.wip.store(.normal, operand, alloca, alignment);
             _ = dib.insertDeclareAtEnd(alloca.toLlvm(&self.wip), di_local_var, debug_loc, insert_block);
@@ -8729,9 +8817,10 @@ pub const FuncGen = struct {
 
             const debug_loc = llvm.getDebugLoc(lbrace_line, lbrace_col, self.di_scope.?, null);
             const insert_block = self.wip.cursor.block.toLlvm(&self.wip);
+            const owner_mod = self.dg.ownerModule();
             if (isByRef(inst_ty, mod)) {
                 _ = dib.insertDeclareAtEnd(arg_val.toLlvm(&self.wip), di_local_var, debug_loc, insert_block);
-            } else if (o.module.comp.bin_file.options.optimize_mode == .Debug) {
+            } else if (owner_mod.optimize_mode == .Debug) {
                 const alignment = inst_ty.abiAlignment(mod).toLlvm();
                 const alloca = try self.buildAlloca(arg_val.typeOfWip(&self.wip), alignment);
                 _ = try self.wip.store(.normal, arg_val, alloca, alignment);
@@ -8821,7 +8910,8 @@ pub const FuncGen = struct {
                 len,
                 if (ptr_ty.isVolatilePtr(mod)) .@"volatile" else .normal,
             );
-            if (safety and mod.comp.bin_file.options.valgrind) {
+            const owner_mod = self.dg.ownerModule();
+            if (safety and owner_mod.valgrind) {
                 try self.valgrindMarkUndef(dest_ptr, len);
             }
             return .none;
@@ -9137,7 +9227,8 @@ pub const FuncGen = struct {
                 } else {
                     _ = try self.wip.callMemSet(dest_ptr, dest_ptr_align, fill_byte, len, access_kind);
                 }
-                if (safety and mod.comp.bin_file.options.valgrind) {
+                const owner_mod = self.dg.ownerModule();
+                if (safety and owner_mod.valgrind) {
                     try self.valgrindMarkUndef(dest_ptr, len);
                 }
                 return .none;
@@ -9488,24 +9579,25 @@ pub const FuncGen = struct {
 
     fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !Builder.Function.Index {
         const o = self.dg.object;
-        const mod = o.module;
-        const enum_type = mod.intern_pool.indexToKey(enum_ty.toIntern()).enum_type;
+        const zcu = o.module;
+        const enum_type = zcu.intern_pool.indexToKey(enum_ty.toIntern()).enum_type;
 
         // TODO: detect when the type changes and re-emit this function.
         const gop = try o.named_enum_map.getOrPut(o.gpa, enum_type.decl);
         if (gop.found_existing) return gop.value_ptr.*;
         errdefer assert(o.named_enum_map.remove(enum_type.decl));
 
-        const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod);
+        const fqn = try zcu.declPtr(enum_type.decl).getFullyQualifiedName(zcu);
+        const target = zcu.root_mod.resolved_target.result;
         const function_index = try o.builder.addFunction(
             try o.builder.fnType(.i1, &.{try o.lowerType(Type.fromInterned(enum_type.tag_ty))}, .normal),
-            try o.builder.fmt("__zig_is_named_enum_value_{}", .{fqn.fmt(&mod.intern_pool)}),
-            toLlvmAddressSpace(.generic, mod.getTarget()),
+            try o.builder.fmt("__zig_is_named_enum_value_{}", .{fqn.fmt(&zcu.intern_pool)}),
+            toLlvmAddressSpace(.generic, target),
         );
 
         var attributes: Builder.FunctionAttributes.Wip = .{};
         defer attributes.deinit(&o.builder);
-        try o.addCommonFnAttributes(&attributes);
+        try o.addCommonFnAttributes(&attributes, zcu.root_mod);
 
         function_index.setLinkage(.internal, &o.builder);
         function_index.setCallConv(.fastcc, &o.builder);
@@ -9524,7 +9616,7 @@ pub const FuncGen = struct {
 
         for (0..enum_type.names.len) |field_index| {
             const this_tag_int_value = try o.lowerValue(
-                (try mod.enumValueFieldIndex(enum_ty, @intCast(field_index))).toIntern(),
+                (try zcu.enumValueFieldIndex(enum_ty, @intCast(field_index))).toIntern(),
             );
             try wip_switch.addCase(this_tag_int_value, named_block, &wip);
         }
@@ -9544,7 +9636,7 @@ pub const FuncGen = struct {
         const operand = try self.resolveInst(un_op);
         const enum_ty = self.typeOf(un_op);
 
-        const llvm_fn = try self.getEnumTagNameFunction(enum_ty);
+        const llvm_fn = try o.getEnumTagNameFunction(enum_ty);
         return self.wip.call(
             .normal,
             .fastcc,
@@ -9556,100 +9648,6 @@ pub const FuncGen = struct {
         );
     }
 
-    fn getEnumTagNameFunction(self: *FuncGen, enum_ty: Type) !Builder.Function.Index {
-        const o = self.dg.object;
-        const mod = o.module;
-        const ip = &mod.intern_pool;
-        const enum_type = ip.indexToKey(enum_ty.toIntern()).enum_type;
-
-        // TODO: detect when the type changes and re-emit this function.
-        const gop = try o.decl_map.getOrPut(o.gpa, enum_type.decl);
-        if (gop.found_existing) return gop.value_ptr.ptrConst(&o.builder).kind.function;
-        errdefer assert(o.decl_map.remove(enum_type.decl));
-
-        const usize_ty = try o.lowerType(Type.usize);
-        const ret_ty = try o.lowerType(Type.slice_const_u8_sentinel_0);
-        const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod);
-        const function_index = try o.builder.addFunction(
-            try o.builder.fnType(ret_ty, &.{try o.lowerType(Type.fromInterned(enum_type.tag_ty))}, .normal),
-            try o.builder.fmt("__zig_tag_name_{}", .{fqn.fmt(ip)}),
-            toLlvmAddressSpace(.generic, mod.getTarget()),
-        );
-
-        var attributes: Builder.FunctionAttributes.Wip = .{};
-        defer attributes.deinit(&o.builder);
-        try o.addCommonFnAttributes(&attributes);
-
-        function_index.setLinkage(.internal, &o.builder);
-        function_index.setCallConv(.fastcc, &o.builder);
-        function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
-        gop.value_ptr.* = function_index.ptrConst(&o.builder).global;
-
-        var wip = try Builder.WipFunction.init(&o.builder, function_index);
-        defer wip.deinit();
-        wip.cursor = .{ .block = try wip.block(0, "Entry") };
-
-        const bad_value_block = try wip.block(1, "BadValue");
-        const tag_int_value = wip.arg(0);
-        var wip_switch =
-            try wip.@"switch"(tag_int_value, bad_value_block, @intCast(enum_type.names.len));
-        defer wip_switch.finish(&wip);
-
-        for (0..enum_type.names.len) |field_index| {
-            const name = try o.builder.string(ip.stringToSlice(enum_type.names.get(ip)[field_index]));
-            const name_init = try o.builder.stringNullConst(name);
-            const name_variable_index =
-                try o.builder.addVariable(.empty, name_init.typeOf(&o.builder), .default);
-            try name_variable_index.setInitializer(name_init, &o.builder);
-            name_variable_index.setLinkage(.private, &o.builder);
-            name_variable_index.setMutability(.constant, &o.builder);
-            name_variable_index.setUnnamedAddr(.unnamed_addr, &o.builder);
-            name_variable_index.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
-
-            const name_val = try o.builder.structValue(ret_ty, &.{
-                name_variable_index.toConst(&o.builder),
-                try o.builder.intConst(usize_ty, name.slice(&o.builder).?.len),
-            });
-
-            const return_block = try wip.block(1, "Name");
-            const this_tag_int_value = try o.lowerValue(
-                (try mod.enumValueFieldIndex(enum_ty, @intCast(field_index))).toIntern(),
-            );
-            try wip_switch.addCase(this_tag_int_value, return_block, &wip);
-
-            wip.cursor = .{ .block = return_block };
-            _ = try wip.ret(name_val);
-        }
-
-        wip.cursor = .{ .block = bad_value_block };
-        _ = try wip.@"unreachable"();
-
-        try wip.finish();
-        return function_index;
-    }
-
-    fn getCmpLtErrorsLenFunction(self: *FuncGen) !Builder.Function.Index {
-        const o = self.dg.object;
-
-        const name = try o.builder.string(lt_errors_fn_name);
-        if (o.builder.getGlobal(name)) |llvm_fn| return llvm_fn.ptrConst(&o.builder).kind.function;
-
-        const function_index = try o.builder.addFunction(
-            try o.builder.fnType(.i1, &.{try o.errorIntType()}, .normal),
-            name,
-            toLlvmAddressSpace(.generic, o.module.getTarget()),
-        );
-
-        var attributes: Builder.FunctionAttributes.Wip = .{};
-        defer attributes.deinit(&o.builder);
-        try o.addCommonFnAttributes(&attributes);
-
-        function_index.setLinkage(.internal, &o.builder);
-        function_index.setCallConv(.fastcc, &o.builder);
-        function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
-        return function_index;
-    }
-
     fn airErrorName(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
         const o = self.dg.object;
         const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
src/link/Elf/Symbol.zig
@@ -43,7 +43,7 @@ pub fn outputShndx(symbol: Symbol) ?u16 {
 }
 
 pub fn isLocal(symbol: Symbol, elf_file: *Elf) bool {
-    if (elf_file.isRelocatable()) return symbol.elfSym(elf_file).st_bind() == elf.STB_LOCAL;
+    if (elf_file.base.isRelocatable()) return symbol.elfSym(elf_file).st_bind() == elf.STB_LOCAL;
     return !(symbol.flags.import or symbol.flags.@"export");
 }
 
@@ -186,7 +186,7 @@ const GetOrCreateZigGotEntryResult = struct {
 };
 
 pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, elf_file: *Elf) !GetOrCreateZigGotEntryResult {
-    assert(!elf_file.isRelocatable());
+    assert(!elf_file.base.isRelocatable());
     assert(symbol.flags.needs_zig_got);
     if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.extra(elf_file).?.zig_got };
     const index = try elf_file.zig_got.addSymbol(symbol_index, elf_file);
@@ -237,7 +237,7 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void {
     const st_shndx = blk: {
         if (symbol.flags.has_copy_rel) break :blk elf_file.copy_rel_section_index.?;
         if (file_ptr == .shared_object or esym.st_shndx == elf.SHN_UNDEF) break :blk elf.SHN_UNDEF;
-        if (elf_file.isRelocatable() and esym.st_shndx == elf.SHN_COMMON) break :blk elf.SHN_COMMON;
+        if (elf_file.base.isRelocatable() and esym.st_shndx == elf.SHN_COMMON) break :blk elf.SHN_COMMON;
         if (symbol.atom(elf_file) == null and file_ptr != .linker_defined) break :blk elf.SHN_ABS;
         break :blk symbol.outputShndx() orelse elf.SHN_UNDEF;
     };
src/link/Elf/ZigObject.zig
@@ -926,7 +926,7 @@ fn updateDeclCode(
                 sym.value = atom_ptr.value;
                 esym.st_value = atom_ptr.value;
 
-                if (!elf_file.isRelocatable()) {
+                if (!elf_file.base.isRelocatable()) {
                     log.debug("  (writing new offset table entry)", .{});
                     assert(sym.flags.has_zig_got);
                     const extra = sym.extra(elf_file).?;
@@ -944,7 +944,7 @@ fn updateDeclCode(
         sym.flags.needs_zig_got = true;
         esym.st_value = atom_ptr.value;
 
-        if (!elf_file.isRelocatable()) {
+        if (!elf_file.base.isRelocatable()) {
             const gop = try sym.getOrCreateZigGotEntry(sym_index, elf_file);
             try elf_file.zig_got.writeOne(elf_file, gop.index);
         }
@@ -1262,7 +1262,7 @@ fn updateLazySymbol(
     local_sym.flags.needs_zig_got = true;
     local_esym.st_value = atom_ptr.value;
 
-    if (!elf_file.isRelocatable()) {
+    if (!elf_file.base.isRelocatable()) {
         const gop = try local_sym.getOrCreateZigGotEntry(symbol_index, elf_file);
         try elf_file.zig_got.writeOne(elf_file, gop.index);
     }
src/link/Coff.zig
@@ -3,7 +3,7 @@
 //! LLD for traditional linking (linking relocatable object files).
 //! LLD is also the default linker for LLVM.
 
-/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
+/// If this is not null, an object file is created by LLVM and emitted to intermediary_basename.
 llvm_object: ?*LlvmObject = null,
 
 base: link.File,
@@ -1711,17 +1711,22 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
     const tracy = trace(@src());
     defer tracy.end();
 
+    const gpa = comp.gpa;
+
     if (self.llvm_object) |llvm_object| {
-        return try llvm_object.flushModule(comp, prog_node);
+        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;
     }
 
     var sub_prog_node = prog_node.start("COFF Flush", 0);
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    const gpa = self.base.comp.gpa;
-
-    const module = self.base.comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
+    const module = comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
 
     if (self.lazy_syms.getPtr(.none)) |metadata| {
         // Most lazy symbols can be updated on first use, but
@@ -1822,7 +1827,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
     try self.writeDataDirectoriesHeaders();
     try self.writeSectionHeaders();
 
-    if (self.entry_addr == null and self.base.comp.config.output_mode == .Exe) {
+    if (self.entry_addr == null and comp.config.output_mode == .Exe) {
         log.debug("flushing. no_entry_point_found = true\n", .{});
         self.base.error_flags.no_entry_point_found = true;
     } else {
src/link/Elf.zig
@@ -27,7 +27,7 @@ version_script: ?[]const u8,
 
 ptr_width: PtrWidth,
 
-/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
+/// If this is not null, an object file is created by LLVM and emitted to intermediary_basename.
 llvm_object: ?*LlvmObject = null,
 
 /// A list of all input files.
@@ -1031,24 +1031,23 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     const tracy = trace(@src());
     defer tracy.end();
 
-    if (self.llvm_object) |llvm_object| {
-        try llvm_object.flushModule(comp, prog_node);
+    const gpa = comp.gpa;
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+    defer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
 
-        const use_lld = build_options.have_llvm and self.base.comp.config.use_lld;
+    if (self.llvm_object) |llvm_object| {
+        try self.base.emitLlvmObject(arena, llvm_object, prog_node);
+        const use_lld = build_options.have_llvm and comp.config.use_lld;
         if (use_lld) return;
     }
 
-    const gpa = self.base.comp.gpa;
     var sub_prog_node = prog_node.start("ELF Flush", 0);
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
-
-    const target = self.base.comp.root_mod.resolved_target.result;
-    const link_mode = self.base.comp.config.link_mode;
+    const target = comp.root_mod.resolved_target.result;
+    const link_mode = comp.config.link_mode;
     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});
     const module_obj_path: ?[]const u8 = if (self.base.intermediary_basename) |path| blk: {
@@ -1060,7 +1059,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     } else null;
 
     // --verbose-link
-    if (self.base.comp.verbose_link) try self.dumpArgv(comp);
+    if (comp.verbose_link) try self.dumpArgv(comp);
 
     const csu = try CsuObjects.init(arena, comp);
     const compiler_rt_path: ?[]const u8 = blk: {
@@ -1082,8 +1081,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     if (csu.crti) |v| try positionals.append(.{ .path = v });
     if (csu.crtbegin) |v| try positionals.append(.{ .path = v });
 
-    try positionals.ensureUnusedCapacity(self.base.comp.objects.len);
-    positionals.appendSliceAssumeCapacity(self.base.comp.objects);
+    try positionals.ensureUnusedCapacity(comp.objects.len);
+    positionals.appendSliceAssumeCapacity(comp.objects);
 
     // This is a set of object files emitted by clang in a single `build-exe` invocation.
     // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
@@ -1106,13 +1105,13 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
         var test_path = std.ArrayList(u8).init(gpa);
         defer test_path.deinit();
         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;
                 _ = try rpath_table.put(lib_dir_path, {});
             }
         }
-        for (self.base.comp.objects) |obj| {
+        for (comp.objects) |obj| {
             if (Compilation.classifyFileExt(obj.path) == .shared_library) {
                 const lib_dir_path = std.fs.path.dirname(obj.path) orelse continue;
                 if (obj.loption) continue;
@@ -1122,7 +1121,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     }
 
     // TSAN
-    if (self.base.comp.config.any_sanitize_thread) {
+    if (comp.config.any_sanitize_thread) {
         try positionals.append(.{ .path = comp.tsan_static_lib.?.full_object_path });
     }
 
@@ -1146,27 +1145,27 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
 
     var system_libs = std.ArrayList(SystemLib).init(arena);
 
-    try system_libs.ensureUnusedCapacity(self.base.comp.system_libs.values().len);
-    for (self.base.comp.system_libs.values()) |lib_info| {
+    try system_libs.ensureUnusedCapacity(comp.system_libs.values().len);
+    for (comp.system_libs.values()) |lib_info| {
         system_libs.appendAssumeCapacity(.{ .needed = lib_info.needed, .path = lib_info.path.? });
     }
 
     // libc++ dep
-    if (self.base.comp.config.link_libcpp) {
+    if (comp.config.link_libcpp) {
         try system_libs.ensureUnusedCapacity(2);
         system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
         system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
     }
 
     // libunwind dep
-    if (self.base.comp.config.link_libunwind) {
+    if (comp.config.link_libunwind) {
         try system_libs.append(.{ .path = comp.libunwind_static_lib.?.full_object_path });
     }
 
     // libc dep
     self.base.error_flags.missing_libc = false;
-    if (self.base.comp.config.link_libc) {
-        if (self.base.comp.libc_installation) |lc| {
+    if (comp.config.link_libc) {
+        if (comp.libc_installation) |lc| {
             const flags = target_util.libcFullLinkFlags(target);
             try system_libs.ensureUnusedCapacity(flags.len);
 
@@ -1305,7 +1304,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     // Look for entry address in objects if not set by the incremental compiler.
     if (self.entry_index == null) {
         const entry: ?[]const u8 = entry: {
-            if (self.base.comp.config.entry) |entry| break :entry entry;
+            if (comp.config.entry) |entry| break :entry entry;
             if (!self.base.isDynLib()) break :entry "_start";
             break :entry null;
         };
src/link/MachO.zig
@@ -1,6 +1,6 @@
 base: File,
 
-/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
+/// If this is not null, an object file is created by LLVM and emitted to intermediary_basename.
 llvm_object: ?*LlvmObject = null,
 
 /// Debug symbols bundle (or dSym).
@@ -352,22 +352,23 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
     const tracy = trace(@src());
     defer tracy.end();
 
-    if (self.llvm_object) |llvm_object| {
-        return try llvm_object.flushModule(comp, prog_node);
-    }
-
-    const gpa = self.base.comp.gpa;
+    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);
+        return;
+    }
+
     var sub_prog_node = prog_node.start("MachO Flush", 0);
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    const output_mode = self.base.comp.config.output_mode;
-    const module = self.base.comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
-    const target = self.base.comp.root_mod.resolved_target.result;
+    const output_mode = comp.config.output_mode;
+    const module = comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
+    const target = comp.root_mod.resolved_target.result;
 
     if (self.lazy_syms.getPtr(.none)) |metadata| {
         // Most lazy symbols can be updated on first use, but
@@ -619,7 +620,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
                 .stacksize = self.base.stack_size,
             });
         },
-        .Lib => if (self.base.comp.config.link_mode == .Dynamic) {
+        .Lib => if (comp.config.link_mode == .Dynamic) {
             try load_commands.writeDylibIdLC(self, lc_writer);
         },
         else => {},
src/link/Wasm.zig
@@ -3700,8 +3700,15 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     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();
+
     if (wasm.llvm_object) |llvm_object| {
-        return try llvm_object.flushModule(comp, prog_node);
+        try wasm.base.emitLlvmObject(arena, llvm_object, prog_node);
+        return;
     }
 
     var sub_prog_node = prog_node.start("Wasm Flush", 0);
@@ -3711,13 +3718,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     // ensure the error names table is populated when an error name is referenced
     try wasm.populateErrorNameTable();
 
-    // Used for all temporary memory allocated during flushin
-    const gpa = wasm.base.comp.gpa;
-    var arena_instance = std.heap.ArenaAllocator.init(gpa);
-    defer arena_instance.deinit();
-    const arena = arena_instance.allocator();
-
-    const objects = wasm.base.comp.objects;
+    const objects = comp.objects;
 
     // Positional arguments to the linker such as object files and static archives.
     var positionals = std.ArrayList([]const u8).init(arena);
@@ -3755,7 +3756,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     try wasm.markReferences();
     try wasm.setupErrorsLen();
     try wasm.setupImports();
-    if (wasm.base.comp.module) |mod| {
+    if (comp.module) |mod| {
         var decl_it = wasm.decls.iterator();
         while (decl_it.next()) |entry| {
             const decl = mod.declPtr(entry.key_ptr.*);
@@ -3810,7 +3811,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
         }
 
         if (wasm.dwarf) |*dwarf| {
-            try dwarf.flushModule(wasm.base.comp.module.?);
+            try dwarf.flushModule(comp.module.?);
         }
     }
 
src/link.zig
@@ -18,6 +18,7 @@ const Module = @import("Module.zig");
 const InternPool = @import("InternPool.zig");
 const Type = @import("type.zig").Type;
 const TypedValue = @import("TypedValue.zig");
+const LlvmObject = @import("codegen/llvm.zig").Object;
 
 /// When adding a new field, remember to update `hashAddSystemLibs`.
 /// These are *always* dynamically linked. Static libraries will be
@@ -1046,6 +1047,51 @@ pub const File = struct {
         return output_mode == .Lib and !self.isStatic();
     }
 
+    pub fn resolveEmitLoc(
+        base: File,
+        arena: Allocator,
+        opt_loc: ?Compilation.EmitLoc,
+    ) Allocator.Error!?[*:0]u8 {
+        const loc = opt_loc orelse return null;
+        const slice = if (loc.directory) |directory|
+            try directory.joinZ(arena, &.{loc.basename})
+        else
+            try base.emit.basenamePath(arena, loc.basename);
+        return slice.ptr;
+    }
+
+    pub fn emitLlvmObject(
+        base: File,
+        arena: Allocator,
+        llvm_object: *LlvmObject,
+        prog_node: *std.Progress.Node,
+    ) !void {
+        const comp = base.comp;
+
+        var sub_prog_node = prog_node.start("LLVM Emit Object", 0);
+        sub_prog_node.activate();
+        sub_prog_node.context.refresh();
+        defer sub_prog_node.end();
+
+        try llvm_object.emit(comp, .{
+            .pre_ir_path = comp.verbose_llvm_ir,
+            .pre_bc_path = comp.verbose_llvm_bc,
+            .bin_path = try base.resolveEmitLoc(arena, .{
+                .directory = null,
+                .basename = base.intermediary_basename.?,
+            }),
+            .asm_path = try base.resolveEmitLoc(arena, comp.emit_asm),
+            .post_llvm_ir_path = try base.resolveEmitLoc(arena, comp.emit_llvm_ir),
+            .post_llvm_bc_path = try base.resolveEmitLoc(arena, comp.emit_llvm_bc),
+
+            .is_debug = comp.root_mod.optimize_mode == .Debug,
+            .is_small = comp.root_mod.optimize_mode == .ReleaseSmall,
+            .time_report = comp.time_report,
+            .sanitize_thread = comp.config.any_sanitize_thread,
+            .lto = comp.config.lto,
+        });
+    }
+
     pub const C = @import("link/C.zig");
     pub const Coff = @import("link/Coff.zig");
     pub const Plan9 = @import("link/Plan9.zig");