Commit dbd2eb7c7f

Jakub Konka <kubkon@jakubkonka.com>
2021-07-06 19:09:49
zld: simplify relocation parsing
1 parent 15b85df
src/link/MachO/reloc/aarch64.zig
@@ -1,618 +0,0 @@
-const std = @import("std");
-const aarch64 = @import("../../../codegen/aarch64.zig");
-const assert = std.debug.assert;
-const log = std.log.scoped(.reloc);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-const meta = std.meta;
-const reloc = @import("../reloc.zig");
-
-const Allocator = mem.Allocator;
-const Object = @import("../Object.zig");
-const Relocation = reloc.Relocation;
-const Symbol = @import("../Symbol.zig");
-const TextBlock = Zld.TextBlock;
-const Zld = @import("../Zld.zig");
-
-pub const Branch = struct {
-    base: Relocation,
-    /// Always .UnconditionalBranchImmediate
-    // inst: aarch64.Instruction,
-
-    pub const base_type: Relocation.Type = .branch_aarch64;
-
-    // pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
-    //     const displacement = try math.cast(i28, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
-
-    //     log.debug("    | displacement 0x{x}", .{displacement});
-
-    //     var inst = branch.inst;
-    //     inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2));
-    //     mem.writeIntLittle(u32, branch.base.code[0..4], inst.toU32());
-    // }
-
-    pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const Page = struct {
-    base: Relocation,
-    addend: ?u32 = null,
-    /// Always .PCRelativeAddress
-    // inst: aarch64.Instruction,
-
-    pub const base_type: Relocation.Type = .page;
-
-    // pub fn resolve(page: Page, args: Relocation.ResolveArgs) !void {
-    //     const target_addr = if (page.addend) |addend| args.target_addr + addend else args.target_addr;
-    //     const source_page = @intCast(i32, args.source_addr >> 12);
-    //     const target_page = @intCast(i32, target_addr >> 12);
-    //     const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
-
-    //     log.debug("    | calculated addend 0x{x}", .{page.addend});
-    //     log.debug("    | moving by {} pages", .{pages});
-
-    //     var inst = page.inst;
-    //     inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
-    //     inst.pc_relative_address.immlo = @truncate(u2, pages);
-
-    //     mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
-    // }
-
-    pub fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = fmt;
-        _ = options;
-        if (self.addend) |addend| {
-            try std.fmt.format(writer, ".addend = {}, ", .{addend});
-        }
-    }
-};
-
-pub const PageOff = struct {
-    base: Relocation,
-    addend: ?u32 = null,
-    op_kind: OpKind,
-    // inst: aarch64.Instruction,
-
-    pub const base_type: Relocation.Type = .page_off;
-
-    pub const OpKind = enum {
-        arithmetic,
-        load_store,
-    };
-
-    // pub fn resolve(page_off: PageOff, args: Relocation.ResolveArgs) !void {
-    //     const target_addr = if (page_off.addend) |addend| args.target_addr + addend else args.target_addr;
-    //     const narrowed = @truncate(u12, target_addr);
-
-    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
-    //     log.debug("    | {s} opcode", .{page_off.op_kind});
-
-    //     var inst = page_off.inst;
-    //     if (page_off.op_kind == .arithmetic) {
-    //         inst.add_subtract_immediate.imm12 = narrowed;
-    //     } else {
-    //         const offset: u12 = blk: {
-    //             if (inst.load_store_register.size == 0) {
-    //                 if (inst.load_store_register.v == 1) {
-    //                     // 128-bit SIMD is scaled by 16.
-    //                     break :blk try math.divExact(u12, narrowed, 16);
-    //                 }
-    //                 // Otherwise, 8-bit SIMD or ldrb.
-    //                 break :blk narrowed;
-    //             } else {
-    //                 const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size);
-    //                 break :blk try math.divExact(u12, narrowed, denom);
-    //             }
-    //         };
-    //         inst.load_store_register.offset = offset;
-    //     }
-
-    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
-    // }
-
-    pub fn format(self: PageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = fmt;
-        _ = options;
-        if (self.addend) |addend| {
-            try std.fmt.format(writer, ".addend = {}, ", .{addend});
-        }
-        try std.fmt.format(writer, ".op_kind = {s}, ", .{self.op_kind});
-    }
-};
-
-pub const GotPage = struct {
-    base: Relocation,
-    /// Always .PCRelativeAddress
-    // inst: aarch64.Instruction,
-
-    pub const base_type: Relocation.Type = .got_page;
-
-    // pub fn resolve(page: GotPage, args: Relocation.ResolveArgs) !void {
-    //     const source_page = @intCast(i32, args.source_addr >> 12);
-    //     const target_page = @intCast(i32, args.target_addr >> 12);
-    //     const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
-
-    //     log.debug("    | moving by {} pages", .{pages});
-
-    //     var inst = page.inst;
-    //     inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
-    //     inst.pc_relative_address.immlo = @truncate(u2, pages);
-
-    //     mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
-    // }
-
-    pub fn format(self: GotPage, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const GotPageOff = struct {
-    base: Relocation,
-    /// Always .LoadStoreRegister with size = 3 for GOT indirection
-    // inst: aarch64.Instruction,
-
-    pub const base_type: Relocation.Type = .got_page_off;
-
-    // pub fn resolve(page_off: GotPageOff, args: Relocation.ResolveArgs) !void {
-    //     const narrowed = @truncate(u12, args.target_addr);
-
-    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
-
-    //     var inst = page_off.inst;
-    //     const offset = try math.divExact(u12, narrowed, 8);
-    //     inst.load_store_register.offset = offset;
-
-    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
-    // }
-
-    pub fn format(self: GotPageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const PointerToGot = struct {
-    base: Relocation,
-
-    pub const base_type: Relocation.Type = .pointer_to_got;
-
-    // pub fn resolve(ptr_to_got: PointerToGot, args: Relocation.ResolveArgs) !void {
-    //     const result = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
-
-    //     log.debug("    | calculated value 0x{x}", .{result});
-
-    //     mem.writeIntLittle(u32, ptr_to_got.base.code[0..4], @bitCast(u32, result));
-    // }
-
-    pub fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const TlvpPage = struct {
-    base: Relocation,
-    /// Always .PCRelativeAddress
-    // inst: aarch64.Instruction,
-
-    pub const base_type: Relocation.Type = .tlvp_page;
-
-    // pub fn resolve(page: TlvpPage, args: Relocation.ResolveArgs) !void {
-    //     const source_page = @intCast(i32, args.source_addr >> 12);
-    //     const target_page = @intCast(i32, args.target_addr >> 12);
-    //     const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
-
-    //     log.debug("    | moving by {} pages", .{pages});
-
-    //     var inst = page.inst;
-    //     inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
-    //     inst.pc_relative_address.immlo = @truncate(u2, pages);
-
-    //     mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
-    // }
-
-    pub fn format(self: TlvpPage, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const TlvpPageOff = struct {
-    base: Relocation,
-    /// Always .AddSubtractImmediate regardless of the source instruction.
-    /// This means, we always rewrite the instruction to add even if the
-    /// source instruction was an ldr.
-    // inst: aarch64.Instruction,
-
-    pub const base_type: Relocation.Type = .tlvp_page_off;
-
-    // pub fn resolve(page_off: TlvpPageOff, args: Relocation.ResolveArgs) !void {
-    //     const narrowed = @truncate(u12, args.target_addr);
-
-    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
-
-    //     var inst = page_off.inst;
-    //     inst.add_subtract_immediate.imm12 = narrowed;
-
-    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
-    // }
-
-    pub fn format(self: TlvpPageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const Parser = struct {
-    object: *Object,
-    zld: *Zld,
-    it: *reloc.RelocIterator,
-    block: *TextBlock,
-    base_addr: u64,
-    addend: ?u32 = null,
-    subtractor: ?*Symbol = null,
-
-    pub fn parse(self: *Parser) !void {
-        while (self.it.next()) |rel| {
-            const out_rel = switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) {
-                .ARM64_RELOC_BRANCH26 => try self.parseBranch(rel),
-                .ARM64_RELOC_SUBTRACTOR => {
-                    // Subtractor is not a relocation with effect on the TextBlock, so
-                    // parse it and carry on.
-                    try self.parseSubtractor(rel);
-                    continue;
-                },
-                .ARM64_RELOC_UNSIGNED => try self.parseUnsigned(rel),
-                .ARM64_RELOC_ADDEND => {
-                    // Addend is not a relocation with effect on the TextBlock, so
-                    // parse it and carry on.
-                    try self.parseAddend(rel);
-                    continue;
-                },
-                .ARM64_RELOC_PAGE21,
-                .ARM64_RELOC_GOT_LOAD_PAGE21,
-                .ARM64_RELOC_TLVP_LOAD_PAGE21,
-                => try self.parsePage(rel),
-                .ARM64_RELOC_PAGEOFF12 => try self.parsePageOff(rel),
-                .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => try self.parseGotLoadPageOff(rel),
-                .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => try self.parseTlvpLoadPageOff(rel),
-                .ARM64_RELOC_POINTER_TO_GOT => try self.parsePointerToGot(rel),
-            };
-            try self.block.relocs.append(out_rel);
-
-            if (out_rel.target.payload == .regular) {
-                try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {});
-            }
-
-            switch (out_rel.@"type") {
-                .got_page, .got_page_off, .pointer_to_got => {
-                    const sym = out_rel.target;
-
-                    if (sym.got_index != null) continue;
-
-                    const index = @intCast(u32, self.zld.got_entries.items.len);
-                    sym.got_index = index;
-                    try self.zld.got_entries.append(self.zld.allocator, sym);
-
-                    log.debug("adding GOT entry for symbol {s} at index {}", .{ sym.name, index });
-                },
-                .branch_aarch64 => {
-                    const sym = out_rel.target;
-
-                    if (sym.stubs_index != null) continue;
-                    if (sym.payload != .proxy) continue;
-
-                    const index = @intCast(u32, self.zld.stubs.items.len);
-                    sym.stubs_index = index;
-                    try self.zld.stubs.append(self.zld.allocator, sym);
-
-                    log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index });
-                },
-                else => {},
-            }
-        }
-    }
-
-    fn parseAddend(self: *Parser, rel: macho.relocation_info) !void {
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_ADDEND);
-        assert(rel.r_pcrel == 0);
-        assert(rel.r_extern == 0);
-        assert(self.addend == null);
-
-        self.addend = rel.r_symbolnum;
-
-        // Verify ADDEND is followed by a load.
-        const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type);
-        switch (next) {
-            .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
-            else => {
-                log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{next});
-                return error.UnexpectedRelocationType;
-            },
-        }
-    }
-
-    fn parseBranch(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_BRANCH26);
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const target = try self.object.symbolFromReloc(rel);
-
-        var branch = try self.object.allocator.create(Branch);
-        errdefer self.object.allocator.destroy(branch);
-
-        branch.* = .{
-            .base = .{
-                .@"type" = .branch_aarch64,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-        };
-
-        return &branch.base;
-    }
-
-    fn parsePage(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        const target = try self.object.symbolFromReloc(rel);
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-
-        const ptr: *Relocation = ptr: {
-            switch (rel_type) {
-                .ARM64_RELOC_PAGE21 => {
-                    defer {
-                        // Reset parser's addend state
-                        self.addend = null;
-                    }
-                    var page = try self.object.allocator.create(Page);
-                    errdefer self.object.allocator.destroy(page);
-
-                    page.* = .{
-                        .base = .{
-                            .@"type" = .page,
-                            .offset = offset,
-                            .target = target,
-                            .block = self.block,
-                        },
-                        .addend = self.addend,
-                    };
-
-                    break :ptr &page.base;
-                },
-                .ARM64_RELOC_GOT_LOAD_PAGE21 => {
-                    var page = try self.object.allocator.create(GotPage);
-                    errdefer self.object.allocator.destroy(page);
-
-                    page.* = .{
-                        .base = .{
-                            .@"type" = .got_page,
-                            .offset = offset,
-                            .target = target,
-                            .block = self.block,
-                        },
-                    };
-
-                    break :ptr &page.base;
-                },
-                .ARM64_RELOC_TLVP_LOAD_PAGE21 => {
-                    var page = try self.object.allocator.create(TlvpPage);
-                    errdefer self.object.allocator.destroy(page);
-
-                    page.* = .{
-                        .base = .{
-                            .@"type" = .tlvp_page,
-                            .offset = offset,
-                            .target = target,
-                            .block = self.block,
-                        },
-                    };
-
-                    break :ptr &page.base;
-                },
-                else => unreachable,
-            }
-        };
-
-        return ptr;
-    }
-
-    fn parsePageOff(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        defer {
-            // Reset parser's addend state
-            self.addend = null;
-        }
-
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_PAGEOFF12);
-        assert(rel.r_pcrel == 0);
-        assert(rel.r_length == 2);
-
-        const target = try self.object.symbolFromReloc(rel);
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const op_kind: PageOff.OpKind = if (isArithmeticOp(self.block.code[offset..][0..4]))
-            .arithmetic
-        else
-            .load_store;
-
-        var page_off = try self.object.allocator.create(PageOff);
-        errdefer self.object.allocator.destroy(page_off);
-
-        page_off.* = .{
-            .base = .{
-                .@"type" = .page_off,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-            .op_kind = op_kind,
-            .addend = self.addend,
-        };
-
-        return &page_off.base;
-    }
-
-    fn parseGotLoadPageOff(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12);
-        assert(rel.r_pcrel == 0);
-        assert(rel.r_length == 2);
-
-        const target = try self.object.symbolFromReloc(rel);
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        assert(!isArithmeticOp(self.block.code[offset..][0..4]));
-
-        var page_off = try self.object.allocator.create(GotPageOff);
-        errdefer self.object.allocator.destroy(page_off);
-
-        page_off.* = .{
-            .base = .{
-                .@"type" = .got_page_off,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-        };
-
-        return &page_off.base;
-    }
-
-    fn parseTlvpLoadPageOff(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_TLVP_LOAD_PAGEOFF12);
-        assert(rel.r_pcrel == 0);
-        assert(rel.r_length == 2);
-
-        const RegInfo = struct {
-            rd: u5,
-            rn: u5,
-            size: u1,
-        };
-
-        const target = try self.object.symbolFromReloc(rel);
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-
-        var page_off = try self.object.allocator.create(TlvpPageOff);
-        errdefer self.object.allocator.destroy(page_off);
-
-        page_off.* = .{
-            .base = .{
-                .@"type" = .tlvp_page_off,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-        };
-
-        return &page_off.base;
-    }
-
-    fn parseSubtractor(self: *Parser, rel: macho.relocation_info) !void {
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_SUBTRACTOR);
-        assert(rel.r_pcrel == 0);
-        assert(self.subtractor == null);
-
-        self.subtractor = try self.object.symbolFromReloc(rel);
-
-        // Verify SUBTRACTOR is followed by UNSIGNED.
-        const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type);
-        if (next != .ARM64_RELOC_UNSIGNED) {
-            log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
-            return error.UnexpectedRelocationType;
-        }
-    }
-
-    fn parseUnsigned(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        defer {
-            // Reset parser's subtractor state
-            self.subtractor = null;
-        }
-
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_UNSIGNED);
-        assert(rel.r_pcrel == 0);
-
-        const target = try self.object.symbolFromReloc(rel);
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const is_64bit: bool = switch (rel.r_length) {
-            3 => true,
-            2 => false,
-            else => unreachable,
-        };
-        const addend: i64 = if (is_64bit)
-            mem.readIntLittle(i64, self.block.code[offset..][0..8])
-        else
-            mem.readIntLittle(i32, self.block.code[offset..][0..4]);
-
-        var unsigned = try self.object.allocator.create(reloc.Unsigned);
-        errdefer self.object.allocator.destroy(unsigned);
-
-        unsigned.* = .{
-            .base = .{
-                .@"type" = .unsigned,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-            .subtractor = self.subtractor,
-            .is_64bit = is_64bit,
-            .addend = addend,
-        };
-
-        return &unsigned.base;
-    }
-
-    fn parsePointerToGot(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
-        assert(rel_type == .ARM64_RELOC_POINTER_TO_GOT);
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        var ptr_to_got = try self.object.allocator.create(PointerToGot);
-        errdefer self.object.allocator.destroy(ptr_to_got);
-
-        const target = try self.object.symbolFromReloc(rel);
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-
-        ptr_to_got.* = .{
-            .base = .{
-                .@"type" = .pointer_to_got,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-        };
-
-        return &ptr_to_got.base;
-    }
-};
-
-inline fn isArithmeticOp(inst: *const [4]u8) bool {
-    const group_decode = @truncate(u5, inst[3]);
-    return ((group_decode >> 2) == 4);
-}
src/link/MachO/reloc/x86_64.zig
@@ -1,385 +0,0 @@
-const std = @import("std");
-const assert = std.debug.assert;
-const log = std.log.scoped(.reloc);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-const meta = std.meta;
-const reloc = @import("../reloc.zig");
-
-const Allocator = mem.Allocator;
-const Object = @import("../Object.zig");
-const Relocation = reloc.Relocation;
-const Symbol = @import("../Symbol.zig");
-const TextBlock = Zld.TextBlock;
-const Zld = @import("../Zld.zig");
-
-pub const Branch = struct {
-    base: Relocation,
-
-    pub const base_type: Relocation.Type = .branch_x86_64;
-
-    // pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
-    //     const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
-    //     log.debug("    | displacement 0x{x}", .{displacement});
-    //     mem.writeIntLittle(u32, branch.base.code[0..4], @bitCast(u32, displacement));
-    // }
-
-    pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const Signed = struct {
-    base: Relocation,
-    addend: i32,
-    correction: i4,
-
-    pub const base_type: Relocation.Type = .signed;
-
-    // pub fn resolve(signed: Signed, args: Relocation.ResolveArgs) !void {
-    //     const target_addr = target_addr: {
-    //         if (signed.base.target == .section) {
-    //             const source_target = @intCast(i64, args.source_source_sect_addr.?) + @intCast(i64, signed.base.offset) + signed.addend + 4;
-    //             const source_disp = source_target - @intCast(i64, args.source_target_sect_addr.?);
-    //             break :target_addr @intCast(i64, args.target_addr) + source_disp;
-    //         }
-    //         break :target_addr @intCast(i64, args.target_addr) + signed.addend;
-    //     };
-    //     const displacement = try math.cast(
-    //         i32,
-    //         target_addr - @intCast(i64, args.source_addr) - signed.correction - 4,
-    //     );
-
-    //     log.debug("    | addend 0x{x}", .{signed.addend});
-    //     log.debug("    | correction 0x{x}", .{signed.correction});
-    //     log.debug("    | displacement 0x{x}", .{displacement});
-
-    //     mem.writeIntLittle(u32, signed.base.code[0..4], @bitCast(u32, displacement));
-    // }
-
-    pub fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = fmt;
-        _ = options;
-        try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
-        try std.fmt.format(writer, ".correction = {}, ", .{self.correction});
-    }
-};
-
-pub const GotLoad = struct {
-    base: Relocation,
-
-    pub const base_type: Relocation.Type = .got_load;
-
-    // pub fn resolve(got_load: GotLoad, args: Relocation.ResolveArgs) !void {
-    //     const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
-    //     log.debug("    | displacement 0x{x}", .{displacement});
-    //     mem.writeIntLittle(u32, got_load.base.code[0..4], @bitCast(u32, displacement));
-    // }
-
-    pub fn format(self: GotLoad, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const Got = struct {
-    base: Relocation,
-    addend: i32,
-
-    pub const base_type: Relocation.Type = .got;
-
-    // pub fn resolve(got: Got, args: Relocation.ResolveArgs) !void {
-    //     const displacement = try math.cast(
-    //         i32,
-    //         @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4 + got.addend,
-    //     );
-    //     log.debug("    | displacement 0x{x}", .{displacement});
-    //     mem.writeIntLittle(u32, got.base.code[0..4], @bitCast(u32, displacement));
-    // }
-
-    pub fn format(self: Got, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = fmt;
-        _ = options;
-        try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
-    }
-};
-
-pub const Tlv = struct {
-    base: Relocation,
-
-    pub const base_type: Relocation.Type = .tlv;
-
-    // pub fn resolve(tlv: Tlv, args: Relocation.ResolveArgs) !void {
-    //     // We need to rewrite the opcode from movq to leaq.
-    //     tlv.op.* = 0x8d;
-    //     log.debug("    | rewriting op to leaq", .{});
-
-    //     const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
-    //     log.debug("    | displacement 0x{x}", .{displacement});
-
-    //     mem.writeIntLittle(u32, tlv.base.code[0..4], @bitCast(u32, displacement));
-    // }
-    pub fn format(self: Tlv, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = self;
-        _ = fmt;
-        _ = options;
-        _ = writer;
-    }
-};
-
-pub const Parser = struct {
-    object: *Object,
-    zld: *Zld,
-    it: *reloc.RelocIterator,
-    block: *TextBlock,
-    base_addr: u64,
-    subtractor: ?*Symbol = null,
-
-    pub fn parse(self: *Parser) !void {
-        while (self.it.next()) |rel| {
-            const out_rel = switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) {
-                .X86_64_RELOC_BRANCH => try self.parseBranch(rel),
-                .X86_64_RELOC_SUBTRACTOR => {
-                    // Subtractor is not a relocation with effect on the TextBlock, so
-                    // parse it and carry on.
-                    try self.parseSubtractor(rel);
-                    continue;
-                },
-                .X86_64_RELOC_UNSIGNED => try self.parseUnsigned(rel),
-                .X86_64_RELOC_SIGNED,
-                .X86_64_RELOC_SIGNED_1,
-                .X86_64_RELOC_SIGNED_2,
-                .X86_64_RELOC_SIGNED_4,
-                => try self.parseSigned(rel),
-                .X86_64_RELOC_GOT_LOAD => try self.parseGotLoad(rel),
-                .X86_64_RELOC_GOT => try self.parseGot(rel),
-                .X86_64_RELOC_TLV => try self.parseTlv(rel),
-            };
-            try self.block.relocs.append(out_rel);
-
-            if (out_rel.target.payload == .regular) {
-                try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {});
-            }
-
-            switch (out_rel.@"type") {
-                .got_load, .got => {
-                    const sym = out_rel.target;
-
-                    if (sym.got_index != null) continue;
-
-                    const index = @intCast(u32, self.zld.got_entries.items.len);
-                    sym.got_index = index;
-                    try self.zld.got_entries.append(self.zld.allocator, sym);
-
-                    log.debug("adding GOT entry for symbol {s} at index {}", .{ sym.name, index });
-                },
-                .branch_x86_64 => {
-                    const sym = out_rel.target;
-
-                    if (sym.stubs_index != null) continue;
-                    if (sym.payload != .proxy) continue;
-
-                    const index = @intCast(u32, self.zld.stubs.items.len);
-                    sym.stubs_index = index;
-                    try self.zld.stubs.append(self.zld.allocator, sym);
-
-                    log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index });
-                },
-                else => {},
-            }
-        }
-    }
-
-    fn parseBranch(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        assert(rel_type == .X86_64_RELOC_BRANCH);
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const target = try self.object.symbolFromReloc(rel);
-
-        var branch = try self.object.allocator.create(Branch);
-        errdefer self.object.allocator.destroy(branch);
-
-        branch.* = .{
-            .base = .{
-                .@"type" = .branch_x86_64,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-        };
-
-        return &branch.base;
-    }
-
-    fn parseSigned(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        const target = try self.object.symbolFromReloc(rel);
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const correction: i4 = switch (rel_type) {
-            .X86_64_RELOC_SIGNED => 0,
-            .X86_64_RELOC_SIGNED_1 => 1,
-            .X86_64_RELOC_SIGNED_2 => 2,
-            .X86_64_RELOC_SIGNED_4 => 4,
-            else => unreachable,
-        };
-        const addend = mem.readIntLittle(i32, self.block.code[offset..][0..4]) + correction;
-
-        var signed = try self.object.allocator.create(Signed);
-        errdefer self.object.allocator.destroy(signed);
-
-        signed.* = .{
-            .base = .{
-                .@"type" = .signed,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-            .addend = addend,
-            .correction = correction,
-        };
-
-        return &signed.base;
-    }
-
-    fn parseGotLoad(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        assert(rel_type == .X86_64_RELOC_GOT_LOAD);
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const target = try self.object.symbolFromReloc(rel);
-
-        var got_load = try self.object.allocator.create(GotLoad);
-        errdefer self.object.allocator.destroy(got_load);
-
-        got_load.* = .{
-            .base = .{
-                .@"type" = .got_load,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-        };
-
-        return &got_load.base;
-    }
-
-    fn parseGot(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        assert(rel_type == .X86_64_RELOC_GOT);
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const target = try self.object.symbolFromReloc(rel);
-        const addend = mem.readIntLittle(i32, self.block.code[offset..][0..4]);
-
-        var got = try self.object.allocator.create(Got);
-        errdefer self.object.allocator.destroy(got);
-
-        got.* = .{
-            .base = .{
-                .@"type" = .got,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-            .addend = addend,
-        };
-
-        return &got.base;
-    }
-
-    fn parseTlv(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        assert(rel_type == .X86_64_RELOC_TLV);
-        assert(rel.r_pcrel == 1);
-        assert(rel.r_length == 2);
-
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const target = try self.object.symbolFromReloc(rel);
-
-        var tlv = try self.object.allocator.create(Tlv);
-        errdefer self.object.allocator.destroy(tlv);
-
-        tlv.* = .{
-            .base = .{
-                .@"type" = .tlv,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-        };
-
-        return &tlv.base;
-    }
-
-    fn parseSubtractor(self: *Parser, rel: macho.relocation_info) !void {
-        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        assert(rel_type == .X86_64_RELOC_SUBTRACTOR);
-        assert(rel.r_pcrel == 0);
-        assert(self.subtractor == null);
-
-        self.subtractor = try self.object.symbolFromReloc(rel);
-
-        // Verify SUBTRACTOR is followed by UNSIGNED.
-        const next = @intToEnum(macho.reloc_type_x86_64, self.it.peek().r_type);
-        if (next != .X86_64_RELOC_UNSIGNED) {
-            log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
-            return error.UnexpectedRelocationType;
-        }
-    }
-
-    fn parseUnsigned(self: *Parser, rel: macho.relocation_info) !*Relocation {
-        defer {
-            // Reset parser's subtractor state
-            self.subtractor = null;
-        }
-
-        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
-        assert(rel_type == .X86_64_RELOC_UNSIGNED);
-        assert(rel.r_pcrel == 0);
-
-        const target = try self.object.symbolFromReloc(rel);
-        const is_64bit: bool = switch (rel.r_length) {
-            3 => true,
-            2 => false,
-            else => unreachable,
-        };
-        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
-        const addend: i64 = if (is_64bit)
-            mem.readIntLittle(i64, self.block.code[offset..][0..8])
-        else
-            mem.readIntLittle(i32, self.block.code[offset..][0..4]);
-
-        var unsigned = try self.object.allocator.create(reloc.Unsigned);
-        errdefer self.object.allocator.destroy(unsigned);
-
-        unsigned.* = .{
-            .base = .{
-                .@"type" = .unsigned,
-                .offset = offset,
-                .target = target,
-                .block = self.block,
-            },
-            .subtractor = self.subtractor,
-            .is_64bit = is_64bit,
-            .addend = addend,
-        };
-
-        return &unsigned.base;
-    }
-};
src/link/MachO/Object.zig
@@ -408,7 +408,6 @@ const TextBlockParser = struct {
 
         const start_addr = senior_nlist.nlist.n_value - self.section.addr;
         const end_addr = if (next_nlist) |n| n.nlist.n_value - self.section.addr else self.section.size;
-        log.warn("{} - {}", .{ start_addr, end_addr });
 
         const code = self.code[start_addr..end_addr];
         const size = code.len;
@@ -430,7 +429,7 @@ const TextBlockParser = struct {
             .aliases = alias_only_indices,
             .references = std.AutoArrayHashMap(u32, void).init(self.allocator),
             .code = try self.allocator.dupe(u8, code),
-            .relocs = std.ArrayList(*Relocation).init(self.allocator),
+            .relocs = std.ArrayList(Relocation).init(self.allocator),
             .size = size,
             .alignment = self.section.@"align",
         };
@@ -579,7 +578,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
                 .local_sym_index = local_sym_index,
                 .references = std.AutoArrayHashMap(u32, void).init(self.allocator),
                 .code = try self.allocator.dupe(u8, code),
-                .relocs = std.ArrayList(*Relocation).init(self.allocator),
+                .relocs = std.ArrayList(Relocation).init(self.allocator),
                 .size = sect.size,
                 .alignment = sect.@"align",
             };
@@ -607,30 +606,14 @@ fn parseRelocs(
     var it = reloc.RelocIterator{
         .buffer = relocs,
     };
-
-    switch (self.arch.?) {
-        .aarch64 => {
-            var parser = reloc.aarch64.Parser{
-                .object = self,
-                .zld = zld,
-                .it = &it,
-                .block = block,
-                .base_addr = base_addr,
-            };
-            try parser.parse();
-        },
-        .x86_64 => {
-            var parser = reloc.x86_64.Parser{
-                .object = self,
-                .zld = zld,
-                .it = &it,
-                .block = block,
-                .base_addr = base_addr,
-            };
-            try parser.parse();
-        },
-        else => unreachable,
-    }
+    var parser = reloc.Parser{
+        .object = self,
+        .zld = zld,
+        .it = &it,
+        .block = block,
+        .base_addr = base_addr,
+    };
+    try parser.parse();
 }
 
 pub fn symbolFromReloc(self: *Object, rel: macho.relocation_info) !*Symbol {
src/link/MachO/reloc.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const aarch64 = @import("../../codegen/aarch64.zig");
 const assert = std.debug.assert;
 const log = std.log.scoped(.reloc);
 const macho = std.macho;
@@ -6,141 +7,431 @@ const math = std.math;
 const mem = std.mem;
 const meta = std.meta;
 
-pub const aarch64 = @import("reloc/aarch64.zig");
-pub const x86_64 = @import("reloc/x86_64.zig");
-
 const Allocator = mem.Allocator;
+const Arch = std.Target.Cpu.Arch;
+const Object = @import("Object.zig");
 const Symbol = @import("Symbol.zig");
-const TextBlock = @import("Zld.zig").TextBlock;
+const TextBlock = Zld.TextBlock;
+const Zld = @import("Zld.zig");
 
 pub const Relocation = struct {
-    @"type": Type,
+    /// Offset within the `block`s code buffer.
+    /// Note relocation size can be inferred by relocation's kind.
     offset: u32,
+
+    /// Parent block containing this relocation.
     block: *TextBlock,
+
+    /// Target symbol: either a regular or a proxy.
     target: *Symbol,
 
-    pub fn cast(base: *Relocation, comptime T: type) ?*T {
-        if (base.@"type" != T.base_type)
-            return null;
+    payload: union(enum) {
+        unsigned: Unsigned,
+        branch: Branch,
+        page: Page,
+        page_off: PageOff,
+        pointer_to_got: PointerToGot,
+        signed: Signed,
+        load: Load,
+    },
 
-        return @fieldParentPtr(T, "base", base);
-    }
+    pub const Unsigned = struct {
+        subtractor: ?*Symbol = null,
+
+        /// Addend embedded directly in the relocation slot
+        addend: i64,
+
+        /// Extracted from r_length:
+        /// => 3 implies true
+        /// => 2 implies false
+        /// => * is unreachable
+        is_64bit: bool,
+
+        pub fn resolve(self: Unsigned, base: Relocation, source_addr: u64, target_addr: u64) !void {
+            //     const addend = if (unsigned.base.target == .section)
+            //         unsigned.addend - @intCast(i64, args.source_target_sect_addr.?)
+            //     else
+            //         unsigned.addend;
+
+            //     const result = if (args.subtractor) |subtractor|
+            //         @intCast(i64, args.target_addr) - @intCast(i64, subtractor) + addend
+            //     else
+            //         @intCast(i64, args.target_addr) + addend;
+
+            //     log.debug("    | calculated addend 0x{x}", .{addend});
+            //     log.debug("    | calculated unsigned value 0x{x}", .{result});
+
+            //     if (unsigned.is_64bit) {
+            //         mem.writeIntLittle(
+            //             u64,
+            //             unsigned.base.code[0..8],
+            //             @bitCast(u64, result),
+            //         );
+            //     } else {
+            //         mem.writeIntLittle(
+            //             u32,
+            //             unsigned.base.code[0..4],
+            //             @truncate(u32, @bitCast(u64, result)),
+            //         );
+            //     }
+        }
 
-    // pub fn resolve(base: *Relocation) !void {
-    //     return switch (base.@"type") {
-    //         .unsigned => @fieldParentPtr(Unsigned, "base", base).resolve(),
-    //         .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).resolve(),
-    //         .page => @fieldParentPtr(aarch64.Page, "base", base).resolve(),
-    //         .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).resolve(),
-    //         .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).resolve(),
-    //         .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).resolve(),
-    //         .pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).resolve(),
-    //         .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).resolve(),
-    //         .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).resolve(),
-    //         .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).resolve(),
-    //         .signed => @fieldParentPtr(x86_64.Signed, "base", base).resolve(),
-    //         .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).resolve(),
-    //         .got => @fieldParentPtr(x86_64.Got, "base", base).resolve(),
-    //         .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).resolve(),
-    //     };
-    // }
-
-    pub const Type = enum {
-        branch_aarch64,
-        unsigned,
-        page,
-        page_off,
-        got_page,
-        got_page_off,
-        tlvp_page,
-        pointer_to_got,
-        tlvp_page_off,
-        branch_x86_64,
-        signed,
-        got_load,
-        got,
-        tlv,
+        pub fn format(self: Unsigned, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+            _ = fmt;
+            _ = options;
+            try std.fmt.format(writer, "Unsigned {{ ", .{});
+            if (self.subtractor) |sub| {
+                try std.fmt.format(writer, ".subtractor = {}, ", .{sub});
+            }
+            try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
+            const length: usize = if (self.is_64bit) 8 else 4;
+            try std.fmt.format(writer, ".length = {}, ", .{length});
+            try std.fmt.format(writer, "}}", .{});
+        }
     };
 
-    pub fn format(base: *const Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        try std.fmt.format(writer, "Relocation {{ ", .{});
-        try std.fmt.format(writer, ".type = {s}, ", .{base.@"type"});
-        try std.fmt.format(writer, ".offset = {}, ", .{base.offset});
-        try std.fmt.format(writer, ".block = {}", .{base.block.local_sym_index});
-        try std.fmt.format(writer, ".target = {}, ", .{base.target});
-
-        try switch (base.@"type") {
-            .unsigned => @fieldParentPtr(Unsigned, "base", base).format(fmt, options, writer),
-            .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).format(fmt, options, writer),
-            .page => @fieldParentPtr(aarch64.Page, "base", base).format(fmt, options, writer),
-            .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).format(fmt, options, writer),
-            .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).format(fmt, options, writer),
-            .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).format(fmt, options, writer),
-            .pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).format(fmt, options, writer),
-            .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).format(fmt, options, writer),
-            .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).format(fmt, options, writer),
-            .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).format(fmt, options, writer),
-            .signed => @fieldParentPtr(x86_64.Signed, "base", base).format(fmt, options, writer),
-            .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).format(fmt, options, writer),
-            .got => @fieldParentPtr(x86_64.Got, "base", base).format(fmt, options, writer),
-            .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).format(fmt, options, writer),
+    pub const Branch = struct {
+        arch: Arch,
+
+        pub fn resolve(self: Branch, base: Relocation, source_addr: u64, target_addr: u64) !void {
+            switch (arch) {
+                .aarch64 => {
+                    const displacement = try math.cast(i28, @intCast(i64, target_addr) - @intCast(i64, source_addr));
+                    var inst = aarch64.Instruction{
+                        .unconditional_branch_immediate = mem.bytesToValue(
+                            meta.TagPayload(
+                                aarch.Instruction,
+                                aarch64.Instruction.unconditional_branch_immediate,
+                            ),
+                            base.block.code[base.offset..][0..4],
+                        ),
+                    };
+                    inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2));
+                    mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32());
+                },
+                .x86_64 => {
+                    const displacement = try math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4);
+                    mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, displacement));
+                },
+                else => return error.UnsupportedCpuArchitecture,
+            }
+        }
+
+        pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+            _ = fmt;
+            _ = options;
+            try std.fmt.format(writer, "Branch {{}}", .{});
+        }
+    };
+
+    pub const Page = struct {
+        kind: enum {
+            page,
+            got,
+            tlvp,
+        },
+        addend: ?u32 = null,
+
+        pub fn resolve(self: Page, base: Relocation, source_addr: u64, target_addr: u64) !void {
+            const actual_target_addr = if (self.addend) |addend| target_addr + addend else target_addr;
+            const source_page = @intCast(i32, source_addr >> 12);
+            const target_page = @intCast(i32, actual_target_addr >> 12);
+            const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
+
+            var inst = aarch64.Instruction{
+                .pc_relative_address = mem.bytesToValue(
+                    meta.TagPayload(
+                        aarch64.Instruction,
+                        aarch64.Instruction.pc_relative_address,
+                    ),
+                    base.block.code[base.offset..][0..4],
+                ),
+            };
+            inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
+            inst.pc_relative_address.immlo = @truncate(u2, pages);
+
+            mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32());
+        }
+
+        pub fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+            _ = fmt;
+            _ = options;
+            try std.fmt.format(writer, "Page {{ ", .{});
+            switch (self.kind) {
+                .page => {},
+                .got => {
+                    try std.fmt.format(writer, ".got, ", .{});
+                },
+                .tlvp => {
+                    try std.fmt.format(writer, ".tlvp", .{});
+                },
+            }
+            if (self.addend) |add| {
+                try std.fmt.format(writer, ".addend = {}, ", .{add});
+            }
+            try std.fmt.format(writer, "}}", .{});
+        }
+    };
+
+    pub const PageOff = struct {
+        kind: enum {
+            page,
+            got,
+            tlvp,
+        },
+        addend: ?u32 = null,
+        op_kind: ?OpKind = null,
+
+        pub const OpKind = enum {
+            arithmetic,
+            load,
         };
 
-        try std.fmt.format(writer, "}}", .{});
+        pub fn resolve(self: PageOff, base: Relocation, source_addr: u64, target_addr: u64) !void {
+            switch (self.kind) {
+                .page => {
+                    //     const target_addr = if (page_off.addend) |addend| args.target_addr + addend else args.target_addr;
+                    //     const narrowed = @truncate(u12, target_addr);
+
+                    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
+                    //     log.debug("    | {s} opcode", .{page_off.op_kind});
+
+                    //     var inst = page_off.inst;
+                    //     if (page_off.op_kind == .arithmetic) {
+                    //         inst.add_subtract_immediate.imm12 = narrowed;
+                    //     } else {
+                    //         const offset: u12 = blk: {
+                    //             if (inst.load_store_register.size == 0) {
+                    //                 if (inst.load_store_register.v == 1) {
+                    //                     // 128-bit SIMD is scaled by 16.
+                    //                     break :blk try math.divExact(u12, narrowed, 16);
+                    //                 }
+                    //                 // Otherwise, 8-bit SIMD or ldrb.
+                    //                 break :blk narrowed;
+                    //             } else {
+                    //                 const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size);
+                    //                 break :blk try math.divExact(u12, narrowed, denom);
+                    //             }
+                    //         };
+                    //         inst.load_store_register.offset = offset;
+                    //     }
+
+                    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+
+                },
+                .got => {
+                    //     const narrowed = @truncate(u12, args.target_addr);
+
+                    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
+
+                    //     var inst = page_off.inst;
+                    //     const offset = try math.divExact(u12, narrowed, 8);
+                    //     inst.load_store_register.offset = offset;
+
+                    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+                },
+                .tlvp => {
+
+                    //     const narrowed = @truncate(u12, args.target_addr);
+
+                    //     log.debug("    | narrowed address within the page 0x{x}", .{narrowed});
+
+                    //     var inst = page_off.inst;
+                    //     inst.add_subtract_immediate.imm12 = narrowed;
+
+                    //     mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
+                },
+            }
+        }
+
+        pub fn format(self: PageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+            _ = fmt;
+            _ = options;
+            try std.fmt.format(writer, "PageOff {{ ", .{});
+            switch (self.kind) {
+                .page => {},
+                .got => {
+                    try std.fmt.format(writer, ".got, ", .{});
+                },
+                .tlvp => {
+                    try std.fmt.format(writer, ".tlvp, ", .{});
+                },
+            }
+            if (self.addend) |add| {
+                try std.fmt.format(writer, ".addend = {}, ", .{add});
+            }
+            if (self.op_kind) |op| {
+                try std.fmt.format(writer, ".op_kind = {s}, ", .{op});
+            }
+            try std.fmt.format(writer, "}}", .{});
+        }
+    };
+
+    pub const PointerToGot = struct {
+        pub fn resolve(self: PointerToGot, base: Relocation, source_addr: u64, target_addr: u64) !void {
+            const result = try math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr));
+            mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, result));
+        }
+
+        pub fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+            _ = fmt;
+            _ = options;
+            try std.fmt.format(writer, "PointerToGot {{}}", .{});
+        }
+    };
+
+    pub const Signed = struct {
+        addend: i32,
+        correction: i4,
+
+        pub fn resolve(self: Signed, base: Relocation, source_addr: u64, target_addr: u64) !void {
+            //     const target_addr = target_addr: {
+            //         if (signed.base.target == .section) {
+            //             const source_target = @intCast(i64, args.source_source_sect_addr.?) + @intCast(i64, signed.base.offset) + signed.addend + 4;
+            //             const source_disp = source_target - @intCast(i64, args.source_target_sect_addr.?);
+            //             break :target_addr @intCast(i64, args.target_addr) + source_disp;
+            //         }
+            //         break :target_addr @intCast(i64, args.target_addr) + signed.addend;
+            //     };
+            //     const displacement = try math.cast(
+            //         i32,
+            //         target_addr - @intCast(i64, args.source_addr) - signed.correction - 4,
+            //     );
+
+            //     log.debug("    | addend 0x{x}", .{signed.addend});
+            //     log.debug("    | correction 0x{x}", .{signed.correction});
+            //     log.debug("    | displacement 0x{x}", .{displacement});
+
+            //     mem.writeIntLittle(u32, signed.base.code[0..4], @bitCast(u32, displacement));
+        }
+
+        pub fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+            _ = fmt;
+            _ = options;
+            try std.fmt.format(writer, "Signed {{ ", .{});
+            try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
+            try std.fmt.format(writer, ".correction = {}, ", .{self.correction});
+            try std.fmt.format(writer, "}}", .{});
+        }
+    };
+
+    pub const Load = struct {
+        kind: enum {
+            got,
+            tlvp,
+        },
+        addend: ?i32 = null,
+
+        pub fn resolve(self: Load, base: Relocation, source_addr: u64, target_addr: u64) !void {
+            if (self.kind == .tlvp) {
+                // We need to rewrite the opcode from movq to leaq.
+                base.block.code[base.offset - 2] = 0x8d;
+            }
+            const addend = if (self.addend) |addend| addend else 0;
+            const displacement = try math.cast(
+                i32,
+                @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4 + addend,
+            );
+            mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, displacement));
+        }
+
+        pub fn format(self: Load, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+            _ = fmt;
+            _ = options;
+            try std.fmt.format(writer, "Load {{ ", .{});
+            try std.fmt.format(writer, "{s}, ", .{self.kind});
+            if (self.addend) |addend| {
+                try std.fmt.format(writer, ".addend = {}, ", .{addend});
+            }
+            try std.fmt.format(writer, "}}", .{});
+        }
+    };
+
+    pub fn resolve(self: Relocation, zld: *Zld) !void {
+        const source_addr = blk: {
+            const sym = zld.locals.items[self.block.local_sym_index];
+            break :blk sym.payload.regular.address;
+        };
+        const target_addr = blk: {
+            const is_via_got = inner: {
+                switch (self.payload) {
+                    .pointer_to_got => break :inner true,
+                    .page => |page| page.kind == .got,
+                    .page_off => |page_off| page_off == .got,
+                    .load => {},
+                    else => break :inner false,
+                }
+            };
+
+            if (is_via_got) {
+                const dc_seg = zld.load_commands.items[zld.data_const_segment_cmd_index.?].Segment;
+                const got = dc_seg.sections.items[zld.got_section_index.?];
+                const got_index = self.target.got_index orelse {
+                    log.err("expected GOT entry for symbol '{s}'", .{self.target.name});
+                    log.err("  this is an internal linker error", .{});
+                    return error.FailedToResolveRelocationTarget;
+                };
+                break :blk got.addr + got_index * @sizeOf(u64);
+            }
+
+            switch (self.target.payload) {
+                .regular => |reg| break :blk reg.address,
+                .proxy => |proxy| {
+                    if (mem.eql(u8, self.target.name, "__tlv_bootstrap")) {
+                        const segment = zld.load_commands.items[zld.data_segment_cmd_index.?].Segment;
+                        const tlv = segment.sections.items[zld.tlv_section_index.?];
+                        break :blk tlv.addr;
+                    }
+
+                    const segment = zld.load_commands.items[zld.text_segment_cmd_index.?].Segment;
+                    const stubs = segment.sections.items[zld.stubs_section_index.?];
+                    const stubs_index = self.target.stubs_index orelse {
+                        if (proxy.bind_info.items.len > 0) {
+                            break :blk 0; // Dynamically bound by dyld.
+                        }
+                        log.err("expected stubs index or dynamic bind address for symbol '{s}'", .{
+                            self.target.name,
+                        });
+                        log.err("  this is an internal linker error", .{});
+                        return error.FailedToResolveRelocationTarget;
+                    };
+                    break :blk stubs.addr + stubs_index * stubs.reserved2;
+                },
+                else => {
+                    log.err("failed to resolve symbol '{s}' as a relocation target", .{self.target.name});
+                    log.err("  this is an internal linker error", .{});
+                    return error.FailedToResolveRelocationTarget;
+                },
+            }
+        };
+        switch (self.payload) {
+            .unsigned => |unsigned| try unsigned.resolve(self, source_addr, target_addr),
+            .branch => |branch| try branch.resolve(self, source_addr, target_addr),
+            .page => |page| try page.resolve(self, source_addr, target_addr),
+            .page_off => |page_off| try page_off.resolve(self, source_addr, target_addr),
+            .pointer_to_got => |pointer_to_got| try pointer_to_got.resolve(self, source_addr, target_addr),
+            .signed => |signed| try signed.resolve(self, source_addr, target_addr),
+            .load => |load| try load.resolve(self, source_addr, target_addr),
+        }
     }
-};
 
-pub const Unsigned = struct {
-    base: Relocation,
-    subtractor: ?*Symbol = null,
-    /// Addend embedded directly in the relocation slot
-    addend: i64,
-    /// Extracted from r_length:
-    /// => 3 implies true
-    /// => 2 implies false
-    /// => * is unreachable
-    is_64bit: bool,
-
-    pub const base_type: Relocation.Type = .unsigned;
-
-    // pub fn resolve(unsigned: Unsigned) !void {
-    //     const addend = if (unsigned.base.target == .section)
-    //         unsigned.addend - @intCast(i64, args.source_target_sect_addr.?)
-    //     else
-    //         unsigned.addend;
-
-    //     const result = if (args.subtractor) |subtractor|
-    //         @intCast(i64, args.target_addr) - @intCast(i64, subtractor) + addend
-    //     else
-    //         @intCast(i64, args.target_addr) + addend;
-
-    //     log.debug("    | calculated addend 0x{x}", .{addend});
-    //     log.debug("    | calculated unsigned value 0x{x}", .{result});
-
-    //     if (unsigned.is_64bit) {
-    //         mem.writeIntLittle(
-    //             u64,
-    //             unsigned.base.code[0..8],
-    //             @bitCast(u64, result),
-    //         );
-    //     } else {
-    //         mem.writeIntLittle(
-    //             u32,
-    //             unsigned.base.code[0..4],
-    //             @truncate(u32, @bitCast(u64, result)),
-    //         );
-    //     }
-    // }
-
-    pub fn format(self: Unsigned, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = fmt;
-        _ = options;
-        if (self.subtractor) |sub| {
-            try std.fmt.format(writer, ".subtractor = {}, ", .{sub});
-        }
-        try std.fmt.format(writer, ".addend = {}, ", .{self.addend});
-        const length: usize = if (self.is_64bit) 8 else 4;
-        try std.fmt.format(writer, ".length = {}, ", .{length});
+    pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+        try std.fmt.format(writer, "Relocation {{ ", .{});
+        try std.fmt.format(writer, ".offset = {}, ", .{self.offset});
+        try std.fmt.format(writer, ".block = {}", .{self.block.local_sym_index});
+        try std.fmt.format(writer, ".target = {}, ", .{self.target});
+
+        switch (self.payload) {
+            .unsigned => |unsigned| try unsigned.format(fmt, options, writer),
+            .branch => |branch| try branch.format(fmt, options, writer),
+            .page => |page| try page.format(fmt, options, writer),
+            .page_off => |page_off| try page_off.format(fmt, options, writer),
+            .pointer_to_got => |pointer_to_got| try pointer_to_got.format(fmt, options, writer),
+            .signed => |signed| try signed.format(fmt, options, writer),
+            .load => |load| try load.format(fmt, options, writer),
+        }
+
+        try std.fmt.format(writer, "}}", .{});
     }
 };
 
@@ -161,3 +452,342 @@ pub const RelocIterator = struct {
         return self.buffer[@intCast(u32, self.index + 1)];
     }
 };
+
+pub const Parser = struct {
+    object: *Object,
+    zld: *Zld,
+    it: *RelocIterator,
+    block: *TextBlock,
+
+    /// Base address of the parsed text block in the source section.
+    base_addr: u64,
+
+    /// Used only when targeting aarch64
+    addend: ?u32 = null,
+
+    /// Parsed subtractor symbol from _RELOC_SUBTRACTOR reloc type.
+    subtractor: ?*Symbol = null,
+
+    pub fn parse(self: *Parser) !void {
+        while (self.it.next()) |rel| {
+            const out_rel = blk: {
+                switch (self.object.arch.?) {
+                    .aarch64 => {
+                        const out_rel = switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) {
+                            .ARM64_RELOC_BRANCH26 => try self.parseBranch(rel),
+                            .ARM64_RELOC_SUBTRACTOR => {
+                                // Subtractor is not a relocation with effect on the TextBlock, so
+                                // parse it and carry on.
+                                try self.parseSubtractor(rel);
+
+                                // Verify SUBTRACTOR is followed by UNSIGNED.
+                                const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type);
+                                if (next != .ARM64_RELOC_UNSIGNED) {
+                                    log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
+                                    return error.UnexpectedRelocationType;
+                                }
+                                continue;
+                            },
+                            .ARM64_RELOC_UNSIGNED => try self.parseUnsigned(rel),
+                            .ARM64_RELOC_ADDEND => {
+                                // Addend is not a relocation with effect on the TextBlock, so
+                                // parse it and carry on.
+                                try self.parseAddend(rel);
+
+                                // Verify ADDEND is followed by a load.
+                                const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type);
+                                switch (next) {
+                                    .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
+                                    else => {
+                                        log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{next});
+                                        return error.UnexpectedRelocationType;
+                                    },
+                                }
+                                continue;
+                            },
+                            .ARM64_RELOC_PAGE21,
+                            .ARM64_RELOC_GOT_LOAD_PAGE21,
+                            .ARM64_RELOC_TLVP_LOAD_PAGE21,
+                            => try self.parsePage(rel),
+                            .ARM64_RELOC_PAGEOFF12,
+                            .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
+                            .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
+                            => try self.parsePageOff(rel),
+                            .ARM64_RELOC_POINTER_TO_GOT => try self.parsePointerToGot(rel),
+                        };
+                        break :blk out_rel;
+                    },
+                    .x86_64 => {
+                        const out_rel = switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) {
+                            .X86_64_RELOC_BRANCH => try self.parseBranch(rel),
+                            .X86_64_RELOC_SUBTRACTOR => {
+                                // Subtractor is not a relocation with effect on the TextBlock, so
+                                // parse it and carry on.
+                                try self.parseSubtractor(rel);
+
+                                // Verify SUBTRACTOR is followed by UNSIGNED.
+                                const next = @intToEnum(macho.reloc_type_x86_64, self.it.peek().r_type);
+                                if (next != .X86_64_RELOC_UNSIGNED) {
+                                    log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
+                                    return error.UnexpectedRelocationType;
+                                }
+                                continue;
+                            },
+                            .X86_64_RELOC_UNSIGNED => try self.parseUnsigned(rel),
+                            .X86_64_RELOC_SIGNED,
+                            .X86_64_RELOC_SIGNED_1,
+                            .X86_64_RELOC_SIGNED_2,
+                            .X86_64_RELOC_SIGNED_4,
+                            => try self.parseSigned(rel),
+                            .X86_64_RELOC_GOT_LOAD,
+                            .X86_64_RELOC_GOT,
+                            .X86_64_RELOC_TLV,
+                            => try self.parseLoad(rel),
+                        };
+                        break :blk out_rel;
+                    },
+                    else => unreachable,
+                }
+            };
+            try self.block.relocs.append(out_rel);
+
+            if (out_rel.target.payload == .regular) {
+                try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {});
+            }
+
+            const is_via_got = switch (out_rel.payload) {
+                .pointer_to_got => true,
+                .load => |load| load.kind == .got,
+                .page => |page| page.kind == .got,
+                .page_off => |page_off| page_off.kind == .got,
+                else => false,
+            };
+
+            if (is_via_got and out_rel.target.got_index == null) {
+                const index = @intCast(u32, self.zld.got_entries.items.len);
+                out_rel.target.got_index = index;
+                try self.zld.got_entries.append(self.zld.allocator, out_rel.target);
+                log.debug("adding GOT entry for symbol {s} at index {}", .{ out_rel.target.name, index });
+            }
+
+            if (out_rel.payload == .branch) {
+                const sym = out_rel.target;
+
+                if (sym.stubs_index != null) continue;
+                if (sym.payload != .proxy) continue;
+
+                const index = @intCast(u32, self.zld.stubs.items.len);
+                sym.stubs_index = index;
+                try self.zld.stubs.append(self.zld.allocator, sym);
+
+                log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index });
+            }
+        }
+    }
+
+    fn parseBaseRelInfo(self: *Parser, rel: macho.relocation_info) !Relocation {
+        const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
+        const target = try self.object.symbolFromReloc(rel);
+        return Relocation{
+            .offset = offset,
+            .target = target,
+            .block = self.block,
+            .payload = undefined,
+        };
+    }
+
+    fn parseUnsigned(self: *Parser, rel: macho.relocation_info) !Relocation {
+        defer {
+            // Reset parser's subtractor state
+            self.subtractor = null;
+        }
+
+        assert(rel.r_pcrel == 0);
+
+        var parsed = try self.parseBaseRelInfo(rel);
+        const is_64bit: bool = switch (rel.r_length) {
+            3 => true,
+            2 => false,
+            else => unreachable,
+        };
+        const addend: i64 = if (is_64bit)
+            mem.readIntLittle(i64, self.block.code[parsed.offset..][0..8])
+        else
+            mem.readIntLittle(i32, self.block.code[parsed.offset..][0..4]);
+
+        parsed.payload = .{
+            .unsigned = .{
+                .subtractor = self.subtractor,
+                .is_64bit = is_64bit,
+                .addend = addend,
+            },
+        };
+
+        return parsed;
+    }
+
+    fn parseBranch(self: *Parser, rel: macho.relocation_info) !Relocation {
+        assert(rel.r_pcrel == 1);
+        assert(rel.r_length == 2);
+
+        var parsed = try self.parseBaseRelInfo(rel);
+        parsed.payload = .{
+            .branch = .{
+                .arch = self.object.arch.?,
+            },
+        };
+        return parsed;
+    }
+
+    fn parsePage(self: *Parser, rel: macho.relocation_info) !Relocation {
+        assert(rel.r_pcrel == 1);
+        assert(rel.r_length == 2);
+
+        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
+
+        defer if (rel_type == .ARM64_RELOC_PAGE21) {
+            // Reset parser's addend state
+            self.addend = null;
+        };
+
+        const addend = if (rel_type == .ARM64_RELOC_PAGE21)
+            self.addend
+        else
+            null;
+
+        var parsed = try self.parseBaseRelInfo(rel);
+        parsed.payload = .{
+            .page = .{
+                .kind = switch (rel_type) {
+                    .ARM64_RELOC_PAGE21 => .page,
+                    .ARM64_RELOC_GOT_LOAD_PAGE21 => .got,
+                    .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp,
+                    else => unreachable,
+                },
+                .addend = addend,
+            },
+        };
+        return parsed;
+    }
+
+    fn parsePageOff(self: *Parser, rel: macho.relocation_info) !Relocation {
+        assert(rel.r_pcrel == 0);
+        assert(rel.r_length == 2);
+
+        const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
+
+        defer if (rel_type == .ARM64_RELOC_PAGEOFF12) {
+            // Reset parser's addend state
+            self.addend = null;
+        };
+
+        const addend = if (rel_type == .ARM64_RELOC_PAGEOFF12)
+            self.addend
+        else
+            null;
+
+        var parsed = try self.parseBaseRelInfo(rel);
+        const op_kind: ?Relocation.PageOff.OpKind = blk: {
+            if (rel_type != .ARM64_RELOC_PAGEOFF12) break :blk null;
+            const op_kind: Relocation.PageOff.OpKind = if (isArithmeticOp(self.block.code[parsed.offset..][0..4]))
+                .arithmetic
+            else
+                .load;
+            break :blk op_kind;
+        };
+
+        parsed.payload = .{
+            .page_off = .{
+                .kind = switch (rel_type) {
+                    .ARM64_RELOC_PAGEOFF12 => .page,
+                    .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got,
+                    .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp,
+                    else => unreachable,
+                },
+                .addend = addend,
+                .op_kind = op_kind,
+            },
+        };
+        return parsed;
+    }
+
+    fn parsePointerToGot(self: *Parser, rel: macho.relocation_info) !Relocation {
+        assert(rel.r_pcrel == 1);
+        assert(rel.r_length == 2);
+
+        var parsed = try self.parseBaseRelInfo(rel);
+        parsed.payload = .{
+            .pointer_to_got = .{},
+        };
+        return parsed;
+    }
+
+    fn parseAddend(self: *Parser, rel: macho.relocation_info) !void {
+        assert(rel.r_pcrel == 0);
+        assert(rel.r_extern == 0);
+        assert(self.addend == null);
+
+        self.addend = rel.r_symbolnum;
+    }
+
+    fn parseSigned(self: *Parser, rel: macho.relocation_info) !Relocation {
+        assert(rel.r_pcrel == 1);
+        assert(rel.r_length == 2);
+
+        var parsed = try self.parseBaseRelInfo(rel);
+        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
+        const correction: i4 = switch (rel_type) {
+            .X86_64_RELOC_SIGNED => 0,
+            .X86_64_RELOC_SIGNED_1 => 1,
+            .X86_64_RELOC_SIGNED_2 => 2,
+            .X86_64_RELOC_SIGNED_4 => 4,
+            else => unreachable,
+        };
+        const addend = mem.readIntLittle(i32, self.block.code[parsed.offset..][0..4]) + correction;
+
+        parsed.payload = .{
+            .signed = .{
+                .correction = correction,
+                .addend = addend,
+            },
+        };
+
+        return parsed;
+    }
+
+    fn parseSubtractor(self: *Parser, rel: macho.relocation_info) !void {
+        assert(rel.r_pcrel == 0);
+        assert(self.subtractor == null);
+
+        self.subtractor = try self.object.symbolFromReloc(rel);
+    }
+
+    fn parseLoad(self: *Parser, rel: macho.relocation_info) !Relocation {
+        assert(rel.r_pcrel == 1);
+        assert(rel.r_length == 2);
+
+        var parsed = try self.parseBaseRelInfo(rel);
+        const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
+        const addend = if (rel_type == .X86_64_RELOC_GOT)
+            mem.readIntLittle(i32, self.block.code[parsed.offset..][0..4])
+        else
+            null;
+
+        parsed.payload = .{
+            .load = .{
+                .kind = switch (rel_type) {
+                    .X86_64_RELOC_GOT_LOAD, .X86_64_RELOC_GOT => .got,
+                    .X86_64_RELOC_TLV => .tlvp,
+                    else => unreachable,
+                },
+                .addend = addend,
+            },
+        };
+        return parsed;
+    }
+};
+
+inline fn isArithmeticOp(inst: *const [4]u8) bool {
+    const group_decode = @truncate(u5, inst[3]);
+    return ((group_decode >> 2) == 4);
+}
src/link/MachO/Zld.zig
@@ -137,7 +137,7 @@ pub const TextBlock = struct {
     aliases: ?[]u32 = null,
     references: std.AutoArrayHashMap(u32, void),
     code: []u8,
-    relocs: std.ArrayList(*Relocation),
+    relocs: std.ArrayList(Relocation),
     size: u64,
     alignment: u32,
     next: ?*TextBlock = null,
@@ -1604,7 +1604,7 @@ fn resolveSymbols(self: *Zld) !void {
                     .local_sym_index = local_sym_index,
                     .references = std.AutoArrayHashMap(u32, void).init(self.allocator),
                     .code = code,
-                    .relocs = std.ArrayList(*Relocation).init(self.allocator),
+                    .relocs = std.ArrayList(Relocation).init(self.allocator),
                     .size = size,
                     .alignment = alignment,
                 };
@@ -1871,64 +1871,6 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void {
     }
 }
 
-fn relocTargetAddr(self: *Zld, object: *const Object, target: reloc.Relocation.Target) !u64 {
-    const target_addr = blk: {
-        switch (target) {
-            .symbol => |sym_id| {
-                const sym = object.symbols.items[sym_id];
-                switch (sym.payload) {
-                    .regular => |reg| {
-                        log.debug("    | regular '{s}'", .{sym.name});
-                        break :blk reg.address;
-                    },
-                    .proxy => |proxy| {
-                        if (mem.eql(u8, sym.name, "__tlv_bootstrap")) {
-                            log.debug("    | symbol '__tlv_bootstrap'", .{});
-                            const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
-                            const tlv = segment.sections.items[self.tlv_section_index.?];
-                            break :blk tlv.addr;
-                        }
-
-                        log.debug("    | symbol stub '{s}'", .{sym.name});
-                        const segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-                        const stubs = segment.sections.items[self.stubs_section_index.?];
-                        const stubs_index = sym.stubs_index orelse {
-                            if (proxy.bind_info.items.len > 0) {
-                                break :blk 0; // Dynamically bound by dyld.
-                            }
-                            log.err(
-                                "expected stubs index or dynamic bind address when relocating symbol '{s}'",
-                                .{sym.name},
-                            );
-                            log.err("this is an internal linker error", .{});
-                            return error.FailedToResolveRelocationTarget;
-                        };
-                        break :blk stubs.addr + stubs_index * stubs.reserved2;
-                    },
-                    else => {
-                        log.err("failed to resolve symbol '{s}' as a relocation target", .{sym.name});
-                        log.err("this is an internal linker error", .{});
-                        return error.FailedToResolveRelocationTarget;
-                    },
-                }
-            },
-            .section => |sect_id| {
-                log.debug("    | section offset", .{});
-                const source_sect = object.sections.items[sect_id];
-                log.debug("    | section '{s},{s}'", .{
-                    segmentName(source_sect.inner),
-                    sectionName(source_sect.inner),
-                });
-                const target_map = source_sect.target_map orelse unreachable;
-                const target_seg = self.load_commands.items[target_map.segment_id].Segment;
-                const target_sect = target_seg.sections.items[target_map.section_id];
-                break :blk target_sect.addr + target_map.offset;
-            },
-        }
-    };
-    return target_addr;
-}
-
 fn populateMetadata(self: *Zld) !void {
     if (self.pagezero_segment_cmd_index == null) {
         self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
CMakeLists.txt
@@ -588,8 +588,6 @@ set(ZIG_STAGE2_SOURCES
     "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc.zig"
-    "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig"
-    "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig"
     "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
     "${CMAKE_SOURCE_DIR}/src/link/tapi.zig"
     "${CMAKE_SOURCE_DIR}/src/link/tapi/parse.zig"