Commit 80b1041c21

Jakub Konka <kubkon@jakubkonka.com>
2020-11-23 13:49:43
stage2 macho: use RIP-relative for memory-set regs x86_64
1 parent 59fe3d4
Changed files (2)
src/link/MachO.zig
@@ -214,10 +214,21 @@ pub const TextBlock = struct {
     /// Unlike in Elf, we need to store the size of this symbol as part of
     /// the TextBlock since macho.nlist_64 lacks this information.
     size: u64,
+    /// List of RIP-relative positions in the code
+    /// This is a table of all RIP-relative positions that will need fixups
+    /// after codegen when linker assigns addresses to GOT entries.
+    /// TODO handle freeing, shrinking and re-allocs
+    rip_positions: std.ArrayListUnmanaged(RipPosition) = .{},
     /// Points to the previous and next neighbours
     prev: ?*TextBlock,
     next: ?*TextBlock,
 
+    pub const RipPosition = struct {
+        address: u64,
+        start: usize,
+        len: usize,
+    };
+
     pub const empty = TextBlock{
         .local_sym_index = 0,
         .offset_table_index = undefined,
@@ -226,6 +237,15 @@ pub const TextBlock = struct {
         .next = null,
     };
 
+    pub fn addRipPosition(self: *TextBlock, alloc: *Allocator, rip: RipPosition) !void {
+        std.debug.print("text_block={}, rip={}\n", .{ self.local_sym_index, rip });
+        return self.rip_positions.append(alloc, rip);
+    }
+
+    fn deinit(self: *TextBlock, alloc: *Allocator) void {
+        self.rip_positions.deinit(alloc);
+    }
+
     /// Returns how much room there is to grow in virtual address space.
     /// File offset relocation happens transparently, so it is not included in
     /// this calculation.
@@ -993,6 +1013,20 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
         try self.writeOffsetTableEntry(decl.link.macho.offset_table_index);
     }
 
+    // Perform RIP-relative fixups (if any)
+    const got_section = self.sections.items[self.got_section_index.?];
+    for (decl.link.macho.rip_positions.items) |rip| {
+        std.debug.print("rip={}\n", .{rip});
+        const target_addr = rip.address;
+        // const got_addr = got_section.addr + decl.link.macho.offset_table_index * @sizeOf(u64);
+        const this_addr = symbol.n_value + rip.start;
+        std.debug.print("target_addr=0x{x},this_addr=0x{x}\n", .{target_addr, this_addr});
+        const displacement = @intCast(u32, target_addr - this_addr + rip.len);
+        std.debug.print("displacement=0x{x}\n", .{displacement});
+        var placeholder = code_buffer.items[rip.start + rip.len - @sizeOf(u32) ..][0..@sizeOf(u32)];
+        mem.writeIntSliceLittle(u32, placeholder, displacement);
+    }
+
     const text_section = self.sections.items[self.text_section_index.?];
     const section_offset = symbol.n_value - text_section.addr;
     const file_offset = text_section.offset + section_offset;
src/codegen.zig
@@ -1683,11 +1683,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64);
                         switch (arch) {
                             .x86_64 => {
-                                // Here, we store the got address in %rax, and then call %rax
-                                // movabsq [addr], %rax
                                 try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr });
                                 // callq *%rax
-                                try self.code.ensureCapacity(self.code.items.len + 2);
                                 self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 });
                             },
                             .aarch64 => {
@@ -2766,7 +2763,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R });
                     },
                     .memory => |x| {
-                        if (x <= math.maxInt(u32)) {
+                        if (self.bin_file.cast(link.File.MachO)) |macho_file| {
+                            // For MachO, the binary, with the exception of object files, has to be a PIE.
+                            // Therefore, we cannot load an absolute address.
+                            assert(x > math.maxInt(u32)); // 32bit direct addressing is not supported by MachO.
+                            // The plan here is to use RIP-relative addressing, but leaving the actual displacement
+                            // information empty (0-padded) and fixing it up later in the linker.
+                            try self.mod_fn.owner_decl.link.macho.addRipPosition(self.bin_file.allocator, .{
+                                .address = x,
+                                .start = self.code.items.len,
+                                .len = 7,
+                            });
+                            try self.code.ensureCapacity(self.code.items.len + 9);
+                            // leaq %r, [rip + disp]
+                            self.code.appendSliceAssumeCapacity(&[_]u8{
+                                0x48,
+                                0x8d,
+                                0x05 | (@as(u8, reg.id() & 0b111) << 3), // R
+                                0x0,
+                                0x0,
+                                0x0,
+                                0x0,
+                            });
+                        } else if (x <= math.maxInt(u32)) {
                             // Moving from memory to a register is a variant of `8B /r`.
                             // Since we're using 64-bit moves, we require a REX.
                             // This variant also requires a SIB, as it would otherwise be RIP-relative.