Commit 989639efba

Jakub Konka <kubkon@jakubkonka.com>
2021-07-03 10:48:39
zld: coalesce symbols on creation
1 parent 980f291
Changed files (3)
src/link/MachO/Object.zig
@@ -45,9 +45,12 @@ dwarf_debug_str_index: ?u16 = null,
 dwarf_debug_line_index: ?u16 = null,
 dwarf_debug_ranges_index: ?u16 = null,
 
+symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
+strtab: std.ArrayListUnmanaged(u8) = .{},
+
 symbols: std.ArrayListUnmanaged(*Symbol) = .{},
 stabs: std.ArrayListUnmanaged(*Symbol) = .{},
-initializers: std.ArrayListUnmanaged(*Symbol) = .{},
+initializers: std.ArrayListUnmanaged(u32) = .{},
 data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
 
 pub const Section = struct {
@@ -216,20 +219,13 @@ pub fn deinit(self: *Object) void {
     }
     self.sections.deinit(self.allocator);
 
-    for (self.symbols.items) |sym| {
-        sym.deinit(self.allocator);
-        self.allocator.destroy(sym);
-    }
     self.symbols.deinit(self.allocator);
-
-    for (self.stabs.items) |stab| {
-        stab.deinit(self.allocator);
-        self.allocator.destroy(stab);
-    }
     self.stabs.deinit(self.allocator);
 
     self.data_in_code_entries.deinit(self.allocator);
     self.initializers.deinit(self.allocator);
+    self.symtab.deinit(self.allocator);
+    self.strtab.deinit(self.allocator);
 
     if (self.name) |n| {
         self.allocator.free(n);
@@ -271,11 +267,10 @@ pub fn parse(self: *Object) !void {
     self.header = header;
 
     try self.readLoadCommands(reader);
-    try self.parseSymbols();
     try self.parseSections();
+    try self.parseSymtab();
     try self.parseDataInCode();
     try self.parseInitializers();
-    try self.parseDebugInfo();
 }
 
 pub fn readLoadCommands(self: *Object, reader: anytype) !void {
@@ -394,14 +389,13 @@ pub fn parseInitializers(self: *Object) !void {
     const relocs = section.relocs orelse unreachable;
     try self.initializers.ensureCapacity(self.allocator, relocs.len);
     for (relocs) |rel| {
-        const sym = self.symbols.items[rel.target.symbol];
-        self.initializers.appendAssumeCapacity(sym);
+        self.initializers.appendAssumeCapacity(rel.target.symbol);
     }
 
-    mem.reverse(*Symbol, self.initializers.items);
+    mem.reverse(u32, self.initializers.items);
 }
 
-pub fn parseSymbols(self: *Object) !void {
+fn parseSymtab(self: *Object) !void {
     const index = self.symtab_cmd_index orelse return;
     const symtab_cmd = self.load_commands.items[index].Symtab;
 
@@ -409,59 +403,12 @@ pub fn parseSymbols(self: *Object) !void {
     defer self.allocator.free(symtab);
     _ = try self.file.?.preadAll(symtab, symtab_cmd.symoff);
     const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, symtab));
+    try self.symtab.appendSlice(self.allocator, slice);
 
     var strtab = try self.allocator.alloc(u8, symtab_cmd.strsize);
     defer self.allocator.free(strtab);
     _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff);
-
-    for (slice) |sym| {
-        const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx));
-
-        if (Symbol.isStab(sym)) {
-            log.err("unhandled symbol type: stab {s} in {s}", .{ sym_name, self.name.? });
-            return error.UnhandledSymbolType;
-        }
-        if (Symbol.isIndr(sym)) {
-            log.err("unhandled symbol type: indirect {s} in {s}", .{ sym_name, self.name.? });
-            return error.UnhandledSymbolType;
-        }
-        if (Symbol.isAbs(sym)) {
-            log.err("unhandled symbol type: absolute {s} in {s}", .{ sym_name, self.name.? });
-            return error.UnhandledSymbolType;
-        }
-
-        const name = try self.allocator.dupe(u8, sym_name);
-        const symbol: *Symbol = symbol: {
-            if (Symbol.isSect(sym)) {
-                const linkage: Symbol.Regular.Linkage = linkage: {
-                    if (!Symbol.isExt(sym)) break :linkage .translation_unit;
-                    if (Symbol.isWeakDef(sym) or Symbol.isPext(sym)) break :linkage .linkage_unit;
-                    break :linkage .global;
-                };
-                break :symbol try Symbol.Regular.new(self.allocator, name, .{
-                    .linkage = linkage,
-                    .address = sym.n_value,
-                    .section = sym.n_sect - 1,
-                    .weak_ref = Symbol.isWeakRef(sym),
-                    .file = self,
-                });
-            }
-
-            if (sym.n_value != 0) {
-                break :symbol try Symbol.Tentative.new(self.allocator, name, .{
-                    .size = sym.n_value,
-                    .alignment = (sym.n_desc >> 8) & 0x0f,
-                    .file = self,
-                });
-            }
-
-            break :symbol try Symbol.Unresolved.new(self.allocator, name, .{
-                .file = self,
-            });
-        };
-
-        try self.symbols.append(self.allocator, symbol);
-    }
+    try self.strtab.appendSlice(self.allocator, strtab);
 }
 
 pub fn parseDebugInfo(self: *Object) !void {
@@ -555,14 +502,6 @@ pub fn parseDebugInfo(self: *Object) !void {
     self.stabs.appendAssumeCapacity(delim_stab);
 }
 
-fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 {
-    const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
-    const sect = seg.sections.items[index];
-    var buffer = try allocator.alloc(u8, @intCast(usize, sect.size));
-    _ = try self.file.?.preadAll(buffer, sect.offset);
-    return buffer;
-}
-
 pub fn parseDataInCode(self: *Object) !void {
     const index = self.data_in_code_cmd_index orelse return;
     const data_in_code = self.load_commands.items[index].LinkeditData;
@@ -582,3 +521,16 @@ pub fn parseDataInCode(self: *Object) !void {
         try self.data_in_code_entries.append(self.allocator, dice);
     }
 }
+
+fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 {
+    const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
+    const sect = seg.sections.items[index];
+    var buffer = try allocator.alloc(u8, @intCast(usize, sect.size));
+    _ = try self.file.?.preadAll(buffer, sect.offset);
+    return buffer;
+}
+
+pub fn getString(self: Object, off: u32) []const u8 {
+    assert(off < self.strtab.items.len);
+    return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + off));
+}
src/link/MachO/Symbol.zig
@@ -1,6 +1,7 @@
 const Symbol = @This();
 
 const std = @import("std");
+const assert = std.debug.assert;
 const macho = std.macho;
 const mem = std.mem;
 
@@ -9,177 +10,32 @@ const Dylib = @import("Dylib.zig");
 const Object = @import("Object.zig");
 const StringTable = @import("StringTable.zig");
 
-pub const Type = enum {
-    stab,
-    regular,
-    proxy,
-    unresolved,
-    tentative,
-};
-
-/// Symbol type.
-@"type": Type,
-
 /// Symbol name. Owned slice.
 name: []const u8,
 
-/// Alias of.
-alias: ?*Symbol = null,
-
 /// Index in GOT table for indirection.
 got_index: ?u32 = null,
 
 /// Index in stubs table for late binding.
 stubs_index: ?u32 = null,
 
-pub const Stab = struct {
-    base: Symbol,
-
-    // Symbol kind: function, etc.
-    kind: Kind,
-
-    // Size of stab.
-    size: u64,
-
-    // Base regular symbol for this stub if defined.
-    symbol: ?*Symbol = null,
-
-    // null means self-reference.
-    file: ?*Object = null,
-
-    pub const base_type: Symbol.Type = .stab;
-
-    pub const Kind = enum {
-        so,
-        oso,
-        function,
-        global,
-        static,
-    };
-
-    const Opts = struct {
-        kind: Kind = .so,
-        size: u64 = 0,
-        symbol: ?*Symbol = null,
-        file: ?*Object = null,
-    };
-
-    pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
-        const stab = try allocator.create(Stab);
-        errdefer allocator.destroy(stab);
-
-        stab.* = .{
-            .base = .{
-                .@"type" = .stab,
-                .name = try allocator.dupe(u8, name),
-            },
-            .kind = opts.kind,
-            .size = opts.size,
-            .symbol = opts.symbol,
-            .file = opts.file,
+payload: union(enum) {
+    regular: Regular,
+    tentative: Tentative,
+    proxy: Proxy,
+    undef: Undefined,
+
+    pub fn format(self: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        return switch (self) {
+            .regular => |p| p.format(fmt, options, writer),
+            .tentative => |p| p.format(fmt, options, writer),
+            .proxy => |p| p.format(fmt, options, writer),
+            .undef => |p| p.format(fmt, options, writer),
         };
-
-        return &stab.base;
     }
-
-    pub fn asNlists(stab: *Stab, allocator: *Allocator, strtab: *StringTable) ![]macho.nlist_64 {
-        var out = std.ArrayList(macho.nlist_64).init(allocator);
-        defer out.deinit();
-        if (stab.kind == .so) {
-            try out.append(.{
-                .n_strx = try strtab.getOrPut(stab.base.name),
-                .n_type = macho.N_SO,
-                .n_sect = 0,
-                .n_desc = 0,
-                .n_value = 0,
-            });
-        } else if (stab.kind == .oso) {
-            const mtime = mtime: {
-                const object = stab.file orelse break :mtime 0;
-                break :mtime object.mtime orelse 0;
-            };
-            try out.append(.{
-                .n_strx = try strtab.getOrPut(stab.base.name),
-                .n_type = macho.N_OSO,
-                .n_sect = 0,
-                .n_desc = 1,
-                .n_value = mtime,
-            });
-        } else outer: {
-            const symbol = stab.symbol orelse unreachable;
-            const regular = symbol.getTopmostAlias().cast(Regular) orelse unreachable;
-            const is_match = blk: {
-                if (regular.file == null and stab.file == null) break :blk true;
-                if (regular.file) |f1| {
-                    if (stab.file) |f2| {
-                        if (f1 == f2) break :blk true;
-                    }
-                }
-                break :blk false;
-            };
-            if (!is_match) break :outer;
-
-            switch (stab.kind) {
-                .function => {
-                    try out.ensureUnusedCapacity(4);
-                    out.appendAssumeCapacity(.{
-                        .n_strx = 0,
-                        .n_type = macho.N_BNSYM,
-                        .n_sect = regular.section,
-                        .n_desc = 0,
-                        .n_value = regular.address,
-                    });
-                    out.appendAssumeCapacity(.{
-                        .n_strx = try strtab.getOrPut(stab.base.name),
-                        .n_type = macho.N_FUN,
-                        .n_sect = regular.section,
-                        .n_desc = 0,
-                        .n_value = regular.address,
-                    });
-                    out.appendAssumeCapacity(.{
-                        .n_strx = 0,
-                        .n_type = macho.N_FUN,
-                        .n_sect = 0,
-                        .n_desc = 0,
-                        .n_value = stab.size,
-                    });
-                    out.appendAssumeCapacity(.{
-                        .n_strx = 0,
-                        .n_type = macho.N_ENSYM,
-                        .n_sect = regular.section,
-                        .n_desc = 0,
-                        .n_value = stab.size,
-                    });
-                },
-                .global => {
-                    try out.append(.{
-                        .n_strx = try strtab.getOrPut(stab.base.name),
-                        .n_type = macho.N_GSYM,
-                        .n_sect = 0,
-                        .n_desc = 0,
-                        .n_value = 0,
-                    });
-                },
-                .static => {
-                    try out.append(.{
-                        .n_strx = try strtab.getOrPut(stab.base.name),
-                        .n_type = macho.N_STSYM,
-                        .n_sect = regular.section,
-                        .n_desc = 0,
-                        .n_value = regular.address,
-                    });
-                },
-                .so, .oso => unreachable,
-            }
-        }
-
-        return out.toOwnedSlice();
-    }
-};
+},
 
 pub const Regular = struct {
-    base: Symbol,
-
     /// Linkage type.
     linkage: Linkage,
 
@@ -196,77 +52,56 @@ pub const Regular = struct {
     /// null means self-reference.
     file: ?*Object = null,
 
-    /// True if symbol was already committed into the final
-    /// symbol table.
-    visited: bool = false,
-
-    pub const base_type: Symbol.Type = .regular;
-
     pub const Linkage = enum {
         translation_unit,
         linkage_unit,
         global,
     };
 
-    const Opts = struct {
-        linkage: Linkage = .translation_unit,
-        address: u64 = 0,
-        section: u8 = 0,
-        weak_ref: bool = false,
-        file: ?*Object = null,
-    };
-
-    pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
-        const reg = try allocator.create(Regular);
-        errdefer allocator.destroy(reg);
-
-        reg.* = .{
-            .base = .{
-                .@"type" = .regular,
-                .name = try allocator.dupe(u8, name),
-            },
-            .linkage = opts.linkage,
-            .address = opts.address,
-            .section = opts.section,
-            .weak_ref = opts.weak_ref,
-            .file = opts.file,
-        };
-
-        return &reg.base;
+    pub fn isTemp(regular: Regular) bool {
+        if (regular.linkage == .translation_unit) {
+            return mem.startsWith(u8, regular.base.name, "l") or mem.startsWith(u8, regular.base.name, "L");
+        }
+        return false;
     }
 
-    pub fn asNlist(regular: *Regular, strtab: *StringTable) !macho.nlist_64 {
-        const n_strx = try strtab.getOrPut(regular.base.name);
-        var nlist = macho.nlist_64{
-            .n_strx = n_strx,
-            .n_type = macho.N_SECT,
-            .n_sect = regular.section,
-            .n_desc = 0,
-            .n_value = regular.address,
-        };
-
-        if (regular.linkage != .translation_unit) {
-            nlist.n_type |= macho.N_EXT;
+    pub fn format(self: Regular, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        try std.fmt.format(writer, "Regular {{ ", .{});
+        try std.fmt.format(writer, ".linkage = {s},  ", .{self.linkage});
+        try std.fmt.format(writer, ".address = 0x{x}, ", .{self.address});
+        try std.fmt.format(writer, ".section = {}, ", .{self.section});
+        if (self.weak_ref) {
+            try std.fmt.format(writer, ".weak_ref, ", .{});
         }
-        if (regular.linkage == .linkage_unit) {
-            nlist.n_type |= macho.N_PEXT;
-            nlist.n_desc |= macho.N_WEAK_DEF;
+        if (self.file) |file| {
+            try std.fmt.format(writer, ".file = {s}, ", .{file.name.?});
         }
-
-        return nlist;
+        try std.fmt.format(writer, "}}", .{});
     }
+};
 
-    pub fn isTemp(regular: *Regular) bool {
-        if (regular.linkage == .translation_unit) {
-            return mem.startsWith(u8, regular.base.name, "l") or mem.startsWith(u8, regular.base.name, "L");
+pub const Tentative = struct {
+    /// Symbol size.
+    size: u64,
+
+    /// Symbol alignment as power of two.
+    alignment: u16,
+
+    /// File where this symbol was referenced.
+    file: ?*Object = null,
+
+    pub fn format(self: Tentative, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        try std.fmt.format(writer, "Tentative {{ ", .{});
+        try std.fmt.format(writer, ".size = 0x{x},  ", .{self.size});
+        try std.fmt.format(writer, ".alignment = 0x{x}, ", .{self.alignment});
+        if (self.file) |file| {
+            try std.fmt.format(writer, ".file = {s}, ", .{file.name.?});
         }
-        return false;
+        try std.fmt.format(writer, "}}", .{});
     }
 };
 
 pub const Proxy = struct {
-    base: Symbol,
-
     /// Dynamic binding info - spots within the final
     /// executable where this proxy is referenced from.
     bind_info: std.ArrayListUnmanaged(struct {
@@ -278,159 +113,121 @@ pub const Proxy = struct {
     /// null means self-reference.
     file: ?*Dylib = null,
 
-    pub const base_type: Symbol.Type = .proxy;
-
-    const Opts = struct {
-        file: ?*Dylib = null,
-    };
-
-    pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
-        const proxy = try allocator.create(Proxy);
-        errdefer allocator.destroy(proxy);
-
-        proxy.* = .{
-            .base = .{
-                .@"type" = .proxy,
-                .name = try allocator.dupe(u8, name),
-            },
-            .file = opts.file,
-        };
-
-        return &proxy.base;
-    }
-
-    pub fn asNlist(proxy: *Proxy, strtab: *StringTable) !macho.nlist_64 {
-        const n_strx = try strtab.getOrPut(proxy.base.name);
-        return macho.nlist_64{
-            .n_strx = n_strx,
-            .n_type = macho.N_UNDF | macho.N_EXT,
-            .n_sect = 0,
-            .n_desc = (proxy.dylibOrdinal() * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
-            .n_value = 0,
-        };
-    }
-
     pub fn deinit(proxy: *Proxy, allocator: *Allocator) void {
         proxy.bind_info.deinit(allocator);
     }
 
-    pub fn dylibOrdinal(proxy: *Proxy) u16 {
+    pub fn dylibOrdinal(proxy: Proxy) u16 {
         const dylib = proxy.file orelse return 0;
         return dylib.ordinal.?;
     }
-};
 
-pub const Unresolved = struct {
-    base: Symbol,
+    pub fn format(self: Proxy, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        try std.fmt.format(writer, "Proxy {{ ", .{});
+        if (self.bind_info.items.len > 0) {
+            // TODO
+            try std.fmt.format(writer, ".bind_info = {}, ", .{self.bind_info.items.len});
+        }
+        if (self.file) |file| {
+            try std.fmt.format(writer, ".file = {s}, ", .{file.name.?});
+        }
+        try std.fmt.format(writer, "}}", .{});
+    }
+};
 
+pub const Undefined = struct {
     /// File where this symbol was referenced.
     /// null means synthetic, e.g., dyld_stub_binder.
     file: ?*Object = null,
 
-    pub const base_type: Symbol.Type = .unresolved;
-
-    const Opts = struct {
-        file: ?*Object = null,
-    };
-
-    pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
-        const undef = try allocator.create(Unresolved);
-        errdefer allocator.destroy(undef);
-
-        undef.* = .{
-            .base = .{
-                .@"type" = .unresolved,
-                .name = try allocator.dupe(u8, name),
-            },
-            .file = opts.file,
-        };
-
-        return &undef.base;
-    }
-
-    pub fn asNlist(undef: *Unresolved, strtab: *StringTable) !macho.nlist_64 {
-        const n_strx = try strtab.getOrPut(undef.base.name);
-        return macho.nlist_64{
-            .n_strx = n_strx,
-            .n_type = macho.N_UNDF,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        };
+    pub fn format(self: Undefined, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        try std.fmt.format(writer, "Undefined {{ ", .{});
+        if (self.file) |file| {
+            try std.fmt.format(writer, ".file = {s}, ", .{file.name.?});
+        }
+        try std.fmt.format(writer, "}}", .{});
     }
 };
 
-pub const Tentative = struct {
-    base: Symbol,
+/// Create new undefined symbol.
+pub fn new(allocator: *Allocator, name: []const u8) !*Symbol {
+    const new_sym = try allocator.create(Symbol);
+    errdefer allocator.destroy(new_sym);
 
-    /// Symbol size.
-    size: u64,
-
-    /// Symbol alignment as power of two.
-    alignment: u16,
-
-    /// File where this symbol was referenced.
-    file: ?*Object = null,
-
-    pub const base_type: Symbol.Type = .tentative;
-
-    const Opts = struct {
-        size: u64 = 0,
-        alignment: u16 = 0,
-        file: ?*Object = null,
+    new_sym.* = .{
+        .name = try allocator.dupe(u8, name),
+        .payload = .{
+            .undef = .{},
+        },
     };
 
-    pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
-        const tent = try allocator.create(Tentative);
-        errdefer allocator.destroy(tent);
-
-        tent.* = .{
-            .base = .{
-                .@"type" = .tentative,
-                .name = try allocator.dupe(u8, name),
-            },
-            .size = opts.size,
-            .alignment = opts.alignment,
-            .file = opts.file,
-        };
-
-        return &tent.base;
-    }
-
-    pub fn asNlist(tent: *Tentative, strtab: *StringTable) !macho.nlist_64 {
-        // TODO
-        const n_strx = try strtab.getOrPut(tent.base.name);
-        return macho.nlist_64{
-            .n_strx = n_strx,
-            .n_type = macho.N_UNDF,
-            .n_sect = 0,
-            .n_desc = 0,
-            .n_value = 0,
-        };
-    }
-};
+    return new_sym;
+}
 
-pub fn deinit(base: *Symbol, allocator: *Allocator) void {
-    allocator.free(base.name);
+pub fn asNlist(symbol: *Symbol, strtab: *StringTable) macho.nlist_64 {
+    const n_strx = try strtab.getOrPut(symbol.name);
+    const nlist = nlist: {
+        switch (symbol.payload) {
+            .regular => |regular| {
+                var nlist = macho.nlist_64{
+                    .n_strx = n_strx,
+                    .n_type = macho.N_SECT,
+                    .n_sect = regular.section,
+                    .n_desc = 0,
+                    .n_value = regular.address,
+                };
+
+                if (regular.linkage != .translation_unit) {
+                    nlist.n_type |= macho.N_EXT;
+                }
+                if (regular.linkage == .linkage_unit) {
+                    nlist.n_type |= macho.N_PEXT;
+                    nlist.n_desc |= macho.N_WEAK_DEF;
+                }
 
-    switch (base.@"type") {
-        .proxy => @fieldParentPtr(Proxy, "base", base).deinit(allocator),
-        else => {},
-    }
+                break :nlist nlist;
+            },
+            .tentative => |tentative| {
+                // TODO
+                break :nlist macho.nlist_64{
+                    .n_strx = n_strx,
+                    .n_type = macho.N_UNDF,
+                    .n_sect = 0,
+                    .n_desc = 0,
+                    .n_value = 0,
+                };
+            },
+            .proxy => |proxy| {
+                break :nlist macho.nlist_64{
+                    .n_strx = n_strx,
+                    .n_type = macho.N_UNDF | macho.N_EXT,
+                    .n_sect = 0,
+                    .n_desc = (proxy.dylibOrdinal() * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
+                    .n_value = 0,
+                };
+            },
+            .undef => |undef| {
+                // TODO
+                break :nlist macho.nlist_64{
+                    .n_strx = n_strx,
+                    .n_type = macho.N_UNDF,
+                    .n_sect = 0,
+                    .n_desc = 0,
+                    .n_value = 0,
+                };
+            },
+        }
+    };
+    return nlist;
 }
 
-pub fn cast(base: *Symbol, comptime T: type) ?*T {
-    if (base.@"type" != T.base_type) {
-        return null;
-    }
-    return @fieldParentPtr(T, "base", base);
-}
+pub fn deinit(symbol: *Symbol, allocator: *Allocator) void {
+    allocator.free(symbol.name);
 
-pub fn getTopmostAlias(base: *Symbol) *Symbol {
-    if (base.alias) |alias| {
-        return alias.getTopmostAlias();
+    switch (symbol.payload) {
+        .proxy => |*proxy| proxy.deinit(allocator),
+        else => {},
     }
-    return base;
 }
 
 pub fn isStab(sym: macho.nlist_64) bool {
src/link/MachO/Zld.zig
@@ -102,10 +102,9 @@ objc_selrefs_section_index: ?u16 = null,
 objc_classrefs_section_index: ?u16 = null,
 objc_data_section_index: ?u16 = null,
 
+locals: std.ArrayListUnmanaged(*Symbol) = .{},
 globals: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
 imports: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
-unresolved: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
-tentatives: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
 
 /// Offset into __DATA,__common section.
 /// Set if the linker found tentative definitions in any of the objects.
@@ -173,15 +172,24 @@ pub fn deinit(self: *Zld) void {
     }
     self.dylibs.deinit(self.allocator);
 
-    for (self.imports.values()) |proxy| {
-        proxy.deinit(self.allocator);
-        self.allocator.destroy(proxy);
+    for (self.imports.values()) |sym| {
+        sym.deinit(self.allocator);
+        self.allocator.destroy(sym);
     }
     self.imports.deinit(self.allocator);
 
-    self.tentatives.deinit(self.allocator);
+    for (self.globals.values()) |sym| {
+        sym.deinit(self.allocator);
+        self.allocator.destroy(sym);
+    }
     self.globals.deinit(self.allocator);
-    self.unresolved.deinit(self.allocator);
+
+    for (self.locals.items) |sym| {
+        sym.deinit(self.allocator);
+        self.allocator.destroy(sym);
+    }
+    self.locals.deinit(self.allocator);
+
     self.strtab.deinit();
 }
 
@@ -221,20 +229,21 @@ pub fn link(self: *Zld, files: []const []const u8, output: Output, args: LinkArg
     try self.parseInputFiles(files, args.syslibroot);
     try self.parseLibs(args.libs, args.syslibroot);
     try self.resolveSymbols();
-    try self.resolveStubsAndGotEntries();
-    try self.updateMetadata();
-    try self.sortSections();
-    try self.addRpaths(args.rpaths);
-    try self.addDataInCodeLC();
-    try self.addCodeSignatureLC();
-    try self.allocateTextSegment();
-    try self.allocateDataConstSegment();
-    try self.allocateDataSegment();
-    self.allocateLinkeditSegment();
-    try self.allocateSymbols();
-    try self.allocateTentativeSymbols();
-    try self.allocateProxyBindAddresses();
-    try self.flush();
+    return error.TODO;
+    // try self.resolveStubsAndGotEntries();
+    // try self.updateMetadata();
+    // try self.sortSections();
+    // try self.addRpaths(args.rpaths);
+    // try self.addDataInCodeLC();
+    // try self.addCodeSignatureLC();
+    // try self.allocateTextSegment();
+    // try self.allocateDataConstSegment();
+    // try self.allocateDataSegment();
+    // self.allocateLinkeditSegment();
+    // try self.allocateSymbols();
+    // try self.allocateTentativeSymbols();
+    // try self.allocateProxyBindAddresses();
+    // try self.flush();
 }
 
 fn parseInputFiles(self: *Zld, files: []const []const u8, syslibroot: ?[]const u8) !void {
@@ -1458,92 +1467,100 @@ fn writeStubInStubHelper(self: *Zld, index: u32) !void {
 fn resolveSymbolsInObject(self: *Zld, object: *Object) !void {
     log.debug("resolving symbols in '{s}'", .{object.name});
 
-    for (object.symbols.items) |sym| {
-        if (sym.cast(Symbol.Regular)) |reg| {
-            if (reg.linkage == .translation_unit) continue; // Symbol local to TU.
+    for (object.symtab.items) |sym| {
+        const sym_name = object.getString(sym.n_strx);
 
-            if (self.tentatives.fetchSwapRemove(sym.name)) |kv| {
-                // Create link to the global.
-                kv.value.alias = sym;
-            }
-            if (self.unresolved.fetchSwapRemove(sym.name)) |kv| {
-                // Create link to the global.
-                kv.value.alias = sym;
-            }
-            const sym_ptr = self.globals.getPtr(sym.name) orelse {
-                // Put new global symbol into the symbol table.
-                try self.globals.putNoClobber(self.allocator, sym.name, sym);
-                continue;
-            };
-            const g_sym = sym_ptr.*;
-            const g_reg = g_sym.cast(Symbol.Regular) orelse unreachable;
-
-            switch (g_reg.linkage) {
-                .translation_unit => unreachable,
-                .linkage_unit => {
-                    if (reg.linkage == .linkage_unit) {
-                        // Create link to the first encountered linkage_unit symbol.
-                        sym.alias = g_sym;
-                        continue;
-                    }
-                },
-                .global => {
-                    if (reg.linkage == .global) {
-                        log.debug("symbol '{s}' defined multiple times", .{reg.base.name});
-                        return error.MultipleSymbolDefinitions;
-                    }
-                    sym.alias = g_sym;
-                    continue;
-                },
-            }
+        if (Symbol.isStab(sym)) {
+            log.err("unhandled symbol type: stab {s}", .{sym_name});
+            log.err("  | first definition in {s}", .{object.name.?});
+            return error.UnhandledSymbolType;
+        }
 
-            g_sym.alias = sym;
-            sym_ptr.* = sym;
-        } else if (sym.cast(Symbol.Tentative)) |tent| {
-            if (self.globals.get(sym.name)) |g_sym| {
-                sym.alias = g_sym;
-                continue;
-            }
+        if (Symbol.isIndr(sym)) {
+            log.err("unhandled symbol type: indirect {s}", .{sym_name});
+            log.err("  | first definition in {s}", .{object.name.?});
+            return error.UnhandledSymbolType;
+        }
 
-            if (self.unresolved.fetchSwapRemove(sym.name)) |kv| {
-                kv.value.alias = sym;
-            }
+        if (Symbol.isAbs(sym)) {
+            log.err("unhandled symbol type: absolute {s}", .{sym_name});
+            log.err("  | first definition in {s}", .{object.name.?});
+            return error.UnhandledSymbolType;
+        }
 
-            const sym_ptr = self.tentatives.getPtr(sym.name) orelse {
-                // Put new tentative definition symbol into symbol table.
-                try self.tentatives.putNoClobber(self.allocator, sym.name, sym);
-                continue;
+        if (Symbol.isSect(sym) and !Symbol.isExt(sym)) {
+            // Regular symbol local to translation unit
+            const symbol = try Symbol.new(self.allocator, sym_name);
+            symbol.payload = .{
+                .regular = .{
+                    .linkage = .translation_unit,
+                    .address = sym.n_value,
+                    .section = sym.n_sect - 1,
+                    .weak_ref = Symbol.isWeakRef(sym),
+                    .file = object,
+                },
             };
+            try self.locals.append(self.allocator, symbol);
+            try object.symbols.append(self.allocator, symbol);
+            continue;
+        }
 
-            // Compare by size and pick the largest tentative definition.
-            // We model this like a heap where the tentative definition with the
-            // largest size always washes up on top.
-            const t_sym = sym_ptr.*;
-            const t_tent = t_sym.cast(Symbol.Tentative) orelse unreachable;
+        const symbol = self.globals.get(sym_name) orelse symbol: {
+            // Insert new global symbol.
+            const symbol = try Symbol.new(self.allocator, sym_name);
+            symbol.payload.undef.file = object;
+            try self.globals.putNoClobber(self.allocator, symbol.name, symbol);
+            break :symbol symbol;
+        };
 
-            if (tent.size < t_tent.size) {
-                sym.alias = t_sym;
-                continue;
+        if (Symbol.isSect(sym)) {
+            // Global symbol
+            const linkage: Symbol.Regular.Linkage = if (Symbol.isWeakDef(sym) or Symbol.isPext(sym))
+                .linkage_unit
+            else
+                .global;
+
+            const should_update = if (symbol.payload == .regular) blk: {
+                if (symbol.payload.regular.linkage == .global and linkage == .global) {
+                    log.err("symbol '{s}' defined multiple times", .{sym_name});
+                    log.err("  | first definition in {s}", .{symbol.payload.regular.file.?.name.?});
+                    log.err("  | next definition in {s}", .{object.name.?});
+                    return error.MultipleSymbolDefinitions;
+                }
+                break :blk symbol.payload.regular.linkage != .global;
+            } else true;
+
+            if (should_update) {
+                symbol.payload = .{
+                    .regular = .{
+                        .linkage = linkage,
+                        .address = sym.n_value,
+                        .section = sym.n_sect - 1,
+                        .weak_ref = Symbol.isWeakRef(sym),
+                        .file = object,
+                    },
+                };
             }
+        } else if (sym.n_value != 0) {
+            // Tentative definition
+            const should_update = switch (symbol.payload) {
+                .tentative => |tent| tent.size < sym.n_value,
+                .undef => true,
+                else => false,
+            };
 
-            t_sym.alias = sym;
-            sym_ptr.* = sym;
-        } else if (sym.cast(Symbol.Unresolved)) |_| {
-            if (self.globals.get(sym.name)) |g_sym| {
-                sym.alias = g_sym;
-                continue;
-            }
-            if (self.tentatives.get(sym.name)) |t_sym| {
-                sym.alias = t_sym;
-                continue;
-            }
-            if (self.unresolved.get(sym.name)) |u_sym| {
-                sym.alias = u_sym;
-                continue;
+            if (should_update) {
+                symbol.payload = .{
+                    .tentative = .{
+                        .size = sym.n_value,
+                        .alignment = (sym.n_desc >> 8) & 0x0f,
+                        .file = object,
+                    },
+                };
             }
+        }
 
-            try self.unresolved.putNoClobber(self.allocator, sym.name, sym);
-        } else unreachable;
+        try object.symbols.append(self.allocator, symbol);
     }
 }
 
@@ -1553,111 +1570,123 @@ fn resolveSymbols(self: *Zld) !void {
         try self.resolveSymbolsInObject(object);
     }
 
-    // Second pass, resolve symbols in static libraries.
-    var next_sym: usize = 0;
-    while (true) {
-        if (next_sym == self.unresolved.count()) break;
-
-        const sym = self.unresolved.values()[next_sym];
-
-        var reset: bool = false;
-        for (self.archives.items) |archive| {
-            // Check if the entry exists in a static archive.
-            const offsets = archive.toc.get(sym.name) orelse {
-                // No hit.
-                continue;
-            };
-            assert(offsets.items.len > 0);
-
-            const object = try archive.parseObject(offsets.items[0]);
-            try self.objects.append(self.allocator, object);
-            try self.resolveSymbolsInObject(object);
-
-            reset = true;
-            break;
-        }
-
-        if (reset) {
-            next_sym = 0;
-        } else {
-            next_sym += 1;
-        }
-    }
-
-    // Third pass, resolve symbols in dynamic libraries.
-    var unresolved = std.ArrayList(*Symbol).init(self.allocator);
-    defer unresolved.deinit();
-
-    try unresolved.ensureCapacity(self.unresolved.count());
-    for (self.unresolved.values()) |value| {
-        unresolved.appendAssumeCapacity(value);
-    }
-    self.unresolved.clearRetainingCapacity();
-
-    // Put dyld_stub_binder as an unresolved special symbol.
-    {
-        const name = try self.allocator.dupe(u8, "dyld_stub_binder");
-        errdefer self.allocator.free(name);
-        const undef = try Symbol.Unresolved.new(self.allocator, name, .{});
-        try unresolved.append(undef);
-    }
-
-    var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator);
-    defer referenced.deinit();
-
-    loop: while (unresolved.popOrNull()) |undef| {
-        const proxy = self.imports.get(undef.name) orelse outer: {
-            const proxy = inner: {
-                for (self.dylibs.items) |dylib| {
-                    const proxy = (try dylib.createProxy(undef.name)) orelse continue;
-                    try referenced.put(dylib, {});
-                    break :inner proxy;
-                }
-                if (mem.eql(u8, undef.name, "___dso_handle")) {
-                    // TODO this is just a temp patch until I work out what to actually
-                    // do with ___dso_handle and __mh_execute_header symbols which are
-                    // synthetically created by the linker on macOS.
-                    break :inner try Symbol.Proxy.new(self.allocator, undef.name, .{});
-                }
-
-                self.unresolved.putAssumeCapacityNoClobber(undef.name, undef);
-                continue :loop;
-            };
-
-            try self.imports.putNoClobber(self.allocator, proxy.name, proxy);
-            break :outer proxy;
-        };
-        undef.alias = proxy;
+    log.warn("globals", .{});
+    for (self.globals.values()) |value| {
+        log.warn("  | {s}: {}", .{ value.name, value.payload });
     }
 
-    // Add LC_LOAD_DYLIB load command for each referenced dylib/stub.
-    var it = referenced.iterator();
-    while (it.next()) |entry| {
-        const dylib = entry.key_ptr.*;
-        dylib.ordinal = self.next_dylib_ordinal;
-        const dylib_id = dylib.id orelse unreachable;
-        var dylib_cmd = try createLoadDylibCommand(
-            self.allocator,
-            dylib_id.name,
-            dylib_id.timestamp,
-            dylib_id.current_version,
-            dylib_id.compatibility_version,
-        );
-        errdefer dylib_cmd.deinit(self.allocator);
-        try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
-        self.next_dylib_ordinal += 1;
-    }
-
-    if (self.unresolved.count() > 0) {
-        for (self.unresolved.values()) |undef| {
-            log.err("undefined reference to symbol '{s}'", .{undef.name});
-            if (undef.cast(Symbol.Unresolved).?.file) |file| {
-                log.err("    | referenced in {s}", .{file.name.?});
-            }
+    for (self.objects.items) |object| {
+        log.warn("object {s}", .{object.name.?});
+        for (object.symbols.items) |sym| {
+            log.warn("  | {s}: {}", .{ sym.name, sym.payload });
         }
-
-        return error.UndefinedSymbolReference;
     }
+
+    // // Second pass, resolve symbols in static libraries.
+    // var next_sym: usize = 0;
+    // while (true) {
+    //     if (next_sym == self.unresolved.count()) break;
+
+    //     const sym = self.unresolved.values()[next_sym];
+
+    //     var reset: bool = false;
+    //     for (self.archives.items) |archive| {
+    //         // Check if the entry exists in a static archive.
+    //         const offsets = archive.toc.get(sym.name) orelse {
+    //             // No hit.
+    //             continue;
+    //         };
+    //         assert(offsets.items.len > 0);
+
+    //         const object = try archive.parseObject(offsets.items[0]);
+    //         try self.objects.append(self.allocator, object);
+    //         try self.resolveSymbolsInObject(object);
+
+    //         reset = true;
+    //         break;
+    //     }
+
+    //     if (reset) {
+    //         next_sym = 0;
+    //     } else {
+    //         next_sym += 1;
+    //     }
+    // }
+
+    // // Third pass, resolve symbols in dynamic libraries.
+    // var unresolved = std.ArrayList(*Symbol).init(self.allocator);
+    // defer unresolved.deinit();
+
+    // try unresolved.ensureCapacity(self.unresolved.count());
+    // for (self.unresolved.values()) |value| {
+    //     unresolved.appendAssumeCapacity(value);
+    // }
+    // self.unresolved.clearRetainingCapacity();
+
+    // // Put dyld_stub_binder as an unresolved special symbol.
+    // {
+    //     const name = try self.allocator.dupe(u8, "dyld_stub_binder");
+    //     errdefer self.allocator.free(name);
+    //     const undef = try Symbol.Unresolved.new(self.allocator, name, .{});
+    //     try unresolved.append(undef);
+    // }
+
+    // var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator);
+    // defer referenced.deinit();
+
+    // loop: while (unresolved.popOrNull()) |undef| {
+    //     const proxy = self.imports.get(undef.name) orelse outer: {
+    //         const proxy = inner: {
+    //             for (self.dylibs.items) |dylib| {
+    //                 const proxy = (try dylib.createProxy(undef.name)) orelse continue;
+    //                 try referenced.put(dylib, {});
+    //                 break :inner proxy;
+    //             }
+    //             if (mem.eql(u8, undef.name, "___dso_handle")) {
+    //                 // TODO this is just a temp patch until I work out what to actually
+    //                 // do with ___dso_handle and __mh_execute_header symbols which are
+    //                 // synthetically created by the linker on macOS.
+    //                 break :inner try Symbol.Proxy.new(self.allocator, undef.name, .{});
+    //             }
+
+    //             self.unresolved.putAssumeCapacityNoClobber(undef.name, undef);
+    //             continue :loop;
+    //         };
+
+    //         try self.imports.putNoClobber(self.allocator, proxy.name, proxy);
+    //         break :outer proxy;
+    //     };
+    //     undef.alias = proxy;
+    // }
+
+    // // Add LC_LOAD_DYLIB load command for each referenced dylib/stub.
+    // var it = referenced.iterator();
+    // while (it.next()) |entry| {
+    //     const dylib = entry.key_ptr.*;
+    //     dylib.ordinal = self.next_dylib_ordinal;
+    //     const dylib_id = dylib.id orelse unreachable;
+    //     var dylib_cmd = try createLoadDylibCommand(
+    //         self.allocator,
+    //         dylib_id.name,
+    //         dylib_id.timestamp,
+    //         dylib_id.current_version,
+    //         dylib_id.compatibility_version,
+    //     );
+    //     errdefer dylib_cmd.deinit(self.allocator);
+    //     try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
+    //     self.next_dylib_ordinal += 1;
+    // }
+
+    // if (self.unresolved.count() > 0) {
+    //     for (self.unresolved.values()) |undef| {
+    //         log.err("undefined reference to symbol '{s}'", .{undef.name});
+    //         if (undef.cast(Symbol.Unresolved).?.file) |file| {
+    //             log.err("    | referenced in {s}", .{file.name.?});
+    //         }
+    //     }
+
+    //     return error.UndefinedSymbolReference;
+    // }
 }
 
 fn resolveStubsAndGotEntries(self: *Zld) !void {