Commit 9471e3da35

joachimschmidt557 <joachim.schmidt557@outlook.com>
2021-10-30 16:16:22
stage2 AArch64 Emit: implement call_extern and load_memory
1 parent 7fc89f6
Changed files (3)
src/arch/aarch64/CodeGen.zig
@@ -315,6 +315,7 @@ pub fn generate(
 
     var emit = Emit{
         .mir = mir,
+        .bin_file = bin_file,
         .target = &bin_file.options.target,
         .code = code,
     };
@@ -337,6 +338,25 @@ fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
     return result_index;
 }
 
+pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 {
+    const fields = std.meta.fields(@TypeOf(extra));
+    try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len);
+    return self.addExtraAssumeCapacity(extra);
+}
+
+pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 {
+    const fields = std.meta.fields(@TypeOf(extra));
+    const result = @intCast(u32, self.mir_extra.items.len);
+    inline for (fields) |field| {
+        self.mir_extra.appendAssumeCapacity(switch (field.field_type) {
+            u32 => @field(extra, field.name),
+            i32 => @bitCast(u32, @field(extra, field.name)),
+            else => @compileError("bad field type"),
+        });
+    }
+    return result;
+}
+
 fn gen(self: *Self) !void {
     const cc = self.fn_type.fnCallingConvention();
     if (cc != .Naked) {
@@ -1537,26 +1557,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void {
             } else if (func_value.castTag(.extern_fn)) |func_payload| {
                 const decl = func_payload.data;
                 const n_strx = try macho_file.addExternFn(mem.spanZ(decl.name));
-                const offset = blk: {
-                    // TODO add a pseudo-instruction
-                    const offset = @intCast(u32, self.mir_instructions.len);
-                    // bl
-                    // mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bl(0).toU32());
-                    _ = try self.addInst(.{
-                        .tag = .bl,
-                        .data = .{ .nop = {} },
-                    });
-                    break :blk offset;
-                };
-                // Add relocation to the decl.
-                try macho_file.active_decl.?.link.macho.relocs.append(self.bin_file.allocator, .{
-                    .offset = offset,
-                    .target = .{ .global = n_strx },
-                    .addend = 0,
-                    .subtractor = null,
-                    .pcrel = true,
-                    .length = 2,
-                    .@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_BRANCH26),
+
+                _ = try self.addInst(.{
+                    .tag = .call_extern,
+                    .data = .{ .extern_fn = n_strx },
                 });
             } else {
                 return self.fail("TODO implement calling bitcasted functions", .{});
@@ -2185,70 +2189,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
             });
         },
         .memory => |addr| {
-            if (self.bin_file.options.pie) {
-                // PC-relative displacement to the entry in the GOT table.
-                // adrp
-                // TODO add a pseudo instruction
-                const offset = @intCast(u32, self.mir_instructions.len);
-                // mem.writeIntLittle(
-                //     u32,
-                //     try self.code.addManyAsArray(4),
-                //     Instruction.adrp(reg, 0).toU32(),
-                // );
-                _ = try self.addInst(.{
-                    .tag = .nop,
-                    .data = .{ .nop = {} },
-                });
-
-                // ldr reg, reg, offset
-                _ = try self.addInst(.{
-                    .tag = .ldr,
-                    .data = .{ .load_store_register = .{
-                        .rt = reg,
-                        .rn = reg,
-                        .offset = Instruction.LoadStoreOffset.imm(0),
-                    } },
-                });
-
-                if (self.bin_file.cast(link.File.MachO)) |macho_file| {
-                    // TODO I think the reloc might be in the wrong place.
-                    const decl = macho_file.active_decl.?;
-                    // Page reloc for adrp instruction.
-                    try decl.link.macho.relocs.append(self.bin_file.allocator, .{
-                        .offset = offset,
-                        .target = .{ .local = @intCast(u32, addr) },
-                        .addend = 0,
-                        .subtractor = null,
-                        .pcrel = true,
-                        .length = 2,
-                        .@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGE21),
-                    });
-                    // Pageoff reloc for adrp instruction.
-                    try decl.link.macho.relocs.append(self.bin_file.allocator, .{
-                        .offset = offset + 4,
-                        .target = .{ .local = @intCast(u32, addr) },
-                        .addend = 0,
-                        .subtractor = null,
-                        .pcrel = false,
-                        .length = 2,
-                        .@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGEOFF12),
-                    });
-                } else {
-                    return self.fail("TODO implement genSetReg for PIE GOT indirection on this platform", .{});
-                }
-            } else {
-                // The value is in memory at a hard-coded address.
-                // If the type is a pointer, it means the pointer address is at this memory location.
-                try self.genSetReg(Type.initTag(.usize), reg, .{ .immediate = addr });
-                _ = try self.addInst(.{
-                    .tag = .ldr,
-                    .data = .{ .load_store_register = .{
-                        .rt = reg,
-                        .rn = reg,
-                        .offset = Instruction.LoadStoreOffset.none,
-                    } },
-                });
-            }
+            _ = try self.addInst(.{
+                .tag = .load_memory,
+                .data = .{ .payload = try self.addExtra(Mir.LoadMemory{
+                    .register = @enumToInt(reg),
+                    .addr = @intCast(u32, addr),
+                }) },
+            });
         },
         .stack_offset => |unadjusted_off| {
             // TODO: maybe addressing from sp instead of fp
src/arch/aarch64/Emit.zig
@@ -3,11 +3,16 @@
 
 const Emit = @This();
 const std = @import("std");
+const math = std.math;
 const Mir = @import("Mir.zig");
 const bits = @import("bits.zig");
+const link = @import("../../link.zig");
+const assert = std.debug.assert;
 const Instruction = bits.Instruction;
+const Register = bits.Register;
 
 mir: Mir,
+bin_file: *link.File,
 target: *const std.Target,
 code: *std.ArrayList(u8),
 
@@ -31,6 +36,10 @@ pub fn emitMir(
             .brk => try emit.mirExceptionGeneration(inst),
             .svc => try emit.mirExceptionGeneration(inst),
 
+            .call_extern => try emit.mirCallExtern(inst),
+
+            .load_memory => try emit.mirLoadMemory(inst),
+
             .ldp => try emit.mirLoadStoreRegisterPair(inst),
             .stp => try emit.mirLoadStoreRegisterPair(inst),
 
@@ -57,6 +66,20 @@ fn writeInstruction(emit: *Emit, instruction: Instruction) !void {
     std.mem.writeInt(u32, try emit.code.addManyAsArray(4), instruction.toU32(), endian);
 }
 
+fn moveImmediate(emit: *Emit, reg: Register, imm64: u64) !void {
+    try emit.writeInstruction(Instruction.movz(reg, @truncate(u16, imm64), 0));
+
+    if (imm64 > math.maxInt(u16)) {
+        try emit.writeInstruction(Instruction.movk(reg, @truncate(u16, imm64 >> 16), 16));
+    }
+    if (imm64 > math.maxInt(u32)) {
+        try emit.writeInstruction(Instruction.movk(reg, @truncate(u16, imm64 >> 32), 32));
+    }
+    if (imm64 > math.maxInt(u48)) {
+        try emit.writeInstruction(Instruction.movk(reg, @truncate(u16, imm64 >> 48), 48));
+    }
+}
+
 fn mirAddSubtractImmediate(emit: *Emit, inst: Mir.Inst.Index) !void {
     const tag = emit.mir.instructions.items(.tag)[inst];
     const rr_imm12_sh = emit.mir.instructions.items(.data)[inst].rr_imm12_sh;
@@ -113,6 +136,90 @@ fn mirExceptionGeneration(emit: *Emit, inst: Mir.Inst.Index) !void {
     }
 }
 
+fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void {
+    assert(emit.mir.instructions.items(.tag)[inst] == .call_extern);
+    const n_strx = emit.mir.instructions.items(.data)[inst].extern_fn;
+
+    if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
+        const offset = blk: {
+            const offset = @intCast(u32, emit.code.items.len);
+            // bl
+            try emit.writeInstruction(Instruction.bl(0));
+            break :blk offset;
+        };
+        // Add relocation to the decl.
+        try macho_file.active_decl.?.link.macho.relocs.append(emit.bin_file.allocator, .{
+            .offset = offset,
+            .target = .{ .global = n_strx },
+            .addend = 0,
+            .subtractor = null,
+            .pcrel = true,
+            .length = 2,
+            .@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_BRANCH26),
+        });
+    } else {
+        @panic("Implement call_extern for linking backends != MachO");
+    }
+}
+
+fn mirLoadMemory(emit: *Emit, inst: Mir.Inst.Index) !void {
+    assert(emit.mir.instructions.items(.tag)[inst] == .load_memory);
+    const payload = emit.mir.instructions.items(.data)[inst].payload;
+    const load_memory = emit.mir.extraData(Mir.LoadMemory, payload).data;
+    const reg = @intToEnum(Register, load_memory.register);
+    const addr = load_memory.addr;
+
+    if (emit.bin_file.options.pie) {
+        // PC-relative displacement to the entry in the GOT table.
+        // adrp
+        const offset = @intCast(u32, emit.code.items.len);
+        try emit.writeInstruction(Instruction.adrp(reg, 0));
+
+        // ldr reg, reg, offset
+        try emit.writeInstruction(Instruction.ldr(reg, .{
+            .register = .{
+                .rn = reg,
+                .offset = Instruction.LoadStoreOffset.imm(0),
+            },
+        }));
+
+        if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
+            // TODO I think the reloc might be in the wrong place.
+            const decl = macho_file.active_decl.?;
+            // Page reloc for adrp instruction.
+            try decl.link.macho.relocs.append(emit.bin_file.allocator, .{
+                .offset = offset,
+                .target = .{ .local = addr },
+                .addend = 0,
+                .subtractor = null,
+                .pcrel = true,
+                .length = 2,
+                .@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGE21),
+            });
+            // Pageoff reloc for adrp instruction.
+            try decl.link.macho.relocs.append(emit.bin_file.allocator, .{
+                .offset = offset + 4,
+                .target = .{ .local = addr },
+                .addend = 0,
+                .subtractor = null,
+                .pcrel = false,
+                .length = 2,
+                .@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGEOFF12),
+            });
+        } else {
+            return @panic("TODO implement load_memory for PIE GOT indirection on this platform");
+        }
+    } else {
+        // The value is in memory at a hard-coded address.
+        // If the type is a pointer, it means the pointer address is at this memory location.
+        try emit.moveImmediate(reg, addr);
+        try emit.writeInstruction(Instruction.ldr(
+            reg,
+            .{ .register = .{ .rn = reg, .offset = Instruction.LoadStoreOffset.none } },
+        ));
+    }
+}
+
 fn mirLoadStoreRegisterPair(emit: *Emit, inst: Mir.Inst.Index) !void {
     const tag = emit.mir.instructions.items(.tag)[inst];
     const load_store_register_pair = emit.mir.instructions.items(.data)[inst].load_store_register_pair;
src/arch/aarch64/Mir.zig
@@ -34,6 +34,12 @@ pub const Inst = struct {
         blr,
         /// Breakpoint
         brk,
+        /// Pseudo-instruction: Call extern
+        call_extern,
+        /// Psuedo-instruction: Load memory
+        ///
+        /// Payload is `LoadMemory`
+        load_memory,
         /// Load Pair of Registers
         ldp,
         /// Load Register
@@ -89,11 +95,17 @@ pub const Inst = struct {
         ///
         /// Used by e.g. b
         inst: Index,
+        /// An extern function
+        ///
+        /// Used by e.g. call_extern
+        extern_fn: u32,
         /// A 16-bit immediate value.
         ///
         /// Used by e.g. svc
         imm16: u16,
         /// Index into `extra`. Meaning of what can be found there is context-dependent.
+        ///
+        /// Used by e.g. load_memory
         payload: u32,
         /// A register
         ///
@@ -156,3 +168,28 @@ pub fn deinit(mir: *Mir, gpa: *std.mem.Allocator) void {
     gpa.free(mir.extra);
     mir.* = undefined;
 }
+
+/// Returns the requested data, as well as the new index which is at the start of the
+/// trailers for the object.
+pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
+    const fields = std.meta.fields(T);
+    var i: usize = index;
+    var result: T = undefined;
+    inline for (fields) |field| {
+        @field(result, field.name) = switch (field.field_type) {
+            u32 => mir.extra[i],
+            i32 => @bitCast(i32, mir.extra[i]),
+            else => @compileError("bad field type"),
+        };
+        i += 1;
+    }
+    return .{
+        .data = result,
+        .end = i,
+    };
+}
+
+pub const LoadMemory = struct {
+    register: u32,
+    addr: u32,
+};