Commit b8674910d4

Andrew Kelley <andrew@ziglang.org>
2023-12-29 00:42:54
restore -fno-emit-bin -femit-llvm-ir functionality
Now, link.File will always be null when -fno-emit-bin is specified, and in the case that LLVM artifacts are still required, the Zcu instance has an LlvmObject.
1 parent 92cc2f4
src/Compilation.zig
@@ -39,6 +39,7 @@ const Zir = @import("Zir.zig");
 const Autodoc = @import("Autodoc.zig");
 const resinator = @import("resinator.zig");
 const Builtin = @import("Builtin.zig");
+const LlvmObject = @import("codegen/llvm.zig").Object;
 
 pub const Config = @import("Compilation/Config.zig");
 
@@ -1434,6 +1435,7 @@ pub fn create(gpa: Allocator, options: CreateOptions) !*Compilation {
                 .emit_h = emit_h,
                 .tmp_hack_arena = std.heap.ArenaAllocator.init(gpa),
                 .error_limit = error_limit,
+                .llvm_object = null,
             };
             try zcu.init();
             break :blk zcu;
@@ -1683,6 +1685,17 @@ pub fn create(gpa: Allocator, options: CreateOptions) !*Compilation {
             },
         }
 
+        // Handle the case of e.g. -fno-emit-bin -femit-llvm-ir.
+        if (options.emit_bin == null and (comp.verbose_llvm_ir != null or
+            comp.verbose_llvm_bc != null or
+            (use_llvm and comp.emit_asm != null) or
+            comp.emit_llvm_ir != null or
+            comp.emit_llvm_bc != null))
+        {
+            if (build_options.only_c) unreachable;
+            if (opt_zcu) |zcu| zcu.llvm_object = try LlvmObject.create(arena, comp);
+        }
+
         comp.arena = arena_allocator;
         break :comp comp;
     };
@@ -2016,6 +2029,12 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
     const tracy_trace = trace(@src());
     defer tracy_trace.end();
 
+    // This arena is scoped to this one update.
+    const gpa = comp.gpa;
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+    defer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
+
     comp.clearMiscFailures();
     comp.last_update_was_cache_hit = false;
 
@@ -2034,7 +2053,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
 
             man = comp.cache_parent.obtain();
             whole.cache_manifest = &man;
-            try comp.addNonIncrementalStuffToCacheManifest(&man);
+            try addNonIncrementalStuffToCacheManifest(comp, arena, &man);
 
             const is_hit = man.hit() catch |err| {
                 const i = man.failed_file_index orelse return err;
@@ -2065,8 +2084,8 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
                 tmp_dir_rand_int = std.crypto.random.int(u64);
                 const tmp_dir_sub_path = "tmp" ++ s ++ Package.Manifest.hex64(tmp_dir_rand_int);
 
-                const path = try comp.local_cache_directory.join(comp.gpa, &.{tmp_dir_sub_path});
-                errdefer comp.gpa.free(path);
+                const path = try comp.local_cache_directory.join(gpa, &.{tmp_dir_sub_path});
+                errdefer gpa.free(path);
 
                 const handle = try comp.local_cache_directory.handle.makeOpenPath(tmp_dir_sub_path, .{});
                 errdefer handle.close();
@@ -2100,10 +2119,6 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
                     .directory = tmp_artifact_directory,
                     .sub_path = std.fs.path.basename(sub_path),
                 };
-                // It's a bit strange to use the Compilation arena allocator here
-                // but in practice it won't leak much and usually whole cache mode
-                // will be combined with exactly one call to update().
-                const arena = comp.arena.allocator();
                 comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts);
             }
         },
@@ -2127,7 +2142,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
     }
 
     if (comp.module) |module| {
-        module.compile_log_text.shrinkAndFree(module.gpa, 0);
+        module.compile_log_text.shrinkAndFree(gpa, 0);
         module.generation += 1;
 
         // Make sure std.zig is inside the import_table. We unconditionally need
@@ -2189,7 +2204,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
                 comp.root_name,
                 @as(usize, @intFromPtr(module)),
             });
-            module.intern_pool.dumpGenericInstances(comp.gpa);
+            module.intern_pool.dumpGenericInstances(gpa);
         }
 
         if (comp.config.is_test and comp.totalErrorCount() == 0) {
@@ -2222,8 +2237,23 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
             };
         }
 
-        if (comp.module) |module| {
-            try link.File.C.flushEmitH(module);
+        if (comp.module) |zcu| {
+            try link.File.C.flushEmitH(zcu);
+
+            if (zcu.llvm_object) |llvm_object| {
+                if (build_options.only_c) unreachable;
+                const default_emit = switch (comp.cache_use) {
+                    .whole => |whole| .{
+                        .directory = whole.tmp_artifact_directory.?,
+                        .sub_path = "dummy",
+                    },
+                    .incremental => |incremental| .{
+                        .directory = incremental.artifact_directory,
+                        .sub_path = "dummy",
+                    },
+                };
+                try emitLlvmObject(comp, arena, default_emit, null, llvm_object, main_progress_node);
+            }
         }
     }
 
@@ -2238,7 +2268,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
             // Close tmp dir and link.File to avoid open handle during rename.
             if (whole.tmp_artifact_directory) |*tmp_directory| {
                 tmp_directory.handle.close();
-                if (tmp_directory.path) |p| comp.gpa.free(p);
+                if (tmp_directory.path) |p| gpa.free(p);
                 whole.tmp_artifact_directory = null;
             } else unreachable;
 
@@ -2389,13 +2419,13 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
 /// anything from the link cache manifest.
 pub const link_hash_implementation_version = 10;
 
-fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
+fn addNonIncrementalStuffToCacheManifest(
+    comp: *Compilation,
+    arena: Allocator,
+    man: *Cache.Manifest,
+) !void {
     const gpa = comp.gpa;
 
-    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
-
     comptime assert(link_hash_implementation_version == 10);
 
     if (comp.module) |mod| {
@@ -2568,6 +2598,50 @@ fn emitOthers(comp: *Compilation) void {
     }
 }
 
+pub fn emitLlvmObject(
+    comp: *Compilation,
+    arena: Allocator,
+    default_emit: Emit,
+    bin_emit_loc: ?EmitLoc,
+    llvm_object: *LlvmObject,
+    prog_node: *std.Progress.Node,
+) !void {
+    if (build_options.only_c) @compileError("unreachable");
+
+    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(.{
+        .pre_ir_path = comp.verbose_llvm_ir,
+        .pre_bc_path = comp.verbose_llvm_bc,
+        .bin_path = try resolveEmitLoc(arena, default_emit, bin_emit_loc),
+        .asm_path = try resolveEmitLoc(arena, default_emit, comp.emit_asm),
+        .post_ir_path = try resolveEmitLoc(arena, default_emit, comp.emit_llvm_ir),
+        .post_bc_path = try resolveEmitLoc(arena, default_emit, 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,
+    });
+}
+
+fn resolveEmitLoc(
+    arena: Allocator,
+    default_emit: Emit,
+    opt_loc: ?EmitLoc,
+) Allocator.Error!?[*:0]const u8 {
+    const loc = opt_loc orelse return null;
+    const slice = if (loc.directory) |directory|
+        try directory.joinZ(arena, &.{loc.basename})
+    else
+        try default_emit.basenamePath(arena, loc.basename);
+    return slice.ptr;
+}
+
 fn reportMultiModuleErrors(mod: *Module) !void {
     // Some cases can give you a whole bunch of multi-module errors, which it's not helpful to
     // print all of, so we'll cap the number of these to emit.
src/link.zig
@@ -984,49 +984,16 @@ 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]const 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(.{
-            .pre_ir_path = comp.verbose_llvm_ir,
-            .pre_bc_path = comp.verbose_llvm_bc,
-            .bin_path = try base.resolveEmitLoc(arena, .{
-                .directory = null,
-                .basename = base.zcu_object_sub_path.?,
-            }),
-            .asm_path = try base.resolveEmitLoc(arena, comp.emit_asm),
-            .post_ir_path = try base.resolveEmitLoc(arena, comp.emit_llvm_ir),
-            .post_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,
-        });
+        return base.comp.emitLlvmObject(arena, base.emit, .{
+            .directory = null,
+            .basename = base.zcu_object_sub_path.?,
+        }, llvm_object, prog_node);
     }
 
     pub const C = @import("link/C.zig");
src/Module.zig
@@ -37,6 +37,7 @@ const clang = @import("clang.zig");
 const InternPool = @import("InternPool.zig");
 const Alignment = InternPool.Alignment;
 const BuiltinFn = std.zig.BuiltinFn;
+const LlvmObject = @import("codegen/llvm.zig").Object;
 
 comptime {
     @setEvalBranchQuota(4000);
@@ -53,6 +54,10 @@ comptime {
 /// General-purpose allocator. Used for both temporary and long-term storage.
 gpa: Allocator,
 comp: *Compilation,
+/// Usually, the LlvmObject is managed by linker code, however, in the case
+/// that -fno-emit-bin is specified, the linker code never executes, so we
+/// store the LlvmObject here.
+llvm_object: ?*LlvmObject,
 
 /// Pointer to externally managed resource.
 root_mod: *Package.Module,
@@ -2476,36 +2481,41 @@ pub fn init(mod: *Module) !void {
     try mod.global_error_set.put(gpa, .empty, {});
 }
 
-pub fn deinit(mod: *Module) void {
-    const gpa = mod.gpa;
+pub fn deinit(zcu: *Zcu) void {
+    const gpa = zcu.gpa;
+
+    if (zcu.llvm_object) |llvm_object| {
+        if (build_options.only_c) unreachable;
+        llvm_object.deinit();
+    }
 
-    for (mod.import_table.keys()) |key| {
+    for (zcu.import_table.keys()) |key| {
         gpa.free(key);
     }
-    var failed_decls = mod.failed_decls;
-    mod.failed_decls = .{};
-    for (mod.import_table.values()) |value| {
-        value.destroy(mod);
+    var failed_decls = zcu.failed_decls;
+    zcu.failed_decls = .{};
+    for (zcu.import_table.values()) |value| {
+        value.destroy(zcu);
     }
-    mod.import_table.deinit(gpa);
+    zcu.import_table.deinit(gpa);
 
-    for (mod.embed_table.keys(), mod.embed_table.values()) |path, embed_file| {
+    for (zcu.embed_table.keys(), zcu.embed_table.values()) |path, embed_file| {
         gpa.free(path);
         gpa.destroy(embed_file);
     }
-    mod.embed_table.deinit(gpa);
+    zcu.embed_table.deinit(gpa);
 
-    mod.compile_log_text.deinit(gpa);
+    zcu.compile_log_text.deinit(gpa);
 
-    mod.local_zir_cache.handle.close();
-    mod.global_zir_cache.handle.close();
+    zcu.local_zir_cache.handle.close();
+    zcu.global_zir_cache.handle.close();
 
     for (failed_decls.values()) |value| {
         value.destroy(gpa);
     }
     failed_decls.deinit(gpa);
 
-    if (mod.emit_h) |emit_h| {
+    if (zcu.emit_h) |emit_h| {
         for (emit_h.failed_decls.values()) |value| {
             value.destroy(gpa);
         }
@@ -2514,68 +2524,68 @@ pub fn deinit(mod: *Module) void {
         emit_h.allocated_emit_h.deinit(gpa);
     }
 
-    for (mod.failed_files.values()) |value| {
+    for (zcu.failed_files.values()) |value| {
         if (value) |msg| msg.destroy(gpa);
     }
-    mod.failed_files.deinit(gpa);
+    zcu.failed_files.deinit(gpa);
 
-    for (mod.failed_embed_files.values()) |msg| {
+    for (zcu.failed_embed_files.values()) |msg| {
         msg.destroy(gpa);
     }
-    mod.failed_embed_files.deinit(gpa);
+    zcu.failed_embed_files.deinit(gpa);
 
-    for (mod.failed_exports.values()) |value| {
+    for (zcu.failed_exports.values()) |value| {
         value.destroy(gpa);
     }
-    mod.failed_exports.deinit(gpa);
+    zcu.failed_exports.deinit(gpa);
 
-    for (mod.cimport_errors.values()) |*errs| {
+    for (zcu.cimport_errors.values()) |*errs| {
         errs.deinit(gpa);
     }
-    mod.cimport_errors.deinit(gpa);
+    zcu.cimport_errors.deinit(gpa);
 
-    mod.compile_log_decls.deinit(gpa);
+    zcu.compile_log_decls.deinit(gpa);
 
-    for (mod.decl_exports.values()) |*export_list| {
+    for (zcu.decl_exports.values()) |*export_list| {
         export_list.deinit(gpa);
     }
-    mod.decl_exports.deinit(gpa);
+    zcu.decl_exports.deinit(gpa);
 
-    for (mod.value_exports.values()) |*export_list| {
+    for (zcu.value_exports.values()) |*export_list| {
         export_list.deinit(gpa);
     }
-    mod.value_exports.deinit(gpa);
+    zcu.value_exports.deinit(gpa);
 
-    for (mod.export_owners.values()) |*value| {
+    for (zcu.export_owners.values()) |*value| {
         freeExportList(gpa, value);
     }
-    mod.export_owners.deinit(gpa);
+    zcu.export_owners.deinit(gpa);
 
-    mod.global_error_set.deinit(gpa);
+    zcu.global_error_set.deinit(gpa);
 
-    mod.test_functions.deinit(gpa);
+    zcu.test_functions.deinit(gpa);
 
-    for (mod.global_assembly.values()) |s| {
+    for (zcu.global_assembly.values()) |s| {
         gpa.free(s);
     }
-    mod.global_assembly.deinit(gpa);
+    zcu.global_assembly.deinit(gpa);
 
-    mod.reference_table.deinit(gpa);
+    zcu.reference_table.deinit(gpa);
 
     {
-        var it = mod.intern_pool.allocated_namespaces.iterator(0);
+        var it = zcu.intern_pool.allocated_namespaces.iterator(0);
         while (it.next()) |namespace| {
             namespace.decls.deinit(gpa);
             namespace.usingnamespace_set.deinit(gpa);
         }
     }
 
-    mod.intern_pool.deinit(gpa);
-    mod.tmp_hack_arena.deinit();
+    zcu.intern_pool.deinit(gpa);
+    zcu.tmp_hack_arena.deinit();
 
-    mod.capture_scope_parents.deinit(gpa);
-    mod.runtime_capture_scopes.deinit(gpa);
-    mod.comptime_capture_scopes.deinit(gpa);
+    zcu.capture_scope_parents.deinit(gpa);
+    zcu.runtime_capture_scopes.deinit(gpa);
+    zcu.comptime_capture_scopes.deinit(gpa);
 }
 
 pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {
@@ -3222,14 +3232,14 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
     }
 }
 
-pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: InternPool.Index) SemaError!void {
+pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const ip = &mod.intern_pool;
-    const func = mod.funcInfo(func_index);
+    const ip = &zcu.intern_pool;
+    const func = zcu.funcInfo(func_index);
     const decl_index = func.owner_decl;
-    const decl = mod.declPtr(decl_index);
+    const decl = zcu.declPtr(decl_index);
 
     switch (decl.analysis) {
         .unreferenced => unreachable,
@@ -3253,13 +3263,13 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: InternPool.Index) SemaEr
                 .success => return,
             }
 
-            const gpa = mod.gpa;
+            const gpa = zcu.gpa;
 
             var tmp_arena = std.heap.ArenaAllocator.init(gpa);
             defer tmp_arena.deinit();
             const sema_arena = tmp_arena.allocator();
 
-            var air = mod.analyzeFnBody(func_index, sema_arena) catch |err| switch (err) {
+            var air = zcu.analyzeFnBody(func_index, sema_arena) catch |err| switch (err) {
                 error.AnalysisFail => {
                     if (func.analysis(ip).state == .in_progress) {
                         // If this decl caused the compile error, the analysis field would
@@ -3273,25 +3283,22 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: InternPool.Index) SemaEr
             };
             defer air.deinit(gpa);
 
-            const comp = mod.comp;
-
-            const no_bin_file = (comp.bin_file == null and
-                comp.emit_asm == null and
-                comp.emit_llvm_ir == null and
-                comp.emit_llvm_bc == null);
+            const comp = zcu.comp;
 
             const dump_air = builtin.mode == .Debug and comp.verbose_air;
             const dump_llvm_ir = builtin.mode == .Debug and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null);
 
-            if (no_bin_file and !dump_air and !dump_llvm_ir) return;
+            if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) {
+                return;
+            }
 
             var liveness = try Liveness.analyze(gpa, air, ip);
             defer liveness.deinit(gpa);
 
             if (dump_air) {
-                const fqn = try decl.getFullyQualifiedName(mod);
+                const fqn = try decl.getFullyQualifiedName(zcu);
                 std.debug.print("# Begin Function AIR: {}:\n", .{fqn.fmt(ip)});
-                @import("print_air.zig").dump(mod, air, liveness);
+                @import("print_air.zig").dump(zcu, air, liveness);
                 std.debug.print("# End Function AIR: {}\n\n", .{fqn.fmt(ip)});
             }
 
@@ -3307,12 +3314,12 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: InternPool.Index) SemaEr
                 verify.verify() catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     else => {
-                        try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
-                        mod.failed_decls.putAssumeCapacityNoClobber(
+                        try zcu.failed_decls.ensureUnusedCapacity(gpa, 1);
+                        zcu.failed_decls.putAssumeCapacityNoClobber(
                             decl_index,
                             try Module.ErrorMsg.create(
                                 gpa,
-                                decl.srcLoc(mod),
+                                decl.srcLoc(zcu),
                                 "invalid liveness: {s}",
                                 .{@errorName(err)},
                             ),
@@ -3323,28 +3330,32 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: InternPool.Index) SemaEr
                 };
             }
 
-            if (no_bin_file and !dump_llvm_ir) return;
-
-            const lf = comp.bin_file.?;
-            lf.updateFunc(mod, func_index, air, liveness) catch |err| switch (err) {
-                error.OutOfMemory => return error.OutOfMemory,
-                error.AnalysisFail => {
-                    decl.analysis = .codegen_failure;
-                    return;
-                },
-                else => {
-                    try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
-                    mod.failed_decls.putAssumeCapacityNoClobber(decl_index, try Module.ErrorMsg.create(
-                        gpa,
-                        decl.srcLoc(mod),
-                        "unable to codegen: {s}",
-                        .{@errorName(err)},
-                    ));
-                    decl.analysis = .codegen_failure_retryable;
-                    return;
-                },
-            };
-            return;
+            if (comp.bin_file) |lf| {
+                lf.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) {
+                    error.OutOfMemory => return error.OutOfMemory,
+                    error.AnalysisFail => {
+                        decl.analysis = .codegen_failure;
+                    },
+                    else => {
+                        try zcu.failed_decls.ensureUnusedCapacity(gpa, 1);
+                        zcu.failed_decls.putAssumeCapacityNoClobber(decl_index, try Module.ErrorMsg.create(
+                            gpa,
+                            decl.srcLoc(zcu),
+                            "unable to codegen: {s}",
+                            .{@errorName(err)},
+                        ));
+                        decl.analysis = .codegen_failure_retryable;
+                    },
+                };
+            } else if (zcu.llvm_object) |llvm_object| {
+                if (build_options.only_c) unreachable;
+                llvm_object.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) {
+                    error.OutOfMemory => return error.OutOfMemory,
+                    error.AnalysisFail => {
+                        decl.analysis = .codegen_failure;
+                    },
+                };
+            }
         },
     }
 }
@@ -5235,44 +5246,57 @@ pub fn processExports(mod: *Module) !void {
 const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, *Export);
 
 fn processExportsInner(
-    mod: *Module,
+    zcu: *Zcu,
     symbol_exports: *SymbolExports,
     exported: Exported,
     exports: []const *Export,
 ) error{OutOfMemory}!void {
-    const gpa = mod.gpa;
+    const gpa = zcu.gpa;
 
     for (exports) |new_export| {
         const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name);
         if (gop.found_existing) {
             new_export.status = .failed_retryable;
-            try mod.failed_exports.ensureUnusedCapacity(gpa, 1);
-            const src_loc = new_export.getSrcLoc(mod);
+            try zcu.failed_exports.ensureUnusedCapacity(gpa, 1);
+            const src_loc = new_export.getSrcLoc(zcu);
             const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {}", .{
-                new_export.opts.name.fmt(&mod.intern_pool),
+                new_export.opts.name.fmt(&zcu.intern_pool),
             });
             errdefer msg.destroy(gpa);
             const other_export = gop.value_ptr.*;
-            const other_src_loc = other_export.getSrcLoc(mod);
-            try mod.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{});
-            mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
+            const other_src_loc = other_export.getSrcLoc(zcu);
+            try zcu.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{});
+            zcu.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
             new_export.status = .failed;
         } else {
             gop.value_ptr.* = new_export;
         }
     }
-    const lf = mod.comp.bin_file orelse return;
-    lf.updateExports(mod, exported, exports) catch |err| switch (err) {
+    if (zcu.comp.bin_file) |lf| {
+        try handleUpdateExports(zcu, exports, lf.updateExports(zcu, exported, exports));
+    } else if (zcu.llvm_object) |llvm_object| {
+        if (build_options.only_c) unreachable;
+        try handleUpdateExports(zcu, exports, llvm_object.updateExports(zcu, exported, exports));
+    }
+}
+
+fn handleUpdateExports(
+    zcu: *Zcu,
+    exports: []const *Export,
+    result: link.File.UpdateExportsError!void,
+) Allocator.Error!void {
+    const gpa = zcu.gpa;
+    result catch |err| switch (err) {
         error.OutOfMemory => return error.OutOfMemory,
-        else => {
+        error.AnalysisFail => {
             const new_export = exports[0];
             new_export.status = .failed_retryable;
-            try mod.failed_exports.ensureUnusedCapacity(gpa, 1);
-            const src_loc = new_export.getSrcLoc(mod);
+            try zcu.failed_exports.ensureUnusedCapacity(gpa, 1);
+            const src_loc = new_export.getSrcLoc(zcu);
             const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{
                 @errorName(err),
             });
-            mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
+            zcu.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
         },
     };
 }
@@ -5415,41 +5439,38 @@ pub fn populateTestFunctions(
     try mod.linkerUpdateDecl(decl_index);
 }
 
-pub fn linkerUpdateDecl(mod: *Module, decl_index: Decl.Index) !void {
-    const comp = mod.comp;
+pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void {
+    const comp = zcu.comp;
 
     if (comp.bin_file) |lf| {
-        const decl = mod.declPtr(decl_index);
-        lf.updateDecl(mod, decl_index) catch |err| switch (err) {
+        lf.updateDecl(zcu, decl_index) catch |err| switch (err) {
             error.OutOfMemory => return error.OutOfMemory,
             error.AnalysisFail => {
+                const decl = zcu.declPtr(decl_index);
                 decl.analysis = .codegen_failure;
-                return;
             },
             else => {
-                const gpa = mod.gpa;
-                try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
-                mod.failed_decls.putAssumeCapacityNoClobber(decl_index, try ErrorMsg.create(
+                const decl = zcu.declPtr(decl_index);
+                const gpa = zcu.gpa;
+                try zcu.failed_decls.ensureUnusedCapacity(gpa, 1);
+                zcu.failed_decls.putAssumeCapacityNoClobber(decl_index, try ErrorMsg.create(
                     gpa,
-                    decl.srcLoc(mod),
+                    decl.srcLoc(zcu),
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
                 decl.analysis = .codegen_failure_retryable;
-                return;
             },
         };
-    } else {
-        const dump_llvm_ir = builtin.mode == .Debug and
-            (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null);
-
-        if (comp.emit_asm != null or
-            comp.emit_llvm_ir != null or
-            comp.emit_llvm_bc != null or
-            dump_llvm_ir)
-        {
-            @panic("TODO handle emit_asm, emit_llvm_ir, and emit_llvm_bc along with -fno-emit-bin");
-        }
+    } else if (zcu.llvm_object) |llvm_object| {
+        if (build_options.only_c) unreachable;
+        llvm_object.updateDecl(zcu, decl_index) catch |err| switch (err) {
+            error.OutOfMemory => return error.OutOfMemory,
+            error.AnalysisFail => {
+                const decl = zcu.declPtr(decl_index);
+                decl.analysis = .codegen_failure;
+            },
+        };
     }
 }