Commit f6d13e9d6f

Jakub Konka <kubkon@jakubkonka.com>
2021-07-18 17:48:00
zld: move contents of Zld into MachO module
1 parent e0b53ad
src/link/MachO/Dylib.zig
@@ -13,7 +13,7 @@ const fat = @import("fat.zig");
 const Allocator = mem.Allocator;
 const Arch = std.Target.Cpu.Arch;
 const LibStub = @import("../tapi.zig").LibStub;
-const Zld = @import("Zld.zig");
+const MachO = @import("../MachO.zig");
 
 usingnamespace @import("commands.zig");
 
@@ -324,7 +324,7 @@ fn parseSymbols(self: *Dylib) !void {
     _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff + self.library_offset);
 
     for (slice) |sym| {
-        const add_to_symtab = Zld.symbolIsExt(sym) and (Zld.symbolIsSect(sym) or Zld.symbolIsIndr(sym));
+        const add_to_symtab = MachO.symbolIsExt(sym) and (MachO.symbolIsSect(sym) or MachO.symbolIsIndr(sym));
 
         if (!add_to_symtab) continue;
 
src/link/MachO/Object.zig
@@ -13,8 +13,8 @@ const sort = std.sort;
 
 const Allocator = mem.Allocator;
 const Arch = std.Target.Cpu.Arch;
+const MachO = @import("../MachO.zig");
 const TextBlock = @import("TextBlock.zig");
-const Zld = @import("Zld.zig");
 
 usingnamespace @import("commands.zig");
 
@@ -307,8 +307,8 @@ const NlistWithIndex = struct {
             }
         };
 
-        const start = Zld.findFirst(NlistWithIndex, symbols, 0, Predicate{ .addr = sect.addr });
-        const end = Zld.findFirst(NlistWithIndex, symbols, start, Predicate{ .addr = sect.addr + sect.size });
+        const start = MachO.findFirst(NlistWithIndex, symbols, 0, Predicate{ .addr = sect.addr });
+        const end = MachO.findFirst(NlistWithIndex, symbols, start, Predicate{ .addr = sect.addr + sect.size });
 
         return symbols[start..end];
     }
@@ -323,8 +323,8 @@ fn filterDice(dices: []macho.data_in_code_entry, start_addr: u64, end_addr: u64)
         }
     };
 
-    const start = Zld.findFirst(macho.data_in_code_entry, dices, 0, Predicate{ .addr = start_addr });
-    const end = Zld.findFirst(macho.data_in_code_entry, dices, start, Predicate{ .addr = end_addr });
+    const start = MachO.findFirst(macho.data_in_code_entry, dices, 0, Predicate{ .addr = start_addr });
+    const end = MachO.findFirst(macho.data_in_code_entry, dices, start, Predicate{ .addr = end_addr });
 
     return dices[start..end];
 }
@@ -335,10 +335,10 @@ const TextBlockParser = struct {
     code: []u8,
     relocs: []macho.relocation_info,
     object: *Object,
-    zld: *Zld,
+    macho_file: *MachO,
     nlists: []NlistWithIndex,
     index: u32 = 0,
-    match: Zld.MatchingSection,
+    match: MachO.MatchingSection,
 
     fn peek(self: *TextBlockParser) ?NlistWithIndex {
         return if (self.index + 1 < self.nlists.len) self.nlists[self.index + 1] else null;
@@ -349,10 +349,10 @@ const TextBlockParser = struct {
     };
 
     fn lessThanBySeniority(context: SeniorityContext, lhs: NlistWithIndex, rhs: NlistWithIndex) bool {
-        if (!Zld.symbolIsExt(rhs.nlist)) {
-            return Zld.symbolIsTemp(lhs.nlist, context.object.getString(lhs.nlist.n_strx));
-        } else if (Zld.symbolIsPext(rhs.nlist) or Zld.symbolIsWeakDef(rhs.nlist)) {
-            return !Zld.symbolIsExt(lhs.nlist);
+        if (!MachO.symbolIsExt(rhs.nlist)) {
+            return MachO.symbolIsTemp(lhs.nlist, context.object.getString(lhs.nlist.n_strx));
+        } else if (MachO.symbolIsPext(rhs.nlist) or MachO.symbolIsWeakDef(rhs.nlist)) {
+            return !MachO.symbolIsExt(lhs.nlist);
         } else {
             return true;
         }
@@ -383,7 +383,7 @@ const TextBlockParser = struct {
             const sym = self.object.symbols.items[nlist_with_index.index];
             if (sym.payload != .regular) {
                 log.err("expected a regular symbol, found {s}", .{sym.payload});
-                log.err("  when remapping {s}", .{self.zld.getString(sym.strx)});
+                log.err("  when remapping {s}", .{self.macho_file.getString(sym.strx)});
                 return error.SymbolIsNotRegular;
             }
             assert(sym.payload.regular.local_sym_index != 0); // This means the symbol has not been properly resolved.
@@ -401,7 +401,7 @@ const TextBlockParser = struct {
         }
 
         const senior_nlist = aliases.pop();
-        const senior_sym = self.zld.locals.items[senior_nlist.index];
+        const senior_sym = self.macho_file.locals.items[senior_nlist.index];
         assert(senior_sym.payload == .regular);
         senior_sym.payload.regular.segment_id = self.match.seg;
         senior_sym.payload.regular.section_id = self.match.sect;
@@ -429,7 +429,7 @@ const TextBlockParser = struct {
                     }
                 }
             }
-            if (self.zld.globals.contains(self.zld.getString(senior_sym.strx))) break :blk .global;
+            if (self.macho_file.globals.contains(self.macho_file.getString(senior_sym.strx))) break :blk .global;
             break :blk .static;
         } else null;
 
@@ -448,19 +448,19 @@ const TextBlockParser = struct {
             for (aliases.items) |alias| {
                 block.aliases.appendAssumeCapacity(alias.index);
 
-                const sym = self.zld.locals.items[alias.index];
+                const sym = self.macho_file.locals.items[alias.index];
                 const reg = &sym.payload.regular;
                 reg.segment_id = self.match.seg;
                 reg.section_id = self.match.sect;
             }
         }
 
-        try block.parseRelocsFromObject(relocs, object, .{
+        try block.parseRelocsFromObject(self.allocator, relocs, object, .{
             .base_addr = start_addr,
-            .zld = self.zld,
+            .macho_file = self.macho_file,
         });
 
-        if (self.zld.has_dices) {
+        if (self.macho_file.has_dices) {
             const dices = filterDice(
                 self.object.data_in_code_entries.items,
                 senior_nlist.nlist.n_value,
@@ -483,7 +483,7 @@ const TextBlockParser = struct {
     }
 };
 
-pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
+pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
     const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
 
     log.debug("analysing {s}", .{self.name.?});
@@ -513,7 +513,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
         });
 
         // Get matching segment/section in the final artifact.
-        const match = (try zld.getMatchingSection(sect)) orelse {
+        const match = (try macho_file.getMatchingSection(sect)) orelse {
             log.debug("unhandled section", .{});
             continue;
         };
@@ -538,7 +538,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
         // duplicates at all? Need some benchmarks!
         // const is_splittable = false;
 
-        zld.has_dices = blk: {
+        macho_file.has_dices = blk: {
             if (self.text_section_index) |index| {
                 if (index != id) break :blk false;
                 if (self.data_in_code_entries.items.len == 0) break :blk false;
@@ -546,7 +546,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
             }
             break :blk false;
         };
-        zld.has_stabs = zld.has_stabs or self.debug_info != null;
+        macho_file.has_stabs = macho_file.has_stabs or self.debug_info != null;
 
         {
             // next: {
@@ -711,11 +711,11 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
             defer self.allocator.free(sym_name);
 
             const block_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
-                const block_local_sym_index = @intCast(u32, zld.locals.items.len);
-                try zld.locals.append(zld.allocator, .{
-                    .n_strx = try zld.makeString(sym_name),
+                const block_local_sym_index = @intCast(u32, macho_file.locals.items.len);
+                try macho_file.locals.append(macho_file.base.allocator, .{
+                    .n_strx = try macho_file.makeString(sym_name),
                     .n_type = macho.N_SECT,
-                    .n_sect = zld.sectionId(match),
+                    .n_sect = macho_file.sectionId(match),
                     .n_desc = 0,
                     .n_value = sect.addr,
                 });
@@ -726,20 +726,20 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
             const block = try self.allocator.create(TextBlock);
             errdefer self.allocator.destroy(block);
 
-            block.* = TextBlock.init(self.allocator);
+            block.* = TextBlock.empty;
             block.local_sym_index = block_local_sym_index;
             block.code = try self.allocator.dupe(u8, code);
             block.size = sect.size;
             block.alignment = sect.@"align";
 
-            try block.parseRelocsFromObject(relocs, self, .{
+            try block.parseRelocsFromObject(self.allocator, relocs, self, .{
                 .base_addr = 0,
-                .zld = zld,
+                .macho_file = macho_file,
             });
 
-            if (zld.has_dices) {
+            if (macho_file.has_dices) {
                 const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + sect.size);
-                try block.dices.ensureTotalCapacity(dices.len);
+                try block.dices.ensureTotalCapacity(self.allocator, dices.len);
 
                 for (dices) |dice| {
                     block.dices.appendAssumeCapacity(.{
@@ -755,15 +755,13 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
             // the filtered symbols and note which symbol is contained within so that
             // we can properly allocate addresses down the line.
             // While we're at it, we need to update segment,section mapping of each symbol too.
-            var contained = std.ArrayList(TextBlock.SymbolAtOffset).init(self.allocator);
-            defer contained.deinit();
-            try contained.ensureTotalCapacity(filtered_nlists.len);
+            try block.contained.ensureTotalCapacity(self.allocator, filtered_nlists.len);
 
             for (filtered_nlists) |nlist_with_index| {
                 const nlist = nlist_with_index.nlist;
                 const local_sym_index = self.symbol_mapping.get(nlist_with_index.index) orelse unreachable;
-                const local = &zld.locals.items[local_sym_index];
-                local.n_sect = zld.sectionId(match);
+                const local = &macho_file.locals.items[local_sym_index];
+                local.n_sect = macho_file.sectionId(match);
 
                 const stab: ?TextBlock.Stab = if (self.debug_info) |di| blk: {
                     // TODO there has to be a better to handle this.
@@ -781,19 +779,17 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
                     break :blk .static;
                 } else null;
 
-                contained.appendAssumeCapacity(.{
+                block.contained.appendAssumeCapacity(.{
                     .local_sym_index = local_sym_index,
                     .offset = nlist.n_value - sect.addr,
                     .stab = stab,
                 });
             }
 
-            block.contained = contained.toOwnedSlice();
-
             // Update target section's metadata
             // TODO should we update segment's size here too?
             // How does it tie with incremental space allocs?
-            const tseg = &zld.load_commands.items[match.seg].Segment;
+            const tseg = &macho_file.load_commands.items[match.seg].Segment;
             const tsect = &tseg.sections.items[match.sect];
             const new_alignment = math.max(tsect.@"align", block.alignment);
             const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
@@ -801,12 +797,12 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
             tsect.size = new_size;
             tsect.@"align" = new_alignment;
 
-            if (zld.blocks.getPtr(match)) |last| {
+            if (macho_file.blocks.getPtr(match)) |last| {
                 last.*.next = block;
                 block.prev = last.*;
                 last.* = block;
             } else {
-                try zld.blocks.putNoClobber(zld.allocator, match, block);
+                try macho_file.blocks.putNoClobber(self.allocator, match, block);
             }
 
             try self.text_blocks.append(self.allocator, block);
@@ -814,7 +810,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
     }
 }
 
-pub fn symbolFromReloc(self: *Object, zld: *Zld, rel: macho.relocation_info) !*Symbol {
+pub fn symbolFromReloc(self: *Object, macho_file: *MachO, rel: macho.relocation_info) !*Symbol {
     const symbol = blk: {
         if (rel.r_extern == 1) {
             break :blk self.symbols.items[rel.r_symbolnum];
@@ -832,9 +828,9 @@ pub fn symbolFromReloc(self: *Object, zld: *Zld, rel: macho.relocation_info) !*S
                     sectionName(sect),
                 });
                 defer self.allocator.free(name);
-                const symbol = try zld.allocator.create(Symbol);
+                const symbol = try macho_file.allocator.create(Symbol);
                 symbol.* = .{
-                    .strx = try zld.makeString(name),
+                    .strx = try macho_file.makeString(name),
                     .payload = .{
                         .regular = .{
                             .linkage = .translation_unit,
src/link/MachO/TextBlock.zig
@@ -12,24 +12,64 @@ const meta = std.meta;
 
 const Allocator = mem.Allocator;
 const Arch = std.Target.Cpu.Arch;
+const MachO = @import("../MachO.zig");
 const Object = @import("Object.zig");
-const Zld = @import("Zld.zig");
 
-allocator: *Allocator,
+/// Each decl always gets a local symbol with the fully qualified name.
+/// The vaddr and size are found here directly.
+/// The file offset is found by computing the vaddr offset from the section vaddr
+/// the symbol references, and adding that to the file offset of the section.
+/// If this field is 0, it means the codegen size = 0 and there is no symbol or
+/// offset table entry.
 local_sym_index: u32,
-stab: ?Stab = null,
-aliases: std.ArrayList(u32),
-references: std.AutoArrayHashMap(u32, void),
-contained: ?[]SymbolAtOffset = null,
+
+/// List of symbol aliases pointing to the same block via different nlists
+aliases: std.ArrayListUnmanaged(u32) = .{},
+
+/// List of symbols contained within this block
+contained: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
+
+/// Code (may be non-relocated) this block represents
 code: []u8,
-relocs: std.ArrayList(Relocation),
+
+/// Size and alignment of this text block
+/// Unlike in Elf, we need to store the size of this symbol as part of
+/// the TextBlock since macho.nlist_64 lacks this information.
 size: u64,
 alignment: u32,
-rebases: std.ArrayList(u64),
-bindings: std.ArrayList(SymbolAtOffset),
-dices: std.ArrayList(macho.data_in_code_entry),
-next: ?*TextBlock = null,
-prev: ?*TextBlock = null,
+
+relocs: std.ArrayListUnmanaged(Relocation) = .{},
+
+/// List of offsets contained within this block that need rebasing by the dynamic
+/// loader in presence of ASLR
+rebases: std.ArrayListUnmanaged(u64) = .{},
+
+/// List of offsets contained within this block that will be dynamically bound
+/// by the dynamic loader and contain pointers to resolved (at load time) extern
+/// symbols (aka proxies aka imports)
+bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
+
+/// List of data-in-code entries. This is currently specific to x86_64 only.
+dices: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
+
+/// Stab entry for this block. This is currently specific to a binary created
+/// by linking object files in a traditional sense - in incremental sense, we
+/// bypass stabs altogether to produce dSYM bundle directly with fully relocated
+/// DWARF sections.
+stab: ?Stab = null,
+
+/// Points to the previous and next neighbours
+next: ?*TextBlock,
+prev: ?*TextBlock,
+
+/// Previous/next linked list pointers.
+/// This is the linked list node for this Decl's corresponding .debug_info tag.
+dbg_info_prev: ?*TextBlock,
+dbg_info_next: ?*TextBlock,
+/// Offset into .debug_info pointing to the tag for this Decl.
+dbg_info_off: u32,
+/// Size of the .debug_info tag for this Decl, not including padding.
+dbg_info_len: u32,
 
 pub const SymbolAtOffset = struct {
     local_sym_index: u32,
@@ -42,11 +82,11 @@ pub const Stab = union(enum) {
     static,
     global,
 
-    pub fn asNlists(stab: Stab, local_sym_index: u32, zld: *Zld) ![]macho.nlist_64 {
-        var nlists = std.ArrayList(macho.nlist_64).init(zld.allocator);
+    pub fn asNlists(stab: Stab, local_sym_index: u32, macho_file: anytype) ![]macho.nlist_64 {
+        var nlists = std.ArrayList(macho.nlist_64).init(macho_file.base.allocator);
         defer nlists.deinit();
 
-        const sym = zld.locals.items[local_sym_index];
+        const sym = macho_file.locals.items[local_sym_index];
         switch (stab) {
             .function => |size| {
                 try nlists.ensureUnusedCapacity(4);
@@ -130,7 +170,7 @@ pub const Relocation = struct {
         offset: u32,
         source_addr: u64,
         target_addr: u64,
-        zld: *Zld,
+        macho_file: *MachO,
     };
 
     pub const Unsigned = struct {
@@ -148,7 +188,7 @@ pub const Relocation = struct {
         pub fn resolve(self: Unsigned, args: ResolveArgs) !void {
             const result = blk: {
                 if (self.subtractor) |subtractor| {
-                    const sym = args.zld.locals.items[subtractor];
+                    const sym = args.macho_file.locals.items[subtractor];
                     break :blk @intCast(i64, args.target_addr) - @intCast(i64, sym.n_value) + self.addend;
                 } else {
                     break :blk @intCast(i64, args.target_addr) + self.addend;
@@ -500,38 +540,59 @@ pub const Relocation = struct {
     }
 };
 
-pub fn init(allocator: *Allocator) TextBlock {
-    return .{
-        .allocator = allocator,
-        .local_sym_index = undefined,
-        .aliases = std.ArrayList(u32).init(allocator),
-        .references = std.AutoArrayHashMap(u32, void).init(allocator),
-        .code = undefined,
-        .relocs = std.ArrayList(Relocation).init(allocator),
-        .size = undefined,
-        .alignment = undefined,
-        .rebases = std.ArrayList(u64).init(allocator),
-        .bindings = std.ArrayList(SymbolAtOffset).init(allocator),
-        .dices = std.ArrayList(macho.data_in_code_entry).init(allocator),
-    };
+pub const empty = TextBlock{
+    .local_sym_index = 0,
+    .code = undefined,
+    .size = 0,
+    .alignment = 0,
+    .prev = null,
+    .next = null,
+    .dbg_info_prev = null,
+    .dbg_info_next = null,
+    .dbg_info_off = undefined,
+    .dbg_info_len = undefined,
+};
+
+pub fn deinit(self: *TextBlock, allocator: *Allocator) void {
+    self.dices.deinit(allocator);
+    self.bindings.deinit(allocator);
+    self.rebases.deinit(allocator);
+    self.relocs.deinit(allocator);
+    self.allocator.free(self.code);
+    self.contained.deinit(allocator);
+    self.aliases.deinit(allocator);
 }
 
-pub fn deinit(self: *TextBlock) void {
-    self.aliases.deinit();
-    self.references.deinit();
-    if (self.contained) |contained| {
-        self.allocator.free(contained);
+/// Returns how much room there is to grow in virtual address space.
+/// File offset relocation happens transparently, so it is not included in
+/// this calculation.
+pub fn capacity(self: TextBlock, macho_file: MachO) u64 {
+    const self_sym = macho_file.locals.items[self.local_sym_index];
+    if (self.next) |next| {
+        const next_sym = macho_file.locals.items[next.local_sym_index];
+        return next_sym.n_value - self_sym.n_value;
+    } else {
+        // We are the last block.
+        // The capacity is limited only by virtual address space.
+        return std.math.maxInt(u64) - self_sym.n_value;
     }
-    self.allocator.free(self.code);
-    self.relocs.deinit();
-    self.rebases.deinit();
-    self.bindings.deinit();
-    self.dices.deinit();
+}
+
+pub fn freeListEligible(self: TextBlock, macho_file: MachO) bool {
+    // No need to keep a free list node for the last block.
+    const next = self.next orelse return false;
+    const self_sym = macho_file.locals.items[self.local_sym_index];
+    const next_sym = macho_file.locals.items[next.local_sym_index];
+    const cap = next_sym.n_value - self_sym.n_value;
+    const ideal_cap = MachO.padToIdeal(self.size);
+    if (cap <= ideal_cap) return false;
+    const surplus = cap - ideal_cap;
+    return surplus >= MachO.min_text_capacity;
 }
 
 const RelocContext = struct {
     base_addr: u64 = 0,
-    zld: *Zld,
+    macho_file: *MachO,
 };
 
 fn initRelocFromObject(rel: macho.relocation_info, object: *Object, ctx: RelocContext) !Relocation {
@@ -548,19 +609,19 @@ fn initRelocFromObject(rel: macho.relocation_info, object: *Object, ctx: RelocCo
         const local_sym_index = object.sections_as_symbols.get(sect_id) orelse blk: {
             const seg = object.load_commands.items[object.segment_cmd_index.?].Segment;
             const sect = seg.sections.items[sect_id];
-            const match = (try ctx.zld.getMatchingSection(sect)) orelse unreachable;
-            const local_sym_index = @intCast(u32, ctx.zld.locals.items.len);
-            const sym_name = try std.fmt.allocPrint(ctx.zld.allocator, "l_{s}_{s}_{s}", .{
+            const match = (try ctx.macho_file.getMatchingSection(sect)) orelse unreachable;
+            const local_sym_index = @intCast(u32, ctx.macho_file.locals.items.len);
+            const sym_name = try std.fmt.allocPrint(ctx.macho_file.base.allocator, "l_{s}_{s}_{s}", .{
                 object.name.?,
                 commands.segmentName(sect),
                 commands.sectionName(sect),
             });
-            defer ctx.zld.allocator.free(sym_name);
+            defer ctx.macho_file.base.allocator.free(sym_name);
 
-            try ctx.zld.locals.append(ctx.zld.allocator, .{
-                .n_strx = try ctx.zld.makeString(sym_name),
+            try ctx.macho_file.locals.append(ctx.macho_file.base.allocator, .{
+                .n_strx = try ctx.macho_file.makeString(sym_name),
                 .n_type = macho.N_SECT,
-                .n_sect = ctx.zld.sectionId(match),
+                .n_sect = ctx.macho_file.sectionId(match),
                 .n_desc = 0,
                 .n_value = sect.addr,
             });
@@ -574,12 +635,12 @@ fn initRelocFromObject(rel: macho.relocation_info, object: *Object, ctx: RelocCo
         const sym = object.symtab.items[rel.r_symbolnum];
         const sym_name = object.getString(sym.n_strx);
 
-        if (Zld.symbolIsSect(sym) and !Zld.symbolIsExt(sym)) {
+        if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) {
             const where_index = object.symbol_mapping.get(rel.r_symbolnum) orelse unreachable;
             parsed_rel.where = .local;
             parsed_rel.where_index = where_index;
         } else {
-            const resolv = ctx.zld.symbol_resolver.get(sym_name) orelse unreachable;
+            const resolv = ctx.macho_file.symbol_resolver.get(sym_name) orelse unreachable;
             switch (resolv.where) {
                 .global => {
                     parsed_rel.where = .local;
@@ -599,6 +660,7 @@ fn initRelocFromObject(rel: macho.relocation_info, object: *Object, ctx: RelocCo
 
 pub fn parseRelocsFromObject(
     self: *TextBlock,
+    allocator: *Allocator,
     relocs: []macho.relocation_info,
     object: *Object,
     ctx: RelocContext,
@@ -638,11 +700,11 @@ pub fn parseRelocsFromObject(
             const sym = object.symtab.items[rel.r_symbolnum];
             const sym_name = object.getString(sym.n_strx);
 
-            if (Zld.symbolIsSect(sym) and !Zld.symbolIsExt(sym)) {
+            if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) {
                 const where_index = object.symbol_mapping.get(rel.r_symbolnum) orelse unreachable;
                 subtractor = where_index;
             } else {
-                const resolv = ctx.zld.symbol_resolver.get(sym_name) orelse unreachable;
+                const resolv = ctx.macho_file.symbol_resolver.get(sym_name) orelse unreachable;
                 assert(resolv.where == .global);
                 subtractor = resolv.local_sym_index;
             }
@@ -732,11 +794,7 @@ pub fn parseRelocsFromObject(
             else => unreachable,
         }
 
-        try self.relocs.append(parsed_rel);
-
-        if (parsed_rel.where == .local) {
-            try self.references.put(parsed_rel.where_index, {});
-        }
+        try self.relocs.append(allocator, parsed_rel);
 
         const is_via_got = switch (parsed_rel.payload) {
             .pointer_to_got => true,
@@ -747,28 +805,30 @@ pub fn parseRelocsFromObject(
         };
 
         if (is_via_got) blk: {
-            const key = Zld.GotIndirectionKey{
+            const key = MachO.GotIndirectionKey{
                 .where = switch (parsed_rel.where) {
                     .local => .local,
                     .import => .import,
                 },
                 .where_index = parsed_rel.where_index,
             };
-            if (ctx.zld.got_entries.contains(key)) break :blk;
+            if (ctx.macho_file.got_entries_map.contains(key)) break :blk;
 
-            try ctx.zld.got_entries.putNoClobber(ctx.zld.allocator, key, {});
+            const got_index = @intCast(u32, ctx.macho_file.got_entries.items.len);
+            try ctx.macho_file.got_entries.append(ctx.macho_file.base.allocator, key);
+            try ctx.macho_file.got_entries_map.putNoClobber(ctx.macho_file.base.allocator, key, got_index);
         } else if (parsed_rel.payload == .unsigned) {
             switch (parsed_rel.where) {
                 .import => {
-                    try self.bindings.append(.{
+                    try self.bindings.append(allocator, .{
                         .local_sym_index = parsed_rel.where_index,
                         .offset = parsed_rel.offset,
                     });
                 },
                 .local => {
-                    const source_sym = ctx.zld.locals.items[self.local_sym_index];
-                    const match = ctx.zld.unpackSectionId(source_sym.n_sect);
-                    const seg = ctx.zld.load_commands.items[match.seg].Segment;
+                    const source_sym = ctx.macho_file.locals.items[self.local_sym_index];
+                    const match = ctx.macho_file.unpackSectionId(source_sym.n_sect);
+                    const seg = ctx.macho_file.load_commands.items[match.seg].Segment;
                     const sect = seg.sections.items[match.sect];
                     const sect_type = commands.sectionType(sect);
 
@@ -778,12 +838,12 @@ pub fn parseRelocsFromObject(
                         // TODO actually, a check similar to what dyld is doing, that is, verifying
                         // that the segment is writable should be enough here.
                         const is_right_segment = blk: {
-                            if (ctx.zld.data_segment_cmd_index) |idx| {
+                            if (ctx.macho_file.data_segment_cmd_index) |idx| {
                                 if (match.seg == idx) {
                                     break :blk true;
                                 }
                             }
-                            if (ctx.zld.data_const_segment_cmd_index) |idx| {
+                            if (ctx.macho_file.data_const_segment_cmd_index) |idx| {
                                 if (match.seg == idx) {
                                     break :blk true;
                                 }
@@ -804,15 +864,17 @@ pub fn parseRelocsFromObject(
                     };
 
                     if (should_rebase) {
-                        try self.rebases.append(parsed_rel.offset);
+                        try self.rebases.append(allocator, parsed_rel.offset);
                     }
                 },
             }
         } else if (parsed_rel.payload == .branch) blk: {
             if (parsed_rel.where != .import) break :blk;
-            if (ctx.zld.stubs.contains(parsed_rel.where_index)) break :blk;
+            if (ctx.macho_file.stubs_map.contains(parsed_rel.where_index)) break :blk;
 
-            try ctx.zld.stubs.putNoClobber(ctx.zld.allocator, parsed_rel.where_index, {});
+            const stubs_index = @intCast(u32, ctx.macho_file.stubs.items.len);
+            try ctx.macho_file.stubs.append(ctx.macho_file.base.allocator, parsed_rel.where_index);
+            try ctx.macho_file.stubs_map.putNoClobber(ctx.macho_file.base.allocator, parsed_rel.where_index, stubs_index);
         }
     }
 }
@@ -852,7 +914,7 @@ fn parseUnsigned(
 
     if (rel.r_extern == 0) {
         assert(out.where == .local);
-        const target_sym = ctx.zld.locals.items[out.where_index];
+        const target_sym = ctx.macho_file.locals.items[out.where_index];
         addend -= @intCast(i64, target_sym.n_value);
     }
 
@@ -872,7 +934,7 @@ fn parseBranch(self: TextBlock, rel: macho.relocation_info, out: *Relocation, ct
 
     out.payload = .{
         .branch = .{
-            .arch = ctx.zld.target.?.cpu.arch,
+            .arch = ctx.macho_file.base.options.target.cpu.arch,
         },
     };
 }
@@ -948,10 +1010,10 @@ fn parseSigned(self: TextBlock, rel: macho.relocation_info, out: *Relocation, ct
     var addend: i64 = mem.readIntLittle(i32, self.code[out.offset..][0..4]) + correction;
 
     if (rel.r_extern == 0) {
-        const source_sym = ctx.zld.locals.items[self.local_sym_index];
+        const source_sym = ctx.macho_file.locals.items[self.local_sym_index];
         const target_sym = switch (out.where) {
-            .local => ctx.zld.locals.items[out.where_index],
-            .import => ctx.zld.imports.items[out.where_index],
+            .local => ctx.macho_file.locals.items[out.where_index],
+            .import => ctx.macho_file.imports.items[out.where_index],
         };
         addend = @intCast(i64, source_sym.n_value + out.offset + 4) + addend - @intCast(i64, target_sym.n_value);
     }
@@ -986,12 +1048,12 @@ fn parseLoad(self: TextBlock, rel: macho.relocation_info, out: *Relocation) void
     };
 }
 
-pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
+pub fn resolveRelocs(self: *TextBlock, macho_file: *MachO) !void {
     for (self.relocs.items) |rel| {
         log.debug("relocating {}", .{rel});
 
         const source_addr = blk: {
-            const sym = zld.locals.items[self.local_sym_index];
+            const sym = macho_file.locals.items[self.local_sym_index];
             break :blk sym.n_value + rel.offset;
         };
         const target_addr = blk: {
@@ -1004,9 +1066,9 @@ pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
             };
 
             if (is_via_got) {
-                const dc_seg = zld.load_commands.items[zld.data_const_segment_cmd_index.?].Segment;
-                const got = dc_seg.sections.items[zld.got_section_index.?];
-                const got_index = zld.got_entries.getIndex(.{
+                const dc_seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment;
+                const got = dc_seg.sections.items[macho_file.got_section_index.?];
+                const got_index = macho_file.got_entries_map.get(.{
                     .where = switch (rel.where) {
                         .local => .local,
                         .import => .import,
@@ -1014,10 +1076,10 @@ pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
                     .where_index = rel.where_index,
                 }) orelse {
                     const sym = switch (rel.where) {
-                        .local => zld.locals.items[rel.where_index],
-                        .import => zld.imports.items[rel.where_index],
+                        .local => macho_file.locals.items[rel.where_index],
+                        .import => macho_file.imports.items[rel.where_index],
                     };
-                    log.err("expected GOT entry for symbol '{s}'", .{zld.getString(sym.n_strx)});
+                    log.err("expected GOT entry for symbol '{s}'", .{macho_file.getString(sym.n_strx)});
                     log.err("  this is an internal linker error", .{});
                     return error.FailedToResolveRelocationTarget;
                 };
@@ -1026,11 +1088,11 @@ pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
 
             switch (rel.where) {
                 .local => {
-                    const sym = zld.locals.items[rel.where_index];
+                    const sym = macho_file.locals.items[rel.where_index];
                     const is_tlv = is_tlv: {
-                        const source_sym = zld.locals.items[self.local_sym_index];
-                        const match = zld.unpackSectionId(source_sym.n_sect);
-                        const seg = zld.load_commands.items[match.seg].Segment;
+                        const source_sym = macho_file.locals.items[self.local_sym_index];
+                        const match = macho_file.unpackSectionId(source_sym.n_sect);
+                        const seg = macho_file.load_commands.items[match.seg].Segment;
                         const sect = seg.sections.items[match.sect];
                         break :is_tlv commands.sectionType(sect) == macho.S_THREAD_LOCAL_VARIABLES;
                     };
@@ -1040,11 +1102,11 @@ pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
                         // defined TLV template init section in the following order:
                         // * wrt to __thread_data if defined, then
                         // * wrt to __thread_bss
-                        const seg = zld.load_commands.items[zld.data_segment_cmd_index.?].Segment;
+                        const seg = macho_file.load_commands.items[macho_file.data_segment_cmd_index.?].Segment;
                         const base_address = inner: {
-                            if (zld.tlv_data_section_index) |i| {
+                            if (macho_file.tlv_data_section_index) |i| {
                                 break :inner seg.sections.items[i].addr;
-                            } else if (zld.tlv_bss_section_index) |i| {
+                            } else if (macho_file.tlv_bss_section_index) |i| {
                                 break :inner seg.sections.items[i].addr;
                             } else {
                                 log.err("threadlocal variables present but no initializer sections found", .{});
@@ -1059,12 +1121,12 @@ pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
                     break :blk sym.n_value;
                 },
                 .import => {
-                    const stubs_index = zld.stubs.getIndex(rel.where_index) orelse {
+                    const stubs_index = macho_file.stubs_map.get(rel.where_index) orelse {
                         // TODO verify in TextBlock that the symbol is indeed dynamically bound.
                         break :blk 0; // Dynamically bound by dyld.
                     };
-                    const segment = zld.load_commands.items[zld.text_segment_cmd_index.?].Segment;
-                    const stubs = segment.sections.items[zld.stubs_section_index.?];
+                    const segment = macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment;
+                    const stubs = segment.sections.items[macho_file.stubs_section_index.?];
                     break :blk stubs.addr + stubs_index * stubs.reserved2;
                 },
             }
@@ -1078,14 +1140,14 @@ pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
             .offset = rel.offset,
             .source_addr = source_addr,
             .target_addr = target_addr,
-            .zld = zld,
+            .macho_file = macho_file,
         });
     }
 }
 
-pub fn print_this(self: *const TextBlock, zld: *Zld) void {
+pub fn print_this(self: *const TextBlock, macho_file: MachO) void {
     log.warn("TextBlock", .{});
-    log.warn("  {}: {}", .{ self.local_sym_index, zld.locals.items[self.local_sym_index] });
+    log.warn("  {}: {}", .{ self.local_sym_index, macho_file.locals.items[self.local_sym_index] });
     if (self.stab) |stab| {
         log.warn("  stab: {}", .{stab});
     }
@@ -1125,11 +1187,11 @@ pub fn print_this(self: *const TextBlock, zld: *Zld) void {
     log.warn("  align = {}", .{self.alignment});
 }
 
-pub fn print(self: *const TextBlock, zld: *Zld) void {
+pub fn print(self: *const TextBlock, macho_file: MachO) void {
     if (self.prev) |prev| {
-        prev.print(zld);
+        prev.print(macho_file);
     }
-    self.print_this(zld);
+    self.print_this(macho_file);
 }
 
 const RelocIterator = struct {
@@ -1159,8 +1221,8 @@ fn filterRelocs(relocs: []macho.relocation_info, start_addr: u64, end_addr: u64)
         }
     };
 
-    const start = Zld.findFirst(macho.relocation_info, relocs, 0, Predicate{ .addr = end_addr });
-    const end = Zld.findFirst(macho.relocation_info, relocs, start, Predicate{ .addr = start_addr });
+    const start = MachO.findFirst(macho.relocation_info, relocs, 0, Predicate{ .addr = end_addr });
+    const end = MachO.findFirst(macho.relocation_info, relocs, start, Predicate{ .addr = start_addr });
 
     return relocs[start..end];
 }
src/link/MachO/Zld.zig
@@ -1,3062 +0,0 @@
-const Zld = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const leb = std.leb;
-const mem = std.mem;
-const meta = std.meta;
-const fs = std.fs;
-const macho = std.macho;
-const math = std.math;
-const log = std.log.scoped(.zld);
-const aarch64 = @import("../../codegen/aarch64.zig");
-
-const Allocator = mem.Allocator;
-const Archive = @import("Archive.zig");
-const CodeSignature = @import("CodeSignature.zig");
-const Dylib = @import("Dylib.zig");
-const Object = @import("Object.zig");
-const TextBlock = @import("TextBlock.zig");
-const Trie = @import("Trie.zig");
-
-usingnamespace @import("commands.zig");
-usingnamespace @import("bind.zig");
-
-allocator: *Allocator,
-
-target: ?std.Target = null,
-page_size: ?u16 = null,
-file: ?fs.File = null,
-output: ?Output = null,
-
-// TODO these args will become obselete once Zld is coalesced with incremental
-// linker.
-stack_size: u64 = 0,
-
-objects: std.ArrayListUnmanaged(*Object) = .{},
-archives: std.ArrayListUnmanaged(*Archive) = .{},
-dylibs: std.ArrayListUnmanaged(*Dylib) = .{},
-
-next_dylib_ordinal: u16 = 1,
-
-load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
-
-pagezero_segment_cmd_index: ?u16 = null,
-text_segment_cmd_index: ?u16 = null,
-data_const_segment_cmd_index: ?u16 = null,
-data_segment_cmd_index: ?u16 = null,
-linkedit_segment_cmd_index: ?u16 = null,
-dyld_info_cmd_index: ?u16 = null,
-symtab_cmd_index: ?u16 = null,
-dysymtab_cmd_index: ?u16 = null,
-dylinker_cmd_index: ?u16 = null,
-data_in_code_cmd_index: ?u16 = null,
-function_starts_cmd_index: ?u16 = null,
-main_cmd_index: ?u16 = null,
-dylib_id_cmd_index: ?u16 = null,
-version_min_cmd_index: ?u16 = null,
-source_version_cmd_index: ?u16 = null,
-uuid_cmd_index: ?u16 = null,
-code_signature_cmd_index: ?u16 = null,
-
-// __TEXT segment sections
-text_section_index: ?u16 = null,
-stubs_section_index: ?u16 = null,
-stub_helper_section_index: ?u16 = null,
-text_const_section_index: ?u16 = null,
-cstring_section_index: ?u16 = null,
-ustring_section_index: ?u16 = null,
-gcc_except_tab_section_index: ?u16 = null,
-unwind_info_section_index: ?u16 = null,
-eh_frame_section_index: ?u16 = null,
-
-objc_methlist_section_index: ?u16 = null,
-objc_methname_section_index: ?u16 = null,
-objc_methtype_section_index: ?u16 = null,
-objc_classname_section_index: ?u16 = null,
-
-// __DATA_CONST segment sections
-got_section_index: ?u16 = null,
-mod_init_func_section_index: ?u16 = null,
-mod_term_func_section_index: ?u16 = null,
-data_const_section_index: ?u16 = null,
-
-objc_cfstring_section_index: ?u16 = null,
-objc_classlist_section_index: ?u16 = null,
-objc_imageinfo_section_index: ?u16 = null,
-
-// __DATA segment sections
-tlv_section_index: ?u16 = null,
-tlv_data_section_index: ?u16 = null,
-tlv_bss_section_index: ?u16 = null,
-la_symbol_ptr_section_index: ?u16 = null,
-data_section_index: ?u16 = null,
-bss_section_index: ?u16 = null,
-common_section_index: ?u16 = null,
-
-objc_const_section_index: ?u16 = null,
-objc_selrefs_section_index: ?u16 = null,
-objc_classrefs_section_index: ?u16 = null,
-objc_data_section_index: ?u16 = null,
-
-locals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-globals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-imports: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-undefs: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-tentatives: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-symbol_resolver: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{},
-
-strtab: std.ArrayListUnmanaged(u8) = .{},
-
-stubs: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
-got_entries: std.AutoArrayHashMapUnmanaged(GotIndirectionKey, void) = .{},
-
-stub_helper_stubs_start_off: ?u64 = null,
-
-blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},
-
-has_dices: bool = false,
-has_stabs: bool = false,
-
-const SymbolWithLoc = struct {
-    // Table where the symbol can be found.
-    where: enum {
-        global,
-        import,
-        undef,
-        tentative,
-    },
-    where_index: u32,
-    local_sym_index: u32 = 0,
-    file: u16 = 0,
-};
-
-pub const GotIndirectionKey = struct {
-    where: enum {
-        local,
-        import,
-    },
-    where_index: u32,
-};
-
-pub const Output = struct {
-    tag: enum { exe, dylib },
-    path: []const u8,
-    install_name: ?[]const u8 = null,
-};
-
-/// Default path to dyld
-const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld";
-
-pub fn init(allocator: *Allocator) !Zld {
-    return Zld{ .allocator = allocator };
-}
-
-pub fn deinit(self: *Zld) void {
-    self.stubs.deinit(self.allocator);
-    self.got_entries.deinit(self.allocator);
-
-    for (self.load_commands.items) |*lc| {
-        lc.deinit(self.allocator);
-    }
-    self.load_commands.deinit(self.allocator);
-
-    for (self.objects.items) |object| {
-        object.deinit();
-        self.allocator.destroy(object);
-    }
-    self.objects.deinit(self.allocator);
-
-    for (self.archives.items) |archive| {
-        archive.deinit();
-        self.allocator.destroy(archive);
-    }
-    self.archives.deinit(self.allocator);
-
-    for (self.dylibs.items) |dylib| {
-        dylib.deinit();
-        self.allocator.destroy(dylib);
-    }
-    self.dylibs.deinit(self.allocator);
-
-    self.locals.deinit(self.allocator);
-    self.globals.deinit(self.allocator);
-    self.imports.deinit(self.allocator);
-    self.undefs.deinit(self.allocator);
-    self.tentatives.deinit(self.allocator);
-
-    for (self.symbol_resolver.keys()) |key| {
-        self.allocator.free(key);
-    }
-    self.symbol_resolver.deinit(self.allocator);
-
-    self.strtab.deinit(self.allocator);
-
-    // TODO dealloc all blocks
-    self.blocks.deinit(self.allocator);
-}
-
-pub fn closeFiles(self: Zld) void {
-    for (self.objects.items) |object| {
-        object.closeFile();
-    }
-    for (self.archives.items) |archive| {
-        archive.closeFile();
-    }
-    if (self.file) |f| f.close();
-}
-
-const LinkArgs = struct {
-    syslibroot: ?[]const u8,
-    libs: []const []const u8,
-    rpaths: []const []const u8,
-};
-
-pub fn link(self: *Zld, files: []const []const u8, output: Output, args: LinkArgs) !void {
-    if (files.len == 0) return error.NoInputFiles;
-    if (output.path.len == 0) return error.EmptyOutputPath;
-
-    self.page_size = switch (self.target.?.cpu.arch) {
-        .aarch64 => 0x4000,
-        .x86_64 => 0x1000,
-        else => unreachable,
-    };
-    self.output = output;
-    self.file = try fs.cwd().createFile(self.output.?.path, .{
-        .truncate = true,
-        .read = true,
-        .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777,
-    });
-
-    try self.populateMetadata();
-    try self.parseInputFiles(files, args.syslibroot);
-    try self.parseLibs(args.libs, args.syslibroot);
-    try self.resolveSymbols();
-    try self.parseTextBlocks();
-
-    {
-        // Add dyld_stub_binder as the final GOT entry.
-        const resolv = self.symbol_resolver.get("dyld_stub_binder") orelse unreachable;
-        try self.got_entries.putNoClobber(self.allocator, .{
-            .where = .import,
-            .where_index = resolv.where_index,
-        }, {});
-    }
-
-    try self.sortSections();
-    try self.addRpaths(args.rpaths);
-    try self.addDataInCodeLC();
-    try self.addCodeSignatureLC();
-    try self.allocateTextSegment();
-    try self.allocateDataConstSegment();
-    try self.allocateDataSegment();
-    self.allocateLinkeditSegment();
-    try self.allocateTextBlocks();
-
-    // log.warn("locals", .{});
-    // for (self.locals.items) |sym, id| {
-    //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-    // }
-
-    // log.warn("globals", .{});
-    // for (self.globals.items) |sym, id| {
-    //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-    // }
-
-    // log.warn("tentatives", .{});
-    // for (self.tentatives.items) |sym, id| {
-    //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-    // }
-
-    // log.warn("undefines", .{});
-    // for (self.undefs.items) |sym, id| {
-    //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-    // }
-
-    // log.warn("imports", .{});
-    // for (self.imports.items) |sym, id| {
-    //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
-    // }
-
-    // log.warn("symbol resolver", .{});
-    // for (self.symbol_resolver.keys()) |key| {
-    //     log.warn("  {s} => {}", .{ key, self.symbol_resolver.get(key).? });
-    // }
-
-    // log.warn("mappings", .{});
-    // for (self.objects.items) |object, id| {
-    //     const object_id = @intCast(u16, id);
-    //     log.warn("  in object {s}", .{object.name.?});
-    //     for (object.symtab.items) |sym, sym_id| {
-    //         if (object.symbol_mapping.get(@intCast(u32, sym_id))) |local_id| {
-    //             log.warn("    | {d} => {d}", .{ sym_id, local_id });
-    //         } else {
-    //             log.warn("    | {d} no local mapping for {s}", .{ sym_id, object.getString(sym.n_strx) });
-    //         }
-    //     }
-    // }
-
-    // var it = self.blocks.iterator();
-    // while (it.next()) |entry| {
-    //     const seg = self.load_commands.items[entry.key_ptr.seg].Segment;
-    //     const sect = seg.sections.items[entry.key_ptr.sect];
-
-    //     log.warn("\n\n{s},{s} contents:", .{ segmentName(sect), sectionName(sect) });
-    //     log.warn("  {}", .{sect});
-    //     entry.value_ptr.*.print(self);
-    // }
-
-    try self.flush();
-}
-
-fn parseInputFiles(self: *Zld, files: []const []const u8, syslibroot: ?[]const u8) !void {
-    for (files) |file_name| {
-        const full_path = full_path: {
-            var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-            const path = try std.fs.realpath(file_name, &buffer);
-            break :full_path try self.allocator.dupe(u8, path);
-        };
-
-        if (try Object.createAndParseFromPath(self.allocator, self.target.?.cpu.arch, full_path)) |object| {
-            try self.objects.append(self.allocator, object);
-            continue;
-        }
-
-        if (try Archive.createAndParseFromPath(self.allocator, self.target.?.cpu.arch, full_path)) |archive| {
-            try self.archives.append(self.allocator, archive);
-            continue;
-        }
-
-        if (try Dylib.createAndParseFromPath(
-            self.allocator,
-            self.target.?.cpu.arch,
-            full_path,
-            .{ .syslibroot = syslibroot },
-        )) |dylibs| {
-            defer self.allocator.free(dylibs);
-            try self.dylibs.appendSlice(self.allocator, dylibs);
-            continue;
-        }
-
-        log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
-    }
-}
-
-fn parseLibs(self: *Zld, libs: []const []const u8, syslibroot: ?[]const u8) !void {
-    for (libs) |lib| {
-        if (try Dylib.createAndParseFromPath(
-            self.allocator,
-            self.target.?.cpu.arch,
-            lib,
-            .{ .syslibroot = syslibroot },
-        )) |dylibs| {
-            defer self.allocator.free(dylibs);
-            try self.dylibs.appendSlice(self.allocator, dylibs);
-            continue;
-        }
-
-        if (try Archive.createAndParseFromPath(self.allocator, self.target.?.cpu.arch, lib)) |archive| {
-            try self.archives.append(self.allocator, archive);
-            continue;
-        }
-
-        log.warn("unknown filetype for a library: '{s}'", .{lib});
-    }
-}
-
-pub const MatchingSection = struct {
-    seg: u16,
-    sect: u16,
-};
-
-pub fn getMatchingSection(self: *Zld, sect: macho.section_64) !?MatchingSection {
-    const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-    const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    const segname = segmentName(sect);
-    const sectname = sectionName(sect);
-
-    const res: ?MatchingSection = blk: {
-        switch (sectionType(sect)) {
-            macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => {
-                if (self.text_const_section_index == null) {
-                    self.text_const_section_index = @intCast(u16, text_seg.sections.items.len);
-                    try text_seg.addSection(self.allocator, "__const", .{});
-                }
-
-                break :blk .{
-                    .seg = self.text_segment_cmd_index.?,
-                    .sect = self.text_const_section_index.?,
-                };
-            },
-            macho.S_CSTRING_LITERALS => {
-                if (mem.eql(u8, sectname, "__objc_methname")) {
-                    // TODO it seems the common values within the sections in objects are deduplicated/merged
-                    // on merging the sections' contents.
-                    if (self.objc_methname_section_index == null) {
-                        self.objc_methname_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.allocator, "__objc_methname", .{
-                            .flags = macho.S_CSTRING_LITERALS,
-                        });
-                    }
-
-                    break :blk .{
-                        .seg = self.text_segment_cmd_index.?,
-                        .sect = self.objc_methname_section_index.?,
-                    };
-                } else if (mem.eql(u8, sectname, "__objc_methtype")) {
-                    if (self.objc_methtype_section_index == null) {
-                        self.objc_methtype_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.allocator, "__objc_methtype", .{
-                            .flags = macho.S_CSTRING_LITERALS,
-                        });
-                    }
-
-                    break :blk .{
-                        .seg = self.text_segment_cmd_index.?,
-                        .sect = self.objc_methtype_section_index.?,
-                    };
-                } else if (mem.eql(u8, sectname, "__objc_classname")) {
-                    if (self.objc_classname_section_index == null) {
-                        self.objc_classname_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.allocator, "__objc_classname", .{});
-                    }
-
-                    break :blk .{
-                        .seg = self.text_segment_cmd_index.?,
-                        .sect = self.objc_classname_section_index.?,
-                    };
-                }
-
-                if (self.cstring_section_index == null) {
-                    self.cstring_section_index = @intCast(u16, text_seg.sections.items.len);
-                    try text_seg.addSection(self.allocator, "__cstring", .{
-                        .flags = macho.S_CSTRING_LITERALS,
-                    });
-                }
-
-                break :blk .{
-                    .seg = self.text_segment_cmd_index.?,
-                    .sect = self.cstring_section_index.?,
-                };
-            },
-            macho.S_LITERAL_POINTERS => {
-                if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__objc_selrefs")) {
-                    if (self.objc_selrefs_section_index == null) {
-                        self.objc_selrefs_section_index = @intCast(u16, data_seg.sections.items.len);
-                        try data_seg.addSection(self.allocator, "__objc_selrefs", .{
-                            .flags = macho.S_LITERAL_POINTERS,
-                        });
-                    }
-
-                    break :blk .{
-                        .seg = self.data_segment_cmd_index.?,
-                        .sect = self.objc_selrefs_section_index.?,
-                    };
-                }
-
-                // TODO investigate
-                break :blk null;
-            },
-            macho.S_MOD_INIT_FUNC_POINTERS => {
-                if (self.mod_init_func_section_index == null) {
-                    self.mod_init_func_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                    try data_const_seg.addSection(self.allocator, "__mod_init_func", .{
-                        .flags = macho.S_MOD_INIT_FUNC_POINTERS,
-                    });
-                }
-
-                break :blk .{
-                    .seg = self.data_const_segment_cmd_index.?,
-                    .sect = self.mod_init_func_section_index.?,
-                };
-            },
-            macho.S_MOD_TERM_FUNC_POINTERS => {
-                if (self.mod_term_func_section_index == null) {
-                    self.mod_term_func_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                    try data_const_seg.addSection(self.allocator, "__mod_term_func", .{
-                        .flags = macho.S_MOD_TERM_FUNC_POINTERS,
-                    });
-                }
-
-                break :blk .{
-                    .seg = self.data_const_segment_cmd_index.?,
-                    .sect = self.mod_term_func_section_index.?,
-                };
-            },
-            macho.S_ZEROFILL => {
-                if (mem.eql(u8, sectname, "__common")) {
-                    if (self.common_section_index == null) {
-                        self.common_section_index = @intCast(u16, data_seg.sections.items.len);
-                        try data_seg.addSection(self.allocator, "__common", .{
-                            .flags = macho.S_ZEROFILL,
-                        });
-                    }
-
-                    break :blk .{
-                        .seg = self.data_segment_cmd_index.?,
-                        .sect = self.common_section_index.?,
-                    };
-                } else {
-                    if (self.bss_section_index == null) {
-                        self.bss_section_index = @intCast(u16, data_seg.sections.items.len);
-                        try data_seg.addSection(self.allocator, "__bss", .{
-                            .flags = macho.S_ZEROFILL,
-                        });
-                    }
-
-                    break :blk .{
-                        .seg = self.data_segment_cmd_index.?,
-                        .sect = self.bss_section_index.?,
-                    };
-                }
-            },
-            macho.S_THREAD_LOCAL_VARIABLES => {
-                if (self.tlv_section_index == null) {
-                    self.tlv_section_index = @intCast(u16, data_seg.sections.items.len);
-                    try data_seg.addSection(self.allocator, "__thread_vars", .{
-                        .flags = macho.S_THREAD_LOCAL_VARIABLES,
-                    });
-                }
-
-                break :blk .{
-                    .seg = self.data_segment_cmd_index.?,
-                    .sect = self.tlv_section_index.?,
-                };
-            },
-            macho.S_THREAD_LOCAL_REGULAR => {
-                if (self.tlv_data_section_index == null) {
-                    self.tlv_data_section_index = @intCast(u16, data_seg.sections.items.len);
-                    try data_seg.addSection(self.allocator, "__thread_data", .{
-                        .flags = macho.S_THREAD_LOCAL_REGULAR,
-                    });
-                }
-
-                break :blk .{
-                    .seg = self.data_segment_cmd_index.?,
-                    .sect = self.tlv_data_section_index.?,
-                };
-            },
-            macho.S_THREAD_LOCAL_ZEROFILL => {
-                if (self.tlv_bss_section_index == null) {
-                    self.tlv_bss_section_index = @intCast(u16, data_seg.sections.items.len);
-                    try data_seg.addSection(self.allocator, "__thread_bss", .{
-                        .flags = macho.S_THREAD_LOCAL_ZEROFILL,
-                    });
-                }
-
-                break :blk .{
-                    .seg = self.data_segment_cmd_index.?,
-                    .sect = self.tlv_bss_section_index.?,
-                };
-            },
-            macho.S_COALESCED => {
-                if (mem.eql(u8, "__TEXT", segname) and mem.eql(u8, "__eh_frame", sectname)) {
-                    // TODO I believe __eh_frame is currently part of __unwind_info section
-                    // in the latest ld64 output.
-                    if (self.eh_frame_section_index == null) {
-                        self.eh_frame_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.allocator, "__eh_frame", .{});
-                    }
-
-                    break :blk .{
-                        .seg = self.text_segment_cmd_index.?,
-                        .sect = self.eh_frame_section_index.?,
-                    };
-                }
-
-                // TODO audit this: is this the right mapping?
-                if (self.data_const_section_index == null) {
-                    self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                    try data_const_seg.addSection(self.allocator, "__const", .{});
-                }
-
-                break :blk .{
-                    .seg = self.data_const_segment_cmd_index.?,
-                    .sect = self.data_const_section_index.?,
-                };
-            },
-            macho.S_REGULAR => {
-                if (sectionIsCode(sect)) {
-                    if (self.text_section_index == null) {
-                        self.text_section_index = @intCast(u16, text_seg.sections.items.len);
-                        try text_seg.addSection(self.allocator, "__text", .{
-                            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-                        });
-                    }
-
-                    break :blk .{
-                        .seg = self.text_segment_cmd_index.?,
-                        .sect = self.text_section_index.?,
-                    };
-                }
-                if (sectionIsDebug(sect)) {
-                    // TODO debug attributes
-                    if (mem.eql(u8, "__LD", segname) and mem.eql(u8, "__compact_unwind", sectname)) {
-                        log.debug("TODO compact unwind section: type 0x{x}, name '{s},{s}'", .{
-                            sect.flags, segname, sectname,
-                        });
-                    }
-                    break :blk null;
-                }
-
-                if (mem.eql(u8, segname, "__TEXT")) {
-                    if (mem.eql(u8, sectname, "__ustring")) {
-                        if (self.ustring_section_index == null) {
-                            self.ustring_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.allocator, "__ustring", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.text_segment_cmd_index.?,
-                            .sect = self.ustring_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__gcc_except_tab")) {
-                        if (self.gcc_except_tab_section_index == null) {
-                            self.gcc_except_tab_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.allocator, "__gcc_except_tab", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.text_segment_cmd_index.?,
-                            .sect = self.gcc_except_tab_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__objc_methlist")) {
-                        if (self.objc_methlist_section_index == null) {
-                            self.objc_methlist_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.allocator, "__objc_methlist", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.text_segment_cmd_index.?,
-                            .sect = self.objc_methlist_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__rodata") or
-                        mem.eql(u8, sectname, "__typelink") or
-                        mem.eql(u8, sectname, "__itablink") or
-                        mem.eql(u8, sectname, "__gosymtab") or
-                        mem.eql(u8, sectname, "__gopclntab"))
-                    {
-                        if (self.data_const_section_index == null) {
-                            self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.allocator, "__const", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_const_segment_cmd_index.?,
-                            .sect = self.data_const_section_index.?,
-                        };
-                    } else {
-                        if (self.text_const_section_index == null) {
-                            self.text_const_section_index = @intCast(u16, text_seg.sections.items.len);
-                            try text_seg.addSection(self.allocator, "__const", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.text_segment_cmd_index.?,
-                            .sect = self.text_const_section_index.?,
-                        };
-                    }
-                }
-
-                if (mem.eql(u8, segname, "__DATA_CONST")) {
-                    if (self.data_const_section_index == null) {
-                        self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                        try data_const_seg.addSection(self.allocator, "__const", .{});
-                    }
-
-                    break :blk .{
-                        .seg = self.data_const_segment_cmd_index.?,
-                        .sect = self.data_const_section_index.?,
-                    };
-                }
-
-                if (mem.eql(u8, segname, "__DATA")) {
-                    if (mem.eql(u8, sectname, "__const")) {
-                        if (self.data_const_section_index == null) {
-                            self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.allocator, "__const", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_const_segment_cmd_index.?,
-                            .sect = self.data_const_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__cfstring")) {
-                        if (self.objc_cfstring_section_index == null) {
-                            self.objc_cfstring_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.allocator, "__cfstring", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_const_segment_cmd_index.?,
-                            .sect = self.objc_cfstring_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__objc_classlist")) {
-                        if (self.objc_classlist_section_index == null) {
-                            self.objc_classlist_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.allocator, "__objc_classlist", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_const_segment_cmd_index.?,
-                            .sect = self.objc_classlist_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__objc_imageinfo")) {
-                        if (self.objc_imageinfo_section_index == null) {
-                            self.objc_imageinfo_section_index = @intCast(u16, data_const_seg.sections.items.len);
-                            try data_const_seg.addSection(self.allocator, "__objc_imageinfo", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_const_segment_cmd_index.?,
-                            .sect = self.objc_imageinfo_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__objc_const")) {
-                        if (self.objc_const_section_index == null) {
-                            self.objc_const_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.allocator, "__objc_const", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_segment_cmd_index.?,
-                            .sect = self.objc_const_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__objc_classrefs")) {
-                        if (self.objc_classrefs_section_index == null) {
-                            self.objc_classrefs_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.allocator, "__objc_classrefs", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_segment_cmd_index.?,
-                            .sect = self.objc_classrefs_section_index.?,
-                        };
-                    } else if (mem.eql(u8, sectname, "__objc_data")) {
-                        if (self.objc_data_section_index == null) {
-                            self.objc_data_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.allocator, "__objc_data", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_segment_cmd_index.?,
-                            .sect = self.objc_data_section_index.?,
-                        };
-                    } else {
-                        if (self.data_section_index == null) {
-                            self.data_section_index = @intCast(u16, data_seg.sections.items.len);
-                            try data_seg.addSection(self.allocator, "__data", .{});
-                        }
-
-                        break :blk .{
-                            .seg = self.data_segment_cmd_index.?,
-                            .sect = self.data_section_index.?,
-                        };
-                    }
-                }
-
-                if (mem.eql(u8, "__LLVM", segname) and mem.eql(u8, "__asm", sectname)) {
-                    log.debug("TODO LLVM asm section: type 0x{x}, name '{s},{s}'", .{
-                        sect.flags, segname, sectname,
-                    });
-                }
-
-                break :blk null;
-            },
-            else => break :blk null,
-        }
-    };
-
-    return res;
-}
-
-fn sortSections(self: *Zld) !void {
-    var text_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator);
-    defer text_index_mapping.deinit();
-    var data_const_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator);
-    defer data_const_index_mapping.deinit();
-    var data_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator);
-    defer data_index_mapping.deinit();
-
-    {
-        // __TEXT segment
-        const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-        var sections = seg.sections.toOwnedSlice(self.allocator);
-        defer self.allocator.free(sections);
-        try seg.sections.ensureCapacity(self.allocator, sections.len);
-
-        const indices = &[_]*?u16{
-            &self.text_section_index,
-            &self.stubs_section_index,
-            &self.stub_helper_section_index,
-            &self.gcc_except_tab_section_index,
-            &self.cstring_section_index,
-            &self.ustring_section_index,
-            &self.text_const_section_index,
-            &self.objc_methname_section_index,
-            &self.objc_methtype_section_index,
-            &self.objc_classname_section_index,
-            &self.eh_frame_section_index,
-        };
-        for (indices) |maybe_index| {
-            const new_index: u16 = if (maybe_index.*) |index| blk: {
-                const idx = @intCast(u16, seg.sections.items.len);
-                seg.sections.appendAssumeCapacity(sections[index]);
-                try text_index_mapping.putNoClobber(index, idx);
-                break :blk idx;
-            } else continue;
-            maybe_index.* = new_index;
-        }
-    }
-
-    {
-        // __DATA_CONST segment
-        const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-        var sections = seg.sections.toOwnedSlice(self.allocator);
-        defer self.allocator.free(sections);
-        try seg.sections.ensureCapacity(self.allocator, sections.len);
-
-        const indices = &[_]*?u16{
-            &self.got_section_index,
-            &self.mod_init_func_section_index,
-            &self.mod_term_func_section_index,
-            &self.data_const_section_index,
-            &self.objc_cfstring_section_index,
-            &self.objc_classlist_section_index,
-            &self.objc_imageinfo_section_index,
-        };
-        for (indices) |maybe_index| {
-            const new_index: u16 = if (maybe_index.*) |index| blk: {
-                const idx = @intCast(u16, seg.sections.items.len);
-                seg.sections.appendAssumeCapacity(sections[index]);
-                try data_const_index_mapping.putNoClobber(index, idx);
-                break :blk idx;
-            } else continue;
-            maybe_index.* = new_index;
-        }
-    }
-
-    {
-        // __DATA segment
-        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        var sections = seg.sections.toOwnedSlice(self.allocator);
-        defer self.allocator.free(sections);
-        try seg.sections.ensureCapacity(self.allocator, sections.len);
-
-        // __DATA segment
-        const indices = &[_]*?u16{
-            &self.la_symbol_ptr_section_index,
-            &self.objc_const_section_index,
-            &self.objc_selrefs_section_index,
-            &self.objc_classrefs_section_index,
-            &self.objc_data_section_index,
-            &self.data_section_index,
-            &self.tlv_section_index,
-            &self.tlv_data_section_index,
-            &self.tlv_bss_section_index,
-            &self.bss_section_index,
-            &self.common_section_index,
-        };
-        for (indices) |maybe_index| {
-            const new_index: u16 = if (maybe_index.*) |index| blk: {
-                const idx = @intCast(u16, seg.sections.items.len);
-                seg.sections.appendAssumeCapacity(sections[index]);
-                try data_index_mapping.putNoClobber(index, idx);
-                break :blk idx;
-            } else continue;
-            maybe_index.* = new_index;
-        }
-    }
-
-    {
-        var transient: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{};
-        try transient.ensureCapacity(self.allocator, self.blocks.count());
-
-        var it = self.blocks.iterator();
-        while (it.next()) |entry| {
-            const old = entry.key_ptr.*;
-            const sect = if (old.seg == self.text_segment_cmd_index.?)
-                text_index_mapping.get(old.sect).?
-            else if (old.seg == self.data_const_segment_cmd_index.?)
-                data_const_index_mapping.get(old.sect).?
-            else
-                data_index_mapping.get(old.sect).?;
-            transient.putAssumeCapacityNoClobber(.{
-                .seg = old.seg,
-                .sect = sect,
-            }, entry.value_ptr.*);
-        }
-
-        self.blocks.clearAndFree(self.allocator);
-        self.blocks.deinit(self.allocator);
-        self.blocks = transient;
-    }
-}
-
-fn allocateTextSegment(self: *Zld) !void {
-    const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const nstubs = @intCast(u32, self.stubs.count());
-
-    const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize;
-    seg.inner.fileoff = 0;
-    seg.inner.vmaddr = base_vmaddr;
-
-    // Set stubs and stub_helper sizes
-    const stubs = &seg.sections.items[self.stubs_section_index.?];
-    const stub_helper = &seg.sections.items[self.stub_helper_section_index.?];
-    stubs.size += nstubs * stubs.reserved2;
-
-    const stub_size: u4 = switch (self.target.?.cpu.arch) {
-        .x86_64 => 10,
-        .aarch64 => 3 * @sizeOf(u32),
-        else => unreachable,
-    };
-    stub_helper.size += nstubs * stub_size;
-
-    var sizeofcmds: u64 = 0;
-    for (self.load_commands.items) |lc| {
-        sizeofcmds += lc.cmdsize();
-    }
-
-    try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds);
-
-    // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments.
-    var min_alignment: u32 = 0;
-    for (seg.sections.items) |sect| {
-        const alignment = try math.powi(u32, 2, sect.@"align");
-        min_alignment = math.max(min_alignment, alignment);
-    }
-
-    assert(min_alignment > 0);
-    const last_sect_idx = seg.sections.items.len - 1;
-    const last_sect = seg.sections.items[last_sect_idx];
-    const shift: u32 = blk: {
-        const diff = seg.inner.filesize - last_sect.offset - last_sect.size;
-        const factor = @divTrunc(diff, min_alignment);
-        break :blk @intCast(u32, factor * min_alignment);
-    };
-
-    if (shift > 0) {
-        for (seg.sections.items) |*sect| {
-            sect.offset += shift;
-            sect.addr += shift;
-        }
-    }
-}
-
-fn allocateDataConstSegment(self: *Zld) !void {
-    const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-    const nentries = @intCast(u32, self.got_entries.count());
-
-    const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    seg.inner.fileoff = text_seg.inner.fileoff + text_seg.inner.filesize;
-    seg.inner.vmaddr = text_seg.inner.vmaddr + text_seg.inner.vmsize;
-
-    // Set got size
-    const got = &seg.sections.items[self.got_section_index.?];
-    got.size += nentries * @sizeOf(u64);
-
-    try self.allocateSegment(self.data_const_segment_cmd_index.?, 0);
-}
-
-fn allocateDataSegment(self: *Zld) !void {
-    const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    const nstubs = @intCast(u32, self.stubs.count());
-
-    const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-    seg.inner.fileoff = data_const_seg.inner.fileoff + data_const_seg.inner.filesize;
-    seg.inner.vmaddr = data_const_seg.inner.vmaddr + data_const_seg.inner.vmsize;
-
-    // Set la_symbol_ptr and data size
-    const la_symbol_ptr = &seg.sections.items[self.la_symbol_ptr_section_index.?];
-    const data = &seg.sections.items[self.data_section_index.?];
-    la_symbol_ptr.size += nstubs * @sizeOf(u64);
-    data.size += @sizeOf(u64); // We need at least 8bytes for address of dyld_stub_binder
-
-    try self.allocateSegment(self.data_segment_cmd_index.?, 0);
-}
-
-fn allocateLinkeditSegment(self: *Zld) void {
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    seg.inner.fileoff = data_seg.inner.fileoff + data_seg.inner.filesize;
-    seg.inner.vmaddr = data_seg.inner.vmaddr + data_seg.inner.vmsize;
-}
-
-fn allocateSegment(self: *Zld, index: u16, offset: u64) !void {
-    const seg = &self.load_commands.items[index].Segment;
-
-    // Allocate the sections according to their alignment at the beginning of the segment.
-    var start: u64 = offset;
-    for (seg.sections.items) |*sect| {
-        const alignment = try math.powi(u32, 2, sect.@"align");
-        const start_aligned = mem.alignForwardGeneric(u64, start, alignment);
-        const end_aligned = mem.alignForwardGeneric(u64, start_aligned + sect.size, alignment);
-        sect.offset = @intCast(u32, seg.inner.fileoff + start_aligned);
-        sect.addr = seg.inner.vmaddr + start_aligned;
-        start = end_aligned;
-    }
-
-    const seg_size_aligned = mem.alignForwardGeneric(u64, start, self.page_size.?);
-    seg.inner.filesize = seg_size_aligned;
-    seg.inner.vmsize = seg_size_aligned;
-}
-
-fn allocateTextBlocks(self: *Zld) !void {
-    var it = self.blocks.iterator();
-    while (it.next()) |entry| {
-        const match = entry.key_ptr.*;
-        var block: *TextBlock = entry.value_ptr.*;
-
-        // Find the first block
-        while (block.prev) |prev| {
-            block = prev;
-        }
-
-        const seg = self.load_commands.items[match.seg].Segment;
-        const sect = seg.sections.items[match.sect];
-
-        var base_addr: u64 = sect.addr;
-        const n_sect = self.sectionId(match);
-
-        log.debug("  within section {s},{s}", .{ segmentName(sect), sectionName(sect) });
-        log.debug("    {}", .{sect});
-
-        while (true) {
-            const block_alignment = try math.powi(u32, 2, block.alignment);
-            base_addr = mem.alignForwardGeneric(u64, base_addr, block_alignment);
-
-            const sym = &self.locals.items[block.local_sym_index];
-            sym.n_value = base_addr;
-            sym.n_sect = n_sect;
-
-            log.debug("    {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{
-                self.getString(sym.n_strx),
-                base_addr,
-                base_addr + block.size,
-                block.size,
-                block.alignment,
-            });
-
-            // Update each alias (if any)
-            for (block.aliases.items) |index| {
-                const alias_sym = &self.locals.items[index];
-                alias_sym.n_value = base_addr;
-                alias_sym.n_sect = n_sect;
-            }
-
-            // Update each symbol contained within the TextBlock
-            if (block.contained) |contained| {
-                for (contained) |sym_at_off| {
-                    const contained_sym = &self.locals.items[sym_at_off.local_sym_index];
-                    contained_sym.n_value = base_addr + sym_at_off.offset;
-                    contained_sym.n_sect = n_sect;
-                }
-            }
-
-            base_addr += block.size;
-
-            if (block.next) |next| {
-                block = next;
-            } else break;
-        }
-    }
-
-    // Update globals
-    for (self.symbol_resolver.values()) |resolv| {
-        if (resolv.where != .global) continue;
-
-        assert(resolv.local_sym_index != 0);
-        const local_sym = self.locals.items[resolv.local_sym_index];
-        const sym = &self.globals.items[resolv.where_index];
-        sym.n_value = local_sym.n_value;
-        sym.n_sect = local_sym.n_sect;
-    }
-}
-
-fn writeTextBlocks(self: *Zld) !void {
-    var it = self.blocks.iterator();
-    while (it.next()) |entry| {
-        const match = entry.key_ptr.*;
-        var block: *TextBlock = entry.value_ptr.*;
-
-        while (block.prev) |prev| {
-            block = prev;
-        }
-
-        const seg = self.load_commands.items[match.seg].Segment;
-        const sect = seg.sections.items[match.sect];
-        const sect_type = sectionType(sect);
-
-        log.debug("  for section {s},{s}", .{ segmentName(sect), sectionName(sect) });
-        log.debug("    {}", .{sect});
-
-        var code = try self.allocator.alloc(u8, sect.size);
-        defer self.allocator.free(code);
-
-        if (sect_type == macho.S_ZEROFILL or sect_type == macho.S_THREAD_LOCAL_ZEROFILL) {
-            mem.set(u8, code, 0);
-        } else {
-            var base_off: u64 = 0;
-
-            while (true) {
-                const block_alignment = try math.powi(u32, 2, block.alignment);
-                const aligned_base_off = mem.alignForwardGeneric(u64, base_off, block_alignment);
-
-                const sym = self.locals.items[block.local_sym_index];
-                log.debug("    {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{
-                    self.getString(sym.n_strx),
-                    aligned_base_off,
-                    aligned_base_off + block.size,
-                    block.size,
-                    block.alignment,
-                });
-
-                try block.resolveRelocs(self);
-                mem.copy(u8, code[aligned_base_off..][0..block.size], block.code);
-
-                // TODO NOP for machine code instead of just zeroing out
-                const padding_len = aligned_base_off - base_off;
-                mem.set(u8, code[base_off..][0..padding_len], 0);
-
-                base_off = aligned_base_off + block.size;
-
-                if (block.next) |next| {
-                    block = next;
-                } else break;
-            }
-
-            mem.set(u8, code[base_off..], 0);
-        }
-
-        try self.file.?.pwriteAll(code, sect.offset);
-    }
-}
-
-fn writeStubHelperCommon(self: *Zld) !void {
-    const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?];
-    const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-    const got = &data_const_segment.sections.items[self.got_section_index.?];
-    const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    const data = &data_segment.sections.items[self.data_section_index.?];
-
-    self.stub_helper_stubs_start_off = blk: {
-        switch (self.target.?.cpu.arch) {
-            .x86_64 => {
-                const code_size = 15;
-                var code: [code_size]u8 = undefined;
-                // lea %r11, [rip + disp]
-                code[0] = 0x4c;
-                code[1] = 0x8d;
-                code[2] = 0x1d;
-                {
-                    const target_addr = data.addr + data.size - @sizeOf(u64);
-                    const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7);
-                    mem.writeIntLittle(u32, code[3..7], displacement);
-                }
-                // push %r11
-                code[7] = 0x41;
-                code[8] = 0x53;
-                // jmp [rip + disp]
-                code[9] = 0xff;
-                code[10] = 0x25;
-                {
-                    const resolv = self.symbol_resolver.get("dyld_stub_binder") orelse unreachable;
-                    const got_index = self.got_entries.getIndex(.{
-                        .where = .import,
-                        .where_index = resolv.where_index,
-                    }) orelse unreachable;
-                    const addr = got.addr + got_index * @sizeOf(u64);
-                    const displacement = try math.cast(u32, addr - stub_helper.addr - code_size);
-                    mem.writeIntLittle(u32, code[11..], displacement);
-                }
-                try self.file.?.pwriteAll(&code, stub_helper.offset);
-                break :blk stub_helper.offset + code_size;
-            },
-            .aarch64 => {
-                var code: [6 * @sizeOf(u32)]u8 = undefined;
-                data_blk_outer: {
-                    const this_addr = stub_helper.addr;
-                    const target_addr = data.addr + data.size - @sizeOf(u64);
-                    data_blk: {
-                        const displacement = math.cast(i21, target_addr - this_addr) catch break :data_blk;
-                        // adr x17, disp
-                        mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32());
-                        // nop
-                        mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32());
-                        break :data_blk_outer;
-                    }
-                    data_blk: {
-                        const new_this_addr = this_addr + @sizeOf(u32);
-                        const displacement = math.cast(i21, target_addr - new_this_addr) catch break :data_blk;
-                        // nop
-                        mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32());
-                        // adr x17, disp
-                        mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32());
-                        break :data_blk_outer;
-                    }
-                    // Jump is too big, replace adr with adrp and add.
-                    const this_page = @intCast(i32, this_addr >> 12);
-                    const target_page = @intCast(i32, target_addr >> 12);
-                    const pages = @intCast(i21, target_page - this_page);
-                    mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32());
-                    const narrowed = @truncate(u12, target_addr);
-                    mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32());
-                }
-                // stp x16, x17, [sp, #-16]!
-                code[8] = 0xf0;
-                code[9] = 0x47;
-                code[10] = 0xbf;
-                code[11] = 0xa9;
-                binder_blk_outer: {
-                    const resolv = self.symbol_resolver.get("dyld_stub_binder") orelse unreachable;
-                    const got_index = self.got_entries.getIndex(.{
-                        .where = .import,
-                        .where_index = resolv.where_index,
-                    }) orelse unreachable;
-                    const this_addr = stub_helper.addr + 3 * @sizeOf(u32);
-                    const target_addr = got.addr + got_index * @sizeOf(u64);
-                    binder_blk: {
-                        const displacement = math.divExact(u64, target_addr - this_addr, 4) catch break :binder_blk;
-                        const literal = math.cast(u18, displacement) catch break :binder_blk;
-                        // ldr x16, label
-                        mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{
-                            .literal = literal,
-                        }).toU32());
-                        // nop
-                        mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32());
-                        break :binder_blk_outer;
-                    }
-                    binder_blk: {
-                        const new_this_addr = this_addr + @sizeOf(u32);
-                        const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch break :binder_blk;
-                        const literal = math.cast(u18, displacement) catch break :binder_blk;
-                        // Pad with nop to please division.
-                        // nop
-                        mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32());
-                        // ldr x16, label
-                        mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{
-                            .literal = literal,
-                        }).toU32());
-                        break :binder_blk_outer;
-                    }
-                    // Use adrp followed by ldr(immediate).
-                    const this_page = @intCast(i32, this_addr >> 12);
-                    const target_page = @intCast(i32, target_addr >> 12);
-                    const pages = @intCast(i21, target_page - this_page);
-                    mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32());
-                    const narrowed = @truncate(u12, target_addr);
-                    const offset = try math.divExact(u12, narrowed, 8);
-                    mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{
-                        .register = .{
-                            .rn = .x16,
-                            .offset = aarch64.Instruction.LoadStoreOffset.imm(offset),
-                        },
-                    }).toU32());
-                }
-                // br x16
-                code[20] = 0x00;
-                code[21] = 0x02;
-                code[22] = 0x1f;
-                code[23] = 0xd6;
-                try self.file.?.pwriteAll(&code, stub_helper.offset);
-                break :blk stub_helper.offset + 6 * @sizeOf(u32);
-            },
-            else => unreachable,
-        }
-    };
-
-    for (self.stubs.keys()) |_, i| {
-        const index = @intCast(u32, i);
-        // TODO weak bound pointers
-        try self.writeLazySymbolPointer(index);
-        try self.writeStub(index);
-        try self.writeStubInStubHelper(index);
-    }
-}
-
-fn writeLazySymbolPointer(self: *Zld, index: u32) !void {
-    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?];
-    const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?];
-
-    const stub_size: u4 = switch (self.target.?.cpu.arch) {
-        .x86_64 => 10,
-        .aarch64 => 3 * @sizeOf(u32),
-        else => unreachable,
-    };
-    const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size;
-    const end = stub_helper.addr + stub_off - stub_helper.offset;
-    var buf: [@sizeOf(u64)]u8 = undefined;
-    mem.writeIntLittle(u64, &buf, end);
-    const off = la_symbol_ptr.offset + index * @sizeOf(u64);
-    log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off });
-    try self.file.?.pwriteAll(&buf, off);
-}
-
-fn writeStub(self: *Zld, index: u32) !void {
-    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const stubs = text_segment.sections.items[self.stubs_section_index.?];
-    const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?];
-
-    const stub_off = stubs.offset + index * stubs.reserved2;
-    const stub_addr = stubs.addr + index * stubs.reserved2;
-    const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64);
-    log.debug("writing stub at 0x{x}", .{stub_off});
-    var code = try self.allocator.alloc(u8, stubs.reserved2);
-    defer self.allocator.free(code);
-    switch (self.target.?.cpu.arch) {
-        .x86_64 => {
-            assert(la_ptr_addr >= stub_addr + stubs.reserved2);
-            const displacement = try math.cast(u32, la_ptr_addr - stub_addr - stubs.reserved2);
-            // jmp
-            code[0] = 0xff;
-            code[1] = 0x25;
-            mem.writeIntLittle(u32, code[2..][0..4], displacement);
-        },
-        .aarch64 => {
-            assert(la_ptr_addr >= stub_addr);
-            outer: {
-                const this_addr = stub_addr;
-                const target_addr = la_ptr_addr;
-                inner: {
-                    const displacement = math.divExact(u64, target_addr - this_addr, 4) catch break :inner;
-                    const literal = math.cast(u18, displacement) catch break :inner;
-                    // ldr x16, literal
-                    mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{
-                        .literal = literal,
-                    }).toU32());
-                    // nop
-                    mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32());
-                    break :outer;
-                }
-                inner: {
-                    const new_this_addr = this_addr + @sizeOf(u32);
-                    const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch break :inner;
-                    const literal = math.cast(u18, displacement) catch break :inner;
-                    // nop
-                    mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32());
-                    // ldr x16, literal
-                    mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{
-                        .literal = literal,
-                    }).toU32());
-                    break :outer;
-                }
-                // Use adrp followed by ldr(immediate).
-                const this_page = @intCast(i32, this_addr >> 12);
-                const target_page = @intCast(i32, target_addr >> 12);
-                const pages = @intCast(i21, target_page - this_page);
-                mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x16, pages).toU32());
-                const narrowed = @truncate(u12, target_addr);
-                const offset = try math.divExact(u12, narrowed, 8);
-                mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{
-                    .register = .{
-                        .rn = .x16,
-                        .offset = aarch64.Instruction.LoadStoreOffset.imm(offset),
-                    },
-                }).toU32());
-            }
-            // br x16
-            mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.br(.x16).toU32());
-        },
-        else => unreachable,
-    }
-    try self.file.?.pwriteAll(code, stub_off);
-}
-
-fn writeStubInStubHelper(self: *Zld, index: u32) !void {
-    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?];
-
-    const stub_size: u4 = switch (self.target.?.cpu.arch) {
-        .x86_64 => 10,
-        .aarch64 => 3 * @sizeOf(u32),
-        else => unreachable,
-    };
-    const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size;
-    var code = try self.allocator.alloc(u8, stub_size);
-    defer self.allocator.free(code);
-    switch (self.target.?.cpu.arch) {
-        .x86_64 => {
-            const displacement = try math.cast(
-                i32,
-                @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - stub_size,
-            );
-            // pushq
-            code[0] = 0x68;
-            mem.writeIntLittle(u32, code[1..][0..4], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`.
-            // jmpq
-            code[5] = 0xe9;
-            mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement));
-        },
-        .aarch64 => {
-            const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4);
-            const literal = @divExact(stub_size - @sizeOf(u32), 4);
-            // ldr w16, literal
-            mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{
-                .literal = literal,
-            }).toU32());
-            // b disp
-            mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(displacement).toU32());
-            mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`.
-        },
-        else => unreachable,
-    }
-    try self.file.?.pwriteAll(code, stub_off);
-}
-
-fn resolveSymbolsInObject(self: *Zld, object_id: u16) !void {
-    const object = self.objects.items[object_id];
-
-    log.debug("resolving symbols in '{s}'", .{object.name});
-
-    for (object.symtab.items) |sym, id| {
-        const sym_id = @intCast(u32, id);
-        const sym_name = object.getString(sym.n_strx);
-
-        if (symbolIsStab(sym)) {
-            log.err("unhandled symbol type: stab", .{});
-            log.err("  symbol '{s}'", .{sym_name});
-            log.err("  first definition in '{s}'", .{object.name.?});
-            return error.UnhandledSymbolType;
-        }
-
-        if (symbolIsIndr(sym)) {
-            log.err("unhandled symbol type: indirect", .{});
-            log.err("  symbol '{s}'", .{sym_name});
-            log.err("  first definition in '{s}'", .{object.name.?});
-            return error.UnhandledSymbolType;
-        }
-
-        if (symbolIsAbs(sym)) {
-            log.err("unhandled symbol type: absolute", .{});
-            log.err("  symbol '{s}'", .{sym_name});
-            log.err("  first definition in '{s}'", .{object.name.?});
-            return error.UnhandledSymbolType;
-        }
-
-        if (symbolIsSect(sym)) {
-            // Defined symbol regardless of scope lands in the locals symbol table.
-            const n_strx = blk: {
-                if (self.symbol_resolver.get(sym_name)) |resolv| {
-                    switch (resolv.where) {
-                        .global => break :blk self.globals.items[resolv.where_index].n_strx,
-                        .tentative => break :blk self.tentatives.items[resolv.where_index].n_strx,
-                        .undef => break :blk self.undefs.items[resolv.where_index].n_strx,
-                        .import => unreachable,
-                    }
-                }
-                break :blk try self.makeString(sym_name);
-            };
-            const local_sym_index = @intCast(u32, self.locals.items.len);
-            try self.locals.append(self.allocator, .{
-                .n_strx = n_strx,
-                .n_type = macho.N_SECT,
-                .n_sect = 0,
-                .n_desc = 0,
-                .n_value = sym.n_value,
-            });
-            try object.symbol_mapping.putNoClobber(self.allocator, sym_id, local_sym_index);
-
-            // If the symbol's scope is not local aka translation unit, then we need work out
-            // if we should save the symbol as a global, or potentially flag the error.
-            if (!symbolIsExt(sym)) continue;
-
-            const local = self.locals.items[local_sym_index];
-            const resolv = self.symbol_resolver.getPtr(sym_name) orelse {
-                const global_sym_index = @intCast(u32, self.globals.items.len);
-                try self.globals.append(self.allocator, .{
-                    .n_strx = n_strx,
-                    .n_type = sym.n_type,
-                    .n_sect = 0,
-                    .n_desc = sym.n_desc,
-                    .n_value = sym.n_value,
-                });
-                try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), .{
-                    .where = .global,
-                    .where_index = global_sym_index,
-                    .local_sym_index = local_sym_index,
-                    .file = object_id,
-                });
-                continue;
-            };
-
-            switch (resolv.where) {
-                .import => unreachable,
-                .global => {
-                    const global = &self.globals.items[resolv.where_index];
-
-                    if (!(symbolIsWeakDef(sym) or symbolIsPext(sym)) and
-                        !(symbolIsWeakDef(global.*) or symbolIsPext(global.*)))
-                    {
-                        log.err("symbol '{s}' defined multiple times", .{sym_name});
-                        log.err("  first definition in '{s}'", .{self.objects.items[resolv.file].name.?});
-                        log.err("  next definition in '{s}'", .{object.name.?});
-                        return error.MultipleSymbolDefinitions;
-                    }
-
-                    if (symbolIsWeakDef(sym) or symbolIsPext(sym)) continue; // Current symbol is weak, so skip it.
-
-                    // Otherwise, update the resolver and the global symbol.
-                    global.n_type = sym.n_type;
-                    resolv.local_sym_index = local_sym_index;
-                    resolv.file = object_id;
-
-                    continue;
-                },
-                .undef => {
-                    const undef = &self.undefs.items[resolv.where_index];
-                    undef.* = .{
-                        .n_strx = 0,
-                        .n_type = macho.N_UNDF,
-                        .n_sect = 0,
-                        .n_desc = 0,
-                        .n_value = 0,
-                    };
-                },
-                .tentative => {
-                    const tentative = &self.tentatives.items[resolv.where_index];
-                    tentative.* = .{
-                        .n_strx = 0,
-                        .n_type = macho.N_UNDF,
-                        .n_sect = 0,
-                        .n_desc = 0,
-                        .n_value = 0,
-                    };
-                },
-            }
-
-            const global_sym_index = @intCast(u32, self.globals.items.len);
-            try self.globals.append(self.allocator, .{
-                .n_strx = local.n_strx,
-                .n_type = sym.n_type,
-                .n_sect = 0,
-                .n_desc = sym.n_desc,
-                .n_value = sym.n_value,
-            });
-            resolv.* = .{
-                .where = .global,
-                .where_index = global_sym_index,
-                .local_sym_index = local_sym_index,
-                .file = object_id,
-            };
-        } else if (symbolIsTentative(sym)) {
-            // Symbol is a tentative definition.
-            const resolv = self.symbol_resolver.getPtr(sym_name) orelse {
-                const tent_sym_index = @intCast(u32, self.tentatives.items.len);
-                try self.tentatives.append(self.allocator, .{
-                    .n_strx = try self.makeString(sym_name),
-                    .n_type = sym.n_type,
-                    .n_sect = 0,
-                    .n_desc = sym.n_desc,
-                    .n_value = sym.n_value,
-                });
-                try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), .{
-                    .where = .tentative,
-                    .where_index = tent_sym_index,
-                    .file = object_id,
-                });
-                continue;
-            };
-
-            switch (resolv.where) {
-                .import => unreachable,
-                .global => {},
-                .undef => {
-                    const undef = &self.undefs.items[resolv.where_index];
-                    const tent_sym_index = @intCast(u32, self.tentatives.items.len);
-                    try self.tentatives.append(self.allocator, .{
-                        .n_strx = undef.n_strx,
-                        .n_type = sym.n_type,
-                        .n_sect = 0,
-                        .n_desc = sym.n_desc,
-                        .n_value = sym.n_value,
-                    });
-                    resolv.* = .{
-                        .where = .tentative,
-                        .where_index = tent_sym_index,
-                        .file = object_id,
-                    };
-                    undef.* = .{
-                        .n_strx = 0,
-                        .n_type = macho.N_UNDF,
-                        .n_sect = 0,
-                        .n_desc = 0,
-                        .n_value = 0,
-                    };
-                },
-                .tentative => {
-                    const tentative = &self.tentatives.items[resolv.where_index];
-                    if (tentative.n_value >= sym.n_value) continue;
-
-                    tentative.n_desc = sym.n_desc;
-                    tentative.n_value = sym.n_value;
-                    resolv.file = object_id;
-                },
-            }
-        } else {
-            // Symbol is undefined.
-            if (self.symbol_resolver.contains(sym_name)) continue;
-
-            const undef_sym_index = @intCast(u32, self.undefs.items.len);
-            try self.undefs.append(self.allocator, .{
-                .n_strx = try self.makeString(sym_name),
-                .n_type = macho.N_UNDF,
-                .n_sect = 0,
-                .n_desc = 0,
-                .n_value = 0,
-            });
-            try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), .{
-                .where = .undef,
-                .where_index = undef_sym_index,
-                .file = object_id,
-            });
-        }
-    }
-}
-
-fn resolveSymbols(self: *Zld) !void {
-    // TODO mimicking insertion of null symbol from incremental linker.
-    // This will need to moved.
-    try self.locals.append(self.allocator, .{
-        .n_strx = 0,
-        .n_type = macho.N_UNDF,
-        .n_sect = 0,
-        .n_desc = 0,
-        .n_value = 0,
-    });
-    try self.strtab.append(self.allocator, 0);
-
-    // First pass, resolve symbols in provided objects.
-    for (self.objects.items) |_, object_id| {
-        try self.resolveSymbolsInObject(@intCast(u16, object_id));
-    }
-
-    // Second pass, resolve symbols in static libraries.
-    var next_sym: usize = 0;
-    loop: while (true) : (next_sym += 1) {
-        if (next_sym == self.undefs.items.len) break;
-
-        const sym = self.undefs.items[next_sym];
-        if (symbolIsNull(sym)) continue;
-
-        const sym_name = self.getString(sym.n_strx);
-
-        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 = try archive.parseObject(offsets.items[0]);
-            const object_id = @intCast(u16, self.objects.items.len);
-            try self.objects.append(self.allocator, object);
-            try self.resolveSymbolsInObject(object_id);
-
-            continue :loop;
-        }
-    }
-
-    // Convert any tentative definition into a regular symbol and allocate
-    // text blocks for each tentative defintion.
-    for (self.tentatives.items) |sym| {
-        if (symbolIsNull(sym)) continue;
-
-        const sym_name = self.getString(sym.n_strx);
-        const match: MatchingSection = blk: {
-            if (self.common_section_index == null) {
-                const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-                self.common_section_index = @intCast(u16, data_seg.sections.items.len);
-                try data_seg.addSection(self.allocator, "__common", .{
-                    .flags = macho.S_ZEROFILL,
-                });
-            }
-            break :blk .{
-                .seg = self.data_segment_cmd_index.?,
-                .sect = self.common_section_index.?,
-            };
-        };
-
-        const size = sym.n_value;
-        const code = try self.allocator.alloc(u8, size);
-        mem.set(u8, code, 0);
-        const alignment = (sym.n_desc >> 8) & 0x0f;
-
-        const resolv = self.symbol_resolver.getPtr(sym_name) orelse unreachable;
-        const local_sym_index = @intCast(u32, self.locals.items.len);
-        var nlist = macho.nlist_64{
-            .n_strx = sym.n_strx,
-            .n_type = macho.N_SECT,
-            .n_sect = self.sectionId(match),
-            .n_desc = 0,
-            .n_value = 0,
-        };
-        try self.locals.append(self.allocator, nlist);
-        const global_sym_index = @intCast(u32, self.globals.items.len);
-        nlist.n_type |= macho.N_EXT;
-        try self.globals.append(self.allocator, nlist);
-        resolv.* = .{
-            .where = .global,
-            .where_index = global_sym_index,
-            .local_sym_index = local_sym_index,
-        };
-
-        const block = try self.allocator.create(TextBlock);
-        errdefer self.allocator.destroy(block);
-
-        block.* = TextBlock.init(self.allocator);
-        block.local_sym_index = local_sym_index;
-        block.code = code;
-        block.size = size;
-        block.alignment = alignment;
-
-        // Update target section's metadata
-        // TODO should we update segment's size here too?
-        // How does it tie with incremental space allocs?
-        const tseg = &self.load_commands.items[match.seg].Segment;
-        const tsect = &tseg.sections.items[match.sect];
-        const new_alignment = math.max(tsect.@"align", block.alignment);
-        const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
-        const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
-        tsect.size = new_size;
-        tsect.@"align" = new_alignment;
-
-        if (self.blocks.getPtr(match)) |last| {
-            last.*.next = block;
-            block.prev = last.*;
-            last.* = block;
-        } else {
-            try self.blocks.putNoClobber(self.allocator, match, block);
-        }
-    }
-
-    // Third pass, resolve symbols in dynamic libraries.
-    {
-        // Put dyld_stub_binder as an undefined special symbol.
-        const undef_sym_index = @intCast(u32, self.undefs.items.len);
-        try self.undefs.append(self.allocator, .{
-            .n_strx = try self.makeString("dyld_stub_binder"),
-            .n_type = macho.N_UNDF,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        });
-        try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, "dyld_stub_binder"), .{
-            .where = .undef,
-            .where_index = undef_sym_index,
-        });
-    }
-
-    var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator);
-    defer referenced.deinit();
-
-    loop: for (self.undefs.items) |sym| {
-        if (symbolIsNull(sym)) continue;
-
-        const sym_name = self.getString(sym.n_strx);
-        for (self.dylibs.items) |dylib| {
-            if (!dylib.symbols.contains(sym_name)) continue;
-
-            if (!referenced.contains(dylib)) {
-                // Add LC_LOAD_DYLIB load command for each referenced dylib/stub.
-                dylib.ordinal = self.next_dylib_ordinal;
-                const dylib_id = dylib.id orelse unreachable;
-                var dylib_cmd = try createLoadDylibCommand(
-                    self.allocator,
-                    dylib_id.name,
-                    dylib_id.timestamp,
-                    dylib_id.current_version,
-                    dylib_id.compatibility_version,
-                );
-                errdefer dylib_cmd.deinit(self.allocator);
-                try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
-                self.next_dylib_ordinal += 1;
-                try referenced.putNoClobber(dylib, {});
-            }
-
-            const resolv = self.symbol_resolver.getPtr(sym_name) orelse unreachable;
-            const undef = &self.undefs.items[resolv.where_index];
-            const import_sym_index = @intCast(u32, self.imports.items.len);
-            try self.imports.append(self.allocator, .{
-                .n_strx = undef.n_strx,
-                .n_type = macho.N_UNDF | macho.N_EXT,
-                .n_sect = 0,
-                .n_desc = packDylibOrdinal(dylib.ordinal.?),
-                .n_value = 0,
-            });
-            resolv.* = .{
-                .where = .import,
-                .where_index = import_sym_index,
-            };
-            undef.* = .{
-                .n_strx = 0,
-                .n_type = macho.N_UNDF,
-                .n_sect = 0,
-                .n_desc = 0,
-                .n_value = 0,
-            };
-
-            continue :loop;
-        }
-    }
-
-    // Fourth pass, handle synthetic symbols and flag any undefined references.
-    if (self.symbol_resolver.getPtr("___dso_handle")) |resolv| blk: {
-        if (resolv.where != .undef) break :blk;
-
-        const undef = &self.undefs.items[resolv.where_index];
-        const match: MatchingSection = .{
-            .seg = self.text_segment_cmd_index.?,
-            .sect = self.text_section_index.?,
-        };
-        const local_sym_index = @intCast(u32, self.locals.items.len);
-        var nlist = macho.nlist_64{
-            .n_strx = undef.n_strx,
-            .n_type = macho.N_SECT,
-            .n_sect = self.sectionId(match),
-            .n_desc = 0,
-            .n_value = 0,
-        };
-        try self.locals.append(self.allocator, nlist);
-        const global_sym_index = @intCast(u32, self.globals.items.len);
-        nlist.n_type |= macho.N_EXT;
-        nlist.n_desc = macho.N_WEAK_DEF;
-        try self.globals.append(self.allocator, nlist);
-
-        undef.* = .{
-            .n_strx = 0,
-            .n_type = macho.N_UNDF,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        };
-        resolv.* = .{
-            .where = .global,
-            .where_index = global_sym_index,
-            .local_sym_index = local_sym_index,
-        };
-
-        // We create an empty atom for this symbol.
-        // TODO perhaps we should special-case special symbols? Create a separate
-        // linked list of atoms?
-        const block = try self.allocator.create(TextBlock);
-        errdefer self.allocator.destroy(block);
-
-        block.* = TextBlock.init(self.allocator);
-        block.local_sym_index = local_sym_index;
-        block.code = try self.allocator.alloc(u8, 0);
-        block.size = 0;
-        block.alignment = 0;
-
-        if (self.blocks.getPtr(match)) |last| {
-            last.*.next = block;
-            block.prev = last.*;
-            last.* = block;
-        } else {
-            try self.blocks.putNoClobber(self.allocator, match, block);
-        }
-    }
-
-    var has_undefined = false;
-    for (self.undefs.items) |sym| {
-        if (symbolIsNull(sym)) continue;
-
-        const sym_name = self.getString(sym.n_strx);
-        const resolv = self.symbol_resolver.get(sym_name) orelse unreachable;
-
-        log.err("undefined reference to symbol '{s}'", .{sym_name});
-        log.err("  first referenced in '{s}'", .{self.objects.items[resolv.file].name.?});
-        has_undefined = true;
-    }
-
-    if (has_undefined) return error.UndefinedSymbolReference;
-}
-
-fn parseTextBlocks(self: *Zld) !void {
-    for (self.objects.items) |object| {
-        try object.parseTextBlocks(self);
-    }
-}
-
-fn populateMetadata(self: *Zld) !void {
-    if (self.pagezero_segment_cmd_index == null) {
-        self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Segment = SegmentCommand.empty("__PAGEZERO", .{
-                .vmsize = 0x100000000, // size always set to 4GB
-            }),
-        });
-    }
-
-    if (self.text_segment_cmd_index == null) {
-        self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Segment = SegmentCommand.empty("__TEXT", .{
-                .vmaddr = 0x100000000, // always starts at 4GB
-                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
-                .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
-            }),
-        });
-    }
-
-    if (self.text_section_index == null) {
-        const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-        self.text_section_index = @intCast(u16, text_seg.sections.items.len);
-        const alignment: u2 = switch (self.target.?.cpu.arch) {
-            .x86_64 => 0,
-            .aarch64 => 2,
-            else => unreachable, // unhandled architecture type
-        };
-        try text_seg.addSection(self.allocator, "__text", .{
-            .@"align" = alignment,
-            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-        });
-    }
-
-    if (self.stubs_section_index == null) {
-        const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-        self.stubs_section_index = @intCast(u16, text_seg.sections.items.len);
-        const alignment: u2 = switch (self.target.?.cpu.arch) {
-            .x86_64 => 0,
-            .aarch64 => 2,
-            else => unreachable, // unhandled architecture type
-        };
-        const stub_size: u4 = switch (self.target.?.cpu.arch) {
-            .x86_64 => 6,
-            .aarch64 => 3 * @sizeOf(u32),
-            else => unreachable, // unhandled architecture type
-        };
-        try text_seg.addSection(self.allocator, "__stubs", .{
-            .@"align" = alignment,
-            .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-            .reserved2 = stub_size,
-        });
-    }
-
-    if (self.stub_helper_section_index == null) {
-        const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-        self.stub_helper_section_index = @intCast(u16, text_seg.sections.items.len);
-        const alignment: u2 = switch (self.target.?.cpu.arch) {
-            .x86_64 => 0,
-            .aarch64 => 2,
-            else => unreachable, // unhandled architecture type
-        };
-        const stub_helper_size: u6 = switch (self.target.?.cpu.arch) {
-            .x86_64 => 15,
-            .aarch64 => 6 * @sizeOf(u32),
-            else => unreachable,
-        };
-        try text_seg.addSection(self.allocator, "__stub_helper", .{
-            .size = stub_helper_size,
-            .@"align" = alignment,
-            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
-        });
-    }
-
-    if (self.data_const_segment_cmd_index == null) {
-        self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Segment = SegmentCommand.empty("__DATA_CONST", .{
-                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-                .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-            }),
-        });
-    }
-
-    if (self.got_section_index == null) {
-        const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-        self.got_section_index = @intCast(u16, data_const_seg.sections.items.len);
-        try data_const_seg.addSection(self.allocator, "__got", .{
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
-        });
-    }
-
-    if (self.data_segment_cmd_index == null) {
-        self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Segment = SegmentCommand.empty("__DATA", .{
-                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-                .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-            }),
-        });
-    }
-
-    if (self.la_symbol_ptr_section_index == null) {
-        const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len);
-        try data_seg.addSection(self.allocator, "__la_symbol_ptr", .{
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-            .flags = macho.S_LAZY_SYMBOL_POINTERS,
-        });
-    }
-
-    if (self.data_section_index == null) {
-        const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        self.data_section_index = @intCast(u16, data_seg.sections.items.len);
-        try data_seg.addSection(self.allocator, "__data", .{
-            .@"align" = 3, // 2^3 = @sizeOf(u64)
-        });
-    }
-
-    if (self.linkedit_segment_cmd_index == null) {
-        self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Segment = SegmentCommand.empty("__LINKEDIT", .{
-                .maxprot = macho.VM_PROT_READ,
-                .initprot = macho.VM_PROT_READ,
-            }),
-        });
-    }
-
-    if (self.dyld_info_cmd_index == null) {
-        self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .DyldInfoOnly = .{
-                .cmd = macho.LC_DYLD_INFO_ONLY,
-                .cmdsize = @sizeOf(macho.dyld_info_command),
-                .rebase_off = 0,
-                .rebase_size = 0,
-                .bind_off = 0,
-                .bind_size = 0,
-                .weak_bind_off = 0,
-                .weak_bind_size = 0,
-                .lazy_bind_off = 0,
-                .lazy_bind_size = 0,
-                .export_off = 0,
-                .export_size = 0,
-            },
-        });
-    }
-
-    if (self.symtab_cmd_index == null) {
-        self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Symtab = .{
-                .cmd = macho.LC_SYMTAB,
-                .cmdsize = @sizeOf(macho.symtab_command),
-                .symoff = 0,
-                .nsyms = 0,
-                .stroff = 0,
-                .strsize = 0,
-            },
-        });
-    }
-
-    if (self.dysymtab_cmd_index == null) {
-        self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Dysymtab = .{
-                .cmd = macho.LC_DYSYMTAB,
-                .cmdsize = @sizeOf(macho.dysymtab_command),
-                .ilocalsym = 0,
-                .nlocalsym = 0,
-                .iextdefsym = 0,
-                .nextdefsym = 0,
-                .iundefsym = 0,
-                .nundefsym = 0,
-                .tocoff = 0,
-                .ntoc = 0,
-                .modtaboff = 0,
-                .nmodtab = 0,
-                .extrefsymoff = 0,
-                .nextrefsyms = 0,
-                .indirectsymoff = 0,
-                .nindirectsyms = 0,
-                .extreloff = 0,
-                .nextrel = 0,
-                .locreloff = 0,
-                .nlocrel = 0,
-            },
-        });
-    }
-
-    if (self.dylinker_cmd_index == null) {
-        self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len);
-        const cmdsize = @intCast(u32, mem.alignForwardGeneric(
-            u64,
-            @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH),
-            @sizeOf(u64),
-        ));
-        var dylinker_cmd = emptyGenericCommandWithData(macho.dylinker_command{
-            .cmd = macho.LC_LOAD_DYLINKER,
-            .cmdsize = cmdsize,
-            .name = @sizeOf(macho.dylinker_command),
-        });
-        dylinker_cmd.data = try self.allocator.alloc(u8, cmdsize - dylinker_cmd.inner.name);
-        mem.set(u8, dylinker_cmd.data, 0);
-        mem.copy(u8, dylinker_cmd.data, mem.spanZ(DEFAULT_DYLD_PATH));
-        try self.load_commands.append(self.allocator, .{ .Dylinker = dylinker_cmd });
-    }
-
-    if (self.main_cmd_index == null and self.output.?.tag == .exe) {
-        self.main_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .Main = .{
-                .cmd = macho.LC_MAIN,
-                .cmdsize = @sizeOf(macho.entry_point_command),
-                .entryoff = 0x0,
-                .stacksize = 0,
-            },
-        });
-    }
-
-    if (self.dylib_id_cmd_index == null and self.output.?.tag == .dylib) {
-        self.dylib_id_cmd_index = @intCast(u16, self.load_commands.items.len);
-        var dylib_cmd = try createLoadDylibCommand(
-            self.allocator,
-            self.output.?.install_name.?,
-            2,
-            0x10000, // TODO forward user-provided versions
-            0x10000,
-        );
-        errdefer dylib_cmd.deinit(self.allocator);
-        dylib_cmd.inner.cmd = macho.LC_ID_DYLIB;
-        try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
-    }
-
-    if (self.version_min_cmd_index == null) {
-        self.version_min_cmd_index = @intCast(u16, self.load_commands.items.len);
-        const cmd: u32 = switch (self.target.?.os.tag) {
-            .macos => macho.LC_VERSION_MIN_MACOSX,
-            .ios => macho.LC_VERSION_MIN_IPHONEOS,
-            .tvos => macho.LC_VERSION_MIN_TVOS,
-            .watchos => macho.LC_VERSION_MIN_WATCHOS,
-            else => unreachable, // wrong OS
-        };
-        const ver = self.target.?.os.version_range.semver.min;
-        const version = ver.major << 16 | ver.minor << 8 | ver.patch;
-        try self.load_commands.append(self.allocator, .{
-            .VersionMin = .{
-                .cmd = cmd,
-                .cmdsize = @sizeOf(macho.version_min_command),
-                .version = version,
-                .sdk = version,
-            },
-        });
-    }
-
-    if (self.source_version_cmd_index == null) {
-        self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .SourceVersion = .{
-                .cmd = macho.LC_SOURCE_VERSION,
-                .cmdsize = @sizeOf(macho.source_version_command),
-                .version = 0x0,
-            },
-        });
-    }
-
-    if (self.uuid_cmd_index == null) {
-        self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len);
-        var uuid_cmd: macho.uuid_command = .{
-            .cmd = macho.LC_UUID,
-            .cmdsize = @sizeOf(macho.uuid_command),
-            .uuid = undefined,
-        };
-        std.crypto.random.bytes(&uuid_cmd.uuid);
-        try self.load_commands.append(self.allocator, .{ .Uuid = uuid_cmd });
-    }
-}
-
-fn addDataInCodeLC(self: *Zld) !void {
-    if (self.data_in_code_cmd_index == null) {
-        self.data_in_code_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .LinkeditData = .{
-                .cmd = macho.LC_DATA_IN_CODE,
-                .cmdsize = @sizeOf(macho.linkedit_data_command),
-                .dataoff = 0,
-                .datasize = 0,
-            },
-        });
-    }
-}
-
-fn addCodeSignatureLC(self: *Zld) !void {
-    if (self.code_signature_cmd_index == null and self.target.?.cpu.arch == .aarch64) {
-        self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
-        try self.load_commands.append(self.allocator, .{
-            .LinkeditData = .{
-                .cmd = macho.LC_CODE_SIGNATURE,
-                .cmdsize = @sizeOf(macho.linkedit_data_command),
-                .dataoff = 0,
-                .datasize = 0,
-            },
-        });
-    }
-}
-
-fn addRpaths(self: *Zld, rpaths: []const []const u8) !void {
-    for (rpaths) |rpath| {
-        const cmdsize = @intCast(u32, mem.alignForwardGeneric(
-            u64,
-            @sizeOf(macho.rpath_command) + rpath.len + 1,
-            @sizeOf(u64),
-        ));
-        var rpath_cmd = emptyGenericCommandWithData(macho.rpath_command{
-            .cmd = macho.LC_RPATH,
-            .cmdsize = cmdsize,
-            .path = @sizeOf(macho.rpath_command),
-        });
-        rpath_cmd.data = try self.allocator.alloc(u8, cmdsize - rpath_cmd.inner.path);
-        mem.set(u8, rpath_cmd.data, 0);
-        mem.copy(u8, rpath_cmd.data, rpath);
-        try self.load_commands.append(self.allocator, .{ .Rpath = rpath_cmd });
-    }
-}
-
-fn flush(self: *Zld) !void {
-    try self.writeTextBlocks();
-    try self.writeStubHelperCommon();
-
-    if (self.common_section_index) |index| {
-        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        const sect = &seg.sections.items[index];
-        sect.offset = 0;
-    }
-
-    if (self.bss_section_index) |index| {
-        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        const sect = &seg.sections.items[index];
-        sect.offset = 0;
-    }
-
-    if (self.tlv_bss_section_index) |index| {
-        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        const sect = &seg.sections.items[index];
-        sect.offset = 0;
-    }
-
-    try self.writeGotEntries();
-    try self.setEntryPoint();
-    try self.writeRebaseInfoTable();
-    try self.writeBindInfoTable();
-    try self.writeLazyBindInfoTable();
-    try self.writeExportInfo();
-    try self.writeDices();
-
-    {
-        const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-        const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
-        symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
-    }
-
-    try self.writeSymbolTable();
-    try self.writeStringTable();
-
-    {
-        // Seal __LINKEDIT size
-        const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-        seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?);
-    }
-
-    if (self.target.?.cpu.arch == .aarch64) {
-        try self.writeCodeSignaturePadding();
-    }
-
-    try self.writeLoadCommands();
-    try self.writeHeader();
-
-    if (self.target.?.cpu.arch == .aarch64) {
-        try self.writeCodeSignature();
-    }
-
-    if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) {
-        const out_path = self.output.?.path;
-        try fs.cwd().copyFile(out_path, fs.cwd(), out_path, .{});
-    }
-}
-
-fn writeGotEntries(self: *Zld) !void {
-    const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-    const sect = seg.sections.items[self.got_section_index.?];
-
-    var buffer = try self.allocator.alloc(u8, self.got_entries.count() * @sizeOf(u64));
-    defer self.allocator.free(buffer);
-
-    var stream = std.io.fixedBufferStream(buffer);
-    var writer = stream.writer();
-
-    for (self.got_entries.keys()) |key| {
-        const address: u64 = switch (key.where) {
-            .local => self.locals.items[key.where_index].n_value,
-            .import => 0,
-        };
-        try writer.writeIntLittle(u64, address);
-    }
-
-    log.debug("writing GOT pointers at 0x{x} to 0x{x}", .{ sect.offset, sect.offset + buffer.len });
-
-    try self.file.?.pwriteAll(buffer, sect.offset);
-}
-
-fn setEntryPoint(self: *Zld) !void {
-    if (self.output.?.tag != .exe) return;
-
-    // TODO we should respect the -entry flag passed in by the user to set a custom
-    // entrypoint. For now, assume default of `_main`.
-    const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const resolv = self.symbol_resolver.get("_main") orelse {
-        log.err("'_main' export not found", .{});
-        return error.MissingMainEntrypoint;
-    };
-    assert(resolv.where == .global);
-    const sym = self.globals.items[resolv.where_index];
-    const ec = &self.load_commands.items[self.main_cmd_index.?].Main;
-    ec.entryoff = @intCast(u32, sym.n_value - seg.inner.vmaddr);
-    ec.stacksize = self.stack_size;
-}
-
-fn writeRebaseInfoTable(self: *Zld) !void {
-    var pointers = std.ArrayList(Pointer).init(self.allocator);
-    defer pointers.deinit();
-
-    {
-        var it = self.blocks.iterator();
-        while (it.next()) |entry| {
-            const match = entry.key_ptr.*;
-            var block: *TextBlock = entry.value_ptr.*;
-
-            if (match.seg == self.text_segment_cmd_index.?) continue; // __TEXT is non-writable
-
-            const seg = self.load_commands.items[match.seg].Segment;
-
-            while (true) {
-                const sym = self.locals.items[block.local_sym_index];
-                const base_offset = sym.n_value - seg.inner.vmaddr;
-
-                for (block.rebases.items) |offset| {
-                    try pointers.append(.{
-                        .offset = base_offset + offset,
-                        .segment_id = match.seg,
-                    });
-                }
-
-                if (block.prev) |prev| {
-                    block = prev;
-                } else break;
-            }
-        }
-    }
-
-    if (self.got_section_index) |idx| {
-        const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-        const sect = seg.sections.items[idx];
-        const base_offset = sect.addr - seg.inner.vmaddr;
-        const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?);
-
-        for (self.got_entries.keys()) |key, i| {
-            if (key.where == .import) continue;
-
-            try pointers.append(.{
-                .offset = base_offset + i * @sizeOf(u64),
-                .segment_id = segment_id,
-            });
-        }
-    }
-
-    if (self.la_symbol_ptr_section_index) |idx| {
-        const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        const sect = seg.sections.items[idx];
-        const base_offset = sect.addr - seg.inner.vmaddr;
-        const segment_id = @intCast(u16, self.data_segment_cmd_index.?);
-
-        try pointers.ensureUnusedCapacity(self.stubs.count());
-        for (self.stubs.keys()) |_, i| {
-            pointers.appendAssumeCapacity(.{
-                .offset = base_offset + i * @sizeOf(u64),
-                .segment_id = segment_id,
-            });
-        }
-    }
-
-    std.sort.sort(Pointer, pointers.items, {}, pointerCmp);
-
-    const size = try rebaseInfoSize(pointers.items);
-    var buffer = try self.allocator.alloc(u8, @intCast(usize, size));
-    defer self.allocator.free(buffer);
-
-    var stream = std.io.fixedBufferStream(buffer);
-    try writeRebaseInfo(pointers.items, stream.writer());
-
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-    dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff);
-    dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64)));
-    seg.inner.filesize += dyld_info.rebase_size;
-
-    log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size });
-
-    try self.file.?.pwriteAll(buffer, dyld_info.rebase_off);
-}
-
-fn writeBindInfoTable(self: *Zld) !void {
-    var pointers = std.ArrayList(Pointer).init(self.allocator);
-    defer pointers.deinit();
-
-    if (self.got_section_index) |idx| {
-        const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-        const sect = seg.sections.items[idx];
-        const base_offset = sect.addr - seg.inner.vmaddr;
-        const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?);
-
-        for (self.got_entries.keys()) |key, i| {
-            if (key.where == .local) continue;
-
-            const sym = self.imports.items[key.where_index];
-            try pointers.append(.{
-                .offset = base_offset + i * @sizeOf(u64),
-                .segment_id = segment_id,
-                .dylib_ordinal = unpackDylibOrdinal(sym.n_desc),
-                .name = self.getString(sym.n_strx),
-            });
-        }
-    }
-
-    {
-        var it = self.blocks.iterator();
-        while (it.next()) |entry| {
-            const match = entry.key_ptr.*;
-            var block: *TextBlock = entry.value_ptr.*;
-
-            if (match.seg == self.text_segment_cmd_index.?) continue; // __TEXT is non-writable
-
-            const seg = self.load_commands.items[match.seg].Segment;
-
-            while (true) {
-                const sym = self.locals.items[block.local_sym_index];
-                const base_offset = sym.n_value - seg.inner.vmaddr;
-
-                for (block.bindings.items) |binding| {
-                    const bind_sym = self.imports.items[binding.local_sym_index];
-                    try pointers.append(.{
-                        .offset = binding.offset + base_offset,
-                        .segment_id = match.seg,
-                        .dylib_ordinal = unpackDylibOrdinal(bind_sym.n_desc),
-                        .name = self.getString(bind_sym.n_strx),
-                    });
-                }
-
-                if (block.prev) |prev| {
-                    block = prev;
-                } else break;
-            }
-        }
-    }
-
-    const size = try bindInfoSize(pointers.items);
-    var buffer = try self.allocator.alloc(u8, @intCast(usize, size));
-    defer self.allocator.free(buffer);
-
-    var stream = std.io.fixedBufferStream(buffer);
-    try writeBindInfo(pointers.items, stream.writer());
-
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-    dyld_info.bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
-    dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)));
-    seg.inner.filesize += dyld_info.bind_size;
-
-    log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size });
-
-    try self.file.?.pwriteAll(buffer, dyld_info.bind_off);
-}
-
-fn writeLazyBindInfoTable(self: *Zld) !void {
-    var pointers = std.ArrayList(Pointer).init(self.allocator);
-    defer pointers.deinit();
-
-    if (self.la_symbol_ptr_section_index) |idx| {
-        const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-        const sect = seg.sections.items[idx];
-        const base_offset = sect.addr - seg.inner.vmaddr;
-        const segment_id = @intCast(u16, self.data_segment_cmd_index.?);
-
-        try pointers.ensureUnusedCapacity(self.stubs.count());
-
-        for (self.stubs.keys()) |key, i| {
-            const sym = self.imports.items[key];
-            pointers.appendAssumeCapacity(.{
-                .offset = base_offset + i * @sizeOf(u64),
-                .segment_id = segment_id,
-                .dylib_ordinal = unpackDylibOrdinal(sym.n_desc),
-                .name = self.getString(sym.n_strx),
-            });
-        }
-    }
-
-    const size = try lazyBindInfoSize(pointers.items);
-    var buffer = try self.allocator.alloc(u8, @intCast(usize, size));
-    defer self.allocator.free(buffer);
-
-    var stream = std.io.fixedBufferStream(buffer);
-    try writeLazyBindInfo(pointers.items, stream.writer());
-
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-    dyld_info.lazy_bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
-    dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)));
-    seg.inner.filesize += dyld_info.lazy_bind_size;
-
-    log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size });
-
-    try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off);
-    try self.populateLazyBindOffsetsInStubHelper(buffer);
-}
-
-fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void {
-    var stream = std.io.fixedBufferStream(buffer);
-    var reader = stream.reader();
-    var offsets = std.ArrayList(u32).init(self.allocator);
-    try offsets.append(0);
-    defer offsets.deinit();
-    var valid_block = false;
-
-    while (true) {
-        const inst = reader.readByte() catch |err| switch (err) {
-            error.EndOfStream => break,
-            else => return err,
-        };
-        const opcode: u8 = inst & macho.BIND_OPCODE_MASK;
-
-        switch (opcode) {
-            macho.BIND_OPCODE_DO_BIND => {
-                valid_block = true;
-            },
-            macho.BIND_OPCODE_DONE => {
-                if (valid_block) {
-                    const offset = try stream.getPos();
-                    try offsets.append(@intCast(u32, offset));
-                }
-                valid_block = false;
-            },
-            macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
-                var next = try reader.readByte();
-                while (next != @as(u8, 0)) {
-                    next = try reader.readByte();
-                }
-            },
-            macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
-                _ = try leb.readULEB128(u64, reader);
-            },
-            macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
-                _ = try leb.readULEB128(u64, reader);
-            },
-            macho.BIND_OPCODE_SET_ADDEND_SLEB => {
-                _ = try leb.readILEB128(i64, reader);
-            },
-            else => {},
-        }
-    }
-    assert(self.stubs.count() <= offsets.items.len);
-
-    const stub_size: u4 = switch (self.target.?.cpu.arch) {
-        .x86_64 => 10,
-        .aarch64 => 3 * @sizeOf(u32),
-        else => unreachable,
-    };
-    const off: u4 = switch (self.target.?.cpu.arch) {
-        .x86_64 => 1,
-        .aarch64 => 2 * @sizeOf(u32),
-        else => unreachable,
-    };
-    var buf: [@sizeOf(u32)]u8 = undefined;
-    for (self.stubs.keys()) |_, index| {
-        const placeholder_off = self.stub_helper_stubs_start_off.? + index * stub_size + off;
-        mem.writeIntLittle(u32, &buf, offsets.items[index]);
-        try self.file.?.pwriteAll(&buf, placeholder_off);
-    }
-}
-
-fn writeExportInfo(self: *Zld) !void {
-    var trie = Trie.init(self.allocator);
-    defer trie.deinit();
-
-    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const base_address = text_segment.inner.vmaddr;
-
-    // TODO handle macho.EXPORT_SYMBOL_FLAGS_REEXPORT and macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER.
-    log.debug("writing export trie", .{});
-
-    for (self.globals.items) |sym| {
-        const sym_name = self.getString(sym.n_strx);
-        log.debug("  | putting '{s}' defined at 0x{x}", .{ sym_name, sym.n_value });
-
-        try trie.put(.{
-            .name = sym_name,
-            .vmaddr_offset = sym.n_value - base_address,
-            .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
-        });
-    }
-
-    try trie.finalize();
-
-    var buffer = try self.allocator.alloc(u8, @intCast(usize, trie.size));
-    defer self.allocator.free(buffer);
-
-    var stream = std.io.fixedBufferStream(buffer);
-    const nwritten = try trie.write(stream.writer());
-    assert(nwritten == trie.size);
-
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-    dyld_info.export_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
-    dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)));
-    seg.inner.filesize += dyld_info.export_size;
-
-    log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size });
-
-    try self.file.?.pwriteAll(buffer, dyld_info.export_off);
-}
-
-fn writeSymbolTable(self: *Zld) !void {
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
-
-    var locals = std.ArrayList(macho.nlist_64).init(self.allocator);
-    defer locals.deinit();
-    try locals.appendSlice(self.locals.items);
-
-    if (self.has_stabs) {
-        for (self.objects.items) |object| {
-            if (object.debug_info == null) continue;
-
-            // Open scope
-            try locals.ensureUnusedCapacity(4);
-            locals.appendAssumeCapacity(.{
-                .n_strx = try self.makeString(object.tu_comp_dir.?),
-                .n_type = macho.N_SO,
-                .n_sect = 0,
-                .n_desc = 0,
-                .n_value = 0,
-            });
-            locals.appendAssumeCapacity(.{
-                .n_strx = try self.makeString(object.tu_name.?),
-                .n_type = macho.N_SO,
-                .n_sect = 0,
-                .n_desc = 0,
-                .n_value = 0,
-            });
-            locals.appendAssumeCapacity(.{
-                .n_strx = try self.makeString(object.name.?),
-                .n_type = macho.N_OSO,
-                .n_sect = 0,
-                .n_desc = 1,
-                .n_value = object.mtime orelse 0,
-            });
-
-            for (object.text_blocks.items) |block| {
-                if (block.stab) |stab| {
-                    const nlists = try stab.asNlists(block.local_sym_index, self);
-                    defer self.allocator.free(nlists);
-                    try locals.appendSlice(nlists);
-                } else {
-                    const contained = block.contained orelse continue;
-                    for (contained) |sym_at_off| {
-                        const stab = sym_at_off.stab orelse continue;
-                        const nlists = try stab.asNlists(sym_at_off.local_sym_index, self);
-                        defer self.allocator.free(nlists);
-                        try locals.appendSlice(nlists);
-                    }
-                }
-            }
-
-            // Close scope
-            locals.appendAssumeCapacity(.{
-                .n_strx = 0,
-                .n_type = macho.N_SO,
-                .n_sect = 0,
-                .n_desc = 0,
-                .n_value = 0,
-            });
-        }
-    }
-
-    const nlocals = locals.items.len;
-    const nexports = self.globals.items.len;
-    const nundefs = self.imports.items.len;
-
-    const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64);
-    const locals_size = nlocals * @sizeOf(macho.nlist_64);
-    log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off });
-    try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off);
-
-    const exports_off = locals_off + locals_size;
-    const exports_size = nexports * @sizeOf(macho.nlist_64);
-    log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
-    try self.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), exports_off);
-
-    const undefs_off = exports_off + exports_size;
-    const undefs_size = nundefs * @sizeOf(macho.nlist_64);
-    log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off });
-    try self.file.?.pwriteAll(mem.sliceAsBytes(self.imports.items), undefs_off);
-
-    symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs);
-    seg.inner.filesize += locals_size + exports_size + undefs_size;
-
-    // Update dynamic symbol table.
-    const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
-    dysymtab.nlocalsym += @intCast(u32, nlocals);
-    dysymtab.iextdefsym = dysymtab.nlocalsym;
-    dysymtab.nextdefsym = @intCast(u32, nexports);
-    dysymtab.iundefsym = dysymtab.nlocalsym + dysymtab.nextdefsym;
-    dysymtab.nundefsym = @intCast(u32, nundefs);
-
-    const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const stubs = &text_segment.sections.items[self.stubs_section_index.?];
-    const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
-    const got = &data_const_segment.sections.items[self.got_section_index.?];
-    const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-    const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?];
-
-    const nstubs = @intCast(u32, self.stubs.count());
-    const ngot_entries = @intCast(u32, self.got_entries.count());
-
-    dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
-    dysymtab.nindirectsyms = nstubs * 2 + ngot_entries;
-
-    const needed_size = dysymtab.nindirectsyms * @sizeOf(u32);
-    seg.inner.filesize += needed_size;
-
-    log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{
-        dysymtab.indirectsymoff,
-        dysymtab.indirectsymoff + needed_size,
-    });
-
-    var buf = try self.allocator.alloc(u8, needed_size);
-    defer self.allocator.free(buf);
-
-    var stream = std.io.fixedBufferStream(buf);
-    var writer = stream.writer();
-
-    stubs.reserved1 = 0;
-    for (self.stubs.keys()) |key| {
-        try writer.writeIntLittle(u32, dysymtab.iundefsym + key);
-    }
-
-    got.reserved1 = nstubs;
-    for (self.got_entries.keys()) |key| {
-        switch (key.where) {
-            .import => {
-                try writer.writeIntLittle(u32, dysymtab.iundefsym + key.where_index);
-            },
-            .local => {
-                try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL);
-            },
-        }
-    }
-
-    la_symbol_ptr.reserved1 = got.reserved1 + ngot_entries;
-    for (self.stubs.keys()) |key| {
-        try writer.writeIntLittle(u32, dysymtab.iundefsym + key);
-    }
-
-    try self.file.?.pwriteAll(buf, dysymtab.indirectsymoff);
-}
-
-fn writeStringTable(self: *Zld) !void {
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
-    symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
-    symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64)));
-    seg.inner.filesize += symtab.strsize;
-
-    log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
-
-    try self.file.?.pwriteAll(self.strtab.items, symtab.stroff);
-
-    if (symtab.strsize > self.strtab.items.len and self.target.?.cpu.arch == .x86_64) {
-        // This is the last section, so we need to pad it out.
-        try self.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1);
-    }
-}
-
-fn writeDices(self: *Zld) !void {
-    if (!self.has_dices) return;
-
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData;
-    const fileoff = seg.inner.fileoff + seg.inner.filesize;
-
-    var buf = std.ArrayList(u8).init(self.allocator);
-    defer buf.deinit();
-
-    var block: *TextBlock = self.blocks.get(.{
-        .seg = self.text_segment_cmd_index orelse return,
-        .sect = self.text_section_index orelse return,
-    }) orelse return;
-
-    while (block.prev) |prev| {
-        block = prev;
-    }
-
-    const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const text_sect = text_seg.sections.items[self.text_section_index.?];
-
-    while (true) {
-        if (block.dices.items.len > 0) {
-            const sym = self.locals.items[block.local_sym_index];
-            const base_off = try math.cast(u32, sym.n_value - text_sect.addr + text_sect.offset);
-
-            try buf.ensureUnusedCapacity(block.dices.items.len * @sizeOf(macho.data_in_code_entry));
-            for (block.dices.items) |dice| {
-                const rebased_dice = macho.data_in_code_entry{
-                    .offset = base_off + dice.offset,
-                    .length = dice.length,
-                    .kind = dice.kind,
-                };
-                buf.appendSliceAssumeCapacity(mem.asBytes(&rebased_dice));
-            }
-        }
-
-        if (block.next) |next| {
-            block = next;
-        } else break;
-    }
-
-    const datasize = @intCast(u32, buf.items.len);
-
-    dice_cmd.dataoff = @intCast(u32, fileoff);
-    dice_cmd.datasize = datasize;
-    seg.inner.filesize += datasize;
-
-    log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize });
-
-    try self.file.?.pwriteAll(buf.items, fileoff);
-}
-
-fn writeCodeSignaturePadding(self: *Zld) !void {
-    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
-    const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData;
-    const fileoff = seg.inner.fileoff + seg.inner.filesize;
-    const needed_size = CodeSignature.calcCodeSignaturePaddingSize(
-        self.output.?.path,
-        fileoff,
-        self.page_size.?,
-    );
-    code_sig_cmd.dataoff = @intCast(u32, fileoff);
-    code_sig_cmd.datasize = needed_size;
-
-    // Advance size of __LINKEDIT segment
-    seg.inner.filesize += needed_size;
-    seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?);
-
-    log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + 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}, fileoff + needed_size - 1);
-}
-
-fn writeCodeSignature(self: *Zld) !void {
-    const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData;
-
-    var code_sig = CodeSignature.init(self.allocator, self.page_size.?);
-    defer code_sig.deinit();
-    try code_sig.calcAdhocSignature(
-        self.file.?,
-        self.output.?.path,
-        text_seg.inner,
-        code_sig_cmd,
-        .Exe,
-    );
-
-    var buffer = try self.allocator.alloc(u8, code_sig.size());
-    defer self.allocator.free(buffer);
-    var stream = std.io.fixedBufferStream(buffer);
-    try code_sig.write(stream.writer());
-
-    log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len });
-    try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff);
-}
-
-fn writeLoadCommands(self: *Zld) !void {
-    var sizeofcmds: u32 = 0;
-    for (self.load_commands.items) |lc| {
-        sizeofcmds += lc.cmdsize();
-    }
-
-    var buffer = try self.allocator.alloc(u8, sizeofcmds);
-    defer self.allocator.free(buffer);
-    var writer = std.io.fixedBufferStream(buffer).writer();
-    for (self.load_commands.items) |lc| {
-        try lc.write(writer);
-    }
-
-    const off = @sizeOf(macho.mach_header_64);
-    log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds });
-    try self.file.?.pwriteAll(buffer, off);
-}
-
-fn writeHeader(self: *Zld) !void {
-    var header = emptyHeader(.{
-        .flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL,
-    });
-
-    switch (self.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.output.?.tag) {
-        .exe => {
-            header.filetype = macho.MH_EXECUTE;
-        },
-        .dylib => {
-            header.filetype = macho.MH_DYLIB;
-            header.flags |= macho.MH_NO_REEXPORTED_DYLIBS;
-        },
-    }
-
-    if (self.tlv_section_index) |_|
-        header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
-
-    header.ncmds = @intCast(u32, self.load_commands.items.len);
-    header.sizeofcmds = 0;
-
-    for (self.load_commands.items) |cmd| {
-        header.sizeofcmds += cmd.cmdsize();
-    }
-
-    log.debug("writing Mach-O header {}", .{header});
-
-    try self.file.?.pwriteAll(mem.asBytes(&header), 0);
-}
-
-pub fn makeString(self: *Zld, string: []const u8) !u32 {
-    try self.strtab.ensureUnusedCapacity(self.allocator, string.len + 1);
-    const new_off = @intCast(u32, self.strtab.items.len);
-
-    log.debug("writing new string '{s}' at offset 0x{x}", .{ string, new_off });
-
-    self.strtab.appendSliceAssumeCapacity(string);
-    self.strtab.appendAssumeCapacity(0);
-
-    return new_off;
-}
-
-pub fn getString(self: *Zld, off: u32) []const u8 {
-    assert(off < self.strtab.items.len);
-    return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + off));
-}
-
-pub fn symbolIsStab(sym: macho.nlist_64) bool {
-    return (macho.N_STAB & sym.n_type) != 0;
-}
-
-pub fn symbolIsPext(sym: macho.nlist_64) bool {
-    return (macho.N_PEXT & sym.n_type) != 0;
-}
-
-pub fn symbolIsExt(sym: macho.nlist_64) bool {
-    return (macho.N_EXT & sym.n_type) != 0;
-}
-
-pub fn symbolIsSect(sym: macho.nlist_64) bool {
-    const type_ = macho.N_TYPE & sym.n_type;
-    return type_ == macho.N_SECT;
-}
-
-pub fn symbolIsUndf(sym: macho.nlist_64) bool {
-    const type_ = macho.N_TYPE & sym.n_type;
-    return type_ == macho.N_UNDF;
-}
-
-pub fn symbolIsIndr(sym: macho.nlist_64) bool {
-    const type_ = macho.N_TYPE & sym.n_type;
-    return type_ == macho.N_INDR;
-}
-
-pub fn symbolIsAbs(sym: macho.nlist_64) bool {
-    const type_ = macho.N_TYPE & sym.n_type;
-    return type_ == macho.N_ABS;
-}
-
-pub fn symbolIsWeakDef(sym: macho.nlist_64) bool {
-    return (sym.n_desc & macho.N_WEAK_DEF) != 0;
-}
-
-pub fn symbolIsWeakRef(sym: macho.nlist_64) bool {
-    return (sym.n_desc & macho.N_WEAK_REF) != 0;
-}
-
-pub fn symbolIsTentative(sym: macho.nlist_64) bool {
-    if (!symbolIsUndf(sym)) return false;
-    return sym.n_value != 0;
-}
-
-pub fn symbolIsNull(sym: macho.nlist_64) bool {
-    return sym.n_value == 0 and sym.n_desc == 0 and sym.n_type == 0 and sym.n_strx == 0 and sym.n_sect == 0;
-}
-
-pub fn symbolIsTemp(sym: macho.nlist_64, sym_name: []const u8) bool {
-    if (!symbolIsSect(sym)) return false;
-    if (symbolIsExt(sym)) return false;
-    return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
-}
-
-pub fn sectionId(self: Zld, match: MatchingSection) u8 {
-    // TODO there might be a more generic way of doing this.
-    var section: u8 = 0;
-    for (self.load_commands.items) |cmd, cmd_id| {
-        if (cmd != .Segment) break;
-        if (cmd_id == match.seg) {
-            section += @intCast(u8, match.sect) + 1;
-            break;
-        }
-        section += @intCast(u8, cmd.Segment.sections.items.len);
-    }
-    return section;
-}
-
-pub fn unpackSectionId(self: Zld, section_id: u8) MatchingSection {
-    var match: MatchingSection = undefined;
-    var section: u8 = 0;
-    outer: for (self.load_commands.items) |cmd, cmd_id| {
-        assert(cmd == .Segment);
-        for (cmd.Segment.sections.items) |_, sect_id| {
-            section += 1;
-            if (section_id == section) {
-                match.seg = @intCast(u16, cmd_id);
-                match.sect = @intCast(u16, sect_id);
-                break :outer;
-            }
-        }
-    }
-    return match;
-}
-
-fn packDylibOrdinal(ordinal: u16) u16 {
-    return ordinal * macho.N_SYMBOL_RESOLVER;
-}
-
-fn unpackDylibOrdinal(pack: u16) u16 {
-    return @divExact(pack, macho.N_SYMBOL_RESOLVER);
-}
-
-pub fn findFirst(comptime T: type, haystack: []T, start: usize, predicate: anytype) usize {
-    if (!@hasDecl(@TypeOf(predicate), "predicate"))
-        @compileError("Predicate is required to define fn predicate(@This(), T) bool");
-
-    if (start == haystack.len) return start;
-
-    var i = start;
-    while (i < haystack.len) : (i += 1) {
-        if (predicate.predicate(haystack[i])) break;
-    }
-    return i;
-}
src/link/MachO.zig
@@ -20,16 +20,19 @@ const target_util = @import("../target.zig");
 const trace = @import("../tracy.zig").trace;
 
 const Allocator = mem.Allocator;
+const Archive = @import("MachO/Archive.zig");
 const Cache = @import("../Cache.zig");
 const CodeSignature = @import("MachO/CodeSignature.zig");
 const Compilation = @import("../Compilation.zig");
 const DebugSymbols = @import("MachO/DebugSymbols.zig");
+const Dylib = @import("MachO/Dylib.zig");
+const Object = @import("MachO/Object.zig");
 const LoadCommand = commands.LoadCommand;
 const Module = @import("../Module.zig");
 const File = link.File;
+pub const TextBlock = @import("MachO/TextBlock.zig");
 const Trie = @import("MachO/Trie.zig");
 const SegmentCommand = commands.SegmentCommand;
-const Zld = @import("MachO/Zld.zig");
 
 pub const base_tag: File.Tag = File.Tag.macho;
 
@@ -47,63 +50,83 @@ page_size: u16,
 /// potential future extensions.
 header_pad: u16 = 0x1000,
 
-/// Table of all load commands
+/// The absolute address of the entry point.
+entry_addr: ?u64 = null,
+
+objects: std.ArrayListUnmanaged(*Object) = .{},
+archives: std.ArrayListUnmanaged(*Archive) = .{},
+dylibs: std.ArrayListUnmanaged(*Dylib) = .{},
+
+next_dylib_ordinal: u16 = 1,
+
 load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
-/// __PAGEZERO segment
+
 pagezero_segment_cmd_index: ?u16 = null,
-/// __TEXT segment
 text_segment_cmd_index: ?u16 = null,
-/// __DATA_CONST segment
 data_const_segment_cmd_index: ?u16 = null,
-/// __DATA segment
 data_segment_cmd_index: ?u16 = null,
-/// __LINKEDIT segment
 linkedit_segment_cmd_index: ?u16 = null,
-/// Dyld info
 dyld_info_cmd_index: ?u16 = null,
-/// Symbol table
 symtab_cmd_index: ?u16 = null,
-/// Dynamic symbol table
 dysymtab_cmd_index: ?u16 = null,
-/// Path to dyld linker
 dylinker_cmd_index: ?u16 = null,
-/// Path to libSystem
-libsystem_cmd_index: ?u16 = null,
-/// Data-in-code section of __LINKEDIT segment
 data_in_code_cmd_index: ?u16 = null,
-/// Address to entry point function
 function_starts_cmd_index: ?u16 = null,
-/// Main/entry point
-/// Specifies offset wrt __TEXT segment start address to the main entry point
-/// of the binary.
 main_cmd_index: ?u16 = null,
-/// Minimum OS version
+dylib_id_cmd_index: ?u16 = null,
 version_min_cmd_index: ?u16 = null,
-/// Source version
 source_version_cmd_index: ?u16 = null,
-/// UUID load command
 uuid_cmd_index: ?u16 = null,
-/// Code signature
 code_signature_cmd_index: ?u16 = null,
+/// Path to libSystem
+/// TODO this is obsolete, remove it.
+libsystem_cmd_index: ?u16 = null,
 
-/// Index into __TEXT,__text section.
+// __TEXT segment sections
 text_section_index: ?u16 = null,
-/// Index into __TEXT,__stubs section.
 stubs_section_index: ?u16 = null,
-/// Index into __TEXT,__stub_helper section.
 stub_helper_section_index: ?u16 = null,
-/// Index into __DATA_CONST,__got section.
+text_const_section_index: ?u16 = null,
+cstring_section_index: ?u16 = null,
+ustring_section_index: ?u16 = null,
+gcc_except_tab_section_index: ?u16 = null,
+unwind_info_section_index: ?u16 = null,
+eh_frame_section_index: ?u16 = null,
+
+objc_methlist_section_index: ?u16 = null,
+objc_methname_section_index: ?u16 = null,
+objc_methtype_section_index: ?u16 = null,
+objc_classname_section_index: ?u16 = null,
+
+// __DATA_CONST segment sections
 got_section_index: ?u16 = null,
-/// Index into __DATA,__la_symbol_ptr section.
+mod_init_func_section_index: ?u16 = null,
+mod_term_func_section_index: ?u16 = null,
+data_const_section_index: ?u16 = null,
+
+objc_cfstring_section_index: ?u16 = null,
+objc_classlist_section_index: ?u16 = null,
+objc_imageinfo_section_index: ?u16 = null,
+
+// __DATA segment sections
+tlv_section_index: ?u16 = null,
+tlv_data_section_index: ?u16 = null,
+tlv_bss_section_index: ?u16 = null,
 la_symbol_ptr_section_index: ?u16 = null,
-/// Index into __DATA,__data section.
 data_section_index: ?u16 = null,
-/// The absolute address of the entry point.
-entry_addr: ?u64 = null,
+bss_section_index: ?u16 = null,
+common_section_index: ?u16 = null,
+
+objc_const_section_index: ?u16 = null,
+objc_selrefs_section_index: ?u16 = null,
+objc_classrefs_section_index: ?u16 = null,
+objc_data_section_index: ?u16 = null,
 
 locals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
 globals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
 imports: std.ArrayListUnmanaged(macho.nlist_64) = .{},
+undefs: std.ArrayListUnmanaged(macho.nlist_64) = .{},
+tentatives: std.ArrayListUnmanaged(macho.nlist_64) = .{},
 symbol_resolver: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{},
 
 locals_free_list: std.ArrayListUnmanaged(u32) = .{},
@@ -133,6 +156,9 @@ export_info_dirty: bool = false,
 strtab_dirty: bool = false,
 strtab_needs_relocation: bool = false,
 
+has_dices: bool = false,
+has_stabs: bool = false,
+
 /// A list of text blocks that have surplus capacity. This list can have false
 /// positives, as functions grow and shrink over time, only sometimes being added
 /// or removed from the freelist.
@@ -153,6 +179,8 @@ text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{},
 /// Pointer to the last allocated text block
 last_text_block: ?*TextBlock = null,
 
+blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},
+
 /// A list of all PIE fixups required for this run of the linker.
 /// Warning, this is currently NOT thread-safe. See the TODO below.
 /// TODO Move this list inside `updateDecl` where it should be allocated
@@ -236,71 +264,7 @@ const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.
 /// it as a possible place to put new symbols, it must have enough room for this many bytes
 /// (plus extra for reserved capacity).
 const minimum_text_block_size = 64;
-const min_text_capacity = padToIdeal(minimum_text_block_size);
-
-pub const TextBlock = struct {
-    /// Each decl always gets a local symbol with the fully qualified name.
-    /// The vaddr and size are found here directly.
-    /// The file offset is found by computing the vaddr offset from the section vaddr
-    /// the symbol references, and adding that to the file offset of the section.
-    /// If this field is 0, it means the codegen size = 0 and there is no symbol or
-    /// offset table entry.
-    local_sym_index: u32,
-    /// Size of this text block
-    /// Unlike in Elf, we need to store the size of this symbol as part of
-    /// the TextBlock since macho.nlist_64 lacks this information.
-    size: u64,
-    /// Points to the previous and next neighbours
-    prev: ?*TextBlock,
-    next: ?*TextBlock,
-
-    /// Previous/next linked list pointers.
-    /// This is the linked list node for this Decl's corresponding .debug_info tag.
-    dbg_info_prev: ?*TextBlock,
-    dbg_info_next: ?*TextBlock,
-    /// Offset into .debug_info pointing to the tag for this Decl.
-    dbg_info_off: u32,
-    /// Size of the .debug_info tag for this Decl, not including padding.
-    dbg_info_len: u32,
-
-    pub const empty = TextBlock{
-        .local_sym_index = 0,
-        .size = 0,
-        .prev = null,
-        .next = null,
-        .dbg_info_prev = null,
-        .dbg_info_next = null,
-        .dbg_info_off = undefined,
-        .dbg_info_len = undefined,
-    };
-
-    /// Returns how much room there is to grow in virtual address space.
-    /// File offset relocation happens transparently, so it is not included in
-    /// this calculation.
-    fn capacity(self: TextBlock, macho_file: MachO) u64 {
-        const self_sym = macho_file.locals.items[self.local_sym_index];
-        if (self.next) |next| {
-            const next_sym = macho_file.locals.items[next.local_sym_index];
-            return next_sym.n_value - self_sym.n_value;
-        } else {
-            // We are the last block.
-            // The capacity is limited only by virtual address space.
-            return std.math.maxInt(u64) - self_sym.n_value;
-        }
-    }
-
-    fn freeListEligible(self: TextBlock, macho_file: MachO) bool {
-        // No need to keep a free list node for the last block.
-        const next = self.next orelse return false;
-        const self_sym = macho_file.locals.items[self.local_sym_index];
-        const next_sym = macho_file.locals.items[next.local_sym_index];
-        const cap = next_sym.n_value - self_sym.n_value;
-        const ideal_cap = padToIdeal(self.size);
-        if (cap <= ideal_cap) return false;
-        const surplus = cap - ideal_cap;
-        return surplus >= min_text_capacity;
-    }
-};
+pub const min_text_capacity = padToIdeal(minimum_text_block_size);
 
 pub const Export = struct {
     sym_index: ?u32 = null,
@@ -452,9 +416,9 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
                 self.load_commands_dirty = true;
             }
             try self.writeRebaseInfoTable();
-            try self.writeBindingInfoTable();
-            try self.writeLazyBindingInfoTable();
-            try self.writeExportTrie();
+            try self.writeBindInfoTable();
+            try self.writeLazyBindInfoTable();
+            try self.writeExportInfo();
             try self.writeAllGlobalAndUndefSymbols();
             try self.writeIndirectSymbolTable();
             try self.writeStringTable();
@@ -718,14 +682,6 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
             try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
         }
     } else {
-        var zld = try Zld.init(self.base.allocator);
-        defer {
-            zld.closeFiles();
-            zld.deinit();
-        }
-        zld.target = target;
-        zld.stack_size = stack_size;
-
         // Positional arguments to the linker such as object files and static archives.
         var positionals = std.ArrayList([]const u8).init(arena);
 
@@ -796,164 +752,2252 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
             }
         }
 
-        // If we're compiling native and we can find libSystem.B.{dylib, tbd},
-        // we link against that instead of embedded libSystem.B.tbd file.
-        var native_libsystem_available = false;
-        if (self.base.options.is_native_os) blk: {
-            // Try stub file first. If we hit it, then we're done as the stub file
-            // re-exports every single symbol definition.
-            if (try resolveLib(arena, lib_dirs.items, "System", ".tbd")) |full_path| {
-                try libs.append(full_path);
-                native_libsystem_available = true;
-                break :blk;
-            }
-            // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib
-            // doesn't export libc.dylib which we'll need to resolve subsequently also.
-            if (try resolveLib(arena, lib_dirs.items, "System", ".dylib")) |libsystem_path| {
-                if (try resolveLib(arena, lib_dirs.items, "c", ".dylib")) |libc_path| {
-                    try libs.append(libsystem_path);
-                    try libs.append(libc_path);
-                    native_libsystem_available = true;
-                    break :blk;
-                }
-            }
-        }
-        if (!native_libsystem_available) {
-            const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
-                "libc", "darwin", "libSystem.B.tbd",
-            });
-            try libs.append(full_path);
-        }
+        // If we're compiling native and we can find libSystem.B.{dylib, tbd},
+        // we link against that instead of embedded libSystem.B.tbd file.
+        var native_libsystem_available = false;
+        if (self.base.options.is_native_os) blk: {
+            // Try stub file first. If we hit it, then we're done as the stub file
+            // re-exports every single symbol definition.
+            if (try resolveLib(arena, lib_dirs.items, "System", ".tbd")) |full_path| {
+                try libs.append(full_path);
+                native_libsystem_available = true;
+                break :blk;
+            }
+            // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib
+            // doesn't export libc.dylib which we'll need to resolve subsequently also.
+            if (try resolveLib(arena, lib_dirs.items, "System", ".dylib")) |libsystem_path| {
+                if (try resolveLib(arena, lib_dirs.items, "c", ".dylib")) |libc_path| {
+                    try libs.append(libsystem_path);
+                    try libs.append(libc_path);
+                    native_libsystem_available = true;
+                    break :blk;
+                }
+            }
+        }
+        if (!native_libsystem_available) {
+            const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
+                "libc", "darwin", "libSystem.B.tbd",
+            });
+            try libs.append(full_path);
+        }
+
+        // frameworks
+        var framework_dirs = std.ArrayList([]const u8).init(arena);
+        for (self.base.options.framework_dirs) |dir| {
+            if (try resolveSearchDir(arena, dir, self.base.options.sysroot)) |search_dir| {
+                try framework_dirs.append(search_dir);
+            } else {
+                log.warn("directory not found for '-F{s}'", .{dir});
+            }
+        }
+
+        var framework_not_found = false;
+        for (self.base.options.frameworks) |framework| {
+            for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
+                if (try resolveFramework(arena, framework_dirs.items, framework, ext)) |full_path| {
+                    try libs.append(full_path);
+                    break;
+                }
+            } else {
+                log.warn("framework not found for '-f{s}'", .{framework});
+                framework_not_found = true;
+            }
+        }
+
+        if (framework_not_found) {
+            log.warn("Framework search paths:", .{});
+            for (framework_dirs.items) |dir| {
+                log.warn("  {s}", .{dir});
+            }
+        }
+
+        // rpaths
+        var rpath_table = std.StringArrayHashMap(void).init(arena);
+        for (self.base.options.rpath_list) |rpath| {
+            if (rpath_table.contains(rpath)) continue;
+            try rpath_table.putNoClobber(rpath, {});
+        }
+
+        var rpaths = std.ArrayList([]const u8).init(arena);
+        try rpaths.ensureCapacity(rpath_table.count());
+        for (rpath_table.keys()) |*key| {
+            rpaths.appendAssumeCapacity(key.*);
+        }
+
+        if (self.base.options.verbose_link) {
+            var argv = std.ArrayList([]const u8).init(arena);
+
+            try argv.append("zig");
+            try argv.append("ld");
+
+            if (is_exe_or_dyn_lib) {
+                try argv.append("-dynamic");
+            }
+
+            if (is_dyn_lib) {
+                try argv.append("-dylib");
+
+                const install_name = try std.fmt.allocPrint(arena, "@rpath/{s}", .{
+                    self.base.options.emit.?.sub_path,
+                });
+                try argv.append("-install_name");
+                try argv.append(install_name);
+            }
+
+            if (self.base.options.sysroot) |syslibroot| {
+                try argv.append("-syslibroot");
+                try argv.append(syslibroot);
+            }
+
+            for (rpaths.items) |rpath| {
+                try argv.append("-rpath");
+                try argv.append(rpath);
+            }
+
+            try argv.appendSlice(positionals.items);
+
+            try argv.append("-o");
+            try argv.append(full_out_path);
+
+            if (native_libsystem_available) {
+                try argv.append("-lSystem");
+                try argv.append("-lc");
+            }
+
+            for (search_lib_names.items) |l_name| {
+                try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name}));
+            }
+
+            for (self.base.options.lib_dirs) |lib_dir| {
+                try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
+            }
+
+            Compilation.dump_argv(argv.items);
+        }
+
+        self.base.file = try fs.cwd().createFile(full_out_path, .{
+            .truncate = true,
+            .read = true,
+            .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777,
+        });
+        self.page_size = switch (self.base.options.target.cpu.arch) {
+            .aarch64 => 0x4000,
+            .x86_64 => 0x1000,
+            else => unreachable,
+        };
+
+        try self.populateMetadata();
+        try self.parseInputFiles(positionals.items, self.base.options.sysroot);
+        try self.parseLibs(libs.items, self.base.options.sysroot);
+        try self.resolveSymbols();
+        try self.parseTextBlocks();
+
+        {
+            // Add dyld_stub_binder as the final GOT entry.
+            const resolv = self.symbol_resolver.get("dyld_stub_binder") orelse unreachable;
+            const got_index = @intCast(u32, self.got_entries.items.len);
+            const got_entry = GotIndirectionKey{
+                .where = .import,
+                .where_index = resolv.where_index,
+            };
+            try self.got_entries.append(self.base.allocator, got_entry);
+            try self.got_entries_map.putNoClobber(self.base.allocator, got_entry, got_index);
+        }
+
+        try self.sortSections();
+        try self.addRpaths(rpaths.items);
+        try self.addDataInCodeLC();
+        try self.addCodeSignatureLC();
+        try self.allocateTextSegment();
+        try self.allocateDataConstSegment();
+        try self.allocateDataSegment();
+        self.allocateLinkeditSegment();
+        try self.allocateTextBlocks();
+
+        // log.warn("locals", .{});
+        // for (self.locals.items) |sym, id| {
+        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+        // }
+
+        // log.warn("globals", .{});
+        // for (self.globals.items) |sym, id| {
+        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+        // }
+
+        // log.warn("tentatives", .{});
+        // for (self.tentatives.items) |sym, id| {
+        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+        // }
+
+        // log.warn("undefines", .{});
+        // for (self.undefs.items) |sym, id| {
+        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+        // }
+
+        // log.warn("imports", .{});
+        // for (self.imports.items) |sym, id| {
+        //     log.warn("  {d}: {s}, {}", .{ id, self.getString(sym.n_strx), sym });
+        // }
+
+        // log.warn("symbol resolver", .{});
+        // for (self.symbol_resolver.keys()) |key| {
+        //     log.warn("  {s} => {}", .{ key, self.symbol_resolver.get(key).? });
+        // }
+
+        // log.warn("mappings", .{});
+        // for (self.objects.items) |object, id| {
+        //     const object_id = @intCast(u16, id);
+        //     log.warn("  in object {s}", .{object.name.?});
+        //     for (object.symtab.items) |sym, sym_id| {
+        //         if (object.symbol_mapping.get(@intCast(u32, sym_id))) |local_id| {
+        //             log.warn("    | {d} => {d}", .{ sym_id, local_id });
+        //         } else {
+        //             log.warn("    | {d} no local mapping for {s}", .{ sym_id, object.getString(sym.n_strx) });
+        //         }
+        //     }
+        // }
+
+        // var it = self.blocks.iterator();
+        // while (it.next()) |entry| {
+        //     const seg = self.load_commands.items[entry.key_ptr.seg].Segment;
+        //     const sect = seg.sections.items[entry.key_ptr.sect];
+
+        //     log.warn("\n\n{s},{s} contents:", .{ segmentName(sect), sectionName(sect) });
+        //     log.warn("  {}", .{sect});
+        //     entry.value_ptr.*.print(self);
+        // }
+
+        try self.flushZld();
+    }
+
+    if (!self.base.options.disable_lld_caching) {
+        // Update the file with the digest. If it fails we can continue; it only
+        // means that the next invocation will have an unnecessary cache miss.
+        Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
+            log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)});
+        };
+        // Again failure here only means an unnecessary cache miss.
+        man.writeManifest() catch |err| {
+            log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
+        };
+        // We hang on to this lock so that the output file path can be used without
+        // other processes clobbering it.
+        self.base.lock = man.toOwnedLock();
+    }
+}
+
+fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void {
+    const arch = self.base.options.target.cpu.arch;
+    for (files) |file_name| {
+        const full_path = full_path: {
+            var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+            const path = try std.fs.realpath(file_name, &buffer);
+            break :full_path try self.base.allocator.dupe(u8, path);
+        };
+
+        if (try Object.createAndParseFromPath(self.base.allocator, arch, full_path)) |object| {
+            try self.objects.append(self.base.allocator, object);
+            continue;
+        }
+
+        if (try Archive.createAndParseFromPath(self.base.allocator, arch, full_path)) |archive| {
+            try self.archives.append(self.base.allocator, archive);
+            continue;
+        }
+
+        if (try Dylib.createAndParseFromPath(self.base.allocator, arch, full_path, .{
+            .syslibroot = syslibroot,
+        })) |dylibs| {
+            defer self.base.allocator.free(dylibs);
+            try self.dylibs.appendSlice(self.base.allocator, dylibs);
+            continue;
+        }
+
+        log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
+    }
+}
+
+fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void {
+    const arch = self.base.options.target.cpu.arch;
+    for (libs) |lib| {
+        if (try Dylib.createAndParseFromPath(self.base.allocator, arch, lib, .{
+            .syslibroot = syslibroot,
+        })) |dylibs| {
+            defer self.base.allocator.free(dylibs);
+            try self.dylibs.appendSlice(self.base.allocator, dylibs);
+            continue;
+        }
+
+        if (try Archive.createAndParseFromPath(self.base.allocator, arch, lib)) |archive| {
+            try self.archives.append(self.base.allocator, archive);
+            continue;
+        }
+
+        log.warn("unknown filetype for a library: '{s}'", .{lib});
+    }
+}
+
+pub const MatchingSection = struct {
+    seg: u16,
+    sect: u16,
+};
+
+pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSection {
+    const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+    const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+    const segname = commands.segmentName(sect);
+    const sectname = commands.sectionName(sect);
+
+    const res: ?MatchingSection = blk: {
+        switch (commands.sectionType(sect)) {
+            macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => {
+                if (self.text_const_section_index == null) {
+                    self.text_const_section_index = @intCast(u16, text_seg.sections.items.len);
+                    try text_seg.addSection(self.base.allocator, "__const", .{});
+                }
+
+                break :blk .{
+                    .seg = self.text_segment_cmd_index.?,
+                    .sect = self.text_const_section_index.?,
+                };
+            },
+            macho.S_CSTRING_LITERALS => {
+                if (mem.eql(u8, sectname, "__objc_methname")) {
+                    // TODO it seems the common values within the sections in objects are deduplicated/merged
+                    // on merging the sections' contents.
+                    if (self.objc_methname_section_index == null) {
+                        self.objc_methname_section_index = @intCast(u16, text_seg.sections.items.len);
+                        try text_seg.addSection(self.base.allocator, "__objc_methname", .{
+                            .flags = macho.S_CSTRING_LITERALS,
+                        });
+                    }
+
+                    break :blk .{
+                        .seg = self.text_segment_cmd_index.?,
+                        .sect = self.objc_methname_section_index.?,
+                    };
+                } else if (mem.eql(u8, sectname, "__objc_methtype")) {
+                    if (self.objc_methtype_section_index == null) {
+                        self.objc_methtype_section_index = @intCast(u16, text_seg.sections.items.len);
+                        try text_seg.addSection(self.base.allocator, "__objc_methtype", .{
+                            .flags = macho.S_CSTRING_LITERALS,
+                        });
+                    }
+
+                    break :blk .{
+                        .seg = self.text_segment_cmd_index.?,
+                        .sect = self.objc_methtype_section_index.?,
+                    };
+                } else if (mem.eql(u8, sectname, "__objc_classname")) {
+                    if (self.objc_classname_section_index == null) {
+                        self.objc_classname_section_index = @intCast(u16, text_seg.sections.items.len);
+                        try text_seg.addSection(self.base.allocator, "__objc_classname", .{});
+                    }
+
+                    break :blk .{
+                        .seg = self.text_segment_cmd_index.?,
+                        .sect = self.objc_classname_section_index.?,
+                    };
+                }
+
+                if (self.cstring_section_index == null) {
+                    self.cstring_section_index = @intCast(u16, text_seg.sections.items.len);
+                    try text_seg.addSection(self.base.allocator, "__cstring", .{
+                        .flags = macho.S_CSTRING_LITERALS,
+                    });
+                }
+
+                break :blk .{
+                    .seg = self.text_segment_cmd_index.?,
+                    .sect = self.cstring_section_index.?,
+                };
+            },
+            macho.S_LITERAL_POINTERS => {
+                if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__objc_selrefs")) {
+                    if (self.objc_selrefs_section_index == null) {
+                        self.objc_selrefs_section_index = @intCast(u16, data_seg.sections.items.len);
+                        try data_seg.addSection(self.base.allocator, "__objc_selrefs", .{
+                            .flags = macho.S_LITERAL_POINTERS,
+                        });
+                    }
+
+                    break :blk .{
+                        .seg = self.data_segment_cmd_index.?,
+                        .sect = self.objc_selrefs_section_index.?,
+                    };
+                }
+
+                // TODO investigate
+                break :blk null;
+            },
+            macho.S_MOD_INIT_FUNC_POINTERS => {
+                if (self.mod_init_func_section_index == null) {
+                    self.mod_init_func_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                    try data_const_seg.addSection(self.base.allocator, "__mod_init_func", .{
+                        .flags = macho.S_MOD_INIT_FUNC_POINTERS,
+                    });
+                }
+
+                break :blk .{
+                    .seg = self.data_const_segment_cmd_index.?,
+                    .sect = self.mod_init_func_section_index.?,
+                };
+            },
+            macho.S_MOD_TERM_FUNC_POINTERS => {
+                if (self.mod_term_func_section_index == null) {
+                    self.mod_term_func_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                    try data_const_seg.addSection(self.base.allocator, "__mod_term_func", .{
+                        .flags = macho.S_MOD_TERM_FUNC_POINTERS,
+                    });
+                }
+
+                break :blk .{
+                    .seg = self.data_const_segment_cmd_index.?,
+                    .sect = self.mod_term_func_section_index.?,
+                };
+            },
+            macho.S_ZEROFILL => {
+                if (mem.eql(u8, sectname, "__common")) {
+                    if (self.common_section_index == null) {
+                        self.common_section_index = @intCast(u16, data_seg.sections.items.len);
+                        try data_seg.addSection(self.base.allocator, "__common", .{
+                            .flags = macho.S_ZEROFILL,
+                        });
+                    }
+
+                    break :blk .{
+                        .seg = self.data_segment_cmd_index.?,
+                        .sect = self.common_section_index.?,
+                    };
+                } else {
+                    if (self.bss_section_index == null) {
+                        self.bss_section_index = @intCast(u16, data_seg.sections.items.len);
+                        try data_seg.addSection(self.base.allocator, "__bss", .{
+                            .flags = macho.S_ZEROFILL,
+                        });
+                    }
+
+                    break :blk .{
+                        .seg = self.data_segment_cmd_index.?,
+                        .sect = self.bss_section_index.?,
+                    };
+                }
+            },
+            macho.S_THREAD_LOCAL_VARIABLES => {
+                if (self.tlv_section_index == null) {
+                    self.tlv_section_index = @intCast(u16, data_seg.sections.items.len);
+                    try data_seg.addSection(self.base.allocator, "__thread_vars", .{
+                        .flags = macho.S_THREAD_LOCAL_VARIABLES,
+                    });
+                }
+
+                break :blk .{
+                    .seg = self.data_segment_cmd_index.?,
+                    .sect = self.tlv_section_index.?,
+                };
+            },
+            macho.S_THREAD_LOCAL_REGULAR => {
+                if (self.tlv_data_section_index == null) {
+                    self.tlv_data_section_index = @intCast(u16, data_seg.sections.items.len);
+                    try data_seg.addSection(self.base.allocator, "__thread_data", .{
+                        .flags = macho.S_THREAD_LOCAL_REGULAR,
+                    });
+                }
+
+                break :blk .{
+                    .seg = self.data_segment_cmd_index.?,
+                    .sect = self.tlv_data_section_index.?,
+                };
+            },
+            macho.S_THREAD_LOCAL_ZEROFILL => {
+                if (self.tlv_bss_section_index == null) {
+                    self.tlv_bss_section_index = @intCast(u16, data_seg.sections.items.len);
+                    try data_seg.addSection(self.base.allocator, "__thread_bss", .{
+                        .flags = macho.S_THREAD_LOCAL_ZEROFILL,
+                    });
+                }
+
+                break :blk .{
+                    .seg = self.data_segment_cmd_index.?,
+                    .sect = self.tlv_bss_section_index.?,
+                };
+            },
+            macho.S_COALESCED => {
+                if (mem.eql(u8, "__TEXT", segname) and mem.eql(u8, "__eh_frame", sectname)) {
+                    // TODO I believe __eh_frame is currently part of __unwind_info section
+                    // in the latest ld64 output.
+                    if (self.eh_frame_section_index == null) {
+                        self.eh_frame_section_index = @intCast(u16, text_seg.sections.items.len);
+                        try text_seg.addSection(self.base.allocator, "__eh_frame", .{});
+                    }
+
+                    break :blk .{
+                        .seg = self.text_segment_cmd_index.?,
+                        .sect = self.eh_frame_section_index.?,
+                    };
+                }
+
+                // TODO audit this: is this the right mapping?
+                if (self.data_const_section_index == null) {
+                    self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                    try data_const_seg.addSection(self.base.allocator, "__const", .{});
+                }
+
+                break :blk .{
+                    .seg = self.data_const_segment_cmd_index.?,
+                    .sect = self.data_const_section_index.?,
+                };
+            },
+            macho.S_REGULAR => {
+                if (commands.sectionIsCode(sect)) {
+                    if (self.text_section_index == null) {
+                        self.text_section_index = @intCast(u16, text_seg.sections.items.len);
+                        try text_seg.addSection(self.base.allocator, "__text", .{
+                            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+                        });
+                    }
+
+                    break :blk .{
+                        .seg = self.text_segment_cmd_index.?,
+                        .sect = self.text_section_index.?,
+                    };
+                }
+                if (commands.sectionIsDebug(sect)) {
+                    // TODO debug attributes
+                    if (mem.eql(u8, "__LD", segname) and mem.eql(u8, "__compact_unwind", sectname)) {
+                        log.debug("TODO compact unwind section: type 0x{x}, name '{s},{s}'", .{
+                            sect.flags, segname, sectname,
+                        });
+                    }
+                    break :blk null;
+                }
+
+                if (mem.eql(u8, segname, "__TEXT")) {
+                    if (mem.eql(u8, sectname, "__ustring")) {
+                        if (self.ustring_section_index == null) {
+                            self.ustring_section_index = @intCast(u16, text_seg.sections.items.len);
+                            try text_seg.addSection(self.base.allocator, "__ustring", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.text_segment_cmd_index.?,
+                            .sect = self.ustring_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__gcc_except_tab")) {
+                        if (self.gcc_except_tab_section_index == null) {
+                            self.gcc_except_tab_section_index = @intCast(u16, text_seg.sections.items.len);
+                            try text_seg.addSection(self.base.allocator, "__gcc_except_tab", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.text_segment_cmd_index.?,
+                            .sect = self.gcc_except_tab_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__objc_methlist")) {
+                        if (self.objc_methlist_section_index == null) {
+                            self.objc_methlist_section_index = @intCast(u16, text_seg.sections.items.len);
+                            try text_seg.addSection(self.base.allocator, "__objc_methlist", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.text_segment_cmd_index.?,
+                            .sect = self.objc_methlist_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__rodata") or
+                        mem.eql(u8, sectname, "__typelink") or
+                        mem.eql(u8, sectname, "__itablink") or
+                        mem.eql(u8, sectname, "__gosymtab") or
+                        mem.eql(u8, sectname, "__gopclntab"))
+                    {
+                        if (self.data_const_section_index == null) {
+                            self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                            try data_const_seg.addSection(self.base.allocator, "__const", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_const_segment_cmd_index.?,
+                            .sect = self.data_const_section_index.?,
+                        };
+                    } else {
+                        if (self.text_const_section_index == null) {
+                            self.text_const_section_index = @intCast(u16, text_seg.sections.items.len);
+                            try text_seg.addSection(self.base.allocator, "__const", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.text_segment_cmd_index.?,
+                            .sect = self.text_const_section_index.?,
+                        };
+                    }
+                }
+
+                if (mem.eql(u8, segname, "__DATA_CONST")) {
+                    if (self.data_const_section_index == null) {
+                        self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                        try data_const_seg.addSection(self.base.allocator, "__const", .{});
+                    }
+
+                    break :blk .{
+                        .seg = self.data_const_segment_cmd_index.?,
+                        .sect = self.data_const_section_index.?,
+                    };
+                }
+
+                if (mem.eql(u8, segname, "__DATA")) {
+                    if (mem.eql(u8, sectname, "__const")) {
+                        if (self.data_const_section_index == null) {
+                            self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                            try data_const_seg.addSection(self.base.allocator, "__const", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_const_segment_cmd_index.?,
+                            .sect = self.data_const_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__cfstring")) {
+                        if (self.objc_cfstring_section_index == null) {
+                            self.objc_cfstring_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                            try data_const_seg.addSection(self.base.allocator, "__cfstring", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_const_segment_cmd_index.?,
+                            .sect = self.objc_cfstring_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__objc_classlist")) {
+                        if (self.objc_classlist_section_index == null) {
+                            self.objc_classlist_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                            try data_const_seg.addSection(self.base.allocator, "__objc_classlist", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_const_segment_cmd_index.?,
+                            .sect = self.objc_classlist_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__objc_imageinfo")) {
+                        if (self.objc_imageinfo_section_index == null) {
+                            self.objc_imageinfo_section_index = @intCast(u16, data_const_seg.sections.items.len);
+                            try data_const_seg.addSection(self.base.allocator, "__objc_imageinfo", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_const_segment_cmd_index.?,
+                            .sect = self.objc_imageinfo_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__objc_const")) {
+                        if (self.objc_const_section_index == null) {
+                            self.objc_const_section_index = @intCast(u16, data_seg.sections.items.len);
+                            try data_seg.addSection(self.base.allocator, "__objc_const", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_segment_cmd_index.?,
+                            .sect = self.objc_const_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__objc_classrefs")) {
+                        if (self.objc_classrefs_section_index == null) {
+                            self.objc_classrefs_section_index = @intCast(u16, data_seg.sections.items.len);
+                            try data_seg.addSection(self.base.allocator, "__objc_classrefs", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_segment_cmd_index.?,
+                            .sect = self.objc_classrefs_section_index.?,
+                        };
+                    } else if (mem.eql(u8, sectname, "__objc_data")) {
+                        if (self.objc_data_section_index == null) {
+                            self.objc_data_section_index = @intCast(u16, data_seg.sections.items.len);
+                            try data_seg.addSection(self.base.allocator, "__objc_data", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_segment_cmd_index.?,
+                            .sect = self.objc_data_section_index.?,
+                        };
+                    } else {
+                        if (self.data_section_index == null) {
+                            self.data_section_index = @intCast(u16, data_seg.sections.items.len);
+                            try data_seg.addSection(self.base.allocator, "__data", .{});
+                        }
+
+                        break :blk .{
+                            .seg = self.data_segment_cmd_index.?,
+                            .sect = self.data_section_index.?,
+                        };
+                    }
+                }
+
+                if (mem.eql(u8, "__LLVM", segname) and mem.eql(u8, "__asm", sectname)) {
+                    log.debug("TODO LLVM asm section: type 0x{x}, name '{s},{s}'", .{
+                        sect.flags, segname, sectname,
+                    });
+                }
+
+                break :blk null;
+            },
+            else => break :blk null,
+        }
+    };
+
+    return res;
+}
+
+fn sortSections(self: *MachO) !void {
+    var text_index_mapping = std.AutoHashMap(u16, u16).init(self.base.allocator);
+    defer text_index_mapping.deinit();
+    var data_const_index_mapping = std.AutoHashMap(u16, u16).init(self.base.allocator);
+    defer data_const_index_mapping.deinit();
+    var data_index_mapping = std.AutoHashMap(u16, u16).init(self.base.allocator);
+    defer data_index_mapping.deinit();
+
+    {
+        // __TEXT segment
+        const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+        var sections = seg.sections.toOwnedSlice(self.base.allocator);
+        defer self.base.allocator.free(sections);
+        try seg.sections.ensureCapacity(self.base.allocator, sections.len);
+
+        const indices = &[_]*?u16{
+            &self.text_section_index,
+            &self.stubs_section_index,
+            &self.stub_helper_section_index,
+            &self.gcc_except_tab_section_index,
+            &self.cstring_section_index,
+            &self.ustring_section_index,
+            &self.text_const_section_index,
+            &self.objc_methname_section_index,
+            &self.objc_methtype_section_index,
+            &self.objc_classname_section_index,
+            &self.eh_frame_section_index,
+        };
+        for (indices) |maybe_index| {
+            const new_index: u16 = if (maybe_index.*) |index| blk: {
+                const idx = @intCast(u16, seg.sections.items.len);
+                seg.sections.appendAssumeCapacity(sections[index]);
+                try text_index_mapping.putNoClobber(index, idx);
+                break :blk idx;
+            } else continue;
+            maybe_index.* = new_index;
+        }
+    }
+
+    {
+        // __DATA_CONST segment
+        const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+        var sections = seg.sections.toOwnedSlice(self.base.allocator);
+        defer self.base.allocator.free(sections);
+        try seg.sections.ensureCapacity(self.base.allocator, sections.len);
+
+        const indices = &[_]*?u16{
+            &self.got_section_index,
+            &self.mod_init_func_section_index,
+            &self.mod_term_func_section_index,
+            &self.data_const_section_index,
+            &self.objc_cfstring_section_index,
+            &self.objc_classlist_section_index,
+            &self.objc_imageinfo_section_index,
+        };
+        for (indices) |maybe_index| {
+            const new_index: u16 = if (maybe_index.*) |index| blk: {
+                const idx = @intCast(u16, seg.sections.items.len);
+                seg.sections.appendAssumeCapacity(sections[index]);
+                try data_const_index_mapping.putNoClobber(index, idx);
+                break :blk idx;
+            } else continue;
+            maybe_index.* = new_index;
+        }
+    }
+
+    {
+        // __DATA segment
+        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        var sections = seg.sections.toOwnedSlice(self.base.allocator);
+        defer self.base.allocator.free(sections);
+        try seg.sections.ensureCapacity(self.base.allocator, sections.len);
+
+        // __DATA segment
+        const indices = &[_]*?u16{
+            &self.la_symbol_ptr_section_index,
+            &self.objc_const_section_index,
+            &self.objc_selrefs_section_index,
+            &self.objc_classrefs_section_index,
+            &self.objc_data_section_index,
+            &self.data_section_index,
+            &self.tlv_section_index,
+            &self.tlv_data_section_index,
+            &self.tlv_bss_section_index,
+            &self.bss_section_index,
+            &self.common_section_index,
+        };
+        for (indices) |maybe_index| {
+            const new_index: u16 = if (maybe_index.*) |index| blk: {
+                const idx = @intCast(u16, seg.sections.items.len);
+                seg.sections.appendAssumeCapacity(sections[index]);
+                try data_index_mapping.putNoClobber(index, idx);
+                break :blk idx;
+            } else continue;
+            maybe_index.* = new_index;
+        }
+    }
+
+    {
+        var transient: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{};
+        try transient.ensureCapacity(self.base.allocator, self.blocks.count());
+
+        var it = self.blocks.iterator();
+        while (it.next()) |entry| {
+            const old = entry.key_ptr.*;
+            const sect = if (old.seg == self.text_segment_cmd_index.?)
+                text_index_mapping.get(old.sect).?
+            else if (old.seg == self.data_const_segment_cmd_index.?)
+                data_const_index_mapping.get(old.sect).?
+            else
+                data_index_mapping.get(old.sect).?;
+            transient.putAssumeCapacityNoClobber(.{
+                .seg = old.seg,
+                .sect = sect,
+            }, entry.value_ptr.*);
+        }
+
+        self.blocks.clearAndFree(self.base.allocator);
+        self.blocks.deinit(self.base.allocator);
+        self.blocks = transient;
+    }
+}
+
+fn allocateTextSegment(self: *MachO) !void {
+    const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const nstubs = @intCast(u32, self.stubs.items.len);
+
+    const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize;
+    seg.inner.fileoff = 0;
+    seg.inner.vmaddr = base_vmaddr;
+
+    // Set stubs and stub_helper sizes
+    const stubs = &seg.sections.items[self.stubs_section_index.?];
+    const stub_helper = &seg.sections.items[self.stub_helper_section_index.?];
+    stubs.size += nstubs * stubs.reserved2;
+
+    const stub_size: u4 = switch (self.base.options.target.cpu.arch) {
+        .x86_64 => 10,
+        .aarch64 => 3 * @sizeOf(u32),
+        else => unreachable,
+    };
+    stub_helper.size += nstubs * stub_size;
+
+    var sizeofcmds: u64 = 0;
+    for (self.load_commands.items) |lc| {
+        sizeofcmds += lc.cmdsize();
+    }
+
+    try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds);
+
+    // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments.
+    var min_alignment: u32 = 0;
+    for (seg.sections.items) |sect| {
+        const alignment = try math.powi(u32, 2, sect.@"align");
+        min_alignment = math.max(min_alignment, alignment);
+    }
+
+    assert(min_alignment > 0);
+    const last_sect_idx = seg.sections.items.len - 1;
+    const last_sect = seg.sections.items[last_sect_idx];
+    const shift: u32 = blk: {
+        const diff = seg.inner.filesize - last_sect.offset - last_sect.size;
+        const factor = @divTrunc(diff, min_alignment);
+        break :blk @intCast(u32, factor * min_alignment);
+    };
+
+    if (shift > 0) {
+        for (seg.sections.items) |*sect| {
+            sect.offset += shift;
+            sect.addr += shift;
+        }
+    }
+}
+
+fn allocateDataConstSegment(self: *MachO) !void {
+    const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+    const nentries = @intCast(u32, self.got_entries.items.len);
+
+    const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    seg.inner.fileoff = text_seg.inner.fileoff + text_seg.inner.filesize;
+    seg.inner.vmaddr = text_seg.inner.vmaddr + text_seg.inner.vmsize;
+
+    // Set got size
+    const got = &seg.sections.items[self.got_section_index.?];
+    got.size += nentries * @sizeOf(u64);
+
+    try self.allocateSegment(self.data_const_segment_cmd_index.?, 0);
+}
+
+fn allocateDataSegment(self: *MachO) !void {
+    const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+    const nstubs = @intCast(u32, self.stubs.items.len);
+
+    const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+    seg.inner.fileoff = data_const_seg.inner.fileoff + data_const_seg.inner.filesize;
+    seg.inner.vmaddr = data_const_seg.inner.vmaddr + data_const_seg.inner.vmsize;
+
+    // Set la_symbol_ptr and data size
+    const la_symbol_ptr = &seg.sections.items[self.la_symbol_ptr_section_index.?];
+    const data = &seg.sections.items[self.data_section_index.?];
+    la_symbol_ptr.size += nstubs * @sizeOf(u64);
+    data.size += @sizeOf(u64); // We need at least 8bytes for address of dyld_stub_binder
+
+    try self.allocateSegment(self.data_segment_cmd_index.?, 0);
+}
+
+fn allocateLinkeditSegment(self: *MachO) void {
+    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+    const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+    seg.inner.fileoff = data_seg.inner.fileoff + data_seg.inner.filesize;
+    seg.inner.vmaddr = data_seg.inner.vmaddr + data_seg.inner.vmsize;
+}
+
+fn allocateSegment(self: *MachO, index: u16, offset: u64) !void {
+    const seg = &self.load_commands.items[index].Segment;
+
+    // Allocate the sections according to their alignment at the beginning of the segment.
+    var start: u64 = offset;
+    for (seg.sections.items) |*sect| {
+        const alignment = try math.powi(u32, 2, sect.@"align");
+        const start_aligned = mem.alignForwardGeneric(u64, start, alignment);
+        const end_aligned = mem.alignForwardGeneric(u64, start_aligned + sect.size, alignment);
+        sect.offset = @intCast(u32, seg.inner.fileoff + start_aligned);
+        sect.addr = seg.inner.vmaddr + start_aligned;
+        start = end_aligned;
+    }
+
+    const seg_size_aligned = mem.alignForwardGeneric(u64, start, self.page_size);
+    seg.inner.filesize = seg_size_aligned;
+    seg.inner.vmsize = seg_size_aligned;
+}
+
+fn allocateTextBlocks(self: *MachO) !void {
+    var it = self.blocks.iterator();
+    while (it.next()) |entry| {
+        const match = entry.key_ptr.*;
+        var block: *TextBlock = entry.value_ptr.*;
+
+        // Find the first block
+        while (block.prev) |prev| {
+            block = prev;
+        }
+
+        const seg = self.load_commands.items[match.seg].Segment;
+        const sect = seg.sections.items[match.sect];
+
+        var base_addr: u64 = sect.addr;
+        const n_sect = self.sectionId(match);
+
+        log.debug("  within section {s},{s}", .{ commands.segmentName(sect), commands.sectionName(sect) });
+        log.debug("    {}", .{sect});
+
+        while (true) {
+            const block_alignment = try math.powi(u32, 2, block.alignment);
+            base_addr = mem.alignForwardGeneric(u64, base_addr, block_alignment);
+
+            const sym = &self.locals.items[block.local_sym_index];
+            sym.n_value = base_addr;
+            sym.n_sect = n_sect;
+
+            log.debug("    {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{
+                self.getString(sym.n_strx),
+                base_addr,
+                base_addr + block.size,
+                block.size,
+                block.alignment,
+            });
+
+            // Update each alias (if any)
+            for (block.aliases.items) |index| {
+                const alias_sym = &self.locals.items[index];
+                alias_sym.n_value = base_addr;
+                alias_sym.n_sect = n_sect;
+            }
+
+            // Update each symbol contained within the TextBlock
+            for (block.contained.items) |sym_at_off| {
+                const contained_sym = &self.locals.items[sym_at_off.local_sym_index];
+                contained_sym.n_value = base_addr + sym_at_off.offset;
+                contained_sym.n_sect = n_sect;
+            }
+
+            base_addr += block.size;
+
+            if (block.next) |next| {
+                block = next;
+            } else break;
+        }
+    }
+
+    // Update globals
+    for (self.symbol_resolver.values()) |resolv| {
+        if (resolv.where != .global) continue;
+
+        assert(resolv.local_sym_index != 0);
+        const local_sym = self.locals.items[resolv.local_sym_index];
+        const sym = &self.globals.items[resolv.where_index];
+        sym.n_value = local_sym.n_value;
+        sym.n_sect = local_sym.n_sect;
+    }
+}
+
+fn writeTextBlocks(self: *MachO) !void {
+    var it = self.blocks.iterator();
+    while (it.next()) |entry| {
+        const match = entry.key_ptr.*;
+        var block: *TextBlock = entry.value_ptr.*;
+
+        while (block.prev) |prev| {
+            block = prev;
+        }
+
+        const seg = self.load_commands.items[match.seg].Segment;
+        const sect = seg.sections.items[match.sect];
+        const sect_type = commands.sectionType(sect);
+
+        log.debug("  for section {s},{s}", .{ commands.segmentName(sect), commands.sectionName(sect) });
+        log.debug("    {}", .{sect});
+
+        var code = try self.base.allocator.alloc(u8, sect.size);
+        defer self.base.allocator.free(code);
+
+        if (sect_type == macho.S_ZEROFILL or sect_type == macho.S_THREAD_LOCAL_ZEROFILL) {
+            mem.set(u8, code, 0);
+        } else {
+            var base_off: u64 = 0;
+
+            while (true) {
+                const block_alignment = try math.powi(u32, 2, block.alignment);
+                const aligned_base_off = mem.alignForwardGeneric(u64, base_off, block_alignment);
+
+                const sym = self.locals.items[block.local_sym_index];
+                log.debug("    {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{
+                    self.getString(sym.n_strx),
+                    aligned_base_off,
+                    aligned_base_off + block.size,
+                    block.size,
+                    block.alignment,
+                });
+
+                try block.resolveRelocs(self);
+                mem.copy(u8, code[aligned_base_off..][0..block.size], block.code);
+
+                // TODO NOP for machine code instead of just zeroing out
+                const padding_len = aligned_base_off - base_off;
+                mem.set(u8, code[base_off..][0..padding_len], 0);
+
+                base_off = aligned_base_off + block.size;
+
+                if (block.next) |next| {
+                    block = next;
+                } else break;
+            }
+
+            mem.set(u8, code[base_off..], 0);
+        }
+
+        try self.base.file.?.pwriteAll(code, sect.offset);
+    }
+}
+
+fn writeStubHelperCommon(self: *MachO) !void {
+    const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?];
+    const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+    const got = &data_const_segment.sections.items[self.got_section_index.?];
+    const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+    const data = &data_segment.sections.items[self.data_section_index.?];
+
+    self.stub_helper_stubs_start_off = blk: {
+        switch (self.base.options.target.cpu.arch) {
+            .x86_64 => {
+                const code_size = 15;
+                var code: [code_size]u8 = undefined;
+                // lea %r11, [rip + disp]
+                code[0] = 0x4c;
+                code[1] = 0x8d;
+                code[2] = 0x1d;
+                {
+                    const target_addr = data.addr + data.size - @sizeOf(u64);
+                    const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7);
+                    mem.writeIntLittle(u32, code[3..7], displacement);
+                }
+                // push %r11
+                code[7] = 0x41;
+                code[8] = 0x53;
+                // jmp [rip + disp]
+                code[9] = 0xff;
+                code[10] = 0x25;
+                {
+                    const resolv = self.symbol_resolver.get("dyld_stub_binder") orelse unreachable;
+                    const got_index = self.got_entries_map.get(.{
+                        .where = .import,
+                        .where_index = resolv.where_index,
+                    }) orelse unreachable;
+                    const addr = got.addr + got_index * @sizeOf(u64);
+                    const displacement = try math.cast(u32, addr - stub_helper.addr - code_size);
+                    mem.writeIntLittle(u32, code[11..], displacement);
+                }
+                try self.base.file.?.pwriteAll(&code, stub_helper.offset);
+                break :blk stub_helper.offset + code_size;
+            },
+            .aarch64 => {
+                var code: [6 * @sizeOf(u32)]u8 = undefined;
+                data_blk_outer: {
+                    const this_addr = stub_helper.addr;
+                    const target_addr = data.addr + data.size - @sizeOf(u64);
+                    data_blk: {
+                        const displacement = math.cast(i21, target_addr - this_addr) catch break :data_blk;
+                        // adr x17, disp
+                        mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32());
+                        // nop
+                        mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32());
+                        break :data_blk_outer;
+                    }
+                    data_blk: {
+                        const new_this_addr = this_addr + @sizeOf(u32);
+                        const displacement = math.cast(i21, target_addr - new_this_addr) catch break :data_blk;
+                        // nop
+                        mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32());
+                        // adr x17, disp
+                        mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32());
+                        break :data_blk_outer;
+                    }
+                    // Jump is too big, replace adr with adrp and add.
+                    const this_page = @intCast(i32, this_addr >> 12);
+                    const target_page = @intCast(i32, target_addr >> 12);
+                    const pages = @intCast(i21, target_page - this_page);
+                    mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32());
+                    const narrowed = @truncate(u12, target_addr);
+                    mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32());
+                }
+                // stp x16, x17, [sp, #-16]!
+                code[8] = 0xf0;
+                code[9] = 0x47;
+                code[10] = 0xbf;
+                code[11] = 0xa9;
+                binder_blk_outer: {
+                    const resolv = self.symbol_resolver.get("dyld_stub_binder") orelse unreachable;
+                    const got_index = self.got_entries_map.get(.{
+                        .where = .import,
+                        .where_index = resolv.where_index,
+                    }) orelse unreachable;
+                    const this_addr = stub_helper.addr + 3 * @sizeOf(u32);
+                    const target_addr = got.addr + got_index * @sizeOf(u64);
+                    binder_blk: {
+                        const displacement = math.divExact(u64, target_addr - this_addr, 4) catch break :binder_blk;
+                        const literal = math.cast(u18, displacement) catch break :binder_blk;
+                        // ldr x16, label
+                        mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{
+                            .literal = literal,
+                        }).toU32());
+                        // nop
+                        mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32());
+                        break :binder_blk_outer;
+                    }
+                    binder_blk: {
+                        const new_this_addr = this_addr + @sizeOf(u32);
+                        const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch break :binder_blk;
+                        const literal = math.cast(u18, displacement) catch break :binder_blk;
+                        // Pad with nop to please division.
+                        // nop
+                        mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32());
+                        // ldr x16, label
+                        mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{
+                            .literal = literal,
+                        }).toU32());
+                        break :binder_blk_outer;
+                    }
+                    // Use adrp followed by ldr(immediate).
+                    const this_page = @intCast(i32, this_addr >> 12);
+                    const target_page = @intCast(i32, target_addr >> 12);
+                    const pages = @intCast(i21, target_page - this_page);
+                    mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32());
+                    const narrowed = @truncate(u12, target_addr);
+                    const offset = try math.divExact(u12, narrowed, 8);
+                    mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{
+                        .register = .{
+                            .rn = .x16,
+                            .offset = aarch64.Instruction.LoadStoreOffset.imm(offset),
+                        },
+                    }).toU32());
+                }
+                // br x16
+                code[20] = 0x00;
+                code[21] = 0x02;
+                code[22] = 0x1f;
+                code[23] = 0xd6;
+                try self.base.file.?.pwriteAll(&code, stub_helper.offset);
+                break :blk stub_helper.offset + 6 * @sizeOf(u32);
+            },
+            else => unreachable,
+        }
+    };
+
+    for (self.stubs.items) |_, i| {
+        const index = @intCast(u32, i);
+        // TODO weak bound pointers
+        try self.writeLazySymbolPointer(index);
+        try self.writeStub(index);
+        try self.writeStubInStubHelper(index);
+    }
+}
+
+fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
+    const object = self.objects.items[object_id];
+
+    log.debug("resolving symbols in '{s}'", .{object.name});
+
+    for (object.symtab.items) |sym, id| {
+        const sym_id = @intCast(u32, id);
+        const sym_name = object.getString(sym.n_strx);
+
+        if (symbolIsStab(sym)) {
+            log.err("unhandled symbol type: stab", .{});
+            log.err("  symbol '{s}'", .{sym_name});
+            log.err("  first definition in '{s}'", .{object.name.?});
+            return error.UnhandledSymbolType;
+        }
+
+        if (symbolIsIndr(sym)) {
+            log.err("unhandled symbol type: indirect", .{});
+            log.err("  symbol '{s}'", .{sym_name});
+            log.err("  first definition in '{s}'", .{object.name.?});
+            return error.UnhandledSymbolType;
+        }
+
+        if (symbolIsAbs(sym)) {
+            log.err("unhandled symbol type: absolute", .{});
+            log.err("  symbol '{s}'", .{sym_name});
+            log.err("  first definition in '{s}'", .{object.name.?});
+            return error.UnhandledSymbolType;
+        }
+
+        if (symbolIsSect(sym)) {
+            // Defined symbol regardless of scope lands in the locals symbol table.
+            const n_strx = blk: {
+                if (self.symbol_resolver.get(sym_name)) |resolv| {
+                    switch (resolv.where) {
+                        .global => break :blk self.globals.items[resolv.where_index].n_strx,
+                        .tentative => break :blk self.tentatives.items[resolv.where_index].n_strx,
+                        .undef => break :blk self.undefs.items[resolv.where_index].n_strx,
+                        .import => unreachable,
+                    }
+                }
+                break :blk try self.makeString(sym_name);
+            };
+            const local_sym_index = @intCast(u32, self.locals.items.len);
+            try self.locals.append(self.base.allocator, .{
+                .n_strx = n_strx,
+                .n_type = macho.N_SECT,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = sym.n_value,
+            });
+            try object.symbol_mapping.putNoClobber(self.base.allocator, sym_id, local_sym_index);
+
+            // If the symbol's scope is not local aka translation unit, then we need work out
+            // if we should save the symbol as a global, or potentially flag the error.
+            if (!symbolIsExt(sym)) continue;
+
+            const local = self.locals.items[local_sym_index];
+            const resolv = self.symbol_resolver.getPtr(sym_name) orelse {
+                const global_sym_index = @intCast(u32, self.globals.items.len);
+                try self.globals.append(self.base.allocator, .{
+                    .n_strx = n_strx,
+                    .n_type = sym.n_type,
+                    .n_sect = 0,
+                    .n_desc = sym.n_desc,
+                    .n_value = sym.n_value,
+                });
+                try self.symbol_resolver.putNoClobber(self.base.allocator, try self.base.allocator.dupe(u8, sym_name), .{
+                    .where = .global,
+                    .where_index = global_sym_index,
+                    .local_sym_index = local_sym_index,
+                    .file = object_id,
+                });
+                continue;
+            };
+
+            switch (resolv.where) {
+                .import => unreachable,
+                .global => {
+                    const global = &self.globals.items[resolv.where_index];
+
+                    if (!(symbolIsWeakDef(sym) or symbolIsPext(sym)) and
+                        !(symbolIsWeakDef(global.*) or symbolIsPext(global.*)))
+                    {
+                        log.err("symbol '{s}' defined multiple times", .{sym_name});
+                        log.err("  first definition in '{s}'", .{self.objects.items[resolv.file].name.?});
+                        log.err("  next definition in '{s}'", .{object.name.?});
+                        return error.MultipleSymbolDefinitions;
+                    }
+
+                    if (symbolIsWeakDef(sym) or symbolIsPext(sym)) continue; // Current symbol is weak, so skip it.
+
+                    // Otherwise, update the resolver and the global symbol.
+                    global.n_type = sym.n_type;
+                    resolv.local_sym_index = local_sym_index;
+                    resolv.file = object_id;
+
+                    continue;
+                },
+                .undef => {
+                    const undef = &self.undefs.items[resolv.where_index];
+                    undef.* = .{
+                        .n_strx = 0,
+                        .n_type = macho.N_UNDF,
+                        .n_sect = 0,
+                        .n_desc = 0,
+                        .n_value = 0,
+                    };
+                },
+                .tentative => {
+                    const tentative = &self.tentatives.items[resolv.where_index];
+                    tentative.* = .{
+                        .n_strx = 0,
+                        .n_type = macho.N_UNDF,
+                        .n_sect = 0,
+                        .n_desc = 0,
+                        .n_value = 0,
+                    };
+                },
+            }
+
+            const global_sym_index = @intCast(u32, self.globals.items.len);
+            try self.globals.append(self.base.allocator, .{
+                .n_strx = local.n_strx,
+                .n_type = sym.n_type,
+                .n_sect = 0,
+                .n_desc = sym.n_desc,
+                .n_value = sym.n_value,
+            });
+            resolv.* = .{
+                .where = .global,
+                .where_index = global_sym_index,
+                .local_sym_index = local_sym_index,
+                .file = object_id,
+            };
+        } else if (symbolIsTentative(sym)) {
+            // Symbol is a tentative definition.
+            const resolv = self.symbol_resolver.getPtr(sym_name) orelse {
+                const tent_sym_index = @intCast(u32, self.tentatives.items.len);
+                try self.tentatives.append(self.base.allocator, .{
+                    .n_strx = try self.makeString(sym_name),
+                    .n_type = sym.n_type,
+                    .n_sect = 0,
+                    .n_desc = sym.n_desc,
+                    .n_value = sym.n_value,
+                });
+                try self.symbol_resolver.putNoClobber(self.base.allocator, try self.base.allocator.dupe(u8, sym_name), .{
+                    .where = .tentative,
+                    .where_index = tent_sym_index,
+                    .file = object_id,
+                });
+                continue;
+            };
+
+            switch (resolv.where) {
+                .import => unreachable,
+                .global => {},
+                .undef => {
+                    const undef = &self.undefs.items[resolv.where_index];
+                    const tent_sym_index = @intCast(u32, self.tentatives.items.len);
+                    try self.tentatives.append(self.base.allocator, .{
+                        .n_strx = undef.n_strx,
+                        .n_type = sym.n_type,
+                        .n_sect = 0,
+                        .n_desc = sym.n_desc,
+                        .n_value = sym.n_value,
+                    });
+                    resolv.* = .{
+                        .where = .tentative,
+                        .where_index = tent_sym_index,
+                        .file = object_id,
+                    };
+                    undef.* = .{
+                        .n_strx = 0,
+                        .n_type = macho.N_UNDF,
+                        .n_sect = 0,
+                        .n_desc = 0,
+                        .n_value = 0,
+                    };
+                },
+                .tentative => {
+                    const tentative = &self.tentatives.items[resolv.where_index];
+                    if (tentative.n_value >= sym.n_value) continue;
+
+                    tentative.n_desc = sym.n_desc;
+                    tentative.n_value = sym.n_value;
+                    resolv.file = object_id;
+                },
+            }
+        } else {
+            // Symbol is undefined.
+            if (self.symbol_resolver.contains(sym_name)) continue;
+
+            const undef_sym_index = @intCast(u32, self.undefs.items.len);
+            try self.undefs.append(self.base.allocator, .{
+                .n_strx = try self.makeString(sym_name),
+                .n_type = macho.N_UNDF,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = 0,
+            });
+            try self.symbol_resolver.putNoClobber(self.base.allocator, try self.base.allocator.dupe(u8, sym_name), .{
+                .where = .undef,
+                .where_index = undef_sym_index,
+                .file = object_id,
+            });
+        }
+    }
+}
+
+fn resolveSymbols(self: *MachO) !void {
+    // TODO mimicking insertion of null symbol from incremental linker.
+    // This will need to moved.
+    try self.locals.append(self.base.allocator, .{
+        .n_strx = 0,
+        .n_type = macho.N_UNDF,
+        .n_sect = 0,
+        .n_desc = 0,
+        .n_value = 0,
+    });
+    try self.strtab.append(self.base.allocator, 0);
+
+    // First pass, resolve symbols in provided objects.
+    for (self.objects.items) |_, object_id| {
+        try self.resolveSymbolsInObject(@intCast(u16, object_id));
+    }
+
+    // Second pass, resolve symbols in static libraries.
+    var next_sym: usize = 0;
+    loop: while (true) : (next_sym += 1) {
+        if (next_sym == self.undefs.items.len) break;
+
+        const sym = self.undefs.items[next_sym];
+        if (symbolIsNull(sym)) continue;
+
+        const sym_name = self.getString(sym.n_strx);
+
+        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 = try archive.parseObject(offsets.items[0]);
+            const object_id = @intCast(u16, self.objects.items.len);
+            try self.objects.append(self.base.allocator, object);
+            try self.resolveSymbolsInObject(object_id);
+
+            continue :loop;
+        }
+    }
+
+    // Convert any tentative definition into a regular symbol and allocate
+    // text blocks for each tentative defintion.
+    for (self.tentatives.items) |sym| {
+        if (symbolIsNull(sym)) continue;
+
+        const sym_name = self.getString(sym.n_strx);
+        const match: MatchingSection = blk: {
+            if (self.common_section_index == null) {
+                const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+                self.common_section_index = @intCast(u16, data_seg.sections.items.len);
+                try data_seg.addSection(self.base.allocator, "__common", .{
+                    .flags = macho.S_ZEROFILL,
+                });
+            }
+            break :blk .{
+                .seg = self.data_segment_cmd_index.?,
+                .sect = self.common_section_index.?,
+            };
+        };
+
+        const size = sym.n_value;
+        const code = try self.base.allocator.alloc(u8, size);
+        mem.set(u8, code, 0);
+        const alignment = (sym.n_desc >> 8) & 0x0f;
+
+        const resolv = self.symbol_resolver.getPtr(sym_name) orelse unreachable;
+        const local_sym_index = @intCast(u32, self.locals.items.len);
+        var nlist = macho.nlist_64{
+            .n_strx = sym.n_strx,
+            .n_type = macho.N_SECT,
+            .n_sect = self.sectionId(match),
+            .n_desc = 0,
+            .n_value = 0,
+        };
+        try self.locals.append(self.base.allocator, nlist);
+        const global_sym_index = @intCast(u32, self.globals.items.len);
+        nlist.n_type |= macho.N_EXT;
+        try self.globals.append(self.base.allocator, nlist);
+        resolv.* = .{
+            .where = .global,
+            .where_index = global_sym_index,
+            .local_sym_index = local_sym_index,
+        };
+
+        const block = try self.base.allocator.create(TextBlock);
+        errdefer self.base.allocator.destroy(block);
+
+        block.* = TextBlock.empty;
+        block.local_sym_index = local_sym_index;
+        block.code = code;
+        block.size = size;
+        block.alignment = alignment;
+
+        // Update target section's metadata
+        // TODO should we update segment's size here too?
+        // How does it tie with incremental space allocs?
+        const tseg = &self.load_commands.items[match.seg].Segment;
+        const tsect = &tseg.sections.items[match.sect];
+        const new_alignment = math.max(tsect.@"align", block.alignment);
+        const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
+        const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
+        tsect.size = new_size;
+        tsect.@"align" = new_alignment;
+
+        if (self.blocks.getPtr(match)) |last| {
+            last.*.next = block;
+            block.prev = last.*;
+            last.* = block;
+        } else {
+            try self.blocks.putNoClobber(self.base.allocator, match, block);
+        }
+    }
+
+    // Third pass, resolve symbols in dynamic libraries.
+    {
+        // Put dyld_stub_binder as an undefined special symbol.
+        const undef_sym_index = @intCast(u32, self.undefs.items.len);
+        try self.undefs.append(self.base.allocator, .{
+            .n_strx = try self.makeString("dyld_stub_binder"),
+            .n_type = macho.N_UNDF,
+            .n_sect = 0,
+            .n_desc = 0,
+            .n_value = 0,
+        });
+        try self.symbol_resolver.putNoClobber(self.base.allocator, try self.base.allocator.dupe(u8, "dyld_stub_binder"), .{
+            .where = .undef,
+            .where_index = undef_sym_index,
+        });
+    }
+
+    var referenced = std.AutoHashMap(*Dylib, void).init(self.base.allocator);
+    defer referenced.deinit();
+
+    loop: for (self.undefs.items) |sym| {
+        if (symbolIsNull(sym)) continue;
+
+        const sym_name = self.getString(sym.n_strx);
+        for (self.dylibs.items) |dylib| {
+            if (!dylib.symbols.contains(sym_name)) continue;
+
+            if (!referenced.contains(dylib)) {
+                // Add LC_LOAD_DYLIB load command for each referenced dylib/stub.
+                dylib.ordinal = self.next_dylib_ordinal;
+                const dylib_id = dylib.id orelse unreachable;
+                var dylib_cmd = try commands.createLoadDylibCommand(
+                    self.base.allocator,
+                    dylib_id.name,
+                    dylib_id.timestamp,
+                    dylib_id.current_version,
+                    dylib_id.compatibility_version,
+                );
+                errdefer dylib_cmd.deinit(self.base.allocator);
+                try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd });
+                self.next_dylib_ordinal += 1;
+                try referenced.putNoClobber(dylib, {});
+            }
+
+            const resolv = self.symbol_resolver.getPtr(sym_name) orelse unreachable;
+            const undef = &self.undefs.items[resolv.where_index];
+            const import_sym_index = @intCast(u32, self.imports.items.len);
+            try self.imports.append(self.base.allocator, .{
+                .n_strx = undef.n_strx,
+                .n_type = macho.N_UNDF | macho.N_EXT,
+                .n_sect = 0,
+                .n_desc = packDylibOrdinal(dylib.ordinal.?),
+                .n_value = 0,
+            });
+            resolv.* = .{
+                .where = .import,
+                .where_index = import_sym_index,
+            };
+            undef.* = .{
+                .n_strx = 0,
+                .n_type = macho.N_UNDF,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = 0,
+            };
+
+            continue :loop;
+        }
+    }
+
+    // Fourth pass, handle synthetic symbols and flag any undefined references.
+    if (self.symbol_resolver.getPtr("___dso_handle")) |resolv| blk: {
+        if (resolv.where != .undef) break :blk;
+
+        const undef = &self.undefs.items[resolv.where_index];
+        const match: MatchingSection = .{
+            .seg = self.text_segment_cmd_index.?,
+            .sect = self.text_section_index.?,
+        };
+        const local_sym_index = @intCast(u32, self.locals.items.len);
+        var nlist = macho.nlist_64{
+            .n_strx = undef.n_strx,
+            .n_type = macho.N_SECT,
+            .n_sect = self.sectionId(match),
+            .n_desc = 0,
+            .n_value = 0,
+        };
+        try self.locals.append(self.base.allocator, nlist);
+        const global_sym_index = @intCast(u32, self.globals.items.len);
+        nlist.n_type |= macho.N_EXT;
+        nlist.n_desc = macho.N_WEAK_DEF;
+        try self.globals.append(self.base.allocator, nlist);
+
+        undef.* = .{
+            .n_strx = 0,
+            .n_type = macho.N_UNDF,
+            .n_sect = 0,
+            .n_desc = 0,
+            .n_value = 0,
+        };
+        resolv.* = .{
+            .where = .global,
+            .where_index = global_sym_index,
+            .local_sym_index = local_sym_index,
+        };
+
+        // We create an empty atom for this symbol.
+        // TODO perhaps we should special-case special symbols? Create a separate
+        // linked list of atoms?
+        const block = try self.base.allocator.create(TextBlock);
+        errdefer self.base.allocator.destroy(block);
+
+        block.* = TextBlock.empty;
+        block.local_sym_index = local_sym_index;
+        block.code = try self.base.allocator.alloc(u8, 0);
+        block.size = 0;
+        block.alignment = 0;
+
+        if (self.blocks.getPtr(match)) |last| {
+            last.*.next = block;
+            block.prev = last.*;
+            last.* = block;
+        } else {
+            try self.blocks.putNoClobber(self.base.allocator, match, block);
+        }
+    }
+
+    var has_undefined = false;
+    for (self.undefs.items) |sym| {
+        if (symbolIsNull(sym)) continue;
+
+        const sym_name = self.getString(sym.n_strx);
+        const resolv = self.symbol_resolver.get(sym_name) orelse unreachable;
+
+        log.err("undefined reference to symbol '{s}'", .{sym_name});
+        log.err("  first referenced in '{s}'", .{self.objects.items[resolv.file].name.?});
+        has_undefined = true;
+    }
+
+    if (has_undefined) return error.UndefinedSymbolReference;
+}
+
+fn parseTextBlocks(self: *MachO) !void {
+    for (self.objects.items) |object| {
+        try object.parseTextBlocks(self);
+    }
+}
+
+fn populateMetadata(self: *MachO) !void {
+    if (self.pagezero_segment_cmd_index == null) {
+        self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Segment = SegmentCommand.empty("__PAGEZERO", .{
+                .vmsize = 0x100000000, // size always set to 4GB
+            }),
+        });
+    }
+
+    if (self.text_segment_cmd_index == null) {
+        self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Segment = SegmentCommand.empty("__TEXT", .{
+                .vmaddr = 0x100000000, // always starts at 4GB
+                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
+                .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
+            }),
+        });
+    }
+
+    if (self.text_section_index == null) {
+        const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+        self.text_section_index = @intCast(u16, text_seg.sections.items.len);
+        const alignment: u2 = switch (self.base.options.target.cpu.arch) {
+            .x86_64 => 0,
+            .aarch64 => 2,
+            else => unreachable, // unhandled architecture type
+        };
+        try text_seg.addSection(self.base.allocator, "__text", .{
+            .@"align" = alignment,
+            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+        });
+    }
+
+    if (self.stubs_section_index == null) {
+        const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+        self.stubs_section_index = @intCast(u16, text_seg.sections.items.len);
+        const alignment: u2 = switch (self.base.options.target.cpu.arch) {
+            .x86_64 => 0,
+            .aarch64 => 2,
+            else => unreachable, // unhandled architecture type
+        };
+        const stub_size: u4 = switch (self.base.options.target.cpu.arch) {
+            .x86_64 => 6,
+            .aarch64 => 3 * @sizeOf(u32),
+            else => unreachable, // unhandled architecture type
+        };
+        try text_seg.addSection(self.base.allocator, "__stubs", .{
+            .@"align" = alignment,
+            .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+            .reserved2 = stub_size,
+        });
+    }
+
+    if (self.stub_helper_section_index == null) {
+        const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+        self.stub_helper_section_index = @intCast(u16, text_seg.sections.items.len);
+        const alignment: u2 = switch (self.base.options.target.cpu.arch) {
+            .x86_64 => 0,
+            .aarch64 => 2,
+            else => unreachable, // unhandled architecture type
+        };
+        const stub_helper_size: u6 = switch (self.base.options.target.cpu.arch) {
+            .x86_64 => 15,
+            .aarch64 => 6 * @sizeOf(u32),
+            else => unreachable,
+        };
+        try text_seg.addSection(self.base.allocator, "__stub_helper", .{
+            .size = stub_helper_size,
+            .@"align" = alignment,
+            .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+        });
+    }
+
+    if (self.data_const_segment_cmd_index == null) {
+        self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Segment = SegmentCommand.empty("__DATA_CONST", .{
+                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+            }),
+        });
+    }
+
+    if (self.got_section_index == null) {
+        const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+        self.got_section_index = @intCast(u16, data_const_seg.sections.items.len);
+        try data_const_seg.addSection(self.base.allocator, "__got", .{
+            .@"align" = 3, // 2^3 = @sizeOf(u64)
+            .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
+        });
+    }
+
+    if (self.data_segment_cmd_index == null) {
+        self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Segment = SegmentCommand.empty("__DATA", .{
+                .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+            }),
+        });
+    }
+
+    if (self.la_symbol_ptr_section_index == null) {
+        const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len);
+        try data_seg.addSection(self.base.allocator, "__la_symbol_ptr", .{
+            .@"align" = 3, // 2^3 = @sizeOf(u64)
+            .flags = macho.S_LAZY_SYMBOL_POINTERS,
+        });
+    }
+
+    if (self.data_section_index == null) {
+        const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        self.data_section_index = @intCast(u16, data_seg.sections.items.len);
+        try data_seg.addSection(self.base.allocator, "__data", .{
+            .@"align" = 3, // 2^3 = @sizeOf(u64)
+        });
+    }
+
+    if (self.linkedit_segment_cmd_index == null) {
+        self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Segment = SegmentCommand.empty("__LINKEDIT", .{
+                .maxprot = macho.VM_PROT_READ,
+                .initprot = macho.VM_PROT_READ,
+            }),
+        });
+    }
+
+    if (self.dyld_info_cmd_index == null) {
+        self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .DyldInfoOnly = .{
+                .cmd = macho.LC_DYLD_INFO_ONLY,
+                .cmdsize = @sizeOf(macho.dyld_info_command),
+                .rebase_off = 0,
+                .rebase_size = 0,
+                .bind_off = 0,
+                .bind_size = 0,
+                .weak_bind_off = 0,
+                .weak_bind_size = 0,
+                .lazy_bind_off = 0,
+                .lazy_bind_size = 0,
+                .export_off = 0,
+                .export_size = 0,
+            },
+        });
+    }
+
+    if (self.symtab_cmd_index == null) {
+        self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Symtab = .{
+                .cmd = macho.LC_SYMTAB,
+                .cmdsize = @sizeOf(macho.symtab_command),
+                .symoff = 0,
+                .nsyms = 0,
+                .stroff = 0,
+                .strsize = 0,
+            },
+        });
+    }
+
+    if (self.dysymtab_cmd_index == null) {
+        self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Dysymtab = .{
+                .cmd = macho.LC_DYSYMTAB,
+                .cmdsize = @sizeOf(macho.dysymtab_command),
+                .ilocalsym = 0,
+                .nlocalsym = 0,
+                .iextdefsym = 0,
+                .nextdefsym = 0,
+                .iundefsym = 0,
+                .nundefsym = 0,
+                .tocoff = 0,
+                .ntoc = 0,
+                .modtaboff = 0,
+                .nmodtab = 0,
+                .extrefsymoff = 0,
+                .nextrefsyms = 0,
+                .indirectsymoff = 0,
+                .nindirectsyms = 0,
+                .extreloff = 0,
+                .nextrel = 0,
+                .locreloff = 0,
+                .nlocrel = 0,
+            },
+        });
+    }
+
+    if (self.dylinker_cmd_index == null) {
+        self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len);
+        const cmdsize = @intCast(u32, mem.alignForwardGeneric(
+            u64,
+            @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH),
+            @sizeOf(u64),
+        ));
+        var dylinker_cmd = commands.emptyGenericCommandWithData(macho.dylinker_command{
+            .cmd = macho.LC_LOAD_DYLINKER,
+            .cmdsize = cmdsize,
+            .name = @sizeOf(macho.dylinker_command),
+        });
+        dylinker_cmd.data = try self.base.allocator.alloc(u8, cmdsize - dylinker_cmd.inner.name);
+        mem.set(u8, dylinker_cmd.data, 0);
+        mem.copy(u8, dylinker_cmd.data, mem.spanZ(DEFAULT_DYLD_PATH));
+        try self.load_commands.append(self.base.allocator, .{ .Dylinker = dylinker_cmd });
+    }
+
+    if (self.main_cmd_index == null and self.base.options.output_mode == .Exe) {
+        self.main_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .Main = .{
+                .cmd = macho.LC_MAIN,
+                .cmdsize = @sizeOf(macho.entry_point_command),
+                .entryoff = 0x0,
+                .stacksize = 0,
+            },
+        });
+    }
+
+    if (self.dylib_id_cmd_index == null and self.base.options.output_mode == .Lib) {
+        self.dylib_id_cmd_index = @intCast(u16, self.load_commands.items.len);
+        const install_name = try std.fmt.allocPrint(self.base.allocator, "@rpath/{s}", .{
+            self.base.options.emit.?.sub_path,
+        });
+        defer self.base.allocator.free(install_name);
+        var dylib_cmd = try commands.createLoadDylibCommand(
+            self.base.allocator,
+            install_name,
+            2,
+            0x10000, // TODO forward user-provided versions
+            0x10000,
+        );
+        errdefer dylib_cmd.deinit(self.base.allocator);
+        dylib_cmd.inner.cmd = macho.LC_ID_DYLIB;
+        try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd });
+    }
+
+    if (self.version_min_cmd_index == null) {
+        self.version_min_cmd_index = @intCast(u16, self.load_commands.items.len);
+        const cmd: u32 = switch (self.base.options.target.os.tag) {
+            .macos => macho.LC_VERSION_MIN_MACOSX,
+            .ios => macho.LC_VERSION_MIN_IPHONEOS,
+            .tvos => macho.LC_VERSION_MIN_TVOS,
+            .watchos => macho.LC_VERSION_MIN_WATCHOS,
+            else => unreachable, // wrong OS
+        };
+        const ver = self.base.options.target.os.version_range.semver.min;
+        const version = ver.major << 16 | ver.minor << 8 | ver.patch;
+        try self.load_commands.append(self.base.allocator, .{
+            .VersionMin = .{
+                .cmd = cmd,
+                .cmdsize = @sizeOf(macho.version_min_command),
+                .version = version,
+                .sdk = version,
+            },
+        });
+    }
+
+    if (self.source_version_cmd_index == null) {
+        self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .SourceVersion = .{
+                .cmd = macho.LC_SOURCE_VERSION,
+                .cmdsize = @sizeOf(macho.source_version_command),
+                .version = 0x0,
+            },
+        });
+    }
+
+    if (self.uuid_cmd_index == null) {
+        self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len);
+        var uuid_cmd: macho.uuid_command = .{
+            .cmd = macho.LC_UUID,
+            .cmdsize = @sizeOf(macho.uuid_command),
+            .uuid = undefined,
+        };
+        std.crypto.random.bytes(&uuid_cmd.uuid);
+        try self.load_commands.append(self.base.allocator, .{ .Uuid = uuid_cmd });
+    }
+}
+
+fn addDataInCodeLC(self: *MachO) !void {
+    if (self.data_in_code_cmd_index == null) {
+        self.data_in_code_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .LinkeditData = .{
+                .cmd = macho.LC_DATA_IN_CODE,
+                .cmdsize = @sizeOf(macho.linkedit_data_command),
+                .dataoff = 0,
+                .datasize = 0,
+            },
+        });
+    }
+}
+
+fn addCodeSignatureLC(self: *MachO) !void {
+    if (self.code_signature_cmd_index == null and self.base.options.target.cpu.arch == .aarch64) {
+        self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
+        try self.load_commands.append(self.base.allocator, .{
+            .LinkeditData = .{
+                .cmd = macho.LC_CODE_SIGNATURE,
+                .cmdsize = @sizeOf(macho.linkedit_data_command),
+                .dataoff = 0,
+                .datasize = 0,
+            },
+        });
+    }
+}
+
+fn addRpaths(self: *MachO, rpaths: []const []const u8) !void {
+    for (rpaths) |rpath| {
+        const cmdsize = @intCast(u32, mem.alignForwardGeneric(
+            u64,
+            @sizeOf(macho.rpath_command) + rpath.len + 1,
+            @sizeOf(u64),
+        ));
+        var rpath_cmd = commands.emptyGenericCommandWithData(macho.rpath_command{
+            .cmd = macho.LC_RPATH,
+            .cmdsize = cmdsize,
+            .path = @sizeOf(macho.rpath_command),
+        });
+        rpath_cmd.data = try self.base.allocator.alloc(u8, cmdsize - rpath_cmd.inner.path);
+        mem.set(u8, rpath_cmd.data, 0);
+        mem.copy(u8, rpath_cmd.data, rpath);
+        try self.load_commands.append(self.base.allocator, .{ .Rpath = rpath_cmd });
+    }
+}
+
+fn flushZld(self: *MachO) !void {
+    try self.writeTextBlocks();
+    try self.writeStubHelperCommon();
+
+    if (self.common_section_index) |index| {
+        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        const sect = &seg.sections.items[index];
+        sect.offset = 0;
+    }
+
+    if (self.bss_section_index) |index| {
+        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        const sect = &seg.sections.items[index];
+        sect.offset = 0;
+    }
+
+    if (self.tlv_bss_section_index) |index| {
+        const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+        const sect = &seg.sections.items[index];
+        sect.offset = 0;
+    }
+
+    try self.writeGotEntries();
+    try self.setEntryPoint();
+    try self.writeRebaseInfoTable();
+    try self.writeBindInfoTable();
+    try self.writeLazyBindInfoTable();
+    try self.writeExportInfo();
+    try self.writeDices();
+
+    {
+        const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+        const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
+        symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
+    }
+
+    try self.writeSymbolTable();
+    try self.writeStringTable();
+
+    {
+        // Seal __LINKEDIT size
+        const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+        seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size);
+    }
+
+    if (self.base.options.target.cpu.arch == .aarch64) {
+        try self.writeCodeSignaturePadding();
+    }
+
+    try self.writeLoadCommands();
+    try self.writeHeader();
+
+    if (self.base.options.target.cpu.arch == .aarch64) {
+        try self.writeCodeSignature();
+    }
+
+    // if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) {
+    //     const out_path = self.output.?.path;
+    //     try fs.cwd().copyFile(out_path, fs.cwd(), out_path, .{});
+    // }
+}
+
+fn writeGotEntries(self: *MachO) !void {
+    const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+    const sect = seg.sections.items[self.got_section_index.?];
+
+    var buffer = try self.base.allocator.alloc(u8, self.got_entries.items.len * @sizeOf(u64));
+    defer self.base.allocator.free(buffer);
+
+    var stream = std.io.fixedBufferStream(buffer);
+    var writer = stream.writer();
+
+    for (self.got_entries.items) |key| {
+        const address: u64 = switch (key.where) {
+            .local => self.locals.items[key.where_index].n_value,
+            .import => 0,
+        };
+        try writer.writeIntLittle(u64, address);
+    }
+
+    log.debug("writing GOT pointers at 0x{x} to 0x{x}", .{ sect.offset, sect.offset + buffer.len });
+
+    try self.base.file.?.pwriteAll(buffer, sect.offset);
+}
+
+fn setEntryPoint(self: *MachO) !void {
+    if (self.base.options.output_mode != .Exe) return;
+
+    // TODO we should respect the -entry flag passed in by the user to set a custom
+    // entrypoint. For now, assume default of `_main`.
+    const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const resolv = self.symbol_resolver.get("_main") orelse {
+        log.err("'_main' export not found", .{});
+        return error.MissingMainEntrypoint;
+    };
+    assert(resolv.where == .global);
+    const sym = self.globals.items[resolv.where_index];
+    const ec = &self.load_commands.items[self.main_cmd_index.?].Main;
+    ec.entryoff = @intCast(u32, sym.n_value - seg.inner.vmaddr);
+    ec.stacksize = self.base.options.stack_size_override orelse 0;
+}
+
+fn writeSymbolTable(self: *MachO) !void {
+    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+    const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
 
-        // frameworks
-        var framework_dirs = std.ArrayList([]const u8).init(arena);
-        for (self.base.options.framework_dirs) |dir| {
-            if (try resolveSearchDir(arena, dir, self.base.options.sysroot)) |search_dir| {
-                try framework_dirs.append(search_dir);
-            } else {
-                log.warn("directory not found for '-F{s}'", .{dir});
-            }
-        }
+    var locals = std.ArrayList(macho.nlist_64).init(self.base.allocator);
+    defer locals.deinit();
+    try locals.appendSlice(self.locals.items);
+
+    if (self.has_stabs) {
+        for (self.objects.items) |object| {
+            if (object.debug_info == null) continue;
+
+            // Open scope
+            try locals.ensureUnusedCapacity(4);
+            locals.appendAssumeCapacity(.{
+                .n_strx = try self.makeString(object.tu_comp_dir.?),
+                .n_type = macho.N_SO,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = 0,
+            });
+            locals.appendAssumeCapacity(.{
+                .n_strx = try self.makeString(object.tu_name.?),
+                .n_type = macho.N_SO,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = 0,
+            });
+            locals.appendAssumeCapacity(.{
+                .n_strx = try self.makeString(object.name.?),
+                .n_type = macho.N_OSO,
+                .n_sect = 0,
+                .n_desc = 1,
+                .n_value = object.mtime orelse 0,
+            });
 
-        var framework_not_found = false;
-        for (self.base.options.frameworks) |framework| {
-            for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
-                if (try resolveFramework(arena, framework_dirs.items, framework, ext)) |full_path| {
-                    try libs.append(full_path);
-                    break;
+            for (object.text_blocks.items) |block| {
+                if (block.stab) |stab| {
+                    const nlists = try stab.asNlists(block.local_sym_index, self);
+                    defer self.base.allocator.free(nlists);
+                    try locals.appendSlice(nlists);
+                } else {
+                    for (block.contained.items) |sym_at_off| {
+                        const stab = sym_at_off.stab orelse continue;
+                        const nlists = try stab.asNlists(sym_at_off.local_sym_index, self);
+                        defer self.base.allocator.free(nlists);
+                        try locals.appendSlice(nlists);
+                    }
                 }
-            } else {
-                log.warn("framework not found for '-f{s}'", .{framework});
-                framework_not_found = true;
-            }
-        }
-
-        if (framework_not_found) {
-            log.warn("Framework search paths:", .{});
-            for (framework_dirs.items) |dir| {
-                log.warn("  {s}", .{dir});
             }
-        }
 
-        // rpaths
-        var rpath_table = std.StringArrayHashMap(void).init(arena);
-        for (self.base.options.rpath_list) |rpath| {
-            if (rpath_table.contains(rpath)) continue;
-            try rpath_table.putNoClobber(rpath, {});
+            // Close scope
+            locals.appendAssumeCapacity(.{
+                .n_strx = 0,
+                .n_type = macho.N_SO,
+                .n_sect = 0,
+                .n_desc = 0,
+                .n_value = 0,
+            });
         }
+    }
 
-        var rpaths = std.ArrayList([]const u8).init(arena);
-        try rpaths.ensureCapacity(rpath_table.count());
-        for (rpath_table.keys()) |*key| {
-            rpaths.appendAssumeCapacity(key.*);
-        }
+    const nlocals = locals.items.len;
+    const nexports = self.globals.items.len;
+    const nundefs = self.imports.items.len;
 
-        const output: Zld.Output = output: {
-            if (is_dyn_lib) {
-                const install_name = try std.fmt.allocPrint(arena, "@rpath/{s}", .{
-                    self.base.options.emit.?.sub_path,
-                });
-                break :output .{
-                    .tag = .dylib,
-                    .path = full_out_path,
-                    .install_name = install_name,
-                };
-            }
-            break :output .{
-                .tag = .exe,
-                .path = full_out_path,
-            };
-        };
+    const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64);
+    const locals_size = nlocals * @sizeOf(macho.nlist_64);
+    log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off });
+    try self.base.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off);
 
-        if (self.base.options.verbose_link) {
-            var argv = std.ArrayList([]const u8).init(arena);
+    const exports_off = locals_off + locals_size;
+    const exports_size = nexports * @sizeOf(macho.nlist_64);
+    log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
+    try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), exports_off);
 
-            try argv.append("zig");
-            try argv.append("ld");
+    const undefs_off = exports_off + exports_size;
+    const undefs_size = nundefs * @sizeOf(macho.nlist_64);
+    log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off });
+    try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.imports.items), undefs_off);
 
-            if (is_exe_or_dyn_lib) {
-                try argv.append("-dynamic");
-            }
+    symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs);
+    seg.inner.filesize += locals_size + exports_size + undefs_size;
 
-            if (is_dyn_lib) {
-                try argv.append("-dylib");
+    // Update dynamic symbol table.
+    const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
+    dysymtab.nlocalsym += @intCast(u32, nlocals);
+    dysymtab.iextdefsym = dysymtab.nlocalsym;
+    dysymtab.nextdefsym = @intCast(u32, nexports);
+    dysymtab.iundefsym = dysymtab.nlocalsym + dysymtab.nextdefsym;
+    dysymtab.nundefsym = @intCast(u32, nundefs);
 
-                try argv.append("-install_name");
-                try argv.append(output.install_name.?);
-            }
+    const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const stubs = &text_segment.sections.items[self.stubs_section_index.?];
+    const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
+    const got = &data_const_segment.sections.items[self.got_section_index.?];
+    const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
+    const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?];
 
-            if (self.base.options.sysroot) |syslibroot| {
-                try argv.append("-syslibroot");
-                try argv.append(syslibroot);
-            }
+    const nstubs = @intCast(u32, self.stubs.items.len);
+    const ngot_entries = @intCast(u32, self.got_entries.items.len);
 
-            for (rpaths.items) |rpath| {
-                try argv.append("-rpath");
-                try argv.append(rpath);
-            }
+    dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
+    dysymtab.nindirectsyms = nstubs * 2 + ngot_entries;
 
-            try argv.appendSlice(positionals.items);
+    const needed_size = dysymtab.nindirectsyms * @sizeOf(u32);
+    seg.inner.filesize += needed_size;
 
-            try argv.append("-o");
-            try argv.append(output.path);
+    log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{
+        dysymtab.indirectsymoff,
+        dysymtab.indirectsymoff + needed_size,
+    });
 
-            if (native_libsystem_available) {
-                try argv.append("-lSystem");
-                try argv.append("-lc");
-            }
+    var buf = try self.base.allocator.alloc(u8, needed_size);
+    defer self.base.allocator.free(buf);
 
-            for (search_lib_names.items) |l_name| {
-                try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name}));
-            }
+    var stream = std.io.fixedBufferStream(buf);
+    var writer = stream.writer();
 
-            for (self.base.options.lib_dirs) |lib_dir| {
-                try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
-            }
+    stubs.reserved1 = 0;
+    for (self.stubs.items) |id| {
+        try writer.writeIntLittle(u32, dysymtab.iundefsym + id);
+    }
 
-            Compilation.dump_argv(argv.items);
+    got.reserved1 = nstubs;
+    for (self.got_entries.items) |entry| {
+        switch (entry.where) {
+            .import => {
+                try writer.writeIntLittle(u32, dysymtab.iundefsym + entry.where_index);
+            },
+            .local => {
+                try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL);
+            },
         }
-
-        try zld.link(positionals.items, output, .{
-            .syslibroot = self.base.options.sysroot,
-            .libs = libs.items,
-            .rpaths = rpaths.items,
-        });
     }
 
-    if (!self.base.options.disable_lld_caching) {
-        // Update the file with the digest. If it fails we can continue; it only
-        // means that the next invocation will have an unnecessary cache miss.
-        Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
-            log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)});
-        };
-        // Again failure here only means an unnecessary cache miss.
-        man.writeManifest() catch |err| {
-            log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
-        };
-        // We hang on to this lock so that the output file path can be used without
-        // other processes clobbering it.
-        self.base.lock = man.toOwnedLock();
+    la_symbol_ptr.reserved1 = got.reserved1 + ngot_entries;
+    for (self.stubs.items) |id| {
+        try writer.writeIntLittle(u32, dysymtab.iundefsym + id);
     }
+
+    try self.base.file.?.pwriteAll(buf, dysymtab.indirectsymoff);
 }
 
 pub fn deinit(self: *MachO) void {
@@ -970,6 +3014,8 @@ pub fn deinit(self: *MachO) void {
     self.stubs.deinit(self.base.allocator);
     self.stubs_map.deinit(self.base.allocator);
     self.strtab.deinit(self.base.allocator);
+    self.undefs.deinit(self.base.allocator);
+    self.tentatives.deinit(self.base.allocator);
     self.imports.deinit(self.base.allocator);
     self.globals.deinit(self.base.allocator);
     self.globals_free_list.deinit(self.base.allocator);
@@ -981,10 +3027,40 @@ pub fn deinit(self: *MachO) void {
     }
     self.symbol_resolver.deinit(self.base.allocator);
 
+    for (self.objects.items) |object| {
+        object.deinit();
+        self.base.allocator.destroy(object);
+    }
+    self.objects.deinit(self.base.allocator);
+
+    for (self.archives.items) |archive| {
+        archive.deinit();
+        self.base.allocator.destroy(archive);
+    }
+    self.archives.deinit(self.base.allocator);
+
+    for (self.dylibs.items) |dylib| {
+        dylib.deinit();
+        self.base.allocator.destroy(dylib);
+    }
+    self.dylibs.deinit(self.base.allocator);
+
     for (self.load_commands.items) |*lc| {
         lc.deinit(self.base.allocator);
     }
     self.load_commands.deinit(self.base.allocator);
+
+    // TODO dealloc all blocks
+    self.blocks.deinit(self.base.allocator);
+}
+
+pub fn closeFiles(self: MachO) void {
+    for (self.objects.items) |object| {
+        object.closeFile();
+    }
+    for (self.archives.items) |archive| {
+        archive.closeFile();
+    }
 }
 
 fn freeTextBlock(self: *MachO, text_block: *TextBlock) void {
@@ -2664,6 +4740,60 @@ fn writeIndirectSymbolTable(self: *MachO) !void {
     self.load_commands_dirty = true;
 }
 
+fn writeDices(self: *MachO) !void {
+    if (!self.has_dices) return;
+
+    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+    const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData;
+    const fileoff = seg.inner.fileoff + seg.inner.filesize;
+
+    var buf = std.ArrayList(u8).init(self.base.allocator);
+    defer buf.deinit();
+
+    var block: *TextBlock = self.blocks.get(.{
+        .seg = self.text_segment_cmd_index orelse return,
+        .sect = self.text_section_index orelse return,
+    }) orelse return;
+
+    while (block.prev) |prev| {
+        block = prev;
+    }
+
+    const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const text_sect = text_seg.sections.items[self.text_section_index.?];
+
+    while (true) {
+        if (block.dices.items.len > 0) {
+            const sym = self.locals.items[block.local_sym_index];
+            const base_off = try math.cast(u32, sym.n_value - text_sect.addr + text_sect.offset);
+
+            try buf.ensureUnusedCapacity(block.dices.items.len * @sizeOf(macho.data_in_code_entry));
+            for (block.dices.items) |dice| {
+                const rebased_dice = macho.data_in_code_entry{
+                    .offset = base_off + dice.offset,
+                    .length = dice.length,
+                    .kind = dice.kind,
+                };
+                buf.appendSliceAssumeCapacity(mem.asBytes(&rebased_dice));
+            }
+        }
+
+        if (block.next) |next| {
+            block = next;
+        } else break;
+    }
+
+    const datasize = @intCast(u32, buf.items.len);
+
+    dice_cmd.dataoff = @intCast(u32, fileoff);
+    dice_cmd.datasize = datasize;
+    seg.inner.filesize += datasize;
+
+    log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize });
+
+    try self.base.file.?.pwriteAll(buf.items, fileoff);
+}
+
 fn writeCodeSignaturePadding(self: *MachO) !void {
     // TODO figure out how not to rewrite padding every single time.
     const tracy = trace(@src());
@@ -2719,7 +4849,7 @@ fn writeCodeSignature(self: *MachO) !void {
     try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff);
 }
 
-fn writeExportTrie(self: *MachO) !void {
+fn writeExportInfo(self: *MachO) !void {
     if (!self.export_info_dirty) return;
     if (self.globals.items.len == 0) return;
 
@@ -2779,6 +4909,34 @@ fn writeRebaseInfoTable(self: *MachO) !void {
     var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator);
     defer pointers.deinit();
 
+    {
+        var it = self.blocks.iterator();
+        while (it.next()) |entry| {
+            const match = entry.key_ptr.*;
+            var block: *TextBlock = entry.value_ptr.*;
+
+            if (match.seg == self.text_segment_cmd_index.?) continue; // __TEXT is non-writable
+
+            const seg = self.load_commands.items[match.seg].Segment;
+
+            while (true) {
+                const sym = self.locals.items[block.local_sym_index];
+                const base_offset = sym.n_value - seg.inner.vmaddr;
+
+                for (block.rebases.items) |offset| {
+                    try pointers.append(.{
+                        .offset = base_offset + offset,
+                        .segment_id = match.seg,
+                    });
+                }
+
+                if (block.prev) |prev| {
+                    block = prev;
+                } else break;
+            }
+        }
+    }
+
     if (self.got_section_index) |idx| {
         const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
         const sect = seg.sections.items[idx];
@@ -2837,7 +4995,7 @@ fn writeRebaseInfoTable(self: *MachO) !void {
     self.rebase_info_dirty = false;
 }
 
-fn writeBindingInfoTable(self: *MachO) !void {
+fn writeBindInfoTable(self: *MachO) !void {
     if (!self.binding_info_dirty) return;
 
     const tracy = trace(@src());
@@ -2865,6 +5023,37 @@ fn writeBindingInfoTable(self: *MachO) !void {
         }
     }
 
+    {
+        var it = self.blocks.iterator();
+        while (it.next()) |entry| {
+            const match = entry.key_ptr.*;
+            var block: *TextBlock = entry.value_ptr.*;
+
+            if (match.seg == self.text_segment_cmd_index.?) continue; // __TEXT is non-writable
+
+            const seg = self.load_commands.items[match.seg].Segment;
+
+            while (true) {
+                const sym = self.locals.items[block.local_sym_index];
+                const base_offset = sym.n_value - seg.inner.vmaddr;
+
+                for (block.bindings.items) |binding| {
+                    const bind_sym = self.imports.items[binding.local_sym_index];
+                    try pointers.append(.{
+                        .offset = binding.offset + base_offset,
+                        .segment_id = match.seg,
+                        .dylib_ordinal = unpackDylibOrdinal(bind_sym.n_desc),
+                        .name = self.getString(bind_sym.n_strx),
+                    });
+                }
+
+                if (block.prev) |prev| {
+                    block = prev;
+                } else break;
+            }
+        }
+    }
+
     const size = try bind.bindInfoSize(pointers.items);
     var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
     defer self.base.allocator.free(buffer);
@@ -2890,7 +5079,7 @@ fn writeBindingInfoTable(self: *MachO) !void {
     self.binding_info_dirty = false;
 }
 
-fn writeLazyBindingInfoTable(self: *MachO) !void {
+fn writeLazyBindInfoTable(self: *MachO) !void {
     if (!self.lazy_binding_info_dirty) return;
 
     const tracy = trace(@src());
@@ -3134,7 +5323,7 @@ fn writeHeader(self: *MachO) !void {
         else => unreachable,
     }
 
-    if (self.hasTlvDescriptors()) {
+    if (self.tlv_section_index) |_| {
         header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
     }
 
@@ -3156,10 +5345,6 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
         std.math.maxInt(@TypeOf(actual_size));
 }
 
-fn hasTlvDescriptors(_: *MachO) bool {
-    return false;
-}
-
 pub fn makeString(self: *MachO, string: []const u8) !u32 {
     try self.strtab.ensureUnusedCapacity(self.base.allocator, string.len + 1);
     const new_off = @intCast(u32, self.strtab.items.len);
@@ -3177,6 +5362,92 @@ pub fn getString(self: *MachO, off: u32) []const u8 {
     return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + off));
 }
 
+pub fn symbolIsStab(sym: macho.nlist_64) bool {
+    return (macho.N_STAB & sym.n_type) != 0;
+}
+
+pub fn symbolIsPext(sym: macho.nlist_64) bool {
+    return (macho.N_PEXT & sym.n_type) != 0;
+}
+
+pub fn symbolIsExt(sym: macho.nlist_64) bool {
+    return (macho.N_EXT & sym.n_type) != 0;
+}
+
+pub fn symbolIsSect(sym: macho.nlist_64) bool {
+    const type_ = macho.N_TYPE & sym.n_type;
+    return type_ == macho.N_SECT;
+}
+
+pub fn symbolIsUndf(sym: macho.nlist_64) bool {
+    const type_ = macho.N_TYPE & sym.n_type;
+    return type_ == macho.N_UNDF;
+}
+
+pub fn symbolIsIndr(sym: macho.nlist_64) bool {
+    const type_ = macho.N_TYPE & sym.n_type;
+    return type_ == macho.N_INDR;
+}
+
+pub fn symbolIsAbs(sym: macho.nlist_64) bool {
+    const type_ = macho.N_TYPE & sym.n_type;
+    return type_ == macho.N_ABS;
+}
+
+pub fn symbolIsWeakDef(sym: macho.nlist_64) bool {
+    return (sym.n_desc & macho.N_WEAK_DEF) != 0;
+}
+
+pub fn symbolIsWeakRef(sym: macho.nlist_64) bool {
+    return (sym.n_desc & macho.N_WEAK_REF) != 0;
+}
+
+pub fn symbolIsTentative(sym: macho.nlist_64) bool {
+    if (!symbolIsUndf(sym)) return false;
+    return sym.n_value != 0;
+}
+
+pub fn symbolIsNull(sym: macho.nlist_64) bool {
+    return sym.n_value == 0 and sym.n_desc == 0 and sym.n_type == 0 and sym.n_strx == 0 and sym.n_sect == 0;
+}
+
+pub fn symbolIsTemp(sym: macho.nlist_64, sym_name: []const u8) bool {
+    if (!symbolIsSect(sym)) return false;
+    if (symbolIsExt(sym)) return false;
+    return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
+}
+
+pub fn sectionId(self: MachO, match: MatchingSection) u8 {
+    // TODO there might be a more generic way of doing this.
+    var section: u8 = 0;
+    for (self.load_commands.items) |cmd, cmd_id| {
+        if (cmd != .Segment) break;
+        if (cmd_id == match.seg) {
+            section += @intCast(u8, match.sect) + 1;
+            break;
+        }
+        section += @intCast(u8, cmd.Segment.sections.items.len);
+    }
+    return section;
+}
+
+pub fn unpackSectionId(self: MachO, section_id: u8) MatchingSection {
+    var match: MatchingSection = undefined;
+    var section: u8 = 0;
+    outer: for (self.load_commands.items) |cmd, cmd_id| {
+        assert(cmd == .Segment);
+        for (cmd.Segment.sections.items) |_, sect_id| {
+            section += 1;
+            if (section_id == section) {
+                match.seg = @intCast(u16, cmd_id);
+                match.sect = @intCast(u16, sect_id);
+                break :outer;
+            }
+        }
+    }
+    return match;
+}
+
 fn packDylibOrdinal(ordinal: u16) u16 {
     return ordinal * macho.N_SYMBOL_RESOLVER;
 }
@@ -3184,3 +5455,16 @@ fn packDylibOrdinal(ordinal: u16) u16 {
 fn unpackDylibOrdinal(pack: u16) u16 {
     return @divExact(pack, macho.N_SYMBOL_RESOLVER);
 }
+
+pub fn findFirst(comptime T: type, haystack: []T, start: usize, predicate: anytype) usize {
+    if (!@hasDecl(@TypeOf(predicate), "predicate"))
+        @compileError("Predicate is required to define fn predicate(@This(), T) bool");
+
+    if (start == haystack.len) return start;
+
+    var i = start;
+    while (i < haystack.len) : (i += 1) {
+        if (predicate.predicate(haystack[i])) break;
+    }
+    return i;
+}
CMakeLists.txt
@@ -583,7 +583,6 @@ set(ZIG_STAGE2_SOURCES
     "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/TextBlock.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
-    "${CMAKE_SOURCE_DIR}/src/link/MachO/Zld.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig"
     "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"