Commit 8c578ba02c

Jakub Konka <kubkon@jakubkonka.com>
2024-01-17 20:09:12
macho: add __zig_got section implementation
1 parent c7de5e5
src/link/MachO/Atom.zig
@@ -37,6 +37,11 @@ unwind_records: Loc = .{},
 
 flags: Flags = .{},
 
+/// Points to the previous and next neighbors, based on the `text_offset`.
+/// This can be used to find, for example, the capacity of this `TextBlock`.
+prev_index: Index = 0,
+next_index: Index = 0,
+
 pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 {
     return macho_file.strings.getAssumeExists(self.name);
 }
@@ -171,6 +176,154 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
     return osec;
 }
 
+/// Returns how much room there is to grow in virtual address space.
+/// File offset relocation happens transparently, so it is not included in
+/// this calculation.
+pub fn capacity(self: Atom, macho_file: *MachO) u64 {
+    const next_value = if (macho_file.getAtom(self.next_index)) |next| next.value else std.math.maxInt(u32);
+    return next_value - self.value;
+}
+
+pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
+    // No need to keep a free list node for the last block.
+    const next = macho_file.getAtom(self.next_index) orelse return false;
+    const cap = next.value - self.value;
+    const ideal_cap = MachO.padToIdeal(self.size);
+    if (cap <= ideal_cap) return false;
+    const surplus = cap - ideal_cap;
+    return surplus >= MachO.min_text_capacity;
+}
+
+pub fn allocate(self: *Atom, macho_file: *MachO) !void {
+    const sect = &macho_file.sections.items(.header)[self.out_n_sect];
+    const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
+    const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
+    const new_atom_ideal_capacity = MachO.padToIdeal(self.size);
+
+    // We use these to indicate our intention to update metadata, placing the new atom,
+    // and possibly removing a free list node.
+    // It would be simpler to do it inside the for loop below, but that would cause a
+    // problem if an error was returned later in the function. So this action
+    // is actually carried out at the end of the function, when errors are no longer possible.
+    var atom_placement: ?Atom.Index = null;
+    var free_list_removal: ?usize = null;
+
+    // First we look for an appropriately sized free list node.
+    // The list is unordered. We'll just take the first thing that works.
+    self.value = blk: {
+        var i: usize = free_list.items.len;
+        while (i < free_list.items.len) {
+            const big_atom_index = free_list.items[i];
+            const big_atom = macho_file.getAtom(big_atom_index).?;
+            // We now have a pointer to a live atom that has too much capacity.
+            // Is it enough that we could fit this new atom?
+            const cap = big_atom.capacity(macho_file);
+            const ideal_capacity = MachO.padToIdeal(cap);
+            const ideal_capacity_end_vaddr = std.math.add(u64, big_atom.value, ideal_capacity) catch ideal_capacity;
+            const capacity_end_vaddr = big_atom.value + cap;
+            const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
+            const new_start_vaddr = self.alignment.backward(new_start_vaddr_unaligned);
+            if (new_start_vaddr < ideal_capacity_end_vaddr) {
+                // Additional bookkeeping here to notice if this free list node
+                // should be deleted because the block that it points to has grown to take up
+                // more of the extra capacity.
+                if (!big_atom.freeListEligible(macho_file)) {
+                    _ = free_list.swapRemove(i);
+                } else {
+                    i += 1;
+                }
+                continue;
+            }
+            // At this point we know that we will place the new block here. But the
+            // remaining question is whether there is still yet enough capacity left
+            // over for there to still be a free list node.
+            const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
+            const keep_free_list_node = remaining_capacity >= MachO.min_text_capacity;
+
+            // Set up the metadata to be updated, after errors are no longer possible.
+            atom_placement = big_atom_index;
+            if (!keep_free_list_node) {
+                free_list_removal = i;
+            }
+            break :blk new_start_vaddr;
+        } else if (macho_file.getAtom(last_atom_index.*)) |last| {
+            const ideal_capacity = MachO.padToIdeal(last.size);
+            const ideal_capacity_end_vaddr = last.value + ideal_capacity;
+            const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr);
+            // Set up the metadata to be updated, after errors are no longer possible.
+            atom_placement = last.atom_index;
+            break :blk new_start_vaddr;
+        } else {
+            break :blk sect.addr;
+        }
+    };
+
+    log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{
+        self.atom_index,
+        self.getName(macho_file),
+        self.value,
+        self.value + self.size,
+    });
+
+    const expand_section = if (atom_placement) |placement_index|
+        macho_file.getAtom(placement_index).?.next_index == 0
+    else
+        true;
+    if (expand_section) {
+        const needed_size = (self.value + self.size) - sect.addr;
+        try macho_file.growSection(self.out_n_sect, needed_size);
+        last_atom_index.* = self.atom_index;
+
+        // const zig_object = macho_file_file.getZigObject().?;
+        // if (zig_object.dwarf) |_| {
+        //     // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
+        //     // range of the compilation unit. When we expand the text section, this range changes,
+        //     // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty.
+        //     zig_object.debug_info_header_dirty = true;
+        //     // This becomes dirty for the same reason. We could potentially make this more
+        //     // fine-grained with the addition of support for more compilation units. It is planned to
+        //     // model each package as a different compilation unit.
+        //     zig_object.debug_aranges_section_dirty = true;
+        // }
+    }
+    sect.@"align" = @max(sect.@"align", self.alignment.toLog2Units());
+
+    // This function can also reallocate an atom.
+    // In this case we need to "unplug" it from its previous location before
+    // plugging it in to its new location.
+    if (macho_file.getAtom(self.prev_index)) |prev| {
+        prev.next_index = self.next_index;
+    }
+    if (macho_file.getAtom(self.next_index)) |next| {
+        next.prev_index = self.prev_index;
+    }
+
+    if (atom_placement) |big_atom_index| {
+        const big_atom = macho_file.getAtom(big_atom_index).?;
+        self.prev_index = big_atom_index;
+        self.next_index = big_atom.next_index;
+        big_atom.next_index = self.atom_index;
+    } else {
+        self.prev_index = 0;
+        self.next_index = 0;
+    }
+    if (free_list_removal) |i| {
+        _ = free_list.swapRemove(i);
+    }
+
+    self.flags.alive = true;
+}
+
+pub fn shrink(self: *Atom, macho_file: *MachO) void {
+    _ = self;
+    _ = macho_file;
+}
+
+pub fn grow(self: *Atom, macho_file: *MachO) !void {
+    if (!self.alignment.check(self.value) or self.size > self.capacity(macho_file))
+        try self.allocate(macho_file);
+}
+
 pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
src/link/MachO/Symbol.zig
@@ -149,6 +149,25 @@ pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
     return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
 }
 
+const GetOrCreateZigGotEntryResult = struct {
+    found_existing: bool,
+    index: ZigGotSection.Index,
+};
+
+pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult {
+    assert(!macho_file.base.isRelocatable());
+    assert(symbol.flags.needs_zig_got);
+    if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).?.zig_got };
+    const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file);
+    return .{ .found_existing = false, .index = index };
+}
+
+pub fn zigGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
+    if (!symbol.flags.has_zig_got) return 0;
+    const extras = symbol.getExtra(macho_file).?;
+    return macho_file.zig_got.entryAddress(extras.zig_got, macho_file);
+}
+
 pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 {
     if (!symbol.flags.output_symtab) return null;
     assert(!symbol.isSymbolStab(macho_file));
@@ -170,6 +189,7 @@ pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 {
 
 const AddExtraOpts = struct {
     got: ?u32 = null,
+    zig_got: ?u32 = null,
     stubs: ?u32 = null,
     objc_stubs: ?u32 = null,
     objc_selrefs: ?u32 = null,
@@ -374,6 +394,7 @@ pub const Visibility = enum {
 
 pub const Extra = struct {
     got: u32 = 0,
+    zig_got: u32 = 0,
     stubs: u32 = 0,
     objc_stubs: u32 = 0,
     objc_selrefs: u32 = 0,
@@ -393,3 +414,4 @@ const MachO = @import("../MachO.zig");
 const Nlist = Object.Nlist;
 const Object = @import("Object.zig");
 const Symbol = @This();
+const ZigGotSection = @import("synthetic.zig").ZigGotSection;
src/link/MachO/synthetic.zig
@@ -1,3 +1,121 @@
+pub const ZigGotSection = struct {
+    entries: std.ArrayListUnmanaged(Symbol.Index) = .{},
+    dirty: bool = false,
+
+    pub const Index = u32;
+
+    pub fn deinit(zig_got: *ZigGotSection, allocator: Allocator) void {
+        zig_got.entries.deinit(allocator);
+    }
+
+    fn allocateEntry(zig_got: *ZigGotSection, allocator: Allocator) !Index {
+        try zig_got.entries.ensureUnusedCapacity(allocator, 1);
+        // TODO add free list
+        const index = @as(Index, @intCast(zig_got.entries.items.len));
+        _ = zig_got.entries.addOneAssumeCapacity();
+        zig_got.dirty = true;
+        return index;
+    }
+
+    pub fn addSymbol(zig_got: *ZigGotSection, sym_index: Symbol.Index, macho_file: *MachO) !Index {
+        const comp = macho_file.base.comp;
+        const gpa = comp.gpa;
+        const index = try zig_got.allocateEntry(gpa);
+        const entry = &zig_got.entries.items[index];
+        entry.* = sym_index;
+        const symbol = macho_file.getSymbol(sym_index);
+        symbol.flags.has_zig_got = true;
+        try symbol.addExtra(.{ .zig_got = index }, macho_file);
+        return index;
+    }
+
+    pub fn entryOffset(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 {
+        _ = zig_got;
+        const sect = macho_file.sections.items(.header)[macho_file.zig_got_section_index.?];
+        return sect.offset + @sizeOf(u64) * index;
+    }
+
+    pub fn entryAddress(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 {
+        _ = zig_got;
+        const sect = macho_file.sections.items(.header)[macho_file.zig_got_section_index.?];
+        return sect.addr + @sizeOf(u64) * index;
+    }
+
+    pub fn size(zig_got: ZigGotSection, macho_file: *MachO) usize {
+        _ = macho_file;
+        return @sizeOf(u64) * zig_got.entries.items.len;
+    }
+
+    pub fn writeOne(zig_got: *ZigGotSection, macho_file: *MachO, index: Index) !void {
+        if (zig_got.dirty) {
+            const needed_size = zig_got.size(macho_file);
+            try macho_file.growSection(macho_file.zig_got_section_index.?, needed_size);
+            zig_got.dirty = false;
+        }
+        const off = zig_got.entryOffset(index, macho_file);
+        const entry = zig_got.entries.items[index];
+        const value = macho_file.getSymbol(entry).getAddress(.{ .stubs = false }, macho_file);
+
+        var buf: [8]u8 = undefined;
+        std.mem.writeInt(u64, &buf, value, .little);
+        try macho_file.base.file.?.pwriteAll(&buf, off);
+    }
+
+    pub fn writeAll(zig_got: ZigGotSection, macho_file: *MachO, writer: anytype) !void {
+        for (zig_got.entries.items) |entry| {
+            const symbol = macho_file.getSymbol(entry);
+            const value = symbol.address(.{ .stubs = false }, macho_file);
+            try writer.writeInt(u64, value, .little);
+        }
+    }
+
+    pub fn addDyldRelocs(zig_got: ZigGotSection, macho_file: *MachO) !void {
+        const tracy = trace(@src());
+        defer tracy.end();
+        const gpa = macho_file.base.comp.gpa;
+        const seg_id = macho_file.sections.items(.segment_id)[macho_file.zig_got_sect_index.?];
+        const seg = macho_file.segments.items[seg_id];
+
+        for (0..zig_got.symbols.items.len) |idx| {
+            const addr = zig_got.entryAddress(@intCast(idx), macho_file);
+            try macho_file.rebase.entries.append(gpa, .{
+                .offset = addr - seg.vmaddr,
+                .segment_id = seg_id,
+            });
+        }
+    }
+
+    const FormatCtx = struct {
+        zig_got: ZigGotSection,
+        macho_file: *MachO,
+    };
+
+    pub fn fmt(zig_got: ZigGotSection, macho_file: *MachO) std.fmt.Formatter(format2) {
+        return .{ .data = .{ .zig_got = zig_got, .macho_file = macho_file } };
+    }
+
+    pub fn format2(
+        ctx: FormatCtx,
+        comptime unused_fmt_string: []const u8,
+        options: std.fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        _ = options;
+        _ = unused_fmt_string;
+        try writer.writeAll("__zig_got\n");
+        for (ctx.zig_got.entries.items, 0..) |entry, index| {
+            const symbol = ctx.macho_file.getSymbol(entry);
+            try writer.print("  {d}@0x{x} => {d}@0x{x} ({s})\n", .{
+                index,
+                ctx.zig_got.entryAddress(@intCast(index), ctx.macho_file),
+                entry,
+                symbol.getAddress(.{}, ctx.macho_file),
+                symbol.getName(ctx.macho_file),
+            });
+        }
+    }
+};
+
 pub const GotSection = struct {
     symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
 
src/link/MachO/ZigObject.zig
@@ -7,9 +7,36 @@ symtab: std.MultiArrayList(Nlist) = .{},
 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
 atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
 
+/// Table of tracked LazySymbols.
+lazy_syms: LazySymbolTable = .{},
+
 /// Table of tracked Decls.
 decls: DeclTable = .{},
 
+/// Table of unnamed constants associated with a parent `Decl`.
+/// We store them here so that we can free the constants whenever the `Decl`
+/// needs updating or is freed.
+///
+/// For example,
+///
+/// ```zig
+/// const Foo = struct{
+///     a: u8,
+/// };
+///
+/// pub fn main() void {
+///     var foo = Foo{ .a = 1 };
+///     _ = foo;
+/// }
+/// ```
+///
+/// value assigned to label `foo` is an unnamed constant belonging/associated
+/// with `Decl` `main`, and lives as long as that `Decl`.
+unnamed_consts: UnnamedConstTable = .{},
+
+/// Table of tracked AnonDecls.
+anon_decls: AnonDeclTable = .{},
+
 /// A table of relocations.
 relocs: RelocationTable = .{},
 
@@ -35,6 +62,24 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void {
         self.decls.deinit(allocator);
     }
 
+    self.lazy_syms.deinit(allocator);
+
+    {
+        var it = self.unnamed_consts.valueIterator();
+        while (it.next()) |syms| {
+            syms.deinit(allocator);
+        }
+        self.unnamed_consts.deinit(allocator);
+    }
+
+    {
+        var it = self.anon_decls.iterator();
+        while (it.next()) |entry| {
+            entry.value_ptr.exports.deinit(allocator);
+        }
+        self.anon_decls.deinit(allocator);
+    }
+
     for (self.relocs.items) |*list| {
         list.deinit(allocator);
     }
@@ -296,7 +341,7 @@ pub fn updateDecl(
         },
     };
     const sect_index = try self.getDeclOutputSection(macho_file, decl, code);
-    const is_threadlocal = switch (macho_file.sections.items(.header[sect_index].type())) {
+    const is_threadlocal = switch (macho_file.sections.items(.header)[sect_index].type()) {
         macho.S_THREAD_LOCAL_ZEROFILL, macho.S_THREAD_LOCAL_REGULAR => true,
         else => false,
     };
@@ -317,21 +362,21 @@ pub fn updateDecl(
     //     );
     // }
 
-    // // Since we updated the vaddr and the size, each corresponding export symbol also
-    // // needs to be updated.
-    // try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
+    // Since we updated the vaddr and the size, each corresponding export symbol also
+    // needs to be updated.
+    try self.updateExports(macho_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
 }
 
 fn updateDeclCode(
     self: *ZigObject,
     macho_file: *MachO,
-    decl_index: Module.Decl.Index,
+    decl_index: InternPool.DeclIndex,
     sym_index: Symbol.Index,
     sect_index: u8,
     code: []const u8,
 ) !void {
-    const gpa = self.base.comp.gpa;
-    const mod = self.base.comp.module.?;
+    const gpa = macho_file.base.comp.gpa;
+    const mod = macho_file.base.comp.module.?;
     const decl = mod.declPtr(decl_index);
     const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
@@ -342,7 +387,6 @@ fn updateDeclCode(
     const sect = &macho_file.sections.items(.header)[sect_index];
     const sym = macho_file.getSymbol(sym_index);
     const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
-    const size = &self.symtab.items(.size)[sym.nlist_idx];
     const atom = sym.getAtom(macho_file).?;
 
     sym.out_n_sect = sect_index;
@@ -354,7 +398,7 @@ fn updateDeclCode(
     nlist.n_strx = sym.name;
     nlist.n_type = macho.N_SECT;
     nlist.n_sect = sect_index + 1;
-    size = code.len;
+    self.symtab.items(.size)[sym.nlist_idx] = code.len;
 
     const old_size = atom.size;
     const old_vaddr = atom.value;
@@ -363,14 +407,14 @@ fn updateDeclCode(
 
     if (old_size > 0) {
         const capacity = atom.capacity(macho_file);
-        const need_realloc = code.len > capacity or !required_alignment.check(sym.value);
+        const need_realloc = code.len > capacity or !required_alignment.check(sym.getAddress(.{}, macho_file));
 
         if (need_realloc) {
             try atom.grow(macho_file);
             log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, old_vaddr, atom.value });
             if (old_vaddr != atom.value) {
-                sym.value = atom.value;
-                nlist.n_value = atom.value;
+                sym.value = 0;
+                nlist.n_value = 0;
 
                 if (!macho_file.base.isRelocatable()) {
                     log.debug("  (updating offset table entry)", .{});
@@ -381,17 +425,17 @@ fn updateDeclCode(
             }
         } else if (code.len < old_size) {
             atom.shrink(macho_file);
-        } else if (atom.next_index == null) {
-            const needed_size = (sym.value + code.len) - sect.addr;
+        } else if (macho_file.getAtom(atom.next_index) == null) {
+            const needed_size = (sym.getAddress(.{}, macho_file) + code.len) - sect.addr;
             sect.size = needed_size;
         }
     } else {
         try atom.allocate(macho_file);
         // TODO: freeDeclMetadata in case of error
 
-        sym.value = atom.value;
+        sym.value = 0;
         sym.flags.needs_zig_got = true;
-        nlist.n_value = atom.value;
+        nlist.n_value = 0;
 
         if (!macho_file.base.isRelocatable()) {
             const gop = try sym.getOrCreateZigGotEntry(sym_index, macho_file);
@@ -400,7 +444,7 @@ fn updateDeclCode(
     }
 
     if (!sect.isZerofill()) {
-        const file_offset = sect.offset + sym.value - sect.addr;
+        const file_offset = sect.offset + sym.getAddress(.{}, macho_file) - sect.addr;
         try macho_file.base.file.?.pwriteAll(code, file_offset);
     }
 }
@@ -478,12 +522,91 @@ pub fn updateExports(
     exported: Module.Exported,
     exports: []const *Module.Export,
 ) link.File.UpdateExportsError!void {
-    _ = self;
-    _ = macho_file;
-    _ = mod;
-    _ = exported;
-    _ = exports;
-    @panic("TODO updateExports");
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const gpa = macho_file.base.comp.gpa;
+    const metadata = switch (exported) {
+        .decl_index => |decl_index| blk: {
+            _ = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
+            break :blk self.decls.getPtr(decl_index).?;
+        },
+        .value => |value| self.anon_decls.getPtr(value) orelse blk: {
+            const first_exp = exports[0];
+            const res = try self.lowerAnonDecl(macho_file, value, .none, first_exp.getSrcLoc(mod));
+            switch (res) {
+                .ok => {},
+                .fail => |em| {
+                    // TODO maybe it's enough to return an error here and let Module.processExportsInner
+                    // handle the error?
+                    try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
+                    mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em);
+                    return;
+                },
+            }
+            break :blk self.anon_decls.getPtr(value).?;
+        },
+    };
+    const sym_index = metadata.symbol_index;
+    const nlist_idx = macho_file.getSymbol(sym_index).nlist_idx;
+    const nlist = self.symtab.items(.nlist)[nlist_idx];
+
+    for (exports) |exp| {
+        if (exp.opts.section.unwrap()) |section_name| {
+            if (!mod.intern_pool.stringEqlSlice(section_name, "__text")) {
+                try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
+                mod.failed_exports.putAssumeCapacityNoClobber(exp, try Module.ErrorMsg.create(
+                    gpa,
+                    exp.getSrcLoc(mod),
+                    "Unimplemented: ExportOptions.section",
+                    .{},
+                ));
+                continue;
+            }
+        }
+        if (exp.opts.linkage == .LinkOnce) {
+            try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
+                gpa,
+                exp.getSrcLoc(mod),
+                "Unimplemented: GlobalLinkage.LinkOnce",
+                .{},
+            ));
+            continue;
+        }
+
+        const exp_name = try std.fmt.allocPrint(gpa, "_{}", .{exp.opts.name.fmt(&mod.intern_pool)});
+        defer gpa.free(exp_name);
+
+        const name_off = try macho_file.strings.insert(gpa, exp_name);
+        const global_nlist_index = if (metadata.@"export"(self, macho_file, exp_name)) |exp_index|
+            exp_index.*
+        else blk: {
+            const global_nlist_index = try self.getGlobalSymbol(macho_file, exp_name, null);
+            try metadata.exports.append(gpa, global_nlist_index);
+            break :blk global_nlist_index;
+        };
+        const global_nlist = &self.symtab.items(.nlist)[global_nlist_index];
+        global_nlist.n_strx = name_off;
+        global_nlist.n_value = nlist.n_value;
+        global_nlist.n_sect = nlist.n_sect;
+        global_nlist.n_type = macho.N_EXT | macho.N_SECT;
+        self.symtab.items(.size)[global_nlist_index] = self.symtab.items(.size)[nlist_idx];
+        self.symtab.items(.atom)[global_nlist_index] = self.symtab.items(.atom)[nlist_idx];
+
+        switch (exp.opts.linkage) {
+            .Internal => {
+                // Symbol should be hidden, or in MachO lingo, private extern.
+                global_nlist.n_type |= macho.N_PEXT;
+            },
+            .Strong => {},
+            .Weak => {
+                // Weak linkage is specified as part of n_desc field.
+                // Symbol's n_type is like for a symbol with strong linkage.
+                global_nlist.n_desc |= macho.N_WEAK_DEF;
+            },
+            else => unreachable,
+        }
+    }
 }
 
 /// Must be called only after a successful call to `updateDecl`.
@@ -612,8 +735,19 @@ const DeclMetadata = struct {
         return null;
     }
 };
-const DeclTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata);
 
+const LazySymbolMetadata = struct {
+    const State = enum { unused, pending_flush, flushed };
+    text_symbol_index: Symbol.Index = undefined,
+    data_const_symbol_index: Symbol.Index = undefined,
+    text_state: State = .unused,
+    rodata_state: State = .unused,
+};
+
+const DeclTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata);
+const UnnamedConstTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, std.ArrayListUnmanaged(Symbol.Index));
+const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata);
+const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata);
 const RelocationTable = std.ArrayListUnmanaged(std.ArrayListUnmanaged(Relocation));
 
 const assert = std.debug.assert;
src/link/MachO.zig
@@ -70,6 +70,7 @@ symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
 strtab: std.ArrayListUnmanaged(u8) = .{},
 indsymtab: Indsymtab = .{},
 got: GotSection = .{},
+zig_got: ZigGotSection = .{},
 stubs: StubsSection = .{},
 stubs_helper: StubsHelperSection = .{},
 objc_stubs: ObjcStubsSection = .{},
@@ -337,6 +338,7 @@ pub fn deinit(self: *MachO) void {
     self.symtab.deinit(gpa);
     self.strtab.deinit(gpa);
     self.got.deinit(gpa);
+    self.zig_got.deinit(gpa);
     self.stubs.deinit(gpa);
     self.objc_stubs.deinit(gpa);
     self.tlv_ptr.deinit(gpa);
@@ -3157,6 +3159,13 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
     }
 }
 
+pub fn growSection(self: *MachO, sect_index: u8, size: u64) !void {
+    _ = self;
+    _ = sect_index;
+    _ = size;
+    @panic("TODO growSection");
+}
+
 pub fn getTarget(self: MachO) std.Target {
     return self.base.comp.root_mod.resolved_target.result;
 }
@@ -3657,6 +3666,7 @@ fn fmtDumpState(
     try writer.print("stubs\n{}\n", .{self.stubs.fmt(self)});
     try writer.print("objc_stubs\n{}\n", .{self.objc_stubs.fmt(self)});
     try writer.print("got\n{}\n", .{self.got.fmt(self)});
+    try writer.print("zig_got\n{}\n", .{self.zig_got.fmt(self)});
     try writer.print("tlv_ptr\n{}\n", .{self.tlv_ptr.fmt(self)});
     try writer.writeByte('\n');
     try writer.print("sections\n{}\n", .{self.fmtSections()});
@@ -3759,6 +3769,8 @@ const Section = struct {
     header: macho.section_64,
     segment_id: u8,
     atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
+    free_list: std.ArrayListUnmanaged(Atom.Index) = .{},
+    last_atom_index: Atom.Index = 0,
 };
 
 const HotUpdateState = struct {
@@ -4125,4 +4137,5 @@ const TlvPtrSection = synthetic.TlvPtrSection;
 const TypedValue = @import("../TypedValue.zig");
 const UnwindInfo = @import("MachO/UnwindInfo.zig");
 const WeakBindSection = synthetic.WeakBindSection;
+const ZigGotSection = synthetic.ZigGotSection;
 const ZigObject = @import("MachO/ZigObject.zig");