Commit b9bac32a25

Jakub Konka <kubkon@jakubkonka.com>
2024-07-09 11:22:24
macho: migrate Atom and Symbol
1 parent c59583e
src/link/MachO/Atom.zig
@@ -47,16 +47,6 @@ pub fn getFile(self: Atom, macho_file: *MachO) File {
     return macho_file.getFile(self.file).?;
 }
 
-pub fn getData(self: Atom, macho_file: *MachO, buffer: []u8) !void {
-    assert(buffer.len == self.size);
-    switch (self.getFile(macho_file)) {
-        .internal => |x| try x.getAtomData(self, buffer),
-        .object => |x| try x.getAtomData(macho_file, self, buffer),
-        .zig_object => |x| try x.getAtomData(macho_file, self, buffer),
-        else => unreachable,
-    }
-}
-
 pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation {
     return switch (self.getFile(macho_file)) {
         .dylib => unreachable,
@@ -88,8 +78,7 @@ pub fn getPriority(self: Atom, macho_file: *MachO) u64 {
 }
 
 pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index {
-    if (!self.flags.unwind) return &[0]UnwindInfo.Record.Index{};
-    const extra = self.getExtra(macho_file).?;
+    const extra = self.getExtra(macho_file);
     return switch (self.getFile(macho_file)) {
         .dylib => unreachable,
         .zig_object, .internal => &[0]UnwindInfo.Record.Index{},
@@ -110,44 +99,39 @@ pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void {
 }
 
 pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk {
-    assert(self.flags.thunk);
-    const extra = self.getExtra(macho_file).?;
+    const extra = self.getExtra(macho_file);
     return macho_file.getThunk(extra.thunk);
 }
 
-pub fn getLiteralPoolIndex(self: Atom, macho_file: *MachO) ?MachO.LiteralPool.Index {
-    if (!self.flags.literal_pool) return null;
-    return self.getExtra(macho_file).?.literal_index;
-}
-
 const AddExtraOpts = struct {
     thunk: ?u32 = null,
     rel_index: ?u32 = null,
     rel_count: ?u32 = null,
+    rel_out_index: ?u32 = null,
+    rel_out_count: ?u32 = null,
     unwind_index: ?u32 = null,
     unwind_count: ?u32 = null,
-    literal_index: ?u32 = null,
+    literal_pool_index: ?u32 = null,
+    literal_symbol_index: ?u32 = null,
 };
 
-pub fn addExtra(atom: *Atom, opts: AddExtraOpts, macho_file: *MachO) !void {
-    if (atom.getExtra(macho_file) == null) {
-        atom.extra = try macho_file.addAtomExtra(.{});
-    }
-    var extra = atom.getExtra(macho_file).?;
+pub fn addExtra(atom: *Atom, opts: AddExtraOpts, macho_file: *MachO) void {
+    const file = atom.getFile(macho_file);
+    var extra = file.getAtomExtra(atom.extra);
     inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
         if (@field(opts, field.name)) |x| {
             @field(extra, field.name) = x;
         }
     }
-    atom.setExtra(extra, macho_file);
+    file.setAtomExtra(atom.extra, extra);
 }
 
-pub inline fn getExtra(atom: Atom, macho_file: *MachO) ?Extra {
-    return macho_file.getAtomExtra(atom.extra);
+pub inline fn getExtra(atom: Atom, macho_file: *MachO) Extra {
+    return atom.getFile(macho_file).getAtomExtra(atom.extra);
 }
 
 pub inline fn setExtra(atom: Atom, extra: Extra, macho_file: *MachO) void {
-    macho_file.setAtomExtra(atom.extra, extra);
+    atom.getFile(macho_file).setAtomExtra(atom.extra, extra);
 }
 
 pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
@@ -467,7 +451,7 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
 
         switch (rel.type) {
             .branch => {
-                const symbol = rel.getTargetSymbol(macho_file);
+                const symbol = rel.getTargetSymbol(self, macho_file);
                 if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) {
                     symbol.flags.stubs = true;
                     if (symbol.flags.weak) {
@@ -482,7 +466,7 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
             .got_load_page,
             .got_load_pageoff,
             => {
-                const symbol = rel.getTargetSymbol(macho_file);
+                const symbol = rel.getTargetSymbol(self, macho_file);
                 if (symbol.flags.import or
                     (symbol.flags.@"export" and symbol.flags.weak) or
                     symbol.flags.interposable or
@@ -496,18 +480,18 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
             },
 
             .zig_got_load => {
-                assert(rel.getTargetSymbol(macho_file).flags.has_zig_got);
+                assert(rel.getTargetSymbol(self, macho_file).flags.has_zig_got);
             },
 
             .got => {
-                rel.getTargetSymbol(macho_file).flags.needs_got = true;
+                rel.getTargetSymbol(self, macho_file).flags.needs_got = true;
             },
 
             .tlv,
             .tlvp_page,
             .tlvp_pageoff,
             => {
-                const symbol = rel.getTargetSymbol(macho_file);
+                const symbol = rel.getTargetSymbol(self, macho_file);
                 if (!symbol.flags.tlv) {
                     try macho_file.reportParseError2(
                         self.getFile(macho_file).getIndex(),
@@ -526,7 +510,7 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
             .unsigned => {
                 if (rel.meta.length == 3) { // TODO this really should check if this is pointer width
                     if (rel.tag == .@"extern") {
-                        const symbol = rel.getTargetSymbol(macho_file);
+                        const symbol = rel.getTargetSymbol(self, macho_file);
                         if (symbol.isTlvInit(macho_file)) {
                             macho_file.has_tlv = true;
                             continue;
@@ -559,14 +543,15 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
 fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool {
     if (rel.tag == .local) return false;
 
-    const sym = rel.getTargetSymbol(macho_file);
-    if (sym.getFile(macho_file) == null) {
+    const file = self.getFile(macho_file);
+    const ref = file.getSymbolRef(rel.target, macho_file);
+    if (ref.getFile(macho_file) == null) {
         const gpa = macho_file.base.comp.gpa;
-        const gop = try macho_file.undefs.getOrPut(gpa, rel.target);
+        const gop = try macho_file.undefs.getOrPut(gpa, .{ .index = rel.target, .file = self.file });
         if (!gop.found_existing) {
             gop.value_ptr.* = .{};
         }
-        try gop.value_ptr.append(gpa, self.atom_index);
+        try gop.value_ptr.append(gpa, .{ .index = self.atom_index, .file = self.file });
         return true;
     }
 
@@ -582,7 +567,7 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
     const name = self.getName(macho_file);
     const relocs = self.getRelocs(macho_file);
 
-    relocs_log.debug("{x}: {s}", .{ self.getAddress(macho_file), name });
+    relocs_log.debug("{x}: {s}", .{ self.value, name });
 
     var has_error = false;
     var stream = std.io.fixedBufferStream(buffer);
@@ -593,7 +578,7 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
         const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null;
 
         if (rel.tag == .@"extern") {
-            if (rel.getTargetSymbol(macho_file).getFile(macho_file) == null) continue;
+            if (rel.getTargetSymbol(self, macho_file).getFile(macho_file) == null) continue;
         }
 
         try stream.seekTo(rel_offset);
@@ -601,8 +586,8 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
             switch (err) {
                 error.RelaxFail => {
                     const target = switch (rel.tag) {
-                        .@"extern" => rel.getTargetSymbol(macho_file).getName(macho_file),
-                        .local => rel.getTargetAtom(macho_file).getName(macho_file),
+                        .@"extern" => rel.getTargetSymbol(self, macho_file).getName(macho_file),
+                        .local => rel.getTargetAtom(self, macho_file).getName(macho_file),
                     };
                     try macho_file.reportParseError2(
                         file.getIndex(),
@@ -642,12 +627,12 @@ fn resolveRelocInner(
     const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
     const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset));
     const A = rel.addend + rel.getRelocAddend(cpu_arch);
-    const S: i64 = @intCast(rel.getTargetAddress(macho_file));
-    const G: i64 = @intCast(rel.getGotTargetAddress(macho_file));
+    const S: i64 = @intCast(rel.getTargetAddress(self, macho_file));
+    const G: i64 = @intCast(rel.getGotTargetAddress(self, macho_file));
     const TLS = @as(i64, @intCast(macho_file.getTlsAddress()));
-    const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(macho_file))) else 0;
+    const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(self, macho_file))) else 0;
     // Address of the __got_zig table entry if any.
-    const ZIG_GOT = @as(i64, @intCast(rel.getZigGotTargetAddress(macho_file)));
+    const ZIG_GOT = @as(i64, @intCast(rel.getZigGotTargetAddress(self, macho_file)));
 
     const divExact = struct {
         fn divExact(atom: Atom, r: Relocation, num: u12, den: u12, ctx: *MachO) !u12 {
@@ -668,7 +653,7 @@ fn resolveRelocInner(
             rel_offset,
             @tagName(rel.type),
             S + A - SUB,
-            rel.getTargetAtom(macho_file).atom_index,
+            rel.getTargetAtom(self, macho_file).atom_index,
         }),
         .@"extern" => relocs_log.debug("  {x}<+{d}>: {s}: [=> {x}] G({x}) ZG({x}) ({s})", .{
             P,
@@ -677,7 +662,7 @@ fn resolveRelocInner(
             S + A - SUB,
             G + A,
             ZIG_GOT + A,
-            rel.getTargetSymbol(macho_file).getName(macho_file),
+            rel.getTargetSymbol(self, macho_file).getName(macho_file),
         }),
     }
 
@@ -688,7 +673,7 @@ fn resolveRelocInner(
             assert(!rel.meta.pcrel);
             if (rel.meta.length == 3) {
                 if (rel.tag == .@"extern") {
-                    const sym = rel.getTargetSymbol(macho_file);
+                    const sym = rel.getTargetSymbol(self, macho_file);
                     if (sym.isTlvInit(macho_file)) {
                         try writer.writeInt(u64, @intCast(S - TLS), .little);
                         return;
@@ -718,7 +703,7 @@ fn resolveRelocInner(
                 .aarch64 => {
                     const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
                         const thunk = self.getThunk(macho_file);
-                        const S_: i64 = @intCast(thunk.getTargetAddress(rel.target, macho_file));
+                        const S_: i64 = @intCast(thunk.getTargetAddress(rel.getTargetSymbolRef(self, macho_file), macho_file));
                         break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
                     };
                     aarch64.writeBranchImm(disp, code[rel_offset..][0..4]);
@@ -731,7 +716,7 @@ fn resolveRelocInner(
             assert(rel.tag == .@"extern");
             assert(rel.meta.length == 2);
             assert(rel.meta.pcrel);
-            if (rel.getTargetSymbol(macho_file).flags.has_got) {
+            if (rel.getTargetSymbol(self, macho_file).flags.has_got) {
                 try writer.writeInt(i32, @intCast(G + A - P), .little);
             } else {
                 try x86_64.relaxGotLoad(self, code[rel_offset - 3 ..], rel, macho_file);
@@ -754,7 +739,7 @@ fn resolveRelocInner(
             assert(rel.tag == .@"extern");
             assert(rel.meta.length == 2);
             assert(rel.meta.pcrel);
-            const sym = rel.getTargetSymbol(macho_file);
+            const sym = rel.getTargetSymbol(self, macho_file);
             if (sym.flags.tlv_ptr) {
                 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
                 try writer.writeInt(i32, @intCast(S_ + A - P), .little);
@@ -777,7 +762,7 @@ fn resolveRelocInner(
             assert(rel.tag == .@"extern");
             assert(rel.meta.length == 2);
             assert(rel.meta.pcrel);
-            const sym = rel.getTargetSymbol(macho_file);
+            const sym = rel.getTargetSymbol(self, macho_file);
             const source = math.cast(u64, P) orelse return error.Overflow;
             const target = target: {
                 const target = switch (rel.type) {
@@ -836,7 +821,7 @@ fn resolveRelocInner(
             assert(rel.meta.length == 2);
             assert(!rel.meta.pcrel);
 
-            const sym = rel.getTargetSymbol(macho_file);
+            const sym = rel.getTargetSymbol(self, macho_file);
             const target = target: {
                 const target = if (sym.flags.tlv_ptr) blk: {
                     const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
@@ -980,48 +965,47 @@ pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 {
     }
 }
 
-pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void {
+pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: []macho.relocation_info) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
     const cpu_arch = macho_file.getTarget().cpu.arch;
     const relocs = self.getRelocs(macho_file);
-    var stream = std.io.fixedBufferStream(code);
 
+    var i: usize = 0;
     for (relocs) |rel| {
+        defer i += 1;
         const rel_offset = rel.offset - self.off;
         const r_address: i32 = math.cast(i32, self.value + rel_offset) orelse return error.Overflow;
         const r_symbolnum = r_symbolnum: {
             const r_symbolnum: u32 = switch (rel.tag) {
-                .local => rel.getTargetAtom(macho_file).out_n_sect + 1,
-                .@"extern" => rel.getTargetSymbol(macho_file).getOutputSymtabIndex(macho_file).?,
+                .local => rel.getTargetAtom(self, macho_file).out_n_sect + 1,
+                .@"extern" => rel.getTargetSymbol(self, macho_file).getOutputSymtabIndex(macho_file).?,
             };
             break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow;
         };
         const r_extern = rel.tag == .@"extern";
         var addend = rel.addend + rel.getRelocAddend(cpu_arch);
         if (rel.tag == .local) {
-            const target: i64 = @intCast(rel.getTargetAddress(macho_file));
+            const target: i64 = @intCast(rel.getTargetAddress(self, macho_file));
             addend += target;
         }
 
-        try stream.seekTo(rel_offset);
-
         switch (cpu_arch) {
             .aarch64 => {
                 if (rel.type == .unsigned) switch (rel.meta.length) {
                     0, 1 => unreachable,
-                    2 => try stream.writer().writeInt(i32, @truncate(addend), .little),
-                    3 => try stream.writer().writeInt(i64, addend, .little),
+                    2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little),
+                    3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little),
                 } else if (addend > 0) {
-                    buffer.appendAssumeCapacity(.{
+                    buffer[i] = .{
                         .r_address = r_address,
                         .r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow),
                         .r_pcrel = 0,
                         .r_length = 2,
                         .r_extern = 0,
                         .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND),
-                    });
+                    };
                 }
 
                 const r_type: macho.reloc_type_arm64 = switch (rel.type) {
@@ -1045,14 +1029,14 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra
                     .tlv,
                     => unreachable,
                 };
-                buffer.appendAssumeCapacity(.{
+                buffer[i] = .{
                     .r_address = r_address,
                     .r_symbolnum = r_symbolnum,
                     .r_pcrel = @intFromBool(rel.meta.pcrel),
                     .r_extern = @intFromBool(r_extern),
                     .r_length = rel.meta.length,
                     .r_type = @intFromEnum(r_type),
-                });
+                };
             },
             .x86_64 => {
                 if (rel.meta.pcrel) {
@@ -1064,8 +1048,8 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra
                 }
                 switch (rel.meta.length) {
                     0, 1 => unreachable,
-                    2 => try stream.writer().writeInt(i32, @truncate(addend), .little),
-                    3 => try stream.writer().writeInt(i64, addend, .little),
+                    2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little),
+                    3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little),
                 }
 
                 const r_type: macho.reloc_type_x86_64 = switch (rel.type) {
@@ -1089,18 +1073,20 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra
                     .tlvp_pageoff,
                     => unreachable,
                 };
-                buffer.appendAssumeCapacity(.{
+                buffer[i] = .{
                     .r_address = r_address,
                     .r_symbolnum = r_symbolnum,
                     .r_pcrel = @intFromBool(rel.meta.pcrel),
                     .r_extern = @intFromBool(r_extern),
                     .r_length = rel.meta.length,
                     .r_type = @intFromEnum(r_type),
-                });
+                };
             },
             else => unreachable,
         }
     }
+
+    assert(i == buffer.len);
 }
 
 pub fn format(
@@ -1139,16 +1125,15 @@ fn format2(
     const atom = ctx.atom;
     const macho_file = ctx.macho_file;
     const file = atom.getFile(macho_file);
-    try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : nreloc({d})", .{
-        atom.atom_index,                atom.getName(macho_file), atom.getAddress(macho_file),
-        atom.out_n_sect,                atom.alignment,           atom.size,
-        atom.getRelocs(macho_file).len,
+    try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : nreloc({d}) : thunk({d})", .{
+        atom.atom_index,                atom.getName(macho_file),        atom.getAddress(macho_file),
+        atom.out_n_sect,                atom.alignment,                  atom.size,
+        atom.getRelocs(macho_file).len, atom.getExtra(macho_file).thunk,
     });
-    if (atom.flags.thunk) try writer.print(" : thunk({d})", .{atom.getExtra(macho_file).?.thunk});
     if (!atom.flags.alive) try writer.writeAll(" : [*]");
-    if (atom.flags.unwind) {
+    if (atom.getUnwindRecords(macho_file).len > 0) {
         try writer.writeAll(" : unwind{ ");
-        const extra = atom.getExtra(macho_file).?;
+        const extra = atom.getExtra(macho_file);
         for (atom.getUnwindRecords(macho_file), extra.unwind_index..) |index, i| {
             const rec = file.object.getUnwindRecord(index);
             try writer.print("{d}", .{index});
@@ -1167,18 +1152,6 @@ pub const Flags = packed struct {
 
     /// Specifies if this atom has been visited during garbage collection.
     visited: bool = false,
-
-    /// Whether this atom has a range extension thunk.
-    thunk: bool = false,
-
-    /// Whether this atom has any relocations.
-    relocs: bool = false,
-
-    /// Whether this atom has any unwind records.
-    unwind: bool = false,
-
-    /// Whether this atom has LiteralPool entry.
-    literal_pool: bool = false,
 };
 
 pub const Extra = struct {
@@ -1191,6 +1164,12 @@ pub const Extra = struct {
     /// Count of relocations belonging to this atom.
     rel_count: u32 = 0,
 
+    /// Start index of relocations being written out to file for this atom.
+    rel_out_index: u32 = 0,
+
+    /// Count of relocations written out to file for this atom.
+    rel_out_count: u32 = 0,
+
     /// Start index of relocations belonging to this atom.
     unwind_index: u32 = 0,
 
@@ -1198,7 +1177,10 @@ pub const Extra = struct {
     unwind_count: u32 = 0,
 
     /// Index into LiteralPool entry for this atom.
-    literal_index: u32 = 0,
+    literal_pool_index: u32 = 0,
+
+    /// Index into the File's symbol table for local symbol representing this literal atom.
+    literal_symbol_index: u32 = 0,
 };
 
 pub const Alignment = @import("../../InternPool.zig").Alignment;
src/link/MachO/InternalObject.zig
@@ -639,6 +639,12 @@ pub fn asFile(self: *InternalObject) File {
     return .{ .internal = self };
 }
 
+pub fn getAtomRelocs(self: *const InternalObject, atom: Atom, macho_file: *MachO) []const 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 addAtom(self: *InternalObject, allocator: Allocator) !Atom.Index {
     const atom_index: Atom.Index = @intCast(self.atoms.items.len);
     const atom = try self.atoms.addOne(allocator);
src/link/MachO/Object.zig
@@ -1860,7 +1860,7 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void {
                 const name = sym.getName(macho_file);
                 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
             }
-            const sect = macho_file.sections.items(.header)[sym.out_n_sect];
+            const sect = macho_file.sections.items(.header)[sym.getOutputSectionIndex(macho_file)];
             if (sect.isCode()) {
                 self.output_symtab_ctx.nstabs += 4; // N_BNSYM, N_FUN, N_FUN, N_ENSYM
             } else if (sym.visibility == .global) {
@@ -2198,13 +2198,13 @@ pub fn writeStabs(self: *const Object, stroff: u32, macho_file: *MachO) void {
                 const name = sym.getName(macho_file);
                 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
             }
-            const sect = macho_file.sections.items(.header)[sym.out_n_sect];
+            const sect = macho_file.sections.items(.header)[sym.getOutputSectionIndex(macho_file)];
             const sym_n_strx = n_strx: {
                 const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
                 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_sect: u8 = if (!sym.flags.abs) @intCast(sym.getOutputSectionIndex(macho_file) + 1) else 0;
             const sym_n_value = sym.getAddress(.{}, macho_file);
             const sym_size = sym.getSize(macho_file);
             if (sect.isCode()) {
@@ -2299,7 +2299,7 @@ pub fn writeStabs(self: *const Object, stroff: u32, macho_file: *MachO) void {
                     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_sect: u8 = if (!sym.flags.abs) @intCast(sym.getOutputSectionIndex(macho_file) + 1) else 0;
                 const sym_n_value = sym.getAddress(.{}, macho_file);
                 const sym_size = sym.getSize(macho_file);
                 if (stab.is_func) {
@@ -2340,6 +2340,12 @@ pub fn writeStabs(self: *const Object, stroff: u32, macho_file: *MachO) void {
     }
 }
 
+pub fn getAtomRelocs(self: *const Object, atom: Atom, macho_file: *MachO) []const 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);
src/link/MachO/Symbol.zig
@@ -9,17 +9,16 @@ name: u32 = 0,
 /// File where this symbol is defined.
 file: File.Index = 0,
 
-/// Atom containing this symbol if any.
-/// Index of 0 means there is no associated atom with this symbol.
+/// Reference to Atom containing this symbol if any.
 /// Use `getAtom` to get the pointer to the atom.
-atom: Atom.Index = 0,
+atom_ref: MachO.Ref = .{ .index = 0, .file = 0 },
 
 /// Assigned output section index for this symbol.
 out_n_sect: u8 = 0,
 
 /// Index of the source nlist this symbol references.
 /// Use `getNlist` to pull the nlist from the relevant file.
-nlist_idx: Index = 0,
+nlist_idx: u32 = 0,
 
 /// Misc flags for the symbol packaged as packed struct for compression.
 flags: Flags = .{},
@@ -55,16 +54,19 @@ pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool {
 }
 
 pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 {
-    if (symbol.flags.global) return macho_file.strings.getAssumeExists(symbol.name);
     return switch (symbol.getFile(macho_file).?) {
-        .dylib => unreachable, // There are no local symbols for dylibs
         .zig_object => |x| x.strtab.getAssumeExists(symbol.name),
         inline else => |x| x.getString(symbol.name),
     };
 }
 
 pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom {
-    return macho_file.getAtom(symbol.atom);
+    return symbol.atom_ref.getAtom(macho_file);
+}
+
+pub fn getOutputSectionIndex(symbol: Symbol, macho_file: *MachO) u8 {
+    if (symbol.getAtom(macho_file)) |atom| return atom.out_n_sect;
+    return symbol.out_n_sect;
 }
 
 pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
@@ -75,8 +77,10 @@ pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
 pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 {
     const file = symbol.getFile(macho_file).?;
     return switch (file) {
+        .dylib => unreachable,
+        .zig_object => unreachable,
         .object => |x| x.symtab.items(.nlist)[symbol.nlist_idx],
-        else => unreachable,
+        .internal => |x| x.symtab.items[symbol.nlist_idx],
     };
 }
 
@@ -124,33 +128,35 @@ pub fn getAddress(symbol: Symbol, opts: struct {
 
 pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
     if (!symbol.flags.has_got) return 0;
-    const extra = symbol.getExtra(macho_file).?;
+    const extra = symbol.getExtra(macho_file);
     return macho_file.got.getAddress(extra.got, macho_file);
 }
 
 pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
     if (!symbol.flags.stubs) return 0;
-    const extra = symbol.getExtra(macho_file).?;
+    const extra = symbol.getExtra(macho_file);
     return macho_file.stubs.getAddress(extra.stubs, macho_file);
 }
 
 pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
     if (!symbol.flags.objc_stubs) return 0;
-    const extra = symbol.getExtra(macho_file).?;
+    const extra = symbol.getExtra(macho_file);
     return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file);
 }
 
 pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 {
     if (!symbol.flags.objc_stubs) return 0;
-    const extra = symbol.getExtra(macho_file).?;
-    const atom = macho_file.getAtom(extra.objc_selrefs).?;
-    assert(atom.flags.alive);
-    return atom.getAddress(macho_file);
+    const extra = symbol.getExtra(macho_file);
+    const file = symbol.getFile(macho_file).?;
+    return switch (file) {
+        .dylib, .zig_object => unreachable,
+        .object, .internal => |x| x.symbols.items[extra.objc_selrefs].getAddress(.{}, macho_file),
+    };
 }
 
 pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
     if (!symbol.flags.tlv_ptr) return 0;
-    const extra = symbol.getExtra(macho_file).?;
+    const extra = symbol.getExtra(macho_file);
     return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
 }
 
@@ -162,14 +168,14 @@ const GetOrCreateZigGotEntryResult = struct {
 pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult {
     assert(!macho_file.base.isRelocatable());
     assert(symbol.flags.needs_zig_got);
-    if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).?.zig_got };
+    if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).zig_got };
     const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file);
     return .{ .found_existing = false, .index = index };
 }
 
 pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
     if (!symbol.flags.has_zig_got) return 0;
-    const extras = symbol.getExtra(macho_file).?;
+    const extras = symbol.getExtra(macho_file);
     return macho_file.zig_got.entryAddress(extras.zig_got, macho_file);
 }
 
@@ -202,11 +208,8 @@ const AddExtraOpts = struct {
     symtab: ?u32 = null,
 };
 
-pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
-    if (symbol.getExtra(macho_file) == null) {
-        symbol.extra = try macho_file.addSymbolExtra(.{});
-    }
-    var extra = symbol.getExtra(macho_file).?;
+pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) void {
+    var extra = symbol.getExtra(macho_file);
     inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
         if (@field(opts, field.name)) |x| {
             @field(extra, field.name) = x;
@@ -215,18 +218,22 @@ pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
     symbol.setExtra(extra, macho_file);
 }
 
-pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra {
-    return macho_file.getSymbolExtra(symbol.extra);
+pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) Extra {
+    return switch (symbol.getFile(macho_file).?) {
+        inline else => |x| x.getSymbolExtra(symbol.extra),
+    };
 }
 
 pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
-    macho_file.setSymbolExtra(symbol.extra, extra);
+    return switch (symbol.getFile(macho_file).?) {
+        inline else => |x| x.setSymbolExtra(symbol.extra, extra),
+    };
 }
 
 pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
     if (symbol.isLocal()) {
         out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
-        out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
+        out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);
         out.n_desc = 0;
         out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
 
@@ -238,7 +245,7 @@ pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) vo
         assert(symbol.visibility == .global);
         out.n_type = macho.N_EXT;
         out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
-        out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
+        out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);
         out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
         out.n_desc = 0;
 
@@ -318,8 +325,8 @@ fn format2(
         symbol.getAddress(.{}, ctx.macho_file),
     });
     if (symbol.getFile(ctx.macho_file)) |file| {
-        if (symbol.out_n_sect != 0) {
-            try writer.print(" : sect({d})", .{symbol.out_n_sect});
+        if (symbol.getOutputSectionIndex(ctx.macho_file) != 0) {
+            try writer.print(" : sect({d})", .{symbol.getOutputSectionIndex(ctx.macho_file)});
         }
         if (symbol.getAtom(ctx.macho_file)) |atom| {
             try writer.print(" : atom({d})", .{atom.atom_index});
@@ -346,11 +353,6 @@ pub const Flags = packed struct {
     /// Whether the symbol is exported at runtime.
     @"export": bool = false,
 
-    /// Whether the symbol is effectively an extern and takes part in global
-    /// symbol resolution. Then, its name will be saved in global string interning
-    /// table.
-    global: bool = false,
-
     /// Whether this symbol is weak.
     weak: bool = false,
 
src/link/MachO.zig
@@ -22,16 +22,10 @@ dylibs: std.ArrayListUnmanaged(File.Index) = .{},
 segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
 sections: std.MultiArrayList(Section) = .{},
 
-symbols: std.ArrayListUnmanaged(Symbol) = .{},
-symbols_extra: std.ArrayListUnmanaged(u32) = .{},
-symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .{},
-globals: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index) = .{},
+resolver: SymbolResolver = .{},
 /// This table will be populated after `scanRelocs` has run.
 /// Key is symbol index.
-undefs: std.AutoHashMapUnmanaged(Symbol.Index, std.ArrayListUnmanaged(Atom.Index)) = .{},
-/// Global symbols we need to resolve for the link to succeed.
-undefined_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
-boundary_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+undefs: std.AutoHashMapUnmanaged(Ref, std.ArrayListUnmanaged(Ref)) = .{},
 
 dyld_info_cmd: macho.dyld_info_command = .{},
 symtab_cmd: macho.symtab_command = .{},
@@ -55,19 +49,8 @@ eh_frame_sect_index: ?u8 = null,
 unwind_info_sect_index: ?u8 = null,
 objc_stubs_sect_index: ?u8 = null,
 
-mh_execute_header_index: ?Symbol.Index = null,
-mh_dylib_header_index: ?Symbol.Index = null,
-dyld_private_index: ?Symbol.Index = null,
-dyld_stub_binder_index: ?Symbol.Index = null,
-dso_handle_index: ?Symbol.Index = null,
-objc_msg_send_index: ?Symbol.Index = null,
-entry_index: ?Symbol.Index = null,
-
 thunks: std.ArrayListUnmanaged(Thunk) = .{},
 
-/// String interning table
-strings: StringTable = .{},
-
 /// Output synthetic sections
 symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
 strtab: std.ArrayListUnmanaged(u8) = .{},
@@ -4196,7 +4179,7 @@ const Section = struct {
 pub const LiteralPool = struct {
     table: std.AutoArrayHashMapUnmanaged(void, void) = .{},
     keys: std.ArrayListUnmanaged(Key) = .{},
-    values: std.ArrayListUnmanaged(Atom.Index) = .{},
+    values: std.ArrayListUnmanaged(MachO.Ref) = .{},
     data: std.ArrayListUnmanaged(u8) = .{},
 
     pub fn deinit(lp: *LiteralPool, allocator: Allocator) void {
@@ -4206,17 +4189,21 @@ pub const LiteralPool = struct {
         lp.data.deinit(allocator);
     }
 
-    pub fn getAtom(lp: LiteralPool, index: Index, macho_file: *MachO) *Atom {
-        assert(index < lp.values.items.len);
-        return macho_file.getAtom(lp.values.items[index]).?;
-    }
-
     const InsertResult = struct {
         found_existing: bool,
         index: Index,
-        atom: *Atom.Index,
+        ref: *MachO.Ref,
     };
 
+    pub fn getSymbolRef(lp: LiteralPool, index: Index) MachO.Ref {
+        assert(index < lp.values.items.len);
+        return lp.values.items[index];
+    }
+
+    pub fn getSymbol(lp: LiteralPool, index: Index, macho_file: *MachO) *Symbol {
+        return lp.getSymbolRef(index).getSymbol(macho_file).?;
+    }
+
     pub fn insert(lp: *LiteralPool, allocator: Allocator, @"type": u8, string: []const u8) !InsertResult {
         const size: u32 = @intCast(string.len);
         try lp.data.ensureUnusedCapacity(allocator, size);
@@ -4278,12 +4265,6 @@ const HotUpdateState = struct {
     mach_task: ?std.c.MachTask = null,
 };
 
-pub const DynamicRelocs = struct {
-    rebase_relocs: u32 = 0,
-    bind_relocs: u32 = 0,
-    weak_bind_relocs: u32 = 0,
-};
-
 pub const SymtabCtx = struct {
     ilocal: u32 = 0,
     istab: u32 = 0,
@@ -4293,6 +4274,7 @@ pub const SymtabCtx = struct {
     nstabs: u32 = 0,
     nexports: u32 = 0,
     nimports: u32 = 0,
+    stroff: u32 = 0,
     strsize: u32 = 0,
 };
 
@@ -4579,6 +4561,131 @@ const UndefinedTreatment = enum {
     dynamic_lookup,
 };
 
+/// A reference to atom or symbol in an input file.
+/// If file == 0, symbol is an undefined global.
+pub const Ref = struct {
+    index: u32,
+    file: File.Index,
+
+    pub fn eql(ref: Ref, other: Ref) bool {
+        return ref.index == other.index and ref.file == other.file;
+    }
+
+    pub fn getFile(ref: Ref, macho_file: *MachO) ?File {
+        return macho_file.getFile(ref.file);
+    }
+
+    pub fn getAtom(ref: Ref, macho_file: *MachO) ?*Atom {
+        const file = ref.getFile(macho_file) orelse return null;
+        return file.getAtom(ref.index);
+    }
+
+    pub fn getSymbol(ref: Ref, macho_file: *MachO) ?*Symbol {
+        const file = ref.getFile(macho_file) orelse return null;
+        return switch (file) {
+            inline else => |x| &x.symbols.items[ref.index],
+        };
+    }
+
+    pub fn format(
+        ref: Ref,
+        comptime unused_fmt_string: []const u8,
+        options: std.fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        _ = unused_fmt_string;
+        _ = options;
+        try writer.print("%{d} in file({d})", .{ ref.index, ref.file });
+    }
+};
+
+pub const SymbolResolver = struct {
+    keys: std.ArrayListUnmanaged(Key) = .{},
+    values: std.ArrayListUnmanaged(Ref) = .{},
+    table: std.AutoArrayHashMapUnmanaged(void, void) = .{},
+
+    const Result = struct {
+        found_existing: bool,
+        index: Index,
+        ref: *Ref,
+    };
+
+    pub fn deinit(resolver: *SymbolResolver, allocator: Allocator) void {
+        resolver.keys.deinit(allocator);
+        resolver.values.deinit(allocator);
+        resolver.table.deinit(allocator);
+    }
+
+    pub fn getOrPut(
+        resolver: *SymbolResolver,
+        allocator: Allocator,
+        ref: Ref,
+        macho_file: *MachO,
+    ) !Result {
+        const adapter = Adapter{ .keys = resolver.keys.items, .macho_file = macho_file };
+        const key = Key{ .index = ref.index, .file = ref.file };
+        const gop = try resolver.table.getOrPutAdapted(allocator, key, adapter);
+        if (!gop.found_existing) {
+            try resolver.keys.append(allocator, key);
+            _ = try resolver.values.addOne(allocator);
+        }
+        return .{
+            .found_existing = gop.found_existing,
+            .index = @intCast(gop.index + 1),
+            .ref = &resolver.values.items[gop.index],
+        };
+    }
+
+    pub fn get(resolver: SymbolResolver, index: Index) ?Ref {
+        if (index == 0) return null;
+        return resolver.values.items[index - 1];
+    }
+
+    pub fn reset(resolver: *SymbolResolver) void {
+        resolver.keys.clearRetainingCapacity();
+        resolver.values.clearRetainingCapacity();
+        resolver.table.clearRetainingCapacity();
+    }
+
+    const Key = struct {
+        index: Symbol.Index,
+        file: File.Index,
+
+        fn getName(key: Key, macho_file: *MachO) [:0]const u8 {
+            const ref = Ref{ .index = key.index, .file = key.file };
+            return ref.getSymbol(macho_file).?.getName(macho_file);
+        }
+
+        fn eql(key: Key, other: Key, macho_file: *MachO) bool {
+            const key_name = key.getName(macho_file);
+            const other_name = other.getName(macho_file);
+            return mem.eql(u8, key_name, other_name);
+        }
+
+        fn hash(key: Key, macho_file: *MachO) u32 {
+            const name = key.getName(macho_file);
+            return @truncate(Hash.hash(0, name));
+        }
+    };
+
+    const Adapter = struct {
+        keys: []const Key,
+        macho_file: *MachO,
+
+        pub fn eql(ctx: @This(), key: Key, b_void: void, b_map_index: usize) bool {
+            _ = b_void;
+            const other = ctx.keys[b_map_index];
+            return key.eql(other, ctx.macho_file);
+        }
+
+        pub fn hash(ctx: @This(), key: Key) u32 {
+            return key.hash(ctx.macho_file);
+        }
+    };
+
+    pub const Index = u32;
+};
+
 const MachO = @This();
 
 const std = @import("std");