diff --git a/src/codegen.zig b/src/codegen.zig index 1482e8de7e..35443ba0b1 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2588,17 +2588,64 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // For MachO, the binary, with the exception of object files, has to be a PIE. // Therefore we cannot load an absolute address. // Instead, we need to make use of PC-relative addressing. - // if (reg.id() == 0) { // x0 is special-cased + // TODO This needs to be optimised in the stack usage (perhaps use a shadow stack + // like described here: + // https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop) + // TODO As far as branching is concerned, instead of saving the return address + // in a register, I'm thinking here of immitating x86_64, and having the address + // passed on the stack. + if (reg.id() == 0) { // x0 is special-cased + // str x28, [sp, #-16] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{ + .offset = Instruction.Offset.imm_pre_index(-16), + }).toU32()); + // adr x28, #8 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ .address = addr, .start = self.code.items.len, .len = 4, }); - // bl [label] - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bl(0).toU32()); - // } else { - // unreachable; // TODO - // } + // b [label] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); + // mov r, x0 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32()); + // ldr x28, [sp], #16 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ + .rn = Register.sp, + .offset = Instruction.Offset.imm_post_index(16), + }).toU32()); + } else { + // str x28, [sp, #-16] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{ + .offset = Instruction.Offset.imm_pre_index(-16), + }).toU32()); + // str x0, [sp, #-16] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x0, Register.sp, .{ + .offset = Instruction.Offset.imm_pre_index(-16), + }).toU32()); + // adr x28, #8 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); + try self.mod_fn.owner_decl.link.macho.addPieFixup(self.bin_file.allocator, .{ + .address = addr, + .start = self.code.items.len, + .len = 4, + }); + // b [label] + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); + // mov r, x0 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32()); + // ldr x0, [sp], #16 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x0, .{ + .rn = Register.sp, + .offset = Instruction.Offset.imm_post_index(16), + }).toU32()); + // ldr x28, [sp], #16 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ + .rn = Register.sp, + .offset = Instruction.Offset.imm_post_index(16), + }).toU32()); + } } 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. diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 18fccb823e..33b4a14eda 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -19,6 +19,8 @@ pub const Register = enum(u6) { w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30, wzr, + pub const sp = .xzr; + pub fn id(self: Register) u5 { return @truncate(u5, @enumToInt(self)); } @@ -195,6 +197,17 @@ test "FloatingPointRegister.toX" { /// Represents an instruction in the AArch64 instruction set pub const Instruction = union(enum) { + OrShiftedRegister: packed struct { + rd: u5, + rn: u5, + imm6: u6, + rm: u5, + n: u1, + shift: u2, + fixed: u5 = 0b01010, + opc: u2 = 0b01, + sf: u1, + }, MoveWideImmediate: packed struct { rd: u5, imm16: u16, @@ -251,6 +264,7 @@ pub const Instruction = union(enum) { pub fn toU32(self: Instruction) u32 { return switch (self) { + .OrShiftedRegister => |v| @bitCast(u32, v), .MoveWideImmediate => |v| @bitCast(u32, v), .PCRelativeAddress => |v| @bitCast(u32, v), .LoadStoreRegister => |v| @bitCast(u32, v), @@ -379,8 +393,65 @@ pub const Instruction = union(enum) { } }; + pub const RegisterShift = struct { + rn: u5, + imm6: u6, + shift: enum(u2) { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3, + }, + + pub fn none() RegisterShift { + return .{ + .rn = 0b11111, + .imm6 = 0, + .shift = .Lsl, + }; + } + }; + // Helper functions for assembly syntax functions + fn orShiftedRegister( + rd: Register, + rm: Register, + shift: RegisterShift, + invert: bool, + ) Instruction { + const n: u1 = if (invert) 1 else 0; + switch (rd.size()) { + 32 => { + return Instruction{ + .OrShiftedRegister = .{ + .rd = rd.id(), + .rn = shift.rn, + .imm6 = shift.imm6, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .sf = 0, + }, + }; + }, + 64 => { + return Instruction{ + .OrShiftedRegister = .{ + .rd = rd.id(), + .rn = shift.rn, + .imm6 = shift.imm6, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .sf = 1, + }, + }; + }, + else => unreachable, // unexpected register size + } + } + fn moveWideImmediate( opc: u2, rd: Register, @@ -543,6 +614,16 @@ pub const Instruction = union(enum) { }; } + // Bitwise (inclusive) OR of a register value + + pub fn orr(rd: Register, rm: Register, shift: RegisterShift) Instruction { + return orShiftedRegister(rd, rm, shift, false); + } + + pub fn orn(rd: Register, rm: Register, shift: RegisterShift) Instruction { + return orShiftedRegister(rd, rm, shift, true); + } + // Move wide (immediate) pub fn movn(rd: Register, imm16: u16, shift: u6) Instruction { @@ -653,6 +734,14 @@ test "serialize instructions" { }; const testcases = [_]Testcase{ + .{ // orr x0 x1 + .inst = Instruction.orr(.x0, .x1, Instruction.RegisterShift.none()), + .expected = 0b1_01_01010_00_0_00001_000000_11111_00000, + }, + .{ // orn x0 x1 + .inst = Instruction.orn(.x0, .x1, Instruction.RegisterShift.none()), + .expected = 0b1_01_01010_00_1_00001_000000_11111_00000, + }, .{ // movz x1 #4 .inst = Instruction.movz(.x1, 4, 0), .expected = 0b1_10_100101_00_0000000000000100_00001, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index b6f5cb9ce8..e0d5cc0d3c 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1028,7 +1028,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { } else { const displacement = @intCast(u27, target_addr - this_addr); var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; - mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); + mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@intCast(i28, displacement)).toU32()); } } @@ -1670,10 +1670,10 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { } else { const pos_symbol_off = @intCast(u20, vmaddr - self.offset_table.items[index]); const symbol_off = @intCast(i21, pos_symbol_off) * -1; - // adr .x0 [-disp] - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x1, symbol_off).toU32()); - // ret - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(null).toU32()); + // adr x0, #-disp + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32()); + // ret x28 + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32()); } log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); try self.base.file.?.pwriteAll(&code, off);