Commit 2c68fb3d7c

Jakub Konka <kubkon@jakubkonka.com>
2023-08-27 09:55:24
macho: merge Zld state with MachO state
1 parent 42e0850
src/link/MachO/dyld_info/bind.zig
@@ -1,12 +1,3 @@
-const std = @import("std");
-const assert = std.debug.assert;
-const leb = std.leb;
-const log = std.log.scoped(.dyld_info);
-const macho = std.macho;
-const testing = std.testing;
-
-const Allocator = std.mem.Allocator;
-
 pub fn Bind(comptime Ctx: type, comptime Target: type) type {
     return struct {
         entries: std.ArrayListUnmanaged(Entry) = .{},
@@ -738,3 +729,12 @@ test "lazy bind" {
         macho.BIND_OPCODE_DONE,
     }, bind.buffer.items);
 }
+
+const std = @import("std");
+const assert = std.debug.assert;
+const leb = std.leb;
+const log = std.log.scoped(.dyld_info);
+const macho = std.macho;
+const testing = std.testing;
+
+const Allocator = std.mem.Allocator;
src/link/MachO/dyld_info/Rebase.zig
@@ -1,14 +1,3 @@
-const Rebase = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const leb = std.leb;
-const log = std.log.scoped(.dyld_info);
-const macho = std.macho;
-const testing = std.testing;
-
-const Allocator = std.mem.Allocator;
-
 entries: std.ArrayListUnmanaged(Entry) = .{},
 buffer: std.ArrayListUnmanaged(u8) = .{},
 
@@ -572,3 +561,14 @@ test "rebase - composite" {
         macho.REBASE_OPCODE_DONE,
     }, rebase.buffer.items);
 }
+
+const Rebase = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const leb = std.leb;
+const log = std.log.scoped(.dyld_info);
+const macho = std.macho;
+const testing = std.testing;
+
+const Allocator = std.mem.Allocator;
src/link/MachO/Archive.zig
@@ -1,15 +1,3 @@
-const Archive = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const fs = std.fs;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const mem = std.mem;
-
-const Allocator = mem.Allocator;
-const Object = @import("Object.zig");
-
 file: fs.File,
 fat_offset: u64,
 name: []const u8,
@@ -215,3 +203,15 @@ pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object {
 
     return object;
 }
+
+const Archive = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const fs = std.fs;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Object = @import("Object.zig");
src/link/MachO/Atom.zig
@@ -105,8 +105,7 @@ pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
     return surplus >= MachO.min_text_capacity;
 }
 
-pub fn getOutputSection(zld: *Zld, sect: macho.section_64) !?u8 {
-    const gpa = zld.gpa;
+pub fn getOutputSection(macho_file: *MachO, sect: macho.section_64) !?u8 {
     const segname = sect.segName();
     const sectname = sect.sectName();
     const res: ?u8 = blk: {
@@ -126,20 +125,14 @@ pub fn getOutputSection(zld: *Zld, sect: macho.section_64) !?u8 {
         }
 
         if (sect.isCode()) {
-            if (zld.text_section_index == null) {
-                zld.text_section_index = try MachO.initSection(
-                    gpa,
-                    zld,
-                    "__TEXT",
-                    "__text",
-                    .{
-                        .flags = macho.S_REGULAR |
-                            macho.S_ATTR_PURE_INSTRUCTIONS |
-                            macho.S_ATTR_SOME_INSTRUCTIONS,
-                    },
-                );
+            if (macho_file.text_section_index == null) {
+                macho_file.text_section_index = try macho_file.initSection("__TEXT", "__text", .{
+                    .flags = macho.S_REGULAR |
+                        macho.S_ATTR_PURE_INSTRUCTIONS |
+                        macho.S_ATTR_SOME_INSTRUCTIONS,
+                });
             }
-            break :blk zld.text_section_index.?;
+            break :blk macho_file.text_section_index.?;
         }
 
         if (sect.isDebug()) {
@@ -151,42 +144,26 @@ pub fn getOutputSection(zld: *Zld, sect: macho.section_64) !?u8 {
             macho.S_8BYTE_LITERALS,
             macho.S_16BYTE_LITERALS,
             => {
-                break :blk zld.getSectionByName("__TEXT", "__const") orelse try MachO.initSection(
-                    gpa,
-                    zld,
-                    "__TEXT",
-                    "__const",
-                    .{},
-                );
+                break :blk macho_file.getSectionByName("__TEXT", "__const") orelse
+                    try macho_file.initSection("__TEXT", "__const", .{});
             },
             macho.S_CSTRING_LITERALS => {
                 if (mem.startsWith(u8, sectname, "__objc")) {
-                    break :blk zld.getSectionByName(segname, sectname) orelse try MachO.initSection(
-                        gpa,
-                        zld,
-                        segname,
-                        sectname,
-                        .{},
-                    );
+                    break :blk macho_file.getSectionByName(segname, sectname) orelse
+                        try macho_file.initSection(segname, sectname, .{});
                 }
-                break :blk zld.getSectionByName("__TEXT", "__cstring") orelse try MachO.initSection(
-                    gpa,
-                    zld,
-                    "__TEXT",
-                    "__cstring",
-                    .{ .flags = macho.S_CSTRING_LITERALS },
-                );
+                break :blk macho_file.getSectionByName("__TEXT", "__cstring") orelse
+                    try macho_file.initSection("__TEXT", "__cstring", .{
+                    .flags = macho.S_CSTRING_LITERALS,
+                });
             },
             macho.S_MOD_INIT_FUNC_POINTERS,
             macho.S_MOD_TERM_FUNC_POINTERS,
             => {
-                break :blk zld.getSectionByName("__DATA_CONST", sectname) orelse try MachO.initSection(
-                    gpa,
-                    zld,
-                    "__DATA_CONST",
-                    sectname,
-                    .{ .flags = sect.flags },
-                );
+                break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse
+                    try macho_file.initSection("__DATA_CONST", sectname, .{
+                    .flags = sect.flags,
+                });
             },
             macho.S_LITERAL_POINTERS,
             macho.S_ZEROFILL,
@@ -195,23 +172,14 @@ pub fn getOutputSection(zld: *Zld, sect: macho.section_64) !?u8 {
             macho.S_THREAD_LOCAL_REGULAR,
             macho.S_THREAD_LOCAL_ZEROFILL,
             => {
-                break :blk zld.getSectionByName(segname, sectname) orelse try MachO.initSection(
-                    gpa,
-                    zld,
-                    segname,
-                    sectname,
-                    .{ .flags = sect.flags },
-                );
+                break :blk macho_file.getSectionByName(segname, sectname) orelse
+                    try macho_file.initSection(segname, sectname, .{
+                    .flags = sect.flags,
+                });
             },
             macho.S_COALESCED => {
-                break :blk zld.getSectionByName(segname, sectname) orelse try MachO.initSection(
-                    gpa,
-                    zld,
-
-                    segname,
-                    sectname,
-                    .{},
-                );
+                break :blk macho_file.getSectionByName(segname, sectname) orelse
+                    try macho_file.initSection(segname, sectname, .{});
             },
             macho.S_REGULAR => {
                 if (mem.eql(u8, segname, "__TEXT")) {
@@ -221,13 +189,8 @@ pub fn getOutputSection(zld: *Zld, sect: macho.section_64) !?u8 {
                         mem.eql(u8, sectname, "__gosymtab") or
                         mem.eql(u8, sectname, "__gopclntab"))
                     {
-                        break :blk zld.getSectionByName("__TEXT", sectname) orelse try MachO.initSection(
-                            gpa,
-                            zld,
-                            "__TEXT",
-                            sectname,
-                            .{},
-                        );
+                        break :blk macho_file.getSectionByName("__TEXT", sectname) orelse
+                            try macho_file.initSection("__TEXT", sectname, .{});
                     }
                 }
                 if (mem.eql(u8, segname, "__DATA")) {
@@ -236,33 +199,17 @@ pub fn getOutputSection(zld: *Zld, sect: macho.section_64) !?u8 {
                         mem.eql(u8, sectname, "__objc_classlist") or
                         mem.eql(u8, sectname, "__objc_imageinfo"))
                     {
-                        break :blk zld.getSectionByName("__DATA_CONST", sectname) orelse try MachO.initSection(
-                            gpa,
-                            zld,
-                            "__DATA_CONST",
-                            sectname,
-                            .{},
-                        );
+                        break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse
+                            try macho_file.initSection("__DATA_CONST", sectname, .{});
                     } else if (mem.eql(u8, sectname, "__data")) {
-                        if (zld.data_section_index == null) {
-                            zld.data_section_index = try MachO.initSection(
-                                gpa,
-                                zld,
-                                "__DATA",
-                                "__data",
-                                .{},
-                            );
+                        if (macho_file.data_section_index == null) {
+                            macho_file.data_section_index = try macho_file.initSection("__DATA", "__data", .{});
                         }
-                        break :blk zld.data_section_index.?;
+                        break :blk macho_file.data_section_index.?;
                     }
                 }
-                break :blk zld.getSectionByName(segname, sectname) orelse try MachO.initSection(
-                    gpa,
-                    zld,
-                    segname,
-                    sectname,
-                    .{},
-                );
+                break :blk macho_file.getSectionByName(segname, sectname) orelse
+                    try macho_file.initSection(segname, sectname, .{});
             },
             else => break :blk null,
         }
@@ -270,29 +217,29 @@ pub fn getOutputSection(zld: *Zld, sect: macho.section_64) !?u8 {
 
     // TODO we can do this directly in the selection logic above.
     // Or is it not worth it?
-    if (zld.data_const_section_index == null) {
-        if (zld.getSectionByName("__DATA_CONST", "__const")) |index| {
-            zld.data_const_section_index = index;
+    if (macho_file.data_const_section_index == null) {
+        if (macho_file.getSectionByName("__DATA_CONST", "__const")) |index| {
+            macho_file.data_const_section_index = index;
         }
     }
-    if (zld.thread_vars_section_index == null) {
-        if (zld.getSectionByName("__DATA", "__thread_vars")) |index| {
-            zld.thread_vars_section_index = index;
+    if (macho_file.thread_vars_section_index == null) {
+        if (macho_file.getSectionByName("__DATA", "__thread_vars")) |index| {
+            macho_file.thread_vars_section_index = index;
         }
     }
-    if (zld.thread_data_section_index == null) {
-        if (zld.getSectionByName("__DATA", "__thread_data")) |index| {
-            zld.thread_data_section_index = index;
+    if (macho_file.thread_data_section_index == null) {
+        if (macho_file.getSectionByName("__DATA", "__thread_data")) |index| {
+            macho_file.thread_data_section_index = index;
         }
     }
-    if (zld.thread_bss_section_index == null) {
-        if (zld.getSectionByName("__DATA", "__thread_bss")) |index| {
-            zld.thread_bss_section_index = index;
+    if (macho_file.thread_bss_section_index == null) {
+        if (macho_file.getSectionByName("__DATA", "__thread_bss")) |index| {
+            macho_file.thread_bss_section_index = index;
         }
     }
-    if (zld.bss_section_index == null) {
-        if (zld.getSectionByName("__DATA", "__bss")) |index| {
-            zld.bss_section_index = index;
+    if (macho_file.bss_section_index == null) {
+        if (macho_file.getSectionByName("__DATA", "__bss")) |index| {
+            macho_file.bss_section_index = index;
         }
     }
 
@@ -383,8 +330,8 @@ const InnerSymIterator = struct {
 
 /// Returns an iterator over potentially contained symbols.
 /// Panics when called on a synthetic Atom.
-pub fn getInnerSymbolsIterator(zld: *Zld, atom_index: Index) InnerSymIterator {
-    const atom = zld.getAtom(atom_index);
+pub fn getInnerSymbolsIterator(macho_file: *MachO, atom_index: Index) InnerSymIterator {
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null);
     return .{
         .sym_index = atom.inner_sym_index,
@@ -397,11 +344,11 @@ pub fn getInnerSymbolsIterator(zld: *Zld, atom_index: Index) InnerSymIterator {
 /// An alias symbol is used to represent the start of an input section
 /// if there were no symbols defined within that range.
 /// Alias symbols are only used on x86_64.
-pub fn getSectionAlias(zld: *Zld, atom_index: Index) ?SymbolWithLoc {
-    const atom = zld.getAtom(atom_index);
+pub fn getSectionAlias(macho_file: *MachO, atom_index: Index) ?SymbolWithLoc {
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null);
 
-    const object = zld.objects.items[atom.getFile().?];
+    const object = macho_file.objects.items[atom.getFile().?];
     const nbase = @as(u32, @intCast(object.in_symtab.?.len));
     const ntotal = @as(u32, @intCast(object.symtab.len));
     var sym_index: u32 = nbase;
@@ -418,13 +365,13 @@ pub fn getSectionAlias(zld: *Zld, atom_index: Index) ?SymbolWithLoc {
 
 /// Given an index into a contained symbol within, calculates an offset wrt
 /// the start of this Atom.
-pub fn calcInnerSymbolOffset(zld: *Zld, atom_index: Index, sym_index: u32) u64 {
-    const atom = zld.getAtom(atom_index);
+pub fn calcInnerSymbolOffset(macho_file: *MachO, atom_index: Index, sym_index: u32) u64 {
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null);
 
     if (atom.sym_index == sym_index) return 0;
 
-    const object = zld.objects.items[atom.getFile().?];
+    const object = macho_file.objects.items[atom.getFile().?];
     const source_sym = object.getSourceSymbol(sym_index).?;
     const base_addr = if (object.getSourceSymbol(atom.sym_index)) |sym|
         sym.n_value
@@ -437,14 +384,14 @@ pub fn calcInnerSymbolOffset(zld: *Zld, atom_index: Index, sym_index: u32) u64 {
     return source_sym.n_value - base_addr;
 }
 
-pub fn scanAtomRelocs(zld: *Zld, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void {
-    const arch = zld.options.target.cpu.arch;
-    const atom = zld.getAtom(atom_index);
+pub fn scanAtomRelocs(macho_file: *MachO, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void {
+    const arch = macho_file.base.options.target.cpu.arch;
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null); // synthetic atoms do not have relocs
 
     return switch (arch) {
-        .aarch64 => scanAtomRelocsArm64(zld, atom_index, relocs),
-        .x86_64 => scanAtomRelocsX86(zld, atom_index, relocs),
+        .aarch64 => scanAtomRelocsArm64(macho_file, atom_index, relocs),
+        .x86_64 => scanAtomRelocsX86(macho_file, atom_index, relocs),
         else => unreachable,
     };
 }
@@ -454,11 +401,11 @@ const RelocContext = struct {
     base_offset: i32 = 0,
 };
 
-pub fn getRelocContext(zld: *Zld, atom_index: Index) RelocContext {
-    const atom = zld.getAtom(atom_index);
+pub fn getRelocContext(macho_file: *MachO, atom_index: Index) RelocContext {
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null); // synthetic atoms do not have relocs
 
-    const object = zld.objects.items[atom.getFile().?];
+    const object = macho_file.objects.items[atom.getFile().?];
     if (object.getSourceSymbol(atom.sym_index)) |source_sym| {
         const source_sect = object.getSourceSection(source_sym.n_sect - 1);
         return .{
@@ -475,7 +422,7 @@ pub fn getRelocContext(zld: *Zld, atom_index: Index) RelocContext {
     };
 }
 
-pub fn parseRelocTarget(zld: *Zld, ctx: struct {
+pub fn parseRelocTarget(macho_file: *MachO, ctx: struct {
     object_id: u32,
     rel: macho.relocation_info,
     code: []const u8,
@@ -485,7 +432,7 @@ pub fn parseRelocTarget(zld: *Zld, ctx: struct {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const object = &zld.objects.items[ctx.object_id];
+    const object = &macho_file.objects.items[ctx.object_id];
     log.debug("parsing reloc target in object({d}) '{s}' ", .{ ctx.object_id, object.name });
 
     const sym_index = if (ctx.rel.r_extern == 0) sym_index: {
@@ -498,7 +445,7 @@ pub fn parseRelocTarget(zld: *Zld, ctx: struct {
             else
                 mem.readIntLittle(u32, ctx.code[rel_offset..][0..4]);
         } else blk: {
-            assert(zld.options.target.cpu.arch == .x86_64);
+            assert(macho_file.base.options.target.cpu.arch == .x86_64);
             const correction: u3 = switch (@as(macho.reloc_type_x86_64, @enumFromInt(ctx.rel.r_type))) {
                 .X86_64_RELOC_SIGNED => 0,
                 .X86_64_RELOC_SIGNED_1 => 1,
@@ -517,35 +464,39 @@ pub fn parseRelocTarget(zld: *Zld, ctx: struct {
     } else object.reverse_symtab_lookup[ctx.rel.r_symbolnum];
 
     const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = ctx.object_id + 1 };
-    const sym = zld.getSymbol(sym_loc);
+    const sym = macho_file.getSymbol(sym_loc);
     const target = if (sym.sect() and !sym.ext())
         sym_loc
     else if (object.getGlobal(sym_index)) |global_index|
-        zld.globals.items[global_index]
+        macho_file.globals.items[global_index]
     else
         sym_loc;
     log.debug("  | target %{d} ('{s}') in object({?d})", .{
         target.sym_index,
-        zld.getSymbolName(target),
+        macho_file.getSymbolName(target),
         target.getFile(),
     });
     return target;
 }
 
-pub fn getRelocTargetAtomIndex(zld: *Zld, target: SymbolWithLoc) ?Index {
+pub fn getRelocTargetAtomIndex(macho_file: *MachO, target: SymbolWithLoc) ?Index {
     if (target.getFile() == null) {
-        const target_sym_name = zld.getSymbolName(target);
+        const target_sym_name = macho_file.getSymbolName(target);
         if (mem.eql(u8, "__mh_execute_header", target_sym_name)) return null;
         if (mem.eql(u8, "___dso_handle", target_sym_name)) return null;
 
         unreachable; // referenced symbol not found
     }
 
-    const object = zld.objects.items[target.getFile().?];
+    const object = macho_file.objects.items[target.getFile().?];
     return object.getAtomIndexForSymbol(target.sym_index);
 }
 
-fn scanAtomRelocsArm64(zld: *Zld, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void {
+fn scanAtomRelocsArm64(
+    macho_file: *MachO,
+    atom_index: Index,
+    relocs: []align(1) const macho.relocation_info,
+) !void {
     for (relocs) |rel| {
         const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
 
@@ -556,8 +507,8 @@ fn scanAtomRelocsArm64(zld: *Zld, atom_index: Index, relocs: []align(1) const ma
 
         if (rel.r_extern == 0) continue;
 
-        const atom = zld.getAtom(atom_index);
-        const object = &zld.objects.items[atom.getFile().?];
+        const atom = macho_file.getAtom(atom_index);
+        const object = &macho_file.objects.items[atom.getFile().?];
         const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];
         const sym_loc = SymbolWithLoc{
             .sym_index = sym_index,
@@ -565,35 +516,39 @@ fn scanAtomRelocsArm64(zld: *Zld, atom_index: Index, relocs: []align(1) const ma
         };
 
         const target = if (object.getGlobal(sym_index)) |global_index|
-            zld.globals.items[global_index]
+            macho_file.globals.items[global_index]
         else
             sym_loc;
 
         switch (rel_type) {
             .ARM64_RELOC_BRANCH26 => {
                 // TODO rewrite relocation
-                const sym = zld.getSymbol(target);
-                if (sym.undf()) try zld.addStubEntry(target);
+                const sym = macho_file.getSymbol(target);
+                if (sym.undf()) try macho_file.addStubEntry(target);
             },
             .ARM64_RELOC_GOT_LOAD_PAGE21,
             .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
             .ARM64_RELOC_POINTER_TO_GOT,
             => {
                 // TODO rewrite relocation
-                try zld.addGotEntry(target);
+                try macho_file.addGotEntry(target);
             },
             .ARM64_RELOC_TLVP_LOAD_PAGE21,
             .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
             => {
-                const sym = zld.getSymbol(target);
-                if (sym.undf()) try zld.addTlvPtrEntry(target);
+                const sym = macho_file.getSymbol(target);
+                if (sym.undf()) try macho_file.addTlvPtrEntry(target);
             },
             else => {},
         }
     }
 }
 
-fn scanAtomRelocsX86(zld: *Zld, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void {
+fn scanAtomRelocsX86(
+    macho_file: *MachO,
+    atom_index: Index,
+    relocs: []align(1) const macho.relocation_info,
+) !void {
     for (relocs) |rel| {
         const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
 
@@ -604,8 +559,8 @@ fn scanAtomRelocsX86(zld: *Zld, atom_index: Index, relocs: []align(1) const mach
 
         if (rel.r_extern == 0) continue;
 
-        const atom = zld.getAtom(atom_index);
-        const object = &zld.objects.items[atom.getFile().?];
+        const atom = macho_file.getAtom(atom_index);
+        const object = &macho_file.objects.items[atom.getFile().?];
         const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];
         const sym_loc = SymbolWithLoc{
             .sym_index = sym_index,
@@ -613,23 +568,23 @@ fn scanAtomRelocsX86(zld: *Zld, atom_index: Index, relocs: []align(1) const mach
         };
 
         const target = if (object.getGlobal(sym_index)) |global_index|
-            zld.globals.items[global_index]
+            macho_file.globals.items[global_index]
         else
             sym_loc;
 
         switch (rel_type) {
             .X86_64_RELOC_BRANCH => {
                 // TODO rewrite relocation
-                const sym = zld.getSymbol(target);
-                if (sym.undf()) try zld.addStubEntry(target);
+                const sym = macho_file.getSymbol(target);
+                if (sym.undf()) try macho_file.addStubEntry(target);
             },
             .X86_64_RELOC_GOT, .X86_64_RELOC_GOT_LOAD => {
                 // TODO rewrite relocation
-                try zld.addGotEntry(target);
+                try macho_file.addGotEntry(target);
             },
             .X86_64_RELOC_TLV => {
-                const sym = zld.getSymbol(target);
-                if (sym.undf()) try zld.addTlvPtrEntry(target);
+                const sym = macho_file.getSymbol(target);
+                if (sym.undf()) try macho_file.addTlvPtrEntry(target);
             },
             else => {},
         }
@@ -637,53 +592,53 @@ fn scanAtomRelocsX86(zld: *Zld, atom_index: Index, relocs: []align(1) const mach
 }
 
 pub fn resolveRelocs(
-    zld: *Zld,
+    macho_file: *MachO,
     atom_index: Index,
     atom_code: []u8,
     atom_relocs: []align(1) const macho.relocation_info,
 ) !void {
-    const arch = zld.options.target.cpu.arch;
-    const atom = zld.getAtom(atom_index);
+    const arch = macho_file.base.options.target.cpu.arch;
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null); // synthetic atoms do not have relocs
 
     log.debug("resolving relocations in ATOM(%{d}, '{s}')", .{
         atom.sym_index,
-        zld.getSymbolName(atom.getSymbolWithLoc()),
+        macho_file.getSymbolName(atom.getSymbolWithLoc()),
     });
 
-    const ctx = getRelocContext(zld, atom_index);
+    const ctx = getRelocContext(macho_file, atom_index);
 
     return switch (arch) {
-        .aarch64 => resolveRelocsArm64(zld, atom_index, atom_code, atom_relocs, ctx),
-        .x86_64 => resolveRelocsX86(zld, atom_index, atom_code, atom_relocs, ctx),
+        .aarch64 => resolveRelocsArm64(macho_file, atom_index, atom_code, atom_relocs, ctx),
+        .x86_64 => resolveRelocsX86(macho_file, atom_index, atom_code, atom_relocs, ctx),
         else => unreachable,
     };
 }
 
-pub fn getRelocTargetAddress(zld: *Zld, target: SymbolWithLoc, is_tlv: bool) !u64 {
-    const target_atom_index = getRelocTargetAtomIndex(zld, target) orelse {
+pub fn getRelocTargetAddress(macho_file: *MachO, target: SymbolWithLoc, is_tlv: bool) !u64 {
+    const target_atom_index = getRelocTargetAtomIndex(macho_file, target) orelse {
         // If there is no atom for target, we still need to check for special, atom-less
         // symbols such as `___dso_handle`.
-        const target_name = zld.getSymbolName(target);
-        const atomless_sym = zld.getSymbol(target);
+        const target_name = macho_file.getSymbolName(target);
+        const atomless_sym = macho_file.getSymbol(target);
         log.debug("    | atomless target '{s}'", .{target_name});
         return atomless_sym.n_value;
     };
-    const target_atom = zld.getAtom(target_atom_index);
+    const target_atom = macho_file.getAtom(target_atom_index);
     log.debug("    | target ATOM(%{d}, '{s}') in object({?})", .{
         target_atom.sym_index,
-        zld.getSymbolName(target_atom.getSymbolWithLoc()),
+        macho_file.getSymbolName(target_atom.getSymbolWithLoc()),
         target_atom.getFile(),
     });
 
-    const target_sym = zld.getSymbol(target_atom.getSymbolWithLoc());
+    const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
     assert(target_sym.n_desc != MachO.N_DEAD);
 
     // If `target` is contained within the target atom, pull its address value.
     const offset = if (target_atom.getFile() != null) blk: {
-        const object = zld.objects.items[target_atom.getFile().?];
+        const object = macho_file.objects.items[target_atom.getFile().?];
         break :blk if (object.getSourceSymbol(target.sym_index)) |_|
-            Atom.calcInnerSymbolOffset(zld, target_atom_index, target.sym_index)
+            Atom.calcInnerSymbolOffset(macho_file, target_atom_index, target.sym_index)
         else
             0; // section alias
     } else 0;
@@ -694,9 +649,9 @@ pub fn getRelocTargetAddress(zld: *Zld, target: SymbolWithLoc, is_tlv: bool) !u6
         // * wrt to __thread_data if defined, then
         // * wrt to __thread_bss
         const sect_id: u16 = sect_id: {
-            if (zld.thread_data_section_index) |i| {
+            if (macho_file.thread_data_section_index) |i| {
                 break :sect_id i;
-            } else if (zld.thread_bss_section_index) |i| {
+            } else if (macho_file.thread_bss_section_index) |i| {
                 break :sect_id i;
             } else {
                 log.err("threadlocal variables present but no initializer sections found", .{});
@@ -705,20 +660,20 @@ pub fn getRelocTargetAddress(zld: *Zld, target: SymbolWithLoc, is_tlv: bool) !u6
                 return error.FailedToResolveRelocationTarget;
             }
         };
-        break :base_address zld.sections.items(.header)[sect_id].addr;
+        break :base_address macho_file.sections.items(.header)[sect_id].addr;
     } else 0;
     return target_sym.n_value + offset - base_address;
 }
 
 fn resolveRelocsArm64(
-    zld: *Zld,
+    macho_file: *MachO,
     atom_index: Index,
     atom_code: []u8,
     atom_relocs: []align(1) const macho.relocation_info,
     context: RelocContext,
 ) !void {
-    const atom = zld.getAtom(atom_index);
-    const object = zld.objects.items[atom.getFile().?];
+    const atom = macho_file.getAtom(atom_index);
+    const object = macho_file.objects.items[atom.getFile().?];
 
     var addend: ?i64 = null;
     var subtractor: ?SymbolWithLoc = null;
@@ -745,7 +700,7 @@ fn resolveRelocsArm64(
                     atom.getFile(),
                 });
 
-                subtractor = parseRelocTarget(zld, .{
+                subtractor = parseRelocTarget(macho_file, .{
                     .object_id = atom.getFile().?,
                     .rel = rel,
                     .code = atom_code,
@@ -757,7 +712,7 @@ fn resolveRelocsArm64(
             else => {},
         }
 
-        const target = parseRelocTarget(zld, .{
+        const target = parseRelocTarget(macho_file, .{
             .object_id = atom.getFile().?,
             .rel = rel,
             .code = atom_code,
@@ -770,26 +725,26 @@ fn resolveRelocsArm64(
             @tagName(rel_type),
             rel.r_address,
             target.sym_index,
-            zld.getSymbolName(target),
+            macho_file.getSymbolName(target),
             target.getFile(),
         });
 
         const source_addr = blk: {
-            const source_sym = zld.getSymbol(atom.getSymbolWithLoc());
+            const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
             break :blk source_sym.n_value + rel_offset;
         };
         const target_addr = blk: {
-            if (relocRequiresGot(zld, rel)) break :blk zld.getGotEntryAddress(target).?;
-            if (relocIsTlv(zld, rel) and zld.getSymbol(target).undf())
-                break :blk zld.getTlvPtrEntryAddress(target).?;
-            if (relocIsStub(zld, rel) and zld.getSymbol(target).undf())
-                break :blk zld.getStubsEntryAddress(target).?;
+            if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?;
+            if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf())
+                break :blk macho_file.getTlvPtrEntryAddress(target).?;
+            if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf())
+                break :blk macho_file.getStubsEntryAddress(target).?;
             const is_tlv = is_tlv: {
-                const source_sym = zld.getSymbol(atom.getSymbolWithLoc());
-                const header = zld.sections.items(.header)[source_sym.n_sect - 1];
+                const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+                const header = macho_file.sections.items(.header)[source_sym.n_sect - 1];
                 break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES;
             };
-            break :blk try getRelocTargetAddress(zld, target, is_tlv);
+            break :blk try getRelocTargetAddress(macho_file, target, is_tlv);
         };
 
         log.debug("    | source_addr = 0x{x}", .{source_addr});
@@ -797,9 +752,9 @@ fn resolveRelocsArm64(
         switch (rel_type) {
             .ARM64_RELOC_BRANCH26 => {
                 log.debug("  source {s} (object({?})), target {s}", .{
-                    zld.getSymbolName(atom.getSymbolWithLoc()),
+                    macho_file.getSymbolName(atom.getSymbolWithLoc()),
                     atom.getFile(),
-                    zld.getSymbolName(target),
+                    macho_file.getSymbolName(target),
                 });
 
                 const displacement = if (Relocation.calcPcRelativeDisplacementArm64(
@@ -809,13 +764,13 @@ fn resolveRelocsArm64(
                     log.debug("    | target_addr = 0x{x}", .{target_addr});
                     break :blk disp;
                 } else |_| blk: {
-                    const thunk_index = zld.thunk_table.get(atom_index).?;
-                    const thunk = zld.thunks.items[thunk_index];
-                    const thunk_sym_loc = if (zld.getSymbol(target).undf())
-                        thunk.getTrampoline(zld, .stub, target).?
+                    const thunk_index = macho_file.thunk_table.get(atom_index).?;
+                    const thunk = macho_file.thunks.items[thunk_index];
+                    const thunk_sym_loc = if (macho_file.getSymbol(target).undf())
+                        thunk.getTrampoline(macho_file, .stub, target).?
                     else
-                        thunk.getTrampoline(zld, .atom, target).?;
-                    const thunk_addr = zld.getSymbol(thunk_sym_loc).n_value;
+                        thunk.getTrampoline(macho_file, .atom, target).?;
+                    const thunk_addr = macho_file.getSymbol(thunk_sym_loc).n_value;
                     log.debug("    | target_addr = 0x{x} (thunk)", .{thunk_addr});
                     break :blk try Relocation.calcPcRelativeDisplacementArm64(source_addr, thunk_addr);
                 };
@@ -944,7 +899,7 @@ fn resolveRelocsArm64(
                     }
                 };
 
-                var inst = if (zld.tlv_ptr_table.lookup.contains(target)) aarch64.Instruction{
+                var inst = if (macho_file.tlv_ptr_table.lookup.contains(target)) aarch64.Instruction{
                     .load_store_register = .{
                         .rt = reg_info.rd,
                         .rn = reg_info.rn,
@@ -992,7 +947,7 @@ fn resolveRelocsArm64(
 
                 const result = blk: {
                     if (subtractor) |sub| {
-                        const sym = zld.getSymbol(sub);
+                        const sym = macho_file.getSymbol(sub);
                         break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + ptr_addend;
                     } else {
                         break :blk @as(i64, @intCast(target_addr)) + ptr_addend;
@@ -1016,14 +971,14 @@ fn resolveRelocsArm64(
 }
 
 fn resolveRelocsX86(
-    zld: *Zld,
+    macho_file: *MachO,
     atom_index: Index,
     atom_code: []u8,
     atom_relocs: []align(1) const macho.relocation_info,
     context: RelocContext,
 ) !void {
-    const atom = zld.getAtom(atom_index);
-    const object = zld.objects.items[atom.getFile().?];
+    const atom = macho_file.getAtom(atom_index);
+    const object = macho_file.objects.items[atom.getFile().?];
 
     var subtractor: ?SymbolWithLoc = null;
 
@@ -1041,7 +996,7 @@ fn resolveRelocsX86(
                     atom.getFile(),
                 });
 
-                subtractor = parseRelocTarget(zld, .{
+                subtractor = parseRelocTarget(macho_file, .{
                     .object_id = atom.getFile().?,
                     .rel = rel,
                     .code = atom_code,
@@ -1053,7 +1008,7 @@ fn resolveRelocsX86(
             else => {},
         }
 
-        const target = parseRelocTarget(zld, .{
+        const target = parseRelocTarget(macho_file, .{
             .object_id = atom.getFile().?,
             .rel = rel,
             .code = atom_code,
@@ -1066,26 +1021,26 @@ fn resolveRelocsX86(
             @tagName(rel_type),
             rel.r_address,
             target.sym_index,
-            zld.getSymbolName(target),
+            macho_file.getSymbolName(target),
             target.getFile(),
         });
 
         const source_addr = blk: {
-            const source_sym = zld.getSymbol(atom.getSymbolWithLoc());
+            const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
             break :blk source_sym.n_value + rel_offset;
         };
         const target_addr = blk: {
-            if (relocRequiresGot(zld, rel)) break :blk zld.getGotEntryAddress(target).?;
-            if (relocIsStub(zld, rel) and zld.getSymbol(target).undf())
-                break :blk zld.getStubsEntryAddress(target).?;
-            if (relocIsTlv(zld, rel) and zld.getSymbol(target).undf())
-                break :blk zld.getTlvPtrEntryAddress(target).?;
+            if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?;
+            if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf())
+                break :blk macho_file.getStubsEntryAddress(target).?;
+            if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf())
+                break :blk macho_file.getTlvPtrEntryAddress(target).?;
             const is_tlv = is_tlv: {
-                const source_sym = zld.getSymbol(atom.getSymbolWithLoc());
-                const header = zld.sections.items(.header)[source_sym.n_sect - 1];
+                const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+                const header = macho_file.sections.items(.header)[source_sym.n_sect - 1];
                 break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES;
             };
-            break :blk try getRelocTargetAddress(zld, target, is_tlv);
+            break :blk try getRelocTargetAddress(macho_file, target, is_tlv);
         };
 
         log.debug("    | source_addr = 0x{x}", .{source_addr});
@@ -1115,7 +1070,7 @@ fn resolveRelocsX86(
                 log.debug("    | target_addr = 0x{x}", .{adjusted_target_addr});
                 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
 
-                if (zld.tlv_ptr_table.lookup.get(target) == null) {
+                if (macho_file.tlv_ptr_table.lookup.get(target) == null) {
                     // We need to rewrite the opcode from movq to leaq.
                     atom_code[rel_offset - 2] = 0x8d;
                 }
@@ -1170,7 +1125,7 @@ fn resolveRelocsX86(
 
                 const result = blk: {
                     if (subtractor) |sub| {
-                        const sym = zld.getSymbol(sub);
+                        const sym = macho_file.getSymbol(sub);
                         break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + addend;
                     } else {
                         break :blk @as(i64, @intCast(target_addr)) + addend;
@@ -1192,10 +1147,10 @@ fn resolveRelocsX86(
     }
 }
 
-pub fn getAtomCode(zld: *Zld, atom_index: Index) []const u8 {
-    const atom = zld.getAtom(atom_index);
+pub fn getAtomCode(macho_file: *MachO, atom_index: Index) []const u8 {
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null); // Synthetic atom shouldn't need to inquire for code.
-    const object = zld.objects.items[atom.getFile().?];
+    const object = macho_file.objects.items[atom.getFile().?];
     const source_sym = object.getSourceSymbol(atom.sym_index) orelse {
         // If there was no matching symbol present in the source symtab, this means
         // we are dealing with either an entire section, or part of it, but also
@@ -1216,10 +1171,10 @@ pub fn getAtomCode(zld: *Zld, atom_index: Index) []const u8 {
     return code[offset..][0..code_len];
 }
 
-pub fn getAtomRelocs(zld: *Zld, atom_index: Index) []const macho.relocation_info {
-    const atom = zld.getAtom(atom_index);
+pub fn getAtomRelocs(macho_file: *MachO, atom_index: Index) []const macho.relocation_info {
+    const atom = macho_file.getAtom(atom_index);
     assert(atom.getFile() != null); // Synthetic atom shouldn't need to unique for relocs.
-    const object = zld.objects.items[atom.getFile().?];
+    const object = macho_file.objects.items[atom.getFile().?];
     const cache = object.relocs_lookup[atom.sym_index];
 
     const source_sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
@@ -1238,8 +1193,8 @@ pub fn getAtomRelocs(zld: *Zld, atom_index: Index) []const macho.relocation_info
     return relocs[cache.start..][0..cache.len];
 }
 
-pub fn relocRequiresGot(zld: *Zld, rel: macho.relocation_info) bool {
-    switch (zld.options.target.cpu.arch) {
+pub fn relocRequiresGot(macho_file: *MachO, rel: macho.relocation_info) bool {
+    switch (macho_file.base.options.target.cpu.arch) {
         .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
             .ARM64_RELOC_GOT_LOAD_PAGE21,
             .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
@@ -1257,8 +1212,8 @@ pub fn relocRequiresGot(zld: *Zld, rel: macho.relocation_info) bool {
     }
 }
 
-pub fn relocIsTlv(zld: *Zld, rel: macho.relocation_info) bool {
-    switch (zld.options.target.cpu.arch) {
+pub fn relocIsTlv(macho_file: *MachO, rel: macho.relocation_info) bool {
+    switch (macho_file.base.options.target.cpu.arch) {
         .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
             .ARM64_RELOC_TLVP_LOAD_PAGE21,
             .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
@@ -1273,8 +1228,8 @@ pub fn relocIsTlv(zld: *Zld, rel: macho.relocation_info) bool {
     }
 }
 
-pub fn relocIsStub(zld: *Zld, rel: macho.relocation_info) bool {
-    switch (zld.options.target.cpu.arch) {
+pub fn relocIsStub(macho_file: *MachO, rel: macho.relocation_info) bool {
+    switch (macho_file.base.options.target.cpu.arch) {
         .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
             .ARM64_RELOC_BRANCH26 => return true,
             else => return false,
@@ -1305,4 +1260,3 @@ const Arch = std.Target.Cpu.Arch;
 const MachO = @import("../MachO.zig");
 pub const Relocation = @import("Relocation.zig");
 const SymbolWithLoc = MachO.SymbolWithLoc;
-const Zld = @import("zld.zig").Zld;
src/link/MachO/CodeSignature.zig
@@ -1,17 +1,175 @@
-const CodeSignature = @This();
+page_size: u16,
+code_directory: CodeDirectory,
+requirements: ?Requirements = null,
+entitlements: ?Entitlements = null,
+signature: ?Signature = null,
 
-const std = @import("std");
-const assert = std.debug.assert;
-const fs = std.fs;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const mem = std.mem;
-const testing = std.testing;
+pub fn init(page_size: u16) CodeSignature {
+    return .{
+        .page_size = page_size,
+        .code_directory = CodeDirectory.init(page_size),
+    };
+}
 
-const Allocator = mem.Allocator;
-const Compilation = @import("../../Compilation.zig");
-const Hasher = @import("hasher.zig").ParallelHasher;
-const Sha256 = std.crypto.hash.sha2.Sha256;
+pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
+    self.code_directory.deinit(allocator);
+    if (self.requirements) |*req| {
+        req.deinit(allocator);
+    }
+    if (self.entitlements) |*ents| {
+        ents.deinit(allocator);
+    }
+    if (self.signature) |*sig| {
+        sig.deinit(allocator);
+    }
+}
+
+pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
+    const file = try fs.cwd().openFile(path, .{});
+    defer file.close();
+    const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
+    self.entitlements = .{ .inner = inner };
+}
+
+pub const WriteOpts = struct {
+    file: fs.File,
+    exec_seg_base: u64,
+    exec_seg_limit: u64,
+    file_size: u32,
+    output_mode: std.builtin.OutputMode,
+};
+
+pub fn writeAdhocSignature(
+    self: *CodeSignature,
+    comp: *const Compilation,
+    opts: WriteOpts,
+    writer: anytype,
+) !void {
+    const gpa = comp.gpa;
+
+    var header: macho.SuperBlob = .{
+        .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
+        .length = @sizeOf(macho.SuperBlob),
+        .count = 0,
+    };
+
+    var blobs = std.ArrayList(Blob).init(gpa);
+    defer blobs.deinit();
+
+    self.code_directory.inner.execSegBase = opts.exec_seg_base;
+    self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
+    self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
+    self.code_directory.inner.codeLimit = opts.file_size;
+
+    const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
+
+    try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages);
+    self.code_directory.code_slots.items.len = total_pages;
+    self.code_directory.inner.nCodeSlots = total_pages;
+
+    // Calculate hash for each page (in file) and write it to the buffer
+    var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool };
+    try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
+        .chunk_size = self.page_size,
+        .max_file_size = opts.file_size,
+    });
+
+    try blobs.append(.{ .code_directory = &self.code_directory });
+    header.length += @sizeOf(macho.BlobIndex);
+    header.count += 1;
+
+    var hash: [hash_size]u8 = undefined;
+
+    if (self.requirements) |*req| {
+        var buf = std.ArrayList(u8).init(gpa);
+        defer buf.deinit();
+        try req.write(buf.writer());
+        Sha256.hash(buf.items, &hash, .{});
+        self.code_directory.addSpecialHash(req.slotType(), hash);
+
+        try blobs.append(.{ .requirements = req });
+        header.count += 1;
+        header.length += @sizeOf(macho.BlobIndex) + req.size();
+    }
+
+    if (self.entitlements) |*ents| {
+        var buf = std.ArrayList(u8).init(gpa);
+        defer buf.deinit();
+        try ents.write(buf.writer());
+        Sha256.hash(buf.items, &hash, .{});
+        self.code_directory.addSpecialHash(ents.slotType(), hash);
+
+        try blobs.append(.{ .entitlements = ents });
+        header.count += 1;
+        header.length += @sizeOf(macho.BlobIndex) + ents.size();
+    }
+
+    if (self.signature) |*sig| {
+        try blobs.append(.{ .signature = sig });
+        header.count += 1;
+        header.length += @sizeOf(macho.BlobIndex) + sig.size();
+    }
+
+    self.code_directory.inner.hashOffset =
+        @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
+    self.code_directory.inner.length = self.code_directory.size();
+    header.length += self.code_directory.size();
+
+    try writer.writeIntBig(u32, header.magic);
+    try writer.writeIntBig(u32, header.length);
+    try writer.writeIntBig(u32, header.count);
+
+    var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
+    for (blobs.items) |blob| {
+        try writer.writeIntBig(u32, blob.slotType());
+        try writer.writeIntBig(u32, offset);
+        offset += blob.size();
+    }
+
+    for (blobs.items) |blob| {
+        try blob.write(writer);
+    }
+}
+
+pub fn size(self: CodeSignature) u32 {
+    var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
+    if (self.requirements) |req| {
+        ssize += @sizeOf(macho.BlobIndex) + req.size();
+    }
+    if (self.entitlements) |ent| {
+        ssize += @sizeOf(macho.BlobIndex) + ent.size();
+    }
+    if (self.signature) |sig| {
+        ssize += @sizeOf(macho.BlobIndex) + sig.size();
+    }
+    return ssize;
+}
+
+pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
+    var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
+    // Approx code slots
+    const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
+    ssize += total_pages * hash_size;
+    var n_special_slots: u32 = 0;
+    if (self.requirements) |req| {
+        ssize += @sizeOf(macho.BlobIndex) + req.size();
+        n_special_slots = @max(n_special_slots, req.slotType());
+    }
+    if (self.entitlements) |ent| {
+        ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
+        n_special_slots = @max(n_special_slots, ent.slotType());
+    }
+    if (self.signature) |sig| {
+        ssize += @sizeOf(macho.BlobIndex) + sig.size();
+    }
+    ssize += n_special_slots * hash_size;
+    return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
+}
+
+pub fn clear(self: *CodeSignature, allocator: Allocator) void {
+    self.code_directory.deinit(allocator);
+    self.code_directory = CodeDirectory.init(self.page_size);
+}
 
 const hash_size = Sha256.digest_length;
 
@@ -218,175 +376,17 @@ const Signature = struct {
     }
 };
 
-page_size: u16,
-code_directory: CodeDirectory,
-requirements: ?Requirements = null,
-entitlements: ?Entitlements = null,
-signature: ?Signature = null,
-
-pub fn init(page_size: u16) CodeSignature {
-    return .{
-        .page_size = page_size,
-        .code_directory = CodeDirectory.init(page_size),
-    };
-}
-
-pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
-    self.code_directory.deinit(allocator);
-    if (self.requirements) |*req| {
-        req.deinit(allocator);
-    }
-    if (self.entitlements) |*ents| {
-        ents.deinit(allocator);
-    }
-    if (self.signature) |*sig| {
-        sig.deinit(allocator);
-    }
-}
-
-pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
-    const file = try fs.cwd().openFile(path, .{});
-    defer file.close();
-    const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
-    self.entitlements = .{ .inner = inner };
-}
-
-pub const WriteOpts = struct {
-    file: fs.File,
-    exec_seg_base: u64,
-    exec_seg_limit: u64,
-    file_size: u32,
-    output_mode: std.builtin.OutputMode,
-};
-
-pub fn writeAdhocSignature(
-    self: *CodeSignature,
-    comp: *const Compilation,
-    opts: WriteOpts,
-    writer: anytype,
-) !void {
-    const gpa = comp.gpa;
-
-    var header: macho.SuperBlob = .{
-        .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
-        .length = @sizeOf(macho.SuperBlob),
-        .count = 0,
-    };
-
-    var blobs = std.ArrayList(Blob).init(gpa);
-    defer blobs.deinit();
-
-    self.code_directory.inner.execSegBase = opts.exec_seg_base;
-    self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
-    self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
-    self.code_directory.inner.codeLimit = opts.file_size;
-
-    const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
-
-    try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages);
-    self.code_directory.code_slots.items.len = total_pages;
-    self.code_directory.inner.nCodeSlots = total_pages;
-
-    // Calculate hash for each page (in file) and write it to the buffer
-    var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool };
-    try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
-        .chunk_size = self.page_size,
-        .max_file_size = opts.file_size,
-    });
-
-    try blobs.append(.{ .code_directory = &self.code_directory });
-    header.length += @sizeOf(macho.BlobIndex);
-    header.count += 1;
-
-    var hash: [hash_size]u8 = undefined;
-
-    if (self.requirements) |*req| {
-        var buf = std.ArrayList(u8).init(gpa);
-        defer buf.deinit();
-        try req.write(buf.writer());
-        Sha256.hash(buf.items, &hash, .{});
-        self.code_directory.addSpecialHash(req.slotType(), hash);
-
-        try blobs.append(.{ .requirements = req });
-        header.count += 1;
-        header.length += @sizeOf(macho.BlobIndex) + req.size();
-    }
-
-    if (self.entitlements) |*ents| {
-        var buf = std.ArrayList(u8).init(gpa);
-        defer buf.deinit();
-        try ents.write(buf.writer());
-        Sha256.hash(buf.items, &hash, .{});
-        self.code_directory.addSpecialHash(ents.slotType(), hash);
-
-        try blobs.append(.{ .entitlements = ents });
-        header.count += 1;
-        header.length += @sizeOf(macho.BlobIndex) + ents.size();
-    }
-
-    if (self.signature) |*sig| {
-        try blobs.append(.{ .signature = sig });
-        header.count += 1;
-        header.length += @sizeOf(macho.BlobIndex) + sig.size();
-    }
-
-    self.code_directory.inner.hashOffset =
-        @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
-    self.code_directory.inner.length = self.code_directory.size();
-    header.length += self.code_directory.size();
-
-    try writer.writeIntBig(u32, header.magic);
-    try writer.writeIntBig(u32, header.length);
-    try writer.writeIntBig(u32, header.count);
-
-    var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
-    for (blobs.items) |blob| {
-        try writer.writeIntBig(u32, blob.slotType());
-        try writer.writeIntBig(u32, offset);
-        offset += blob.size();
-    }
-
-    for (blobs.items) |blob| {
-        try blob.write(writer);
-    }
-}
-
-pub fn size(self: CodeSignature) u32 {
-    var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
-    if (self.requirements) |req| {
-        ssize += @sizeOf(macho.BlobIndex) + req.size();
-    }
-    if (self.entitlements) |ent| {
-        ssize += @sizeOf(macho.BlobIndex) + ent.size();
-    }
-    if (self.signature) |sig| {
-        ssize += @sizeOf(macho.BlobIndex) + sig.size();
-    }
-    return ssize;
-}
+const CodeSignature = @This();
 
-pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
-    var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
-    // Approx code slots
-    const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
-    ssize += total_pages * hash_size;
-    var n_special_slots: u32 = 0;
-    if (self.requirements) |req| {
-        ssize += @sizeOf(macho.BlobIndex) + req.size();
-        n_special_slots = @max(n_special_slots, req.slotType());
-    }
-    if (self.entitlements) |ent| {
-        ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
-        n_special_slots = @max(n_special_slots, ent.slotType());
-    }
-    if (self.signature) |sig| {
-        ssize += @sizeOf(macho.BlobIndex) + sig.size();
-    }
-    ssize += n_special_slots * hash_size;
-    return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
-}
+const std = @import("std");
+const assert = std.debug.assert;
+const fs = std.fs;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const mem = std.mem;
+const testing = std.testing;
 
-pub fn clear(self: *CodeSignature, allocator: Allocator) void {
-    self.code_directory.deinit(allocator);
-    self.code_directory = CodeDirectory.init(self.page_size);
-}
+const Allocator = mem.Allocator;
+const Compilation = @import("../../Compilation.zig");
+const Hasher = @import("hasher.zig").ParallelHasher;
+const Sha256 = std.crypto.hash.sha2.Sha256;
src/link/MachO/dead_strip.zig
@@ -1,89 +1,72 @@
 //! An algorithm for dead stripping of unreferenced Atoms.
 
-const std = @import("std");
-const assert = std.debug.assert;
-const eh_frame = @import("eh_frame.zig");
-const log = std.log.scoped(.dead_strip);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-
-const Allocator = mem.Allocator;
-const Atom = @import("Atom.zig");
-const MachO = @import("../MachO.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const UnwindInfo = @import("UnwindInfo.zig");
-const Zld = @import("zld.zig").Zld;
-
-const AtomTable = std.AutoHashMap(Atom.Index, void);
-
-pub fn gcAtoms(zld: *Zld) !void {
-    const gpa = zld.gpa;
+pub fn gcAtoms(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
 
     var arena = std.heap.ArenaAllocator.init(gpa);
     defer arena.deinit();
 
     var roots = AtomTable.init(arena.allocator());
-    try roots.ensureUnusedCapacity(@as(u32, @intCast(zld.globals.items.len)));
+    try roots.ensureUnusedCapacity(@as(u32, @intCast(macho_file.globals.items.len)));
 
     var alive = AtomTable.init(arena.allocator());
-    try alive.ensureTotalCapacity(@as(u32, @intCast(zld.atoms.items.len)));
+    try alive.ensureTotalCapacity(@as(u32, @intCast(macho_file.atoms.items.len)));
 
-    try collectRoots(zld, &roots);
-    try mark(zld, roots, &alive);
-    prune(zld, alive);
+    try collectRoots(macho_file, &roots);
+    try mark(macho_file, roots, &alive);
+    prune(macho_file, alive);
 }
 
-fn addRoot(zld: *Zld, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void {
-    const sym = zld.getSymbol(sym_loc);
+fn addRoot(macho_file: *MachO, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void {
+    const sym = macho_file.getSymbol(sym_loc);
     assert(!sym.undf());
-    const object = &zld.objects.items[file];
+    const object = &macho_file.objects.items[file];
     const atom_index = object.getAtomIndexForSymbol(sym_loc.sym_index).?; // panic here means fatal error
     log.debug("root(ATOM({d}, %{d}, {d}))", .{
         atom_index,
-        zld.getAtom(atom_index).sym_index,
+        macho_file.getAtom(atom_index).sym_index,
         file,
     });
     _ = try roots.getOrPut(atom_index);
 }
 
-fn collectRoots(zld: *Zld, roots: *AtomTable) !void {
+fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void {
     log.debug("collecting roots", .{});
 
-    switch (zld.options.output_mode) {
+    switch (macho_file.base.options.output_mode) {
         .Exe => {
             // Add entrypoint as GC root
-            const global: SymbolWithLoc = zld.getEntryPoint();
+            const global: SymbolWithLoc = macho_file.getEntryPoint();
             if (global.getFile()) |file| {
-                try addRoot(zld, roots, file, global);
+                try addRoot(macho_file, roots, file, global);
             } else {
-                assert(zld.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib.
+                assert(macho_file.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib.
             }
         },
         else => |other| {
             assert(other == .Lib);
             // Add exports as GC roots
-            for (zld.globals.items) |global| {
-                const sym = zld.getSymbol(global);
+            for (macho_file.globals.items) |global| {
+                const sym = macho_file.getSymbol(global);
                 if (sym.undf()) continue;
 
                 if (global.getFile()) |file| {
-                    try addRoot(zld, roots, file, global);
+                    try addRoot(macho_file, roots, file, global);
                 }
             }
         },
     }
 
     // Add all symbols force-defined by the user.
-    for (zld.options.force_undefined_symbols.keys()) |sym_name| {
-        const global_index = zld.resolver.get(sym_name).?;
-        const global = zld.globals.items[global_index];
-        const sym = zld.getSymbol(global);
+    for (macho_file.base.options.force_undefined_symbols.keys()) |sym_name| {
+        const global_index = macho_file.resolver.get(sym_name).?;
+        const global = macho_file.globals.items[global_index];
+        const sym = macho_file.getSymbol(global);
         assert(!sym.undf());
-        try addRoot(zld, roots, global.getFile().?, global);
+        try addRoot(macho_file, roots, global.getFile().?, global);
     }
 
-    for (zld.objects.items) |object| {
+    for (macho_file.objects.items) |object| {
         const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
 
         for (object.atoms.items) |atom_index| {
@@ -92,7 +75,7 @@ fn collectRoots(zld: *Zld, roots: *AtomTable) !void {
                 // as a root.
                 if (!has_subsections) break :blk true;
 
-                const atom = zld.getAtom(atom_index);
+                const atom = macho_file.getAtom(atom_index);
                 const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
                     source_sym.n_sect - 1
                 else sect_id: {
@@ -115,39 +98,39 @@ fn collectRoots(zld: *Zld, roots: *AtomTable) !void {
 
                 log.debug("root(ATOM({d}, %{d}, {?d}))", .{
                     atom_index,
-                    zld.getAtom(atom_index).sym_index,
-                    zld.getAtom(atom_index).getFile(),
+                    macho_file.getAtom(atom_index).sym_index,
+                    macho_file.getAtom(atom_index).getFile(),
                 });
             }
         }
     }
 }
 
-fn markLive(zld: *Zld, atom_index: Atom.Index, alive: *AtomTable) void {
+fn markLive(macho_file: *MachO, atom_index: Atom.Index, alive: *AtomTable) void {
     if (alive.contains(atom_index)) return;
 
-    const atom = zld.getAtom(atom_index);
+    const atom = macho_file.getAtom(atom_index);
     const sym_loc = atom.getSymbolWithLoc();
 
     log.debug("mark(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
 
     alive.putAssumeCapacityNoClobber(atom_index, {});
 
-    const cpu_arch = zld.options.target.cpu.arch;
+    const cpu_arch = macho_file.options.target.cpu.arch;
 
-    const sym = zld.getSymbol(atom.getSymbolWithLoc());
-    const header = zld.sections.items(.header)[sym.n_sect - 1];
+    const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+    const header = macho_file.sections.items(.header)[sym.n_sect - 1];
     if (header.isZerofill()) return;
 
-    const code = Atom.getAtomCode(zld, atom_index);
-    const relocs = Atom.getAtomRelocs(zld, atom_index);
-    const ctx = Atom.getRelocContext(zld, atom_index);
+    const code = Atom.getAtomCode(macho_file, atom_index);
+    const relocs = Atom.getAtomRelocs(macho_file, atom_index);
+    const ctx = Atom.getRelocContext(macho_file, atom_index);
 
     for (relocs) |rel| {
         const target = switch (cpu_arch) {
             .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
                 .ARM64_RELOC_ADDEND => continue,
-                else => Atom.parseRelocTarget(zld, .{
+                else => Atom.parseRelocTarget(macho_file, .{
                     .object_id = atom.getFile().?,
                     .rel = rel,
                     .code = code,
@@ -155,7 +138,7 @@ fn markLive(zld: *Zld, atom_index: Atom.Index, alive: *AtomTable) void {
                     .base_addr = ctx.base_addr,
                 }),
             },
-            .x86_64 => Atom.parseRelocTarget(zld, .{
+            .x86_64 => Atom.parseRelocTarget(macho_file, .{
                 .object_id = atom.getFile().?,
                 .rel = rel,
                 .code = code,
@@ -164,50 +147,50 @@ fn markLive(zld: *Zld, atom_index: Atom.Index, alive: *AtomTable) void {
             }),
             else => unreachable,
         };
-        const target_sym = zld.getSymbol(target);
+        const target_sym = macho_file.getSymbol(target);
 
         if (target_sym.undf()) continue;
         if (target.getFile() == null) {
-            const target_sym_name = zld.getSymbolName(target);
+            const target_sym_name = macho_file.getSymbolName(target);
             if (mem.eql(u8, "__mh_execute_header", target_sym_name)) continue;
             if (mem.eql(u8, "___dso_handle", target_sym_name)) continue;
 
             unreachable; // referenced symbol not found
         }
 
-        const object = zld.objects.items[target.getFile().?];
+        const object = macho_file.objects.items[target.getFile().?];
         const target_atom_index = object.getAtomIndexForSymbol(target.sym_index).?;
         log.debug("  following ATOM({d}, %{d}, {?d})", .{
             target_atom_index,
-            zld.getAtom(target_atom_index).sym_index,
-            zld.getAtom(target_atom_index).getFile(),
+            macho_file.getAtom(target_atom_index).sym_index,
+            macho_file.getAtom(target_atom_index).getFile(),
         });
 
-        markLive(zld, target_atom_index, alive);
+        markLive(macho_file, target_atom_index, alive);
     }
 }
 
-fn refersLive(zld: *Zld, atom_index: Atom.Index, alive: AtomTable) bool {
-    const atom = zld.getAtom(atom_index);
+fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool {
+    const atom = macho_file.getAtom(atom_index);
     const sym_loc = atom.getSymbolWithLoc();
 
     log.debug("refersLive(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
 
-    const cpu_arch = zld.options.target.cpu.arch;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
 
-    const sym = zld.getSymbol(sym_loc);
-    const header = zld.sections.items(.header)[sym.n_sect - 1];
+    const sym = macho_file.getSymbol(sym_loc);
+    const header = macho_file.sections.items(.header)[sym.n_sect - 1];
     assert(!header.isZerofill());
 
-    const code = Atom.getAtomCode(zld, atom_index);
-    const relocs = Atom.getAtomRelocs(zld, atom_index);
-    const ctx = Atom.getRelocContext(zld, atom_index);
+    const code = Atom.getAtomCode(macho_file, atom_index);
+    const relocs = Atom.getAtomRelocs(macho_file, atom_index);
+    const ctx = Atom.getRelocContext(macho_file, atom_index);
 
     for (relocs) |rel| {
         const target = switch (cpu_arch) {
             .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
                 .ARM64_RELOC_ADDEND => continue,
-                else => Atom.parseRelocTarget(zld, .{
+                else => Atom.parseRelocTarget(macho_file, .{
                     .object_id = atom.getFile().?,
                     .rel = rel,
                     .code = code,
@@ -215,7 +198,7 @@ fn refersLive(zld: *Zld, atom_index: Atom.Index, alive: AtomTable) bool {
                     .base_addr = ctx.base_addr,
                 }),
             },
-            .x86_64 => Atom.parseRelocTarget(zld, .{
+            .x86_64 => Atom.parseRelocTarget(macho_file, .{
                 .object_id = atom.getFile().?,
                 .rel = rel,
                 .code = code,
@@ -225,16 +208,16 @@ fn refersLive(zld: *Zld, atom_index: Atom.Index, alive: AtomTable) bool {
             else => unreachable,
         };
 
-        const object = zld.objects.items[target.getFile().?];
+        const object = macho_file.objects.items[target.getFile().?];
         const target_atom_index = object.getAtomIndexForSymbol(target.sym_index) orelse {
-            log.debug("atom for symbol '{s}' not found; skipping...", .{zld.getSymbolName(target)});
+            log.debug("atom for symbol '{s}' not found; skipping...", .{macho_file.getSymbolName(target)});
             continue;
         };
         if (alive.contains(target_atom_index)) {
             log.debug("  refers live ATOM({d}, %{d}, {?d})", .{
                 target_atom_index,
-                zld.getAtom(target_atom_index).sym_index,
-                zld.getAtom(target_atom_index).getFile(),
+                macho_file.getAtom(target_atom_index).sym_index,
+                macho_file.getAtom(target_atom_index).getFile(),
             });
             return true;
         }
@@ -243,21 +226,21 @@ fn refersLive(zld: *Zld, atom_index: Atom.Index, alive: AtomTable) bool {
     return false;
 }
 
-fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {
+fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) !void {
     var it = roots.keyIterator();
     while (it.next()) |root| {
-        markLive(zld, root.*, alive);
+        markLive(macho_file, root.*, alive);
     }
 
     var loop: bool = true;
     while (loop) {
         loop = false;
 
-        for (zld.objects.items) |object| {
+        for (macho_file.objects.items) |object| {
             for (object.atoms.items) |atom_index| {
                 if (alive.contains(atom_index)) continue;
 
-                const atom = zld.getAtom(atom_index);
+                const atom = macho_file.getAtom(atom_index);
                 const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
                     source_sym.n_sect - 1
                 else blk: {
@@ -268,8 +251,8 @@ fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {
                 const source_sect = object.getSourceSection(sect_id);
 
                 if (source_sect.isDontDeadStripIfReferencesLive()) {
-                    if (refersLive(zld, atom_index, alive.*)) {
-                        markLive(zld, atom_index, alive);
+                    if (refersLive(macho_file, atom_index, alive.*)) {
+                        markLive(macho_file, atom_index, alive);
                         loop = true;
                     }
                 }
@@ -277,26 +260,26 @@ fn mark(zld: *Zld, roots: AtomTable, alive: *AtomTable) !void {
         }
     }
 
-    for (zld.objects.items, 0..) |_, object_id| {
+    for (macho_file.objects.items, 0..) |_, object_id| {
         // Traverse unwind and eh_frame records noting if the source symbol has been marked, and if so,
         // marking all references as live.
-        try markUnwindRecords(zld, @as(u32, @intCast(object_id)), alive);
+        try markUnwindRecords(macho_file, @as(u32, @intCast(object_id)), alive);
     }
 }
 
-fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
-    const object = &zld.objects.items[object_id];
-    const cpu_arch = zld.options.target.cpu.arch;
+fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) !void {
+    const object = &macho_file.objects.items[object_id];
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
 
     const unwind_records = object.getUnwindRecords();
 
     for (object.exec_atoms.items) |atom_index| {
-        var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+        var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
 
         if (!object.hasUnwindRecords()) {
             if (alive.contains(atom_index)) {
                 // Mark references live and continue.
-                try markEhFrameRecords(zld, object_id, atom_index, alive);
+                try markEhFrameRecords(macho_file, object_id, atom_index, alive);
             } else {
                 while (inner_syms_it.next()) |sym| {
                     if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
@@ -322,51 +305,51 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
 
             const record = unwind_records[record_id];
             if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
-                try markEhFrameRecords(zld, object_id, atom_index, alive);
+                try markEhFrameRecords(macho_file, object_id, atom_index, alive);
             } else {
-                if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {
-                    const target = Atom.parseRelocTarget(zld, .{
+                if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| {
+                    const target = Atom.parseRelocTarget(macho_file, .{
                         .object_id = object_id,
                         .rel = rel,
                         .code = mem.asBytes(&record),
                         .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
                     });
-                    const target_sym = zld.getSymbol(target);
+                    const target_sym = macho_file.getSymbol(target);
                     if (!target_sym.undf()) {
-                        const target_object = zld.objects.items[target.getFile().?];
+                        const target_object = macho_file.objects.items[target.getFile().?];
                         const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
-                        markLive(zld, target_atom_index, alive);
+                        markLive(macho_file, target_atom_index, alive);
                     }
                 }
 
-                if (UnwindInfo.getLsdaReloc(zld, object_id, record_id)) |rel| {
-                    const target = Atom.parseRelocTarget(zld, .{
+                if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| {
+                    const target = Atom.parseRelocTarget(macho_file, .{
                         .object_id = object_id,
                         .rel = rel,
                         .code = mem.asBytes(&record),
                         .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
                     });
-                    const target_object = zld.objects.items[target.getFile().?];
+                    const target_object = macho_file.objects.items[target.getFile().?];
                     const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
-                    markLive(zld, target_atom_index, alive);
+                    markLive(macho_file, target_atom_index, alive);
                 }
             }
         }
     }
 }
 
-fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) !void {
-    const cpu_arch = zld.options.target.cpu.arch;
-    const object = &zld.objects.items[object_id];
+fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) !void {
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+    const object = &macho_file.objects.items[object_id];
     var it = object.getEhFrameRecordsIterator();
-    var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+    var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
 
     while (inner_syms_it.next()) |sym| {
         const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias
         it.seekTo(fde_offset);
         const fde = (try it.next()).?;
 
-        const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
+        const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
         const cie_offset = fde_offset + 4 - cie_ptr;
         it.seekTo(cie_offset);
         const cie = (try it.next()).?;
@@ -374,20 +357,20 @@ fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: Atom.Index, alive:
         switch (cpu_arch) {
             .aarch64 => {
                 // Mark FDE references which should include any referenced LSDA record
-                const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
+                const relocs = eh_frame.getRelocs(macho_file, object_id, fde_offset);
                 for (relocs) |rel| {
-                    const target = Atom.parseRelocTarget(zld, .{
+                    const target = Atom.parseRelocTarget(macho_file, .{
                         .object_id = object_id,
                         .rel = rel,
                         .code = fde.data,
                         .base_offset = @as(i32, @intCast(fde_offset)) + 4,
                     });
-                    const target_sym = zld.getSymbol(target);
+                    const target_sym = macho_file.getSymbol(target);
                     if (!target_sym.undf()) blk: {
-                        const target_object = zld.objects.items[target.getFile().?];
+                        const target_object = macho_file.objects.items[target.getFile().?];
                         const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
                             break :blk;
-                        markLive(zld, target_atom_index, alive);
+                        markLive(macho_file, target_atom_index, alive);
                     }
                 }
             },
@@ -401,7 +384,7 @@ fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: Atom.Index, alive:
                     // Mark LSDA record as live
                     const sym_index = object.getSymbolByAddress(lsda_address, null);
                     const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
-                    markLive(zld, target_atom_index, alive);
+                    markLive(macho_file, target_atom_index, alive);
                 }
             },
             else => unreachable,
@@ -409,20 +392,20 @@ fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: Atom.Index, alive:
 
         // Mark CIE references which should include any referenced personalities
         // that are defined locally.
-        if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
-            const target_sym = zld.getSymbol(target);
+        if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |target| {
+            const target_sym = macho_file.getSymbol(target);
             if (!target_sym.undf()) {
-                const target_object = zld.objects.items[target.getFile().?];
+                const target_object = macho_file.objects.items[target.getFile().?];
                 const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
-                markLive(zld, target_atom_index, alive);
+                markLive(macho_file, target_atom_index, alive);
             }
         }
     }
 }
 
-fn prune(zld: *Zld, alive: AtomTable) void {
+fn prune(macho_file: *MachO, alive: AtomTable) void {
     log.debug("pruning dead atoms", .{});
-    for (zld.objects.items) |*object| {
+    for (macho_file.objects.items) |*object| {
         var i: usize = 0;
         while (i < object.atoms.items.len) {
             const atom_index = object.atoms.items[i];
@@ -431,7 +414,7 @@ fn prune(zld: *Zld, alive: AtomTable) void {
                 continue;
             }
 
-            const atom = zld.getAtom(atom_index);
+            const atom = macho_file.getAtom(atom_index);
             const sym_loc = atom.getSymbolWithLoc();
 
             log.debug("prune(ATOM({d}, %{d}, {?d}))", .{
@@ -439,15 +422,15 @@ fn prune(zld: *Zld, alive: AtomTable) void {
                 sym_loc.sym_index,
                 sym_loc.getFile(),
             });
-            log.debug("  {s} in {s}", .{ zld.getSymbolName(sym_loc), object.name });
+            log.debug("  {s} in {s}", .{ macho_file.getSymbolName(sym_loc), object.name });
 
-            const sym = zld.getSymbolPtr(sym_loc);
+            const sym = macho_file.getSymbolPtr(sym_loc);
             const sect_id = sym.n_sect - 1;
-            var section = zld.sections.get(sect_id);
+            var section = macho_file.sections.get(sect_id);
             section.header.size -= atom.size;
 
             if (atom.prev_index) |prev_index| {
-                const prev = zld.getAtomPtr(prev_index);
+                const prev = macho_file.getAtomPtr(prev_index);
                 prev.next_index = atom.next_index;
             } else {
                 if (atom.next_index) |next_index| {
@@ -455,7 +438,7 @@ fn prune(zld: *Zld, alive: AtomTable) void {
                 }
             }
             if (atom.next_index) |next_index| {
-                const next = zld.getAtomPtr(next_index);
+                const next = macho_file.getAtomPtr(next_index);
                 next.prev_index = atom.prev_index;
             } else {
                 if (atom.prev_index) |prev_index| {
@@ -467,21 +450,37 @@ fn prune(zld: *Zld, alive: AtomTable) void {
                 }
             }
 
-            zld.sections.set(sect_id, section);
+            macho_file.sections.set(sect_id, section);
             _ = object.atoms.swapRemove(i);
 
             sym.n_desc = MachO.N_DEAD;
 
-            var inner_sym_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
             while (inner_sym_it.next()) |inner| {
-                const inner_sym = zld.getSymbolPtr(inner);
+                const inner_sym = macho_file.getSymbolPtr(inner);
                 inner_sym.n_desc = MachO.N_DEAD;
             }
 
-            if (Atom.getSectionAlias(zld, atom_index)) |alias| {
-                const alias_sym = zld.getSymbolPtr(alias);
+            if (Atom.getSectionAlias(macho_file, atom_index)) |alias| {
+                const alias_sym = macho_file.getSymbolPtr(alias);
                 alias_sym.n_desc = MachO.N_DEAD;
             }
         }
     }
 }
+
+const std = @import("std");
+const assert = std.debug.assert;
+const eh_frame = @import("eh_frame.zig");
+const log = std.log.scoped(.dead_strip);
+const macho = std.macho;
+const math = std.math;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Atom = @import("Atom.zig");
+const MachO = @import("../MachO.zig");
+const SymbolWithLoc = MachO.SymbolWithLoc;
+const UnwindInfo = @import("UnwindInfo.zig");
+
+const AtomTable = std.AutoHashMap(Atom.Index, void);
src/link/MachO/DebugSymbols.zig
@@ -1,26 +1,3 @@
-const DebugSymbols = @This();
-
-const std = @import("std");
-const build_options = @import("build_options");
-const assert = std.debug.assert;
-const fs = std.fs;
-const link = @import("../../link.zig");
-const load_commands = @import("load_commands.zig");
-const log = std.log.scoped(.dsym);
-const macho = std.macho;
-const makeStaticString = MachO.makeStaticString;
-const math = std.math;
-const mem = std.mem;
-const padToIdeal = MachO.padToIdeal;
-const trace = @import("../../tracy.zig").trace;
-
-const Allocator = mem.Allocator;
-const Dwarf = @import("../Dwarf.zig");
-const MachO = @import("../MachO.zig");
-const Module = @import("../../Module.zig");
-const StringTable = @import("../strtab.zig").StringTable;
-const Type = @import("../../type.zig").Type;
-
 allocator: Allocator,
 dwarf: Dwarf,
 file: fs.File,
@@ -569,3 +546,26 @@ pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 {
     assert(sect < self.sections.items.len);
     return self.sections.items[sect];
 }
+
+const DebugSymbols = @This();
+
+const std = @import("std");
+const build_options = @import("build_options");
+const assert = std.debug.assert;
+const fs = std.fs;
+const link = @import("../../link.zig");
+const load_commands = @import("load_commands.zig");
+const log = std.log.scoped(.dsym);
+const macho = std.macho;
+const makeStaticString = MachO.makeStaticString;
+const math = std.math;
+const mem = std.mem;
+const padToIdeal = MachO.padToIdeal;
+const trace = @import("../../tracy.zig").trace;
+
+const Allocator = mem.Allocator;
+const Dwarf = @import("../Dwarf.zig");
+const MachO = @import("../MachO.zig");
+const Module = @import("../../Module.zig");
+const StringTable = @import("../strtab.zig").StringTable;
+const Type = @import("../../type.zig").Type;
src/link/MachO/DwarfInfo.zig
@@ -1,17 +1,3 @@
-const DwarfInfo = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const dwarf = std.dwarf;
-const leb = std.leb;
-const log = std.log.scoped(.macho);
-const math = std.math;
-const mem = std.mem;
-
-const Allocator = mem.Allocator;
-pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize });
-pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 });
-
 debug_info: []const u8,
 debug_abbrev: []const u8,
 debug_str: []const u8,
@@ -501,3 +487,17 @@ fn getString(self: DwarfInfo, off: u64) []const u8 {
     assert(off < self.debug_str.len);
     return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0);
 }
+
+const DwarfInfo = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const dwarf = std.dwarf;
+const leb = std.leb;
+const log = std.log.scoped(.macho);
+const math = std.math;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize });
+pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 });
src/link/MachO/Dylib.zig
@@ -1,23 +1,3 @@
-const Dylib = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const fs = std.fs;
-const fmt = std.fmt;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-const fat = @import("fat.zig");
-const tapi = @import("../tapi.zig");
-
-const Allocator = mem.Allocator;
-const CrossTarget = std.zig.CrossTarget;
-const LibStub = tapi.LibStub;
-const LoadCommandIterator = macho.LoadCommandIterator;
-const MachO = @import("../MachO.zig");
-const Tbd = tapi.Tbd;
-
 id: ?Id = null,
 weak: bool = false,
 /// Header is only set if Dylib is parsed directly from a binary and not a stub file.
@@ -546,3 +526,23 @@ pub fn parseFromStub(
         }
     }
 }
+
+const Dylib = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const fs = std.fs;
+const fmt = std.fmt;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const math = std.math;
+const mem = std.mem;
+const fat = @import("fat.zig");
+const tapi = @import("../tapi.zig");
+
+const Allocator = mem.Allocator;
+const CrossTarget = std.zig.CrossTarget;
+const LibStub = tapi.LibStub;
+const LoadCommandIterator = macho.LoadCommandIterator;
+const MachO = @import("../MachO.zig");
+const Tbd = tapi.Tbd;
src/link/MachO/eh_frame.zig
@@ -1,68 +1,52 @@
-const std = @import("std");
-const assert = std.debug.assert;
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-const leb = std.leb;
-const log = std.log.scoped(.eh_frame);
-
-const Allocator = mem.Allocator;
-const Atom = @import("Atom.zig");
-const MachO = @import("../MachO.zig");
-const Relocation = @import("Relocation.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const UnwindInfo = @import("UnwindInfo.zig");
-const Zld = @import("zld.zig").Zld;
-
-pub fn scanRelocs(zld: *Zld) !void {
-    const gpa = zld.gpa;
+pub fn scanRelocs(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
 
-    for (zld.objects.items, 0..) |*object, object_id| {
+    for (macho_file.objects.items, 0..) |*object, object_id| {
         var cies = std.AutoHashMap(u32, void).init(gpa);
         defer cies.deinit();
 
         var it = object.getEhFrameRecordsIterator();
 
         for (object.exec_atoms.items) |atom_index| {
-            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
             while (inner_syms_it.next()) |sym| {
                 const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
                 if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
                 it.seekTo(fde_offset);
                 const fde = (try it.next()).?;
 
-                const cie_ptr = fde.getCiePointerSource(@intCast(object_id), zld, fde_offset);
+                const cie_ptr = fde.getCiePointerSource(@intCast(object_id), macho_file, fde_offset);
                 const cie_offset = fde_offset + 4 - cie_ptr;
 
                 if (!cies.contains(cie_offset)) {
                     try cies.putNoClobber(cie_offset, {});
                     it.seekTo(cie_offset);
                     const cie = (try it.next()).?;
-                    try cie.scanRelocs(zld, @as(u32, @intCast(object_id)), cie_offset);
+                    try cie.scanRelocs(macho_file, @as(u32, @intCast(object_id)), cie_offset);
                 }
             }
         }
     }
 }
 
-pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
-    const sect_id = zld.eh_frame_section_index orelse return;
-    const sect = &zld.sections.items(.header)[sect_id];
+pub fn calcSectionSize(macho_file: *MachO, unwind_info: *const UnwindInfo) !void {
+    const sect_id = macho_file.eh_frame_section_index orelse return;
+    const sect = &macho_file.sections.items(.header)[sect_id];
     sect.@"align" = 3;
     sect.size = 0;
 
-    const cpu_arch = zld.options.target.cpu.arch;
-    const gpa = zld.gpa;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+    const gpa = macho_file.base.allocator;
     var size: u32 = 0;
 
-    for (zld.objects.items, 0..) |*object, object_id| {
+    for (macho_file.objects.items, 0..) |*object, object_id| {
         var cies = std.AutoHashMap(u32, u32).init(gpa);
         defer cies.deinit();
 
         var eh_it = object.getEhFrameRecordsIterator();
 
         for (object.exec_atoms.items) |atom_index| {
-            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
             while (inner_syms_it.next()) |sym| {
                 const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
                 if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
@@ -77,7 +61,7 @@ pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
                 eh_it.seekTo(fde_record_offset);
                 const source_fde_record = (try eh_it.next()).?;
 
-                const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
+                const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
                 const cie_offset = fde_record_offset + 4 - cie_ptr;
 
                 const gop = try cies.getOrPut(cie_offset);
@@ -96,14 +80,14 @@ pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
     }
 }
 
-pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
-    const sect_id = zld.eh_frame_section_index orelse return;
-    const sect = zld.sections.items(.header)[sect_id];
-    const seg_id = zld.sections.items(.segment_index)[sect_id];
-    const seg = zld.segments.items[seg_id];
+pub fn write(macho_file: *MachO, unwind_info: *UnwindInfo) !void {
+    const sect_id = macho_file.eh_frame_section_index orelse return;
+    const sect = macho_file.sections.items(.header)[sect_id];
+    const seg_id = macho_file.sections.items(.segment_index)[sect_id];
+    const seg = macho_file.segments.items[seg_id];
 
-    const cpu_arch = zld.options.target.cpu.arch;
-    const gpa = zld.gpa;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+    const gpa = macho_file.base.allocator;
 
     var eh_records = std.AutoArrayHashMap(u32, EhFrameRecord(true)).init(gpa);
     defer {
@@ -115,7 +99,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
 
     var eh_frame_offset: u32 = 0;
 
-    for (zld.objects.items, 0..) |*object, object_id| {
+    for (macho_file.objects.items, 0..) |*object, object_id| {
         try eh_records.ensureUnusedCapacity(2 * @as(u32, @intCast(object.exec_atoms.items.len)));
 
         var cies = std.AutoHashMap(u32, u32).init(gpa);
@@ -124,7 +108,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
         var eh_it = object.getEhFrameRecordsIterator();
 
         for (object.exec_atoms.items) |atom_index| {
-            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
             while (inner_syms_it.next()) |target| {
                 const fde_record_offset = object.eh_frame_records_lookup.get(target) orelse continue;
                 if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
@@ -139,7 +123,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
                 eh_it.seekTo(fde_record_offset);
                 const source_fde_record = (try eh_it.next()).?;
 
-                const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
+                const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
                 const cie_offset = fde_record_offset + 4 - cie_ptr;
 
                 const gop = try cies.getOrPut(cie_offset);
@@ -147,7 +131,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
                     eh_it.seekTo(cie_offset);
                     const source_cie_record = (try eh_it.next()).?;
                     var cie_record = try source_cie_record.toOwned(gpa);
-                    try cie_record.relocate(zld, @as(u32, @intCast(object_id)), .{
+                    try cie_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
                         .source_offset = cie_offset,
                         .out_offset = eh_frame_offset,
                         .sect_addr = sect.addr,
@@ -158,7 +142,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
                 }
 
                 var fde_record = try source_fde_record.toOwned(gpa);
-                try fde_record.relocate(zld, @as(u32, @intCast(object_id)), .{
+                try fde_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
                     .source_offset = fde_record_offset,
                     .out_offset = eh_frame_offset,
                     .sect_addr = sect.addr,
@@ -169,7 +153,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
                     .aarch64 => {}, // relocs take care of LSDA pointers
                     .x86_64 => {
                         // We need to relocate target symbol address ourselves.
-                        const atom_sym = zld.getSymbol(target);
+                        const atom_sym = macho_file.getSymbol(target);
                         try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
                             .base_addr = sect.addr,
                             .base_offset = eh_frame_offset,
@@ -229,7 +213,7 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
         try buffer.appendSlice(record.data);
     }
 
-    try zld.file.pwriteAll(buffer.items, sect.offset);
+    try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
 }
 const EhFrameRecordTag = enum { cie, fde };
 
@@ -261,12 +245,12 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
 
         pub fn scanRelocs(
             rec: Record,
-            zld: *Zld,
+            macho_file: *MachO,
             object_id: u32,
             source_offset: u32,
         ) !void {
-            if (rec.getPersonalityPointerReloc(zld, object_id, source_offset)) |target| {
-                try zld.addGotEntry(target);
+            if (rec.getPersonalityPointerReloc(macho_file, object_id, source_offset)) |target| {
+                try macho_file.addGotEntry(target);
             }
         }
 
@@ -290,12 +274,12 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
 
         pub fn getPersonalityPointerReloc(
             rec: Record,
-            zld: *Zld,
+            macho_file: *MachO,
             object_id: u32,
             source_offset: u32,
         ) ?SymbolWithLoc {
-            const cpu_arch = zld.options.target.cpu.arch;
-            const relocs = getRelocs(zld, object_id, source_offset);
+            const cpu_arch = macho_file.base.options.target.cpu.arch;
+            const relocs = getRelocs(macho_file, object_id, source_offset);
             for (relocs) |rel| {
                 switch (cpu_arch) {
                     .aarch64 => {
@@ -317,7 +301,7 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
                     },
                     else => unreachable,
                 }
-                const target = Atom.parseRelocTarget(zld, .{
+                const target = Atom.parseRelocTarget(macho_file, .{
                     .object_id = object_id,
                     .rel = rel,
                     .code = rec.data,
@@ -328,18 +312,18 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
             return null;
         }
 
-        pub fn relocate(rec: *Record, zld: *Zld, object_id: u32, ctx: struct {
+        pub fn relocate(rec: *Record, macho_file: *MachO, object_id: u32, ctx: struct {
             source_offset: u32,
             out_offset: u32,
             sect_addr: u64,
         }) !void {
             comptime assert(is_mutable);
 
-            const cpu_arch = zld.options.target.cpu.arch;
-            const relocs = getRelocs(zld, object_id, ctx.source_offset);
+            const cpu_arch = macho_file.base.options.target.cpu.arch;
+            const relocs = getRelocs(macho_file, object_id, ctx.source_offset);
 
             for (relocs) |rel| {
-                const target = Atom.parseRelocTarget(zld, .{
+                const target = Atom.parseRelocTarget(macho_file, .{
                     .object_id = object_id,
                     .rel = rel,
                     .code = rec.data,
@@ -356,14 +340,14 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
                                 // Address of the __eh_frame in the source object file
                             },
                             .ARM64_RELOC_POINTER_TO_GOT => {
-                                const target_addr = zld.getGotEntryAddress(target).?;
+                                const target_addr = macho_file.getGotEntryAddress(target).?;
                                 const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse
                                     return error.Overflow;
                                 mem.writeIntLittle(i32, rec.data[rel_offset..][0..4], result);
                             },
                             .ARM64_RELOC_UNSIGNED => {
                                 assert(rel.r_extern == 1);
-                                const target_addr = try Atom.getRelocTargetAddress(zld, target, false);
+                                const target_addr = try Atom.getRelocTargetAddress(macho_file, target, false);
                                 const result = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));
                                 mem.writeIntLittle(i64, rec.data[rel_offset..][0..8], @as(i64, @intCast(result)));
                             },
@@ -374,7 +358,7 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
                         const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
                         switch (rel_type) {
                             .X86_64_RELOC_GOT => {
-                                const target_addr = zld.getGotEntryAddress(target).?;
+                                const target_addr = macho_file.getGotEntryAddress(target).?;
                                 const addend = mem.readIntLittle(i32, rec.data[rel_offset..][0..4]);
                                 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
                                 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
@@ -388,20 +372,20 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
             }
         }
 
-        pub fn getCiePointerSource(rec: Record, object_id: u32, zld: *Zld, offset: u32) u32 {
+        pub fn getCiePointerSource(rec: Record, object_id: u32, macho_file: *MachO, offset: u32) u32 {
             assert(rec.tag == .fde);
-            const cpu_arch = zld.options.target.cpu.arch;
+            const cpu_arch = macho_file.base.options.target.cpu.arch;
             const addend = mem.readIntLittle(u32, rec.data[0..4]);
             switch (cpu_arch) {
                 .aarch64 => {
-                    const relocs = getRelocs(zld, object_id, offset);
+                    const relocs = getRelocs(macho_file, object_id, offset);
                     const maybe_rel = for (relocs) |rel| {
                         if (rel.r_address - @as(i32, @intCast(offset)) == 4 and
                             @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_SUBTRACTOR)
                             break rel;
                     } else null;
                     const rel = maybe_rel orelse return addend;
-                    const object = &zld.objects.items[object_id];
+                    const object = &macho_file.objects.items[object_id];
                     const target_addr = object.in_symtab.?[rel.r_symbolnum].n_value;
                     const sect = object.getSourceSection(object.eh_frame_sect_id.?);
                     return @intCast(sect.addr + offset - target_addr + addend);
@@ -583,8 +567,8 @@ pub fn EhFrameRecord(comptime is_mutable: bool) type {
     };
 }
 
-pub fn getRelocs(zld: *Zld, object_id: u32, source_offset: u32) []const macho.relocation_info {
-    const object = &zld.objects.items[object_id];
+pub fn getRelocs(macho_file: *MachO, object_id: u32, source_offset: u32) []const macho.relocation_info {
+    const object = &macho_file.objects.items[object_id];
     assert(object.hasEhFrameRecords());
     const urel = object.eh_frame_relocs_lookup.get(source_offset) orelse
         return &[0]macho.relocation_info{};
@@ -650,3 +634,18 @@ pub const EH_PE = struct {
     pub const indirect = 0x80;
     pub const omit = 0xFF;
 };
+
+const std = @import("std");
+const assert = std.debug.assert;
+const macho = std.macho;
+const math = std.math;
+const mem = std.mem;
+const leb = std.leb;
+const log = std.log.scoped(.eh_frame);
+
+const Allocator = mem.Allocator;
+const Atom = @import("Atom.zig");
+const MachO = @import("../MachO.zig");
+const Relocation = @import("Relocation.zig");
+const SymbolWithLoc = MachO.SymbolWithLoc;
+const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/fat.zig
@@ -1,9 +1,3 @@
-const std = @import("std");
-const assert = std.debug.assert;
-const log = std.log.scoped(.archive);
-const macho = std.macho;
-const mem = std.mem;
-
 pub fn isFatLibrary(file: std.fs.File) bool {
     const reader = file.reader();
     const hdr = reader.readStructBig(macho.fat_header) catch return false;
@@ -38,3 +32,9 @@ pub fn parseArchs(file: std.fs.File, buffer: *[2]Arch) ![]const Arch {
 
     return buffer[0..count];
 }
+
+const std = @import("std");
+const assert = std.debug.assert;
+const log = std.log.scoped(.archive);
+const macho = std.macho;
+const mem = std.mem;
src/link/MachO/hasher.zig
@@ -1,12 +1,3 @@
-const std = @import("std");
-const assert = std.debug.assert;
-const fs = std.fs;
-const mem = std.mem;
-
-const Allocator = mem.Allocator;
-const ThreadPool = std.Thread.Pool;
-const WaitGroup = std.Thread.WaitGroup;
-
 pub fn ParallelHasher(comptime Hasher: type) type {
     const hash_size = Hasher.digest_length;
 
@@ -69,3 +60,12 @@ pub fn ParallelHasher(comptime Hasher: type) type {
         const Self = @This();
     };
 }
+
+const std = @import("std");
+const assert = std.debug.assert;
+const fs = std.fs;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const ThreadPool = std.Thread.Pool;
+const WaitGroup = std.Thread.WaitGroup;
src/link/MachO/load_commands.zig
@@ -1,13 +1,3 @@
-const std = @import("std");
-const assert = std.debug.assert;
-const link = @import("../../link.zig");
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const mem = std.mem;
-
-const Allocator = mem.Allocator;
-const Dylib = @import("Dylib.zig");
-
 /// Default implicit entrypoint symbol name.
 pub const default_entry_point: []const u8 = "_main";
 
@@ -374,3 +364,13 @@ test "parseSdkVersion" {
 
     try expect(parseSdkVersion("11") == null);
 }
+
+const std = @import("std");
+const assert = std.debug.assert;
+const link = @import("../../link.zig");
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Dylib = @import("Dylib.zig");
src/link/MachO/Object.zig
@@ -2,31 +2,6 @@
 //! Each Object is fully loaded into memory for easier
 //! access into different data within.
 
-const Object = @This();
-
-const std = @import("std");
-const build_options = @import("build_options");
-const assert = std.debug.assert;
-const dwarf = std.dwarf;
-const eh_frame = @import("eh_frame.zig");
-const fs = std.fs;
-const io = std.io;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-const sort = std.sort;
-const trace = @import("../../tracy.zig").trace;
-
-const Allocator = mem.Allocator;
-const Atom = @import("Atom.zig");
-const DwarfInfo = @import("DwarfInfo.zig");
-const LoadCommandIterator = macho.LoadCommandIterator;
-const MachO = @import("../MachO.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const UnwindInfo = @import("UnwindInfo.zig");
-const Zld = @import("zld.zig").Zld;
-
 name: []const u8,
 mtime: u64,
 contents: []align(@alignOf(u64)) const u8,
@@ -359,25 +334,25 @@ fn sectionLessThanByAddress(ctx: void, lhs: SortedSection, rhs: SortedSection) b
     return lhs.header.addr < rhs.header.addr;
 }
 
-pub fn splitIntoAtoms(self: *Object, zld: *Zld, object_id: u32) !void {
+pub fn splitIntoAtoms(self: *Object, macho_file: *MachO, object_id: u32) !void {
     log.debug("splitting object({d}, {s}) into atoms", .{ object_id, self.name });
 
-    try self.splitRegularSections(zld, object_id);
-    try self.parseEhFrameSection(zld, object_id);
-    try self.parseUnwindInfo(zld, object_id);
-    try self.parseDataInCode(zld.gpa);
+    try self.splitRegularSections(macho_file, object_id);
+    try self.parseEhFrameSection(macho_file, object_id);
+    try self.parseUnwindInfo(macho_file, object_id);
+    try self.parseDataInCode(macho_file.base.allocator);
 }
 
 /// Splits input regular sections into Atoms.
 /// If the Object was compiled with `MH_SUBSECTIONS_VIA_SYMBOLS`, splits section
 /// into subsections where each subsection then represents an Atom.
-pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
-    const gpa = zld.gpa;
+pub fn splitRegularSections(self: *Object, macho_file: *MachO, object_id: u32) !void {
+    const gpa = macho_file.base.allocator;
 
     const sections = self.getSourceSections();
     for (sections, 0..) |sect, id| {
         if (sect.isDebug()) continue;
-        const out_sect_id = (try Atom.getOutputSection(zld, sect)) orelse {
+        const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse {
             log.debug("  unhandled section '{s},{s}'", .{ sect.segName(), sect.sectName() });
             continue;
         };
@@ -397,13 +372,13 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
     if (self.in_symtab == null) {
         for (sections, 0..) |sect, id| {
             if (sect.isDebug()) continue;
-            const out_sect_id = (try Atom.getOutputSection(zld, sect)) orelse continue;
+            const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
             if (sect.size == 0) continue;
 
             const sect_id = @as(u8, @intCast(id));
             const sym_index = self.getSectionAliasSymbolIndex(sect_id);
             const atom_index = try self.createAtomFromSubsection(
-                zld,
+                macho_file,
                 object_id,
                 sym_index,
                 sym_index,
@@ -412,7 +387,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                 sect.@"align",
                 out_sect_id,
             );
-            zld.addAtomToSection(atom_index);
+            macho_file.addAtomToSection(atom_index);
         }
         return;
     }
@@ -456,17 +431,17 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
         log.debug("splitting section '{s},{s}' into atoms", .{ sect.segName(), sect.sectName() });
 
         // Get output segment/section in the final artifact.
-        const out_sect_id = (try Atom.getOutputSection(zld, sect)) orelse continue;
+        const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
 
         log.debug("  output sect({d}, '{s},{s}')", .{
             out_sect_id + 1,
-            zld.sections.items(.header)[out_sect_id].segName(),
-            zld.sections.items(.header)[out_sect_id].sectName(),
+            macho_file.sections.items(.header)[out_sect_id].segName(),
+            macho_file.sections.items(.header)[out_sect_id].sectName(),
         });
 
         try self.parseRelocs(gpa, section.id);
 
-        const cpu_arch = zld.options.target.cpu.arch;
+        const cpu_arch = macho_file.base.options.target.cpu.arch;
         const sect_loc = filterSymbolsBySection(symtab[sect_sym_index..], sect_id + 1);
         const sect_start_index = sect_sym_index + sect_loc.index;
 
@@ -482,7 +457,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                 const sym_index = self.getSectionAliasSymbolIndex(sect_id);
                 const atom_size = first_sym.n_value - sect.addr;
                 const atom_index = try self.createAtomFromSubsection(
-                    zld,
+                    macho_file,
                     object_id,
                     sym_index,
                     sym_index,
@@ -492,9 +467,9 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                     out_sect_id,
                 );
                 if (!sect.isZerofill()) {
-                    try self.cacheRelocs(zld, atom_index);
+                    try self.cacheRelocs(macho_file, atom_index);
                 }
-                zld.addAtomToSection(atom_index);
+                macho_file.addAtomToSection(atom_index);
             }
 
             var next_sym_index = sect_start_index;
@@ -518,7 +493,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                     sect.@"align";
 
                 const atom_index = try self.createAtomFromSubsection(
-                    zld,
+                    macho_file,
                     object_id,
                     atom_sym_index,
                     atom_sym_index,
@@ -537,14 +512,14 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                     self.atom_by_index_table[alias_index] = atom_index;
                 }
                 if (!sect.isZerofill()) {
-                    try self.cacheRelocs(zld, atom_index);
+                    try self.cacheRelocs(macho_file, atom_index);
                 }
-                zld.addAtomToSection(atom_index);
+                macho_file.addAtomToSection(atom_index);
             }
         } else {
             const alias_index = self.getSectionAliasSymbolIndex(sect_id);
             const atom_index = try self.createAtomFromSubsection(
-                zld,
+                macho_file,
                 object_id,
                 alias_index,
                 sect_start_index,
@@ -554,16 +529,16 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
                 out_sect_id,
             );
             if (!sect.isZerofill()) {
-                try self.cacheRelocs(zld, atom_index);
+                try self.cacheRelocs(macho_file, atom_index);
             }
-            zld.addAtomToSection(atom_index);
+            macho_file.addAtomToSection(atom_index);
         }
     }
 }
 
 fn createAtomFromSubsection(
     self: *Object,
-    zld: *Zld,
+    macho_file: *MachO,
     object_id: u32,
     sym_index: u32,
     inner_sym_index: u32,
@@ -572,9 +547,9 @@ fn createAtomFromSubsection(
     alignment: u32,
     out_sect_id: u8,
 ) !Atom.Index {
-    const gpa = zld.gpa;
-    const atom_index = try zld.createAtom(sym_index, .{ .size = size, .alignment = alignment });
-    const atom = zld.getAtomPtr(atom_index);
+    const gpa = macho_file.base.allocator;
+    const atom_index = try macho_file.createAtom(sym_index, .{ .size = size, .alignment = alignment });
+    const atom = macho_file.getAtomPtr(atom_index);
     atom.inner_sym_index = inner_sym_index;
     atom.inner_nsyms_trailing = inner_nsyms_trailing;
     atom.file = object_id + 1;
@@ -584,22 +559,22 @@ fn createAtomFromSubsection(
         sym_index,
         self.getSymbolName(sym_index),
         out_sect_id + 1,
-        zld.sections.items(.header)[out_sect_id].segName(),
-        zld.sections.items(.header)[out_sect_id].sectName(),
+        macho_file.sections.items(.header)[out_sect_id].segName(),
+        macho_file.sections.items(.header)[out_sect_id].sectName(),
         object_id,
     });
 
     try self.atoms.append(gpa, atom_index);
     self.atom_by_index_table[sym_index] = atom_index;
 
-    var it = Atom.getInnerSymbolsIterator(zld, atom_index);
+    var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
     while (it.next()) |sym_loc| {
-        const inner = zld.getSymbolPtr(sym_loc);
+        const inner = macho_file.getSymbolPtr(sym_loc);
         inner.n_sect = out_sect_id + 1;
         self.atom_by_index_table[sym_loc.sym_index] = atom_index;
     }
 
-    const out_sect = zld.sections.items(.header)[out_sect_id];
+    const out_sect = macho_file.sections.items(.header)[out_sect_id];
     if (out_sect.isCode() and
         mem.eql(u8, "__TEXT", out_sect.segName()) and
         mem.eql(u8, "__text", out_sect.sectName()))
@@ -651,8 +626,8 @@ fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void {
     self.section_relocs_lookup.items[sect_id] = start;
 }
 
-fn cacheRelocs(self: *Object, zld: *Zld, atom_index: Atom.Index) !void {
-    const atom = zld.getAtom(atom_index);
+fn cacheRelocs(self: *Object, macho_file: *MachO, atom_index: Atom.Index) !void {
+    const atom = macho_file.getAtom(atom_index);
 
     const source_sect_id = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
         break :blk source_sym.n_sect - 1;
@@ -679,19 +654,19 @@ fn relocGreaterThan(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation
     return lhs.r_address > rhs.r_address;
 }
 
-fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
+fn parseEhFrameSection(self: *Object, macho_file: *MachO, object_id: u32) !void {
     const sect_id = self.eh_frame_sect_id orelse return;
     const sect = self.getSourceSection(sect_id);
 
     log.debug("parsing __TEXT,__eh_frame section", .{});
 
-    const gpa = zld.gpa;
+    const gpa = macho_file.base.allocator;
 
-    if (zld.eh_frame_section_index == null) {
-        zld.eh_frame_section_index = try MachO.initSection(gpa, zld, "__TEXT", "__eh_frame", .{});
+    if (macho_file.eh_frame_section_index == null) {
+        macho_file.eh_frame_section_index = try macho_file.initSection("__TEXT", "__eh_frame", .{});
     }
 
-    const cpu_arch = zld.options.target.cpu.arch;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
     try self.parseRelocs(gpa, sect_id);
     const relocs = self.getRelocs(sect_id);
 
@@ -729,7 +704,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
                                 @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_UNSIGNED)
                                 break rel;
                         } else unreachable;
-                        const target = Atom.parseRelocTarget(zld, .{
+                        const target = Atom.parseRelocTarget(macho_file, .{
                             .object_id = object_id,
                             .rel = rel,
                             .code = it.data[offset..],
@@ -744,7 +719,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
                         });
                         const target_sym_index = self.getSymbolByAddress(target_address, null);
                         const target = if (self.getGlobal(target_sym_index)) |global_index|
-                            zld.globals.items[global_index]
+                            macho_file.globals.items[global_index]
                         else
                             SymbolWithLoc{ .sym_index = target_sym_index, .file = object_id + 1 };
                         break :blk target;
@@ -770,7 +745,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
                     };
                     log.debug("FDE at offset {x} tracks {s}", .{
                         offset,
-                        zld.getSymbolName(actual_target),
+                        macho_file.getSymbolName(actual_target),
                     });
                     try self.eh_frame_records_lookup.putNoClobber(gpa, actual_target, offset);
                 }
@@ -779,19 +754,17 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
     }
 }
 
-fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
-    const gpa = zld.gpa;
-    const cpu_arch = zld.options.target.cpu.arch;
+fn parseUnwindInfo(self: *Object, macho_file: *MachO, object_id: u32) !void {
+    const gpa = macho_file.base.allocator;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
     const sect_id = self.unwind_info_sect_id orelse {
         // If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`,
         // we will try fully synthesising unwind info records to somewhat match Apple ld's
         // approach. However, we will only synthesise DWARF records and nothing more. For this reason,
         // we still create the output `__TEXT,__unwind_info` section.
         if (self.hasEhFrameRecords()) {
-            if (zld.unwind_info_section_index == null) {
-                zld.unwind_info_section_index = try MachO.initSection(
-                    gpa,
-                    zld,
+            if (macho_file.unwind_info_section_index == null) {
+                macho_file.unwind_info_section_index = try macho_file.initSection(
                     "__TEXT",
                     "__unwind_info",
                     .{},
@@ -803,8 +776,8 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
 
     log.debug("parsing unwind info in {s}", .{self.name});
 
-    if (zld.unwind_info_section_index == null) {
-        zld.unwind_info_section_index = try MachO.initSection(gpa, zld, "__TEXT", "__unwind_info", .{});
+    if (macho_file.unwind_info_section_index == null) {
+        macho_file.unwind_info_section_index = try macho_file.initSection("__TEXT", "__unwind_info", .{});
     }
 
     const unwind_records = self.getUnwindRecords();
@@ -839,7 +812,7 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
 
         // Find function symbol that this record describes
         const rel = relocs[rel_pos.start..][rel_pos.len - 1];
-        const target = Atom.parseRelocTarget(zld, .{
+        const target = Atom.parseRelocTarget(macho_file, .{
             .object_id = object_id,
             .rel = rel,
             .code = mem.asBytes(&record),
@@ -863,7 +836,7 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
                 };
                 log.debug("unwind record {d} tracks {s}", .{
                     record_id,
-                    zld.getSymbolName(actual_target),
+                    macho_file.getSymbolName(actual_target),
                 });
                 try self.unwind_records_lookup.putNoClobber(gpa, actual_target, @intCast(record_id));
             }
@@ -1094,3 +1067,27 @@ pub fn getEhFrameRecordsIterator(self: Object) eh_frame.Iterator {
 pub fn hasDataInCode(self: Object) bool {
     return self.data_in_code.items.len > 0;
 }
+
+const Object = @This();
+
+const std = @import("std");
+const build_options = @import("build_options");
+const assert = std.debug.assert;
+const dwarf = std.dwarf;
+const eh_frame = @import("eh_frame.zig");
+const fs = std.fs;
+const io = std.io;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const math = std.math;
+const mem = std.mem;
+const sort = std.sort;
+const trace = @import("../../tracy.zig").trace;
+
+const Allocator = mem.Allocator;
+const Atom = @import("Atom.zig");
+const DwarfInfo = @import("DwarfInfo.zig");
+const LoadCommandIterator = macho.LoadCommandIterator;
+const MachO = @import("../MachO.zig");
+const SymbolWithLoc = MachO.SymbolWithLoc;
+const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/stubs.zig
@@ -1,8 +1,3 @@
-const std = @import("std");
-const aarch64 = @import("../../arch/aarch64/bits.zig");
-
-const Relocation = @import("Relocation.zig");
-
 pub inline fn stubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u8 {
     return switch (cpu_arch) {
         .x86_64 => 15,
@@ -167,3 +162,8 @@ pub fn writeStubCode(args: struct {
         else => unreachable,
     }
 }
+
+const std = @import("std");
+const aarch64 = @import("../../arch/aarch64/bits.zig");
+
+const Relocation = @import("Relocation.zig");
src/link/MachO/thunks.zig
@@ -5,22 +5,6 @@
 //! The algorithm works pessimistically and assumes that any reference to an Atom in
 //! another output section is out of range.
 
-const std = @import("std");
-const assert = std.debug.assert;
-const log = std.log.scoped(.thunks);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-
-const aarch64 = @import("../../arch/aarch64/bits.zig");
-
-const Allocator = mem.Allocator;
-const Atom = @import("Atom.zig");
-const MachO = @import("../MachO.zig");
-const Relocation = @import("Relocation.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const Zld = @import("zld.zig").Zld;
-
 /// Branch instruction has 26 bits immediate but 4 byte aligned.
 const jump_bits = @bitSizeOf(i28);
 
@@ -74,18 +58,18 @@ pub const Thunk = struct {
         return @alignOf(u32);
     }
 
-    pub fn getTrampoline(self: Thunk, zld: *Zld, tag: Tag, target: SymbolWithLoc) ?SymbolWithLoc {
+    pub fn getTrampoline(self: Thunk, macho_file: *MachO, tag: Tag, target: SymbolWithLoc) ?SymbolWithLoc {
         const atom_index = self.lookup.get(.{ .tag = tag, .target = target }) orelse return null;
-        return zld.getAtom(atom_index).getSymbolWithLoc();
+        return macho_file.getAtom(atom_index).getSymbolWithLoc();
     }
 };
 
-pub fn createThunks(zld: *Zld, sect_id: u8) !void {
-    const header = &zld.sections.items(.header)[sect_id];
+pub fn createThunks(macho_file: *MachO, sect_id: u8) !void {
+    const header = &macho_file.sections.items(.header)[sect_id];
     if (header.size == 0) return;
 
-    const gpa = zld.gpa;
-    const first_atom_index = zld.sections.items(.first_atom_index)[sect_id].?;
+    const gpa = macho_file.base.allocator;
+    const first_atom_index = macho_file.sections.items(.first_atom_index)[sect_id].?;
 
     header.size = 0;
     header.@"align" = 0;
@@ -95,8 +79,8 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
     {
         var atom_index = first_atom_index;
         while (true) {
-            const atom = zld.getAtom(atom_index);
-            const sym = zld.getSymbolPtr(atom.getSymbolWithLoc());
+            const atom = macho_file.getAtom(atom_index);
+            const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
             sym.n_value = 0;
             atom_count += 1;
 
@@ -115,24 +99,24 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
     var offset: u64 = 0;
 
     while (true) {
-        const group_start_atom = zld.getAtom(group_start);
+        const group_start_atom = macho_file.getAtom(group_start);
         log.debug("GROUP START at {d}", .{group_start});
 
         while (true) {
-            const atom = zld.getAtom(group_end);
+            const atom = macho_file.getAtom(group_end);
             offset = mem.alignForward(u64, offset, try math.powi(u32, 2, atom.alignment));
 
-            const sym = zld.getSymbolPtr(atom.getSymbolWithLoc());
+            const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
             sym.n_value = offset;
             offset += atom.size;
 
-            zld.logAtom(group_end, log);
+            macho_file.logAtom(group_end, log);
 
             header.@"align" = @max(header.@"align", atom.alignment);
 
             allocated.putAssumeCapacityNoClobber(group_end, {});
 
-            const group_start_sym = zld.getSymbol(group_start_atom.getSymbolWithLoc());
+            const group_start_sym = macho_file.getSymbol(group_start_atom.getSymbolWithLoc());
             if (offset - group_start_sym.n_value >= max_allowed_distance) break;
 
             if (atom.next_index) |next_index| {
@@ -142,15 +126,15 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
         log.debug("GROUP END at {d}", .{group_end});
 
         // Insert thunk at group_end
-        const thunk_index = @as(u32, @intCast(zld.thunks.items.len));
-        try zld.thunks.append(gpa, .{ .start_index = undefined, .len = 0 });
+        const thunk_index = @as(u32, @intCast(macho_file.thunks.items.len));
+        try macho_file.thunks.append(gpa, .{ .start_index = undefined, .len = 0 });
 
         // Scan relocs in the group and create trampolines for any unreachable callsite.
         var atom_index = group_start;
         while (true) {
-            const atom = zld.getAtom(atom_index);
+            const atom = macho_file.getAtom(atom_index);
             try scanRelocs(
-                zld,
+                macho_file,
                 atom_index,
                 allocated,
                 thunk_index,
@@ -165,19 +149,19 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
         }
 
         offset = mem.alignForward(u64, offset, Thunk.getAlignment());
-        allocateThunk(zld, thunk_index, offset, header);
-        offset += zld.thunks.items[thunk_index].getSize();
+        allocateThunk(macho_file, thunk_index, offset, header);
+        offset += macho_file.thunks.items[thunk_index].getSize();
 
-        const thunk = zld.thunks.items[thunk_index];
+        const thunk = macho_file.thunks.items[thunk_index];
         if (thunk.len == 0) {
-            const group_end_atom = zld.getAtom(group_end);
+            const group_end_atom = macho_file.getAtom(group_end);
             if (group_end_atom.next_index) |next_index| {
                 group_start = next_index;
                 group_end = next_index;
             } else break;
         } else {
             const thunk_end_atom_index = thunk.getEndAtomIndex();
-            const thunk_end_atom = zld.getAtom(thunk_end_atom_index);
+            const thunk_end_atom = macho_file.getAtom(thunk_end_atom_index);
             if (thunk_end_atom.next_index) |next_index| {
                 group_start = next_index;
                 group_end = next_index;
@@ -189,12 +173,12 @@ pub fn createThunks(zld: *Zld, sect_id: u8) !void {
 }
 
 fn allocateThunk(
-    zld: *Zld,
+    macho_file: *MachO,
     thunk_index: Thunk.Index,
     base_offset: u64,
     header: *macho.section_64,
 ) void {
-    const thunk = zld.thunks.items[thunk_index];
+    const thunk = macho_file.thunks.items[thunk_index];
     if (thunk.len == 0) return;
 
     const first_atom_index = thunk.getStartAtomIndex();
@@ -203,14 +187,14 @@ fn allocateThunk(
     var atom_index = first_atom_index;
     var offset = base_offset;
     while (true) {
-        const atom = zld.getAtom(atom_index);
+        const atom = macho_file.getAtom(atom_index);
         offset = mem.alignForward(u64, offset, Thunk.getAlignment());
 
-        const sym = zld.getSymbolPtr(atom.getSymbolWithLoc());
+        const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
         sym.n_value = offset;
         offset += atom.size;
 
-        zld.logAtom(atom_index, log);
+        macho_file.logAtom(atom_index, log);
 
         header.@"align" = @max(header.@"align", atom.alignment);
 
@@ -223,69 +207,69 @@ fn allocateThunk(
 }
 
 fn scanRelocs(
-    zld: *Zld,
+    macho_file: *MachO,
     atom_index: Atom.Index,
     allocated: std.AutoHashMap(Atom.Index, void),
     thunk_index: Thunk.Index,
     group_end: Atom.Index,
 ) !void {
-    const atom = zld.getAtom(atom_index);
-    const object = zld.objects.items[atom.getFile().?];
+    const atom = macho_file.getAtom(atom_index);
+    const object = macho_file.objects.items[atom.getFile().?];
 
     const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
         const source_sect = object.getSourceSection(source_sym.n_sect - 1);
         break :blk @as(i32, @intCast(source_sym.n_value - source_sect.addr));
     } else 0;
 
-    const code = Atom.getAtomCode(zld, atom_index);
-    const relocs = Atom.getAtomRelocs(zld, atom_index);
-    const ctx = Atom.getRelocContext(zld, atom_index);
+    const code = Atom.getAtomCode(macho_file, atom_index);
+    const relocs = Atom.getAtomRelocs(macho_file, atom_index);
+    const ctx = Atom.getRelocContext(macho_file, atom_index);
 
     for (relocs) |rel| {
         if (!relocNeedsThunk(rel)) continue;
 
-        const target = Atom.parseRelocTarget(zld, .{
+        const target = Atom.parseRelocTarget(macho_file, .{
             .object_id = atom.getFile().?,
             .rel = rel,
             .code = code,
             .base_offset = ctx.base_offset,
             .base_addr = ctx.base_addr,
         });
-        if (isReachable(zld, atom_index, rel, base_offset, target, allocated)) continue;
+        if (isReachable(macho_file, atom_index, rel, base_offset, target, allocated)) continue;
 
         log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{
             rel.r_address - base_offset,
-            zld.getSymbolName(atom.getSymbolWithLoc()),
-            zld.getSymbol(atom.getSymbolWithLoc()).n_value,
-            zld.getSymbolName(target),
-            zld.getSymbol(target).n_value,
+            macho_file.getSymbolName(atom.getSymbolWithLoc()),
+            macho_file.getSymbol(atom.getSymbolWithLoc()).n_value,
+            macho_file.getSymbolName(target),
+            macho_file.getSymbol(target).n_value,
         });
 
-        const gpa = zld.gpa;
-        const target_sym = zld.getSymbol(target);
-        const thunk = &zld.thunks.items[thunk_index];
+        const gpa = macho_file.base.allocator;
+        const target_sym = macho_file.getSymbol(target);
+        const thunk = &macho_file.thunks.items[thunk_index];
 
         const tag: Thunk.Tag = if (target_sym.undf()) .stub else .atom;
         const thunk_target: Thunk.Target = .{ .tag = tag, .target = target };
         const gop = try thunk.lookup.getOrPut(gpa, thunk_target);
         if (!gop.found_existing) {
-            gop.value_ptr.* = try pushThunkAtom(zld, thunk, group_end);
+            gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end);
             try thunk.targets.append(gpa, thunk_target);
         }
 
-        try zld.thunk_table.put(gpa, atom_index, thunk_index);
+        try macho_file.thunk_table.put(gpa, atom_index, thunk_index);
     }
 }
 
-fn pushThunkAtom(zld: *Zld, thunk: *Thunk, group_end: Atom.Index) !Atom.Index {
-    const thunk_atom_index = try createThunkAtom(zld);
+fn pushThunkAtom(macho_file: *MachO, thunk: *Thunk, group_end: Atom.Index) !Atom.Index {
+    const thunk_atom_index = try createThunkAtom(macho_file);
 
-    const thunk_atom = zld.getAtomPtr(thunk_atom_index);
+    const thunk_atom = macho_file.getAtomPtr(thunk_atom_index);
     const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex();
-    const end_atom = zld.getAtomPtr(end_atom_index);
+    const end_atom = macho_file.getAtomPtr(end_atom_index);
 
     if (end_atom.next_index) |first_after_index| {
-        const first_after_atom = zld.getAtomPtr(first_after_index);
+        const first_after_atom = macho_file.getAtomPtr(first_after_index);
         first_after_atom.prev_index = thunk_atom_index;
         thunk_atom.next_index = first_after_index;
     }
@@ -308,58 +292,58 @@ inline fn relocNeedsThunk(rel: macho.relocation_info) bool {
 }
 
 fn isReachable(
-    zld: *Zld,
+    macho_file: *MachO,
     atom_index: Atom.Index,
     rel: macho.relocation_info,
     base_offset: i32,
     target: SymbolWithLoc,
     allocated: std.AutoHashMap(Atom.Index, void),
 ) bool {
-    if (zld.stubs_table.lookup.contains(target)) return false;
+    if (macho_file.stub_table.lookup.contains(target)) return false;
 
-    const source_atom = zld.getAtom(atom_index);
-    const source_sym = zld.getSymbol(source_atom.getSymbolWithLoc());
+    const source_atom = macho_file.getAtom(atom_index);
+    const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc());
 
-    const target_object = zld.objects.items[target.getFile().?];
+    const target_object = macho_file.objects.items[target.getFile().?];
     const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
-    const target_atom = zld.getAtom(target_atom_index);
-    const target_sym = zld.getSymbol(target_atom.getSymbolWithLoc());
+    const target_atom = macho_file.getAtom(target_atom_index);
+    const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
 
     if (source_sym.n_sect != target_sym.n_sect) return false;
 
     if (!allocated.contains(target_atom_index)) return false;
 
     const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset));
-    const target_addr = if (Atom.relocRequiresGot(zld, rel))
-        zld.getGotEntryAddress(target).?
+    const target_addr = if (Atom.relocRequiresGot(macho_file, rel))
+        macho_file.getGotEntryAddress(target).?
     else
-        Atom.getRelocTargetAddress(zld, target, false) catch unreachable;
+        Atom.getRelocTargetAddress(macho_file, target, false) catch unreachable;
     _ = Relocation.calcPcRelativeDisplacementArm64(source_addr, target_addr) catch
         return false;
 
     return true;
 }
 
-fn createThunkAtom(zld: *Zld) !Atom.Index {
-    const sym_index = try zld.allocateSymbol();
-    const atom_index = try zld.createAtom(sym_index, .{ .size = @sizeOf(u32) * 3, .alignment = 2 });
-    const sym = zld.getSymbolPtr(.{ .sym_index = sym_index });
+fn createThunkAtom(macho_file: *MachO) !Atom.Index {
+    const sym_index = try macho_file.allocateSymbol();
+    const atom_index = try macho_file.createAtom(sym_index, .{ .size = @sizeOf(u32) * 3, .alignment = 2 });
+    const sym = macho_file.getSymbolPtr(.{ .sym_index = sym_index });
     sym.n_type = macho.N_SECT;
-    sym.n_sect = zld.text_section_index.? + 1;
+    sym.n_sect = macho_file.text_section_index.? + 1;
     return atom_index;
 }
 
-pub fn writeThunkCode(zld: *Zld, thunk: *const Thunk, writer: anytype) !void {
+pub fn writeThunkCode(macho_file: *MachO, thunk: *const Thunk, writer: anytype) !void {
     const slice = thunk.targets.slice();
     for (thunk.getStartAtomIndex()..thunk.getEndAtomIndex(), 0..) |atom_index, target_index| {
-        const atom = zld.getAtom(@intCast(atom_index));
-        const sym = zld.getSymbol(atom.getSymbolWithLoc());
+        const atom = macho_file.getAtom(@intCast(atom_index));
+        const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
         const source_addr = sym.n_value;
         const tag = slice.items(.tag)[target_index];
         const target = slice.items(.target)[target_index];
         const target_addr = switch (tag) {
-            .stub => zld.getStubsEntryAddress(target).?,
-            .atom => zld.getSymbol(target).n_value,
+            .stub => macho_file.getStubsEntryAddress(target).?,
+            .atom => macho_file.getSymbol(target).n_value,
         };
         const pages = Relocation.calcNumberOfPages(source_addr, target_addr);
         try writer.writeIntLittle(u32, aarch64.Instruction.adrp(.x16, pages).toU32());
@@ -368,3 +352,18 @@ pub fn writeThunkCode(zld: *Zld, thunk: *const Thunk, writer: anytype) !void {
         try writer.writeIntLittle(u32, aarch64.Instruction.br(.x16).toU32());
     }
 }
+
+const std = @import("std");
+const assert = std.debug.assert;
+const log = std.log.scoped(.thunks);
+const macho = std.macho;
+const math = std.math;
+const mem = std.mem;
+
+const aarch64 = @import("../../arch/aarch64/bits.zig");
+
+const Allocator = mem.Allocator;
+const Atom = @import("Atom.zig");
+const MachO = @import("../MachO.zig");
+const Relocation = @import("Relocation.zig");
+const SymbolWithLoc = MachO.SymbolWithLoc;
src/link/MachO/Trie.zig
@@ -28,248 +28,6 @@
 //! After the optional exported symbol information is a byte of how many edges (0-255) that
 //! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
 //! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
-const Trie = @This();
-
-const std = @import("std");
-const mem = std.mem;
-const leb = std.leb;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const testing = std.testing;
-const assert = std.debug.assert;
-const Allocator = mem.Allocator;
-
-pub const Node = struct {
-    base: *Trie,
-
-    /// Terminal info associated with this node.
-    /// If this node is not a terminal node, info is null.
-    terminal_info: ?struct {
-        /// Export flags associated with this exported symbol.
-        export_flags: u64,
-        /// VM address offset wrt to the section this symbol is defined against.
-        vmaddr_offset: u64,
-    } = null,
-
-    /// Offset of this node in the trie output byte stream.
-    trie_offset: ?u64 = null,
-
-    /// List of all edges originating from this node.
-    edges: std.ArrayListUnmanaged(Edge) = .{},
-
-    node_dirty: bool = true,
-
-    /// Edge connecting to nodes in the trie.
-    pub const Edge = struct {
-        from: *Node,
-        to: *Node,
-        label: []u8,
-
-        fn deinit(self: *Edge, allocator: Allocator) void {
-            self.to.deinit(allocator);
-            allocator.destroy(self.to);
-            allocator.free(self.label);
-            self.from = undefined;
-            self.to = undefined;
-            self.label = undefined;
-        }
-    };
-
-    fn deinit(self: *Node, allocator: Allocator) void {
-        for (self.edges.items) |*edge| {
-            edge.deinit(allocator);
-        }
-        self.edges.deinit(allocator);
-    }
-
-    /// Inserts a new node starting from `self`.
-    fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
-        // Check for match with edges from this node.
-        for (self.edges.items) |*edge| {
-            const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
-            if (match == 0) continue;
-            if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
-
-            // Found a match, need to splice up nodes.
-            // From: A -> B
-            // To: A -> C -> B
-            const mid = try allocator.create(Node);
-            mid.* = .{ .base = self.base };
-            var to_label = try allocator.dupe(u8, edge.label[match..]);
-            allocator.free(edge.label);
-            const to_node = edge.to;
-            edge.to = mid;
-            edge.label = try allocator.dupe(u8, label[0..match]);
-            self.base.node_count += 1;
-
-            try mid.edges.append(allocator, .{
-                .from = mid,
-                .to = to_node,
-                .label = to_label,
-            });
-
-            return if (match == label.len) mid else mid.put(allocator, label[match..]);
-        }
-
-        // Add a new node.
-        const node = try allocator.create(Node);
-        node.* = .{ .base = self.base };
-        self.base.node_count += 1;
-
-        try self.edges.append(allocator, .{
-            .from = self,
-            .to = node,
-            .label = try allocator.dupe(u8, label),
-        });
-
-        return node;
-    }
-
-    /// Recursively parses the node from the input byte stream.
-    fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
-        self.node_dirty = true;
-        const trie_offset = try reader.context.getPos();
-        self.trie_offset = trie_offset;
-
-        var nread: usize = 0;
-
-        const node_size = try leb.readULEB128(u64, reader);
-        if (node_size > 0) {
-            const export_flags = try leb.readULEB128(u64, reader);
-            // TODO Parse special flags.
-            assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
-                export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
-
-            const vmaddr_offset = try leb.readULEB128(u64, reader);
-
-            self.terminal_info = .{
-                .export_flags = export_flags,
-                .vmaddr_offset = vmaddr_offset,
-            };
-        }
-
-        const nedges = try reader.readByte();
-        self.base.node_count += nedges;
-
-        nread += (try reader.context.getPos()) - trie_offset;
-
-        var i: usize = 0;
-        while (i < nedges) : (i += 1) {
-            const edge_start_pos = try reader.context.getPos();
-
-            const label = blk: {
-                var label_buf = std.ArrayList(u8).init(allocator);
-                while (true) {
-                    const next = try reader.readByte();
-                    if (next == @as(u8, 0))
-                        break;
-                    try label_buf.append(next);
-                }
-                break :blk try label_buf.toOwnedSlice();
-            };
-
-            const seek_to = try leb.readULEB128(u64, reader);
-            const return_pos = try reader.context.getPos();
-
-            nread += return_pos - edge_start_pos;
-            try reader.context.seekTo(seek_to);
-
-            const node = try allocator.create(Node);
-            node.* = .{ .base = self.base };
-
-            nread += try node.read(allocator, reader);
-            try self.edges.append(allocator, .{
-                .from = self,
-                .to = node,
-                .label = label,
-            });
-            try reader.context.seekTo(return_pos);
-        }
-
-        return nread;
-    }
-
-    /// Writes this node to a byte stream.
-    /// The children of this node *are* not written to the byte stream
-    /// recursively. To write all nodes to a byte stream in sequence,
-    /// iterate over `Trie.ordered_nodes` and call this method on each node.
-    /// This is one of the requirements of the MachO.
-    /// Panics if `finalize` was not called before calling this method.
-    fn write(self: Node, writer: anytype) !void {
-        assert(!self.node_dirty);
-        if (self.terminal_info) |info| {
-            // Terminal node info: encode export flags and vmaddr offset of this symbol.
-            var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
-            var info_stream = std.io.fixedBufferStream(&info_buf);
-            // TODO Implement for special flags.
-            assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
-                info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
-            try leb.writeULEB128(info_stream.writer(), info.export_flags);
-            try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
-
-            // Encode the size of the terminal node info.
-            var size_buf: [@sizeOf(u64)]u8 = undefined;
-            var size_stream = std.io.fixedBufferStream(&size_buf);
-            try leb.writeULEB128(size_stream.writer(), info_stream.pos);
-
-            // Now, write them to the output stream.
-            try writer.writeAll(size_buf[0..size_stream.pos]);
-            try writer.writeAll(info_buf[0..info_stream.pos]);
-        } else {
-            // Non-terminal node is delimited by 0 byte.
-            try writer.writeByte(0);
-        }
-        // Write number of edges (max legal number of edges is 256).
-        try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
-
-        for (self.edges.items) |edge| {
-            // Write edge label and offset to next node in trie.
-            try writer.writeAll(edge.label);
-            try writer.writeByte(0);
-            try leb.writeULEB128(writer, edge.to.trie_offset.?);
-        }
-    }
-
-    const FinalizeResult = struct {
-        /// Current size of this node in bytes.
-        node_size: u64,
-
-        /// True if the trie offset of this node in the output byte stream
-        /// would need updating; false otherwise.
-        updated: bool,
-    };
-
-    /// Updates offset of this node in the output byte stream.
-    fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
-        var stream = std.io.countingWriter(std.io.null_writer);
-        var writer = stream.writer();
-
-        var node_size: u64 = 0;
-        if (self.terminal_info) |info| {
-            try leb.writeULEB128(writer, info.export_flags);
-            try leb.writeULEB128(writer, info.vmaddr_offset);
-            try leb.writeULEB128(writer, stream.bytes_written);
-        } else {
-            node_size += 1; // 0x0 for non-terminal nodes
-        }
-        node_size += 1; // 1 byte for edge count
-
-        for (self.edges.items) |edge| {
-            const next_node_offset = edge.to.trie_offset orelse 0;
-            node_size += edge.label.len + 1;
-            try leb.writeULEB128(writer, next_node_offset);
-        }
-
-        const trie_offset = self.trie_offset orelse 0;
-        const updated = offset_in_trie != trie_offset;
-        self.trie_offset = offset_in_trie;
-        self.node_dirty = false;
-        node_size += stream.bytes_written;
-
-        return FinalizeResult{ .node_size = node_size, .updated = updated };
-    }
-};
-
 /// The root node of the trie.
 root: ?*Node = null,
 
@@ -611,3 +369,245 @@ test "ordering bug" {
     _ = try trie.write(stream.writer());
     try expectEqualHexStrings(&exp_buffer, buffer);
 }
+
+pub const Node = struct {
+    base: *Trie,
+
+    /// Terminal info associated with this node.
+    /// If this node is not a terminal node, info is null.
+    terminal_info: ?struct {
+        /// Export flags associated with this exported symbol.
+        export_flags: u64,
+        /// VM address offset wrt to the section this symbol is defined against.
+        vmaddr_offset: u64,
+    } = null,
+
+    /// Offset of this node in the trie output byte stream.
+    trie_offset: ?u64 = null,
+
+    /// List of all edges originating from this node.
+    edges: std.ArrayListUnmanaged(Edge) = .{},
+
+    node_dirty: bool = true,
+
+    /// Edge connecting to nodes in the trie.
+    pub const Edge = struct {
+        from: *Node,
+        to: *Node,
+        label: []u8,
+
+        fn deinit(self: *Edge, allocator: Allocator) void {
+            self.to.deinit(allocator);
+            allocator.destroy(self.to);
+            allocator.free(self.label);
+            self.from = undefined;
+            self.to = undefined;
+            self.label = undefined;
+        }
+    };
+
+    fn deinit(self: *Node, allocator: Allocator) void {
+        for (self.edges.items) |*edge| {
+            edge.deinit(allocator);
+        }
+        self.edges.deinit(allocator);
+    }
+
+    /// Inserts a new node starting from `self`.
+    fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
+        // Check for match with edges from this node.
+        for (self.edges.items) |*edge| {
+            const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
+            if (match == 0) continue;
+            if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
+
+            // Found a match, need to splice up nodes.
+            // From: A -> B
+            // To: A -> C -> B
+            const mid = try allocator.create(Node);
+            mid.* = .{ .base = self.base };
+            var to_label = try allocator.dupe(u8, edge.label[match..]);
+            allocator.free(edge.label);
+            const to_node = edge.to;
+            edge.to = mid;
+            edge.label = try allocator.dupe(u8, label[0..match]);
+            self.base.node_count += 1;
+
+            try mid.edges.append(allocator, .{
+                .from = mid,
+                .to = to_node,
+                .label = to_label,
+            });
+
+            return if (match == label.len) mid else mid.put(allocator, label[match..]);
+        }
+
+        // Add a new node.
+        const node = try allocator.create(Node);
+        node.* = .{ .base = self.base };
+        self.base.node_count += 1;
+
+        try self.edges.append(allocator, .{
+            .from = self,
+            .to = node,
+            .label = try allocator.dupe(u8, label),
+        });
+
+        return node;
+    }
+
+    /// Recursively parses the node from the input byte stream.
+    fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
+        self.node_dirty = true;
+        const trie_offset = try reader.context.getPos();
+        self.trie_offset = trie_offset;
+
+        var nread: usize = 0;
+
+        const node_size = try leb.readULEB128(u64, reader);
+        if (node_size > 0) {
+            const export_flags = try leb.readULEB128(u64, reader);
+            // TODO Parse special flags.
+            assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
+                export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
+
+            const vmaddr_offset = try leb.readULEB128(u64, reader);
+
+            self.terminal_info = .{
+                .export_flags = export_flags,
+                .vmaddr_offset = vmaddr_offset,
+            };
+        }
+
+        const nedges = try reader.readByte();
+        self.base.node_count += nedges;
+
+        nread += (try reader.context.getPos()) - trie_offset;
+
+        var i: usize = 0;
+        while (i < nedges) : (i += 1) {
+            const edge_start_pos = try reader.context.getPos();
+
+            const label = blk: {
+                var label_buf = std.ArrayList(u8).init(allocator);
+                while (true) {
+                    const next = try reader.readByte();
+                    if (next == @as(u8, 0))
+                        break;
+                    try label_buf.append(next);
+                }
+                break :blk try label_buf.toOwnedSlice();
+            };
+
+            const seek_to = try leb.readULEB128(u64, reader);
+            const return_pos = try reader.context.getPos();
+
+            nread += return_pos - edge_start_pos;
+            try reader.context.seekTo(seek_to);
+
+            const node = try allocator.create(Node);
+            node.* = .{ .base = self.base };
+
+            nread += try node.read(allocator, reader);
+            try self.edges.append(allocator, .{
+                .from = self,
+                .to = node,
+                .label = label,
+            });
+            try reader.context.seekTo(return_pos);
+        }
+
+        return nread;
+    }
+
+    /// Writes this node to a byte stream.
+    /// The children of this node *are* not written to the byte stream
+    /// recursively. To write all nodes to a byte stream in sequence,
+    /// iterate over `Trie.ordered_nodes` and call this method on each node.
+    /// This is one of the requirements of the MachO.
+    /// Panics if `finalize` was not called before calling this method.
+    fn write(self: Node, writer: anytype) !void {
+        assert(!self.node_dirty);
+        if (self.terminal_info) |info| {
+            // Terminal node info: encode export flags and vmaddr offset of this symbol.
+            var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
+            var info_stream = std.io.fixedBufferStream(&info_buf);
+            // TODO Implement for special flags.
+            assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
+                info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
+            try leb.writeULEB128(info_stream.writer(), info.export_flags);
+            try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
+
+            // Encode the size of the terminal node info.
+            var size_buf: [@sizeOf(u64)]u8 = undefined;
+            var size_stream = std.io.fixedBufferStream(&size_buf);
+            try leb.writeULEB128(size_stream.writer(), info_stream.pos);
+
+            // Now, write them to the output stream.
+            try writer.writeAll(size_buf[0..size_stream.pos]);
+            try writer.writeAll(info_buf[0..info_stream.pos]);
+        } else {
+            // Non-terminal node is delimited by 0 byte.
+            try writer.writeByte(0);
+        }
+        // Write number of edges (max legal number of edges is 256).
+        try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
+
+        for (self.edges.items) |edge| {
+            // Write edge label and offset to next node in trie.
+            try writer.writeAll(edge.label);
+            try writer.writeByte(0);
+            try leb.writeULEB128(writer, edge.to.trie_offset.?);
+        }
+    }
+
+    const FinalizeResult = struct {
+        /// Current size of this node in bytes.
+        node_size: u64,
+
+        /// True if the trie offset of this node in the output byte stream
+        /// would need updating; false otherwise.
+        updated: bool,
+    };
+
+    /// Updates offset of this node in the output byte stream.
+    fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
+        var stream = std.io.countingWriter(std.io.null_writer);
+        var writer = stream.writer();
+
+        var node_size: u64 = 0;
+        if (self.terminal_info) |info| {
+            try leb.writeULEB128(writer, info.export_flags);
+            try leb.writeULEB128(writer, info.vmaddr_offset);
+            try leb.writeULEB128(writer, stream.bytes_written);
+        } else {
+            node_size += 1; // 0x0 for non-terminal nodes
+        }
+        node_size += 1; // 1 byte for edge count
+
+        for (self.edges.items) |edge| {
+            const next_node_offset = edge.to.trie_offset orelse 0;
+            node_size += edge.label.len + 1;
+            try leb.writeULEB128(writer, next_node_offset);
+        }
+
+        const trie_offset = self.trie_offset orelse 0;
+        const updated = offset_in_trie != trie_offset;
+        self.trie_offset = offset_in_trie;
+        self.node_dirty = false;
+        node_size += stream.bytes_written;
+
+        return FinalizeResult{ .node_size = node_size, .updated = updated };
+    }
+};
+
+const Trie = @This();
+
+const std = @import("std");
+const mem = std.mem;
+const leb = std.leb;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const testing = std.testing;
+const assert = std.debug.assert;
+const Allocator = mem.Allocator;
src/link/MachO/UnwindInfo.zig
@@ -1,25 +1,3 @@
-const UnwindInfo = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const eh_frame = @import("eh_frame.zig");
-const fs = std.fs;
-const leb = std.leb;
-const log = std.log.scoped(.unwind_info);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-const trace = @import("../../tracy.zig").trace;
-
-const Allocator = mem.Allocator;
-const Atom = @import("Atom.zig");
-const AtomIndex = @import("zld.zig").AtomIndex;
-const EhFrameRecord = eh_frame.EhFrameRecord;
-const MachO = @import("../MachO.zig");
-const Object = @import("Object.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const Zld = @import("zld.zig").Zld;
-
 gpa: Allocator,
 
 /// List of all unwind records gathered from all objects and sorted
@@ -203,28 +181,28 @@ pub fn deinit(info: *UnwindInfo) void {
     info.lsdas_lookup.deinit(info.gpa);
 }
 
-pub fn scanRelocs(zld: *Zld) !void {
-    if (zld.unwind_info_section_index == null) return;
+pub fn scanRelocs(macho_file: *MachO) !void {
+    if (macho_file.unwind_info_section_index == null) return;
 
-    const cpu_arch = zld.options.target.cpu.arch;
-    for (zld.objects.items, 0..) |*object, object_id| {
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+    for (macho_file.objects.items, 0..) |*object, object_id| {
         const unwind_records = object.getUnwindRecords();
         for (object.exec_atoms.items) |atom_index| {
-            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
             while (inner_syms_it.next()) |sym| {
                 const record_id = object.unwind_records_lookup.get(sym) orelse continue;
                 if (object.unwind_relocs_lookup[record_id].dead) continue;
                 const record = unwind_records[record_id];
                 if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
-                    if (getPersonalityFunctionReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
+                    if (getPersonalityFunctionReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
                         // Personality function; add GOT pointer.
-                        const target = Atom.parseRelocTarget(zld, .{
+                        const target = Atom.parseRelocTarget(macho_file, .{
                             .object_id = @as(u32, @intCast(object_id)),
                             .rel = rel,
                             .code = mem.asBytes(&record),
                             .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
                         });
-                        try zld.addGotEntry(target);
+                        try macho_file.addGotEntry(target);
                     }
                 }
             }
@@ -232,10 +210,10 @@ pub fn scanRelocs(zld: *Zld) !void {
     }
 }
 
-pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
-    if (zld.unwind_info_section_index == null) return;
+pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
+    if (macho_file.unwind_info_section_index == null) return;
 
-    const cpu_arch = zld.options.target.cpu.arch;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
 
     var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa);
     defer records.deinit();
@@ -244,7 +222,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
     defer sym_indexes.deinit();
 
     // TODO handle dead stripping
-    for (zld.objects.items, 0..) |*object, object_id| {
+    for (macho_file.objects.items, 0..) |*object, object_id| {
         log.debug("collecting unwind records in {s} ({d})", .{ object.name, object_id });
         const unwind_records = object.getUnwindRecords();
 
@@ -254,7 +232,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
         try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
 
         for (object.exec_atoms.items) |atom_index| {
-            var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
+            var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
             var prev_symbol: ?SymbolWithLoc = null;
             while (inner_syms_it.next()) |symbol| {
                 var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: {
@@ -262,14 +240,14 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
                     var record = unwind_records[record_id];
 
                     if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
-                        try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
+                        try info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
                     } else {
                         if (getPersonalityFunctionReloc(
-                            zld,
+                            macho_file,
                             @as(u32, @intCast(object_id)),
                             record_id,
                         )) |rel| {
-                            const target = Atom.parseRelocTarget(zld, .{
+                            const target = Atom.parseRelocTarget(macho_file, .{
                                 .object_id = @as(u32, @intCast(object_id)),
                                 .rel = rel,
                                 .code = mem.asBytes(&record),
@@ -286,8 +264,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
                             UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
                         }
 
-                        if (getLsdaReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
-                            const target = Atom.parseRelocTarget(zld, .{
+                        if (getLsdaReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
+                            const target = Atom.parseRelocTarget(macho_file, .{
                                 .object_id = @as(u32, @intCast(object_id)),
                                 .rel = rel,
                                 .code = mem.asBytes(&record),
@@ -298,7 +276,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
                     }
                     break :blk record;
                 } else blk: {
-                    const sym = zld.getSymbol(symbol);
+                    const sym = macho_file.getSymbol(symbol);
                     if (sym.n_desc == MachO.N_DEAD) continue;
                     if (prev_symbol) |prev_sym| {
                         const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value;
@@ -310,7 +288,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
                         if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| {
                             if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
                             var record = nullRecord();
-                            try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
+                            try info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
                             switch (cpu_arch) {
                                 .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
                                 .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
@@ -323,8 +301,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
                     break :blk nullRecord();
                 };
 
-                const atom = zld.getAtom(atom_index);
-                const sym = zld.getSymbol(symbol);
+                const atom = macho_file.getAtom(atom_index);
+                const sym = macho_file.getSymbol(symbol);
                 assert(sym.n_desc != MachO.N_DEAD);
                 const size = if (inner_syms_it.next()) |next_sym| blk: {
                     // All this trouble to account for symbol aliases.
@@ -336,8 +314,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
                     const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
                     const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value;
                     if (next_addr > curr_addr) break :blk next_addr - curr_addr;
-                    break :blk zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
-                } else zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
+                    break :blk macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
+                } else macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
                 record.rangeStart = sym.n_value;
                 record.rangeLength = @as(u32, @intCast(size));
 
@@ -518,23 +496,23 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
 
 fn collectPersonalityFromDwarf(
     info: *UnwindInfo,
-    zld: *Zld,
+    macho_file: *MachO,
     object_id: u32,
     sym_loc: SymbolWithLoc,
     record: *macho.compact_unwind_entry,
 ) !void {
-    const object = &zld.objects.items[object_id];
+    const object = &macho_file.objects.items[object_id];
     var it = object.getEhFrameRecordsIterator();
     const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?;
     it.seekTo(fde_offset);
     const fde = (try it.next()).?;
-    const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
+    const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
     const cie_offset = fde_offset + 4 - cie_ptr;
     it.seekTo(cie_offset);
     const cie = (try it.next()).?;
 
     if (cie.getPersonalityPointerReloc(
-        zld,
+        macho_file,
         @as(u32, @intCast(object_id)),
         cie_offset,
     )) |target| {
@@ -550,9 +528,9 @@ fn collectPersonalityFromDwarf(
     }
 }
 
-pub fn calcSectionSize(info: UnwindInfo, zld: *Zld) !void {
-    const sect_id = zld.unwind_info_section_index orelse return;
-    const sect = &zld.sections.items(.header)[sect_id];
+pub fn calcSectionSize(info: UnwindInfo, macho_file: *MachO) !void {
+    const sect_id = macho_file.unwind_info_section_index orelse return;
+    const sect = &macho_file.sections.items(.header)[sect_id];
     sect.@"align" = 2;
     sect.size = info.calcRequiredSize();
 }
@@ -569,23 +547,23 @@ fn calcRequiredSize(info: UnwindInfo) usize {
     return total_size;
 }
 
-pub fn write(info: *UnwindInfo, zld: *Zld) !void {
-    const sect_id = zld.unwind_info_section_index orelse return;
-    const sect = &zld.sections.items(.header)[sect_id];
-    const seg_id = zld.sections.items(.segment_index)[sect_id];
-    const seg = zld.segments.items[seg_id];
+pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {
+    const sect_id = macho_file.unwind_info_section_index orelse return;
+    const sect = &macho_file.sections.items(.header)[sect_id];
+    const seg_id = macho_file.sections.items(.segment_index)[sect_id];
+    const seg = macho_file.segments.items[seg_id];
 
-    const text_sect_id = zld.text_section_index.?;
-    const text_sect = zld.sections.items(.header)[text_sect_id];
+    const text_sect_id = macho_file.text_section_index.?;
+    const text_sect = macho_file.sections.items(.header)[text_sect_id];
 
     var personalities: [max_personalities]u32 = undefined;
-    const cpu_arch = zld.options.target.cpu.arch;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
 
     log.debug("Personalities:", .{});
     for (info.personalities[0..info.personalities_count], 0..) |target, i| {
-        const addr = zld.getGotEntryAddress(target).?;
+        const addr = macho_file.getGotEntryAddress(target).?;
         personalities[i] = @as(u32, @intCast(addr - seg.vmaddr));
-        log.debug("  {d}: 0x{x} ({s})", .{ i, personalities[i], zld.getSymbolName(target) });
+        log.debug("  {d}: 0x{x} ({s})", .{ i, personalities[i], macho_file.getSymbolName(target) });
     }
 
     for (info.records.items) |*rec| {
@@ -599,7 +577,7 @@ pub fn write(info: *UnwindInfo, zld: *Zld) !void {
         if (rec.compactUnwindEncoding > 0 and !UnwindEncoding.isDwarf(rec.compactUnwindEncoding, cpu_arch)) {
             const lsda_target = @as(SymbolWithLoc, @bitCast(rec.lsda));
             if (lsda_target.getFile()) |_| {
-                const sym = zld.getSymbol(lsda_target);
+                const sym = macho_file.getSymbol(lsda_target);
                 rec.lsda = sym.n_value - seg.vmaddr;
             }
         }
@@ -689,11 +667,11 @@ pub fn write(info: *UnwindInfo, zld: *Zld) !void {
         @memset(buffer.items[offset..], 0);
     }
 
-    try zld.file.pwriteAll(buffer.items, sect.offset);
+    try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
 }
 
-fn getRelocs(zld: *Zld, object_id: u32, record_id: usize) []const macho.relocation_info {
-    const object = &zld.objects.items[object_id];
+fn getRelocs(macho_file: *MachO, object_id: u32, record_id: usize) []const macho.relocation_info {
+    const object = &macho_file.objects.items[object_id];
     assert(object.hasUnwindRecords());
     const rel_pos = object.unwind_relocs_lookup[record_id].reloc;
     const relocs = object.getRelocs(object.unwind_info_sect_id.?);
@@ -707,11 +685,11 @@ fn isPersonalityFunction(record_id: usize, rel: macho.relocation_info) bool {
 }
 
 pub fn getPersonalityFunctionReloc(
-    zld: *Zld,
+    macho_file: *MachO,
     object_id: u32,
     record_id: usize,
 ) ?macho.relocation_info {
-    const relocs = getRelocs(zld, object_id, record_id);
+    const relocs = getRelocs(macho_file, object_id, record_id);
     for (relocs) |rel| {
         if (isPersonalityFunction(record_id, rel)) return rel;
     }
@@ -735,8 +713,8 @@ fn isLsda(record_id: usize, rel: macho.relocation_info) bool {
     return rel_offset == 24;
 }
 
-pub fn getLsdaReloc(zld: *Zld, object_id: u32, record_id: usize) ?macho.relocation_info {
-    const relocs = getRelocs(zld, object_id, record_id);
+pub fn getLsdaReloc(macho_file: *MachO, object_id: u32, record_id: usize) ?macho.relocation_info {
+    const relocs = getRelocs(macho_file, object_id, record_id);
     for (relocs) |rel| {
         if (isLsda(record_id, rel)) return rel;
     }
@@ -828,3 +806,23 @@ pub const UnwindEncoding = struct {
         enc.* |= offset;
     }
 };
+
+const UnwindInfo = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const eh_frame = @import("eh_frame.zig");
+const fs = std.fs;
+const leb = std.leb;
+const log = std.log.scoped(.unwind_info);
+const macho = std.macho;
+const math = std.math;
+const mem = std.mem;
+const trace = @import("../../tracy.zig").trace;
+
+const Allocator = mem.Allocator;
+const Atom = @import("Atom.zig");
+const EhFrameRecord = eh_frame.EhFrameRecord;
+const MachO = @import("../MachO.zig");
+const Object = @import("Object.zig");
+const SymbolWithLoc = MachO.SymbolWithLoc;
src/link/MachO/uuid.zig
@@ -1,12 +1,3 @@
-const std = @import("std");
-const fs = std.fs;
-const mem = std.mem;
-
-const Allocator = mem.Allocator;
-const Compilation = @import("../../Compilation.zig");
-const Md5 = std.crypto.hash.Md5;
-const Hasher = @import("hasher.zig").ParallelHasher;
-
 /// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce
 /// the final digest.
 /// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD
@@ -43,3 +34,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void {
     out[6] = (out[6] & 0x0F) | (3 << 4);
     out[8] = (out[8] & 0x3F) | 0x80;
 }
+
+const std = @import("std");
+const fs = std.fs;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Compilation = @import("../../Compilation.zig");
+const Md5 = std.crypto.hash.Md5;
+const Hasher = @import("hasher.zig").ParallelHasher;
src/link/MachO/zld.zig
@@ -1,2584 +1,8 @@
-const std = @import("std");
-const build_options = @import("build_options");
-const assert = std.debug.assert;
-const dwarf = std.dwarf;
-const fs = std.fs;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-
-const aarch64 = @import("../../arch/aarch64/bits.zig");
-const calcUuid = @import("uuid.zig").calcUuid;
-const dead_strip = @import("dead_strip.zig");
-const eh_frame = @import("eh_frame.zig");
-const fat = @import("fat.zig");
-const link = @import("../../link.zig");
-const load_commands = @import("load_commands.zig");
-const stubs = @import("stubs.zig");
-const thunks = @import("thunks.zig");
-const trace = @import("../../tracy.zig").trace;
-
-const Allocator = mem.Allocator;
-const Archive = @import("Archive.zig");
-const Atom = @import("Atom.zig");
-const Cache = std.Build.Cache;
-const CodeSignature = @import("CodeSignature.zig");
-const Compilation = @import("../../Compilation.zig");
-const DwarfInfo = @import("DwarfInfo.zig");
-const Dylib = @import("Dylib.zig");
-const MachO = @import("../MachO.zig");
-const Md5 = std.crypto.hash.Md5;
-const LibStub = @import("../tapi.zig").LibStub;
-const Object = @import("Object.zig");
-const Section = MachO.Section;
-const StringTable = @import("../strtab.zig").StringTable;
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const TableSection = @import("../table_section.zig").TableSection;
-const Trie = @import("Trie.zig");
-const UnwindInfo = @import("UnwindInfo.zig");
-
-const Bind = @import("dyld_info/bind.zig").Bind(*const Zld, SymbolWithLoc);
-const LazyBind = @import("dyld_info/bind.zig").LazyBind(*const Zld, SymbolWithLoc);
-const Rebase = @import("dyld_info/Rebase.zig");
-
-pub const Zld = struct {
-    gpa: Allocator,
-    file: fs.File,
-    options: *const link.Options,
-
-    dyld_info_cmd: macho.dyld_info_command = .{},
-    symtab_cmd: macho.symtab_command = .{},
-    dysymtab_cmd: macho.dysymtab_command = .{},
-    function_starts_cmd: macho.linkedit_data_command = .{ .cmd = .FUNCTION_STARTS },
-    data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE },
-    uuid_cmd: macho.uuid_command = .{
-        .uuid = [_]u8{0} ** 16,
-    },
-    codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE },
-
-    objects: std.ArrayListUnmanaged(Object) = .{},
-    archives: std.ArrayListUnmanaged(Archive) = .{},
-    dylibs: std.ArrayListUnmanaged(Dylib) = .{},
-    dylibs_map: std.StringHashMapUnmanaged(u16) = .{},
-    referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{},
-
-    segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
-    sections: std.MultiArrayList(Section) = .{},
-
-    pagezero_segment_cmd_index: ?u8 = null,
-    header_segment_cmd_index: ?u8 = null,
-    text_segment_cmd_index: ?u8 = null,
-    data_const_segment_cmd_index: ?u8 = null,
-    data_segment_cmd_index: ?u8 = null,
-    linkedit_segment_cmd_index: ?u8 = null,
-
-    text_section_index: ?u8 = null,
-    data_const_section_index: ?u8 = null,
-    data_section_index: ?u8 = null,
-    bss_section_index: ?u8 = null,
-    thread_vars_section_index: ?u8 = null,
-    thread_data_section_index: ?u8 = null,
-    thread_bss_section_index: ?u8 = null,
-    eh_frame_section_index: ?u8 = null,
-    unwind_info_section_index: ?u8 = null,
-    got_section_index: ?u8 = null,
-    tlv_ptr_section_index: ?u8 = null,
-    stubs_section_index: ?u8 = null,
-    stub_helper_section_index: ?u8 = null,
-    la_symbol_ptr_section_index: ?u8 = null,
-
-    locals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-    globals: std.ArrayListUnmanaged(SymbolWithLoc) = .{},
-    resolver: std.StringHashMapUnmanaged(u32) = .{},
-    unresolved: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
-
-    locals_free_list: std.ArrayListUnmanaged(u32) = .{},
-    globals_free_list: std.ArrayListUnmanaged(u32) = .{},
-
-    entry_index: ?u32 = null,
-    dyld_stub_binder_index: ?u32 = null,
-    dyld_private_atom_index: ?Atom.Index = null,
-
-    strtab: StringTable(.strtab) = .{},
-
-    tlv_ptr_table: TableSection(SymbolWithLoc) = .{},
-    got_table: TableSection(SymbolWithLoc) = .{},
-    stubs_table: TableSection(SymbolWithLoc) = .{},
-
-    thunk_table: std.AutoHashMapUnmanaged(Atom.Index, thunks.Thunk.Index) = .{},
-    thunks: std.ArrayListUnmanaged(thunks.Thunk) = .{},
-
-    atoms: std.ArrayListUnmanaged(Atom) = .{},
-
-    pub fn addAtomToSection(self: *Zld, atom_index: Atom.Index) void {
-        const atom = self.getAtomPtr(atom_index);
-        const sym = self.getSymbol(atom.getSymbolWithLoc());
-        var section = self.sections.get(sym.n_sect - 1);
-        if (section.header.size > 0) {
-            const last_atom = self.getAtomPtr(section.last_atom_index.?);
-            last_atom.next_index = atom_index;
-            atom.prev_index = section.last_atom_index;
-        } else {
-            section.first_atom_index = atom_index;
-        }
-        section.last_atom_index = atom_index;
-        section.header.size += atom.size;
-        self.sections.set(sym.n_sect - 1, section);
-    }
-
-    const CreateAtomOpts = struct {
-        size: u64 = 0,
-        alignment: u32 = 0,
-    };
-
-    pub fn createAtom(self: *Zld, sym_index: u32, opts: CreateAtomOpts) !Atom.Index {
-        const gpa = self.gpa;
-        const index = @as(Atom.Index, @intCast(self.atoms.items.len));
-        const atom = try self.atoms.addOne(gpa);
-        atom.* = .{};
-        atom.sym_index = sym_index;
-        atom.size = opts.size;
-        atom.alignment = opts.alignment;
-        log.debug("creating ATOM(%{d}) at index {d}", .{ sym_index, index });
-        return index;
-    }
-
-    fn createDyldPrivateAtom(self: *Zld) !void {
-        const sym_index = try self.allocateSymbol();
-        const atom_index = try self.createAtom(sym_index, .{ .size = @sizeOf(u64), .alignment = 3 });
-        const sym = self.getSymbolPtr(.{ .sym_index = sym_index });
-        sym.n_type = macho.N_SECT;
-
-        if (self.data_section_index == null) {
-            self.data_section_index = try MachO.initSection(self.gpa, self, "__DATA", "__data", .{});
-        }
-        sym.n_sect = self.data_section_index.? + 1;
-        self.dyld_private_atom_index = atom_index;
-
-        self.addAtomToSection(atom_index);
-    }
-
-    fn createTentativeDefAtoms(self: *Zld) !void {
-        const gpa = self.gpa;
-
-        for (self.globals.items) |global| {
-            const sym = self.getSymbolPtr(global);
-            if (!sym.tentative()) continue;
-            if (sym.n_desc == MachO.N_DEAD) continue;
-
-            log.debug("creating tentative definition for ATOM(%{d}, '{s}') in object({?})", .{
-                global.sym_index, self.getSymbolName(global), global.file,
-            });
-
-            // Convert any tentative definition into a regular symbol and allocate
-            // text blocks for each tentative definition.
-            const size = sym.n_value;
-            const alignment = (sym.n_desc >> 8) & 0x0f;
-
-            if (self.bss_section_index == null) {
-                self.bss_section_index = try MachO.initSection(gpa, self, "__DATA", "__bss", .{
-                    .flags = macho.S_ZEROFILL,
-                });
-            }
-
-            sym.* = .{
-                .n_strx = sym.n_strx,
-                .n_type = macho.N_SECT | macho.N_EXT,
-                .n_sect = self.bss_section_index.? + 1,
-                .n_desc = 0,
-                .n_value = 0,
-            };
-
-            const atom_index = try self.createAtom(global.sym_index, .{
-                .size = size,
-                .alignment = alignment,
-            });
-            const atom = self.getAtomPtr(atom_index);
-            atom.file = global.file;
-
-            self.addAtomToSection(atom_index);
-
-            assert(global.getFile() != null);
-            const object = &self.objects.items[global.getFile().?];
-            try object.atoms.append(gpa, atom_index);
-            object.atom_by_index_table[global.sym_index] = atom_index;
-        }
-    }
-
-    fn addUndefined(self: *Zld, name: []const u8) !u32 {
-        const gop = try self.getOrPutGlobalPtr(name);
-        const global_index = self.getGlobalIndex(name).?;
-
-        if (gop.found_existing) return global_index;
-
-        const sym_index = try self.allocateSymbol();
-        const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
-        gop.value_ptr.* = sym_loc;
-
-        const sym = self.getSymbolPtr(sym_loc);
-        sym.n_strx = try self.strtab.insert(self.gpa, name);
-        sym.n_type = macho.N_UNDF;
-
-        try self.unresolved.putNoClobber(self.gpa, global_index, {});
-
-        return global_index;
-    }
-
-    fn resolveSymbols(self: *Zld) !void {
-        // We add the specified entrypoint as the first unresolved symbols so that
-        // we search for it in libraries should there be no object files specified
-        // on the linker line.
-        if (self.options.output_mode == .Exe) {
-            const entry_name = self.options.entry orelse load_commands.default_entry_point;
-            _ = try self.addUndefined(entry_name);
-        }
-
-        // Force resolution of any symbols requested by the user.
-        for (self.options.force_undefined_symbols.keys()) |sym_name| {
-            _ = try self.addUndefined(sym_name);
-        }
-
-        for (self.objects.items, 0..) |_, object_id| {
-            try self.resolveSymbolsInObject(@as(u32, @intCast(object_id)));
-        }
-
-        try self.resolveSymbolsInArchives();
-
-        // Finally, force resolution of dyld_stub_binder if there are imports
-        // requested.
-        if (self.unresolved.count() > 0) {
-            self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder");
-        }
-
-        try self.resolveSymbolsInDylibs();
-
-        try self.createMhExecuteHeaderSymbol();
-        try self.createDsoHandleSymbol();
-        try self.resolveSymbolsAtLoading();
-    }
-
-    fn resolveGlobalSymbol(self: *Zld, current: SymbolWithLoc) !void {
-        const gpa = self.gpa;
-        const sym = self.getSymbol(current);
-        const sym_name = self.getSymbolName(current);
-
-        const gop = try self.getOrPutGlobalPtr(sym_name);
-        if (!gop.found_existing) {
-            gop.value_ptr.* = current;
-            if (sym.undf() and !sym.tentative()) {
-                try self.unresolved.putNoClobber(gpa, self.getGlobalIndex(sym_name).?, {});
-            }
-            return;
-        }
-        const global_index = self.getGlobalIndex(sym_name).?;
-        const global = gop.value_ptr.*;
-        const global_sym = self.getSymbol(global);
-
-        // Cases to consider: sym vs global_sym
-        // 1.  strong(sym) and strong(global_sym) => error
-        // 2.  strong(sym) and weak(global_sym) => sym
-        // 3.  strong(sym) and tentative(global_sym) => sym
-        // 4.  strong(sym) and undf(global_sym) => sym
-        // 5.  weak(sym) and strong(global_sym) => global_sym
-        // 6.  weak(sym) and tentative(global_sym) => sym
-        // 7.  weak(sym) and undf(global_sym) => sym
-        // 8.  tentative(sym) and strong(global_sym) => global_sym
-        // 9.  tentative(sym) and weak(global_sym) => global_sym
-        // 10. tentative(sym) and tentative(global_sym) => pick larger
-        // 11. tentative(sym) and undf(global_sym) => sym
-        // 12. undf(sym) and * => global_sym
-        //
-        // Reduces to:
-        // 1. strong(sym) and strong(global_sym) => error
-        // 2. * and strong(global_sym) => global_sym
-        // 3. weak(sym) and weak(global_sym) => global_sym
-        // 4. tentative(sym) and tentative(global_sym) => pick larger
-        // 5. undf(sym) and * => global_sym
-        // 6. else => sym
-
-        const sym_is_strong = sym.sect() and !(sym.weakDef() or sym.pext());
-        const global_is_strong = global_sym.sect() and !(global_sym.weakDef() or global_sym.pext());
-        const sym_is_weak = sym.sect() and (sym.weakDef() or sym.pext());
-        const global_is_weak = global_sym.sect() and (global_sym.weakDef() or global_sym.pext());
-
-        if (sym_is_strong and global_is_strong) {
-            log.err("symbol '{s}' defined multiple times", .{sym_name});
-            if (global.getFile()) |file| {
-                log.err("  first definition in '{s}'", .{self.objects.items[file].name});
-            }
-            if (current.getFile()) |file| {
-                log.err("  next definition in '{s}'", .{self.objects.items[file].name});
-            }
-            return error.MultipleSymbolDefinitions;
-        }
-
-        if (current.getFile()) |file| {
-            const object = &self.objects.items[file];
-            object.globals_lookup[current.sym_index] = global_index;
-        }
-
-        if (global_is_strong) return;
-        if (sym_is_weak and global_is_weak) return;
-        if (sym.tentative() and global_sym.tentative()) {
-            if (global_sym.n_value >= sym.n_value) return;
-        }
-        if (sym.undf() and !sym.tentative()) return;
-
-        if (global.getFile()) |file| {
-            const global_object = &self.objects.items[file];
-            global_object.globals_lookup[global.sym_index] = global_index;
-        }
-        _ = self.unresolved.swapRemove(global_index);
-
-        gop.value_ptr.* = current;
-    }
-
-    fn resolveSymbolsInObject(self: *Zld, object_id: u32) !void {
-        const object = &self.objects.items[object_id];
-        const in_symtab = object.in_symtab orelse return;
-
-        log.debug("resolving symbols in '{s}'", .{object.name});
-
-        var sym_index: u32 = 0;
-        while (sym_index < in_symtab.len) : (sym_index += 1) {
-            const sym = &object.symtab[sym_index];
-            const sym_name = object.getSymbolName(sym_index);
-
-            if (sym.stab()) {
-                log.err("unhandled symbol type: stab", .{});
-                log.err("  symbol '{s}'", .{sym_name});
-                log.err("  first definition in '{s}'", .{object.name});
-                return error.UnhandledSymbolType;
-            }
-
-            if (sym.indr()) {
-                log.err("unhandled symbol type: indirect", .{});
-                log.err("  symbol '{s}'", .{sym_name});
-                log.err("  first definition in '{s}'", .{object.name});
-                return error.UnhandledSymbolType;
-            }
-
-            if (sym.abs()) {
-                log.err("unhandled symbol type: absolute", .{});
-                log.err("  symbol '{s}'", .{sym_name});
-                log.err("  first definition in '{s}'", .{object.name});
-                return error.UnhandledSymbolType;
-            }
-
-            if (sym.sect() and !sym.ext()) {
-                log.debug("symbol '{s}' local to object {s}; skipping...", .{
-                    sym_name,
-                    object.name,
-                });
-                continue;
-            }
-
-            try self.resolveGlobalSymbol(.{ .sym_index = sym_index, .file = object_id + 1 });
-        }
-    }
-
-    fn resolveSymbolsInArchives(self: *Zld) !void {
-        if (self.archives.items.len == 0) return;
-
-        const gpa = self.gpa;
-
-        var next_sym: usize = 0;
-        loop: while (next_sym < self.unresolved.count()) {
-            const global = self.globals.items[self.unresolved.keys()[next_sym]];
-            const sym_name = self.getSymbolName(global);
-
-            for (self.archives.items) |archive| {
-                // Check if the entry exists in a static archive.
-                const offsets = archive.toc.get(sym_name) orelse {
-                    // No hit.
-                    continue;
-                };
-                assert(offsets.items.len > 0);
-
-                const object_id = @as(u16, @intCast(self.objects.items.len));
-                const object = try archive.parseObject(gpa, offsets.items[0]);
-                try self.objects.append(gpa, object);
-                try self.resolveSymbolsInObject(object_id);
-
-                continue :loop;
-            }
-
-            next_sym += 1;
-        }
-    }
-
-    fn resolveSymbolsInDylibs(self: *Zld) !void {
-        if (self.dylibs.items.len == 0) return;
-
-        var next_sym: usize = 0;
-        loop: while (next_sym < self.unresolved.count()) {
-            const global_index = self.unresolved.keys()[next_sym];
-            const global = self.globals.items[global_index];
-            const sym = self.getSymbolPtr(global);
-            const sym_name = self.getSymbolName(global);
-
-            for (self.dylibs.items, 0..) |dylib, id| {
-                if (!dylib.symbols.contains(sym_name)) continue;
-
-                const dylib_id = @as(u16, @intCast(id));
-                if (!self.referenced_dylibs.contains(dylib_id)) {
-                    try self.referenced_dylibs.putNoClobber(self.gpa, dylib_id, {});
-                }
-
-                const ordinal = self.referenced_dylibs.getIndex(dylib_id) orelse unreachable;
-                sym.n_type |= macho.N_EXT;
-                sym.n_desc = @as(u16, @intCast(ordinal + 1)) * macho.N_SYMBOL_RESOLVER;
-
-                if (dylib.weak) {
-                    sym.n_desc |= macho.N_WEAK_REF;
-                }
-
-                assert(self.unresolved.swapRemove(global_index));
-                continue :loop;
-            }
-
-            next_sym += 1;
-        }
-    }
-
-    fn resolveSymbolsAtLoading(self: *Zld) !void {
-        const is_lib = self.options.output_mode == .Lib;
-        const is_dyn_lib = self.options.link_mode == .Dynamic and is_lib;
-        const allow_undef = is_dyn_lib and (self.options.allow_shlib_undefined orelse false);
-
-        var next_sym: usize = 0;
-        while (next_sym < self.unresolved.count()) {
-            const global_index = self.unresolved.keys()[next_sym];
-            const global = self.globals.items[global_index];
-            const sym = self.getSymbolPtr(global);
-
-            if (sym.discarded()) {
-                sym.* = .{
-                    .n_strx = 0,
-                    .n_type = macho.N_UNDF,
-                    .n_sect = 0,
-                    .n_desc = 0,
-                    .n_value = 0,
-                };
-                _ = self.unresolved.swapRemove(global_index);
-                continue;
-            } else if (allow_undef) {
-                const n_desc = @as(
-                    u16,
-                    @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP * @as(i16, @intCast(macho.N_SYMBOL_RESOLVER))),
-                );
-                sym.n_type = macho.N_EXT;
-                sym.n_desc = n_desc;
-                _ = self.unresolved.swapRemove(global_index);
-                continue;
-            }
-
-            next_sym += 1;
-        }
-    }
-
-    fn createMhExecuteHeaderSymbol(self: *Zld) !void {
-        if (self.options.output_mode != .Exe) return;
-
-        const gpa = self.gpa;
-        const sym_index = try self.allocateSymbol();
-        const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
-        const sym = self.getSymbolPtr(sym_loc);
-        sym.* = .{
-            .n_strx = try self.strtab.insert(gpa, "__mh_execute_header"),
-            .n_type = macho.N_SECT | macho.N_EXT,
-            .n_sect = 0,
-            .n_desc = macho.REFERENCED_DYNAMICALLY,
-            .n_value = 0,
-        };
-
-        const gop = try self.getOrPutGlobalPtr("__mh_execute_header");
-        if (gop.found_existing) {
-            const global = gop.value_ptr.*;
-            if (global.getFile()) |file| {
-                const global_object = &self.objects.items[file];
-                global_object.globals_lookup[global.sym_index] = self.getGlobalIndex("__mh_execute_header").?;
-            }
-        }
-        gop.value_ptr.* = sym_loc;
-    }
-
-    fn createDsoHandleSymbol(self: *Zld) !void {
-        const global = self.getGlobalPtr("___dso_handle") orelse return;
-        if (!self.getSymbol(global.*).undf()) return;
-
-        const sym_index = try self.allocateSymbol();
-        const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
-        const sym = self.getSymbolPtr(sym_loc);
-        sym.* = .{
-            .n_strx = try self.strtab.insert(self.gpa, "___dso_handle"),
-            .n_type = macho.N_SECT | macho.N_EXT,
-            .n_sect = 0,
-            .n_desc = macho.N_WEAK_DEF,
-            .n_value = 0,
-        };
-        const global_index = self.getGlobalIndex("___dso_handle").?;
-        if (global.getFile()) |file| {
-            const global_object = &self.objects.items[file];
-            global_object.globals_lookup[global.sym_index] = global_index;
-        }
-        global.* = sym_loc;
-        _ = self.unresolved.swapRemove(global_index);
-    }
-
-    pub fn deinit(self: *Zld) void {
-        const gpa = self.gpa;
-
-        self.tlv_ptr_table.deinit(gpa);
-        self.got_table.deinit(gpa);
-        self.stubs_table.deinit(gpa);
-        self.thunk_table.deinit(gpa);
-
-        for (self.thunks.items) |*thunk| {
-            thunk.deinit(gpa);
-        }
-        self.thunks.deinit(gpa);
-
-        self.strtab.deinit(gpa);
-        self.locals.deinit(gpa);
-        self.globals.deinit(gpa);
-        self.resolver.deinit(gpa);
-        self.unresolved.deinit(gpa);
-        self.locals_free_list.deinit(gpa);
-        self.globals_free_list.deinit(gpa);
-
-        for (self.objects.items) |*object| {
-            object.deinit(gpa);
-        }
-        self.objects.deinit(gpa);
-        for (self.archives.items) |*archive| {
-            archive.deinit(gpa);
-        }
-        self.archives.deinit(gpa);
-        for (self.dylibs.items) |*dylib| {
-            dylib.deinit(gpa);
-        }
-        self.dylibs.deinit(gpa);
-        self.dylibs_map.deinit(gpa);
-        self.referenced_dylibs.deinit(gpa);
-
-        self.segments.deinit(gpa);
-        self.sections.deinit(gpa);
-        self.atoms.deinit(gpa);
-    }
-
-    fn createSegments(self: *Zld) !void {
-        const pagezero_vmsize = self.options.pagezero_size orelse MachO.default_pagezero_vmsize;
-        const page_size = MachO.getPageSize(self.options.target.cpu.arch);
-        const aligned_pagezero_vmsize = mem.alignBackward(u64, pagezero_vmsize, page_size);
-        if (self.options.output_mode != .Lib and aligned_pagezero_vmsize > 0) {
-            if (aligned_pagezero_vmsize != pagezero_vmsize) {
-                log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_vmsize});
-                log.warn("  rounding down to 0x{x}", .{aligned_pagezero_vmsize});
-            }
-            self.pagezero_segment_cmd_index = @intCast(self.segments.items.len);
-            try self.segments.append(self.gpa, .{
-                .cmdsize = @sizeOf(macho.segment_command_64),
-                .segname = makeStaticString("__PAGEZERO"),
-                .vmsize = aligned_pagezero_vmsize,
-            });
-        }
-
-        // __TEXT segment is non-optional
-        {
-            const protection = MachO.getSegmentMemoryProtection("__TEXT");
-            self.text_segment_cmd_index = @intCast(self.segments.items.len);
-            self.header_segment_cmd_index = self.text_segment_cmd_index.?;
-            try self.segments.append(self.gpa, .{
-                .cmdsize = @sizeOf(macho.segment_command_64),
-                .segname = makeStaticString("__TEXT"),
-                .maxprot = protection,
-                .initprot = protection,
-            });
-        }
-
-        for (self.sections.items(.header), 0..) |header, sect_id| {
-            if (header.size == 0) continue; // empty section
-
-            const segname = header.segName();
-            const segment_id = self.getSegmentByName(segname) orelse blk: {
-                log.debug("creating segment '{s}'", .{segname});
-                const segment_id = @as(u8, @intCast(self.segments.items.len));
-                const protection = MachO.getSegmentMemoryProtection(segname);
-                try self.segments.append(self.gpa, .{
-                    .cmdsize = @sizeOf(macho.segment_command_64),
-                    .segname = makeStaticString(segname),
-                    .maxprot = protection,
-                    .initprot = protection,
-                });
-                break :blk segment_id;
-            };
-            const segment = &self.segments.items[segment_id];
-            segment.cmdsize += @sizeOf(macho.section_64);
-            segment.nsects += 1;
-            self.sections.items(.segment_index)[sect_id] = segment_id;
-        }
-
-        if (self.getSegmentByName("__DATA_CONST")) |index| {
-            self.data_const_segment_cmd_index = index;
-        }
-
-        if (self.getSegmentByName("__DATA")) |index| {
-            self.data_segment_cmd_index = index;
-        }
-
-        // __LINKEDIT always comes last
-        {
-            const protection = MachO.getSegmentMemoryProtection("__LINKEDIT");
-            self.linkedit_segment_cmd_index = @intCast(self.segments.items.len);
-            try self.segments.append(self.gpa, .{
-                .cmdsize = @sizeOf(macho.segment_command_64),
-                .segname = makeStaticString("__LINKEDIT"),
-                .maxprot = protection,
-                .initprot = protection,
-            });
-        }
-    }
-
-    pub fn allocateSymbol(self: *Zld) !u32 {
-        try self.locals.ensureUnusedCapacity(self.gpa, 1);
-        log.debug("  (allocating symbol index {d})", .{self.locals.items.len});
-        const index = @as(u32, @intCast(self.locals.items.len));
-        _ = self.locals.addOneAssumeCapacity();
-        self.locals.items[index] = .{
-            .n_strx = 0,
-            .n_type = 0,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        };
-        return index;
-    }
-
-    fn allocateGlobal(self: *Zld) !u32 {
-        try self.globals.ensureUnusedCapacity(self.gpa, 1);
-
-        const index = blk: {
-            if (self.globals_free_list.popOrNull()) |index| {
-                log.debug("  (reusing global index {d})", .{index});
-                break :blk index;
-            } else {
-                log.debug("  (allocating symbol index {d})", .{self.globals.items.len});
-                const index = @as(u32, @intCast(self.globals.items.len));
-                _ = self.globals.addOneAssumeCapacity();
-                break :blk index;
-            }
-        };
-
-        self.globals.items[index] = .{ .sym_index = 0 };
-
-        return index;
-    }
-
-    pub fn addGotEntry(self: *Zld, target: SymbolWithLoc) !void {
-        if (self.got_table.lookup.contains(target)) return;
-        _ = try self.got_table.allocateEntry(self.gpa, target);
-        if (self.got_section_index == null) {
-            self.got_section_index = try MachO.initSection(self.gpa, self, "__DATA_CONST", "__got", .{
-                .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
-            });
-        }
-    }
-
-    pub fn addTlvPtrEntry(self: *Zld, target: SymbolWithLoc) !void {
-        if (self.tlv_ptr_table.lookup.contains(target)) return;
-        _ = try self.tlv_ptr_table.allocateEntry(self.gpa, target);
-        if (self.tlv_ptr_section_index == null) {
-            self.tlv_ptr_section_index = try MachO.initSection(self.gpa, self, "__DATA", "__thread_ptrs", .{
-                .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
-            });
-        }
-    }
-
-    pub fn addStubEntry(self: *Zld, target: SymbolWithLoc) !void {
-        if (self.stubs_table.lookup.contains(target)) return;
-        _ = try self.stubs_table.allocateEntry(self.gpa, target);
-        if (self.stubs_section_index == null) {
-            self.stubs_section_index = try MachO.initSection(self.gpa, self, "__TEXT", "__stubs", .{
-                .flags = macho.S_SYMBOL_STUBS |
-                    macho.S_ATTR_PURE_INSTRUCTIONS |
-                    macho.S_ATTR_SOME_INSTRUCTIONS,
-                .reserved2 = stubs.stubSize(self.options.target.cpu.arch),
-            });
-            self.stub_helper_section_index = try MachO.initSection(self.gpa, self, "__TEXT", "__stub_helper", .{
-                .flags = macho.S_REGULAR |
-                    macho.S_ATTR_PURE_INSTRUCTIONS |
-                    macho.S_ATTR_SOME_INSTRUCTIONS,
-            });
-            self.la_symbol_ptr_section_index = try MachO.initSection(self.gpa, self, "__DATA", "__la_symbol_ptr", .{
-                .flags = macho.S_LAZY_SYMBOL_POINTERS,
-            });
-        }
-    }
-
-    fn writeAtoms(self: *Zld) !void {
-        const gpa = self.gpa;
-        const slice = self.sections.slice();
-
-        for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
-            const header = slice.items(.header)[sect_id];
-            if (header.isZerofill()) continue;
-
-            var atom_index = first_atom_index orelse continue;
-
-            var buffer = try gpa.alloc(u8, math.cast(usize, header.size) orelse return error.Overflow);
-            defer gpa.free(buffer);
-            @memset(buffer, 0); // TODO with NOPs
-
-            log.debug("writing atoms in {s},{s}", .{ header.segName(), header.sectName() });
-
-            while (true) {
-                const atom = self.getAtom(atom_index);
-                if (atom.getFile()) |file| {
-                    const this_sym = self.getSymbol(atom.getSymbolWithLoc());
-                    const padding_size: usize = if (atom.next_index) |next_index| blk: {
-                        const next_sym = self.getSymbol(self.getAtom(next_index).getSymbolWithLoc());
-                        const size = next_sym.n_value - (this_sym.n_value + atom.size);
-                        break :blk math.cast(usize, size) orelse return error.Overflow;
-                    } else 0;
-
-                    log.debug("  (adding ATOM(%{d}, '{s}') from object({d}) to buffer)", .{
-                        atom.sym_index,
-                        self.getSymbolName(atom.getSymbolWithLoc()),
-                        file,
-                    });
-                    if (padding_size > 0) {
-                        log.debug("    (with padding {x})", .{padding_size});
-                    }
-
-                    const offset = this_sym.n_value - header.addr;
-                    log.debug("  (at offset 0x{x})", .{offset});
-
-                    const code = Atom.getAtomCode(self, atom_index);
-                    const relocs = Atom.getAtomRelocs(self, atom_index);
-                    const size = math.cast(usize, atom.size) orelse return error.Overflow;
-                    @memcpy(buffer[offset .. offset + size], code);
-                    try Atom.resolveRelocs(
-                        self,
-                        atom_index,
-                        buffer[offset..][0..size],
-                        relocs,
-                    );
-                }
-
-                if (atom.next_index) |next_index| {
-                    atom_index = next_index;
-                } else break;
-            }
-
-            log.debug("  (writing at file offset 0x{x})", .{header.offset});
-            try self.file.pwriteAll(buffer, header.offset);
-        }
-    }
-
-    fn writeDyldPrivateAtom(self: *Zld) !void {
-        const atom_index = self.dyld_private_atom_index orelse return;
-        const atom = self.getAtom(atom_index);
-        const sym = self.getSymbol(atom.getSymbolWithLoc());
-        const sect_id = self.data_section_index.?;
-        const header = self.sections.items(.header)[sect_id];
-        const offset = sym.n_value - header.addr + header.offset;
-        log.debug("writing __dyld_private at offset 0x{x}", .{offset});
-        const buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
-        try self.file.pwriteAll(&buffer, offset);
-    }
-
-    fn writeThunks(self: *Zld) !void {
-        assert(self.requiresThunks());
-        const gpa = self.gpa;
-
-        const sect_id = self.text_section_index orelse return;
-        const header = self.sections.items(.header)[sect_id];
-
-        for (self.thunks.items, 0..) |*thunk, i| {
-            if (thunk.getSize() == 0) continue;
-            var buffer = try std.ArrayList(u8).initCapacity(gpa, thunk.getSize());
-            defer buffer.deinit();
-            try thunks.writeThunkCode(self, thunk, buffer.writer());
-            const thunk_atom = self.getAtom(thunk.getStartAtomIndex());
-            const thunk_sym = self.getSymbol(thunk_atom.getSymbolWithLoc());
-            const offset = thunk_sym.n_value - header.addr + header.offset;
-            log.debug("writing thunk({d}) at offset 0x{x}", .{ i, offset });
-            try self.file.pwriteAll(buffer.items, offset);
-        }
-    }
-
-    fn writePointerEntries(self: *Zld, sect_id: u8, table: anytype) !void {
-        const header = self.sections.items(.header)[sect_id];
-        var buffer = try std.ArrayList(u8).initCapacity(self.gpa, header.size);
-        defer buffer.deinit();
-        for (table.entries.items) |entry| {
-            const sym = self.getSymbol(entry);
-            buffer.writer().writeIntLittle(u64, sym.n_value) catch unreachable;
-        }
-        log.debug("writing __DATA_CONST,__got contents at file offset 0x{x}", .{header.offset});
-        try self.file.pwriteAll(buffer.items, header.offset);
-    }
-
-    fn writeStubs(self: *Zld) !void {
-        const gpa = self.gpa;
-        const cpu_arch = self.options.target.cpu.arch;
-        const stubs_header = self.sections.items(.header)[self.stubs_section_index.?];
-        const la_symbol_ptr_header = self.sections.items(.header)[self.la_symbol_ptr_section_index.?];
-
-        var buffer = try std.ArrayList(u8).initCapacity(gpa, stubs_header.size);
-        defer buffer.deinit();
-
-        for (0..self.stubs_table.count()) |index| {
-            try stubs.writeStubCode(.{
-                .cpu_arch = cpu_arch,
-                .source_addr = stubs_header.addr + stubs.stubSize(cpu_arch) * index,
-                .target_addr = la_symbol_ptr_header.addr + index * @sizeOf(u64),
-            }, buffer.writer());
-        }
-
-        log.debug("writing __TEXT,__stubs contents at file offset 0x{x}", .{stubs_header.offset});
-        try self.file.pwriteAll(buffer.items, stubs_header.offset);
-    }
-
-    fn writeStubHelpers(self: *Zld) !void {
-        const gpa = self.gpa;
-        const cpu_arch = self.options.target.cpu.arch;
-        const stub_helper_header = self.sections.items(.header)[self.stub_helper_section_index.?];
-
-        var buffer = try std.ArrayList(u8).initCapacity(gpa, stub_helper_header.size);
-        defer buffer.deinit();
-
-        {
-            const dyld_private_addr = blk: {
-                const atom = self.getAtom(self.dyld_private_atom_index.?);
-                const sym = self.getSymbol(atom.getSymbolWithLoc());
-                break :blk sym.n_value;
-            };
-            const dyld_stub_binder_got_addr = blk: {
-                const sym_loc = self.globals.items[self.dyld_stub_binder_index.?];
-                break :blk self.getGotEntryAddress(sym_loc).?;
-            };
-            try stubs.writeStubHelperPreambleCode(.{
-                .cpu_arch = cpu_arch,
-                .source_addr = stub_helper_header.addr,
-                .dyld_private_addr = dyld_private_addr,
-                .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr,
-            }, buffer.writer());
-        }
-
-        for (0..self.stubs_table.count()) |index| {
-            const source_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
-                stubs.stubHelperSize(cpu_arch) * index;
-            try stubs.writeStubHelperCode(.{
-                .cpu_arch = cpu_arch,
-                .source_addr = source_addr,
-                .target_addr = stub_helper_header.addr,
-            }, buffer.writer());
-        }
-
-        log.debug("writing __TEXT,__stub_helper contents at file offset 0x{x}", .{
-            stub_helper_header.offset,
-        });
-        try self.file.pwriteAll(buffer.items, stub_helper_header.offset);
-    }
-
-    fn writeLaSymbolPtrs(self: *Zld) !void {
-        const gpa = self.gpa;
-        const cpu_arch = self.options.target.cpu.arch;
-        const la_symbol_ptr_header = self.sections.items(.header)[self.la_symbol_ptr_section_index.?];
-        const stub_helper_header = self.sections.items(.header)[self.stub_helper_section_index.?];
-
-        var buffer = try std.ArrayList(u8).initCapacity(gpa, la_symbol_ptr_header.size);
-        defer buffer.deinit();
-
-        for (0..self.stubs_table.count()) |index| {
-            const target_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
-                stubs.stubHelperSize(cpu_arch) * index;
-            buffer.writer().writeIntLittle(u64, target_addr) catch unreachable;
-        }
-
-        log.debug("writing __DATA,__la_symbol_ptr contents at file offset 0x{x}", .{
-            la_symbol_ptr_header.offset,
-        });
-        try self.file.pwriteAll(buffer.items, la_symbol_ptr_header.offset);
-    }
-
-    fn pruneAndSortSections(self: *Zld) !void {
-        const Entry = struct {
-            index: u8,
-
-            pub fn lessThan(zld: *Zld, lhs: @This(), rhs: @This()) bool {
-                const lhs_header = zld.sections.items(.header)[lhs.index];
-                const rhs_header = zld.sections.items(.header)[rhs.index];
-                return MachO.getSectionPrecedence(lhs_header) < MachO.getSectionPrecedence(rhs_header);
-            }
-        };
-
-        const gpa = self.gpa;
-
-        var entries = try std.ArrayList(Entry).initCapacity(gpa, self.sections.slice().len);
-        defer entries.deinit();
-
-        for (0..self.sections.slice().len) |index| {
-            const section = self.sections.get(index);
-            if (section.header.size == 0) {
-                log.debug("pruning section {s},{s} {?d}", .{
-                    section.header.segName(),
-                    section.header.sectName(),
-                    section.first_atom_index,
-                });
-                for (&[_]*?u8{
-                    &self.text_section_index,
-                    &self.data_const_section_index,
-                    &self.data_section_index,
-                    &self.bss_section_index,
-                    &self.thread_vars_section_index,
-                    &self.thread_data_section_index,
-                    &self.thread_bss_section_index,
-                    &self.eh_frame_section_index,
-                    &self.unwind_info_section_index,
-                    &self.got_section_index,
-                    &self.tlv_ptr_section_index,
-                    &self.stubs_section_index,
-                    &self.stub_helper_section_index,
-                    &self.la_symbol_ptr_section_index,
-                }) |maybe_index| {
-                    if (maybe_index.* != null and maybe_index.*.? == index) {
-                        maybe_index.* = null;
-                    }
-                }
-                continue;
-            }
-            entries.appendAssumeCapacity(.{ .index = @intCast(index) });
-        }
-
-        mem.sort(Entry, entries.items, self, Entry.lessThan);
-
-        var slice = self.sections.toOwnedSlice();
-        defer slice.deinit(gpa);
-
-        const backlinks = try gpa.alloc(u8, slice.len);
-        defer gpa.free(backlinks);
-        for (entries.items, 0..) |entry, i| {
-            backlinks[entry.index] = @as(u8, @intCast(i));
-        }
-
-        try self.sections.ensureTotalCapacity(gpa, entries.items.len);
-        for (entries.items) |entry| {
-            self.sections.appendAssumeCapacity(slice.get(entry.index));
-        }
-
-        for (&[_]*?u8{
-            &self.text_section_index,
-            &self.data_const_section_index,
-            &self.data_section_index,
-            &self.bss_section_index,
-            &self.thread_vars_section_index,
-            &self.thread_data_section_index,
-            &self.thread_bss_section_index,
-            &self.eh_frame_section_index,
-            &self.unwind_info_section_index,
-            &self.got_section_index,
-            &self.tlv_ptr_section_index,
-            &self.stubs_section_index,
-            &self.stub_helper_section_index,
-            &self.la_symbol_ptr_section_index,
-        }) |maybe_index| {
-            if (maybe_index.*) |*index| {
-                index.* = backlinks[index.*];
-            }
-        }
-    }
-
-    fn calcSectionSizes(self: *Zld) !void {
-        const slice = self.sections.slice();
-        for (slice.items(.header), 0..) |*header, sect_id| {
-            if (header.size == 0) continue;
-            if (self.text_section_index) |txt| {
-                if (txt == sect_id and self.requiresThunks()) continue;
-            }
-
-            var atom_index = slice.items(.first_atom_index)[sect_id] orelse continue;
-
-            header.size = 0;
-            header.@"align" = 0;
-
-            while (true) {
-                const atom = self.getAtom(atom_index);
-                const atom_alignment = try math.powi(u32, 2, atom.alignment);
-                const atom_offset = mem.alignForward(u64, header.size, atom_alignment);
-                const padding = atom_offset - header.size;
-
-                const sym = self.getSymbolPtr(atom.getSymbolWithLoc());
-                sym.n_value = atom_offset;
-
-                header.size += padding + atom.size;
-                header.@"align" = @max(header.@"align", atom.alignment);
-
-                if (atom.next_index) |next_index| {
-                    atom_index = next_index;
-                } else break;
-            }
-        }
-
-        if (self.text_section_index != null and self.requiresThunks()) {
-            // Create jump/branch range extenders if needed.
-            try thunks.createThunks(self, self.text_section_index.?);
-        }
-
-        // Update offsets of all symbols contained within each Atom.
-        // We need to do this since our unwind info synthesiser relies on
-        // traversing the symbols when synthesising unwind info and DWARF CFI records.
-        for (slice.items(.first_atom_index)) |first_atom_index| {
-            var atom_index = first_atom_index orelse continue;
-
-            while (true) {
-                const atom = self.getAtom(atom_index);
-                const sym = self.getSymbol(atom.getSymbolWithLoc());
-
-                if (atom.getFile() != null) {
-                    // Update each symbol contained within the atom
-                    var it = Atom.getInnerSymbolsIterator(self, atom_index);
-                    while (it.next()) |sym_loc| {
-                        const inner_sym = self.getSymbolPtr(sym_loc);
-                        inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
-                            self,
-                            atom_index,
-                            sym_loc.sym_index,
-                        );
-                    }
-
-                    // If there is a section alias, update it now too
-                    if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
-                        const alias = self.getSymbolPtr(sym_loc);
-                        alias.n_value = sym.n_value;
-                    }
-                }
-
-                if (atom.next_index) |next_index| {
-                    atom_index = next_index;
-                } else break;
-            }
-        }
-
-        if (self.got_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.size = self.got_table.count() * @sizeOf(u64);
-            header.@"align" = 3;
-        }
-
-        if (self.tlv_ptr_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.size = self.tlv_ptr_table.count() * @sizeOf(u64);
-            header.@"align" = 3;
-        }
-
-        const cpu_arch = self.options.target.cpu.arch;
-
-        if (self.stubs_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.size = self.stubs_table.count() * stubs.stubSize(cpu_arch);
-            header.@"align" = stubs.stubAlignment(cpu_arch);
-        }
-
-        if (self.stub_helper_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.size = self.stubs_table.count() * stubs.stubHelperSize(cpu_arch) +
-                stubs.stubHelperPreambleSize(cpu_arch);
-            header.@"align" = stubs.stubAlignment(cpu_arch);
-        }
-
-        if (self.la_symbol_ptr_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.size = self.stubs_table.count() * @sizeOf(u64);
-            header.@"align" = 3;
-        }
-    }
-
-    fn allocateSegments(self: *Zld) !void {
-        for (self.segments.items, 0..) |*segment, segment_index| {
-            const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT");
-            const base_size = if (is_text_segment) try load_commands.calcMinHeaderPad(self.gpa, self.options, .{
-                .segments = self.segments.items,
-                .dylibs = self.dylibs.items,
-                .referenced_dylibs = self.referenced_dylibs.keys(),
-            }) else 0;
-            try self.allocateSegment(@as(u8, @intCast(segment_index)), base_size);
-        }
-    }
-
-    fn getSegmentAllocBase(self: Zld, segment_index: u8) struct { vmaddr: u64, fileoff: u64 } {
-        if (segment_index > 0) {
-            const prev_segment = self.segments.items[segment_index - 1];
-            return .{
-                .vmaddr = prev_segment.vmaddr + prev_segment.vmsize,
-                .fileoff = prev_segment.fileoff + prev_segment.filesize,
-            };
-        }
-        return .{ .vmaddr = 0, .fileoff = 0 };
-    }
-
-    fn allocateSegment(self: *Zld, segment_index: u8, init_size: u64) !void {
-        const segment = &self.segments.items[segment_index];
-
-        if (mem.eql(u8, segment.segName(), "__PAGEZERO")) return; // allocated upon creation
-
-        const base = self.getSegmentAllocBase(segment_index);
-        segment.vmaddr = base.vmaddr;
-        segment.fileoff = base.fileoff;
-        segment.filesize = init_size;
-        segment.vmsize = init_size;
-
-        // Allocate the sections according to their alignment at the beginning of the segment.
-        const indexes = self.getSectionIndexes(segment_index);
-        var start = init_size;
-
-        const slice = self.sections.slice();
-        for (slice.items(.header)[indexes.start..indexes.end], 0..) |*header, sect_id| {
-            const alignment = try math.powi(u32, 2, header.@"align");
-            const start_aligned = mem.alignForward(u64, start, alignment);
-            const n_sect = @as(u8, @intCast(indexes.start + sect_id + 1));
-
-            header.offset = if (header.isZerofill())
-                0
-            else
-                @as(u32, @intCast(segment.fileoff + start_aligned));
-            header.addr = segment.vmaddr + start_aligned;
-
-            if (slice.items(.first_atom_index)[indexes.start + sect_id]) |first_atom_index| {
-                var atom_index = first_atom_index;
-
-                log.debug("allocating local symbols in sect({d}, '{s},{s}')", .{
-                    n_sect,
-                    header.segName(),
-                    header.sectName(),
-                });
-
-                while (true) {
-                    const atom = self.getAtom(atom_index);
-                    const sym = self.getSymbolPtr(atom.getSymbolWithLoc());
-                    sym.n_value += header.addr;
-                    sym.n_sect = n_sect;
-
-                    log.debug("  ATOM(%{d}, '{s}') @{x}", .{
-                        atom.sym_index,
-                        self.getSymbolName(atom.getSymbolWithLoc()),
-                        sym.n_value,
-                    });
-
-                    if (atom.getFile() != null) {
-                        // Update each symbol contained within the atom
-                        var it = Atom.getInnerSymbolsIterator(self, atom_index);
-                        while (it.next()) |sym_loc| {
-                            const inner_sym = self.getSymbolPtr(sym_loc);
-                            inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
-                                self,
-                                atom_index,
-                                sym_loc.sym_index,
-                            );
-                            inner_sym.n_sect = n_sect;
-                        }
-
-                        // If there is a section alias, update it now too
-                        if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
-                            const alias = self.getSymbolPtr(sym_loc);
-                            alias.n_value = sym.n_value;
-                            alias.n_sect = n_sect;
-                        }
-                    }
-
-                    if (atom.next_index) |next_index| {
-                        atom_index = next_index;
-                    } else break;
-                }
-            }
-
-            start = start_aligned + header.size;
-
-            if (!header.isZerofill()) {
-                segment.filesize = start;
-            }
-            segment.vmsize = start;
-        }
-
-        const page_size = MachO.getPageSize(self.options.target.cpu.arch);
-        segment.filesize = mem.alignForward(u64, segment.filesize, page_size);
-        segment.vmsize = mem.alignForward(u64, segment.vmsize, page_size);
-    }
-
-    fn writeLinkeditSegmentData(self: *Zld) !void {
-        const page_size = MachO.getPageSize(self.options.target.cpu.arch);
-        const seg = self.getLinkeditSegmentPtr();
-        seg.filesize = 0;
-        seg.vmsize = 0;
-
-        for (self.segments.items, 0..) |segment, id| {
-            if (self.linkedit_segment_cmd_index.? == @as(u8, @intCast(id))) continue;
-            if (seg.vmaddr < segment.vmaddr + segment.vmsize) {
-                seg.vmaddr = mem.alignForward(u64, segment.vmaddr + segment.vmsize, page_size);
-            }
-            if (seg.fileoff < segment.fileoff + segment.filesize) {
-                seg.fileoff = mem.alignForward(u64, segment.fileoff + segment.filesize, page_size);
-            }
-        }
-        try self.writeDyldInfoData();
-        try self.writeFunctionStarts();
-        try self.writeDataInCode();
-        try self.writeSymtabs();
-
-        seg.vmsize = mem.alignForward(u64, seg.filesize, page_size);
-    }
-
-    fn collectRebaseData(self: *Zld, rebase: *Rebase) !void {
-        log.debug("collecting rebase data", .{});
-
-        // First, unpack GOT entries
-        if (self.got_section_index) |sect_id| {
-            try MachO.collectRebaseDataFromTableSection(self.gpa, self, sect_id, rebase, self.got_table);
-        }
-
-        // Next, unpack __la_symbol_ptr entries
-        if (self.la_symbol_ptr_section_index) |sect_id| {
-            try MachO.collectRebaseDataFromTableSection(self.gpa, self, sect_id, rebase, self.stubs_table);
-        }
-
-        // Finally, unpack the rest.
-        const cpu_arch = self.options.target.cpu.arch;
-        for (self.objects.items) |*object| {
-            for (object.atoms.items) |atom_index| {
-                const atom = self.getAtom(atom_index);
-                const sym = self.getSymbol(atom.getSymbolWithLoc());
-                if (sym.n_desc == MachO.N_DEAD) continue;
-
-                const sect_id = sym.n_sect - 1;
-                const section = self.sections.items(.header)[sect_id];
-                const segment_id = self.sections.items(.segment_index)[sect_id];
-                const segment = self.segments.items[segment_id];
-                if (segment.maxprot & macho.PROT.WRITE == 0) continue;
-                switch (section.type()) {
-                    macho.S_LITERAL_POINTERS,
-                    macho.S_REGULAR,
-                    macho.S_MOD_INIT_FUNC_POINTERS,
-                    macho.S_MOD_TERM_FUNC_POINTERS,
-                    => {},
-                    else => continue,
-                }
-
-                log.debug("  ATOM({d}, %{d}, '{s}')", .{
-                    atom_index,
-                    atom.sym_index,
-                    self.getSymbolName(atom.getSymbolWithLoc()),
-                });
-
-                const code = Atom.getAtomCode(self, atom_index);
-                const relocs = Atom.getAtomRelocs(self, atom_index);
-                const ctx = Atom.getRelocContext(self, atom_index);
-
-                for (relocs) |rel| {
-                    switch (cpu_arch) {
-                        .aarch64 => {
-                            const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
-                            if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
-                            if (rel.r_length != 3) continue;
-                        },
-                        .x86_64 => {
-                            const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
-                            if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
-                            if (rel.r_length != 3) continue;
-                        },
-                        else => unreachable,
-                    }
-                    const target = Atom.parseRelocTarget(self, .{
-                        .object_id = atom.getFile().?,
-                        .rel = rel,
-                        .code = code,
-                        .base_offset = ctx.base_offset,
-                        .base_addr = ctx.base_addr,
-                    });
-                    const target_sym = self.getSymbol(target);
-                    if (target_sym.undf()) continue;
-
-                    const base_offset = @as(i32, @intCast(sym.n_value - segment.vmaddr));
-                    const rel_offset = rel.r_address - ctx.base_offset;
-                    const offset = @as(u64, @intCast(base_offset + rel_offset));
-                    log.debug("    | rebase at {x}", .{offset});
-
-                    try rebase.entries.append(self.gpa, .{
-                        .offset = offset,
-                        .segment_id = segment_id,
-                    });
-                }
-            }
-        }
-
-        try rebase.finalize(self.gpa);
-    }
-
-    fn collectBindData(
-        self: *Zld,
-        bind: *Bind,
-    ) !void {
-        log.debug("collecting bind data", .{});
-
-        // First, unpack GOT section
-        if (self.got_section_index) |sect_id| {
-            try MachO.collectBindDataFromTableSection(self.gpa, self, sect_id, bind, self.got_table);
-        }
-
-        // Next, unpack TLV pointers section
-        if (self.tlv_ptr_section_index) |sect_id| {
-            try MachO.collectBindDataFromTableSection(self.gpa, self, sect_id, bind, self.tlv_ptr_table);
-        }
-
-        // Finally, unpack the rest.
-        const cpu_arch = self.options.target.cpu.arch;
-        for (self.objects.items) |*object| {
-            for (object.atoms.items) |atom_index| {
-                const atom = self.getAtom(atom_index);
-                const sym = self.getSymbol(atom.getSymbolWithLoc());
-                if (sym.n_desc == MachO.N_DEAD) continue;
-
-                const sect_id = sym.n_sect - 1;
-                const section = self.sections.items(.header)[sect_id];
-                const segment_id = self.sections.items(.segment_index)[sect_id];
-                const segment = self.segments.items[segment_id];
-                if (segment.maxprot & macho.PROT.WRITE == 0) continue;
-                switch (section.type()) {
-                    macho.S_LITERAL_POINTERS,
-                    macho.S_REGULAR,
-                    macho.S_MOD_INIT_FUNC_POINTERS,
-                    macho.S_MOD_TERM_FUNC_POINTERS,
-                    => {},
-                    else => continue,
-                }
-
-                log.debug("  ATOM({d}, %{d}, '{s}')", .{
-                    atom_index,
-                    atom.sym_index,
-                    self.getSymbolName(atom.getSymbolWithLoc()),
-                });
-
-                const code = Atom.getAtomCode(self, atom_index);
-                const relocs = Atom.getAtomRelocs(self, atom_index);
-                const ctx = Atom.getRelocContext(self, atom_index);
-
-                for (relocs) |rel| {
-                    switch (cpu_arch) {
-                        .aarch64 => {
-                            const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
-                            if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
-                            if (rel.r_length != 3) continue;
-                        },
-                        .x86_64 => {
-                            const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
-                            if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
-                            if (rel.r_length != 3) continue;
-                        },
-                        else => unreachable,
-                    }
-
-                    const global = Atom.parseRelocTarget(self, .{
-                        .object_id = atom.getFile().?,
-                        .rel = rel,
-                        .code = code,
-                        .base_offset = ctx.base_offset,
-                        .base_addr = ctx.base_addr,
-                    });
-                    const bind_sym_name = self.getSymbolName(global);
-                    const bind_sym = self.getSymbol(global);
-                    if (!bind_sym.undf()) continue;
-
-                    const base_offset = sym.n_value - segment.vmaddr;
-                    const rel_offset = @as(u32, @intCast(rel.r_address - ctx.base_offset));
-                    const offset = @as(u64, @intCast(base_offset + rel_offset));
-                    const addend = mem.readIntLittle(i64, code[rel_offset..][0..8]);
-
-                    const dylib_ordinal = @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER);
-                    log.debug("    | bind at {x}, import('{s}') in dylib({d})", .{
-                        base_offset,
-                        bind_sym_name,
-                        dylib_ordinal,
-                    });
-                    log.debug("    | with addend {x}", .{addend});
-                    if (bind_sym.weakRef()) {
-                        log.debug("    | marking as weak ref ", .{});
-                    }
-                    try bind.entries.append(self.gpa, .{
-                        .target = global,
-                        .offset = offset,
-                        .segment_id = segment_id,
-                        .addend = addend,
-                    });
-                }
-            }
-        }
-
-        try bind.finalize(self.gpa, self);
-    }
-
-    fn collectLazyBindData(self: *Zld, lazy_bind: *LazyBind) !void {
-        const sect_id = self.la_symbol_ptr_section_index orelse return;
-        try MachO.collectBindDataFromTableSection(self.gpa, self, sect_id, lazy_bind, self.stubs_table);
-        try lazy_bind.finalize(self.gpa, self);
-    }
-
-    fn collectExportData(self: *Zld, trie: *Trie) !void {
-        const gpa = self.gpa;
-
-        // TODO handle macho.EXPORT_SYMBOL_FLAGS_REEXPORT and macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER.
-        log.debug("collecting export data", .{});
-
-        const exec_segment = self.segments.items[self.header_segment_cmd_index.?];
-        const base_address = exec_segment.vmaddr;
-
-        for (self.globals.items) |global| {
-            const sym = self.getSymbol(global);
-            if (sym.undf()) continue;
-            if (sym.n_desc == MachO.N_DEAD) continue;
-
-            const sym_name = self.getSymbolName(global);
-            log.debug("  (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value });
-            try trie.put(gpa, .{
-                .name = sym_name,
-                .vmaddr_offset = sym.n_value - base_address,
-                .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
-            });
-        }
-
-        try trie.finalize(gpa);
-    }
-
-    fn writeDyldInfoData(self: *Zld) !void {
-        const gpa = self.gpa;
-
-        var rebase = Rebase{};
-        defer rebase.deinit(gpa);
-        try self.collectRebaseData(&rebase);
-
-        var bind = Bind{};
-        defer bind.deinit(gpa);
-        try self.collectBindData(&bind);
-
-        var lazy_bind = LazyBind{};
-        defer lazy_bind.deinit(gpa);
-        try self.collectLazyBindData(&lazy_bind);
-
-        var trie = Trie{};
-        defer trie.deinit(gpa);
-        try trie.init(gpa);
-        try self.collectExportData(&trie);
-
-        const link_seg = self.getLinkeditSegmentPtr();
-        assert(mem.isAlignedGeneric(u64, link_seg.fileoff, @alignOf(u64)));
-        const rebase_off = link_seg.fileoff;
-        const rebase_size = rebase.size();
-        const rebase_size_aligned = mem.alignForward(u64, rebase_size, @alignOf(u64));
-        log.debug("writing rebase info from 0x{x} to 0x{x}", .{ rebase_off, rebase_off + rebase_size_aligned });
-
-        const bind_off = rebase_off + rebase_size_aligned;
-        const bind_size = bind.size();
-        const bind_size_aligned = mem.alignForward(u64, bind_size, @alignOf(u64));
-        log.debug("writing bind info from 0x{x} to 0x{x}", .{ bind_off, bind_off + bind_size_aligned });
-
-        const lazy_bind_off = bind_off + bind_size_aligned;
-        const lazy_bind_size = lazy_bind.size();
-        const lazy_bind_size_aligned = mem.alignForward(u64, lazy_bind_size, @alignOf(u64));
-        log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{
-            lazy_bind_off,
-            lazy_bind_off + lazy_bind_size_aligned,
-        });
-
-        const export_off = lazy_bind_off + lazy_bind_size_aligned;
-        const export_size = trie.size;
-        const export_size_aligned = mem.alignForward(u64, export_size, @alignOf(u64));
-        log.debug("writing export trie from 0x{x} to 0x{x}", .{ export_off, export_off + export_size_aligned });
-
-        const needed_size = math.cast(usize, export_off + export_size_aligned - rebase_off) orelse
-            return error.Overflow;
-        link_seg.filesize = needed_size;
-        assert(mem.isAlignedGeneric(u64, link_seg.fileoff + link_seg.filesize, @alignOf(u64)));
-
-        var buffer = try gpa.alloc(u8, needed_size);
-        defer gpa.free(buffer);
-        @memset(buffer, 0);
-
-        var stream = std.io.fixedBufferStream(buffer);
-        const writer = stream.writer();
-
-        try rebase.write(writer);
-        try stream.seekTo(bind_off - rebase_off);
-
-        try bind.write(writer);
-        try stream.seekTo(lazy_bind_off - rebase_off);
-
-        try lazy_bind.write(writer);
-        try stream.seekTo(export_off - rebase_off);
-
-        _ = try trie.write(writer);
-
-        log.debug("writing dyld info from 0x{x} to 0x{x}", .{
-            rebase_off,
-            rebase_off + needed_size,
-        });
-
-        try self.file.pwriteAll(buffer, rebase_off);
-        try MachO.populateLazyBindOffsetsInStubHelper(
-            self,
-            self.options.target.cpu.arch,
-            self.file,
-            lazy_bind,
-        );
-
-        self.dyld_info_cmd.rebase_off = @as(u32, @intCast(rebase_off));
-        self.dyld_info_cmd.rebase_size = @as(u32, @intCast(rebase_size_aligned));
-        self.dyld_info_cmd.bind_off = @as(u32, @intCast(bind_off));
-        self.dyld_info_cmd.bind_size = @as(u32, @intCast(bind_size_aligned));
-        self.dyld_info_cmd.lazy_bind_off = @as(u32, @intCast(lazy_bind_off));
-        self.dyld_info_cmd.lazy_bind_size = @as(u32, @intCast(lazy_bind_size_aligned));
-        self.dyld_info_cmd.export_off = @as(u32, @intCast(export_off));
-        self.dyld_info_cmd.export_size = @as(u32, @intCast(export_size_aligned));
-    }
-
-    const asc_u64 = std.sort.asc(u64);
-
-    fn addSymbolToFunctionStarts(self: *Zld, sym_loc: SymbolWithLoc, addresses: *std.ArrayList(u64)) !void {
-        const sym = self.getSymbol(sym_loc);
-        if (sym.n_strx == 0) return;
-        if (sym.n_desc == MachO.N_DEAD) return;
-        if (self.symbolIsTemp(sym_loc)) return;
-        try addresses.append(sym.n_value);
-    }
-
-    fn writeFunctionStarts(self: *Zld) !void {
-        const gpa = self.gpa;
-        const seg = self.segments.items[self.header_segment_cmd_index.?];
-
-        // We need to sort by address first
-        var addresses = std.ArrayList(u64).init(gpa);
-        defer addresses.deinit();
-
-        for (self.objects.items) |object| {
-            for (object.exec_atoms.items) |atom_index| {
-                const atom = self.getAtom(atom_index);
-                const sym_loc = atom.getSymbolWithLoc();
-                try self.addSymbolToFunctionStarts(sym_loc, &addresses);
-
-                var it = Atom.getInnerSymbolsIterator(self, atom_index);
-                while (it.next()) |inner_sym_loc| {
-                    try self.addSymbolToFunctionStarts(inner_sym_loc, &addresses);
-                }
-            }
-        }
-
-        mem.sort(u64, addresses.items, {}, asc_u64);
-
-        var offsets = std.ArrayList(u32).init(gpa);
-        defer offsets.deinit();
-        try offsets.ensureTotalCapacityPrecise(addresses.items.len);
-
-        var last_off: u32 = 0;
-        for (addresses.items) |addr| {
-            const offset = @as(u32, @intCast(addr - seg.vmaddr));
-            const diff = offset - last_off;
-
-            if (diff == 0) continue;
-
-            offsets.appendAssumeCapacity(diff);
-            last_off = offset;
-        }
-
-        var buffer = std.ArrayList(u8).init(gpa);
-        defer buffer.deinit();
-
-        const max_size = @as(usize, @intCast(offsets.items.len * @sizeOf(u64)));
-        try buffer.ensureTotalCapacity(max_size);
-
-        for (offsets.items) |offset| {
-            try std.leb.writeULEB128(buffer.writer(), offset);
-        }
-
-        const link_seg = self.getLinkeditSegmentPtr();
-        const offset = link_seg.fileoff + link_seg.filesize;
-        assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
-        const needed_size = buffer.items.len;
-        const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
-        const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow;
-        if (padding > 0) {
-            try buffer.ensureUnusedCapacity(padding);
-            buffer.appendNTimesAssumeCapacity(0, padding);
-        }
-        link_seg.filesize = offset + needed_size_aligned - link_seg.fileoff;
-
-        log.debug("writing function starts info from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
-
-        try self.file.pwriteAll(buffer.items, offset);
-
-        self.function_starts_cmd.dataoff = @as(u32, @intCast(offset));
-        self.function_starts_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
-    }
-
-    fn filterDataInCode(
-        dices: []const macho.data_in_code_entry,
-        start_addr: u64,
-        end_addr: u64,
-    ) []const macho.data_in_code_entry {
-        const Predicate = struct {
-            addr: u64,
-
-            pub fn predicate(self: @This(), dice: macho.data_in_code_entry) bool {
-                return dice.offset >= self.addr;
-            }
-        };
-
-        const start = MachO.lsearch(macho.data_in_code_entry, dices, Predicate{ .addr = start_addr });
-        const end = MachO.lsearch(macho.data_in_code_entry, dices[start..], Predicate{ .addr = end_addr }) + start;
-
-        return dices[start..end];
-    }
-
-    fn writeDataInCode(self: *Zld) !void {
-        var out_dice = std.ArrayList(macho.data_in_code_entry).init(self.gpa);
-        defer out_dice.deinit();
-
-        const text_sect_id = self.text_section_index orelse return;
-        const text_sect_header = self.sections.items(.header)[text_sect_id];
-
-        for (self.objects.items) |object| {
-            if (!object.hasDataInCode()) continue;
-            const dice = object.data_in_code.items;
-            try out_dice.ensureUnusedCapacity(dice.len);
-
-            for (object.exec_atoms.items) |atom_index| {
-                const atom = self.getAtom(atom_index);
-                const sym = self.getSymbol(atom.getSymbolWithLoc());
-                if (sym.n_desc == MachO.N_DEAD) continue;
-
-                const source_addr = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
-                    source_sym.n_value
-                else blk: {
-                    const nbase = @as(u32, @intCast(object.in_symtab.?.len));
-                    const source_sect_id = @as(u8, @intCast(atom.sym_index - nbase));
-                    break :blk object.getSourceSection(source_sect_id).addr;
-                };
-                const filtered_dice = filterDataInCode(dice, source_addr, source_addr + atom.size);
-                const base = math.cast(u32, sym.n_value - text_sect_header.addr + text_sect_header.offset) orelse
-                    return error.Overflow;
-
-                for (filtered_dice) |single| {
-                    const offset = math.cast(u32, single.offset - source_addr + base) orelse
-                        return error.Overflow;
-                    out_dice.appendAssumeCapacity(.{
-                        .offset = offset,
-                        .length = single.length,
-                        .kind = single.kind,
-                    });
-                }
-            }
-        }
-
-        const seg = self.getLinkeditSegmentPtr();
-        const offset = seg.fileoff + seg.filesize;
-        assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
-        const needed_size = out_dice.items.len * @sizeOf(macho.data_in_code_entry);
-        const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
-        seg.filesize = offset + needed_size_aligned - seg.fileoff;
-
-        const buffer = try self.gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow);
-        defer self.gpa.free(buffer);
-        {
-            const src = mem.sliceAsBytes(out_dice.items);
-            @memcpy(buffer[0..src.len], src);
-            @memset(buffer[src.len..], 0);
-        }
-
-        log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
-
-        try self.file.pwriteAll(buffer, offset);
-
-        self.data_in_code_cmd.dataoff = @as(u32, @intCast(offset));
-        self.data_in_code_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
-    }
-
-    fn writeSymtabs(self: *Zld) !void {
-        var ctx = try self.writeSymtab();
-        defer ctx.imports_table.deinit();
-        try self.writeDysymtab(ctx);
-        try self.writeStrtab();
-    }
-
-    fn addLocalToSymtab(self: *Zld, sym_loc: SymbolWithLoc, locals: *std.ArrayList(macho.nlist_64)) !void {
-        const sym = self.getSymbol(sym_loc);
-        if (sym.n_strx == 0) return; // no name, skip
-        if (sym.n_desc == MachO.N_DEAD) return; // garbage-collected, skip
-        if (sym.ext()) return; // an export lands in its own symtab section, skip
-        if (self.symbolIsTemp(sym_loc)) return; // local temp symbol, skip
-
-        var out_sym = sym;
-        out_sym.n_strx = try self.strtab.insert(self.gpa, self.getSymbolName(sym_loc));
-        try locals.append(out_sym);
-    }
-
-    fn writeSymtab(self: *Zld) !SymtabCtx {
-        const gpa = self.gpa;
-
-        var locals = std.ArrayList(macho.nlist_64).init(gpa);
-        defer locals.deinit();
-
-        for (self.objects.items) |object| {
-            for (object.atoms.items) |atom_index| {
-                const atom = self.getAtom(atom_index);
-                const sym_loc = atom.getSymbolWithLoc();
-                try self.addLocalToSymtab(sym_loc, &locals);
-
-                var it = Atom.getInnerSymbolsIterator(self, atom_index);
-                while (it.next()) |inner_sym_loc| {
-                    try self.addLocalToSymtab(inner_sym_loc, &locals);
-                }
-            }
-        }
-
-        var exports = std.ArrayList(macho.nlist_64).init(gpa);
-        defer exports.deinit();
-
-        for (self.globals.items) |global| {
-            const sym = self.getSymbol(global);
-            if (sym.undf()) continue; // import, skip
-            if (sym.n_desc == MachO.N_DEAD) continue;
-
-            var out_sym = sym;
-            out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
-            try exports.append(out_sym);
-        }
-
-        var imports = std.ArrayList(macho.nlist_64).init(gpa);
-        defer imports.deinit();
-
-        var imports_table = std.AutoHashMap(SymbolWithLoc, u32).init(gpa);
-
-        for (self.globals.items) |global| {
-            const sym = self.getSymbol(global);
-            if (!sym.undf()) continue; // not an import, skip
-            if (sym.n_desc == MachO.N_DEAD) continue;
-
-            const new_index = @as(u32, @intCast(imports.items.len));
-            var out_sym = sym;
-            out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
-            try imports.append(out_sym);
-            try imports_table.putNoClobber(global, new_index);
-        }
-
-        // We generate stabs last in order to ensure that the strtab always has debug info
-        // strings trailing
-        if (!self.options.strip) {
-            for (self.objects.items) |object| {
-                try self.generateSymbolStabs(object, &locals);
-            }
-        }
-
-        const nlocals = @as(u32, @intCast(locals.items.len));
-        const nexports = @as(u32, @intCast(exports.items.len));
-        const nimports = @as(u32, @intCast(imports.items.len));
-        const nsyms = nlocals + nexports + nimports;
-
-        const seg = self.getLinkeditSegmentPtr();
-        const offset = seg.fileoff + seg.filesize;
-        assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
-        const needed_size = nsyms * @sizeOf(macho.nlist_64);
-        seg.filesize = offset + needed_size - seg.fileoff;
-        assert(mem.isAlignedGeneric(u64, seg.fileoff + seg.filesize, @alignOf(u64)));
-
-        var buffer = std.ArrayList(u8).init(gpa);
-        defer buffer.deinit();
-        try buffer.ensureTotalCapacityPrecise(needed_size);
-        buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(locals.items));
-        buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(exports.items));
-        buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(imports.items));
-
-        log.debug("writing symtab from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
-        try self.file.pwriteAll(buffer.items, offset);
-
-        self.symtab_cmd.symoff = @as(u32, @intCast(offset));
-        self.symtab_cmd.nsyms = nsyms;
-
-        return SymtabCtx{
-            .nlocalsym = nlocals,
-            .nextdefsym = nexports,
-            .nundefsym = nimports,
-            .imports_table = imports_table,
-        };
-    }
-
-    fn writeStrtab(self: *Zld) !void {
-        const seg = self.getLinkeditSegmentPtr();
-        const offset = seg.fileoff + seg.filesize;
-        assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
-        const needed_size = self.strtab.buffer.items.len;
-        const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
-        seg.filesize = offset + needed_size_aligned - seg.fileoff;
-
-        log.debug("writing string table from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
-
-        const buffer = try self.gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow);
-        defer self.gpa.free(buffer);
-        @memcpy(buffer[0..self.strtab.buffer.items.len], self.strtab.buffer.items);
-        @memset(buffer[self.strtab.buffer.items.len..], 0);
-
-        try self.file.pwriteAll(buffer, offset);
-
-        self.symtab_cmd.stroff = @as(u32, @intCast(offset));
-        self.symtab_cmd.strsize = @as(u32, @intCast(needed_size_aligned));
-    }
-
-    const SymtabCtx = struct {
-        nlocalsym: u32,
-        nextdefsym: u32,
-        nundefsym: u32,
-        imports_table: std.AutoHashMap(SymbolWithLoc, u32),
-    };
-
-    fn writeDysymtab(self: *Zld, ctx: SymtabCtx) !void {
-        const gpa = self.gpa;
-        const nstubs = @as(u32, @intCast(self.stubs_table.lookup.count()));
-        const ngot_entries = @as(u32, @intCast(self.got_table.lookup.count()));
-        const nindirectsyms = nstubs * 2 + ngot_entries;
-        const iextdefsym = ctx.nlocalsym;
-        const iundefsym = iextdefsym + ctx.nextdefsym;
-
-        const seg = self.getLinkeditSegmentPtr();
-        const offset = seg.fileoff + seg.filesize;
-        assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
-        const needed_size = nindirectsyms * @sizeOf(u32);
-        const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
-        seg.filesize = offset + needed_size_aligned - seg.fileoff;
-
-        log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
-
-        var buf = std.ArrayList(u8).init(gpa);
-        defer buf.deinit();
-        try buf.ensureTotalCapacityPrecise(math.cast(usize, needed_size_aligned) orelse return error.Overflow);
-        const writer = buf.writer();
-
-        if (self.stubs_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.reserved1 = 0;
-            for (self.stubs_table.entries.items) |entry| {
-                if (!self.stubs_table.lookup.contains(entry)) continue;
-                const target_sym = self.getSymbol(entry);
-                assert(target_sym.undf());
-                try writer.writeIntLittle(u32, iundefsym + ctx.imports_table.get(entry).?);
-            }
-        }
-
-        if (self.got_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.reserved1 = nstubs;
-            for (self.got_table.entries.items) |entry| {
-                if (!self.got_table.lookup.contains(entry)) continue;
-                const target_sym = self.getSymbol(entry);
-                if (target_sym.undf()) {
-                    try writer.writeIntLittle(u32, iundefsym + ctx.imports_table.get(entry).?);
-                } else {
-                    try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL);
-                }
-            }
-        }
-
-        if (self.la_symbol_ptr_section_index) |sect_id| {
-            const header = &self.sections.items(.header)[sect_id];
-            header.reserved1 = nstubs + ngot_entries;
-            for (self.stubs_table.entries.items) |entry| {
-                if (!self.stubs_table.lookup.contains(entry)) continue;
-                const target_sym = self.getSymbol(entry);
-                assert(target_sym.undf());
-                try writer.writeIntLittle(u32, iundefsym + ctx.imports_table.get(entry).?);
-            }
-        }
-
-        const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow;
-        if (padding > 0) {
-            buf.appendNTimesAssumeCapacity(0, padding);
-        }
-
-        assert(buf.items.len == needed_size_aligned);
-        try self.file.pwriteAll(buf.items, offset);
-
-        self.dysymtab_cmd.nlocalsym = ctx.nlocalsym;
-        self.dysymtab_cmd.iextdefsym = iextdefsym;
-        self.dysymtab_cmd.nextdefsym = ctx.nextdefsym;
-        self.dysymtab_cmd.iundefsym = iundefsym;
-        self.dysymtab_cmd.nundefsym = ctx.nundefsym;
-        self.dysymtab_cmd.indirectsymoff = @as(u32, @intCast(offset));
-        self.dysymtab_cmd.nindirectsyms = nindirectsyms;
-    }
-
-    fn writeUuid(self: *Zld, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void {
-        const file_size = if (!has_codesig) blk: {
-            const seg = self.getLinkeditSegmentPtr();
-            break :blk seg.fileoff + seg.filesize;
-        } else self.codesig_cmd.dataoff;
-        try calcUuid(comp, self.file, file_size, &self.uuid_cmd.uuid);
-        const offset = uuid_cmd_offset + @sizeOf(macho.load_command);
-        try self.file.pwriteAll(&self.uuid_cmd.uuid, offset);
-    }
-
-    fn writeCodeSignaturePadding(self: *Zld, code_sig: *CodeSignature) !void {
-        const seg = self.getLinkeditSegmentPtr();
-        // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
-        // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
-        const offset = mem.alignForward(u64, seg.fileoff + seg.filesize, 16);
-        const needed_size = code_sig.estimateSize(offset);
-        seg.filesize = offset + needed_size - seg.fileoff;
-        seg.vmsize = mem.alignForward(u64, seg.filesize, MachO.getPageSize(self.options.target.cpu.arch));
-        log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
-        // Pad out the space. We need to do this to calculate valid hashes for everything in the file
-        // except for code signature data.
-        try self.file.pwriteAll(&[_]u8{0}, offset + needed_size - 1);
-
-        self.codesig_cmd.dataoff = @as(u32, @intCast(offset));
-        self.codesig_cmd.datasize = @as(u32, @intCast(needed_size));
-    }
-
-    fn writeCodeSignature(self: *Zld, comp: *const Compilation, code_sig: *CodeSignature) !void {
-        const seg_id = self.header_segment_cmd_index.?;
-        const seg = self.segments.items[seg_id];
-
-        var buffer = std.ArrayList(u8).init(self.gpa);
-        defer buffer.deinit();
-        try buffer.ensureTotalCapacityPrecise(code_sig.size());
-        try code_sig.writeAdhocSignature(comp, .{
-            .file = self.file,
-            .exec_seg_base = seg.fileoff,
-            .exec_seg_limit = seg.filesize,
-            .file_size = self.codesig_cmd.dataoff,
-            .output_mode = self.options.output_mode,
-        }, buffer.writer());
-        assert(buffer.items.len == code_sig.size());
-
-        log.debug("writing code signature from 0x{x} to 0x{x}", .{
-            self.codesig_cmd.dataoff,
-            self.codesig_cmd.dataoff + buffer.items.len,
-        });
-
-        try self.file.pwriteAll(buffer.items, self.codesig_cmd.dataoff);
-    }
-
-    /// Writes Mach-O file header.
-    fn writeHeader(self: *Zld, ncmds: u32, sizeofcmds: u32) !void {
-        var header: macho.mach_header_64 = .{};
-        header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL;
-
-        switch (self.options.target.cpu.arch) {
-            .aarch64 => {
-                header.cputype = macho.CPU_TYPE_ARM64;
-                header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
-            },
-            .x86_64 => {
-                header.cputype = macho.CPU_TYPE_X86_64;
-                header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
-            },
-            else => return error.UnsupportedCpuArchitecture,
-        }
-
-        switch (self.options.output_mode) {
-            .Exe => {
-                header.filetype = macho.MH_EXECUTE;
-            },
-            .Lib => {
-                // By this point, it can only be a dylib.
-                header.filetype = macho.MH_DYLIB;
-                header.flags |= macho.MH_NO_REEXPORTED_DYLIBS;
-            },
-            else => unreachable,
-        }
-
-        if (self.thread_vars_section_index) |sect_id| {
-            header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
-            if (self.sections.items(.header)[sect_id].size > 0) {
-                header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
-            }
-        }
-
-        header.ncmds = ncmds;
-        header.sizeofcmds = sizeofcmds;
-
-        log.debug("writing Mach-O header {}", .{header});
-
-        try self.file.pwriteAll(mem.asBytes(&header), 0);
-    }
-
-    pub fn makeStaticString(bytes: []const u8) [16]u8 {
-        var buf = [_]u8{0} ** 16;
-        @memcpy(buf[0..bytes.len], bytes);
-        return buf;
-    }
-
-    pub fn getAtomPtr(self: *Zld, atom_index: Atom.Index) *Atom {
-        assert(atom_index < self.atoms.items.len);
-        return &self.atoms.items[atom_index];
-    }
-
-    pub fn getAtom(self: Zld, atom_index: Atom.Index) Atom {
-        assert(atom_index < self.atoms.items.len);
-        return self.atoms.items[atom_index];
-    }
-
-    fn getSegmentByName(self: Zld, segname: []const u8) ?u8 {
-        for (self.segments.items, 0..) |seg, i| {
-            if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i));
-        } else return null;
-    }
-
-    pub fn getSegment(self: Zld, sect_id: u8) macho.segment_command_64 {
-        const index = self.sections.items(.segment_index)[sect_id];
-        return self.segments.items[index];
-    }
-
-    pub fn getSegmentPtr(self: *Zld, sect_id: u8) *macho.segment_command_64 {
-        const index = self.sections.items(.segment_index)[sect_id];
-        return &self.segments.items[index];
-    }
-
-    pub fn getLinkeditSegmentPtr(self: *Zld) *macho.segment_command_64 {
-        assert(self.segments.items.len > 0);
-        const seg = &self.segments.items[self.segments.items.len - 1];
-        assert(mem.eql(u8, seg.segName(), "__LINKEDIT"));
-        return seg;
-    }
-
-    pub fn getSectionByName(self: Zld, segname: []const u8, sectname: []const u8) ?u8 {
-        // TODO investigate caching with a hashmap
-        for (self.sections.items(.header), 0..) |header, i| {
-            if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname))
-                return @as(u8, @intCast(i));
-        } else return null;
-    }
-
-    pub fn getSectionIndexes(self: Zld, segment_index: u8) struct { start: u8, end: u8 } {
-        var start: u8 = 0;
-        const nsects = for (self.segments.items, 0..) |seg, i| {
-            if (i == segment_index) break @as(u8, @intCast(seg.nsects));
-            start += @as(u8, @intCast(seg.nsects));
-        } else 0;
-        return .{ .start = start, .end = start + nsects };
-    }
-
-    pub fn symbolIsTemp(self: *Zld, sym_with_loc: SymbolWithLoc) bool {
-        const sym = self.getSymbol(sym_with_loc);
-        if (!sym.sect()) return false;
-        if (sym.ext()) return false;
-        const sym_name = self.getSymbolName(sym_with_loc);
-        return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
-    }
-
-    /// Returns pointer-to-symbol described by `sym_with_loc` descriptor.
-    pub fn getSymbolPtr(self: *Zld, sym_with_loc: SymbolWithLoc) *macho.nlist_64 {
-        if (sym_with_loc.getFile()) |file| {
-            const object = &self.objects.items[file];
-            return &object.symtab[sym_with_loc.sym_index];
-        } else {
-            return &self.locals.items[sym_with_loc.sym_index];
-        }
-    }
-
-    /// Returns symbol described by `sym_with_loc` descriptor.
-    pub fn getSymbol(self: *const Zld, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
-        if (sym_with_loc.getFile()) |file| {
-            const object = &self.objects.items[file];
-            return object.symtab[sym_with_loc.sym_index];
-        } else {
-            return self.locals.items[sym_with_loc.sym_index];
-        }
-    }
-
-    /// Returns name of the symbol described by `sym_with_loc` descriptor.
-    pub fn getSymbolName(self: *const Zld, sym_with_loc: SymbolWithLoc) []const u8 {
-        if (sym_with_loc.getFile()) |file| {
-            const object = self.objects.items[file];
-            return object.getSymbolName(sym_with_loc.sym_index);
-        } else {
-            const sym = self.locals.items[sym_with_loc.sym_index];
-            return self.strtab.get(sym.n_strx).?;
-        }
-    }
-
-    pub fn getGlobalIndex(self: *const Zld, name: []const u8) ?u32 {
-        return self.resolver.get(name);
-    }
-
-    pub fn getGlobalPtr(self: *Zld, name: []const u8) ?*SymbolWithLoc {
-        const global_index = self.resolver.get(name) orelse return null;
-        return &self.globals.items[global_index];
-    }
-
-    pub fn getGlobal(self: *const Zld, name: []const u8) ?SymbolWithLoc {
-        const global_index = self.resolver.get(name) orelse return null;
-        return self.globals.items[global_index];
-    }
-
-    const GetOrPutGlobalPtrResult = struct {
-        found_existing: bool,
-        value_ptr: *SymbolWithLoc,
-    };
-
-    pub fn getOrPutGlobalPtr(self: *Zld, name: []const u8) !GetOrPutGlobalPtrResult {
-        if (self.getGlobalPtr(name)) |ptr| {
-            return GetOrPutGlobalPtrResult{ .found_existing = true, .value_ptr = ptr };
-        }
-        const global_index = try self.allocateGlobal();
-        const global_name = try self.gpa.dupe(u8, name);
-        _ = try self.resolver.put(self.gpa, global_name, global_index);
-        const ptr = &self.globals.items[global_index];
-        return GetOrPutGlobalPtrResult{ .found_existing = false, .value_ptr = ptr };
-    }
-
-    pub fn getGotEntryAddress(self: *Zld, sym_with_loc: SymbolWithLoc) ?u64 {
-        const index = self.got_table.lookup.get(sym_with_loc) orelse return null;
-        const header = self.sections.items(.header)[self.got_section_index.?];
-        return header.addr + @sizeOf(u64) * index;
-    }
-
-    pub fn getTlvPtrEntryAddress(self: *Zld, sym_with_loc: SymbolWithLoc) ?u64 {
-        const index = self.tlv_ptr_table.lookup.get(sym_with_loc) orelse return null;
-        const header = self.sections.items(.header)[self.tlv_ptr_section_index.?];
-        return header.addr + @sizeOf(u64) * index;
-    }
-
-    pub fn getStubsEntryAddress(self: *Zld, sym_with_loc: SymbolWithLoc) ?u64 {
-        const index = self.stubs_table.lookup.get(sym_with_loc) orelse return null;
-        const header = self.sections.items(.header)[self.stubs_section_index.?];
-        return header.addr + stubs.stubSize(self.options.target.cpu.arch) * index;
-    }
-
-    /// Returns symbol location corresponding to the set entrypoint.
-    /// Asserts output mode is executable.
-    pub fn getEntryPoint(self: Zld) SymbolWithLoc {
-        assert(self.options.output_mode == .Exe);
-        const global_index = self.entry_index.?;
-        return self.globals.items[global_index];
-    }
-
-    inline fn requiresThunks(self: Zld) bool {
-        return self.options.target.cpu.arch == .aarch64;
-    }
-
-    pub fn generateSymbolStabs(self: *Zld, object: Object, locals: *std.ArrayList(macho.nlist_64)) !void {
-        log.debug("generating stabs for '{s}'", .{object.name});
-
-        const gpa = self.gpa;
-        var debug_info = object.parseDwarfInfo();
-
-        var lookup = DwarfInfo.AbbrevLookupTable.init(gpa);
-        defer lookup.deinit();
-        try lookup.ensureUnusedCapacity(std.math.maxInt(u8));
-
-        // We assume there is only one CU.
-        var cu_it = debug_info.getCompileUnitIterator();
-        const compile_unit = while (try cu_it.next()) |cu| {
-            const offset = math.cast(usize, cu.cuh.debug_abbrev_offset) orelse return error.Overflow;
-            try debug_info.genAbbrevLookupByKind(offset, &lookup);
-            break cu;
-        } else {
-            log.debug("no compile unit found in debug info in {s}; skipping", .{object.name});
-            return;
-        };
-
-        var abbrev_it = compile_unit.getAbbrevEntryIterator(debug_info);
-        const cu_entry: DwarfInfo.AbbrevEntry = while (try abbrev_it.next(lookup)) |entry| switch (entry.tag) {
-            dwarf.TAG.compile_unit => break entry,
-            else => continue,
-        } else {
-            log.debug("missing DWARF_TAG_compile_unit tag in {s}; skipping", .{object.name});
-            return;
-        };
-
-        var maybe_tu_name: ?[]const u8 = null;
-        var maybe_tu_comp_dir: ?[]const u8 = null;
-        var attr_it = cu_entry.getAttributeIterator(debug_info, compile_unit.cuh);
-
-        while (try attr_it.next()) |attr| switch (attr.name) {
-            dwarf.AT.comp_dir => maybe_tu_comp_dir = attr.getString(debug_info, compile_unit.cuh) orelse continue,
-            dwarf.AT.name => maybe_tu_name = attr.getString(debug_info, compile_unit.cuh) orelse continue,
-            else => continue,
-        };
-
-        if (maybe_tu_name == null or maybe_tu_comp_dir == null) {
-            log.debug("missing DWARF_AT_comp_dir and DWARF_AT_name attributes {s}; skipping", .{object.name});
-            return;
-        }
-
-        const tu_name = maybe_tu_name.?;
-        const tu_comp_dir = maybe_tu_comp_dir.?;
-
-        // Open scope
-        try locals.ensureUnusedCapacity(3);
-        locals.appendAssumeCapacity(.{
-            .n_strx = try self.strtab.insert(gpa, tu_comp_dir),
-            .n_type = macho.N_SO,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        });
-        locals.appendAssumeCapacity(.{
-            .n_strx = try self.strtab.insert(gpa, tu_name),
-            .n_type = macho.N_SO,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        });
-        locals.appendAssumeCapacity(.{
-            .n_strx = try self.strtab.insert(gpa, object.name),
-            .n_type = macho.N_OSO,
-            .n_sect = 0,
-            .n_desc = 1,
-            .n_value = object.mtime,
-        });
-
-        var stabs_buf: [4]macho.nlist_64 = undefined;
-
-        var name_lookup: ?DwarfInfo.SubprogramLookupByName = if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) blk: {
-            var name_lookup = DwarfInfo.SubprogramLookupByName.init(gpa);
-            errdefer name_lookup.deinit();
-            try name_lookup.ensureUnusedCapacity(@as(u32, @intCast(object.atoms.items.len)));
-            try debug_info.genSubprogramLookupByName(compile_unit, lookup, &name_lookup);
-            break :blk name_lookup;
-        } else null;
-        defer if (name_lookup) |*nl| nl.deinit();
-
-        for (object.atoms.items) |atom_index| {
-            const atom = self.getAtom(atom_index);
-            const stabs = try self.generateSymbolStabsForSymbol(
-                atom_index,
-                atom.getSymbolWithLoc(),
-                name_lookup,
-                &stabs_buf,
-            );
-            try locals.appendSlice(stabs);
-
-            var it = Atom.getInnerSymbolsIterator(self, atom_index);
-            while (it.next()) |sym_loc| {
-                const contained_stabs = try self.generateSymbolStabsForSymbol(
-                    atom_index,
-                    sym_loc,
-                    name_lookup,
-                    &stabs_buf,
-                );
-                try locals.appendSlice(contained_stabs);
-            }
-        }
-
-        // Close scope
-        try locals.append(.{
-            .n_strx = 0,
-            .n_type = macho.N_SO,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        });
-    }
-
-    fn generateSymbolStabsForSymbol(
-        self: *Zld,
-        atom_index: Atom.Index,
-        sym_loc: SymbolWithLoc,
-        lookup: ?DwarfInfo.SubprogramLookupByName,
-        buf: *[4]macho.nlist_64,
-    ) ![]const macho.nlist_64 {
-        const gpa = self.gpa;
-        const object = self.objects.items[sym_loc.getFile().?];
-        const sym = self.getSymbol(sym_loc);
-        const sym_name = self.getSymbolName(sym_loc);
-        const header = self.sections.items(.header)[sym.n_sect - 1];
-
-        if (sym.n_strx == 0) return buf[0..0];
-        if (self.symbolIsTemp(sym_loc)) return buf[0..0];
-
-        if (!header.isCode()) {
-            // Since we are not dealing with machine code, it's either a global or a static depending
-            // on the linkage scope.
-            if (sym.sect() and sym.ext()) {
-                // Global gets an N_GSYM stab type.
-                buf[0] = .{
-                    .n_strx = try self.strtab.insert(gpa, sym_name),
-                    .n_type = macho.N_GSYM,
-                    .n_sect = sym.n_sect,
-                    .n_desc = 0,
-                    .n_value = 0,
-                };
-            } else {
-                // Local static gets an N_STSYM stab type.
-                buf[0] = .{
-                    .n_strx = try self.strtab.insert(gpa, sym_name),
-                    .n_type = macho.N_STSYM,
-                    .n_sect = sym.n_sect,
-                    .n_desc = 0,
-                    .n_value = sym.n_value,
-                };
-            }
-            return buf[0..1];
-        }
-
-        const size: u64 = size: {
-            if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) {
-                break :size self.getAtom(atom_index).size;
-            }
-
-            // Since we don't have subsections to work with, we need to infer the size of each function
-            // the slow way by scanning the debug info for matching symbol names and extracting
-            // the symbol's DWARF_AT_low_pc and DWARF_AT_high_pc values.
-            const source_sym = object.getSourceSymbol(sym_loc.sym_index) orelse return buf[0..0];
-            const subprogram = lookup.?.get(sym_name[1..]) orelse return buf[0..0];
-
-            if (subprogram.addr <= source_sym.n_value and source_sym.n_value < subprogram.addr + subprogram.size) {
-                break :size subprogram.size;
-            } else {
-                log.debug("no stab found for {s}", .{sym_name});
-                return buf[0..0];
-            }
-        };
-
-        buf[0] = .{
-            .n_strx = 0,
-            .n_type = macho.N_BNSYM,
-            .n_sect = sym.n_sect,
-            .n_desc = 0,
-            .n_value = sym.n_value,
-        };
-        buf[1] = .{
-            .n_strx = try self.strtab.insert(gpa, sym_name),
-            .n_type = macho.N_FUN,
-            .n_sect = sym.n_sect,
-            .n_desc = 0,
-            .n_value = sym.n_value,
-        };
-        buf[2] = .{
-            .n_strx = 0,
-            .n_type = macho.N_FUN,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = size,
-        };
-        buf[3] = .{
-            .n_strx = 0,
-            .n_type = macho.N_ENSYM,
-            .n_sect = sym.n_sect,
-            .n_desc = 0,
-            .n_value = size,
-        };
-
-        return buf;
-    }
-
-    fn logSegments(self: *Zld) void {
-        log.debug("segments:", .{});
-        for (self.segments.items, 0..) |segment, i| {
-            log.debug("  segment({d}): {s} @{x} ({x}), sizeof({x})", .{
-                i,
-                segment.segName(),
-                segment.fileoff,
-                segment.vmaddr,
-                segment.vmsize,
-            });
-        }
-    }
-
-    fn logSections(self: *Zld) void {
-        log.debug("sections:", .{});
-        for (self.sections.items(.header), 0..) |header, i| {
-            log.debug("  sect({d}): {s},{s} @{x} ({x}), sizeof({x})", .{
-                i + 1,
-                header.segName(),
-                header.sectName(),
-                header.offset,
-                header.addr,
-                header.size,
-            });
-        }
-    }
-
-    fn logSymAttributes(sym: macho.nlist_64, buf: []u8) []const u8 {
-        if (sym.sect()) {
-            buf[0] = 's';
-        }
-        if (sym.ext()) {
-            if (sym.weakDef() or sym.pext()) {
-                buf[1] = 'w';
-            } else {
-                buf[1] = 'e';
-            }
-        }
-        if (sym.tentative()) {
-            buf[2] = 't';
-        }
-        if (sym.undf()) {
-            buf[3] = 'u';
-        }
-        return buf[0..];
-    }
-
-    fn logSymtab(self: *Zld) void {
-        var buf: [4]u8 = undefined;
-
-        const scoped_log = std.log.scoped(.symtab);
-
-        scoped_log.debug("locals:", .{});
-        for (self.objects.items, 0..) |object, id| {
-            scoped_log.debug("  object({d}): {s}", .{ id, object.name });
-            if (object.in_symtab == null) continue;
-            for (object.symtab, 0..) |sym, sym_id| {
-                @memset(&buf, '_');
-                scoped_log.debug("    %{d}: {s} @{x} in sect({d}), {s}", .{
-                    sym_id,
-                    object.getSymbolName(@as(u32, @intCast(sym_id))),
-                    sym.n_value,
-                    sym.n_sect,
-                    logSymAttributes(sym, &buf),
-                });
-            }
-        }
-        scoped_log.debug("  object(-1)", .{});
-        for (self.locals.items, 0..) |sym, sym_id| {
-            if (sym.undf()) continue;
-            scoped_log.debug("    %{d}: {s} @{x} in sect({d}), {s}", .{
-                sym_id,
-                self.strtab.get(sym.n_strx).?,
-                sym.n_value,
-                sym.n_sect,
-                logSymAttributes(sym, &buf),
-            });
-        }
-
-        scoped_log.debug("exports:", .{});
-        for (self.globals.items, 0..) |global, i| {
-            const sym = self.getSymbol(global);
-            if (sym.undf()) continue;
-            if (sym.n_desc == MachO.N_DEAD) continue;
-            scoped_log.debug("    %{d}: {s} @{x} in sect({d}), {s} (def in object({?}))", .{
-                i,
-                self.getSymbolName(global),
-                sym.n_value,
-                sym.n_sect,
-                logSymAttributes(sym, &buf),
-                global.file,
-            });
-        }
-
-        scoped_log.debug("imports:", .{});
-        for (self.globals.items, 0..) |global, i| {
-            const sym = self.getSymbol(global);
-            if (!sym.undf()) continue;
-            if (sym.n_desc == MachO.N_DEAD) continue;
-            const ord = @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER);
-            scoped_log.debug("    %{d}: {s} @{x} in ord({d}), {s}", .{
-                i,
-                self.getSymbolName(global),
-                sym.n_value,
-                ord,
-                logSymAttributes(sym, &buf),
-            });
-        }
-
-        scoped_log.debug("GOT entries:", .{});
-        scoped_log.debug("{}", .{self.got_table});
-
-        scoped_log.debug("TLV pointers:", .{});
-        scoped_log.debug("{}", .{self.tlv_ptr_table});
-
-        scoped_log.debug("stubs entries:", .{});
-        scoped_log.debug("{}", .{self.stubs_table});
-
-        scoped_log.debug("thunks:", .{});
-        for (self.thunks.items, 0..) |thunk, i| {
-            scoped_log.debug("  thunk({d})", .{i});
-            const slice = thunk.targets.slice();
-            for (slice.items(.tag), slice.items(.target), 0..) |tag, target, j| {
-                const atom_index = @as(u32, @intCast(thunk.getStartAtomIndex() + j));
-                const atom = self.getAtom(atom_index);
-                const atom_sym = self.getSymbol(atom.getSymbolWithLoc());
-                const target_addr = switch (tag) {
-                    .stub => self.getStubsEntryAddress(target).?,
-                    .atom => self.getSymbol(target).n_value,
-                };
-                scoped_log.debug("    {d}@{x} => {s}({s}@{x})", .{
-                    j,
-                    atom_sym.n_value,
-                    @tagName(tag),
-                    self.getSymbolName(target),
-                    target_addr,
-                });
-            }
-        }
-    }
-
-    fn logAtoms(self: *Zld) void {
-        log.debug("atoms:", .{});
-        const slice = self.sections.slice();
-        for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
-            var atom_index = first_atom_index orelse continue;
-            const header = slice.items(.header)[sect_id];
-
-            log.debug("{s},{s}", .{ header.segName(), header.sectName() });
-
-            while (true) {
-                const atom = self.getAtom(atom_index);
-                self.logAtom(atom_index, log);
-
-                if (atom.next_index) |next_index| {
-                    atom_index = next_index;
-                } else break;
-            }
-        }
-    }
-
-    pub fn logAtom(self: *Zld, atom_index: Atom.Index, logger: anytype) void {
-        if (!build_options.enable_logging) return;
-
-        const atom = self.getAtom(atom_index);
-        const sym = self.getSymbol(atom.getSymbolWithLoc());
-        const sym_name = self.getSymbolName(atom.getSymbolWithLoc());
-        logger.debug("  ATOM({d}, %{d}, '{s}') @ {x} (sizeof({x}), alignof({x})) in object({?}) in sect({d})", .{
-            atom_index,
-            atom.sym_index,
-            sym_name,
-            sym.n_value,
-            atom.size,
-            atom.alignment,
-            atom.getFile(),
-            sym.n_sect,
-        });
-
-        if (atom.getFile() != null) {
-            var it = Atom.getInnerSymbolsIterator(self, atom_index);
-            while (it.next()) |sym_loc| {
-                const inner = self.getSymbol(sym_loc);
-                const inner_name = self.getSymbolName(sym_loc);
-                const offset = Atom.calcInnerSymbolOffset(self, atom_index, sym_loc.sym_index);
-
-                logger.debug("    (%{d}, '{s}') @ {x} ({x})", .{
-                    sym_loc.sym_index,
-                    inner_name,
-                    inner.n_value,
-                    offset,
-                });
-            }
-
-            if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
-                const alias = self.getSymbol(sym_loc);
-                const alias_name = self.getSymbolName(sym_loc);
-
-                logger.debug("    (%{d}, '{s}') @ {x} ({x})", .{
-                    sym_loc.sym_index,
-                    alias_name,
-                    alias.n_value,
-                    0,
-                });
-            }
-        }
-    }
-};
-
-pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
+pub fn linkWithZld(
+    macho_file: *MachO,
+    comp: *Compilation,
+    prog_node: *std.Progress.Node,
+) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2611,8 +35,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
     defer sub_prog_node.end();
 
     const cpu_arch = target.cpu.arch;
-    const os_tag = target.os.tag;
-    const abi = target.abi;
     const is_lib = options.output_mode == .Lib;
     const is_dyn_lib = options.link_mode == .Dynamic and is_lib;
     const is_exe_or_dyn_lib = is_dyn_lib or options.output_mode == .Exe;
@@ -2730,29 +152,26 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
     } else {
         const sub_path = options.emit.?.sub_path;
 
+        const old_file = macho_file.base.file; // TODO is this needed at all?
+        defer macho_file.base.file = old_file;
+
         const file = try directory.handle.createFile(sub_path, .{
             .truncate = true,
             .read = true,
             .mode = link.determineMode(options.*),
         });
         defer file.close();
-
-        var zld = Zld{
-            .gpa = gpa,
-            .file = file,
-            .options = options,
-        };
-        defer zld.deinit();
+        macho_file.base.file = file;
 
         // Index 0 is always a null symbol.
-        try zld.locals.append(gpa, .{
+        try macho_file.locals.append(gpa, .{
             .n_strx = 0,
             .n_type = 0,
             .n_sect = 0,
             .n_desc = 0,
             .n_value = 0,
         });
-        try zld.strtab.buffer.append(gpa, 0);
+        try macho_file.strtab.buffer.append(gpa, 0);
 
         // Positional arguments to the linker such as object files and static archives.
         var positionals = std.ArrayList(Compilation.LinkObject).init(arena);
@@ -2930,9 +349,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
             const in_file = try std.fs.cwd().openFile(obj.path, .{});
             defer in_file.close();
 
-            MachO.parsePositional(
-                &zld,
-                gpa,
+            macho_file.parsePositional(
                 in_file,
                 obj.path,
                 obj.must_link,
@@ -2949,9 +366,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
             const in_file = try std.fs.cwd().openFile(path, .{});
             defer in_file.close();
 
-            MachO.parseLibrary(
-                &zld,
-                gpa,
+            macho_file.parseLibrary(
                 in_file,
                 path,
                 lib,
@@ -2965,198 +380,199 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
             };
         }
 
-        MachO.parseDependentLibs(&zld, gpa, &dependent_libs, options) catch |err| {
+        macho_file.parseDependentLibs(&dependent_libs, options) catch |err| {
             // TODO convert to error
             log.err("parsing dependent libraries failed with err {s}", .{@errorName(err)});
         };
 
-        try zld.resolveSymbols();
-        try macho_file.reportUndefined(&zld);
-
-        if (options.output_mode == .Exe) {
-            const entry_name = options.entry orelse load_commands.default_entry_point;
-            const global_index = zld.resolver.get(entry_name).?; // Error was flagged earlier
-            zld.entry_index = global_index;
-        }
+        var actions = std.ArrayList(MachO.ResolveAction).init(gpa);
+        defer actions.deinit();
+        try macho_file.resolveSymbols(&actions);
+        try macho_file.reportUndefined();
 
-        for (zld.objects.items, 0..) |*object, object_id| {
-            try object.splitIntoAtoms(&zld, @as(u32, @intCast(object_id)));
+        for (macho_file.objects.items, 0..) |*object, object_id| {
+            try object.splitIntoAtoms(macho_file, @as(u32, @intCast(object_id)));
         }
 
         if (gc_sections) {
-            try dead_strip.gcAtoms(&zld);
+            try dead_strip.gcAtoms(macho_file);
         }
 
-        try zld.createDyldPrivateAtom();
-        try zld.createTentativeDefAtoms();
+        try macho_file.createDyldPrivateAtom();
+        try macho_file.createTentativeDefAtoms();
 
-        if (zld.options.output_mode == .Exe) {
-            const global = zld.getEntryPoint();
-            if (zld.getSymbol(global).undf()) {
+        if (macho_file.options.output_mode == .Exe) {
+            const global = macho_file.getEntryPoint().?;
+            if (macho_file.getSymbol(global).undf()) {
                 // We do one additional check here in case the entry point was found in one of the dylibs.
                 // (I actually have no idea what this would imply but it is a possible outcome and so we
                 // support it.)
-                try zld.addStubEntry(global);
+                try macho_file.addStubEntry(global);
             }
         }
 
-        for (zld.objects.items) |object| {
+        for (macho_file.objects.items) |object| {
             for (object.atoms.items) |atom_index| {
-                const atom = zld.getAtom(atom_index);
-                const sym = zld.getSymbol(atom.getSymbolWithLoc());
-                const header = zld.sections.items(.header)[sym.n_sect - 1];
+                const atom = macho_file.getAtom(atom_index);
+                const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+                const header = macho_file.sections.items(.header)[sym.n_sect - 1];
                 if (header.isZerofill()) continue;
 
-                const relocs = Atom.getAtomRelocs(&zld, atom_index);
-                try Atom.scanAtomRelocs(&zld, atom_index, relocs);
+                const relocs = Atom.getAtomRelocs(macho_file, atom_index);
+                try Atom.scanAtomRelocs(macho_file, atom_index, relocs);
             }
         }
 
-        try eh_frame.scanRelocs(&zld);
-        try UnwindInfo.scanRelocs(&zld);
+        try eh_frame.scanRelocs(macho_file);
+        try UnwindInfo.scanRelocs(macho_file);
 
-        if (zld.dyld_stub_binder_index) |index| try zld.addGotEntry(zld.globals.items[index]);
+        if (macho_file.dyld_stub_binder_index) |index|
+            try macho_file.addGotEntry(macho_file.globals.items[index]);
 
-        try zld.calcSectionSizes();
+        try macho_file.calcSectionSizes();
 
-        var unwind_info = UnwindInfo{ .gpa = zld.gpa };
+        var unwind_info = UnwindInfo{ .gpa = gpa };
         defer unwind_info.deinit();
-        try unwind_info.collect(&zld);
+        try unwind_info.collect(macho_file);
 
-        try eh_frame.calcSectionSize(&zld, &unwind_info);
-        try unwind_info.calcSectionSize(&zld);
+        try eh_frame.calcSectionSize(macho_file, &unwind_info);
+        try unwind_info.calcSectionSize(macho_file);
 
-        try zld.pruneAndSortSections();
-        try zld.createSegments();
-        try zld.allocateSegments();
+        try pruneAndSortSections(macho_file);
+        try createSegments(macho_file);
+        try allocateSegments(macho_file);
 
-        try MachO.allocateSpecialSymbols(&zld);
+        try macho_file.allocateSpecialSymbols();
 
         if (build_options.enable_logging) {
-            zld.logSymtab();
-            zld.logSegments();
-            zld.logSections();
-            zld.logAtoms();
+            macho_file.logSymtab();
+            macho_file.logSegments();
+            macho_file.logSections();
+            macho_file.logAtoms();
         }
 
-        try zld.writeAtoms();
-        if (zld.requiresThunks()) try zld.writeThunks();
-        try zld.writeDyldPrivateAtom();
+        try writeAtoms(macho_file);
+        if (macho_file.requiresThunks()) try writeThunks(macho_file);
+        try writeDyldPrivateAtom(macho_file);
 
-        if (zld.stubs_section_index) |_| {
-            try zld.writeStubs();
-            try zld.writeStubHelpers();
-            try zld.writeLaSymbolPtrs();
+        if (macho_file.stubs_section_index) |_| {
+            try writeStubs(macho_file);
+            try writeStubHelpers(macho_file);
+            try writeLaSymbolPtrs(macho_file);
         }
-        if (zld.got_section_index) |sect_id| try zld.writePointerEntries(sect_id, &zld.got_table);
-        if (zld.tlv_ptr_section_index) |sect_id| try zld.writePointerEntries(sect_id, &zld.tlv_ptr_table);
+        if (macho_file.got_section_index) |sect_id|
+            try macho_file.writePointerEntries(sect_id, &macho_file.got_table);
+        if (macho_file.tlv_ptr_section_index) |sect_id|
+            try macho_file.writePointerEntries(sect_id, &macho_file.tlv_ptr_table);
 
-        try eh_frame.write(&zld, &unwind_info);
-        try unwind_info.write(&zld);
-        try zld.writeLinkeditSegmentData();
+        try eh_frame.write(macho_file, &unwind_info);
+        try unwind_info.write(macho_file);
+        try macho_file.writeLinkeditSegmentData();
 
         // If the last section of __DATA segment is zerofill section, we need to ensure
         // that the free space between the end of the last non-zerofill section of __DATA
         // segment and the beginning of __LINKEDIT segment is zerofilled as the loader will
         // copy-paste this space into memory for quicker zerofill operation.
-        if (zld.data_segment_cmd_index) |data_seg_id| blk: {
+        if (macho_file.data_segment_cmd_index) |data_seg_id| blk: {
             var physical_zerofill_start: ?u64 = null;
-            const section_indexes = zld.getSectionIndexes(data_seg_id);
-            for (zld.sections.items(.header)[section_indexes.start..section_indexes.end]) |header| {
+            const section_indexes = macho_file.getSectionIndexes(data_seg_id);
+            for (macho_file.sections.items(.header)[section_indexes.start..section_indexes.end]) |header| {
                 if (header.isZerofill() and header.size > 0) break;
                 physical_zerofill_start = header.offset + header.size;
             } else break :blk;
             const start = physical_zerofill_start orelse break :blk;
-            const linkedit = zld.getLinkeditSegmentPtr();
+            const linkedit = macho_file.getLinkeditSegmentPtr();
             const size = math.cast(usize, linkedit.fileoff - start) orelse return error.Overflow;
             if (size > 0) {
                 log.debug("zeroing out zerofill area of length {x} at {x}", .{ size, start });
-                var padding = try zld.gpa.alloc(u8, size);
-                defer zld.gpa.free(padding);
+                var padding = try gpa.alloc(u8, size);
+                defer gpa.free(padding);
                 @memset(padding, 0);
-                try zld.file.pwriteAll(padding, start);
+                try macho_file.base.file.?.pwriteAll(padding, start);
             }
         }
 
         // Write code signature padding if required
-        const requires_codesig = blk: {
-            if (options.entitlements) |_| break :blk true;
-            if (cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator)) break :blk true;
-            break :blk false;
-        };
-        var codesig: ?CodeSignature = if (requires_codesig) blk: {
+        var codesig: ?CodeSignature = if (macho_file.requiresCodeSignature()) blk: {
             // Preallocate space for the code signature.
             // We need to do this at this stage so that we have the load commands with proper values
             // written out to the file.
             // The most important here is to have the correct vm and filesize of the __LINKEDIT segment
             // where the code signature goes into.
-            var codesig = CodeSignature.init(MachO.getPageSize(zld.options.target.cpu.arch));
+            var codesig = CodeSignature.init(MachO.getPageSize(cpu_arch));
             codesig.code_directory.ident = fs.path.basename(full_out_path);
             if (options.entitlements) |path| {
-                try codesig.addEntitlements(zld.gpa, path);
+                try codesig.addEntitlements(gpa, path);
             }
-            try zld.writeCodeSignaturePadding(&codesig);
+            try macho_file.writeCodeSignaturePadding(&codesig);
             break :blk codesig;
         } else null;
-        defer if (codesig) |*csig| csig.deinit(zld.gpa);
+        defer if (codesig) |*csig| csig.deinit(gpa);
 
         // Write load commands
         var lc_buffer = std.ArrayList(u8).init(arena);
         const lc_writer = lc_buffer.writer();
 
-        try MachO.writeSegmentHeaders(&zld, lc_writer);
-        try lc_writer.writeStruct(zld.dyld_info_cmd);
-        try lc_writer.writeStruct(zld.function_starts_cmd);
-        try lc_writer.writeStruct(zld.data_in_code_cmd);
-        try lc_writer.writeStruct(zld.symtab_cmd);
-        try lc_writer.writeStruct(zld.dysymtab_cmd);
+        try macho_file.writeSegmentHeaders(lc_writer);
+        try lc_writer.writeStruct(macho_file.dyld_info_cmd);
+        try lc_writer.writeStruct(macho_file.function_starts_cmd);
+        try lc_writer.writeStruct(macho_file.data_in_code_cmd);
+        try lc_writer.writeStruct(macho_file.symtab_cmd);
+        try lc_writer.writeStruct(macho_file.dysymtab_cmd);
         try load_commands.writeDylinkerLC(lc_writer);
 
-        if (zld.options.output_mode == .Exe) {
-            const seg_id = zld.header_segment_cmd_index.?;
-            const seg = zld.segments.items[seg_id];
-            const global = zld.getEntryPoint();
-            const sym = zld.getSymbol(global);
-
-            const addr: u64 = if (sym.undf())
-                // In this case, the symbol has been resolved in one of dylibs and so we point
-                // to the stub as its vmaddr value.
-                zld.getStubsEntryAddress(global).?
-            else
-                sym.n_value;
-
-            try lc_writer.writeStruct(macho.entry_point_command{
-                .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
-                .stacksize = options.stack_size_override orelse 0,
-            });
-        } else {
-            assert(zld.options.output_mode == .Lib);
-            try load_commands.writeDylibIdLC(zld.gpa, zld.options, lc_writer);
+        switch (macho_file.base.options.output_mode) {
+            .Exe => blk: {
+                const seg_id = macho_file.header_segment_cmd_index.?;
+                const seg = macho_file.segments.items[seg_id];
+                const global = macho_file.getEntryPoint() orelse break :blk;
+                const sym = macho_file.getSymbol(global);
+
+                const addr: u64 = if (sym.undf())
+                    // In this case, the symbol has been resolved in one of dylibs and so we point
+                    // to the stub as its vmaddr value.
+                    macho_file.getStubsEntryAddress(global).?
+                else
+                    sym.n_value;
+
+                try lc_writer.writeStruct(macho.entry_point_command{
+                    .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
+                    .stacksize = macho_file.base.options.stack_size_override orelse 0,
+                });
+            },
+            .Lib => if (macho_file.base.options.link_mode == .Dynamic) {
+                try load_commands.writeDylibIdLC(gpa, &macho_file.base.options, lc_writer);
+            },
+            else => {},
         }
 
-        try load_commands.writeRpathLCs(zld.gpa, zld.options, lc_writer);
+        try load_commands.writeRpathLCs(gpa, macho_file.base.options, lc_writer);
         try lc_writer.writeStruct(macho.source_version_command{
             .version = 0,
         });
-        try load_commands.writeBuildVersionLC(zld.options, lc_writer);
+        try load_commands.writeBuildVersionLC(macho_file.base.options, lc_writer);
 
         const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
-        try lc_writer.writeStruct(zld.uuid_cmd);
+        try lc_writer.writeStruct(macho_file.uuid_cmd);
 
-        try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), lc_writer);
+        try load_commands.writeLoadDylibLCs(
+            macho_file.dylibs.items,
+            macho_file.referenced_dylibs.keys(),
+            lc_writer,
+        );
 
-        if (requires_codesig) {
-            try lc_writer.writeStruct(zld.codesig_cmd);
+        if (codesig != null) {
+            try lc_writer.writeStruct(macho_file.codesig_cmd);
         }
 
         const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
-        try zld.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
-        try zld.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
-        try zld.writeUuid(comp, uuid_cmd_offset, requires_codesig);
+        try macho_file.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
+        try macho_file.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
+        try macho_file.writeUuid(comp, uuid_cmd_offset, codesig != null);
 
         if (codesig) |*csig| {
-            try zld.writeCodeSignature(comp, csig); // code signing always comes last
-            try MachO.invalidateKernelCache(directory.handle, zld.options.emit.?.sub_path);
+            try macho_file.writeCodeSignature(comp, csig); // code signing always comes last
+            try MachO.invalidateKernelCache(directory.handle, macho_file.base.options.emit.?.sub_path);
         }
     }
 
@@ -3177,3 +593,609 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
         macho_file.base.lock = man.toOwnedLock();
     }
 }
+
+fn createSegments(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
+    const pagezero_vmsize = macho_file.base.options.pagezero_size orelse MachO.default_pagezero_vmsize;
+    const page_size = MachO.getPageSize(macho_file.base.options.target.cpu.arch);
+    const aligned_pagezero_vmsize = mem.alignBackward(u64, pagezero_vmsize, page_size);
+    if (macho_file.base.options.output_mode != .Lib and aligned_pagezero_vmsize > 0) {
+        if (aligned_pagezero_vmsize != pagezero_vmsize) {
+            log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_vmsize});
+            log.warn("  rounding down to 0x{x}", .{aligned_pagezero_vmsize});
+        }
+        macho_file.pagezero_segment_cmd_index = @intCast(macho_file.segments.items.len);
+        try macho_file.segments.append(gpa, .{
+            .cmdsize = @sizeOf(macho.segment_command_64),
+            .segname = MachO.makeStaticString("__PAGEZERO"),
+            .vmsize = aligned_pagezero_vmsize,
+        });
+    }
+
+    // __TEXT segment is non-optional
+    {
+        const protection = MachO.getSegmentMemoryProtection("__TEXT");
+        macho_file.text_segment_cmd_index = @intCast(macho_file.segments.items.len);
+        macho_file.header_segment_cmd_index = macho_file.text_segment_cmd_index.?;
+        try macho_file.segments.append(gpa, .{
+            .cmdsize = @sizeOf(macho.segment_command_64),
+            .segname = MachO.makeStaticString("__TEXT"),
+            .maxprot = protection,
+            .initprot = protection,
+        });
+    }
+
+    for (macho_file.sections.items(.header), 0..) |header, sect_id| {
+        if (header.size == 0) continue; // empty section
+
+        const segname = header.segName();
+        const segment_id = macho_file.getSegmentByName(segname) orelse blk: {
+            log.debug("creating segment '{s}'", .{segname});
+            const segment_id = @as(u8, @intCast(macho_file.segments.items.len));
+            const protection = MachO.getSegmentMemoryProtection(segname);
+            try macho_file.segments.append(gpa, .{
+                .cmdsize = @sizeOf(macho.segment_command_64),
+                .segname = MachO.makeStaticString(segname),
+                .maxprot = protection,
+                .initprot = protection,
+            });
+            break :blk segment_id;
+        };
+        const segment = &macho_file.segments.items[segment_id];
+        segment.cmdsize += @sizeOf(macho.section_64);
+        segment.nsects += 1;
+        macho_file.sections.items(.segment_index)[sect_id] = segment_id;
+    }
+
+    if (macho_file.getSegmentByName("__DATA_CONST")) |index| {
+        macho_file.data_const_segment_cmd_index = index;
+    }
+
+    if (macho_file.getSegmentByName("__DATA")) |index| {
+        macho_file.data_segment_cmd_index = index;
+    }
+
+    // __LINKEDIT always comes last
+    {
+        const protection = MachO.getSegmentMemoryProtection("__LINKEDIT");
+        macho_file.linkedit_segment_cmd_index = @intCast(macho_file.segments.items.len);
+        try macho_file.segments.append(gpa, .{
+            .cmdsize = @sizeOf(macho.segment_command_64),
+            .segname = MachO.makeStaticString("__LINKEDIT"),
+            .maxprot = protection,
+            .initprot = protection,
+        });
+    }
+}
+
+fn writeAtoms(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
+    const slice = macho_file.sections.slice();
+
+    for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
+        const header = slice.items(.header)[sect_id];
+        if (header.isZerofill()) continue;
+
+        var atom_index = first_atom_index orelse continue;
+
+        var buffer = try gpa.alloc(u8, math.cast(usize, header.size) orelse return error.Overflow);
+        defer gpa.free(buffer);
+        @memset(buffer, 0); // TODO with NOPs
+
+        log.debug("writing atoms in {s},{s}", .{ header.segName(), header.sectName() });
+
+        while (true) {
+            const atom = macho_file.getAtom(atom_index);
+            if (atom.getFile()) |file| {
+                const this_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+                const padding_size: usize = if (atom.next_index) |next_index| blk: {
+                    const next_sym = macho_file.getSymbol(macho_file.getAtom(next_index).getSymbolWithLoc());
+                    const size = next_sym.n_value - (this_sym.n_value + atom.size);
+                    break :blk math.cast(usize, size) orelse return error.Overflow;
+                } else 0;
+
+                log.debug("  (adding ATOM(%{d}, '{s}') from object({d}) to buffer)", .{
+                    atom.sym_index,
+                    macho_file.getSymbolName(atom.getSymbolWithLoc()),
+                    file,
+                });
+                if (padding_size > 0) {
+                    log.debug("    (with padding {x})", .{padding_size});
+                }
+
+                const offset = this_sym.n_value - header.addr;
+                log.debug("  (at offset 0x{x})", .{offset});
+
+                const code = Atom.getAtomCode(macho_file, atom_index);
+                const relocs = Atom.getAtomRelocs(macho_file, atom_index);
+                const size = math.cast(usize, atom.size) orelse return error.Overflow;
+                @memcpy(buffer[offset .. offset + size], code);
+                try Atom.resolveRelocs(
+                    macho_file,
+                    atom_index,
+                    buffer[offset..][0..size],
+                    relocs,
+                );
+            }
+
+            if (atom.next_index) |next_index| {
+                atom_index = next_index;
+            } else break;
+        }
+
+        log.debug("  (writing at file offset 0x{x})", .{header.offset});
+        try macho_file.base.file.?.pwriteAll(buffer, header.offset);
+    }
+}
+
+fn writeDyldPrivateAtom(macho_file: *MachO) !void {
+    const atom_index = macho_file.dyld_private_atom_index orelse return;
+    const atom = macho_file.getAtom(atom_index);
+    const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+    const sect_id = macho_file.data_section_index.?;
+    const header = macho_file.sections.items(.header)[sect_id];
+    const offset = sym.n_value - header.addr + header.offset;
+    log.debug("writing __dyld_private at offset 0x{x}", .{offset});
+    const buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
+    try macho_file.base.file.?.pwriteAll(&buffer, offset);
+}
+
+fn writeThunks(macho_file: *MachO) !void {
+    assert(macho_file.requiresThunks());
+    const gpa = macho_file.base.allocator;
+
+    const sect_id = macho_file.text_section_index orelse return;
+    const header = macho_file.sections.items(.header)[sect_id];
+
+    for (macho_file.thunks.items, 0..) |*thunk, i| {
+        if (thunk.getSize() == 0) continue;
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, thunk.getSize());
+        defer buffer.deinit();
+        try thunks.writeThunkCode(macho_file, thunk, buffer.writer());
+        const thunk_atom = macho_file.getAtom(thunk.getStartAtomIndex());
+        const thunk_sym = macho_file.getSymbol(thunk_atom.getSymbolWithLoc());
+        const offset = thunk_sym.n_value - header.addr + header.offset;
+        log.debug("writing thunk({d}) at offset 0x{x}", .{ i, offset });
+        try macho_file.base.file.?.pwriteAll(buffer.items, offset);
+    }
+}
+
+fn writePointerEntries(macho_file: *MachO, sect_id: u8, table: anytype) !void {
+    const gpa = macho_file.base.allocator;
+    const header = macho_file.sections.items(.header)[sect_id];
+    var buffer = try std.ArrayList(u8).initCapacity(gpa, header.size);
+    defer buffer.deinit();
+    for (table.entries.items) |entry| {
+        const sym = macho_file.getSymbol(entry);
+        buffer.writer().writeIntLittle(u64, sym.n_value) catch unreachable;
+    }
+    log.debug("writing __DATA_CONST,__got contents at file offset 0x{x}", .{header.offset});
+    try macho_file.base.file.?.pwriteAll(buffer.items, header.offset);
+}
+
+fn writeStubs(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+    const stubs_header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?];
+    const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
+
+    var buffer = try std.ArrayList(u8).initCapacity(gpa, stubs_header.size);
+    defer buffer.deinit();
+
+    for (0..macho_file.stub_table.count()) |index| {
+        try stubs.writeStubCode(.{
+            .cpu_arch = cpu_arch,
+            .source_addr = stubs_header.addr + stubs.stubSize(cpu_arch) * index,
+            .target_addr = la_symbol_ptr_header.addr + index * @sizeOf(u64),
+        }, buffer.writer());
+    }
+
+    log.debug("writing __TEXT,__stubs contents at file offset 0x{x}", .{stubs_header.offset});
+    try macho_file.base.file.?.pwriteAll(buffer.items, stubs_header.offset);
+}
+
+fn writeStubHelpers(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+    const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
+
+    var buffer = try std.ArrayList(u8).initCapacity(gpa, stub_helper_header.size);
+    defer buffer.deinit();
+
+    {
+        const dyld_private_addr = blk: {
+            const atom = macho_file.getAtom(macho_file.dyld_private_atom_index.?);
+            const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+            break :blk sym.n_value;
+        };
+        const dyld_stub_binder_got_addr = blk: {
+            const sym_loc = macho_file.globals.items[macho_file.dyld_stub_binder_index.?];
+            break :blk macho_file.getGotEntryAddress(sym_loc).?;
+        };
+        try stubs.writeStubHelperPreambleCode(.{
+            .cpu_arch = cpu_arch,
+            .source_addr = stub_helper_header.addr,
+            .dyld_private_addr = dyld_private_addr,
+            .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr,
+        }, buffer.writer());
+    }
+
+    for (0..macho_file.stub_table.count()) |index| {
+        const source_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
+            stubs.stubHelperSize(cpu_arch) * index;
+        try stubs.writeStubHelperCode(.{
+            .cpu_arch = cpu_arch,
+            .source_addr = source_addr,
+            .target_addr = stub_helper_header.addr,
+        }, buffer.writer());
+    }
+
+    log.debug("writing __TEXT,__stub_helper contents at file offset 0x{x}", .{
+        stub_helper_header.offset,
+    });
+    try macho_file.base.file.?.pwriteAll(buffer.items, stub_helper_header.offset);
+}
+
+fn writeLaSymbolPtrs(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+    const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
+    const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
+
+    var buffer = try std.ArrayList(u8).initCapacity(gpa, la_symbol_ptr_header.size);
+    defer buffer.deinit();
+
+    for (0..macho_file.stub_table.count()) |index| {
+        const target_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
+            stubs.stubHelperSize(cpu_arch) * index;
+        buffer.writer().writeIntLittle(u64, target_addr) catch unreachable;
+    }
+
+    log.debug("writing __DATA,__la_symbol_ptr contents at file offset 0x{x}", .{
+        la_symbol_ptr_header.offset,
+    });
+    try macho_file.base.file.?.pwriteAll(buffer.items, la_symbol_ptr_header.offset);
+}
+
+fn pruneAndSortSections(macho_file: *MachO) !void {
+    const Entry = struct {
+        index: u8,
+
+        pub fn lessThan(ctx: *MachO, lhs: @This(), rhs: @This()) bool {
+            const lhs_header = ctx.sections.items(.header)[lhs.index];
+            const rhs_header = ctx.sections.items(.header)[rhs.index];
+            return MachO.getSectionPrecedence(lhs_header) < MachO.getSectionPrecedence(rhs_header);
+        }
+    };
+
+    const gpa = macho_file.base.allocator;
+
+    var entries = try std.ArrayList(Entry).initCapacity(gpa, macho_file.sections.slice().len);
+    defer entries.deinit();
+
+    for (0..macho_file.sections.slice().len) |index| {
+        const section = macho_file.sections.get(index);
+        if (section.header.size == 0) {
+            log.debug("pruning section {s},{s} {?d}", .{
+                section.header.segName(),
+                section.header.sectName(),
+                section.first_atom_index,
+            });
+            for (&[_]*?u8{
+                &macho_file.text_section_index,
+                &macho_file.data_const_section_index,
+                &macho_file.data_section_index,
+                &macho_file.bss_section_index,
+                &macho_file.thread_vars_section_index,
+                &macho_file.thread_data_section_index,
+                &macho_file.thread_bss_section_index,
+                &macho_file.eh_frame_section_index,
+                &macho_file.unwind_info_section_index,
+                &macho_file.got_section_index,
+                &macho_file.tlv_ptr_section_index,
+                &macho_file.stubs_section_index,
+                &macho_file.stub_helper_section_index,
+                &macho_file.la_symbol_ptr_section_index,
+            }) |maybe_index| {
+                if (maybe_index.* != null and maybe_index.*.? == index) {
+                    maybe_index.* = null;
+                }
+            }
+            continue;
+        }
+        entries.appendAssumeCapacity(.{ .index = @intCast(index) });
+    }
+
+    mem.sort(Entry, entries.items, macho_file, Entry.lessThan);
+
+    var slice = macho_file.sections.toOwnedSlice();
+    defer slice.deinit(gpa);
+
+    const backlinks = try gpa.alloc(u8, slice.len);
+    defer gpa.free(backlinks);
+    for (entries.items, 0..) |entry, i| {
+        backlinks[entry.index] = @as(u8, @intCast(i));
+    }
+
+    try macho_file.sections.ensureTotalCapacity(gpa, entries.items.len);
+    for (entries.items) |entry| {
+        macho_file.sections.appendAssumeCapacity(slice.get(entry.index));
+    }
+
+    for (&[_]*?u8{
+        &macho_file.text_section_index,
+        &macho_file.data_const_section_index,
+        &macho_file.data_section_index,
+        &macho_file.bss_section_index,
+        &macho_file.thread_vars_section_index,
+        &macho_file.thread_data_section_index,
+        &macho_file.thread_bss_section_index,
+        &macho_file.eh_frame_section_index,
+        &macho_file.unwind_info_section_index,
+        &macho_file.got_section_index,
+        &macho_file.tlv_ptr_section_index,
+        &macho_file.stubs_section_index,
+        &macho_file.stub_helper_section_index,
+        &macho_file.la_symbol_ptr_section_index,
+    }) |maybe_index| {
+        if (maybe_index.*) |*index| {
+            index.* = backlinks[index.*];
+        }
+    }
+}
+
+fn calcSectionSizes(macho_file: *MachO) !void {
+    const slice = macho_file.sections.slice();
+    for (slice.items(.header), 0..) |*header, sect_id| {
+        if (header.size == 0) continue;
+        if (macho_file.text_section_index) |txt| {
+            if (txt == sect_id and macho_file.requiresThunks()) continue;
+        }
+
+        var atom_index = slice.items(.first_atom_index)[sect_id] orelse continue;
+
+        header.size = 0;
+        header.@"align" = 0;
+
+        while (true) {
+            const atom = macho_file.getAtom(atom_index);
+            const atom_alignment = try math.powi(u32, 2, atom.alignment);
+            const atom_offset = mem.alignForward(u64, header.size, atom_alignment);
+            const padding = atom_offset - header.size;
+
+            const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
+            sym.n_value = atom_offset;
+
+            header.size += padding + atom.size;
+            header.@"align" = @max(header.@"align", atom.alignment);
+
+            if (atom.next_index) |next_index| {
+                atom_index = next_index;
+            } else break;
+        }
+    }
+
+    if (macho_file.text_section_index != null and macho_file.requiresThunks()) {
+        // Create jump/branch range extenders if needed.
+        try thunks.createThunks(macho_file, macho_file.text_section_index.?);
+    }
+
+    // Update offsets of all symbols contained within each Atom.
+    // We need to do this since our unwind info synthesiser relies on
+    // traversing the symbols when synthesising unwind info and DWARF CFI records.
+    for (slice.items(.first_atom_index)) |first_atom_index| {
+        var atom_index = first_atom_index orelse continue;
+
+        while (true) {
+            const atom = macho_file.getAtom(atom_index);
+            const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
+
+            if (atom.getFile() != null) {
+                // Update each symbol contained within the atom
+                var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
+                while (it.next()) |sym_loc| {
+                    const inner_sym = macho_file.getSymbolPtr(sym_loc);
+                    inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
+                        macho_file,
+                        atom_index,
+                        sym_loc.sym_index,
+                    );
+                }
+
+                // If there is a section alias, update it now too
+                if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
+                    const alias = macho_file.getSymbolPtr(sym_loc);
+                    alias.n_value = sym.n_value;
+                }
+            }
+
+            if (atom.next_index) |next_index| {
+                atom_index = next_index;
+            } else break;
+        }
+    }
+
+    if (macho_file.got_section_index) |sect_id| {
+        const header = &macho_file.sections.items(.header)[sect_id];
+        header.size = macho_file.got_table.count() * @sizeOf(u64);
+        header.@"align" = 3;
+    }
+
+    if (macho_file.tlv_ptr_section_index) |sect_id| {
+        const header = &macho_file.sections.items(.header)[sect_id];
+        header.size = macho_file.tlv_ptr_table.count() * @sizeOf(u64);
+        header.@"align" = 3;
+    }
+
+    const cpu_arch = macho_file.base.options.target.cpu.arch;
+
+    if (macho_file.stubs_section_index) |sect_id| {
+        const header = &macho_file.sections.items(.header)[sect_id];
+        header.size = macho_file.stub_table.count() * stubs.stubSize(cpu_arch);
+        header.@"align" = stubs.stubAlignment(cpu_arch);
+    }
+
+    if (macho_file.stub_helper_section_index) |sect_id| {
+        const header = &macho_file.sections.items(.header)[sect_id];
+        header.size = macho_file.stub_table.count() * stubs.stubHelperSize(cpu_arch) +
+            stubs.stubHelperPreambleSize(cpu_arch);
+        header.@"align" = stubs.stubAlignment(cpu_arch);
+    }
+
+    if (macho_file.la_symbol_ptr_section_index) |sect_id| {
+        const header = &macho_file.sections.items(.header)[sect_id];
+        header.size = macho_file.stub_table.count() * @sizeOf(u64);
+        header.@"align" = 3;
+    }
+}
+
+fn allocateSegments(macho_file: *MachO) !void {
+    const gpa = macho_file.base.allocator;
+    for (macho_file.segments.items, 0..) |*segment, segment_index| {
+        const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT");
+        const base_size = if (is_text_segment) try load_commands.calcMinHeaderPad(gpa, macho_file.base.options, .{
+            .segments = macho_file.segments.items,
+            .dylibs = macho_file.dylibs.items,
+            .referenced_dylibs = macho_file.referenced_dylibs.keys(),
+        }) else 0;
+        try allocateSegment(macho_file, @as(u8, @intCast(segment_index)), base_size);
+    }
+}
+
+fn getSegmentAllocBase(macho_file: *MachO, segment_index: u8) struct { vmaddr: u64, fileoff: u64 } {
+    if (segment_index > 0) {
+        const prev_segment = macho_file.segments.items[segment_index - 1];
+        return .{
+            .vmaddr = prev_segment.vmaddr + prev_segment.vmsize,
+            .fileoff = prev_segment.fileoff + prev_segment.filesize,
+        };
+    }
+    return .{ .vmaddr = 0, .fileoff = 0 };
+}
+
+fn allocateSegment(macho_file: *MachO, segment_index: u8, init_size: u64) !void {
+    const segment = &macho_file.segments.items[segment_index];
+
+    if (mem.eql(u8, segment.segName(), "__PAGEZERO")) return; // allocated upon creation
+
+    const base = getSegmentAllocBase(macho_file, segment_index);
+    segment.vmaddr = base.vmaddr;
+    segment.fileoff = base.fileoff;
+    segment.filesize = init_size;
+    segment.vmsize = init_size;
+
+    // Allocate the sections according to their alignment at the beginning of the segment.
+    const indexes = macho_file.getSectionIndexes(segment_index);
+    var start = init_size;
+
+    const slice = macho_file.sections.slice();
+    for (slice.items(.header)[indexes.start..indexes.end], 0..) |*header, sect_id| {
+        const alignment = try math.powi(u32, 2, header.@"align");
+        const start_aligned = mem.alignForward(u64, start, alignment);
+        const n_sect = @as(u8, @intCast(indexes.start + sect_id + 1));
+
+        header.offset = if (header.isZerofill())
+            0
+        else
+            @as(u32, @intCast(segment.fileoff + start_aligned));
+        header.addr = segment.vmaddr + start_aligned;
+
+        if (slice.items(.first_atom_index)[indexes.start + sect_id]) |first_atom_index| {
+            var atom_index = first_atom_index;
+
+            log.debug("allocating local symbols in sect({d}, '{s},{s}')", .{
+                n_sect,
+                header.segName(),
+                header.sectName(),
+            });
+
+            while (true) {
+                const atom = macho_file.getAtom(atom_index);
+                const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
+                sym.n_value += header.addr;
+                sym.n_sect = n_sect;
+
+                log.debug("  ATOM(%{d}, '{s}') @{x}", .{
+                    atom.sym_index,
+                    macho_file.getSymbolName(atom.getSymbolWithLoc()),
+                    sym.n_value,
+                });
+
+                if (atom.getFile() != null) {
+                    // Update each symbol contained within the atom
+                    var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
+                    while (it.next()) |sym_loc| {
+                        const inner_sym = macho_file.getSymbolPtr(sym_loc);
+                        inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
+                            macho_file,
+                            atom_index,
+                            sym_loc.sym_index,
+                        );
+                        inner_sym.n_sect = n_sect;
+                    }
+
+                    // If there is a section alias, update it now too
+                    if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
+                        const alias = macho_file.getSymbolPtr(sym_loc);
+                        alias.n_value = sym.n_value;
+                        alias.n_sect = n_sect;
+                    }
+                }
+
+                if (atom.next_index) |next_index| {
+                    atom_index = next_index;
+                } else break;
+            }
+        }
+
+        start = start_aligned + header.size;
+
+        if (!header.isZerofill()) {
+            segment.filesize = start;
+        }
+        segment.vmsize = start;
+    }
+
+    const page_size = MachO.getPageSize(macho_file.base.options.target.cpu.arch);
+    segment.filesize = mem.alignForward(u64, segment.filesize, page_size);
+    segment.vmsize = mem.alignForward(u64, segment.vmsize, page_size);
+}
+
+const std = @import("std");
+const build_options = @import("build_options");
+const assert = std.debug.assert;
+const dwarf = std.dwarf;
+const fs = std.fs;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const math = std.math;
+const mem = std.mem;
+
+const aarch64 = @import("../../arch/aarch64/bits.zig");
+const calcUuid = @import("uuid.zig").calcUuid;
+const dead_strip = @import("dead_strip.zig");
+const eh_frame = @import("eh_frame.zig");
+const fat = @import("fat.zig");
+const link = @import("../../link.zig");
+const load_commands = @import("load_commands.zig");
+const stubs = @import("stubs.zig");
+const thunks = @import("thunks.zig");
+const trace = @import("../../tracy.zig").trace;
+
+const Allocator = mem.Allocator;
+const Archive = @import("Archive.zig");
+const Atom = @import("Atom.zig");
+const Cache = std.Build.Cache;
+const CodeSignature = @import("CodeSignature.zig");
+const Compilation = @import("../../Compilation.zig");
+const Dylib = @import("Dylib.zig");
+const MachO = @import("../MachO.zig");
+const Md5 = std.crypto.hash.Md5;
+const LibStub = @import("../tapi.zig").LibStub;
+const Object = @import("Object.zig");
+const Section = MachO.Section;
+const StringTable = @import("../strtab.zig").StringTable;
+const SymbolWithLoc = MachO.SymbolWithLoc;
+const TableSection = @import("../table_section.zig").TableSection;
+const Trie = @import("Trie.zig");
+const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO.zig
@@ -405,9 +405,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
             const in_file = try std.fs.cwd().openFile(path, .{});
             defer in_file.close();
 
-            parseLibrary(
-                self,
-                self.base.allocator,
+            self.parseLibrary(
                 in_file,
                 path,
                 lib,
@@ -421,24 +419,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
             };
         }
 
-        parseDependentLibs(self, self.base.allocator, &dependent_libs, &self.base.options) catch |err| {
+        self.parseDependentLibs(&dependent_libs, &self.base.options) catch |err| {
             // TODO convert to error
             log.err("parsing dependent libraries failed with err {s}", .{@errorName(err)});
         };
     }
 
-    if (self.dyld_stub_binder_index == null) {
-        self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder", .add_got);
-    }
-    if (!self.base.options.single_threaded) {
-        _ = try self.addUndefined("__tlv_bootstrap", .none);
-    }
-
-    try self.createMhExecuteHeaderSymbol();
-
     var actions = std.ArrayList(ResolveAction).init(self.base.allocator);
     defer actions.deinit();
-    try self.resolveSymbolsInDylibs(&actions);
+    try self.resolveSymbols(&actions);
 
     if (self.getEntryPoint() == null) {
         self.error_flags.no_entry_point_found = true;
@@ -527,14 +516,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
 
     try self.writeLinkeditSegmentData();
 
-    const target = self.base.options.target;
-    const requires_codesig = blk: {
-        if (self.base.options.entitlements) |_| break :blk true;
-        if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator))
-            break :blk true;
-        break :blk false;
-    };
-    var codesig: ?CodeSignature = if (requires_codesig) blk: {
+    var codesig: ?CodeSignature = if (self.requiresCodeSignature()) blk: {
         // Preallocate space for the code signature.
         // We need to do this at this stage so that we have the load commands with proper values
         // written out to the file.
@@ -596,14 +578,14 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
 
     try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer);
 
-    if (requires_codesig) {
+    if (codesig != null) {
         try lc_writer.writeStruct(self.codesig_cmd);
     }
 
     const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
     try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
     try self.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
-    try self.writeUuid(comp, uuid_cmd_offset, requires_codesig);
+    try self.writeUuid(comp, uuid_cmd_offset, codesig != null);
 
     if (codesig) |*csig| {
         try self.writeCodeSignature(comp, csig); // code signing always comes last
@@ -729,8 +711,7 @@ fn resolveLib(
 }
 
 pub fn parsePositional(
-    ctx: anytype,
-    gpa: Allocator,
+    self: *MachO,
     file: std.fs.File,
     path: []const u8,
     must_link: bool,
@@ -741,9 +722,9 @@ pub fn parsePositional(
     defer tracy.end();
 
     if (Object.isObject(file)) {
-        try parseObject(ctx, gpa, file, path, link_options);
+        try self.parseObject(file, path, link_options);
     } else {
-        try parseLibrary(ctx, gpa, file, path, .{
+        try self.parseLibrary(file, path, .{
             .path = null,
             .needed = false,
             .weak = false,
@@ -752,8 +733,7 @@ pub fn parsePositional(
 }
 
 fn parseObject(
-    ctx: anytype,
-    gpa: Allocator,
+    self: *MachO,
     file: std.fs.File,
     path: []const u8,
     link_options: *const link.Options,
@@ -761,6 +741,7 @@ fn parseObject(
     const tracy = trace(@src());
     defer tracy.end();
 
+    const gpa = self.base.allocator;
     const mtime: u64 = mtime: {
         const stat = file.stat() catch break :mtime 0;
         break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000)));
@@ -776,7 +757,7 @@ fn parseObject(
     };
     errdefer object.deinit(gpa);
     try object.parse(gpa);
-    try ctx.objects.append(gpa, object);
+    try self.objects.append(gpa, object);
 
     const cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) {
         macho.CPU_TYPE_ARM64 => .aarch64,
@@ -796,8 +777,7 @@ fn parseObject(
 }
 
 pub fn parseLibrary(
-    ctx: anytype,
-    gpa: Allocator,
+    self: *MachO,
     file: std.fs.File,
     path: []const u8,
     lib: link.SystemLib,
@@ -811,16 +791,16 @@ pub fn parseLibrary(
     const cpu_arch = link_options.target.cpu.arch;
 
     if (fat.isFatLibrary(file)) {
-        const offset = parseFatLibrary(ctx, file, path, cpu_arch) catch |err| switch (err) {
+        const offset = self.parseFatLibrary(file, path, cpu_arch) catch |err| switch (err) {
             error.MissingArch => return,
             else => |e| return e,
         };
         try file.seekTo(offset);
 
         if (Archive.isArchive(file, offset)) {
-            try parseArchive(ctx, gpa, path, offset, must_link, cpu_arch);
+            try self.parseArchive(path, offset, must_link, cpu_arch);
         } else if (Dylib.isDylib(file, offset)) {
-            try parseDylib(ctx, gpa, file, path, offset, dependent_libs, link_options, .{
+            try self.parseDylib(file, path, offset, dependent_libs, link_options, .{
                 .needed = lib.needed,
                 .weak = lib.weak,
             });
@@ -830,14 +810,14 @@ pub fn parseLibrary(
             return;
         }
     } else if (Archive.isArchive(file, 0)) {
-        try parseArchive(ctx, gpa, path, 0, must_link, cpu_arch);
+        try self.parseArchive(path, 0, must_link, cpu_arch);
     } else if (Dylib.isDylib(file, 0)) {
-        try parseDylib(ctx, gpa, file, path, 0, dependent_libs, link_options, .{
+        try self.parseDylib(file, path, 0, dependent_libs, link_options, .{
             .needed = lib.needed,
             .weak = lib.weak,
         });
     } else {
-        parseLibStub(ctx, gpa, file, path, dependent_libs, link_options, .{
+        self.parseLibStub(file, path, dependent_libs, link_options, .{
             .needed = lib.needed,
             .weak = lib.weak,
         }) catch |err| switch (err) {
@@ -852,12 +832,12 @@ pub fn parseLibrary(
 }
 
 pub fn parseFatLibrary(
-    ctx: anytype,
+    self: *MachO,
     file: std.fs.File,
     path: []const u8,
     cpu_arch: std.Target.Cpu.Arch,
 ) !u64 {
-    _ = ctx;
+    _ = self;
     var buffer: [2]fat.Arch = undefined;
     const fat_archs = try fat.parseArchs(file, &buffer);
     const offset = for (fat_archs) |arch| {
@@ -871,13 +851,13 @@ pub fn parseFatLibrary(
 }
 
 fn parseArchive(
-    ctx: anytype,
-    gpa: Allocator,
+    self: *MachO,
     path: []const u8,
     fat_offset: u64,
     must_link: bool,
     cpu_arch: std.Target.Cpu.Arch,
 ) !void {
+    const gpa = self.base.allocator;
 
     // We take ownership of the file so that we can store it for the duration of symbol resolution.
     // TODO we shouldn't need to do that and could pre-parse the archive like we do for zld/ELF?
@@ -929,10 +909,10 @@ fn parseArchive(
         }
         for (offsets.keys()) |off| {
             const object = try archive.parseObject(gpa, off);
-            try ctx.objects.append(gpa, object);
+            try self.objects.append(gpa, object);
         }
     } else {
-        try ctx.archives.append(gpa, archive);
+        try self.archives.append(gpa, archive);
     }
 }
 
@@ -944,8 +924,7 @@ const DylibOpts = struct {
 };
 
 fn parseDylib(
-    ctx: anytype,
-    gpa: Allocator,
+    self: *MachO,
     file: std.fs.File,
     path: []const u8,
     offset: u64,
@@ -953,6 +932,7 @@ fn parseDylib(
     link_options: *const link.Options,
     dylib_options: DylibOpts,
 ) !void {
+    const gpa = self.base.allocator;
     const self_cpu_arch = link_options.target.cpu.arch;
 
     const file_stat = try file.stat();
@@ -968,7 +948,7 @@ fn parseDylib(
 
     try dylib.parseFromBinary(
         gpa,
-        @intCast(ctx.dylibs.items.len), // TODO defer it till later
+        @intCast(self.dylibs.items.len), // TODO defer it till later
         dependent_libs,
         path,
         contents,
@@ -991,7 +971,7 @@ fn parseDylib(
 
     // TODO verify platform
 
-    addDylib(ctx, gpa, dylib, link_options, .{
+    self.addDylib(dylib, link_options, .{
         .needed = dylib_options.needed,
         .weak = dylib_options.weak,
     }) catch |err| switch (err) {
@@ -1001,14 +981,14 @@ fn parseDylib(
 }
 
 fn parseLibStub(
-    ctx: anytype,
-    gpa: Allocator,
+    self: *MachO,
     file: std.fs.File,
     path: []const u8,
     dependent_libs: anytype,
     link_options: *const link.Options,
     dylib_options: DylibOpts,
 ) !void {
+    const gpa = self.base.allocator;
     var lib_stub = try LibStub.loadFromFile(gpa, file);
     defer lib_stub.deinit();
 
@@ -1023,12 +1003,12 @@ fn parseLibStub(
         gpa,
         link_options.target,
         lib_stub,
-        @intCast(ctx.dylibs.items.len), // TODO defer it till later
+        @intCast(self.dylibs.items.len), // TODO defer it till later
         dependent_libs,
         path,
     );
 
-    addDylib(ctx, gpa, dylib, link_options, .{
+    self.addDylib(dylib, link_options, .{
         .needed = dylib_options.needed,
         .weak = dylib_options.weak,
     }) catch |err| switch (err) {
@@ -1038,8 +1018,7 @@ fn parseLibStub(
 }
 
 fn addDylib(
-    ctx: anytype,
-    gpa: Allocator,
+    self: *MachO,
     dylib: Dylib,
     link_options: *const link.Options,
     dylib_options: DylibOpts,
@@ -1055,28 +1034,24 @@ fn addDylib(
         }
     }
 
-    const gop = try ctx.dylibs_map.getOrPut(gpa, dylib.id.?.name);
+    const gpa = self.base.allocator;
+    const gop = try self.dylibs_map.getOrPut(gpa, dylib.id.?.name);
     if (gop.found_existing) return error.DylibAlreadyExists;
 
-    gop.value_ptr.* = @as(u16, @intCast(ctx.dylibs.items.len));
-    try ctx.dylibs.append(gpa, dylib);
+    gop.value_ptr.* = @as(u16, @intCast(self.dylibs.items.len));
+    try self.dylibs.append(gpa, dylib);
 
     const should_link_dylib_even_if_unreachable = blk: {
         if (link_options.dead_strip_dylibs and !dylib_options.needed) break :blk false;
-        break :blk !(dylib_options.dependent or ctx.referenced_dylibs.contains(gop.value_ptr.*));
+        break :blk !(dylib_options.dependent or self.referenced_dylibs.contains(gop.value_ptr.*));
     };
 
     if (should_link_dylib_even_if_unreachable) {
-        try ctx.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {});
+        try self.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {});
     }
 }
 
-pub fn parseDependentLibs(
-    ctx: anytype,
-    gpa: Allocator,
-    dependent_libs: anytype,
-    link_options: *const link.Options,
-) !void {
+pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype, link_options: *const link.Options) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1085,6 +1060,7 @@ pub fn parseDependentLibs(
     // 2) afterwards, we parse dependents of the included dylibs
     // TODO this should not be performed if the user specifies `-flat_namespace` flag.
     // See ld64 manpages.
+    const gpa = self.base.allocator;
     var arena_alloc = std.heap.ArenaAllocator.init(gpa);
     const arena = arena_alloc.allocator();
     defer arena_alloc.deinit();
@@ -1092,9 +1068,9 @@ pub fn parseDependentLibs(
     outer: while (dependent_libs.readItem()) |dep_id| {
         defer dep_id.id.deinit(gpa);
 
-        if (ctx.dylibs_map.contains(dep_id.id.name)) continue;
+        if (self.dylibs_map.contains(dep_id.id.name)) continue;
 
-        const weak = ctx.dylibs.items[dep_id.parent].weak;
+        const weak = self.dylibs.items[dep_id.parent].weak;
         const has_ext = blk: {
             const basename = fs.path.basename(dep_id.id.name);
             break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
@@ -1121,7 +1097,7 @@ pub fn parseDependentLibs(
             log.debug("trying dependency at fully resolved path {s}", .{full_path});
 
             const offset: u64 = if (fat.isFatLibrary(file)) blk: {
-                const offset = parseFatLibrary(ctx, file, full_path, link_options.target.cpu.arch) catch |err| switch (err) {
+                const offset = self.parseFatLibrary(file, full_path, link_options.target.cpu.arch) catch |err| switch (err) {
                     error.MissingArch => break,
                     else => |e| return e,
                 };
@@ -1130,12 +1106,12 @@ pub fn parseDependentLibs(
             } else 0;
 
             if (Dylib.isDylib(file, offset)) {
-                try parseDylib(ctx, gpa, file, full_path, offset, dependent_libs, link_options, .{
+                try self.parseDylib(file, full_path, offset, dependent_libs, link_options, .{
                     .dependent = true,
                     .weak = weak,
                 });
             } else {
-                parseLibStub(ctx, gpa, file, full_path, dependent_libs, link_options, .{
+                self.parseLibStub(file, full_path, dependent_libs, link_options, .{
                     .dependent = true,
                     .weak = weak,
                 }) catch |err| switch (err) {
@@ -1394,7 +1370,7 @@ fn markRelocsDirtyByAddress(self: *MachO, addr: u64) void {
     }
 }
 
-pub fn allocateSpecialSymbols(self: anytype) !void {
+pub fn allocateSpecialSymbols(self: *MachO) !void {
     for (&[_][]const u8{
         "___dso_handle",
         "__mh_execute_header",
@@ -1432,24 +1408,82 @@ pub fn createAtom(self: *MachO, sym_index: u32, opts: CreateAtomOpts) !Atom.Inde
     return index;
 }
 
+pub fn createTentativeDefAtoms(self: *MachO) !void {
+    const gpa = self.base.allocator;
+
+    for (self.globals.items) |global| {
+        const sym = self.getSymbolPtr(global);
+        if (!sym.tentative()) continue;
+        if (sym.n_desc == N_DEAD) continue;
+
+        log.debug("creating tentative definition for ATOM(%{d}, '{s}') in object({?})", .{
+            global.sym_index, self.getSymbolName(global), global.file,
+        });
+
+        // Convert any tentative definition into a regular symbol and allocate
+        // text blocks for each tentative definition.
+        const size = sym.n_value;
+        const alignment = (sym.n_desc >> 8) & 0x0f;
+
+        if (self.bss_section_index == null) {
+            self.bss_section_index = try self.initSection("__DATA", "__bss", .{
+                .flags = macho.S_ZEROFILL,
+            });
+        }
+
+        sym.* = .{
+            .n_strx = sym.n_strx,
+            .n_type = macho.N_SECT | macho.N_EXT,
+            .n_sect = self.bss_section_index.? + 1,
+            .n_desc = 0,
+            .n_value = 0,
+        };
+
+        const atom_index = try self.createAtom(global.sym_index, .{
+            .size = size,
+            .alignment = alignment,
+        });
+        const atom = self.getAtomPtr(atom_index);
+        atom.file = global.file;
+
+        self.addAtomToSection(atom_index);
+
+        assert(global.getFile() != null);
+        const object = &self.objects.items[global.getFile().?];
+        try object.atoms.append(gpa, atom_index);
+        object.atom_by_index_table[global.sym_index] = atom_index;
+    }
+}
+
 fn createDyldPrivateAtom(self: *MachO) !void {
     if (self.dyld_private_atom_index != null) return;
 
     const sym_index = try self.allocateSymbol();
-    const atom_index = try self.createAtom(sym_index, .{});
+    const atom_index = try self.createAtom(sym_index, .{
+        .size = @sizeOf(u64),
+        .alignment = 3,
+    });
     try self.atom_by_index_table.putNoClobber(self.base.allocator, sym_index, atom_index);
-    const atom = self.getAtomPtr(atom_index);
-    atom.size = @sizeOf(u64);
 
+    if (self.data_section_index == null) {
+        self.data_section_index = try self.initSection("__DATA", "__data", .{});
+    }
+
+    const atom = self.getAtom(atom_index);
     const sym = atom.getSymbolPtr(self);
     sym.n_type = macho.N_SECT;
     sym.n_sect = self.data_section_index.? + 1;
     self.dyld_private_atom_index = atom_index;
 
-    sym.n_value = try self.allocateAtom(atom_index, atom.size, @alignOf(u64));
-    log.debug("allocated dyld_private atom at 0x{x}", .{sym.n_value});
-    var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
-    try self.writeAtom(atom_index, &buffer);
+    switch (self.mode) {
+        .zld => self.addAtomToSection(atom_index),
+        .incremental => {
+            sym.n_value = try self.allocateAtom(atom_index, atom.size, @alignOf(u64));
+            log.debug("allocated dyld_private atom at 0x{x}", .{sym.n_value});
+            var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
+            try self.writeAtom(atom_index, &buffer);
+        },
+    }
 }
 
 fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: SymbolWithLoc) !Atom.Index {
@@ -1485,7 +1519,7 @@ fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: S
     return atom_index;
 }
 
-fn createMhExecuteHeaderSymbol(self: *MachO) !void {
+pub fn createMhExecuteHeaderSymbol(self: *MachO) !void {
     if (self.base.options.output_mode != .Exe) return;
 
     const gpa = self.base.allocator;
@@ -1501,10 +1535,17 @@ fn createMhExecuteHeaderSymbol(self: *MachO) !void {
     };
 
     const gop = try self.getOrPutGlobalPtr("__mh_execute_header");
+    if (gop.found_existing) {
+        const global = gop.value_ptr.*;
+        if (global.getFile()) |file| {
+            const global_object = &self.objects.items[file];
+            global_object.globals_lookup[global.sym_index] = self.getGlobalIndex("__mh_execute_header").?;
+        }
+    }
     gop.value_ptr.* = sym_loc;
 }
 
-fn createDsoHandleSymbol(self: *MachO) !void {
+pub fn createDsoHandleSymbol(self: *MachO) !void {
     const global = self.getGlobalPtr("___dso_handle") orelse return;
     if (!self.getSymbol(global.*).undf()) return;
 
@@ -1519,10 +1560,51 @@ fn createDsoHandleSymbol(self: *MachO) !void {
         .n_desc = macho.N_WEAK_DEF,
         .n_value = 0,
     };
+    const global_index = self.getGlobalIndex("___dso_handle").?;
+    if (global.getFile()) |file| {
+        const global_object = &self.objects.items[file];
+        global_object.globals_lookup[global.sym_index] = global_index;
+    }
     global.* = sym_loc;
     _ = self.unresolved.swapRemove(self.getGlobalIndex("___dso_handle").?);
 }
 
+pub fn resolveSymbols(self: *MachO, actions: *std.ArrayList(ResolveAction)) !void {
+    // We add the specified entrypoint as the first unresolved symbols so that
+    // we search for it in libraries should there be no object files specified
+    // on the linker line.
+    if (self.base.options.output_mode == .Exe) {
+        const entry_name = self.base.options.entry orelse load_commands.default_entry_point;
+        _ = try self.addUndefined(entry_name, .none);
+    }
+
+    // Force resolution of any symbols requested by the user.
+    for (self.base.options.force_undefined_symbols.keys()) |sym_name| {
+        _ = try self.addUndefined(sym_name, .none);
+    }
+
+    for (self.objects.items, 0..) |_, object_id| {
+        try self.resolveSymbolsInObject(@as(u32, @intCast(object_id)));
+    }
+
+    try self.resolveSymbolsInArchives();
+
+    // Finally, force resolution of dyld_stub_binder if there are imports
+    // requested.
+    if (self.unresolved.count() > 0 and self.dyld_stub_binder_index == null) {
+        self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder", .add_got);
+    }
+    if (!self.base.options.single_threaded and self.mode == .incremental) {
+        _ = try self.addUndefined("__tlv_bootstrap", .none);
+    }
+
+    try self.resolveSymbolsInDylibs(actions);
+
+    try self.createMhExecuteHeaderSymbol();
+    try self.createDsoHandleSymbol();
+    try self.resolveSymbolsAtLoading();
+}
+
 fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void {
     const gpa = self.base.allocator;
     const sym = self.getSymbol(current);
@@ -1536,6 +1618,7 @@ fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void {
         }
         return;
     }
+    const global_index = self.getGlobalIndex(sym_name).?;
     const global = gop.value_ptr.*;
     const global_sym = self.getSymbol(global);
 
@@ -1566,7 +1649,22 @@ fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void {
     const sym_is_weak = sym.sect() and (sym.weakDef() or sym.pext());
     const global_is_weak = global_sym.sect() and (global_sym.weakDef() or global_sym.pext());
 
-    if (sym_is_strong and global_is_strong) return error.MultipleSymbolDefinitions;
+    if (sym_is_strong and global_is_strong) {
+        log.err("symbol '{s}' defined multiple times", .{sym_name});
+        if (global.getFile()) |file| {
+            log.err("  first definition in '{s}'", .{self.objects.items[file].name});
+        }
+        if (current.getFile()) |file| {
+            log.err("  next definition in '{s}'", .{self.objects.items[file].name});
+        }
+        return error.MultipleSymbolDefinitions;
+    }
+
+    if (current.getFile()) |file| {
+        const object = &self.objects.items[file];
+        object.globals_lookup[current.sym_index] = global_index;
+    }
+
     if (global_is_strong) return;
     if (sym_is_weak and global_is_weak) return;
     if (sym.tentative() and global_sym.tentative()) {
@@ -1574,11 +1672,88 @@ fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void {
     }
     if (sym.undf() and !sym.tentative()) return;
 
-    _ = self.unresolved.swapRemove(self.getGlobalIndex(sym_name).?);
+    if (global.getFile()) |file| {
+        const global_object = &self.objects.items[file];
+        global_object.globals_lookup[global.sym_index] = global_index;
+    }
+    _ = self.unresolved.swapRemove(global_index);
 
     gop.value_ptr.* = current;
 }
 
+fn resolveSymbolsInObject(self: *MachO, object_id: u32) !void {
+    const object = &self.objects.items[object_id];
+    const in_symtab = object.in_symtab orelse return;
+
+    log.debug("resolving symbols in '{s}'", .{object.name});
+
+    var sym_index: u32 = 0;
+    while (sym_index < in_symtab.len) : (sym_index += 1) {
+        const sym = &object.symtab[sym_index];
+        const sym_name = object.getSymbolName(sym_index);
+
+        if (sym.stab()) {
+            log.err("unhandled symbol type: stab", .{});
+            log.err("  symbol '{s}'", .{sym_name});
+            log.err("  first definition in '{s}'", .{object.name});
+            return error.UnhandledSymbolType;
+        }
+
+        if (sym.indr()) {
+            log.err("unhandled symbol type: indirect", .{});
+            log.err("  symbol '{s}'", .{sym_name});
+            log.err("  first definition in '{s}'", .{object.name});
+            return error.UnhandledSymbolType;
+        }
+
+        if (sym.abs()) {
+            log.err("unhandled symbol type: absolute", .{});
+            log.err("  symbol '{s}'", .{sym_name});
+            log.err("  first definition in '{s}'", .{object.name});
+            return error.UnhandledSymbolType;
+        }
+
+        if (sym.sect() and !sym.ext()) {
+            log.debug("symbol '{s}' local to object {s}; skipping...", .{
+                sym_name,
+                object.name,
+            });
+            continue;
+        }
+
+        try self.resolveGlobalSymbol(.{ .sym_index = sym_index, .file = object_id + 1 });
+    }
+}
+
+fn resolveSymbolsInArchives(self: *MachO) !void {
+    if (self.archives.items.len == 0) return;
+
+    const gpa = self.base.allocator;
+    var next_sym: usize = 0;
+    loop: while (next_sym < self.unresolved.count()) {
+        const global = self.globals.items[self.unresolved.keys()[next_sym]];
+        const sym_name = self.getSymbolName(global);
+
+        for (self.archives.items) |archive| {
+            // Check if the entry exists in a static archive.
+            const offsets = archive.toc.get(sym_name) orelse {
+                // No hit.
+                continue;
+            };
+            assert(offsets.items.len > 0);
+
+            const object_id = @as(u16, @intCast(self.objects.items.len));
+            const object = try archive.parseObject(gpa, offsets.items[0]);
+            try self.objects.append(gpa, object);
+            try self.resolveSymbolsInObject(object_id);
+
+            continue :loop;
+        }
+
+        next_sym += 1;
+    }
+}
+
 fn resolveSymbolsInDylibs(self: *MachO, actions: *std.ArrayList(ResolveAction)) !void {
     if (self.dylibs.items.len == 0) return;
 
@@ -1608,6 +1783,7 @@ fn resolveSymbolsInDylibs(self: *MachO, actions: *std.ArrayList(ResolveAction))
 
             if (self.unresolved.fetchSwapRemove(global_index)) |entry| blk: {
                 if (!sym.undf()) break :blk;
+                if (self.mode == .zld) break :blk;
                 try actions.append(.{ .kind = entry.value, .target = global });
             }
 
@@ -1618,6 +1794,42 @@ fn resolveSymbolsInDylibs(self: *MachO, actions: *std.ArrayList(ResolveAction))
     }
 }
 
+fn resolveSymbolsAtLoading(self: *MachO) !void {
+    const is_lib = self.base.options.output_mode == .Lib;
+    const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
+    const allow_undef = is_dyn_lib and (self.base.options.allow_shlib_undefined orelse false);
+
+    var next_sym: usize = 0;
+    while (next_sym < self.unresolved.count()) {
+        const global_index = self.unresolved.keys()[next_sym];
+        const global = self.globals.items[global_index];
+        const sym = self.getSymbolPtr(global);
+
+        if (sym.discarded()) {
+            sym.* = .{
+                .n_strx = 0,
+                .n_type = macho.N_UNDF,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = 0,
+            };
+            _ = self.unresolved.swapRemove(global_index);
+            continue;
+        } else if (allow_undef) {
+            const n_desc = @as(
+                u16,
+                @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP * @as(i16, @intCast(macho.N_SYMBOL_RESOLVER))),
+            );
+            sym.n_type = macho.N_EXT;
+            sym.n_desc = n_desc;
+            _ = self.unresolved.swapRemove(global_index);
+            continue;
+        }
+
+        next_sym += 1;
+    }
+}
+
 pub fn deinit(self: *MachO) void {
     const gpa = self.base.allocator;
 
@@ -1638,7 +1850,6 @@ pub fn deinit(self: *MachO) void {
     self.thunks.deinit(gpa);
 
     self.strtab.deinit(gpa);
-
     self.locals.deinit(gpa);
     self.globals.deinit(gpa);
     self.locals_free_list.deinit(gpa);
@@ -1653,6 +1864,14 @@ pub fn deinit(self: *MachO) void {
         self.resolver.deinit(gpa);
     }
 
+    for (self.objects.items) |*object| {
+        object.deinit(gpa);
+    }
+    self.objects.deinit(gpa);
+    for (self.archives.items) |*archive| {
+        archive.deinit(gpa);
+    }
+    self.archives.deinit(gpa);
     for (self.dylibs.items) |*dylib| {
         dylib.deinit(gpa);
     }
@@ -1842,20 +2061,55 @@ fn allocateGlobal(self: *MachO) !u32 {
     return index;
 }
 
-fn addGotEntry(self: *MachO, target: SymbolWithLoc) !void {
+pub fn addGotEntry(self: *MachO, target: SymbolWithLoc) !void {
     if (self.got_table.lookup.contains(target)) return;
     const got_index = try self.got_table.allocateEntry(self.base.allocator, target);
-    try self.writeOffsetTableEntry(got_index);
-    self.got_table_count_dirty = true;
-    self.markRelocsDirtyByTarget(target);
+    if (self.got_section_index == null) {
+        self.got_section_index = try self.initSection("__DATA_CONST", "__got", .{
+            .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
+        });
+    }
+    if (self.mode == .incremental) {
+        try self.writeOffsetTableEntry(got_index);
+        self.got_table_count_dirty = true;
+        self.markRelocsDirtyByTarget(target);
+    }
 }
 
-fn addStubEntry(self: *MachO, target: SymbolWithLoc) !void {
+pub fn addStubEntry(self: *MachO, target: SymbolWithLoc) !void {
     if (self.stub_table.lookup.contains(target)) return;
     const stub_index = try self.stub_table.allocateEntry(self.base.allocator, target);
-    try self.writeStubTableEntry(stub_index);
-    self.stub_table_count_dirty = true;
-    self.markRelocsDirtyByTarget(target);
+    if (self.stubs_section_index == null) {
+        self.stubs_section_index = try self.initSection("__TEXT", "__stubs", .{
+            .flags = macho.S_SYMBOL_STUBS |
+                macho.S_ATTR_PURE_INSTRUCTIONS |
+                macho.S_ATTR_SOME_INSTRUCTIONS,
+            .reserved2 = stubs.stubSize(self.base.options.target.cpu.arch),
+        });
+        self.stub_helper_section_index = try self.initSection("__TEXT", "__stub_helper", .{
+            .flags = macho.S_REGULAR |
+                macho.S_ATTR_PURE_INSTRUCTIONS |
+                macho.S_ATTR_SOME_INSTRUCTIONS,
+        });
+        self.la_symbol_ptr_section_index = try self.initSection("__DATA", "__la_symbol_ptr", .{
+            .flags = macho.S_LAZY_SYMBOL_POINTERS,
+        });
+    }
+    if (self.mode == .incremental) {
+        try self.writeStubTableEntry(stub_index);
+        self.stub_table_count_dirty = true;
+        self.markRelocsDirtyByTarget(target);
+    }
+}
+
+pub fn addTlvPtrEntry(self: *MachO, target: SymbolWithLoc) !void {
+    if (self.tlv_ptr_table.lookup.contains(target)) return;
+    _ = try self.tlv_ptr_table.allocateEntry(self.gpa, target);
+    if (self.tlv_ptr_section_index == null) {
+        self.tlv_ptr_section_index = try self.initSection("__DATA", "__thread_ptrs", .{
+            .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
+        });
+    }
 }
 
 pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
@@ -2758,16 +3012,10 @@ const InitSectionOpts = struct {
     reserved2: u32 = 0,
 };
 
-pub fn initSection(
-    gpa: Allocator,
-    ctx: anytype,
-    segname: []const u8,
-    sectname: []const u8,
-    opts: InitSectionOpts,
-) !u8 {
+pub fn initSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: InitSectionOpts) !u8 {
     log.debug("creating section '{s},{s}'", .{ segname, sectname });
-    const index = @as(u8, @intCast(ctx.sections.slice().len));
-    try ctx.sections.append(gpa, .{
+    const index = @as(u8, @intCast(self.sections.slice().len));
+    try self.sections.append(self.base.allocator, .{
         .segment_index = undefined, // Segments will be created automatically later down the pipeline
         .header = .{
             .sectname = makeStaticString(sectname),
@@ -2822,7 +3070,7 @@ fn allocateSection(self: *MachO, segname: []const u8, sectname: []const u8, opts
         .cmdsize = @sizeOf(macho.segment_command_64) + @sizeOf(macho.section_64),
     };
 
-    const sect_id = try initSection(gpa, self, sectname, segname, .{
+    const sect_id = try self.initSection(sectname, segname, .{
         .flags = opts.flags,
         .reserved2 = opts.reserved2,
     });
@@ -2918,10 +3166,29 @@ fn growSectionVirtualMemory(self: *MachO, sect_id: u8, needed_size: u64) !void {
     }
 }
 
+pub fn addAtomToSection(self: *MachO, atom_index: Atom.Index) void {
+    assert(self.mode == .zld);
+    const atom = self.getAtomPtr(atom_index);
+    const sym = self.getSymbol(atom.getSymbolWithLoc());
+    var section = self.sections.get(sym.n_sect - 1);
+    if (section.header.size > 0) {
+        const last_atom = self.getAtomPtr(section.last_atom_index.?);
+        last_atom.next_index = atom_index;
+        atom.prev_index = section.last_atom_index;
+    } else {
+        section.first_atom_index = atom_index;
+    }
+    section.last_atom_index = atom_index;
+    section.header.size += atom.size;
+    self.sections.set(sym.n_sect - 1, section);
+}
+
 fn allocateAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: u64) !u64 {
     const tracy = trace(@src());
     defer tracy.end();
 
+    assert(self.mode == .incremental);
+
     const atom = self.getAtom(atom_index);
     const sect_id = atom.getSymbol(self).n_sect - 1;
     const segment = self.getSegmentPtr(sect_id);
@@ -3048,7 +3315,7 @@ pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u
     return self.addUndefined(sym_name, .add_stub);
 }
 
-pub fn writeSegmentHeaders(self: anytype, writer: anytype) !void {
+pub fn writeSegmentHeaders(self: *MachO, writer: anytype) !void {
     for (self.segments.items, 0..) |seg, i| {
         const indexes = self.getSectionIndexes(@as(u8, @intCast(i)));
         var out_seg = seg;
@@ -3075,7 +3342,7 @@ pub fn writeSegmentHeaders(self: anytype, writer: anytype) !void {
     }
 }
 
-fn writeLinkeditSegmentData(self: *MachO) !void {
+pub fn writeLinkeditSegmentData(self: *MachO) !void {
     const page_size = getPageSize(self.base.options.target.cpu.arch);
     const seg = self.getLinkeditSegmentPtr();
     seg.filesize = 0;
@@ -3092,29 +3359,29 @@ fn writeLinkeditSegmentData(self: *MachO) !void {
     }
 
     try self.writeDyldInfoData();
+    // TODO handle this better
+    if (self.mode == .zld) {
+        try self.writeFunctionStarts();
+        try self.writeDataInCode();
+    }
     try self.writeSymtabs();
 
     seg.vmsize = mem.alignForward(u64, seg.filesize, page_size);
 }
 
-pub fn collectRebaseDataFromTableSection(
-    gpa: Allocator,
-    ctx: anytype,
-    sect_id: u8,
-    rebase: *Rebase,
-    table: anytype,
-) !void {
-    const header = ctx.sections.items(.header)[sect_id];
-    const segment_index = ctx.sections.items(.segment_index)[sect_id];
-    const segment = ctx.segments.items[segment_index];
+fn collectRebaseDataFromTableSection(self: *MachO, sect_id: u8, rebase: *Rebase, table: anytype) !void {
+    const gpa = self.base.allocator;
+    const header = self.sections.items(.header)[sect_id];
+    const segment_index = self.sections.items(.segment_index)[sect_id];
+    const segment = self.segments.items[segment_index];
     const base_offset = header.addr - segment.vmaddr;
-    const is_got = if (ctx.got_section_index) |index| index == sect_id else false;
+    const is_got = if (self.got_section_index) |index| index == sect_id else false;
 
     try rebase.entries.ensureUnusedCapacity(gpa, table.entries.items.len);
 
     for (table.entries.items, 0..) |entry, i| {
         if (!table.lookup.contains(entry)) continue;
-        const sym = ctx.getSymbol(entry);
+        const sym = self.getSymbol(entry);
         if (is_got and sym.undf()) continue;
         const offset = i * @sizeOf(u64);
         log.debug("    | rebase at {x}", .{base_offset + offset});
@@ -3152,34 +3419,105 @@ fn collectRebaseData(self: *MachO, rebase: *Rebase) !void {
         }
     }
 
-    try collectRebaseDataFromTableSection(gpa, self, self.got_section_index.?, rebase, self.got_table);
-    try collectRebaseDataFromTableSection(gpa, self, self.la_symbol_ptr_section_index.?, rebase, self.stub_table);
+    // Unpack GOT entries
+    if (self.got_section_index) |sect_id| {
+        try self.collectRebaseDataFromTableSection(sect_id, rebase, self.got_table);
+    }
+
+    // Next, unpack __la_symbol_ptr entries
+    if (self.la_symbol_ptr_section_index) |sect_id| {
+        try self.collectRebaseDataFromTableSection(sect_id, rebase, self.stub_table);
+    }
+
+    // Finally, unpack the rest.
+    const cpu_arch = self.base.options.target.cpu.arch;
+    for (self.objects.items) |*object| {
+        for (object.atoms.items) |atom_index| {
+            const atom = self.getAtom(atom_index);
+            const sym = self.getSymbol(atom.getSymbolWithLoc());
+            if (sym.n_desc == N_DEAD) continue;
+
+            const sect_id = sym.n_sect - 1;
+            const section = self.sections.items(.header)[sect_id];
+            const segment_id = self.sections.items(.segment_index)[sect_id];
+            const segment = self.segments.items[segment_id];
+            if (segment.maxprot & macho.PROT.WRITE == 0) continue;
+            switch (section.type()) {
+                macho.S_LITERAL_POINTERS,
+                macho.S_REGULAR,
+                macho.S_MOD_INIT_FUNC_POINTERS,
+                macho.S_MOD_TERM_FUNC_POINTERS,
+                => {},
+                else => continue,
+            }
+
+            log.debug("  ATOM({d}, %{d}, '{s}')", .{
+                atom_index,
+                atom.sym_index,
+                self.getSymbolName(atom.getSymbolWithLoc()),
+            });
+
+            const code = Atom.getAtomCode(self, atom_index);
+            const relocs = Atom.getAtomRelocs(self, atom_index);
+            const ctx = Atom.getRelocContext(self, atom_index);
+
+            for (relocs) |rel| {
+                switch (cpu_arch) {
+                    .aarch64 => {
+                        const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
+                        if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
+                        if (rel.r_length != 3) continue;
+                    },
+                    .x86_64 => {
+                        const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
+                        if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
+                        if (rel.r_length != 3) continue;
+                    },
+                    else => unreachable,
+                }
+                const target = Atom.parseRelocTarget(self, .{
+                    .object_id = atom.getFile().?,
+                    .rel = rel,
+                    .code = code,
+                    .base_offset = ctx.base_offset,
+                    .base_addr = ctx.base_addr,
+                });
+                const target_sym = self.getSymbol(target);
+                if (target_sym.undf()) continue;
+
+                const base_offset = @as(i32, @intCast(sym.n_value - segment.vmaddr));
+                const rel_offset = rel.r_address - ctx.base_offset;
+                const offset = @as(u64, @intCast(base_offset + rel_offset));
+                log.debug("    | rebase at {x}", .{offset});
+
+                try rebase.entries.append(self.gpa, .{
+                    .offset = offset,
+                    .segment_id = segment_id,
+                });
+            }
+        }
+    }
 
     try rebase.finalize(gpa);
 }
 
-pub fn collectBindDataFromTableSection(
-    gpa: Allocator,
-    ctx: anytype,
-    sect_id: u8,
-    bind: anytype,
-    table: anytype,
-) !void {
-    const header = ctx.sections.items(.header)[sect_id];
-    const segment_index = ctx.sections.items(.segment_index)[sect_id];
-    const segment = ctx.segments.items[segment_index];
+fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, table: anytype) !void {
+    const gpa = self.base.allocator;
+    const header = self.sections.items(.header)[sect_id];
+    const segment_index = self.sections.items(.segment_index)[sect_id];
+    const segment = self.segments.items[segment_index];
     const base_offset = header.addr - segment.vmaddr;
 
     try bind.entries.ensureUnusedCapacity(gpa, table.entries.items.len);
 
     for (table.entries.items, 0..) |entry, i| {
         if (!table.lookup.contains(entry)) continue;
-        const bind_sym = ctx.getSymbol(entry);
+        const bind_sym = self.getSymbol(entry);
         if (!bind_sym.undf()) continue;
         const offset = i * @sizeOf(u64);
         log.debug("    | bind at {x}, import('{s}') in dylib({d})", .{
             base_offset + offset,
-            ctx.getSymbolName(entry),
+            self.getSymbolName(entry),
             @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER),
         });
         if (bind_sym.weakRef()) {
@@ -3235,13 +3573,105 @@ fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void {
         }
     }
 
-    // Gather GOT pointers
-    try collectBindDataFromTableSection(gpa, self, self.got_section_index.?, bind, self.got_table);
+    // Unpack GOT pointers
+    if (self.got_section_index) |sect_id| {
+        try self.collectBindDataFromTableSection(sect_id, bind, self.got_table);
+    }
+
+    // Next, unpack TLV pointers section
+    if (self.tlv_ptr_section_index) |sect_id| {
+        try self.collectBindDataFromTableSection(sect_id, bind, self.tlv_ptr_table);
+    }
+
+    // Finally, unpack the rest.
+    const cpu_arch = self.base.options.target.cpu.arch;
+    for (self.objects.items) |*object| {
+        for (object.atoms.items) |atom_index| {
+            const atom = self.getAtom(atom_index);
+            const sym = self.getSymbol(atom.getSymbolWithLoc());
+            if (sym.n_desc == N_DEAD) continue;
+
+            const sect_id = sym.n_sect - 1;
+            const section = self.sections.items(.header)[sect_id];
+            const segment_id = self.sections.items(.segment_index)[sect_id];
+            const segment = self.segments.items[segment_id];
+            if (segment.maxprot & macho.PROT.WRITE == 0) continue;
+            switch (section.type()) {
+                macho.S_LITERAL_POINTERS,
+                macho.S_REGULAR,
+                macho.S_MOD_INIT_FUNC_POINTERS,
+                macho.S_MOD_TERM_FUNC_POINTERS,
+                => {},
+                else => continue,
+            }
+
+            log.debug("  ATOM({d}, %{d}, '{s}')", .{
+                atom_index,
+                atom.sym_index,
+                self.getSymbolName(atom.getSymbolWithLoc()),
+            });
+
+            const code = Atom.getAtomCode(self, atom_index);
+            const relocs = Atom.getAtomRelocs(self, atom_index);
+            const ctx = Atom.getRelocContext(self, atom_index);
+
+            for (relocs) |rel| {
+                switch (cpu_arch) {
+                    .aarch64 => {
+                        const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
+                        if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
+                        if (rel.r_length != 3) continue;
+                    },
+                    .x86_64 => {
+                        const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
+                        if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
+                        if (rel.r_length != 3) continue;
+                    },
+                    else => unreachable,
+                }
+
+                const global = Atom.parseRelocTarget(self, .{
+                    .object_id = atom.getFile().?,
+                    .rel = rel,
+                    .code = code,
+                    .base_offset = ctx.base_offset,
+                    .base_addr = ctx.base_addr,
+                });
+                const bind_sym_name = self.getSymbolName(global);
+                const bind_sym = self.getSymbol(global);
+                if (!bind_sym.undf()) continue;
+
+                const base_offset = sym.n_value - segment.vmaddr;
+                const rel_offset = @as(u32, @intCast(rel.r_address - ctx.base_offset));
+                const offset = @as(u64, @intCast(base_offset + rel_offset));
+                const addend = mem.readIntLittle(i64, code[rel_offset..][0..8]);
+
+                const dylib_ordinal = @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER);
+                log.debug("    | bind at {x}, import('{s}') in dylib({d})", .{
+                    base_offset,
+                    bind_sym_name,
+                    dylib_ordinal,
+                });
+                log.debug("    | with addend {x}", .{addend});
+                if (bind_sym.weakRef()) {
+                    log.debug("    | marking as weak ref ", .{});
+                }
+                try bind.entries.append(self.gpa, .{
+                    .target = global,
+                    .offset = offset,
+                    .segment_id = segment_id,
+                    .addend = addend,
+                });
+            }
+        }
+    }
+
     try bind.finalize(gpa, self);
 }
 
 fn collectLazyBindData(self: *MachO, bind: anytype) !void {
-    try collectBindDataFromTableSection(self.base.allocator, self, self.la_symbol_ptr_section_index.?, bind, self.stub_table);
+    const sect_id = self.la_symbol_ptr_section_index orelse return;
+    try self.collectBindDataFromTableSection(sect_id, bind, self.stub_table);
     try bind.finalize(self.base.allocator, self);
 }
 
@@ -3259,6 +3689,7 @@ fn collectExportData(self: *MachO, trie: *Trie) !void {
 
         if (sym.undf()) continue;
         assert(sym.ext());
+        if (sym.n_desc == N_DEAD) continue;
 
         const sym_name = self.getSymbolName(global);
         log.debug("  (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value });
@@ -3349,12 +3780,7 @@ fn writeDyldInfoData(self: *MachO) !void {
     });
 
     try self.base.file.?.pwriteAll(buffer, rebase_off);
-    try populateLazyBindOffsetsInStubHelper(
-        self,
-        self.base.options.target.cpu.arch,
-        self.base.file.?,
-        lazy_bind,
-    );
+    try self.populateLazyBindOffsetsInStubHelper(lazy_bind);
 
     self.dyld_info_cmd.rebase_off = @as(u32, @intCast(rebase_off));
     self.dyld_info_cmd.rebase_size = @as(u32, @intCast(rebase_size_aligned));
@@ -3366,19 +3792,15 @@ fn writeDyldInfoData(self: *MachO) !void {
     self.dyld_info_cmd.export_size = @as(u32, @intCast(export_size_aligned));
 }
 
-pub fn populateLazyBindOffsetsInStubHelper(
-    ctx: anytype,
-    cpu_arch: std.Target.Cpu.Arch,
-    file: fs.File,
-    lazy_bind: anytype,
-) !void {
+fn populateLazyBindOffsetsInStubHelper(self: *MachO, lazy_bind: anytype) !void {
     if (lazy_bind.size() == 0) return;
 
-    const stub_helper_section_index = ctx.stub_helper_section_index.?;
+    const stub_helper_section_index = self.stub_helper_section_index.?;
     // assert(ctx.stub_helper_preamble_allocated);
 
-    const header = ctx.sections.items(.header)[stub_helper_section_index];
+    const header = self.sections.items(.header)[stub_helper_section_index];
 
+    const cpu_arch = self.base.options.target.cpu.arch;
     const preamble_size = stubs.stubHelperPreambleSize(cpu_arch);
     const stub_size = stubs.stubHelperSize(cpu_arch);
     const stub_offset = stubs.stubOffsetInStubHelper(cpu_arch);
@@ -3389,14 +3811,175 @@ pub fn populateLazyBindOffsetsInStubHelper(
 
         log.debug("writing lazy bind offset 0x{x} ({s}) in stub helper at 0x{x}", .{
             bind_offset,
-            ctx.getSymbolName(lazy_bind.entries.items[index].target),
+            self.getSymbolName(lazy_bind.entries.items[index].target),
             file_offset,
         });
 
-        try file.pwriteAll(mem.asBytes(&bind_offset), file_offset);
+        try self.base.file.?.pwriteAll(mem.asBytes(&bind_offset), file_offset);
     }
 }
 
+const asc_u64 = std.sort.asc(u64);
+
+fn addSymbolToFunctionStarts(self: *MachO, sym_loc: SymbolWithLoc, addresses: *std.ArrayList(u64)) !void {
+    const sym = self.getSymbol(sym_loc);
+    if (sym.n_strx == 0) return;
+    if (sym.n_desc == MachO.N_DEAD) return;
+    if (self.symbolIsTemp(sym_loc)) return;
+    try addresses.append(sym.n_value);
+}
+
+fn writeFunctionStarts(self: *MachO) !void {
+    const gpa = self.base.allocator;
+    const seg = self.segments.items[self.header_segment_cmd_index.?];
+
+    // We need to sort by address first
+    var addresses = std.ArrayList(u64).init(gpa);
+    defer addresses.deinit();
+
+    for (self.objects.items) |object| {
+        for (object.exec_atoms.items) |atom_index| {
+            const atom = self.getAtom(atom_index);
+            const sym_loc = atom.getSymbolWithLoc();
+            try self.addSymbolToFunctionStarts(sym_loc, &addresses);
+
+            var it = Atom.getInnerSymbolsIterator(self, atom_index);
+            while (it.next()) |inner_sym_loc| {
+                try self.addSymbolToFunctionStarts(inner_sym_loc, &addresses);
+            }
+        }
+    }
+
+    mem.sort(u64, addresses.items, {}, asc_u64);
+
+    var offsets = std.ArrayList(u32).init(gpa);
+    defer offsets.deinit();
+    try offsets.ensureTotalCapacityPrecise(addresses.items.len);
+
+    var last_off: u32 = 0;
+    for (addresses.items) |addr| {
+        const offset = @as(u32, @intCast(addr - seg.vmaddr));
+        const diff = offset - last_off;
+
+        if (diff == 0) continue;
+
+        offsets.appendAssumeCapacity(diff);
+        last_off = offset;
+    }
+
+    var buffer = std.ArrayList(u8).init(gpa);
+    defer buffer.deinit();
+
+    const max_size = @as(usize, @intCast(offsets.items.len * @sizeOf(u64)));
+    try buffer.ensureTotalCapacity(max_size);
+
+    for (offsets.items) |offset| {
+        try std.leb.writeULEB128(buffer.writer(), offset);
+    }
+
+    const link_seg = self.getLinkeditSegmentPtr();
+    const offset = link_seg.fileoff + link_seg.filesize;
+    assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
+    const needed_size = buffer.items.len;
+    const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
+    const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow;
+    if (padding > 0) {
+        try buffer.ensureUnusedCapacity(padding);
+        buffer.appendNTimesAssumeCapacity(0, padding);
+    }
+    link_seg.filesize = offset + needed_size_aligned - link_seg.fileoff;
+
+    log.debug("writing function starts info from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
+
+    try self.base.file.?.pwriteAll(buffer.items, offset);
+
+    self.function_starts_cmd.dataoff = @as(u32, @intCast(offset));
+    self.function_starts_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
+}
+
+fn filterDataInCode(
+    dices: []const macho.data_in_code_entry,
+    start_addr: u64,
+    end_addr: u64,
+) []const macho.data_in_code_entry {
+    const Predicate = struct {
+        addr: u64,
+
+        pub fn predicate(self: @This(), dice: macho.data_in_code_entry) bool {
+            return dice.offset >= self.addr;
+        }
+    };
+
+    const start = MachO.lsearch(macho.data_in_code_entry, dices, Predicate{ .addr = start_addr });
+    const end = MachO.lsearch(macho.data_in_code_entry, dices[start..], Predicate{ .addr = end_addr }) + start;
+
+    return dices[start..end];
+}
+
+pub fn writeDataInCode(self: *MachO) !void {
+    const gpa = self.base.allocator;
+    var out_dice = std.ArrayList(macho.data_in_code_entry).init(gpa);
+    defer out_dice.deinit();
+
+    const text_sect_id = self.text_section_index orelse return;
+    const text_sect_header = self.sections.items(.header)[text_sect_id];
+
+    for (self.objects.items) |object| {
+        if (!object.hasDataInCode()) continue;
+        const dice = object.data_in_code.items;
+        try out_dice.ensureUnusedCapacity(dice.len);
+
+        for (object.exec_atoms.items) |atom_index| {
+            const atom = self.getAtom(atom_index);
+            const sym = self.getSymbol(atom.getSymbolWithLoc());
+            if (sym.n_desc == MachO.N_DEAD) continue;
+
+            const source_addr = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
+                source_sym.n_value
+            else blk: {
+                const nbase = @as(u32, @intCast(object.in_symtab.?.len));
+                const source_sect_id = @as(u8, @intCast(atom.sym_index - nbase));
+                break :blk object.getSourceSection(source_sect_id).addr;
+            };
+            const filtered_dice = filterDataInCode(dice, source_addr, source_addr + atom.size);
+            const base = math.cast(u32, sym.n_value - text_sect_header.addr + text_sect_header.offset) orelse
+                return error.Overflow;
+
+            for (filtered_dice) |single| {
+                const offset = math.cast(u32, single.offset - source_addr + base) orelse
+                    return error.Overflow;
+                out_dice.appendAssumeCapacity(.{
+                    .offset = offset,
+                    .length = single.length,
+                    .kind = single.kind,
+                });
+            }
+        }
+    }
+
+    const seg = self.getLinkeditSegmentPtr();
+    const offset = seg.fileoff + seg.filesize;
+    assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
+    const needed_size = out_dice.items.len * @sizeOf(macho.data_in_code_entry);
+    const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
+    seg.filesize = offset + needed_size_aligned - seg.fileoff;
+
+    const buffer = try gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow);
+    defer gpa.free(buffer);
+    {
+        const src = mem.sliceAsBytes(out_dice.items);
+        @memcpy(buffer[0..src.len], src);
+        @memset(buffer[src.len..], 0);
+    }
+
+    log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
+
+    try self.base.file.?.pwriteAll(buffer, offset);
+
+    self.data_in_code_cmd.dataoff = @as(u32, @intCast(offset));
+    self.data_in_code_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
+}
+
 fn writeSymtabs(self: *MachO) !void {
     var ctx = try self.writeSymtab();
     defer ctx.imports_table.deinit();
@@ -3404,18 +3987,38 @@ fn writeSymtabs(self: *MachO) !void {
     try self.writeStrtab();
 }
 
+fn addLocalToSymtab(self: *MachO, sym_loc: SymbolWithLoc, locals: *std.ArrayList(macho.nlist_64)) !void {
+    const sym = self.getSymbol(sym_loc);
+    if (sym.n_strx == 0) return; // no name, skip
+    if (sym.n_desc == MachO.N_DEAD) return; // garbage-collected, skip
+    if (sym.ext()) return; // an export lands in its own symtab section, skip
+    if (self.symbolIsTemp(sym_loc)) return; // local temp symbol, skip
+    var out_sym = sym;
+    out_sym.n_strx = try self.strtab.insert(self.base.allocator, self.getSymbolName(sym_loc));
+    try locals.append(out_sym);
+}
+
 fn writeSymtab(self: *MachO) !SymtabCtx {
     const gpa = self.base.allocator;
 
     var locals = std.ArrayList(macho.nlist_64).init(gpa);
     defer locals.deinit();
 
-    for (self.locals.items, 0..) |sym, sym_id| {
-        if (sym.n_strx == 0) continue; // no name, skip
-        const sym_loc = SymbolWithLoc{ .sym_index = @as(u32, @intCast(sym_id)) };
-        if (self.symbolIsTemp(sym_loc)) continue; // local temp symbol, skip
-        if (self.getGlobal(self.getSymbolName(sym_loc)) != null) continue; // global symbol is either an export or import, skip
-        try locals.append(sym);
+    for (0..self.locals.items) |sym_id| {
+        try self.addLocalToSymtab(.{ .sym_index = @intCast(sym_id) });
+    }
+
+    for (self.objects.items) |object| {
+        for (object.atoms.items) |atom_index| {
+            const atom = self.getAtom(atom_index);
+            const sym_loc = atom.getSymbolWithLoc();
+            try self.addLocalToSymtab(sym_loc, &locals);
+
+            var it = Atom.getInnerSymbolsIterator(self, atom_index);
+            while (it.next()) |inner_sym_loc| {
+                try self.addLocalToSymtab(inner_sym_loc, &locals);
+            }
+        }
     }
 
     var exports = std.ArrayList(macho.nlist_64).init(gpa);
@@ -3424,6 +4027,7 @@ fn writeSymtab(self: *MachO) !SymtabCtx {
     for (self.globals.items) |global| {
         const sym = self.getSymbol(global);
         if (sym.undf()) continue; // import, skip
+        if (sym.n_desc == N_DEAD) continue;
         var out_sym = sym;
         out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
         try exports.append(out_sym);
@@ -3438,6 +4042,7 @@ fn writeSymtab(self: *MachO) !SymtabCtx {
         const sym = self.getSymbol(global);
         if (sym.n_strx == 0) continue; // no name, skip
         if (!sym.undf()) continue; // not an import, skip
+        if (sym.n_desc == N_DEAD) continue;
         const new_index = @as(u32, @intCast(imports.items.len));
         var out_sym = sym;
         out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
@@ -3445,6 +4050,15 @@ fn writeSymtab(self: *MachO) !SymtabCtx {
         try imports_table.putNoClobber(global, new_index);
     }
 
+    // We generate stabs last in order to ensure that the strtab always has debug info
+    // strings trailing
+    if (!self.base.options.strip) {
+        assert(self.d_sym == null); // TODO
+        for (self.objects.items) |object| {
+            try self.generateSymbolStabs(object, &locals);
+        }
+    }
+
     const nlocals = @as(u32, @intCast(locals.items.len));
     const nexports = @as(u32, @intCast(exports.items.len));
     const nimports = @as(u32, @intCast(imports.items.len));
@@ -3478,7 +4092,218 @@ fn writeSymtab(self: *MachO) !SymtabCtx {
     };
 }
 
-fn writeStrtab(self: *MachO) !void {
+fn generateSymbolStabs(
+    self: *MachO,
+    object: Object,
+    locals: *std.ArrayList(macho.nlist_64),
+) !void {
+    log.debug("generating stabs for '{s}'", .{object.name});
+
+    const gpa = self.base.allocator;
+    var debug_info = object.parseDwarfInfo();
+
+    var lookup = DwarfInfo.AbbrevLookupTable.init(gpa);
+    defer lookup.deinit();
+    try lookup.ensureUnusedCapacity(std.math.maxInt(u8));
+
+    // We assume there is only one CU.
+    var cu_it = debug_info.getCompileUnitIterator();
+    const compile_unit = while (try cu_it.next()) |cu| {
+        const offset = math.cast(usize, cu.cuh.debug_abbrev_offset) orelse return error.Overflow;
+        try debug_info.genAbbrevLookupByKind(offset, &lookup);
+        break cu;
+    } else {
+        log.debug("no compile unit found in debug info in {s}; skipping", .{object.name});
+        return;
+    };
+
+    var abbrev_it = compile_unit.getAbbrevEntryIterator(debug_info);
+    const cu_entry: DwarfInfo.AbbrevEntry = while (try abbrev_it.next(lookup)) |entry| switch (entry.tag) {
+        dwarf.TAG.compile_unit => break entry,
+        else => continue,
+    } else {
+        log.debug("missing DWARF_TAG_compile_unit tag in {s}; skipping", .{object.name});
+        return;
+    };
+
+    var maybe_tu_name: ?[]const u8 = null;
+    var maybe_tu_comp_dir: ?[]const u8 = null;
+    var attr_it = cu_entry.getAttributeIterator(debug_info, compile_unit.cuh);
+
+    while (try attr_it.next()) |attr| switch (attr.name) {
+        dwarf.AT.comp_dir => maybe_tu_comp_dir = attr.getString(debug_info, compile_unit.cuh) orelse continue,
+        dwarf.AT.name => maybe_tu_name = attr.getString(debug_info, compile_unit.cuh) orelse continue,
+        else => continue,
+    };
+
+    if (maybe_tu_name == null or maybe_tu_comp_dir == null) {
+        log.debug("missing DWARF_AT_comp_dir and DWARF_AT_name attributes {s}; skipping", .{object.name});
+        return;
+    }
+
+    const tu_name = maybe_tu_name.?;
+    const tu_comp_dir = maybe_tu_comp_dir.?;
+
+    // Open scope
+    try locals.ensureUnusedCapacity(3);
+    locals.appendAssumeCapacity(.{
+        .n_strx = try self.strtab.insert(gpa, tu_comp_dir),
+        .n_type = macho.N_SO,
+        .n_sect = 0,
+        .n_desc = 0,
+        .n_value = 0,
+    });
+    locals.appendAssumeCapacity(.{
+        .n_strx = try self.strtab.insert(gpa, tu_name),
+        .n_type = macho.N_SO,
+        .n_sect = 0,
+        .n_desc = 0,
+        .n_value = 0,
+    });
+    locals.appendAssumeCapacity(.{
+        .n_strx = try self.strtab.insert(gpa, object.name),
+        .n_type = macho.N_OSO,
+        .n_sect = 0,
+        .n_desc = 1,
+        .n_value = object.mtime,
+    });
+
+    var stabs_buf: [4]macho.nlist_64 = undefined;
+
+    var name_lookup: ?DwarfInfo.SubprogramLookupByName = if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) blk: {
+        var name_lookup = DwarfInfo.SubprogramLookupByName.init(gpa);
+        errdefer name_lookup.deinit();
+        try name_lookup.ensureUnusedCapacity(@as(u32, @intCast(object.atoms.items.len)));
+        try debug_info.genSubprogramLookupByName(compile_unit, lookup, &name_lookup);
+        break :blk name_lookup;
+    } else null;
+    defer if (name_lookup) |*nl| nl.deinit();
+
+    for (object.atoms.items) |atom_index| {
+        const atom = self.getAtom(atom_index);
+        const stabs = try self.generateSymbolStabsForSymbol(
+            atom_index,
+            atom.getSymbolWithLoc(),
+            name_lookup,
+            &stabs_buf,
+        );
+        try locals.appendSlice(stabs);
+
+        var it = Atom.getInnerSymbolsIterator(self, atom_index);
+        while (it.next()) |sym_loc| {
+            const contained_stabs = try self.generateSymbolStabsForSymbol(
+                atom_index,
+                sym_loc,
+                name_lookup,
+                &stabs_buf,
+            );
+            try locals.appendSlice(contained_stabs);
+        }
+    }
+
+    // Close scope
+    try locals.append(.{
+        .n_strx = 0,
+        .n_type = macho.N_SO,
+        .n_sect = 0,
+        .n_desc = 0,
+        .n_value = 0,
+    });
+}
+
+fn generateSymbolStabsForSymbol(
+    self: *MachO,
+    atom_index: Atom.Index,
+    sym_loc: SymbolWithLoc,
+    lookup: ?DwarfInfo.SubprogramLookupByName,
+    buf: *[4]macho.nlist_64,
+) ![]const macho.nlist_64 {
+    const gpa = self.base.allocator;
+    const object = self.objects.items[sym_loc.getFile().?];
+    const sym = self.getSymbol(sym_loc);
+    const sym_name = self.getSymbolName(sym_loc);
+    const header = self.sections.items(.header)[sym.n_sect - 1];
+
+    if (sym.n_strx == 0) return buf[0..0];
+    if (self.symbolIsTemp(sym_loc)) return buf[0..0];
+
+    if (!header.isCode()) {
+        // Since we are not dealing with machine code, it's either a global or a static depending
+        // on the linkage scope.
+        if (sym.sect() and sym.ext()) {
+            // Global gets an N_GSYM stab type.
+            buf[0] = .{
+                .n_strx = try self.strtab.insert(gpa, sym_name),
+                .n_type = macho.N_GSYM,
+                .n_sect = sym.n_sect,
+                .n_desc = 0,
+                .n_value = 0,
+            };
+        } else {
+            // Local static gets an N_STSYM stab type.
+            buf[0] = .{
+                .n_strx = try self.strtab.insert(gpa, sym_name),
+                .n_type = macho.N_STSYM,
+                .n_sect = sym.n_sect,
+                .n_desc = 0,
+                .n_value = sym.n_value,
+            };
+        }
+        return buf[0..1];
+    }
+
+    const size: u64 = size: {
+        if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) {
+            break :size self.getAtom(atom_index).size;
+        }
+
+        // Since we don't have subsections to work with, we need to infer the size of each function
+        // the slow way by scanning the debug info for matching symbol names and extracting
+        // the symbol's DWARF_AT_low_pc and DWARF_AT_high_pc values.
+        const source_sym = object.getSourceSymbol(sym_loc.sym_index) orelse return buf[0..0];
+        const subprogram = lookup.?.get(sym_name[1..]) orelse return buf[0..0];
+
+        if (subprogram.addr <= source_sym.n_value and source_sym.n_value < subprogram.addr + subprogram.size) {
+            break :size subprogram.size;
+        } else {
+            log.debug("no stab found for {s}", .{sym_name});
+            return buf[0..0];
+        }
+    };
+
+    buf[0] = .{
+        .n_strx = 0,
+        .n_type = macho.N_BNSYM,
+        .n_sect = sym.n_sect,
+        .n_desc = 0,
+        .n_value = sym.n_value,
+    };
+    buf[1] = .{
+        .n_strx = try self.strtab.insert(gpa, sym_name),
+        .n_type = macho.N_FUN,
+        .n_sect = sym.n_sect,
+        .n_desc = 0,
+        .n_value = sym.n_value,
+    };
+    buf[2] = .{
+        .n_strx = 0,
+        .n_type = macho.N_FUN,
+        .n_sect = 0,
+        .n_desc = 0,
+        .n_value = size,
+    };
+    buf[3] = .{
+        .n_strx = 0,
+        .n_type = macho.N_ENSYM,
+        .n_sect = sym.n_sect,
+        .n_desc = 0,
+        .n_value = size,
+    };
+
+    return buf;
+}
+
+pub fn writeStrtab(self: *MachO) !void {
     const gpa = self.base.allocator;
     const seg = self.getLinkeditSegmentPtr();
     const offset = seg.fileoff + seg.filesize;
@@ -3507,7 +4332,7 @@ const SymtabCtx = struct {
     imports_table: std.AutoHashMap(SymbolWithLoc, u32),
 };
 
-fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void {
+pub fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void {
     const gpa = self.base.allocator;
     const nstubs = @as(u32, @intCast(self.stub_table.lookup.count()));
     const ngot_entries = @as(u32, @intCast(self.got_table.lookup.count()));
@@ -3582,7 +4407,7 @@ fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void {
     self.dysymtab_cmd.nindirectsyms = nindirectsyms;
 }
 
-fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void {
+pub fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void {
     const file_size = if (!has_codesig) blk: {
         const seg = self.getLinkeditSegmentPtr();
         break :blk seg.fileoff + seg.filesize;
@@ -3592,7 +4417,7 @@ fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_c
     try self.base.file.?.pwriteAll(&self.uuid_cmd.uuid, offset);
 }
 
-fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
+pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
     const seg = self.getLinkeditSegmentPtr();
     // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
     // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
@@ -3609,8 +4434,9 @@ fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
     self.codesig_cmd.datasize = @as(u32, @intCast(needed_size));
 }
 
-fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void {
-    const seg = self.getSegment(self.text_section_index.?);
+pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void {
+    const seg_id = self.header_segment_cmd_index.?;
+    const seg = self.segments.items[seg_id];
     const offset = self.codesig_cmd.dataoff;
 
     var buffer = std.ArrayList(u8).init(self.base.allocator);
@@ -3634,14 +4460,10 @@ fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSig
 }
 
 /// Writes Mach-O file header.
-fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void {
+pub fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void {
     var header: macho.mach_header_64 = .{};
     header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL;
 
-    if (!self.base.options.single_threaded) {
-        header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
-    }
-
     switch (self.base.options.target.cpu.arch) {
         .aarch64 => {
             header.cputype = macho.CPU_TYPE_ARM64;
@@ -3666,6 +4488,13 @@ fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void {
         else => unreachable,
     }
 
+    if (self.thread_vars_section_index) |sect_id| {
+        header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
+        if (self.sections.items(.header)[sect_id].size > 0) {
+            header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
+        }
+    }
+
     header.ncmds = ncmds;
     header.sizeofcmds = sizeofcmds;
 
@@ -3830,20 +4659,33 @@ pub fn symbolIsTemp(self: *MachO, sym_with_loc: SymbolWithLoc) bool {
 
 /// Returns pointer-to-symbol described by `sym_with_loc` descriptor.
 pub fn getSymbolPtr(self: *MachO, sym_with_loc: SymbolWithLoc) *macho.nlist_64 {
-    assert(sym_with_loc.getFile() == null);
-    return &self.locals.items[sym_with_loc.sym_index];
+    if (sym_with_loc.getFile()) |file| {
+        const object = &self.objects.items[file];
+        return &object.symtab[sym_with_loc.sym_index];
+    } else {
+        return &self.locals.items[sym_with_loc.sym_index];
+    }
 }
 
 /// Returns symbol described by `sym_with_loc` descriptor.
 pub fn getSymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
-    assert(sym_with_loc.getFile() == null);
-    return self.locals.items[sym_with_loc.sym_index];
+    if (sym_with_loc.getFile()) |file| {
+        const object = &self.objects.items[file];
+        return object.symtab[sym_with_loc.sym_index];
+    } else {
+        return self.locals.items[sym_with_loc.sym_index];
+    }
 }
 
 /// Returns name of the symbol described by `sym_with_loc` descriptor.
 pub fn getSymbolName(self: *const MachO, sym_with_loc: SymbolWithLoc) []const u8 {
-    const sym = self.getSymbol(sym_with_loc);
-    return self.strtab.get(sym.n_strx).?;
+    if (sym_with_loc.getFile()) |file| {
+        const object = self.objects.items[file];
+        return object.getSymbolName(sym_with_loc.sym_index);
+    } else {
+        const sym = self.locals.items[sym_with_loc.sym_index];
+        return self.strtab.get(sym.n_strx).?;
+    }
 }
 
 /// Returns pointer to the global entry for `name` if one exists.
@@ -3945,6 +4787,19 @@ pub inline fn getPageSize(cpu_arch: std.Target.Cpu.Arch) u16 {
     };
 }
 
+pub inline fn requiresThunks(self: MachO) bool {
+    return self.base.options.target.cpu.arch == .aarch64;
+}
+
+pub fn requiresCodeSignature(self: MachO) bool {
+    if (self.base.options.entitlements) |_| return true;
+    const cpu_arch = self.base.options.target.cpu.arch;
+    const os_tag = self.base.options.target.os.tag;
+    const abi = self.base.options.target.abi;
+    if (cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator)) return true;
+    return false;
+}
+
 pub fn getSegmentPrecedence(segname: []const u8) u4 {
     if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0;
     if (mem.eql(u8, segname, "__TEXT")) return 0x1;
@@ -3988,24 +4843,26 @@ pub fn getSectionPrecedence(header: macho.section_64) u8 {
     return (@as(u8, @intCast(segment_precedence)) << 4) + section_precedence;
 }
 
-pub fn reportUndefined(self: *MachO, ctx: anytype) !void {
-    const count = ctx.unresolved.count();
+pub fn reportUndefined(self: *MachO) !void {
+    const count = self.unresolved.count();
     if (count == 0) return;
 
     const gpa = self.base.allocator;
 
     try self.misc_errors.ensureUnusedCapacity(gpa, count);
 
-    for (ctx.unresolved.keys()) |global_index| {
-        const global = ctx.globals.items[global_index];
-        const sym_name = ctx.getSymbolName(global);
+    for (self.unresolved.keys()) |global_index| {
+        const global = self.globals.items[global_index];
+        const sym_name = self.getSymbolName(global);
 
         const nnotes: usize = if (global.getFile() == null) @as(usize, 0) else 1;
         var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, nnotes);
         defer notes.deinit();
 
         if (global.getFile()) |file| {
-            const note = try std.fmt.allocPrint(gpa, "referenced in {s}", .{ctx.objects.items[file].name});
+            const note = try std.fmt.allocPrint(gpa, "referenced in {s}", .{
+                self.objects.items[file].name,
+            });
             notes.appendAssumeCapacity(.{ .msg = note });
         }
 
@@ -4051,6 +4908,19 @@ pub fn lsearch(comptime T: type, haystack: []align(1) const T, predicate: anytyp
     return i;
 }
 
+pub fn logSegments(self: *MachO) void {
+    log.debug("segments:", .{});
+    for (self.segments.items, 0..) |segment, i| {
+        log.debug("  segment({d}): {s} @{x} ({x}), sizeof({x})", .{
+            i,
+            segment.segName(),
+            segment.fileoff,
+            segment.vmaddr,
+            segment.vmsize,
+        });
+    }
+}
+
 pub fn logSections(self: *MachO) void {
     log.debug("sections:", .{});
     for (self.sections.items(.header), 0..) |header, i| {
@@ -4065,9 +4935,7 @@ pub fn logSections(self: *MachO) void {
     }
 }
 
-fn logSymAttributes(sym: macho.nlist_64, buf: *[4]u8) []const u8 {
-    @memset(buf[0..4], '_');
-    @memset(buf[4..], ' ');
+fn logSymAttributes(sym: macho.nlist_64, buf: []u8) []const u8 {
     if (sym.sect()) {
         buf[0] = 's';
     }
@@ -4090,56 +4958,110 @@ fn logSymAttributes(sym: macho.nlist_64, buf: *[4]u8) []const u8 {
 pub fn logSymtab(self: *MachO) void {
     var buf: [4]u8 = undefined;
 
-    log.debug("symtab:", .{});
+    const scoped_log = std.log.scoped(.symtab);
+
+    scoped_log.debug("locals:", .{});
+    for (self.objects.items, 0..) |object, id| {
+        scoped_log.debug("  object({d}): {s}", .{ id, object.name });
+        if (object.in_symtab == null) continue;
+        for (object.symtab, 0..) |sym, sym_id| {
+            @memset(&buf, '_');
+            scoped_log.debug("    %{d}: {s} @{x} in sect({d}), {s}", .{
+                sym_id,
+                object.getSymbolName(@as(u32, @intCast(sym_id))),
+                sym.n_value,
+                sym.n_sect,
+                logSymAttributes(sym, &buf),
+            });
+        }
+    }
+    scoped_log.debug("  object(-1)", .{});
     for (self.locals.items, 0..) |sym, sym_id| {
-        const where = if (sym.undf() and !sym.tentative()) "ord" else "sect";
-        const def_index = if (sym.undf() and !sym.tentative())
-            @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER)
-        else
-            sym.n_sect + 1;
-        log.debug("    %{d}: {?s} @{x} in {s}({d}), {s}", .{
+        if (sym.undf()) continue;
+        scoped_log.debug("    %{d}: {s} @{x} in sect({d}), {s}", .{
             sym_id,
-            self.strtab.get(sym.n_strx),
+            self.strtab.get(sym.n_strx).?,
             sym.n_value,
-            where,
-            def_index,
+            sym.n_sect,
             logSymAttributes(sym, &buf),
         });
     }
 
-    log.debug("globals table:", .{});
-    for (self.globals.items) |global| {
-        const name = self.getSymbolName(global);
-        log.debug("  {s} => %{d} in object({?d})", .{ name, global.sym_index, global.file });
+    scoped_log.debug("exports:", .{});
+    for (self.globals.items, 0..) |global, i| {
+        const sym = self.getSymbol(global);
+        if (sym.undf()) continue;
+        if (sym.n_desc == MachO.N_DEAD) continue;
+        scoped_log.debug("    %{d}: {s} @{x} in sect({d}), {s} (def in object({?}))", .{
+            i,
+            self.getSymbolName(global),
+            sym.n_value,
+            sym.n_sect,
+            logSymAttributes(sym, &buf),
+            global.file,
+        });
+    }
+
+    scoped_log.debug("imports:", .{});
+    for (self.globals.items, 0..) |global, i| {
+        const sym = self.getSymbol(global);
+        if (!sym.undf()) continue;
+        if (sym.n_desc == MachO.N_DEAD) continue;
+        const ord = @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER);
+        scoped_log.debug("    %{d}: {s} @{x} in ord({d}), {s}", .{
+            i,
+            self.getSymbolName(global),
+            sym.n_value,
+            ord,
+            logSymAttributes(sym, &buf),
+        });
     }
 
-    log.debug("GOT entries:", .{});
-    log.debug("{}", .{self.got_table});
+    scoped_log.debug("GOT entries:", .{});
+    scoped_log.debug("{}", .{self.got_table});
+
+    scoped_log.debug("TLV pointers:", .{});
+    scoped_log.debug("{}", .{self.tlv_ptr_table});
 
-    log.debug("stubs entries:", .{});
-    log.debug("{}", .{self.stub_table});
+    scoped_log.debug("stubs entries:", .{});
+    scoped_log.debug("{}", .{self.stubs_table});
+
+    scoped_log.debug("thunks:", .{});
+    for (self.thunks.items, 0..) |thunk, i| {
+        scoped_log.debug("  thunk({d})", .{i});
+        const slice = thunk.targets.slice();
+        for (slice.items(.tag), slice.items(.target), 0..) |tag, target, j| {
+            const atom_index = @as(u32, @intCast(thunk.getStartAtomIndex() + j));
+            const atom = self.getAtom(atom_index);
+            const atom_sym = self.getSymbol(atom.getSymbolWithLoc());
+            const target_addr = switch (tag) {
+                .stub => self.getStubsEntryAddress(target).?,
+                .atom => self.getSymbol(target).n_value,
+            };
+            scoped_log.debug("    {d}@{x} => {s}({s}@{x})", .{
+                j,
+                atom_sym.n_value,
+                @tagName(tag),
+                self.getSymbolName(target),
+                target_addr,
+            });
+        }
+    }
 }
 
 pub fn logAtoms(self: *MachO) void {
     log.debug("atoms:", .{});
-
     const slice = self.sections.slice();
-    for (slice.items(.last_atom_index), 0..) |last_atom_index, i| {
-        var atom_index = last_atom_index orelse continue;
-        const header = slice.items(.header)[i];
-
-        while (true) {
-            const atom = self.getAtom(atom_index);
-            if (atom.prev_index) |prev_index| {
-                atom_index = prev_index;
-            } else break;
-        }
+    for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
+        var atom_index = first_atom_index orelse continue;
+        const header = slice.items(.header)[sect_id];
 
         log.debug("{s},{s}", .{ header.segName(), header.sectName() });
 
         while (true) {
-            self.logAtom(atom_index);
             const atom = self.getAtom(atom_index);
+            self.logAtom(atom_index, log);
+
             if (atom.next_index) |next_index| {
                 atom_index = next_index;
             } else break;
@@ -4147,18 +5069,50 @@ pub fn logAtoms(self: *MachO) void {
     }
 }
 
-pub fn logAtom(self: *MachO, atom_index: Atom.Index) void {
+pub fn logAtom(self: *MachO, atom_index: Atom.Index, logger: anytype) void {
+    if (!build_options.enable_logging) return;
+
     const atom = self.getAtom(atom_index);
-    const sym = atom.getSymbol(self);
-    const sym_name = atom.getName(self);
-    log.debug("  ATOM(%{?d}, '{s}') @ {x} sizeof({x}) in object({?d}) in sect({d})", .{
-        atom.getSymbolIndex(),
+    const sym = self.getSymbol(atom.getSymbolWithLoc());
+    const sym_name = self.getSymbolName(atom.getSymbolWithLoc());
+    logger.debug("  ATOM({d}, %{d}, '{s}') @ {x} (sizeof({x}), alignof({x})) in object({?}) in sect({d})", .{
+        atom_index,
+        atom.sym_index,
         sym_name,
         sym.n_value,
         atom.size,
-        atom.file,
-        sym.n_sect + 1,
+        atom.alignment,
+        atom.getFile(),
+        sym.n_sect,
     });
+
+    if (atom.getFile() != null) {
+        var it = Atom.getInnerSymbolsIterator(self, atom_index);
+        while (it.next()) |sym_loc| {
+            const inner = self.getSymbol(sym_loc);
+            const inner_name = self.getSymbolName(sym_loc);
+            const offset = Atom.calcInnerSymbolOffset(self, atom_index, sym_loc.sym_index);
+
+            logger.debug("    (%{d}, '{s}') @ {x} ({x})", .{
+                sym_loc.sym_index,
+                inner_name,
+                inner.n_value,
+                offset,
+            });
+        }
+
+        if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
+            const alias = self.getSymbol(sym_loc);
+            const alias_name = self.getSymbolName(sym_loc);
+
+            logger.debug("    (%{d}, '{s}') @ {x} ({x})", .{
+                sym_loc.sym_index,
+                alias_name,
+                alias.n_value,
+                0,
+            });
+        }
+    }
 }
 
 const MachO = @This();
@@ -4197,6 +5151,7 @@ const Cache = std.Build.Cache;
 const CodeSignature = @import("MachO/CodeSignature.zig");
 const Compilation = @import("../Compilation.zig");
 const Dwarf = File.Dwarf;
+const DwarfInfo = @import("DwarfInfo.zig");
 const Dylib = @import("MachO/Dylib.zig");
 const File = link.File;
 const Object = @import("MachO/Object.zig");