Commit 3b1ea390a3

Jacob Young <jacobly0@users.noreply.github.com>
2023-05-02 01:18:49
x86_64: cleanup lazy symbols
In theory fixes updating lazy symbols during incremental compilation.
1 parent db88b41
Changed files (4)
src/arch/x86_64/CodeGen.zig
@@ -112,7 +112,7 @@ const Owner = union(enum) {
     mod_fn: *const Module.Fn,
     lazy_sym: link.File.LazySymbol,
 
-    fn getOwnerDecl(owner: Owner) Module.Decl.Index {
+    fn getDecl(owner: Owner) Module.Decl.Index {
         return switch (owner) {
             .mod_fn => |mod_fn| mod_fn.owner_decl,
             .lazy_sym => |lazy_sym| lazy_sym.ty.getOwnerDecl(),
@@ -1688,35 +1688,7 @@ fn genLazy(self: *Self, lazy_sym: link.File.LazySymbol) InnerError!void {
             const data_reg = try self.register_manager.allocReg(null, gp);
             const data_lock = self.register_manager.lockRegAssumeUnused(data_reg);
             defer self.register_manager.unlockReg(data_lock);
-
-            const data_lazy_sym = link.File.LazySymbol{ .kind = .const_data, .ty = enum_ty };
-            if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-                const atom_index = elf_file.getOrCreateAtomForLazySymbol(data_lazy_sym) catch |err|
-                    return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-                const atom = elf_file.getAtom(atom_index);
-                _ = try atom.getOrCreateOffsetTableEntry(elf_file);
-                const got_addr = atom.getOffsetTableAddress(elf_file);
-                try self.asmRegisterMemory(
-                    .mov,
-                    data_reg.to64(),
-                    Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(i32, got_addr) }),
-                );
-            } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
-                const atom_index = coff_file.getOrCreateAtomForLazySymbol(data_lazy_sym) catch |err|
-                    return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-                const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?;
-                try self.genSetReg(data_reg, Type.usize, .{ .lea_got = sym_index });
-            } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
-                const atom_index = macho_file.getOrCreateAtomForLazySymbol(data_lazy_sym) catch |err|
-                    return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-                const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
-                try self.genSetReg(data_reg, Type.usize, .{ .lea_got = sym_index });
-            } else {
-                return self.fail("TODO implement {s} for {}", .{
-                    @tagName(lazy_sym.kind),
-                    lazy_sym.ty.fmt(self.bin_file.options.module.?),
-                });
-            }
+            try self.genLazySymbolRef(.lea, data_reg, .{ .kind = .const_data, .ty = enum_ty });
 
             var data_off: i32 = 0;
             for (
@@ -6262,7 +6234,7 @@ fn genArgDbgInfo(self: Self, ty: Type, name: [:0]const u8, mcv: MCValue) !void {
             // TODO: this might need adjusting like the linkers do.
             // Instead of flattening the owner and passing Decl.Index here we may
             // want to special case LazySymbol in DWARF linker too.
-            try dw.genArgDbgInfo(name, ty, self.owner.getOwnerDecl(), loc);
+            try dw.genArgDbgInfo(name, ty, self.owner.getDecl(), loc);
         },
         .plan9 => {},
         .none => {},
@@ -6306,7 +6278,7 @@ fn genVarDbgInfo(
             // TODO: this might need adjusting like the linkers do.
             // Instead of flattening the owner and passing Decl.Index here we may
             // want to special case LazySymbol in DWARF linker too.
-            try dw.genVarDbgInfo(name, ty, self.owner.getOwnerDecl(), is_ptr, loc);
+            try dw.genVarDbgInfo(name, ty, self.owner.getDecl(), is_ptr, loc);
         },
         .plan9 => {},
         .none => {},
@@ -6630,38 +6602,13 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
+    const mod = self.bin_file.options.module.?;
     const un_op = self.air.instructions.items(.data)[inst].un_op;
 
     const addr_reg = try self.register_manager.allocReg(null, gp);
     const addr_lock = self.register_manager.lockRegAssumeUnused(addr_reg);
     defer self.register_manager.unlockReg(addr_lock);
-
-    const mod = self.bin_file.options.module.?;
-    const lazy_sym = link.File.LazySymbol.initDecl(.const_data, null, mod);
-    if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        const atom_index = elf_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const atom = elf_file.getAtom(atom_index);
-        _ = try atom.getOrCreateOffsetTableEntry(elf_file);
-        const got_addr = atom.getOffsetTableAddress(elf_file);
-        try self.asmRegisterMemory(
-            .mov,
-            addr_reg.to64(),
-            Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(i32, got_addr) }),
-        );
-    } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
-        const atom_index = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?;
-        try self.genSetReg(addr_reg, Type.usize, .{ .lea_got = sym_index });
-    } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
-        const atom_index = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
-        try self.genSetReg(addr_reg, Type.usize, .{ .lea_got = sym_index });
-    } else {
-        return self.fail("TODO implement airCmpLtErrorsLen for x86_64 {s}", .{@tagName(self.bin_file.tag)});
-    }
+    try self.genLazySymbolRef(.lea, addr_reg, link.File.LazySymbol.initDecl(.const_data, null, mod));
 
     try self.spillEflagsIfOccupied();
     self.eflags_inst = inst;
@@ -7999,6 +7946,67 @@ fn genInlineMemset(self: *Self, dst_ptr: MCValue, value: MCValue, len: MCValue)
     });
 }
 
+fn genLazySymbolRef(
+    self: *Self,
+    comptime tag: Mir.Inst.Tag,
+    reg: Register,
+    lazy_sym: link.File.LazySymbol,
+) InnerError!void {
+    if (self.bin_file.cast(link.File.Elf)) |elf_file| {
+        const atom_index = elf_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
+            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
+        const atom = elf_file.getAtom(atom_index);
+        _ = try atom.getOrCreateOffsetTableEntry(elf_file);
+        const got_addr = atom.getOffsetTableAddress(elf_file);
+        const got_mem =
+            Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(i32, got_addr) });
+        switch (tag) {
+            .lea, .mov => try self.asmRegisterMemory(.mov, reg.to64(), got_mem),
+            .call => try self.asmMemory(.call, got_mem),
+            else => unreachable,
+        }
+        switch (tag) {
+            .lea, .call => {},
+            .mov => try self.asmRegisterMemory(
+                tag,
+                reg.to64(),
+                Memory.sib(.qword, .{ .base = .{ .reg = reg.to64() } }),
+            ),
+            else => unreachable,
+        }
+    } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
+        const atom_index = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
+            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
+        const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?;
+        switch (tag) {
+            .lea, .call => try self.genSetReg(reg, Type.usize, .{ .lea_got = sym_index }),
+            .mov => try self.genSetReg(reg, Type.usize, .{ .load_got = sym_index }),
+            else => unreachable,
+        }
+        switch (tag) {
+            .lea, .mov => {},
+            .call => try self.asmRegister(.call, reg),
+            else => unreachable,
+        }
+    } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
+        const atom_index = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
+            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
+        const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
+        switch (tag) {
+            .lea, .call => try self.genSetReg(reg, Type.usize, .{ .lea_got = sym_index }),
+            .mov => try self.genSetReg(reg, Type.usize, .{ .load_got = sym_index }),
+            else => unreachable,
+        }
+        switch (tag) {
+            .lea, .mov => {},
+            .call => try self.asmRegister(.call, reg),
+            else => unreachable,
+        }
+    } else {
+        return self.fail("TODO implement genLazySymbol for x86_64 {s}", .{@tagName(self.bin_file.tag)});
+    }
+}
+
 fn airPtrToInt(self: *Self, inst: Air.Inst.Index) !void {
     const un_op = self.air.instructions.items(.data)[inst].un_op;
     const result = result: {
@@ -8701,6 +8709,7 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
+    const mod = self.bin_file.options.module.?;
     const un_op = self.air.instructions.items(.data)[inst].un_op;
     const inst_ty = self.air.typeOfIndex(inst);
     const enum_ty = self.air.typeOf(un_op);
@@ -8731,38 +8740,17 @@ fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
     const operand = try self.resolveInst(un_op);
     try self.genSetReg(param_regs[1], enum_ty, operand);
 
-    const mod = self.bin_file.options.module.?;
-    const lazy_sym = link.File.LazySymbol.initDecl(.code, enum_ty.getOwnerDecl(), mod);
-    if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        const atom_index = elf_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const atom = elf_file.getAtom(atom_index);
-        _ = try atom.getOrCreateOffsetTableEntry(elf_file);
-        const got_addr = atom.getOffsetTableAddress(elf_file);
-        try self.asmMemory(
-            .call,
-            Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(i32, got_addr) }),
-        );
-    } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
-        const atom_index = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?;
-        try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index });
-        try self.asmRegister(.call, .rax);
-    } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
-        const atom_index = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
-        try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index });
-        try self.asmRegister(.call, .rax);
-    } else {
-        return self.fail("TODO implement airTagName for x86_64 {s}", .{@tagName(self.bin_file.tag)});
-    }
+    try self.genLazySymbolRef(
+        .call,
+        .rax,
+        link.File.LazySymbol.initDecl(.code, enum_ty.getOwnerDecl(), mod),
+    );
 
     return self.finishAir(inst, dst_mcv, .{ un_op, .none, .none });
 }
 
 fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
+    const mod = self.bin_file.options.module.?;
     const un_op = self.air.instructions.items(.data)[inst].un_op;
 
     const err_ty = self.air.typeOf(un_op);
@@ -8774,33 +8762,7 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
     const addr_reg = try self.register_manager.allocReg(null, gp);
     const addr_lock = self.register_manager.lockRegAssumeUnused(addr_reg);
     defer self.register_manager.unlockReg(addr_lock);
-
-    const mod = self.bin_file.options.module.?;
-    const lazy_sym = link.File.LazySymbol.initDecl(.const_data, null, mod);
-    if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        const atom_index = elf_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const atom = elf_file.getAtom(atom_index);
-        _ = try atom.getOrCreateOffsetTableEntry(elf_file);
-        const got_addr = atom.getOffsetTableAddress(elf_file);
-        try self.asmRegisterMemory(
-            .mov,
-            addr_reg.to64(),
-            Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(i32, got_addr) }),
-        );
-    } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
-        const atom_index = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?;
-        try self.genSetReg(addr_reg, Type.usize, .{ .lea_got = sym_index });
-    } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
-        const atom_index = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
-            return self.fail("{s} creating lazy symbol", .{@errorName(err)});
-        const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
-        try self.genSetReg(addr_reg, Type.usize, .{ .lea_got = sym_index });
-    } else {
-        return self.fail("TODO implement airErrorName for x86_64 {s}", .{@tagName(self.bin_file.tag)});
-    }
+    try self.genLazySymbolRef(.lea, addr_reg, link.File.LazySymbol.initDecl(.const_data, null, mod));
 
     const start_reg = try self.register_manager.allocReg(null, gp);
     const start_lock = self.register_manager.lockRegAssumeUnused(start_reg);
@@ -9117,7 +9079,7 @@ fn limitImmediateType(self: *Self, operand: Air.Inst.Ref, comptime T: type) !MCV
 }
 
 fn genTypedValue(self: *Self, arg_tv: TypedValue) InnerError!MCValue {
-    return switch (try codegen.genTypedValue(self.bin_file, self.src_loc, arg_tv, self.owner.getOwnerDecl())) {
+    return switch (try codegen.genTypedValue(self.bin_file, self.src_loc, arg_tv, self.owner.getDecl())) {
         .mcv => |mcv| switch (mcv) {
             .none => .none,
             .undef => .undef,
src/link/Coff.zig
@@ -143,8 +143,11 @@ const Section = struct {
 const LazySymbolTable = std.AutoArrayHashMapUnmanaged(Module.Decl.OptionalIndex, LazySymbolMetadata);
 
 const LazySymbolMetadata = struct {
-    text_atom: ?Atom.Index = null,
-    rdata_atom: ?Atom.Index = null,
+    const State = enum { unused, pending_flush, flushed };
+    text_atom: Atom.Index = undefined,
+    rdata_atom: Atom.Index = undefined,
+    text_state: State = .unused,
+    rdata_state: State = .unused,
 };
 
 const DeclMetadata = struct {
@@ -1150,8 +1153,6 @@ pub fn updateDecl(
     const tracy = trace(@src());
     defer tracy.end();
 
-    try self.updateLazySymbol(decl_index);
-
     const decl = module.declPtr(decl_index);
 
     if (decl.val.tag() == .extern_fn) {
@@ -1194,21 +1195,6 @@ pub fn updateDecl(
     return self.updateDeclExports(module, decl_index, module.getDeclExports(decl_index));
 }
 
-fn updateLazySymbol(self: *Coff, decl: ?Module.Decl.Index) !void {
-    const metadata = self.lazy_syms.get(Module.Decl.OptionalIndex.init(decl)) orelse return;
-    const mod = self.base.options.module.?;
-    if (metadata.text_atom) |atom| try self.updateLazySymbolAtom(
-        link.File.LazySymbol.initDecl(.code, decl, mod),
-        atom,
-        self.text_section_index.?,
-    );
-    if (metadata.rdata_atom) |atom| try self.updateLazySymbolAtom(
-        link.File.LazySymbol.initDecl(.const_data, decl, mod),
-        atom,
-        self.rdata_section_index.?,
-    );
-}
-
 fn updateLazySymbolAtom(
     self: *Coff,
     sym: link.File.LazySymbol,
@@ -1279,14 +1265,19 @@ pub fn getOrCreateAtomForLazySymbol(self: *Coff, sym: link.File.LazySymbol) !Ato
     const gop = try self.lazy_syms.getOrPut(self.base.allocator, sym.getDecl());
     errdefer _ = if (!gop.found_existing) self.lazy_syms.pop();
     if (!gop.found_existing) gop.value_ptr.* = .{};
-    const atom_ptr = switch (sym.kind) {
-        .code => &gop.value_ptr.text_atom,
-        .const_data => &gop.value_ptr.rdata_atom,
+    const metadata: struct { atom: *Atom.Index, state: *LazySymbolMetadata.State } = switch (sym.kind) {
+        .code => .{ .atom = &gop.value_ptr.text_atom, .state = &gop.value_ptr.text_state },
+        .const_data => .{ .atom = &gop.value_ptr.rdata_atom, .state = &gop.value_ptr.rdata_state },
     };
-    if (atom_ptr.*) |atom| return atom;
-    const atom = try self.createAtom();
-    atom_ptr.* = atom;
-    try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
+    switch (metadata.state.*) {
+        .unused => metadata.atom.* = try self.createAtom(),
+        .pending_flush => return metadata.atom.*,
+        .flushed => {},
+    }
+    metadata.state.* = .pending_flush;
+    const atom = metadata.atom.*;
+    // anyerror needs to be deferred until flushModule
+    if (sym.getDecl() != .none) try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
         .code => self.text_section_index.?,
         .const_data => self.rdata_section_index.?,
     });
@@ -1617,15 +1608,35 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    // Most lazy symbols can be updated when the corresponding decl is,
-    // so we only have to worry about the one without an associated decl.
-    self.updateLazySymbol(null) catch |err| switch (err) {
-        error.CodegenFail => return error.FlushFailure,
-        else => |e| return e,
-    };
-
     const gpa = self.base.allocator;
 
+    const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
+
+    if (self.lazy_syms.getPtr(.none)) |metadata| {
+        // Most lazy symbols can be updated on first use, but
+        // anyerror needs to wait for everything to be flushed.
+        if (metadata.text_state != .unused) self.updateLazySymbolAtom(
+            link.File.LazySymbol.initDecl(.code, null, module),
+            metadata.text_atom,
+            self.text_section_index.?,
+        ) catch |err| return switch (err) {
+            error.CodegenFail => error.FlushFailure,
+            else => |e| e,
+        };
+        if (metadata.rdata_state != .unused) self.updateLazySymbolAtom(
+            link.File.LazySymbol.initDecl(.const_data, null, module),
+            metadata.rdata_atom,
+            self.rdata_section_index.?,
+        ) catch |err| return switch (err) {
+            error.CodegenFail => error.FlushFailure,
+            else => |e| e,
+        };
+    }
+    for (self.lazy_syms.values()) |*metadata| {
+        if (metadata.text_state != .unused) metadata.text_state = .flushed;
+        if (metadata.rdata_state != .unused) metadata.rdata_state = .flushed;
+    }
+
     while (self.unresolved.popOrNull()) |entry| {
         assert(entry.value); // We only expect imports generated by the incremental linker for now.
         const global = self.globals.items[entry.key];
src/link/Elf.zig
@@ -65,8 +65,11 @@ const Section = struct {
 };
 
 const LazySymbolMetadata = struct {
-    text_atom: ?Atom.Index = null,
-    rodata_atom: ?Atom.Index = null,
+    const State = enum { unused, pending_flush, flushed };
+    text_atom: Atom.Index = undefined,
+    rodata_atom: Atom.Index = undefined,
+    text_state: State = .unused,
+    rodata_state: State = .unused,
 };
 
 const DeclMetadata = struct {
@@ -1032,17 +1035,35 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    // Most lazy symbols can be updated when the corresponding decl is,
-    // so we only have to worry about the one without an associated decl.
-    self.updateLazySymbol(null) catch |err| switch (err) {
-        error.CodegenFail => return error.FlushFailure,
-        else => |e| return e,
-    };
-
     // TODO This linker code currently assumes there is only 1 compilation unit and it
     // corresponds to the Zig source code.
     const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
 
+    if (self.lazy_syms.getPtr(.none)) |metadata| {
+        // Most lazy symbols can be updated on first use, but
+        // anyerror needs to wait for everything to be flushed.
+        if (metadata.text_state != .unused) self.updateLazySymbolAtom(
+            File.LazySymbol.initDecl(.code, null, module),
+            metadata.text_atom,
+            self.text_section_index.?,
+        ) catch |err| return switch (err) {
+            error.CodegenFail => error.FlushFailure,
+            else => |e| e,
+        };
+        if (metadata.rodata_state != .unused) self.updateLazySymbolAtom(
+            File.LazySymbol.initDecl(.const_data, null, module),
+            metadata.rodata_atom,
+            self.rodata_section_index.?,
+        ) catch |err| return switch (err) {
+            error.CodegenFail => error.FlushFailure,
+            else => |e| e,
+        };
+    }
+    for (self.lazy_syms.values()) |*metadata| {
+        if (metadata.text_state != .unused) metadata.text_state = .flushed;
+        if (metadata.rodata_state != .unused) metadata.rodata_state = .flushed;
+    }
+
     const target_endian = self.base.options.target.cpu.arch.endian();
     const foreign_endian = target_endian != builtin.cpu.arch.endian();
 
@@ -2378,14 +2399,19 @@ pub fn getOrCreateAtomForLazySymbol(self: *Elf, sym: File.LazySymbol) !Atom.Inde
     const gop = try self.lazy_syms.getOrPut(self.base.allocator, sym.getDecl());
     errdefer _ = if (!gop.found_existing) self.lazy_syms.pop();
     if (!gop.found_existing) gop.value_ptr.* = .{};
-    const atom_ptr = switch (sym.kind) {
-        .code => &gop.value_ptr.text_atom,
-        .const_data => &gop.value_ptr.rodata_atom,
+    const metadata: struct { atom: *Atom.Index, state: *LazySymbolMetadata.State } = switch (sym.kind) {
+        .code => .{ .atom = &gop.value_ptr.text_atom, .state = &gop.value_ptr.text_state },
+        .const_data => .{ .atom = &gop.value_ptr.rodata_atom, .state = &gop.value_ptr.rodata_state },
     };
-    if (atom_ptr.*) |atom| return atom;
-    const atom = try self.createAtom();
-    atom_ptr.* = atom;
-    try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
+    switch (metadata.state.*) {
+        .unused => metadata.atom.* = try self.createAtom(),
+        .pending_flush => return metadata.atom.*,
+        .flushed => {},
+    }
+    metadata.state.* = .pending_flush;
+    const atom = metadata.atom.*;
+    // anyerror needs to be deferred until flushModule
+    if (sym.getDecl() != .none) try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
         .code => self.text_section_index.?,
         .const_data => self.rodata_section_index.?,
     });
@@ -2598,8 +2624,6 @@ pub fn updateDecl(
     const tracy = trace(@src());
     defer tracy.end();
 
-    try self.updateLazySymbol(decl_index);
-
     const decl = module.declPtr(decl_index);
 
     if (decl.val.tag() == .extern_fn) {
@@ -2666,21 +2690,6 @@ pub fn updateDecl(
     return self.updateDeclExports(module, decl_index, module.getDeclExports(decl_index));
 }
 
-fn updateLazySymbol(self: *Elf, decl: ?Module.Decl.Index) !void {
-    const metadata = self.lazy_syms.get(Module.Decl.OptionalIndex.init(decl)) orelse return;
-    const mod = self.base.options.module.?;
-    if (metadata.text_atom) |atom| try self.updateLazySymbolAtom(
-        File.LazySymbol.initDecl(.code, decl, mod),
-        atom,
-        self.text_section_index.?,
-    );
-    if (metadata.rodata_atom) |atom| try self.updateLazySymbolAtom(
-        File.LazySymbol.initDecl(.const_data, decl, mod),
-        atom,
-        self.rodata_section_index.?,
-    );
-}
-
 fn updateLazySymbolAtom(
     self: *Elf,
     sym: File.LazySymbol,
src/link/MachO.zig
@@ -236,8 +236,11 @@ const is_hot_update_compatible = switch (builtin.target.os.tag) {
 const LazySymbolTable = std.AutoArrayHashMapUnmanaged(Module.Decl.OptionalIndex, LazySymbolMetadata);
 
 const LazySymbolMetadata = struct {
-    text_atom: ?Atom.Index = null,
-    data_const_atom: ?Atom.Index = null,
+    const State = enum { unused, pending_flush, flushed };
+    text_atom: Atom.Index = undefined,
+    data_const_atom: Atom.Index = undefined,
+    text_state: State = .unused,
+    data_const_state: State = .unused,
 };
 
 const TlvSymbolTable = std.AutoArrayHashMapUnmanaged(SymbolWithLoc, Atom.Index);
@@ -493,15 +496,33 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
     sub_prog_node.activate();
     defer sub_prog_node.end();
 
-    // Most lazy symbols can be updated when the corresponding decl is,
-    // so we only have to worry about the one without an associated decl.
-    self.updateLazySymbol(null) catch |err| switch (err) {
-        error.CodegenFail => return error.FlushFailure,
-        else => |e| return e,
-    };
-
     const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
 
+    if (self.lazy_syms.getPtr(.none)) |metadata| {
+        // Most lazy symbols can be updated on first use, but
+        // anyerror needs to wait for everything to be flushed.
+        if (metadata.text_state != .unused) self.updateLazySymbolAtom(
+            File.LazySymbol.initDecl(.code, null, module),
+            metadata.text_atom,
+            self.text_section_index.?,
+        ) catch |err| return switch (err) {
+            error.CodegenFail => error.FlushFailure,
+            else => |e| e,
+        };
+        if (metadata.data_const_state != .unused) self.updateLazySymbolAtom(
+            File.LazySymbol.initDecl(.const_data, null, module),
+            metadata.data_const_atom,
+            self.data_const_section_index.?,
+        ) catch |err| return switch (err) {
+            error.CodegenFail => error.FlushFailure,
+            else => |e| e,
+        };
+    }
+    for (self.lazy_syms.values()) |*metadata| {
+        if (metadata.text_state != .unused) metadata.text_state = .flushed;
+        if (metadata.data_const_state != .unused) metadata.data_const_state = .flushed;
+    }
+
     if (self.d_sym) |*d_sym| {
         try d_sym.dwarf.flushModule(module);
     }
@@ -1960,8 +1981,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl_index: Module.Decl.Index)
     const tracy = trace(@src());
     defer tracy.end();
 
-    try self.updateLazySymbol(decl_index);
-
     const decl = module.declPtr(decl_index);
 
     if (decl.val.tag() == .extern_fn) {
@@ -2036,21 +2055,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl_index: Module.Decl.Index)
     try self.updateDeclExports(module, decl_index, module.getDeclExports(decl_index));
 }
 
-fn updateLazySymbol(self: *MachO, decl: ?Module.Decl.Index) !void {
-    const metadata = self.lazy_syms.get(Module.Decl.OptionalIndex.init(decl)) orelse return;
-    const mod = self.base.options.module.?;
-    if (metadata.text_atom) |atom| try self.updateLazySymbolAtom(
-        File.LazySymbol.initDecl(.code, decl, mod),
-        atom,
-        self.text_section_index.?,
-    );
-    if (metadata.data_const_atom) |atom| try self.updateLazySymbolAtom(
-        File.LazySymbol.initDecl(.const_data, decl, mod),
-        atom,
-        self.data_const_section_index.?,
-    );
-}
-
 fn updateLazySymbolAtom(
     self: *MachO,
     sym: File.LazySymbol,
@@ -2125,14 +2129,22 @@ pub fn getOrCreateAtomForLazySymbol(self: *MachO, sym: File.LazySymbol) !Atom.In
     const gop = try self.lazy_syms.getOrPut(self.base.allocator, sym.getDecl());
     errdefer _ = if (!gop.found_existing) self.lazy_syms.pop();
     if (!gop.found_existing) gop.value_ptr.* = .{};
-    const atom_ptr = switch (sym.kind) {
-        .code => &gop.value_ptr.text_atom,
-        .const_data => &gop.value_ptr.data_const_atom,
+    const metadata: struct { atom: *Atom.Index, state: *LazySymbolMetadata.State } = switch (sym.kind) {
+        .code => .{ .atom = &gop.value_ptr.text_atom, .state = &gop.value_ptr.text_state },
+        .const_data => .{
+            .atom = &gop.value_ptr.data_const_atom,
+            .state = &gop.value_ptr.data_const_state,
+        },
     };
-    if (atom_ptr.*) |atom| return atom;
-    const atom = try self.createAtom();
-    atom_ptr.* = atom;
-    try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
+    switch (metadata.state.*) {
+        .unused => metadata.atom.* = try self.createAtom(),
+        .pending_flush => return metadata.atom.*,
+        .flushed => {},
+    }
+    metadata.state.* = .pending_flush;
+    const atom = metadata.atom.*;
+    // anyerror needs to be deferred until flushModule
+    if (sym.getDecl() != .none) try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
         .code => self.text_section_index.?,
         .const_data => self.data_const_section_index.?,
     });