Commit 9d5a900f4b

Jakub Konka <kubkon@jakubkonka.com>
2024-07-08 08:01:31
macho: migrate Object to self-ownership of atoms and symbols
1 parent f8cea21
Changed files (2)
src
src/link/MachO/Object.zig
@@ -12,7 +12,11 @@ symtab: std.MultiArrayList(Nlist) = .{},
 strtab: std.ArrayListUnmanaged(u8) = .{},
 
 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+symbols_extra: std.ArrayListUnmanaged(u32) = .{},
+globals: std.ArrayListUnmanaged(MachO.SymbolResolver.Index) = .{},
 atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
+atoms_indexes: std.ArrayListUnmanaged(Atom.Index) = .{},
+atoms_extra: std.ArrayListUnmanaged(u32) = .{},
 
 platform: ?MachO.Platform = null,
 compile_unit: ?CompileUnit = null,
@@ -30,6 +34,7 @@ data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
 alive: bool = true,
 hidden: bool = false,
 
+compact_unwind_ctx: CompactUnwindCtx = .{},
 output_symtab_ctx: MachO.SymtabCtx = .{},
 output_ar_state: Archive.ArState = .{},
 
@@ -51,7 +56,11 @@ pub fn deinit(self: *Object, allocator: Allocator) void {
     self.symtab.deinit(allocator);
     self.strtab.deinit(allocator);
     self.symbols.deinit(allocator);
+    self.symbols_extra.deinit(allocator);
+    self.globals.deinit(allocator);
     self.atoms.deinit(allocator);
+    self.atoms_indexes.deinit(allocator);
+    self.atoms_extra.deinit(allocator);
     self.cies.deinit(allocator);
     self.fdes.deinit(allocator);
     self.eh_frame_data.deinit(allocator);
@@ -70,6 +79,10 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
 
     const gpa = macho_file.base.comp.gpa;
     const handle = macho_file.getFileHandle(self.file_handle);
+    const cpu_arch = macho_file.getTarget().cpu.arch;
+
+    // Atom at index 0 is reserved as null atom
+    try self.atoms.append(gpa, .{ .extra = try self.addAtomExtra(gpa, .{}) });
 
     var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
     {
@@ -86,7 +99,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
             return error.InvalidCpuArch;
         },
     };
-    if (macho_file.getTarget().cpu.arch != this_cpu_arch) {
+    if (cpu_arch != this_cpu_arch) {
         try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)});
         return error.InvalidCpuArch;
     }
@@ -198,20 +211,20 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
     mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);
 
     if (self.hasSubsections()) {
-        try self.initSubsections(nlists.items, macho_file);
+        try self.initSubsections(gpa, nlists.items);
     } else {
-        try self.initSections(nlists.items, macho_file);
+        try self.initSections(gpa, nlists.items);
     }
 
-    try self.initCstringLiterals(macho_file);
-    try self.initFixedSizeLiterals(macho_file);
-    try self.initPointerLiterals(macho_file);
+    try self.initCstringLiterals(gpa, handle, macho_file);
+    try self.initFixedSizeLiterals(gpa, macho_file);
+    try self.initPointerLiterals(gpa, macho_file);
     try self.linkNlistToAtom(macho_file);
 
     try self.sortAtoms(macho_file);
-    try self.initSymbols(macho_file);
-    try self.initSymbolStabs(nlists.items, macho_file);
-    try self.initRelocs(macho_file);
+    try self.initSymbols(gpa, macho_file);
+    try self.initSymbolStabs(gpa, nlists.items, macho_file);
+    try self.initRelocs(handle, cpu_arch, macho_file);
 
     // Parse DWARF __TEXT,__eh_frame section
     if (self.eh_frame_sect_index) |index| {
@@ -224,13 +237,13 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
     }
 
     if (self.hasUnwindRecords() or self.hasEhFrameRecords()) {
-        try self.parseUnwindRecords(gpa, macho_file.getTarget().cpu.arch, macho_file);
+        try self.parseUnwindRecords(gpa, cpu_arch, macho_file);
     }
 
     if (self.platform) |platform| {
         if (!macho_file.platform.eqlTarget(platform)) {
             try macho_file.reportParseError2(self.index, "invalid platform: {}", .{
-                platform.fmtTarget(macho_file.getTarget().cpu.arch),
+                platform.fmtTarget(cpu_arch),
             });
             return error.InvalidTarget;
         }
@@ -247,7 +260,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
     }
 
     for (self.atoms.items) |atom_index| {
-        const atom = macho_file.getAtom(atom_index).?;
+        const atom = self.getAtom(atom_index) orelse continue;
         const isec = atom.getInputSection(macho_file);
         if (mem.eql(u8, isec.sectName(), "__eh_frame") or
             mem.eql(u8, isec.sectName(), "__compact_unwind") or
@@ -276,10 +289,9 @@ pub fn isPtrLiteral(sect: macho.section_64) bool {
     return sect.type() == macho.S_LITERAL_POINTERS;
 }
 
-fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
+fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {
     const tracy = trace(@src());
     defer tracy.end();
-    const gpa = macho_file.base.comp.gpa;
     const slice = self.sections.slice();
     for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| {
         if (isCstringLiteral(sect)) continue;
@@ -294,17 +306,18 @@ fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
         } else nlists.len;
 
         if (nlist_start == nlist_end or nlists[nlist_start].nlist.n_value > sect.addr) {
-            const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
-            defer gpa.free(name);
+            const name = try std.fmt.allocPrintZ(allocator, "{s}${s}", .{ sect.segName(), sect.sectName() });
+            defer allocator.free(name);
             const size = if (nlist_start == nlist_end) sect.size else nlists[nlist_start].nlist.n_value - sect.addr;
-            const atom_index = try self.addAtom(.{
-                .name = try self.addString(gpa, name),
+            const atom_index = try self.addAtom(allocator, .{
+                .name = try self.addString(allocator, name),
                 .n_sect = @intCast(n_sect),
                 .off = 0,
                 .size = size,
                 .alignment = sect.@"align",
-            }, macho_file);
-            try subsections.append(gpa, .{
+            });
+            try self.atoms_indexes.append(allocator, atom_index);
+            try subsections.append(allocator, .{
                 .atom = atom_index,
                 .off = 0,
             });
@@ -327,14 +340,15 @@ fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
                 @min(@ctz(nlist.nlist.n_value), sect.@"align")
             else
                 sect.@"align";
-            const atom_index = try self.addAtom(.{
+            const atom_index = try self.addAtom(allocator, .{
                 .name = nlist.nlist.n_strx,
                 .n_sect = @intCast(n_sect),
                 .off = nlist.nlist.n_value - sect.addr,
                 .size = size,
                 .alignment = alignment,
-            }, macho_file);
-            try subsections.append(gpa, .{
+            });
+            try self.atoms_indexes.append(allocator, atom_index);
+            try subsections.append(allocator, .{
                 .atom = atom_index,
                 .off = nlist.nlist.n_value - sect.addr,
             });
@@ -346,30 +360,31 @@ fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
     }
 }
 
-fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
+fn initSections(self: *Object, allocator: Allocator, nlists: anytype) !void {
     const tracy = trace(@src());
     defer tracy.end();
-    const gpa = macho_file.base.comp.gpa;
     const slice = self.sections.slice();
 
-    try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
+    try self.atoms.ensureUnusedCapacity(allocator, self.sections.items(.header).len);
+    try self.atoms_indexes.ensureUnusedCapacity(allocator, self.sections.items(.header).len);
 
     for (slice.items(.header), 0..) |sect, n_sect| {
         if (isCstringLiteral(sect)) continue;
         if (isFixedSizeLiteral(sect)) continue;
         if (isPtrLiteral(sect)) continue;
 
-        const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
-        defer gpa.free(name);
+        const name = try std.fmt.allocPrintZ(allocator, "{s}${s}", .{ sect.segName(), sect.sectName() });
+        defer allocator.free(name);
 
-        const atom_index = try self.addAtom(.{
-            .name = try self.addString(gpa, name),
+        const atom_index = try self.addAtom(allocator, .{
+            .name = try self.addString(allocator, name),
             .n_sect = @intCast(n_sect),
             .off = 0,
             .size = sect.size,
             .alignment = sect.@"align",
-        }, macho_file);
-        try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
+        });
+        try self.atoms_indexes.append(allocator, atom_index);
+        try slice.items(.subsections)[n_sect].append(allocator, .{ .atom = atom_index, .off = 0 });
 
         const nlist_start = for (nlists, 0..) |nlist, i| {
             if (nlist.nlist.n_sect - 1 == n_sect) break i;
@@ -398,21 +413,25 @@ fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
     }
 }
 
-fn initCstringLiterals(self: *Object, macho_file: *MachO) !void {
+fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = macho_file.base.comp.gpa;
     const slice = self.sections.slice();
 
     for (slice.items(.header), 0..) |sect, n_sect| {
         if (!isCstringLiteral(sect)) continue;
 
-        const data = try self.getSectionData(@intCast(n_sect), macho_file);
-        defer gpa.free(data);
+        const sect_size = math.cast(usize, sect.size) orelse return error.Overflow;
+        const data = try allocator.alloc(u8, sect_size);
+        defer allocator.free(data);
+        const amt = try file.preadAll(data, sect.offset + self.offset);
+        if (amt != data.len) return error.InputOutput;
 
+        var count: u32 = 0;
         var start: u32 = 0;
         while (start < data.len) {
+            defer count += 1;
             var end = start;
             while (end < data.len - 1 and data[end] != 0) : (end += 1) {}
             if (data[end] != 0) {
@@ -425,32 +444,52 @@ fn initCstringLiterals(self: *Object, macho_file: *MachO) !void {
             }
             end += 1;
 
-            const atom_index = try self.addAtom(.{
-                .name = 0,
+            const name = try std.fmt.allocPrintZ(allocator, "l._str{d}", .{count});
+            defer allocator.free(name);
+            const name_str = try self.addString(allocator, name);
+
+            const atom_index = try self.addAtom(allocator, .{
+                .name = name_str,
                 .n_sect = @intCast(n_sect),
                 .off = start,
                 .size = end - start,
                 .alignment = sect.@"align",
-            }, macho_file);
-            try slice.items(.subsections)[n_sect].append(gpa, .{
+            });
+            try self.atoms_indexes.append(allocator, atom_index);
+            try slice.items(.subsections)[n_sect].append(allocator, .{
                 .atom = atom_index,
                 .off = start,
             });
 
+            const atom = self.getAtom(atom_index).?;
+            const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator));
+            self.symtab.set(nlist_index, .{
+                .nlist = .{
+                    .n_strx = name_str,
+                    .n_type = macho.N_SECT,
+                    .n_sect = @intCast(atom.n_sect + 1),
+                    .n_desc = 0,
+                    .n_value = atom.getInputAddress(macho_file),
+                },
+                .size = atom.size,
+                .atom = atom_index,
+            });
+            atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file);
+
             start = end;
         }
     }
 }
 
-fn initFixedSizeLiterals(self: *Object, macho_file: *MachO) !void {
+fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = macho_file.base.comp.gpa;
     const slice = self.sections.slice();
 
     for (slice.items(.header), 0..) |sect, n_sect| {
         if (!isFixedSizeLiteral(sect)) continue;
+
         const rec_size: u8 = switch (sect.type()) {
             macho.S_4BYTE_LITERALS => 4,
             macho.S_8BYTE_LITERALS => 8,
@@ -465,28 +504,52 @@ fn initFixedSizeLiterals(self: *Object, macho_file: *MachO) !void {
             );
             return error.MalformedObject;
         }
+
         var pos: u32 = 0;
-        while (pos < sect.size) : (pos += rec_size) {
-            const atom_index = try self.addAtom(.{
-                .name = 0,
+        var count: u32 = 0;
+        while (pos < sect.size) : ({
+            pos += rec_size;
+            count += 1;
+        }) {
+            const name = try std.fmt.allocPrintZ(allocator, "l._literal{d}", .{count});
+            defer allocator.free(name);
+            const name_str = try self.addString(allocator, name);
+
+            const atom_index = try self.addAtom(allocator, .{
+                .name = name_str,
                 .n_sect = @intCast(n_sect),
                 .off = pos,
                 .size = rec_size,
                 .alignment = sect.@"align",
-            }, macho_file);
-            try slice.items(.subsections)[n_sect].append(gpa, .{
+            });
+            try self.atoms_indexes.append(allocator, atom_index);
+            try slice.items(.subsections)[n_sect].append(allocator, .{
                 .atom = atom_index,
                 .off = pos,
             });
+
+            const atom = self.getAtom(atom_index).?;
+            const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator));
+            self.symtab.set(nlist_index, .{
+                .nlist = .{
+                    .n_strx = name_str,
+                    .n_type = macho.N_SECT,
+                    .n_sect = @intCast(atom.n_sect + 1),
+                    .n_desc = 0,
+                    .n_value = atom.getInputAddress(macho_file),
+                },
+                .size = atom.size,
+                .atom = atom_index,
+            });
+            atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file);
         }
     }
 }
 
-fn initPointerLiterals(self: *Object, macho_file: *MachO) !void {
+fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = macho_file.base.comp.gpa;
     const slice = self.sections.slice();
 
     for (slice.items(.header), 0..) |sect, n_sect| {
@@ -505,134 +568,171 @@ fn initPointerLiterals(self: *Object, macho_file: *MachO) !void {
 
         for (0..num_ptrs) |i| {
             const pos: u32 = @as(u32, @intCast(i)) * rec_size;
-            const atom_index = try self.addAtom(.{
-                .name = 0,
+
+            const name = try std.fmt.allocPrintZ(allocator, "l._ptr{d}", .{i});
+            defer allocator.free(name);
+            const name_str = try self.addString(allocator, name);
+
+            const atom_index = try self.addAtom(allocator, .{
+                .name = name_str,
                 .n_sect = @intCast(n_sect),
                 .off = pos,
                 .size = rec_size,
                 .alignment = sect.@"align",
-            }, macho_file);
-            try slice.items(.subsections)[n_sect].append(gpa, .{
+            });
+            try self.atoms_indexes.append(allocator, atom_index);
+            try slice.items(.subsections)[n_sect].append(allocator, .{
                 .atom = atom_index,
                 .off = pos,
             });
+
+            const atom = self.getAtom(atom_index).?;
+            const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator));
+            self.symtab.set(nlist_index, .{
+                .nlist = .{
+                    .n_strx = name_str,
+                    .n_type = macho.N_SECT,
+                    .n_sect = @intCast(atom.n_sect + 1),
+                    .n_desc = 0,
+                    .n_value = atom.getInputAddress(macho_file),
+                },
+                .size = atom.size,
+                .atom = atom_index,
+            });
+            atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file);
         }
     }
 }
 
 pub fn resolveLiterals(self: Object, lp: *MachO.LiteralPool, macho_file: *MachO) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
     const gpa = macho_file.base.comp.gpa;
+    const file = macho_file.getFileHandle(self.file_handle);
 
     var buffer = std.ArrayList(u8).init(gpa);
     defer buffer.deinit();
 
+    var sections_data = std.AutoHashMap(u32, []const u8).init(gpa);
+    try sections_data.ensureTotalCapacity(@intCast(self.sections.items(.header).len));
+    defer {
+        var it = sections_data.iterator();
+        while (it.next()) |entry| {
+            gpa.free(entry.value_ptr.*);
+        }
+        sections_data.deinit();
+    }
+
     const slice = self.sections.slice();
-    for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| {
+    for (slice.items(.header), slice.items(.subsections)) |header, subs| {
         if (isCstringLiteral(header) or isFixedSizeLiteral(header)) {
-            const data = try self.getSectionData(@intCast(n_sect), macho_file);
+            const sect_size = math.cast(usize, header.size) orelse return error.Overflow;
+            const data = try gpa.alloc(u8, sect_size);
             defer gpa.free(data);
+            const amt = try file.preadAll(data, header.offset + self.offset);
+            if (amt != data.len) return error.InputOutput;
 
             for (subs.items) |sub| {
-                const atom = macho_file.getAtom(sub.atom).?;
+                const atom = self.getAtom(sub.atom).?;
                 const atom_off = math.cast(usize, atom.off) orelse return error.Overflow;
                 const atom_size = math.cast(usize, atom.size) orelse return error.Overflow;
                 const atom_data = data[atom_off..][0..atom_size];
                 const res = try lp.insert(gpa, header.type(), atom_data);
                 if (!res.found_existing) {
-                    res.atom.* = sub.atom;
+                    res.ref.* = .{ .index = atom.getExtra(macho_file).literal_symbol_index, .file = self.index };
+                } else {
+                    const lp_sym = lp.getSymbol(res.index, macho_file);
+                    const lp_atom = lp_sym.getAtom(macho_file).?;
+                    lp_atom.alignment = @max(lp_atom.alignment, atom.alignment);
+                    atom.flags.alive = false;
                 }
-                atom.flags.literal_pool = true;
-                try atom.addExtra(.{ .literal_index = res.index }, macho_file);
+                atom.addExtra(.{ .literal_pool_index = res.index }, macho_file);
             }
         } else if (isPtrLiteral(header)) {
             for (subs.items) |sub| {
-                const atom = macho_file.getAtom(sub.atom).?;
+                const atom = self.getAtom(sub.atom).?;
                 const relocs = atom.getRelocs(macho_file);
                 assert(relocs.len == 1);
                 const rel = relocs[0];
                 const target = switch (rel.tag) {
-                    .local => rel.target,
-                    .@"extern" => rel.getTargetSymbol(macho_file).atom,
+                    .local => rel.getTargetAtom(atom.*, macho_file),
+                    .@"extern" => rel.getTargetSymbol(atom.*, macho_file).getAtom(macho_file).?,
                 };
                 const addend = math.cast(u32, rel.addend) orelse return error.Overflow;
-                const target_atom = macho_file.getAtom(target).?;
-                const target_atom_size = math.cast(usize, target_atom.size) orelse return error.Overflow;
-                try buffer.ensureUnusedCapacity(target_atom_size);
-                buffer.resize(target_atom_size) catch unreachable;
-                try target_atom.getData(macho_file, buffer.items);
+                const target_size = math.cast(usize, target.size) orelse return error.Overflow;
+                try buffer.ensureUnusedCapacity(target_size);
+                buffer.resize(target_size) catch unreachable;
+                const gop = try sections_data.getOrPut(target.n_sect);
+                if (!gop.found_existing) {
+                    const target_sect = slice.items(.header)[target.n_sect];
+                    const target_sect_size = math.cast(usize, target_sect.size) orelse return error.Overflow;
+                    const data = try gpa.alloc(u8, target_sect_size);
+                    const amt = try file.preadAll(data, target_sect.offset + self.offset);
+                    if (amt != data.len) return error.InputOutput;
+                    gop.value_ptr.* = data;
+                }
+                const data = gop.value_ptr.*;
+                const target_off = math.cast(usize, target.off) orelse return error.Overflow;
+                @memcpy(buffer.items, data[target_off..][0..target_size]);
                 const res = try lp.insert(gpa, header.type(), buffer.items[addend..]);
                 buffer.clearRetainingCapacity();
                 if (!res.found_existing) {
-                    res.atom.* = sub.atom;
+                    res.ref.* = .{ .index = atom.getExtra(macho_file).literal_symbol_index, .file = self.index };
+                } else {
+                    const lp_sym = lp.getSymbol(res.index, macho_file);
+                    const lp_atom = lp_sym.getAtom(macho_file).?;
+                    lp_atom.alignment = @max(lp_atom.alignment, atom.alignment);
+                    atom.flags.alive = false;
                 }
-                atom.flags.literal_pool = true;
-                try atom.addExtra(.{ .literal_index = res.index }, macho_file);
+                atom.addExtra(.{ .literal_pool_index = res.index }, macho_file);
             }
         }
     }
 }
 
-pub fn dedupLiterals(self: Object, lp: MachO.LiteralPool, macho_file: *MachO) void {
-    for (self.atoms.items) |atom_index| {
-        const atom = macho_file.getAtom(atom_index) orelse continue;
+pub fn dedupLiterals(self: *Object, lp: MachO.LiteralPool, macho_file: *MachO) void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    for (self.getAtoms()) |atom_index| {
+        const atom = self.getAtom(atom_index) orelse continue;
         if (!atom.flags.alive) continue;
-        if (!atom.flags.relocs) continue;
 
         const relocs = blk: {
-            const extra = atom.getExtra(macho_file).?;
+            const extra = atom.getExtra(macho_file);
             const relocs = self.sections.items(.relocs)[atom.n_sect].items;
             break :blk relocs[extra.rel_index..][0..extra.rel_count];
         };
-        for (relocs) |*rel| switch (rel.tag) {
-            .local => {
-                const target = macho_file.getAtom(rel.target).?;
-                if (target.getLiteralPoolIndex(macho_file)) |lp_index| {
-                    const lp_atom = lp.getAtom(lp_index, macho_file);
-                    if (target.atom_index != lp_atom.atom_index) {
-                        lp_atom.alignment = lp_atom.alignment.max(target.alignment);
-                        target.flags.alive = false;
-                        rel.target = lp_atom.atom_index;
-                    }
-                }
-            },
-            .@"extern" => {
-                const target_sym = rel.getTargetSymbol(macho_file);
-                if (target_sym.getAtom(macho_file)) |target_atom| {
-                    if (target_atom.getLiteralPoolIndex(macho_file)) |lp_index| {
-                        const lp_atom = lp.getAtom(lp_index, macho_file);
-                        if (target_atom.atom_index != lp_atom.atom_index) {
-                            lp_atom.alignment = lp_atom.alignment.max(target_atom.alignment);
-                            target_atom.flags.alive = false;
-                            target_sym.atom = lp_atom.atom_index;
-                        }
-                    }
-                }
-            },
-        };
+        for (relocs) |*rel| {
+            if (rel.tag != .@"extern") continue;
+            const target_sym_ref = rel.getTargetSymbolRef(atom.*, macho_file);
+            const file = target_sym_ref.getFile(macho_file) orelse continue;
+            if (file.getIndex() != self.index) continue;
+            const target_sym = target_sym_ref.getSymbol(macho_file).?;
+            const target_atom = target_sym.getAtom(macho_file) orelse continue;
+            const isec = target_atom.getInputSection(macho_file);
+            if (!Object.isCstringLiteral(isec) and !Object.isFixedSizeLiteral(isec) and !Object.isPtrLiteral(isec)) continue;
+            const lp_index = target_atom.getExtra(macho_file).literal_pool_index;
+            const lp_sym = lp.getSymbol(lp_index, macho_file);
+            const lp_atom_ref = lp_sym.atom_ref;
+            if (target_atom.atom_index != lp_atom_ref.index or target_atom.file != lp_atom_ref.file) {
+                target_sym.atom_ref = lp_atom_ref;
+            }
+        }
     }
-}
-
-const AddAtomArgs = struct {
-    name: u32,
-    n_sect: u8,
-    off: u64,
-    size: u64,
-    alignment: u32,
-};
 
-fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index {
-    const gpa = macho_file.base.comp.gpa;
-    const atom_index = try macho_file.addAtom();
-    const atom = macho_file.getAtom(atom_index).?;
-    atom.file = self.index;
-    atom.atom_index = atom_index;
-    atom.name = args.name;
-    atom.n_sect = args.n_sect;
-    atom.size = args.size;
-    atom.alignment = Atom.Alignment.fromLog2Units(args.alignment);
-    atom.off = args.off;
-    try self.atoms.append(gpa, atom_index);
-    return atom_index;
+    for (self.symbols.items) |*sym| {
+        const atom = sym.getAtom(macho_file) orelse continue;
+        const isec = atom.getInputSection(macho_file);
+        if (!Object.isCstringLiteral(isec) and !Object.isFixedSizeLiteral(isec) and !Object.isPtrLiteral(isec)) continue;
+        const lp_index = atom.getExtra(macho_file).literal_pool_index;
+        const lp_sym = lp.getSymbol(lp_index, macho_file);
+        const lp_atom_ref = lp_sym.atom_ref;
+        if (atom.atom_index != lp_atom_ref.index or self.index != lp_atom_ref.file) {
+            sym.atom_ref = lp_atom_ref;
+        }
+    }
 }
 
 pub fn findAtom(self: Object, addr: u64) ?Atom.Index {
@@ -704,55 +804,59 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {
     }
 }
 
-fn initSymbols(self: *Object, macho_file: *MachO) !void {
+fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
-    const gpa = macho_file.base.comp.gpa;
+
     const slice = self.symtab.slice();
+    const nsyms = slice.items(.nlist).len;
 
-    try self.symbols.ensureUnusedCapacity(gpa, slice.items(.nlist).len);
+    try self.symbols.ensureTotalCapacityPrecise(allocator, nsyms);
+    try self.symbols_extra.ensureTotalCapacityPrecise(allocator, nsyms * @sizeOf(Symbol.Extra));
+    try self.globals.ensureTotalCapacityPrecise(allocator, nsyms);
+    self.globals.resize(allocator, nsyms) catch unreachable;
+    @memset(self.globals.items, 0);
 
     for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| {
-        if (nlist.ext()) {
-            const name = self.getString(nlist.n_strx);
-            const off = try macho_file.strings.insert(gpa, name);
-            const gop = try macho_file.getOrCreateGlobal(off);
-            self.symbols.addOneAssumeCapacity().* = gop.index;
-            if (nlist.undf() and nlist.weakRef()) {
-                macho_file.getSymbol(gop.index).flags.weak_ref = true;
-            }
-            continue;
-        }
-
-        const index = try macho_file.addSymbol();
-        self.symbols.appendAssumeCapacity(index);
-        const symbol = macho_file.getSymbol(index);
-        symbol.* = .{
-            .value = nlist.n_value,
-            .name = nlist.n_strx,
-            .nlist_idx = @intCast(i),
-            .atom = 0,
-            .file = self.index,
-        };
-
-        if (macho_file.getAtom(atom_index)) |atom| {
+        const index = self.addSymbolAssumeCapacity();
+        const symbol = &self.symbols.items[index];
+        symbol.value = nlist.n_value;
+        symbol.name = .{ .pos = nlist.n_strx, .len = @intCast(self.getNStrx(nlist.n_strx).len + 1) };
+        symbol.nlist_idx = @intCast(i);
+        symbol.extra = self.addSymbolExtraAssumeCapacity(.{});
+
+        if (self.getAtom(atom_index)) |atom| {
             assert(!nlist.abs());
             symbol.value -= atom.getInputAddress(macho_file);
-            symbol.atom = atom_index;
+            symbol.atom_ref = .{ .index = atom_index, .file = self.index };
         }
 
+        symbol.flags.weak = nlist.weakDef();
         symbol.flags.abs = nlist.abs();
+        symbol.flags.tentative = nlist.tentative();
         symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
+        symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
+        symbol.flags.interposable = nlist.ext() and (nlist.sect() or nlist.abs()) and macho_file.options.dylib and macho_file.options.namespace == .flat and !nlist.pext();
 
         if (nlist.sect() and
             self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
         {
             symbol.flags.tlv = true;
         }
+
+        if (nlist.ext()) {
+            if (nlist.undf()) {
+                symbol.flags.weak_ref = nlist.weakRef();
+            } else if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) {
+                symbol.visibility = .hidden;
+            } else {
+                symbol.visibility = .global;
+            }
+        }
     }
 }
 
-fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void {
+fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -778,14 +882,13 @@ fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void {
 
     if (start == end) return;
 
-    const gpa = macho_file.base.comp.gpa;
     const syms = self.symtab.items(.nlist);
     const sym_lookup = SymbolLookup{ .ctx = self, .entries = nlists };
 
     // We need to cache nlists by name so that we can properly resolve local N_GSYM stabs.
     // What happens is `ld -r` will emit an N_GSYM stab for a symbol that may be either an
     // external or private external.
-    var addr_lookup = std.StringHashMap(u64).init(gpa);
+    var addr_lookup = std.StringHashMap(u64).init(allocator);
     defer addr_lookup.deinit();
     for (syms) |sym| {
         if (sym.sect() and (sym.ext() or sym.pext())) {
@@ -834,29 +937,35 @@ fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void {
                     return error.MalformedObject;
                 },
             }
-            try sf.stabs.append(gpa, stab);
+            try sf.stabs.append(allocator, stab);
         }
 
-        try self.stab_files.append(gpa, sf);
+        try self.stab_files.append(allocator, sf);
     }
 }
 
 fn sortAtoms(self: *Object, macho_file: *MachO) !void {
-    const lessThanAtom = struct {
-        fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool {
-            return ctx.getAtom(lhs).?.getInputAddress(ctx) < ctx.getAtom(rhs).?.getInputAddress(ctx);
+    const Ctx = struct {
+        object: *Object,
+        mfile: *MachO,
+
+        fn lessThanAtom(ctx: @This(), lhs: Atom.Index, rhs: Atom.Index) bool {
+            return ctx.object.getAtom(lhs).?.getInputAddress(ctx.mfile) <
+                ctx.object.getAtom(rhs).?.getInputAddress(ctx.mfile);
         }
-    }.lessThanAtom;
-    mem.sort(Atom.Index, self.atoms.items, macho_file, lessThanAtom);
+    };
+    mem.sort(Atom.Index, self.atoms_indexes.items, Ctx{
+        .object = self,
+        .mfile = macho_file,
+    }, Ctx.lessThanAtom);
 }
 
-fn initRelocs(self: *Object, macho_file: *MachO) !void {
+fn initRelocs(self: *Object, file: File.Handle, cpu_arch: std.Target.Cpu.Arch, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
-    const cpu_arch = macho_file.getTarget().cpu.arch;
     const slice = self.sections.slice();
 
-    for (slice.items(.header), slice.items(.relocs), 0..) |sect, *out, n_sect| {
+    for (slice.items(.header), slice.items(.relocs)) |sect, *out| {
         if (sect.nreloc == 0) continue;
         // We skip relocs for __DWARF since even in -r mode, the linker is expected to emit
         // debug symbol stabs in the relocatable. This made me curious why that is. For now,
@@ -865,8 +974,8 @@ fn initRelocs(self: *Object, macho_file: *MachO) !void {
             !mem.eql(u8, sect.sectName(), "__compact_unwind")) continue;
 
         switch (cpu_arch) {
-            .x86_64 => try x86_64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
-            .aarch64 => try aarch64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
+            .x86_64 => try x86_64.parseRelocs(self, sect, out, file, macho_file),
+            .aarch64 => try aarch64.parseRelocs(self, sect, out, file, macho_file),
             else => unreachable,
         }
 
@@ -878,7 +987,7 @@ fn initRelocs(self: *Object, macho_file: *MachO) !void {
 
         var next_reloc: u32 = 0;
         for (subsections.items) |subsection| {
-            const atom = macho_file.getAtom(subsection.atom).?;
+            const atom = self.getAtom(subsection.atom).?;
             if (!atom.flags.alive) continue;
             if (next_reloc >= relocs.items.len) break;
             const end_addr = atom.off + atom.size;
@@ -887,27 +996,22 @@ fn initRelocs(self: *Object, macho_file: *MachO) !void {
             while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {}
 
             const rel_count = next_reloc - rel_index;
-            try atom.addExtra(.{ .rel_index = rel_index, .rel_count = rel_count }, macho_file);
-            atom.flags.relocs = true;
+            atom.addExtra(.{ .rel_index = @intCast(rel_index), .rel_count = @intCast(rel_count) }, macho_file);
         }
     }
 }
 
-fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
+fn initEhFrameRecords(self: *Object, allocator: Allocator, sect_id: u8, file: File.Handle, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
-    const gpa = macho_file.base.comp.gpa;
     const nlists = self.symtab.items(.nlist);
     const slice = self.sections.slice();
     const sect = slice.items(.header)[sect_id];
     const relocs = slice.items(.relocs)[sect_id];
 
-    // TODO: read into buffer directly
-    const data = try self.getSectionData(sect_id, macho_file);
-    defer gpa.free(data);
-
-    try self.eh_frame_data.ensureTotalCapacityPrecise(gpa, data.len);
-    self.eh_frame_data.appendSliceAssumeCapacity(data);
+    try self.eh_frame_data.resize(allocator, sect.size);
+    const amt = try file.preadAll(self.eh_frame_data.items, sect.offset + self.offset);
+    if (amt != self.eh_frame_data.items.len) return error.InputOutput;
 
     // Check for non-personality relocs in FDEs and apply them
     for (relocs.items, 0..) |rel, i| {
@@ -939,12 +1043,12 @@ fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
     var it = eh_frame.Iterator{ .data = self.eh_frame_data.items };
     while (try it.next()) |rec| {
         switch (rec.tag) {
-            .cie => try self.cies.append(gpa, .{
+            .cie => try self.cies.append(allocator, .{
                 .offset = rec.offset,
                 .size = rec.size,
                 .file = self.index,
             }),
-            .fde => try self.fdes.append(gpa, .{
+            .fde => try self.fdes.append(allocator, .{
                 .offset = rec.offset,
                 .size = rec.size,
                 .cie = undefined,
@@ -1205,8 +1309,7 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target.
         {}
 
         const atom = rec.getAtom(macho_file);
-        try atom.addExtra(.{ .unwind_index = start, .unwind_count = next_cu - start }, macho_file);
-        atom.flags.unwind = true;
+        atom.addExtra(.{ .unwind_index = start, .unwind_count = next_cu - start }, macho_file);
     }
 }
 
@@ -1233,11 +1336,31 @@ pub fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {
 
     if (debug_info_index == null or debug_abbrev_index == null) return;
 
-    const debug_info = try self.getSectionData(@intCast(debug_info_index.?), macho_file);
+    const slice = self.sections.slice();
+    const file = macho_file.getFileHandle(self.file_handle);
+    const debug_info = blk: {
+        const sect = slice.items(.header)[debug_info_index.?];
+        const data = try gpa.alloc(u8, sect.size);
+        const amt = try file.preadAll(data, sect.offset + self.offset);
+        if (amt != data.len) return error.InputOutput;
+        break :blk data;
+    };
     defer gpa.free(debug_info);
-    const debug_abbrev = try self.getSectionData(@intCast(debug_abbrev_index.?), macho_file);
+    const debug_abbrev = blk: {
+        const sect = slice.items(.header)[debug_abbrev_index.?];
+        const data = try gpa.alloc(u8, sect.size);
+        const amt = try file.preadAll(data, sect.offset + self.offset);
+        if (amt != data.len) return error.InputOutput;
+        break :blk data;
+    };
     defer gpa.free(debug_abbrev);
-    const debug_str = if (debug_str_index) |index| try self.getSectionData(@intCast(index), macho_file) else &[0]u8{};
+    const debug_str = if (debug_str_index) |sid| blk: {
+        const sect = slice.items(.header)[sid];
+        const data = try gpa.alloc(u8, sect.size);
+        const amt = try file.preadAll(data, sect.offset + self.offset);
+        if (amt != data.len) return error.InputOutput;
+        break :blk data;
+    } else &[0]u8{};
     defer gpa.free(debug_str);
 
     self.compile_unit = self.findCompileUnit(.{
@@ -1347,83 +1470,51 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    for (self.symbols.items, 0..) |index, i| {
-        const nlist_idx = @as(Symbol.Index, @intCast(i));
-        const nlist = self.symtab.items(.nlist)[nlist_idx];
-        const atom_index = self.symtab.items(.atom)[nlist_idx];
+    const gpa = macho_file.base.allocator;
 
+    for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {
         if (!nlist.ext()) continue;
-        if (nlist.undf() and !nlist.tentative()) continue;
         if (nlist.sect()) {
-            const atom = macho_file.getAtom(atom_index).?;
-            if (!atom.flags.alive) continue;
+            const atom = self.getAtom(atom_index).?;
+            if (!atom.alive.load(.seq_cst)) continue;
+        }
+
+        const gop = try macho_file.resolver.getOrPut(gpa, .{
+            .index = @intCast(i),
+            .file = self.index,
+        }, macho_file);
+        if (!gop.found_existing) {
+            gop.ref.* = .{ .index = 0, .file = 0 };
+        }
+        global.* = gop.index;
+
+        if (nlist.undf() and !nlist.tentative()) continue;
+        if (gop.ref.getFile(macho_file) == null) {
+            gop.ref.* = .{ .index = @intCast(i), .file = self.index };
+            continue;
         }
 
-        const symbol = macho_file.getSymbol(index);
         if (self.asFile().getSymbolRank(.{
             .archive = !self.alive,
             .weak = nlist.weakDef(),
             .tentative = nlist.tentative(),
-        }) < symbol.getSymbolRank(macho_file)) {
-            const value = if (nlist.sect()) blk: {
-                const atom = macho_file.getAtom(atom_index).?;
-                break :blk nlist.n_value - atom.getInputAddress(macho_file);
-            } else nlist.n_value;
-            symbol.value = value;
-            symbol.atom = atom_index;
-            symbol.nlist_idx = nlist_idx;
-            symbol.file = self.index;
-            symbol.flags.weak = nlist.weakDef();
-            symbol.flags.abs = nlist.abs();
-            symbol.flags.tentative = nlist.tentative();
-            symbol.flags.weak_ref = false;
-            symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
-            symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
-            // TODO: symbol.flags.interposable = macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext();
-            symbol.flags.interposable = false;
-
-            if (nlist.sect() and
-                self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
-            {
-                symbol.flags.tlv = true;
-            }
-        }
-
-        // Regardless of who the winner is, we still merge symbol visibility here.
-        if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) {
-            if (symbol.visibility != .global) {
-                symbol.visibility = .hidden;
-            }
-        } else {
-            symbol.visibility = .global;
+        }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
+            gop.ref.* = .{ .index = @intCast(i), .file = self.index };
         }
     }
 }
 
-pub fn resetGlobals(self: *Object, macho_file: *MachO) void {
-    for (self.symbols.items, 0..) |sym_index, nlist_idx| {
-        if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue;
-        const sym = macho_file.getSymbol(sym_index);
-        const name = sym.name;
-        const global = sym.flags.global;
-        const weak_ref = sym.flags.weak_ref;
-        sym.* = .{};
-        sym.name = name;
-        sym.flags.global = global;
-        sym.flags.weak_ref = weak_ref;
-    }
-}
-
 pub fn markLive(self: *Object, macho_file: *MachO) void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    for (self.symbols.items, 0..) |index, nlist_idx| {
-        const nlist = self.symtab.items(.nlist)[nlist_idx];
+    for (0..self.symbols.items.len) |i| {
+        const nlist = self.symtab.items(.nlist)[i];
         if (!nlist.ext()) continue;
 
-        const sym = macho_file.getSymbol(index);
-        const file = sym.getFile(macho_file) orelse continue;
+        const ref = self.getSymbolRef(@intCast(i), macho_file);
+        const file = ref.getFile(macho_file) orelse continue;
+        const sym = ref.getSymbol(macho_file).?;
         const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative);
         if (should_keep and file == .object and !file.object.alive) {
             file.object.alive = true;
@@ -1432,30 +1523,47 @@ pub fn markLive(self: *Object, macho_file: *MachO) void {
     }
 }
 
-pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void {
-    for (self.symbols.items, 0..) |index, nlist_idx| {
-        const sym = macho_file.getSymbol(index);
-        if (sym.visibility != .global) continue;
-        const file = sym.getFile(macho_file) orelse continue;
-        if (file.getIndex() == self.index) continue;
+pub fn mergeSymbolVisibility(self: *Object, macho_file: *MachO) void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-        const nlist = self.symtab.items(.nlist)[nlist_idx];
-        if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
-            const gop = try dupes.getOrPut(index);
-            if (!gop.found_existing) {
-                gop.value_ptr.* = .{};
-            }
-            try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
+    for (self.symbols.items, 0..) |sym, i| {
+        const ref = self.getSymbolRef(@intCast(i), macho_file);
+        const global = ref.getSymbol(macho_file) orelse continue;
+        if (global.visibility != .global) {
+            global.visibility = sym.visibility;
+        }
+        if (sym.flags.weak_ref) {
+            global.flags.weak_ref = true;
         }
     }
 }
 
+// TODO
+// pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void {
+//     for (self.symbols.items, 0..) |index, nlist_idx| {
+//         const sym = macho_file.getSymbol(index);
+//         if (sym.visibility != .global) continue;
+//         const file = sym.getFile(macho_file) orelse continue;
+//         if (file.getIndex() == self.index) continue;
+
+//         const nlist = self.symtab.items(.nlist)[nlist_idx];
+//         if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
+//             const gop = try dupes.getOrPut(index);
+//             if (!gop.found_existing) {
+//                 gop.value_ptr.* = .{};
+//             }
+//             try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
+//         }
+//     }
+// }
+
 pub fn scanRelocs(self: *Object, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    for (self.atoms.items) |atom_index| {
-        const atom = macho_file.getAtom(atom_index).?;
+    for (self.getAtoms()) |atom_index| {
+        const atom = self.getAtom(atom_index) orelse continue;
         if (!atom.flags.alive) continue;
         const sect = atom.getInputSection(macho_file);
         if (sect.isZerofill()) continue;
@@ -1480,38 +1588,35 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
     defer tracy.end();
     const gpa = macho_file.base.comp.gpa;
 
-    for (self.symbols.items, 0..) |index, i| {
-        const sym = macho_file.getSymbol(index);
+    for (self.symbols.items, self.globals.items, 0..) |*sym, off, i| {
         if (!sym.flags.tentative) continue;
-        const sym_file = sym.getFile(macho_file).?;
-        if (sym_file.getIndex() != self.index) continue;
+        if (macho_file.resolver.get(off).?.file != self.index) continue;
 
         const nlist_idx = @as(Symbol.Index, @intCast(i));
         const nlist = &self.symtab.items(.nlist)[nlist_idx];
         const nlist_atom = &self.symtab.items(.atom)[nlist_idx];
 
-        const atom_index = try macho_file.addAtom();
-        try self.atoms.append(gpa, atom_index);
-
         const name = try std.fmt.allocPrintZ(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)});
         defer gpa.free(name);
-        const atom = macho_file.getAtom(atom_index).?;
-        atom.atom_index = atom_index;
-        atom.name = try self.addString(gpa, name);
-        atom.file = self.index;
-        atom.size = nlist.n_value;
-        atom.alignment = Atom.Alignment.fromLog2Units((nlist.n_desc >> 8) & 0x0f);
 
+        const alignment = (nlist.n_desc >> 8) & 0x0f;
         const n_sect = try self.addSection(gpa, "__DATA", "__common");
+        const atom_index = try self.addAtom(gpa, .{
+            .name = try self.addString(gpa, name),
+            .n_sect = n_sect,
+            .off = 0,
+            .size = nlist.n_value,
+            .alignment = alignment,
+        });
+        try self.atoms_indexes.append(gpa, atom_index);
+
         const sect = &self.sections.items(.header)[n_sect];
         sect.flags = macho.S_ZEROFILL;
-        sect.size = atom.size;
-        sect.@"align" = atom.alignment.toLog2Units();
-        atom.n_sect = n_sect;
+        sect.size = nlist.n_value;
+        sect.@"align" = alignment;
 
         sym.value = 0;
-        sym.atom = atom_index;
-        sym.flags.global = true;
+        sym.atom_ref = .{ .index = atom_index, .file = self.index };
         sym.flags.weak = false;
         sym.flags.weak_ref = false;
         sym.flags.tentative = false;
@@ -1525,6 +1630,56 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
     }
 }
 
+pub fn claimUnresolved(self: *Object, macho_file: *MachO) void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    for (self.symbols.items, 0..) |*sym, i| {
+        const nlist = self.symtab.items(.nlist)[i];
+        if (!nlist.ext()) continue;
+        if (!nlist.undf()) continue;
+
+        if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
+
+        const is_import = switch (macho_file.options.undefined_treatment) {
+            .@"error" => false,
+            .warn, .suppress => nlist.weakRef(),
+            .dynamic_lookup => true,
+        };
+        if (is_import) {
+            sym.value = 0;
+            sym.atom_ref = .{ .index = 0, .file = 0 };
+            sym.flags.weak = false;
+            sym.flags.weak_ref = nlist.weakRef();
+            sym.flags.import = is_import;
+            sym.visibility = .global;
+
+            const idx = self.globals.items[i];
+            macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = self.index };
+        }
+    }
+}
+
+pub fn claimUnresolvedRelocatable(self: *Object, macho_file: *MachO) void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    for (self.symbols.items, self.symtab.items(.nlist), 0..) |*sym, nlist, i| {
+        if (!nlist.ext()) continue;
+        if (!nlist.undf()) continue;
+        if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
+
+        sym.value = 0;
+        sym.atom_ref = .{ .index = 0, .file = 0 };
+        sym.flags.weak_ref = nlist.weakRef();
+        sym.flags.import = true;
+        sym.visibility = .global;
+
+        const idx = self.globals.items[i];
+        macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = self.index };
+    }
+}
+
 fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
     const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
     self.sections.set(n_sect, .{
@@ -1646,19 +1801,22 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    for (self.symbols.items) |sym_index| {
-        const sym = macho_file.getSymbol(sym_index);
-        const file = sym.getFile(macho_file) orelse continue;
+    const is_obj = macho_file.base.isObject();
+
+    for (self.symbols.items, 0..) |*sym, i| {
+        const ref = self.getSymbolRef(@intCast(i), macho_file);
+        const file = ref.getFile(macho_file) orelse continue;
         if (file.getIndex() != self.index) continue;
         if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
         if (sym.isSymbolStab(macho_file)) continue;
         const name = sym.getName(macho_file);
+        if (name.len == 0) continue;
         // TODO in -r mode, we actually want to merge symbol names and emit only one
         // work it out when emitting relocs
-        if (name.len > 0 and
-            (name[0] == 'L' or name[0] == 'l' or
+        if ((name[0] == 'L' or name[0] == 'l' or
             mem.startsWith(u8, name, "_OBJC_SELECTOR_REFERENCES_")) and
-            !macho_file.base.isObject()) continue;
+            !is_obj)
+            continue;
         sym.flags.output_symtab = true;
         if (sym.isLocal()) {
             try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
@@ -1693,9 +1851,9 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void {
             self.output_symtab_ctx.strsize += @as(u32, @intCast(self.path.len + 1));
         }
 
-        for (self.symbols.items) |sym_index| {
-            const sym = macho_file.getSymbol(sym_index);
-            const file = sym.getFile(macho_file) orelse continue;
+        for (self.symbols.items, 0..) |sym, i| {
+            const ref = self.getSymbolRef(@intCast(i), macho_file);
+            const file = ref.getFile(macho_file) orelse continue;
             if (file.getIndex() != self.index) continue;
             if (!sym.flags.output_symtab) continue;
             if (macho_file.base.isObject()) {
@@ -1732,28 +1890,204 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void {
     }
 }
 
-pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) error{Overflow}!void {
+pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const gpa = macho_file.base.comp.gpa;
+    const headers = self.sections.items(.header);
+    const sections_data = try gpa.alloc([]const u8, headers.len);
+    defer {
+        for (sections_data) |data| {
+            gpa.free(data);
+        }
+        gpa.free(sections_data);
+    }
+    @memset(sections_data, &[0]u8{});
+    const file = macho_file.getFileHandle(self.file_handle);
+
+    for (headers, 0..) |header, n_sect| {
+        if (header.isZerofill()) continue;
+        const data = try gpa.alloc(u8, header.size);
+        const amt = try file.preadAll(data, header.offset + self.offset);
+        if (amt != data.len) return error.InputOutput;
+        sections_data[n_sect] = data;
+    }
+    for (self.getAtoms()) |atom_index| {
+        const atom = self.getAtom(atom_index) orelse continue;
+        if (!atom.flags.alive) continue;
+        const sect = atom.getInputSection(macho_file);
+        if (sect.isZerofill()) continue;
+        const off = atom.value;
+        const buffer = macho_file.sections.items(.out)[atom.out_n_sect].items;
+        const data = sections_data[atom.n_sect];
+        @memcpy(buffer[off..][0..atom.size], data[atom.off..][0..atom.size]);
+        try atom.resolveRelocs(macho_file, buffer[off..][0..atom.size]);
+    }
+}
+
+pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const gpa = macho_file.base.allocator;
+    const headers = self.sections.items(.header);
+    const sections_data = try gpa.alloc([]const u8, headers.len);
+    defer {
+        for (sections_data) |data| {
+            gpa.free(data);
+        }
+        gpa.free(sections_data);
+    }
+    @memset(sections_data, &[0]u8{});
+    const file = macho_file.getFileHandle(self.file_handle);
+
+    for (headers, 0..) |header, n_sect| {
+        if (header.isZerofill()) continue;
+        const data = try gpa.alloc(u8, header.size);
+        const amt = try file.preadAll(data, header.offset + self.offset);
+        if (amt != data.len) return error.InputOutput;
+        sections_data[n_sect] = data;
+    }
+    for (self.getAtoms()) |atom_index| {
+        const atom = self.getAtom(atom_index) orelse continue;
+        if (!atom.flags.alive) continue;
+        const sect = atom.getInputSection(macho_file);
+        if (sect.isZerofill()) continue;
+        const off = atom.value;
+        const buffer = macho_file.sections.items(.out)[atom.out_n_sect].items;
+        const data = sections_data[atom.n_sect];
+        @memcpy(buffer[off..][0..atom.size], data[atom.off..][0..atom.size]);
+        const relocs = macho_file.sections.items(.relocs)[atom.out_n_sect].items;
+        const extra = atom.getExtra(macho_file);
+        try atom.writeRelocs(macho_file, buffer[off..][0..atom.size], relocs[extra.rel_out_index..][0..extra.rel_out_count]);
+    }
+}
+
+pub fn calcCompactUnwindSizeRelocatable(self: *Object, macho_file: *MachO) void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const ctx = &self.compact_unwind_ctx;
+
+    for (self.unwind_records_indexes.items) |irec| {
+        const rec = self.getUnwindRecord(irec);
+        if (!rec.alive) continue;
+
+        ctx.rec_count += 1;
+        ctx.reloc_count += 1;
+        if (rec.getPersonality(macho_file)) |_| {
+            ctx.reloc_count += 1;
+        }
+        if (rec.getLsdaAtom(macho_file)) |_| {
+            ctx.reloc_count += 1;
+        }
+    }
+}
+
+pub fn writeCompactUnwindRelocatable(self: *Object, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    for (self.symbols.items) |sym_index| {
-        const sym = macho_file.getSymbol(sym_index);
-        const file = sym.getFile(macho_file) orelse continue;
+    const addReloc = struct {
+        fn addReloc(offset: u32, cpu_arch: std.Target.Cpu.Arch) !macho.relocation_info {
+            return .{
+                .r_address = math.cast(i32, offset) orelse return error.Overflow,
+                .r_symbolnum = 0,
+                .r_pcrel = 0,
+                .r_length = 3,
+                .r_extern = 0,
+                .r_type = switch (cpu_arch) {
+                    .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED),
+                    .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED),
+                    else => unreachable,
+                },
+            };
+        }
+    }.addReloc;
+
+    const nsect = macho_file.unwind_info_sect_index.?;
+    const buffer = macho_file.sections.items(.out)[nsect].items;
+    const relocs = macho_file.sections.items(.relocs)[nsect].items;
+
+    var rec_index: u32 = self.compact_unwind_ctx.rec_index;
+    var reloc_index: u32 = self.compact_unwind_ctx.reloc_index;
+
+    for (self.unwind_records_indexes.items) |irec| {
+        const rec = self.getUnwindRecord(irec);
+        if (!rec.alive) continue;
+
+        var out: macho.compact_unwind_entry = .{
+            .rangeStart = 0,
+            .rangeLength = rec.length,
+            .compactUnwindEncoding = rec.enc.enc,
+            .personalityFunction = 0,
+            .lsda = 0,
+        };
+        defer rec_index += 1;
+
+        const offset = rec_index * @sizeOf(macho.compact_unwind_entry);
+
+        {
+            // Function address
+            const atom = rec.getAtom(macho_file);
+            const addr = rec.getAtomAddress(macho_file);
+            out.rangeStart = addr;
+            var reloc = try addReloc(offset, macho_file.options.cpu_arch.?);
+            reloc.r_symbolnum = atom.out_n_sect + 1;
+            relocs[reloc_index] = reloc;
+            reloc_index += 1;
+        }
+
+        // Personality function
+        if (rec.getPersonality(macho_file)) |sym| {
+            const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
+            var reloc = try addReloc(offset + 16, macho_file.options.cpu_arch.?);
+            reloc.r_symbolnum = r_symbolnum;
+            reloc.r_extern = 1;
+            relocs[reloc_index] = reloc;
+            reloc_index += 1;
+        }
+
+        // LSDA address
+        if (rec.getLsdaAtom(macho_file)) |atom| {
+            const addr = rec.getLsdaAddress(macho_file);
+            out.lsda = addr;
+            var reloc = try addReloc(offset + 24, macho_file.options.cpu_arch.?);
+            reloc.r_symbolnum = atom.out_n_sect + 1;
+            relocs[reloc_index] = reloc;
+            reloc_index += 1;
+        }
+
+        @memcpy(buffer[offset..][0..@sizeOf(macho.compact_unwind_entry)], mem.asBytes(&out));
+    }
+}
+
+pub fn writeSymtab(self: Object, macho_file: *MachO) void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    var n_strx = self.output_symtab_ctx.stroff;
+    for (self.symbols.items, 0..) |sym, i| {
+        const ref = self.getSymbolRef(@intCast(i), macho_file);
+        const file = ref.getFile(macho_file) orelse continue;
         if (file.getIndex() != self.index) continue;
         const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
-        const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
-        ctx.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
-        ctx.strtab.appendAssumeCapacity(0);
-        const out_sym = &ctx.symtab.items[idx];
+        const out_sym = &macho_file.symtab.items[idx];
         out_sym.n_strx = n_strx;
         sym.setOutputSym(macho_file, out_sym);
+        const name = sym.getName(macho_file);
+        @memcpy(macho_file.strtab.items[n_strx..][0..name.len], name);
+        n_strx += @intCast(name.len);
+        macho_file.strtab.items[n_strx] = 0;
+        n_strx += 1;
     }
 
     if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
-        try self.writeStabs(macho_file, ctx);
+        try self.writeStabs(n_strx, macho_file);
 }
 
-pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{Overflow}!void {
+pub fn writeStabs(self: *const Object, stroff: u32, macho_file: *MachO) void {
     const writeFuncStab = struct {
         inline fn writeFuncStab(
             n_strx: u32,
@@ -1761,7 +2095,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
             n_value: u64,
             size: u64,
             index: u32,
-            context: anytype,
+            context: *MachO,
         ) void {
             context.symtab.items[index] = .{
                 .n_strx = 0,
@@ -1795,6 +2129,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
     }.writeFuncStab;
 
     var index = self.output_symtab_ctx.istab;
+    var n_strx = stroff;
 
     if (self.compile_unit) |cu| {
         const comp_dir = cu.getCompDir(self);
@@ -1802,10 +2137,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
 
         // Open scope
         // N_SO comp_dir
-        var n_strx = @as(u32, @intCast(ctx.strtab.items.len));
-        ctx.strtab.appendSliceAssumeCapacity(comp_dir);
-        ctx.strtab.appendAssumeCapacity(0);
-        ctx.symtab.items[index] = .{
+        macho_file.symtab.items[index] = .{
             .n_strx = n_strx,
             .n_type = macho.N_SO,
             .n_sect = 0,
@@ -1813,11 +2145,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
             .n_value = 0,
         };
         index += 1;
+        @memcpy(macho_file.strtab.items[n_strx..][0..comp_dir.len], comp_dir);
+        n_strx += @intCast(comp_dir.len);
+        macho_file.strtab.items[n_strx] = 0;
+        n_strx += 1;
         // N_SO tu_name
-        n_strx = @as(u32, @intCast(ctx.strtab.items.len));
-        ctx.strtab.appendSliceAssumeCapacity(tu_name);
-        ctx.strtab.appendAssumeCapacity(0);
-        ctx.symtab.items[index] = .{
+        macho_file.symtab.items[index] = .{
             .n_strx = n_strx,
             .n_type = macho.N_SO,
             .n_sect = 0,
@@ -1825,19 +2158,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
             .n_value = 0,
         };
         index += 1;
+        @memcpy(macho_file.strtab.items[n_strx..][0..tu_name.len], tu_name);
+        n_strx += @intCast(tu_name.len);
+        macho_file.strtab.items[n_strx] = 0;
+        n_strx += 1;
         // N_OSO path
-        n_strx = @as(u32, @intCast(ctx.strtab.items.len));
-        if (self.in_archive) |ar| {
-            ctx.strtab.appendSliceAssumeCapacity(ar.path);
-            ctx.strtab.appendAssumeCapacity('(');
-            ctx.strtab.appendSliceAssumeCapacity(self.path);
-            ctx.strtab.appendAssumeCapacity(')');
-            ctx.strtab.appendAssumeCapacity(0);
-        } else {
-            ctx.strtab.appendSliceAssumeCapacity(self.path);
-            ctx.strtab.appendAssumeCapacity(0);
-        }
-        ctx.symtab.items[index] = .{
+        macho_file.symtab.items[index] = .{
             .n_strx = n_strx,
             .n_type = macho.N_OSO,
             .n_sect = 0,
@@ -1845,10 +2171,27 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
             .n_value = self.mtime,
         };
         index += 1;
+        if (self.in_archive) |ar| {
+            @memcpy(macho_file.strtab.items[n_strx..][0..ar.path.len], ar.path);
+            n_strx += @intCast(ar.path.len);
+            macho_file.strtab.items[n_strx] = '(';
+            n_strx += 1;
+            @memcpy(macho_file.strtab.items[n_strx..][0..self.path.len], self.path);
+            n_strx += @intCast(self.path.len);
+            macho_file.strtab.items[n_strx] = ')';
+            n_strx += 1;
+            macho_file.strtab.items[n_strx] = 0;
+            n_strx += 1;
+        } else {
+            @memcpy(macho_file.strtab.items[n_strx..][0..self.path.len], self.path);
+            n_strx += @intCast(self.path.len);
+            macho_file.strtab.items[n_strx] = 0;
+            n_strx += 1;
+        }
 
-        for (self.symbols.items) |sym_index| {
-            const sym = macho_file.getSymbol(sym_index);
-            const file = sym.getFile(macho_file) orelse continue;
+        for (self.symbols.items, 0..) |sym, i| {
+            const ref = self.getSymbolRef(@intCast(i), macho_file);
+            const file = ref.getFile(macho_file) orelse continue;
             if (file.getIndex() != self.index) continue;
             if (!sym.flags.output_symtab) continue;
             if (macho_file.base.isObject()) {
@@ -1858,17 +2201,17 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
             const sect = macho_file.sections.items(.header)[sym.out_n_sect];
             const sym_n_strx = n_strx: {
                 const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
-                const osym = ctx.symtab.items[symtab_index];
+                const osym = macho_file.symtab.items[symtab_index];
                 break :n_strx osym.n_strx;
             };
             const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
             const sym_n_value = sym.getAddress(.{}, macho_file);
             const sym_size = sym.getSize(macho_file);
             if (sect.isCode()) {
-                writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx);
+                writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
                 index += 4;
             } else if (sym.visibility == .global) {
-                ctx.symtab.items[index] = .{
+                macho_file.symtab.items[index] = .{
                     .n_strx = sym_n_strx,
                     .n_type = macho.N_GSYM,
                     .n_sect = sym_n_sect,
@@ -1877,7 +2220,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
                 };
                 index += 1;
             } else {
-                ctx.symtab.items[index] = .{
+                macho_file.symtab.items[index] = .{
                     .n_strx = sym_n_strx,
                     .n_type = macho.N_STSYM,
                     .n_sect = sym_n_sect,
@@ -1890,7 +2233,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
 
         // Close scope
         // N_SO
-        ctx.symtab.items[index] = .{
+        macho_file.symtab.items[index] = .{
             .n_strx = 0,
             .n_type = macho.N_SO,
             .n_sect = 0,
@@ -1901,12 +2244,13 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
         assert(self.hasSymbolStabs());
 
         for (self.stab_files.items) |sf| {
+            const comp_dir = sf.getCombDir(self);
+            const tu_name = sf.getTuName(self);
+            const oso_path = sf.getOsoPath(self);
+
             // Open scope
             // N_SO comp_dir
-            var n_strx = @as(u32, @intCast(ctx.strtab.items.len));
-            ctx.strtab.appendSliceAssumeCapacity(sf.getCompDir(self));
-            ctx.strtab.appendAssumeCapacity(0);
-            ctx.symtab.items[index] = .{
+            macho_file.symtab.items[index] = .{
                 .n_strx = n_strx,
                 .n_type = macho.N_SO,
                 .n_sect = 0,
@@ -1914,11 +2258,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
                 .n_value = 0,
             };
             index += 1;
+            @memcpy(macho_file.strtab.items[n_strx..][0..comp_dir.len], comp_dir);
+            n_strx += @intCast(comp_dir.len);
+            macho_file.strtab.items[n_strx] = 0;
+            n_strx += 1;
             // N_SO tu_name
-            n_strx = @as(u32, @intCast(ctx.strtab.items.len));
-            ctx.strtab.appendSliceAssumeCapacity(sf.getTuName(self));
-            ctx.strtab.appendAssumeCapacity(0);
-            ctx.symtab.items[index] = .{
+            macho_file.symtab.items[index] = .{
                 .n_strx = n_strx,
                 .n_type = macho.N_SO,
                 .n_sect = 0,
@@ -1926,11 +2271,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
                 .n_value = 0,
             };
             index += 1;
+            @memcpy(macho_file.strtab.items[n_strx..][0..tu_name.len], tu_name);
+            n_strx += @intCast(tu_name.len);
+            macho_file.strtab.items[n_strx] = 0;
+            n_strx += 1;
             // N_OSO path
-            n_strx = @as(u32, @intCast(ctx.strtab.items.len));
-            ctx.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self));
-            ctx.strtab.appendAssumeCapacity(0);
-            ctx.symtab.items[index] = .{
+            macho_file.symtab.items[index] = .{
                 .n_strx = n_strx,
                 .n_type = macho.N_OSO,
                 .n_sect = 0,
@@ -1938,6 +2284,10 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
                 .n_value = sf.getOsoModTime(self),
             };
             index += 1;
+            @memcpy(macho_file.strtab.items[n_strx..][0..oso_path.len], oso_path);
+            n_strx += @intCast(oso_path.len);
+            macho_file.strtab.items[n_strx] = 0;
+            n_strx += 1;
 
             for (sf.stabs.items) |stab| {
                 const sym = stab.getSymbol(macho_file) orelse continue;
@@ -1946,17 +2296,17 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
                 if (!sym.flags.output_symtab) continue;
                 const sym_n_strx = n_strx: {
                     const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
-                    const osym = ctx.symtab.items[symtab_index];
+                    const osym = macho_file.symtab.items[symtab_index];
                     break :n_strx osym.n_strx;
                 };
                 const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
                 const sym_n_value = sym.getAddress(.{}, macho_file);
                 const sym_size = sym.getSize(macho_file);
                 if (stab.is_func) {
-                    writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx);
+                    writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
                     index += 4;
                 } else if (sym.visibility == .global) {
-                    ctx.symtab.items[index] = .{
+                    macho_file.symtab.items[index] = .{
                         .n_strx = sym_n_strx,
                         .n_type = macho.N_GSYM,
                         .n_sect = sym_n_sect,
@@ -1965,7 +2315,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
                     };
                     index += 1;
                 } else {
-                    ctx.symtab.items[index] = .{
+                    macho_file.symtab.items[index] = .{
                         .n_strx = sym_n_strx,
                         .n_type = macho.N_STSYM,
                         .n_sect = sym_n_sect,
@@ -1978,7 +2328,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
 
             // Close scope
             // N_SO
-            ctx.symtab.items[index] = .{
+            macho_file.symtab.items[index] = .{
                 .n_strx = 0,
                 .n_type = macho.N_SO,
                 .n_sect = 0,
@@ -1990,36 +2340,6 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
     }
 }
 
-fn getSectionData(self: *const Object, index: u32, macho_file: *MachO) ![]u8 {
-    const gpa = macho_file.base.comp.gpa;
-    const slice = self.sections.slice();
-    assert(index < slice.items(.header).len);
-    const sect = slice.items(.header)[index];
-    const handle = macho_file.getFileHandle(self.file_handle);
-    const size = math.cast(usize, sect.size) orelse return error.Overflow;
-    const buffer = try gpa.alloc(u8, size);
-    errdefer gpa.free(buffer);
-    const amt = try handle.preadAll(buffer, sect.offset + self.offset);
-    if (amt != buffer.len) return error.InputOutput;
-    return buffer;
-}
-
-pub fn getAtomData(self: *const Object, macho_file: *MachO, atom: Atom, buffer: []u8) !void {
-    assert(buffer.len == atom.size);
-    const slice = self.sections.slice();
-    const handle = macho_file.getFileHandle(self.file_handle);
-    const sect = slice.items(.header)[atom.n_sect];
-    const amt = try handle.preadAll(buffer, sect.offset + self.offset + atom.off);
-    if (amt != buffer.len) return error.InputOutput;
-}
-
-pub fn getAtomRelocs(self: *const Object, atom: Atom, macho_file: *MachO) []const Relocation {
-    if (!atom.flags.relocs) return &[0]Relocation{};
-    const extra = atom.getExtra(macho_file).?;
-    const relocs = self.sections.items(.relocs)[atom.n_sect];
-    return relocs.items[extra.rel_index..][0..extra.rel_count];
-}
-
 fn addString(self: *Object, allocator: Allocator, name: [:0]const u8) error{OutOfMemory}!u32 {
     const off: u32 = @intCast(self.strtab.items.len);
     try self.strtab.ensureUnusedCapacity(allocator, name.len + 1);
@@ -2073,6 +2393,143 @@ pub fn asFile(self: *Object) File {
     return .{ .object = self };
 }
 
+const AddAtomArgs = struct {
+    name: MachO.String,
+    n_sect: u8,
+    off: u64,
+    size: u64,
+    alignment: u32,
+};
+
+fn addAtom(self: *Object, allocator: Allocator, args: AddAtomArgs) !Atom.Index {
+    const atom_index: Atom.Index = @intCast(self.atoms.items.len);
+    const atom = try self.atoms.addOne(allocator);
+    atom.* = .{
+        .file = self.index,
+        .atom_index = atom_index,
+        .name = args.name,
+        .n_sect = args.n_sect,
+        .size = args.size,
+        .off = args.off,
+        .extra = try self.addAtomExtra(allocator, .{}),
+        .alignment = args.alignment,
+    };
+    return atom_index;
+}
+
+pub fn getAtom(self: *Object, atom_index: Atom.Index) ?*Atom {
+    if (atom_index == 0) return null;
+    assert(atom_index < self.atoms.items.len);
+    return &self.atoms.items[atom_index];
+}
+
+pub fn getAtoms(self: *Object) []const Atom.Index {
+    return self.atoms_indexes.items;
+}
+
+fn addAtomExtra(self: *Object, allocator: Allocator, extra: Atom.Extra) !u32 {
+    const fields = @typeInfo(Atom.Extra).Struct.fields;
+    try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len);
+    return self.addAtomExtraAssumeCapacity(extra);
+}
+
+fn addAtomExtraAssumeCapacity(self: *Object, extra: Atom.Extra) u32 {
+    const index = @as(u32, @intCast(self.atoms_extra.items.len));
+    const fields = @typeInfo(Atom.Extra).Struct.fields;
+    inline for (fields) |field| {
+        self.atoms_extra.appendAssumeCapacity(switch (field.type) {
+            u32 => @field(extra, field.name),
+            else => @compileError("bad field type"),
+        });
+    }
+    return index;
+}
+
+pub fn getAtomExtra(self: Object, index: u32) Atom.Extra {
+    const fields = @typeInfo(Atom.Extra).Struct.fields;
+    var i: usize = index;
+    var result: Atom.Extra = undefined;
+    inline for (fields) |field| {
+        @field(result, field.name) = switch (field.type) {
+            u32 => self.atoms_extra.items[i],
+            else => @compileError("bad field type"),
+        };
+        i += 1;
+    }
+    return result;
+}
+
+pub fn setAtomExtra(self: *Object, index: u32, extra: Atom.Extra) void {
+    assert(index > 0);
+    const fields = @typeInfo(Atom.Extra).Struct.fields;
+    inline for (fields, 0..) |field, i| {
+        self.atoms_extra.items[index + i] = switch (field.type) {
+            u32 => @field(extra, field.name),
+            else => @compileError("bad field type"),
+        };
+    }
+}
+
+fn addSymbol(self: *Object, allocator: Allocator) !Symbol.Index {
+    try self.symbols.ensureUnusedCapacity(allocator, 1);
+    return self.addSymbolAssumeCapacity();
+}
+
+fn addSymbolAssumeCapacity(self: *Object) Symbol.Index {
+    const index: Symbol.Index = @intCast(self.symbols.items.len);
+    const symbol = self.symbols.addOneAssumeCapacity();
+    symbol.* = .{ .file = self.index };
+    return index;
+}
+
+pub fn getSymbolRef(self: Object, index: Symbol.Index, macho_file: *MachO) MachO.Ref {
+    const global_index = self.globals.items[index];
+    if (macho_file.resolver.get(global_index)) |ref| return ref;
+    return .{ .index = index, .file = self.index };
+}
+
+pub fn addSymbolExtra(self: *Object, allocator: Allocator, extra: Symbol.Extra) !u32 {
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len);
+    return self.addSymbolExtraAssumeCapacity(extra);
+}
+
+fn addSymbolExtraAssumeCapacity(self: *Object, extra: Symbol.Extra) u32 {
+    const index = @as(u32, @intCast(self.symbols_extra.items.len));
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    inline for (fields) |field| {
+        self.symbols_extra.appendAssumeCapacity(switch (field.type) {
+            u32 => @field(extra, field.name),
+            else => @compileError("bad field type"),
+        });
+    }
+    return index;
+}
+
+pub fn getSymbolExtra(self: Object, index: u32) Symbol.Extra {
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    var i: usize = index;
+    var result: Symbol.Extra = undefined;
+    inline for (fields) |field| {
+        @field(result, field.name) = switch (field.type) {
+            u32 => self.symbols_extra.items[i],
+            else => @compileError("bad field type"),
+        };
+        i += 1;
+    }
+    return result;
+}
+
+pub fn setSymbolExtra(self: *Object, index: u32, extra: Symbol.Extra) void {
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    inline for (fields, 0..) |field, i| {
+        self.symbols_extra.items[index + i] = switch (field.type) {
+            u32 => @field(extra, field.name),
+            else => @compileError("bad field type"),
+        };
+    }
+}
+
 fn addUnwindRecord(self: *Object, allocator: Allocator) !UnwindInfo.Record.Index {
     try self.unwind_records.ensureUnusedCapacity(allocator, 1);
     return self.addUnwindRecordAssumeCapacity();
@@ -2124,10 +2581,11 @@ fn formatAtoms(
     _ = unused_fmt_string;
     _ = options;
     const object = ctx.object;
+    const macho_file = ctx.macho_file;
     try writer.writeAll("  atoms\n");
-    for (object.atoms.items) |atom_index| {
-        const atom = ctx.macho_file.getAtom(atom_index).?;
-        try writer.print("    {}\n", .{atom.fmt(ctx.macho_file)});
+    for (object.getAtoms()) |atom_index| {
+        const atom = object.getAtom(atom_index) orelse continue;
+        try writer.print("    {}\n", .{atom.fmt(macho_file)});
     }
 }
 
@@ -2214,10 +2672,26 @@ fn formatSymtab(
     _ = unused_fmt_string;
     _ = options;
     const object = ctx.object;
+    const macho_file = ctx.macho_file;
     try writer.writeAll("  symbols\n");
-    for (object.symbols.items) |index| {
-        const sym = ctx.macho_file.getSymbol(index);
-        try writer.print("    {}\n", .{sym.fmt(ctx.macho_file)});
+    for (object.symbols.items, 0..) |sym, i| {
+        const ref = object.getSymbolRef(@intCast(i), macho_file);
+        if (ref.getFile(macho_file) == null) {
+            // TODO any better way of handling this?
+            try writer.print("    {s} : unclaimed\n", .{sym.getName(macho_file)});
+        } else {
+            try writer.print("    {}\n", .{ref.getSymbol(macho_file).?.fmt(macho_file)});
+        }
+    }
+    for (object.stab_files.items) |sf| {
+        try writer.print("  stabs({s},{s},{s})\n", .{
+            sf.getCompDir(object),
+            sf.getTuName(object),
+            sf.getOsoPath(object),
+        });
+        for (sf.stabs.items) |stab| {
+            try writer.print("    {}", .{stab.fmt(object)});
+        }
     }
 }
 
@@ -2286,8 +2760,47 @@ const StabFile = struct {
         is_func: bool = true,
         symbol: ?Symbol.Index = null,
 
-        fn getSymbol(stab: Stab, macho_file: *MachO) ?*Symbol {
-            return if (stab.symbol) |s| macho_file.getSymbol(s) else null;
+        fn getSymbol(stab: Stab, object: *const Object) ?*Symbol {
+            const index = stab.index orelse return null;
+            return &object.symbols.items[index];
+        }
+
+        pub fn format(
+            stab: Stab,
+            comptime unused_fmt_string: []const u8,
+            options: std.fmt.FormatOptions,
+            writer: anytype,
+        ) !void {
+            _ = stab;
+            _ = unused_fmt_string;
+            _ = options;
+            _ = writer;
+            @compileError("do not format stabs directly");
+        }
+
+        const StabFormatContext = struct { Stab, *const Object };
+
+        pub fn fmt(stab: Stab, object: *const Object) std.fmt.Formatter(format2) {
+            return .{ .data = .{ stab, object } };
+        }
+
+        fn format2(
+            ctx: StabFormatContext,
+            comptime unused_fmt_string: []const u8,
+            options: std.fmt.FormatOptions,
+            writer: anytype,
+        ) !void {
+            _ = unused_fmt_string;
+            _ = options;
+            const stab, const object = ctx;
+            const sym = stab.getSymbol(object).?;
+            if (stab.is_func) {
+                try writer.print("func({d})", .{stab.index.?});
+            } else if (sym.visibility == .global) {
+                try writer.print("gsym({d})", .{stab.index.?});
+            } else {
+                try writer.print("stsym({d})", .{stab.index.?});
+            }
         }
     };
 };
@@ -2310,6 +2823,13 @@ const InArchive = struct {
     size: u32,
 };
 
+const CompactUnwindCtx = struct {
+    rec_index: u32 = 0,
+    rec_count: u32 = 0,
+    reloc_index: u32 = 0,
+    reloc_count: u32 = 0,
+};
+
 const x86_64 = struct {
     fn parseRelocs(
         self: *const Object,
src/link/MachO.zig
@@ -63,9 +63,6 @@ dso_handle_index: ?Symbol.Index = null,
 objc_msg_send_index: ?Symbol.Index = null,
 entry_index: ?Symbol.Index = null,
 
-/// List of atoms that are either synthetic or map directly to the Zig source program.
-atoms: std.ArrayListUnmanaged(Atom) = .{},
-atoms_extra: std.ArrayListUnmanaged(u32) = .{},
 thunks: std.ArrayListUnmanaged(Thunk) = .{},
 
 /// String interning table
@@ -549,13 +546,14 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
         try dead_strip.gcAtoms(self);
     }
 
-    self.checkDuplicates() catch |err| switch (err) {
-        error.HasDuplicates => return error.FlushFailure,
-        else => |e| {
-            try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{});
-            return e;
-        },
-    };
+    // TODO
+    // self.checkDuplicates() catch |err| switch (err) {
+    //     error.HasDuplicates => return error.FlushFailure,
+    //     else => |e| {
+    //         try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{});
+    //         return e;
+    //     },
+    // };
 
     self.markImportsAndExports();
     self.deadStripDylibs();