From 5ad501c00b59205154caeec95685351ee613ea5e Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Wed, 28 Oct 2020 14:39:55 +0100 Subject: [PATCH 01/16] stage2 aarch64: add codegen/aarch64.zig --- src/codegen.zig | 7 +- src/codegen/aarch64.zig | 240 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 src/codegen/aarch64.zig diff --git a/src/codegen.zig b/src/codegen.zig index ce509eb952..6962dcf8e4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -83,9 +83,9 @@ pub fn generateSymbol( .wasm64 => unreachable, // has its own code path .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output), .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output), + .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output), + .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output), + .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output), //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output), //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output), //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, debug_output), @@ -3007,6 +3007,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .riscv64 => @import("codegen/riscv64.zig"), .spu_2 => @import("codegen/spu-mk2.zig"), .arm, .armeb => @import("codegen/arm.zig"), + .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig"), else => struct { pub const Register = enum { dummy, diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig new file mode 100644 index 0000000000..2ca9a71292 --- /dev/null +++ b/src/codegen/aarch64.zig @@ -0,0 +1,240 @@ +const std = @import("std"); +const DW = std.dwarf; +const testing = std.testing; + +// zig fmt: off + +/// General purpose registers in the AArch64 instruction set +pub const Register = enum(u6) { + // 64-bit registers + x0, x1, x2, x3, x4, x5, x6, x7, + x8, x9, x10, x11, x12, x13, x14, x15, + x16, x17, x18, x19, x20, x21, x22, x23, + x24, x25, x26, x27, x28, x29, x30, xzr, + + // 32-bit registers + w0, w1, w2, w3, w4, w5, w6, w7, + w8, w9, w10, w11, w12, w13, w14, w15, + w16, w17, w18, w19, w20, w21, w22, w23, + w24, w25, w26, w27, w28, w29, w30, wzr, + + pub fn id(self: Register) u5 { + return @truncate(u5, @enumToInt(self)); + } + + /// Returns the bit-width of the register. + pub fn size(self: Register) u7 { + return switch (@enumToInt(self)) { + 0...31 => 64, + 32...63 => 32, + }; + } + + /// Convert from any register to its 64 bit alias. + pub fn to64(self: Register) Register { + return @intToEnum(Register, self.id()); + } + + /// Convert from any register to its 32 bit alias. + pub fn to32(self: Register) Register { + return @intToEnum(Register, @as(u6, self.id()) + 32); + } + + /// Returns the index into `callee_preserved_regs`. + pub fn allocIndex(self: Register) ?u4 { + inline for (callee_preserved_regs) |cpreg, i| { + if (self.id() == cpreg.id()) return i; + } + return null; + } + + pub fn dwarfLocOp(self: Register) u8 { + return @as(u8, self.id()) + DW.OP_reg0; + } +}; + +// zig fmt: on + +pub const callee_preserved_regs = [_]Register{ + .x19, .x20, .x21, .x22, .x23, + .x24, .x25, .x26, .x27, .x28, +}; + +test "Register.id" { + testing.expectEqual(@as(u5, 0), Register.x0.id()); + testing.expectEqual(@as(u5, 0), Register.w0.id()); + + testing.expectEqual(@as(u5, 31), Register.xzr.id()); + testing.expectEqual(@as(u5, 31), Register.wzr.id()); +} + +test "Register.size" { + testing.expectEqual(@as(u7, 64), Register.x19.size()); + testing.expectEqual(@as(u7, 32), Register.w3.size()); +} + +test "Register.to64/to32" { + testing.expectEqual(Register.x0, Register.w0.to64()); + testing.expectEqual(Register.x0, Register.x0.to64()); + + testing.expectEqual(Register.w3, Register.w3.to32()); + testing.expectEqual(Register.w3, Register.x3.to32()); +} + +// zig fmt: off + +/// Scalar floating point registers in the aarch64 instruction set +pub const FloatingPointRegister = enum(u8) { + // 128-bit registers + q0, q1, q2, q3, q4, q5, q6, q7, + q8, q9, q10, q11, q12, q13, q14, q15, + q16, q17, q18, q19, q20, q21, q22, q23, + q24, q25, q26, q27, q28, q29, q30, q31, + + // 64-bit registers + d0, d1, d2, d3, d4, d5, d6, d7, + d8, d9, d10, d11, d12, d13, d14, d15, + d16, d17, d18, d19, d20, d21, d22, d23, + d24, d25, d26, d27, d28, d29, d30, d31, + + // 32-bit registers + s0, s1, s2, s3, s4, s5, s6, s7, + s8, s9, s10, s11, s12, s13, s14, s15, + s16, s17, s18, s19, s20, s21, s22, s23, + s24, s25, s26, s27, s28, s29, s30, s31, + + // 16-bit registers + h0, h1, h2, h3, h4, h5, h6, h7, + h8, h9, h10, h11, h12, h13, h14, h15, + h16, h17, h18, h19, h20, h21, h22, h23, + h24, h25, h26, h27, h28, h29, h30, h31, + + // 8-bit registers + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, + + pub fn id(self: FloatingPointRegister) u5 { + return @truncate(u5, @enumToInt(self)); + } + + /// Returns the bit-width of the register. + pub fn size(self: FloatingPointRegister) u8 { + return switch (@enumToInt(self)) { + 0...31 => 128, + 32...63 => 64, + 64...95 => 32, + 96...127 => 16, + 128...159 => 8, + else => unreachable, + }; + } + + /// Convert from any register to its 128 bit alias. + pub fn to128(self: FloatingPointRegister) FloatingPointRegister { + return @intToEnum(FloatingPointRegister, self.id()); + } + + /// Convert from any register to its 64 bit alias. + pub fn to64(self: FloatingPointRegister) FloatingPointRegister { + return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 32); + } + + /// Convert from any register to its 32 bit alias. + pub fn to32(self: FloatingPointRegister) FloatingPointRegister { + return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 64); + } + + /// Convert from any register to its 16 bit alias. + pub fn to16(self: FloatingPointRegister) FloatingPointRegister { + return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 96); + } + + /// Convert from any register to its 8 bit alias. + pub fn to8(self: FloatingPointRegister) FloatingPointRegister { + return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 128); + } +}; + +// zig fmt: on + +test "FloatingPointRegister.id" { + testing.expectEqual(@as(u5, 0), FloatingPointRegister.b0.id()); + testing.expectEqual(@as(u5, 0), FloatingPointRegister.h0.id()); + testing.expectEqual(@as(u5, 0), FloatingPointRegister.s0.id()); + testing.expectEqual(@as(u5, 0), FloatingPointRegister.d0.id()); + testing.expectEqual(@as(u5, 0), FloatingPointRegister.q0.id()); + + testing.expectEqual(@as(u5, 2), FloatingPointRegister.q2.id()); + testing.expectEqual(@as(u5, 31), FloatingPointRegister.d31.id()); +} + +test "FloatingPointRegister.size" { + testing.expectEqual(@as(u8, 128), FloatingPointRegister.q1.size()); + testing.expectEqual(@as(u8, 64), FloatingPointRegister.d2.size()); + testing.expectEqual(@as(u8, 32), FloatingPointRegister.s3.size()); + testing.expectEqual(@as(u8, 16), FloatingPointRegister.h4.size()); + testing.expectEqual(@as(u8, 8), FloatingPointRegister.b5.size()); +} + +test "FloatingPointRegister.toX" { + testing.expectEqual(FloatingPointRegister.q1, FloatingPointRegister.q1.to128()); + testing.expectEqual(FloatingPointRegister.q2, FloatingPointRegister.b2.to128()); + testing.expectEqual(FloatingPointRegister.q3, FloatingPointRegister.h3.to128()); + + testing.expectEqual(FloatingPointRegister.d0, FloatingPointRegister.q0.to64()); + testing.expectEqual(FloatingPointRegister.s1, FloatingPointRegister.d1.to32()); + testing.expectEqual(FloatingPointRegister.h2, FloatingPointRegister.s2.to16()); + testing.expectEqual(FloatingPointRegister.b3, FloatingPointRegister.h3.to8()); +} + +/// Represents an instruction in the AArch64 instruction set +pub const Instruction = union(enum) { + SupervisorCall: packed struct { + fixed_1: u5 = 0b00001, + imm16: u16, + fixed_2: u11 = 0b11010100000, + }, + + pub fn toU32(self: Instruction) u32 { + return switch (self) { + .SupervisorCall => |v| @bitCast(u32, v), + }; + } + + // Helper functions for assembly syntax functions + + fn supervisorCall(imm16: u16) Instruction { + return Instruction{ + .SupervisorCall = .{ + .imm16 = imm16, + }, + }; + } + + // Supervisor Call + + fn svc(imm16: u16) Instruction { + return supervisorCall(imm16); + } +}; + +test "serialize instructions" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + const testcases = [_]Testcase{ + .{ // svc #0 + .inst = Instruction.svc(0), + .expected = 0b1101_0100_000_0000000000000000_00001, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + testing.expectEqual(case.expected, actual); + } +} From d542e8870688eeaa46f84efe892c9ab5d51d09c9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 31 Oct 2020 23:52:48 +0100 Subject: [PATCH 02/16] Implement genAsm on aarch64 Add remaining PCS info: param and return registers in procedure calls. --- src/codegen.zig | 41 +++++++++++++++++++++++++++++++++++++++++ src/codegen/aarch64.zig | 4 +++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/codegen.zig b/src/codegen.zig index 6962dcf8e4..6a7b67aee2 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2114,6 +2114,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return MCValue.none; } }, + .aarch64 => { + for (inst.inputs) |input, i| { + if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { + return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); + } + const reg_name = input[1 .. input.len - 1]; + const reg = parseRegName(reg_name) orelse + return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + const arg = try self.resolveInst(inst.args[i]); + try self.genSetReg(inst.base.src, reg, arg); + } + + // TODO move this to lib/std/{elf, macho}.zig, etc. + const is_syscall_inst = switch (self.bin_file.tag) { + .macho => mem.eql(u8, inst.asm_source, "svc #0x80"), + .elf => mem.eql(u8, inst.asm_source, "svc #0"), + else => |tag| return self.fail(inst.base.src, "TODO implement aarch64 support for other syscall instructions for file format: '{}'", .{tag}), + }; + if (is_syscall_inst) { + const imm16: u16 = switch (self.bin_file.tag) { + .macho => 0x80, + .elf => 0, + else => unreachable, + }; + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(imm16).toU32()); + } else { + return self.fail(inst.base.src, "TODO implement support for more aarch64 assembly instructions", .{}); + } + + if (inst.output) |output| { + if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { + return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); + } + const reg_name = output[2 .. output.len - 1]; + const reg = parseRegName(reg_name) orelse + return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return MCValue{ .register = reg }; + } else { + return MCValue.none; + } + }, .riscv64 => { for (inst.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 2ca9a71292..97491cdfd6 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -59,6 +59,8 @@ pub const callee_preserved_regs = [_]Register{ .x19, .x20, .x21, .x22, .x23, .x24, .x25, .x26, .x27, .x28, }; +pub const c_abi_int_param_regs = [_]Register{ .x0, .x1, .x2, .x3, .x4, .x5, .x6, .x7 }; +pub const c_abi_int_return_regs = [_]Register{ .x0, .x1 }; test "Register.id" { testing.expectEqual(@as(u5, 0), Register.x0.id()); @@ -215,7 +217,7 @@ pub const Instruction = union(enum) { // Supervisor Call - fn svc(imm16: u16) Instruction { + pub fn svc(imm16: u16) Instruction { return supervisorCall(imm16); } }; From 68bb1e91aaafbf57dc26cde7233e2deaf951b3a6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 1 Nov 2020 00:00:02 +0100 Subject: [PATCH 03/16] Add testcase for serializing svc #0x80 --- src/codegen/aarch64.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 97491cdfd6..a55dcc4978 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -233,6 +233,10 @@ test "serialize instructions" { .inst = Instruction.svc(0), .expected = 0b1101_0100_000_0000000000000000_00001, }, + .{ // svc #0x80 ; typical on Darwin + .inst = Instruction.svc(0x80), + .expected = 0b1101_0100_000_0000000010000000_00001, + }, }; for (testcases) |case| { From d601b0f4eb025c753ca9f139480578511122afad Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 6 Nov 2020 23:03:20 +0100 Subject: [PATCH 04/16] Add basic genSetReg for aarch64 --- src/codegen.zig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/codegen.zig b/src/codegen.zig index 6a7b67aee2..47d33570d5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2489,6 +2489,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}), }, + .aarch64 => switch (mcv) { + .dead => unreachable, + .ptr_stack_offset => unreachable, + .ptr_embedded_in_code => unreachable, + .unreach, .none => return, // Nothing to do. + .undef => { + if (!self.wantSafety()) + return; // The already existing value will do just fine. + // Write the debug undefined value. + switch (reg.size()) { + 32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }), + 64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), + else => unreachable, // unexpected register size + } + }, + .immediate => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), + .register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), + else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), + }, .riscv64 => switch (mcv) { .dead => unreachable, .ptr_stack_offset => unreachable, From 4ef6864a155cbb23edb5e5aaa5aa34fdd0e53b39 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 8 Nov 2020 14:49:31 +0100 Subject: [PATCH 05/16] Add move wide with zero (movz) instruction --- src/codegen.zig | 8 ++++- src/codegen/aarch64.zig | 66 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/codegen.zig b/src/codegen.zig index 47d33570d5..d946c913a9 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2504,7 +2504,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else => unreachable, // unexpected register size } }, - .immediate => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), + .immediate => |x| { + if (x <= math.maxInt(u16)) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32()); + } else { + return self.fail(src, "TODO genSetReg with 32,48,64bit immediates", .{}); + } + }, .register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), }, diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index a55dcc4978..ca5bbfc1ea 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -193,6 +193,15 @@ test "FloatingPointRegister.toX" { /// Represents an instruction in the AArch64 instruction set pub const Instruction = union(enum) { + MoveWideWithZero: packed struct { + rd: u5, + imm16: u16, + hw: u2, + fixed: u6 = 0b100101, + opc: u2 = 0b10, + sf: u1, + }, + SupervisorCall: packed struct { fixed_1: u5 = 0b00001, imm16: u16, @@ -201,12 +210,39 @@ pub const Instruction = union(enum) { pub fn toU32(self: Instruction) u32 { return switch (self) { + .MoveWideWithZero => |v| @bitCast(u32, v), .SupervisorCall => |v| @bitCast(u32, v), }; } // Helper functions for assembly syntax functions + fn moveWideWithZero(rd: Register, imm16: u16, shift: u2) Instruction { + switch (rd.size()) { + 32 => { + return Instruction{ + .MoveWideWithZero = .{ + .rd = rd.id(), + .imm16 = imm16, + .hw = 0b01 & shift, // TODO shift should be an enum + .sf = 0, + }, + }; + }, + 64 => { + return Instruction{ + .MoveWideWithZero = .{ + .rd = rd.id(), + .imm16 = imm16, + .hw = shift, + .sf = 1, + }, + }; + }, + else => unreachable, // unexpected register size + } + } + fn supervisorCall(imm16: u16) Instruction { return Instruction{ .SupervisorCall = .{ @@ -215,6 +251,12 @@ pub const Instruction = union(enum) { }; } + // movz + + pub fn movz(rd: Register, imm16: u16, shift: u2) Instruction { + return moveWideWithZero(rd, imm16, shift); + } + // Supervisor Call pub fn svc(imm16: u16) Instruction { @@ -237,6 +279,30 @@ test "serialize instructions" { .inst = Instruction.svc(0x80), .expected = 0b1101_0100_000_0000000010000000_00001, }, + .{ // movz x1 #4 + .inst = Instruction.movz(.x1, 4, 0), + .expected = 0b1_10_100101_00_0000000000000100_00001, + }, + .{ // movz x1, #4, lsl 16 + .inst = Instruction.movz(.x1, 4, 1), + .expected = 0b1_10_100101_01_0000000000000100_00001, + }, + .{ // movz x1, #4, lsl 32 + .inst = Instruction.movz(.x1, 4, 2), + .expected = 0b1_10_100101_10_0000000000000100_00001, + }, + .{ // movz x1, #4, lsl 48 + .inst = Instruction.movz(.x1, 4, 3), + .expected = 0b1_10_100101_11_0000000000000100_00001, + }, + .{ // movz w1, #4 + .inst = Instruction.movz(.w1, 4, 0), + .expected = 0b0_10_100101_00_0000000000000100_00001, + }, + .{ // movz w1, #4, lsl 16 + .inst = Instruction.movz(.w1, 4, 1), + .expected = 0b0_10_100101_01_0000000000000100_00001, + }, }; for (testcases) |case| { From 3c75d723ac94e578b95690a4f9558b2fbf894c39 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Wed, 28 Oct 2020 14:39:55 +0100 Subject: [PATCH 06/16] stage2 aarch64: add codegen/aarch64.zig --- src/codegen/aarch64.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index ca5bbfc1ea..b0f4026db7 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -59,6 +59,7 @@ pub const callee_preserved_regs = [_]Register{ .x19, .x20, .x21, .x22, .x23, .x24, .x25, .x26, .x27, .x28, }; + pub const c_abi_int_param_regs = [_]Register{ .x0, .x1, .x2, .x3, .x4, .x5, .x6, .x7 }; pub const c_abi_int_return_regs = [_]Register{ .x0, .x1 }; @@ -201,7 +202,6 @@ pub const Instruction = union(enum) { opc: u2 = 0b10, sf: u1, }, - SupervisorCall: packed struct { fixed_1: u5 = 0b00001, imm16: u16, From 4c8f69241a70d8eac76b288a5c6a2d586696534f Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 6 Nov 2020 09:15:16 +0100 Subject: [PATCH 07/16] stage2 aarch64: add more instructions --- src/codegen.zig | 3 ++ src/codegen/aarch64.zig | 113 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index d946c913a9..4f666dd0e7 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1380,6 +1380,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .arm, .armeb => { writeInt(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32()); }, + .aarch64 => { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.brk(1).toU32()); + }, else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), } return .none; diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index b0f4026db7..3b433eb267 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -1,5 +1,6 @@ const std = @import("std"); const DW = std.dwarf; +const assert = std.debug.assert; const testing = std.testing; // zig fmt: off @@ -202,16 +203,33 @@ pub const Instruction = union(enum) { opc: u2 = 0b10, sf: u1, }, - SupervisorCall: packed struct { - fixed_1: u5 = 0b00001, + ExceptionGeneration: packed struct { + ll: u2, + op2: u3, imm16: u16, - fixed_2: u11 = 0b11010100000, + opc: u3, + fixed: u8 = 0b1101_0100, + }, + UnconditionalBranchRegister: packed struct { + op4: u5, + rn: u5, + op3: u6, + op2: u5, + opc: u4, + fixed: u7 = 0b1101_011, + }, + UnconditionalBranchImmediate: packed struct { + imm26: u26, + fixed: u5 = 0b00101, + op: u1, }, pub fn toU32(self: Instruction) u32 { return switch (self) { .MoveWideWithZero => |v| @bitCast(u32, v), - .SupervisorCall => |v| @bitCast(u32, v), + .ExceptionGeneration => |v| @bitCast(u32, v), + .UnconditionalBranchRegister => |v| @bitCast(u32, v), + .UnconditionalBranchImmediate => |v| @bitCast(u32, v), }; } @@ -243,10 +261,48 @@ pub const Instruction = union(enum) { } } - fn supervisorCall(imm16: u16) Instruction { + fn exceptionGeneration( + opc: u3, + op2: u3, + ll: u2, + imm16: u16, + ) Instruction { return Instruction{ - .SupervisorCall = .{ + .ExceptionGeneration = .{ + .ll = ll, + .op2 = op2, .imm16 = imm16, + .opc = opc, + }, + }; + } + + fn unconditionalBranchRegister( + opc: u4, + op2: u5, + op3: u6, + rn: Register, + op4: u5, + ) Instruction { + return Instruction{ + .UnconditionalBranchRegister = .{ + .op4 = op4, + .rn = rn.id(), + .op3 = op3, + .op2 = op2, + .opc = opc, + }, + }; + } + + fn unconditionalBranchImmediate( + op: u1, + offset: i28, + ) Instruction { + return Instruction{ + .UnconditionalBranchImmediate = .{ + .imm26 = @bitCast(u26, @intCast(i26, imm26 >> 2)), + .op = op, }, }; } @@ -257,10 +313,51 @@ pub const Instruction = union(enum) { return moveWideWithZero(rd, imm16, shift); } - // Supervisor Call + // Exception generation pub fn svc(imm16: u16) Instruction { - return supervisorCall(imm16); + return exceptionGeneration(0b000, 0b000, 0b01, imm16); + } + + pub fn hvc(imm16: u16) Instruction { + return exceptionGeneration(0b000, 0b000, 0b10, imm16); + } + + pub fn smc(imm16: u16) Instruction { + return exceptionGeneration(0b000, 0b000, 0b11, imm16); + } + + pub fn brk(imm16: u16) Instruction { + return exceptionGeneration(0b001, 0b000, 0b00, imm16); + } + + pub fn hlt(imm16: u16) Instruction { + return exceptionGeneration(0b010, 0b000, 0b00, imm16); + } + + // Unconditional branch (register) + + pub fn br(rn: Register) Instruction { + assert(rn.size() == 64); + return unconditionalBranchRegister(0b0000, 0b11111, 0b000000, rn, 0b00000); + } + + pub fn blr(rn: Register) Instruction { + return unconditionalBranchRegister(0b0001, 0b11111, 0b000000, rn, 0b00000); + } + + pub fn ret(rn: ?Register) Instruction { + return unconditionalBranchRegister(0b0010, 0b11111, 0b000000, rn orelse .x30, 0b00000); + } + + // Unconditional branch (immediate) + + pub fn b(offset: i28) Instruction { + return unconditionalBranchImmediate(0, offset); + } + + pub fn bl(offset: i28) Instruction { + return unconditionalBranchImmediate(1, offset); } }; From aa9df72f714c0bd3c8653ea0c5431d43543b6f6f Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sun, 8 Nov 2020 20:21:36 +0100 Subject: [PATCH 08/16] stage2 AArch64: MoveWideImmediate instructions + test coverage --- src/codegen/aarch64.zig | 96 ++++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 35 deletions(-) diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 3b433eb267..6ceeaea47e 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -195,12 +195,12 @@ test "FloatingPointRegister.toX" { /// Represents an instruction in the AArch64 instruction set pub const Instruction = union(enum) { - MoveWideWithZero: packed struct { + MoveWideImmediate: packed struct { rd: u5, imm16: u16, hw: u2, fixed: u6 = 0b100101, - opc: u2 = 0b10, + opc: u2, sf: u1, }, ExceptionGeneration: packed struct { @@ -226,7 +226,7 @@ pub const Instruction = union(enum) { pub fn toU32(self: Instruction) u32 { return switch (self) { - .MoveWideWithZero => |v| @bitCast(u32, v), + .MoveWideImmediate => |v| @bitCast(u32, v), .ExceptionGeneration => |v| @bitCast(u32, v), .UnconditionalBranchRegister => |v| @bitCast(u32, v), .UnconditionalBranchImmediate => |v| @bitCast(u32, v), @@ -235,24 +235,33 @@ pub const Instruction = union(enum) { // Helper functions for assembly syntax functions - fn moveWideWithZero(rd: Register, imm16: u16, shift: u2) Instruction { + fn moveWideImmediate( + opc: u2, + rd: Register, + imm16: u16, + shift: u6, + ) Instruction { switch (rd.size()) { 32 => { + assert(shift % 16 == 0 and shift <= 16); return Instruction{ - .MoveWideWithZero = .{ + .MoveWideImmediate = .{ .rd = rd.id(), .imm16 = imm16, - .hw = 0b01 & shift, // TODO shift should be an enum + .hw = @intCast(u2, shift / 16), + .opc = opc, .sf = 0, }, }; }, 64 => { + assert(shift % 16 == 0 and shift <= 48); return Instruction{ - .MoveWideWithZero = .{ + .MoveWideImmediate = .{ .rd = rd.id(), .imm16 = imm16, - .hw = shift, + .hw = @intCast(u2, shift / 16), + .opc = opc, .sf = 1, }, }; @@ -284,6 +293,8 @@ pub const Instruction = union(enum) { rn: Register, op4: u5, ) Instruction { + assert(rn.size() == 64); + return Instruction{ .UnconditionalBranchRegister = .{ .op4 = op4, @@ -301,16 +312,24 @@ pub const Instruction = union(enum) { ) Instruction { return Instruction{ .UnconditionalBranchImmediate = .{ - .imm26 = @bitCast(u26, @intCast(i26, imm26 >> 2)), + .imm26 = @bitCast(u26, @intCast(i26, offset >> 2)), .op = op, }, }; } - // movz + // Move wide (immediate) - pub fn movz(rd: Register, imm16: u16, shift: u2) Instruction { - return moveWideWithZero(rd, imm16, shift); + pub fn movn(rd: Register, imm16: u16, shift: u6) Instruction { + return moveWideImmediate(0b00, rd, imm16, shift); + } + + pub fn movz(rd: Register, imm16: u16, shift: u6) Instruction { + return moveWideImmediate(0b10, rd, imm16, shift); + } + + pub fn movk(rd: Register, imm16: u16, shift: u6) Instruction { + return moveWideImmediate(0b11, rd, imm16, shift); } // Exception generation @@ -338,7 +357,6 @@ pub const Instruction = union(enum) { // Unconditional branch (register) pub fn br(rn: Register) Instruction { - assert(rn.size() == 64); return unconditionalBranchRegister(0b0000, 0b11111, 0b000000, rn, 0b00000); } @@ -368,6 +386,30 @@ test "serialize instructions" { }; const testcases = [_]Testcase{ + .{ // movz x1 #4 + .inst = Instruction.movz(.x1, 4, 0), + .expected = 0b1_10_100101_00_0000000000000100_00001, + }, + .{ // movz x1, #4, lsl 16 + .inst = Instruction.movz(.x1, 4, 16), + .expected = 0b1_10_100101_01_0000000000000100_00001, + }, + .{ // movz x1, #4, lsl 32 + .inst = Instruction.movz(.x1, 4, 32), + .expected = 0b1_10_100101_10_0000000000000100_00001, + }, + .{ // movz x1, #4, lsl 48 + .inst = Instruction.movz(.x1, 4, 48), + .expected = 0b1_10_100101_11_0000000000000100_00001, + }, + .{ // movz w1, #4 + .inst = Instruction.movz(.w1, 4, 0), + .expected = 0b0_10_100101_00_0000000000000100_00001, + }, + .{ // movz w1, #4, lsl 16 + .inst = Instruction.movz(.w1, 4, 16), + .expected = 0b0_10_100101_01_0000000000000100_00001, + }, .{ // svc #0 .inst = Instruction.svc(0), .expected = 0b1101_0100_000_0000000000000000_00001, @@ -376,29 +418,13 @@ test "serialize instructions" { .inst = Instruction.svc(0x80), .expected = 0b1101_0100_000_0000000010000000_00001, }, - .{ // movz x1 #4 - .inst = Instruction.movz(.x1, 4, 0), - .expected = 0b1_10_100101_00_0000000000000100_00001, + .{ // ret + .inst = Instruction.ret(null), + .expected = 0b1101_011_00_10_11111_0000_00_11110_00000, }, - .{ // movz x1, #4, lsl 16 - .inst = Instruction.movz(.x1, 4, 1), - .expected = 0b1_10_100101_01_0000000000000100_00001, - }, - .{ // movz x1, #4, lsl 32 - .inst = Instruction.movz(.x1, 4, 2), - .expected = 0b1_10_100101_10_0000000000000100_00001, - }, - .{ // movz x1, #4, lsl 48 - .inst = Instruction.movz(.x1, 4, 3), - .expected = 0b1_10_100101_11_0000000000000100_00001, - }, - .{ // movz w1, #4 - .inst = Instruction.movz(.w1, 4, 0), - .expected = 0b0_10_100101_00_0000000000000100_00001, - }, - .{ // movz w1, #4, lsl 16 - .inst = Instruction.movz(.w1, 4, 1), - .expected = 0b0_10_100101_01_0000000000000100_00001, + .{ // bl #0x10 + .inst = Instruction.bl(0x10), + .expected = 0b1_00101_00_0000_0000_0000_0000_0000_0100, }, }; From ca0016a225e7ad96d286923594d4077dbd215f0d Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Mon, 9 Nov 2020 01:02:57 +0100 Subject: [PATCH 09/16] stage2 ARM: start implementing genCall for ELF + genSetReg immediates --- src/codegen.zig | 70 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/codegen.zig b/src/codegen.zig index 4f666dd0e7..d494476aad 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1586,6 +1586,60 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); } }, + .aarch64 => { + for (info.args) |mc_arg, arg_i| { + const arg = inst.args[arg_i]; + const arg_mcv = try self.resolveInst(inst.args[arg_i]); + + switch (mc_arg) { + .none => continue, + .undef => unreachable, + .immediate => unreachable, + .unreach => unreachable, + .dead => unreachable, + .embedded_in_code => unreachable, + .memory => unreachable, + .compare_flags_signed => unreachable, + .compare_flags_unsigned => unreachable, + .register => |reg| { + try self.genSetReg(arg.src, reg, arg_mcv); + // TODO interact with the register allocator to mark the instruction as moved. + }, + .stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); + }, + .ptr_stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); + }, + .ptr_embedded_in_code => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); + }, + } + } + + if (inst.func.cast(ir.Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const func = func_val.func; + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; + break :blk @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes); + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| + coff_file.offset_table_virtual_address + func.owner_decl.link.coff.offset_table_index * ptr_bytes + else + unreachable; + + try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr }); + + writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32()); + } else { + return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); + } + } else { + return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); + } + }, else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), } } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { @@ -1702,6 +1756,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.code.resize(self.code.items.len + 4); try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); }, + .aarch64 => { + // TODO: relocations + writeInt(u32, try self.code.addManyAsArray(4), Instruction.ret(null).toU32()); + }, else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), } return .unreach; @@ -2510,8 +2568,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .immediate => |x| { if (x <= math.maxInt(u16)) { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32()); + } else if (x <= math.maxInt(u32)) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32()); + } else if (x <= math.maxInt(u32)) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 32), 32).toU32()); } else { - return self.fail(src, "TODO genSetReg with 32,48,64bit immediates", .{}); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 32), 32).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 48), 48).toU32()); } }, .register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), From f1960302d115cf42554481871d22393998b87338 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 10 Nov 2020 18:43:53 +0100 Subject: [PATCH 10/16] stage2 aarch64: add ldr instruction + smoke tests --- src/codegen/aarch64.zig | 228 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 6ceeaea47e..e18cc830b4 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -203,6 +203,21 @@ pub const Instruction = union(enum) { opc: u2, sf: u1, }, + LoadRegister: packed struct { + rt: u5, + rn: u5, + offset: u12, + opc: u2 = 0b01, + op1: u2, + fixed: u4 = 0b111_0, + size: u2, + }, + LoadLiteral: packed struct { + rt: u5, + imm19: u19, + fixed: u6 = 0b011_0_00, + opc: u2, + }, ExceptionGeneration: packed struct { ll: u2, op2: u3, @@ -227,12 +242,131 @@ pub const Instruction = union(enum) { pub fn toU32(self: Instruction) u32 { return switch (self) { .MoveWideImmediate => |v| @bitCast(u32, v), + .LoadRegister => |v| @bitCast(u32, v), + .LoadLiteral => |v| @bitCast(u32, v), .ExceptionGeneration => |v| @bitCast(u32, v), .UnconditionalBranchRegister => |v| @bitCast(u32, v), .UnconditionalBranchImmediate => |v| @bitCast(u32, v), }; } + /// Represents the offset operand of a load or store instruction. + /// Data can be loaded from memory with either an immediate offset + /// or an offset that is stored in some register. + pub const Offset = union(enum) { + Immediate: union(enum) { + PostIndex: i9, + PreIndex: i9, + Unsigned: u12, + }, + Register: struct { + rm: u5, + shift: union(enum) { + Uxtw: u2, + Lsl: u2, + Sxtw: u2, + Sxtx: u2, + }, + }, + + pub const none = Offset{ + .Immediate = .{ .Unsigned = 0 }, + }; + + pub fn toU12(self: Offset) u12 { + return switch (self) { + .Immediate => |imm_type| switch (imm_type) { + .PostIndex => |v| @intCast(u12, @bitCast(u9, v)) << 2 + 1, + .PreIndex => |v| @intCast(u12, @bitCast(u9, v)) << 2 + 3, + .Unsigned => |v| v, + }, + .Register => |r| switch (r.shift) { + .Uxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 16 + 2050, + .Lsl => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 24 + 2050, + .Sxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 48 + 2050, + .Sxtx => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 56 + 2050, + }, + }; + } + + pub fn imm(offset: u12) Offset { + return Offset{ + .Immediate = .{ .Unsigned = offset }, + }; + } + + pub fn imm_post_index(offset: i9) Offset { + return Offset{ + .Immediate = .{ .PostIndex = offset }, + }; + } + + pub fn imm_pre_index(offset: i9) Offset { + return Offset{ + .Immediate = .{ .PreIndex = offset }, + }; + } + + pub fn reg(rm: Register) Offset { + return Offset{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Lsl = 0, + }, + }, + }; + } + + pub fn reg_uxtw(rm: Register, shift: u2) Offset { + assert(rm.size() == 32 and (shift == 0 or shift == 2)); + return Offset{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Uxtw = shift, + }, + }, + }; + } + + pub fn reg_lsl(rm: Register, shift: u2) Offset { + assert(rm.size() == 64 and (shift == 0 or shift == 3)); + return Offset{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Lsl = shift, + }, + }, + }; + } + + pub fn reg_sxtw(rm: Register, shift: u2) Offset { + assert(rm.size() == 32 and (shift == 0 or shift == 2)); + return Offset{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Sxtw = shift, + }, + }, + }; + } + + pub fn reg_sxtx(rm: Register, shift: u2) Offset { + assert(rm.size() == 64 and (shift == 0 or shift == 3)); + return Offset{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Sxtx = shift, + }, + }, + }; + } + }; + // Helper functions for assembly syntax functions fn moveWideImmediate( @@ -270,6 +404,69 @@ pub const Instruction = union(enum) { } } + fn loadRegister(rt: Register, rn: Register, offset: Offset) Instruction { + const off = offset.toU12(); + const op1: u2 = blk: { + switch (offset) { + .Immediate => |imm| switch (imm) { + .Unsigned => break :blk 0b01, + else => {}, + }, + else => {}, + } + break :blk 0b00; + }; + switch (rt.size()) { + 32 => { + return Instruction{ + .LoadRegister = .{ + .rt = rt.id(), + .rn = rn.id(), + .offset = offset.toU12(), + .op1 = op1, + .size = 0b10, + }, + }; + }, + 64 => { + return Instruction{ + .LoadRegister = .{ + .rt = rt.id(), + .rn = rn.id(), + .offset = offset.toU12(), + .op1 = op1, + .size = 0b11, + }, + }; + }, + else => unreachable, // unexpected register size + } + } + + fn loadLiteral(rt: Register, imm19: u19) Instruction { + switch (rt.size()) { + 32 => { + return Instruction{ + .LoadLiteral = .{ + .rt = rt.id(), + .imm19 = imm19, + .opc = 0b00, + }, + }; + }, + 64 => { + return Instruction{ + .LoadLiteral = .{ + .rt = rt.id(), + .imm19 = imm19, + .opc = 0b01, + }, + }; + }, + else => unreachable, // unexpected register size + } + } + fn exceptionGeneration( opc: u3, op2: u3, @@ -332,6 +529,21 @@ pub const Instruction = union(enum) { return moveWideImmediate(0b11, rd, imm16, shift); } + // Load register + + pub const LdrArgs = struct { + rn: ?Register = null, + offset: Offset = Offset.none, + literal: ?u19 = null, + }; + pub fn ldr(rt: Register, args: LdrArgs) Instruction { + if (args.rn) |rn| { + return loadRegister(rt, rn, args.offset); + } else { + return loadLiteral(rt, args.literal.?); + } + } + // Exception generation pub fn svc(imm16: u16) Instruction { @@ -379,6 +591,10 @@ pub const Instruction = union(enum) { } }; +test "" { + testing.refAllDecls(@This()); +} + test "serialize instructions" { const Testcase = struct { inst: Instruction, @@ -426,6 +642,18 @@ test "serialize instructions" { .inst = Instruction.bl(0x10), .expected = 0b1_00101_00_0000_0000_0000_0000_0000_0100, }, + .{ // ldr x2, [x1] + .inst = Instruction.ldr(.x2, .{ .rn = .x1 }), + .expected = 0b11_111_0_01_01_000000000000_00001_00010, + }, + .{ // ldr x2, [x1], (x3) + .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.reg(.x3) }), + .expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010, + }, + .{ // ldr x2, label + .inst = Instruction.ldr(.x2, .{ .literal = 0x1 }), + .expected = 0b01_011_0_00_0000000000000000001_00010, + }, }; for (testcases) |case| { From f512676d0b4220a5f771110982657140c363a146 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 11 Nov 2020 00:27:58 +0100 Subject: [PATCH 11/16] stage2 aarch64: add str instruction --- src/codegen/aarch64.zig | 46 ++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index e18cc830b4..925d3e122d 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -203,11 +203,11 @@ pub const Instruction = union(enum) { opc: u2, sf: u1, }, - LoadRegister: packed struct { + LoadStoreRegister: packed struct { rt: u5, rn: u5, offset: u12, - opc: u2 = 0b01, + opc: u2, op1: u2, fixed: u4 = 0b111_0, size: u2, @@ -242,7 +242,7 @@ pub const Instruction = union(enum) { pub fn toU32(self: Instruction) u32 { return switch (self) { .MoveWideImmediate => |v| @bitCast(u32, v), - .LoadRegister => |v| @bitCast(u32, v), + .LoadStoreRegister => |v| @bitCast(u32, v), .LoadLiteral => |v| @bitCast(u32, v), .ExceptionGeneration => |v| @bitCast(u32, v), .UnconditionalBranchRegister => |v| @bitCast(u32, v), @@ -276,8 +276,8 @@ pub const Instruction = union(enum) { pub fn toU12(self: Offset) u12 { return switch (self) { .Immediate => |imm_type| switch (imm_type) { - .PostIndex => |v| @intCast(u12, @bitCast(u9, v)) << 2 + 1, - .PreIndex => |v| @intCast(u12, @bitCast(u9, v)) << 2 + 3, + .PostIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 1, + .PreIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 3, .Unsigned => |v| v, }, .Register => |r| switch (r.shift) { @@ -404,7 +404,7 @@ pub const Instruction = union(enum) { } } - fn loadRegister(rt: Register, rn: Register, offset: Offset) Instruction { + fn loadStoreRegister(rt: Register, rn: Register, offset: Offset, load: bool) Instruction { const off = offset.toU12(); const op1: u2 = blk: { switch (offset) { @@ -416,13 +416,15 @@ pub const Instruction = union(enum) { } break :blk 0b00; }; + const opc: u2 = if (load) 0b01 else 0b00; switch (rt.size()) { 32 => { return Instruction{ - .LoadRegister = .{ + .LoadStoreRegister = .{ .rt = rt.id(), .rn = rn.id(), .offset = offset.toU12(), + .opc = opc, .op1 = op1, .size = 0b10, }, @@ -430,10 +432,11 @@ pub const Instruction = union(enum) { }, 64 => { return Instruction{ - .LoadRegister = .{ + .LoadStoreRegister = .{ .rt = rt.id(), .rn = rn.id(), .offset = offset.toU12(), + .opc = opc, .op1 = op1, .size = 0b11, }, @@ -529,7 +532,7 @@ pub const Instruction = union(enum) { return moveWideImmediate(0b11, rd, imm16, shift); } - // Load register + // Load or store register pub const LdrArgs = struct { rn: ?Register = null, @@ -538,12 +541,19 @@ pub const Instruction = union(enum) { }; pub fn ldr(rt: Register, args: LdrArgs) Instruction { if (args.rn) |rn| { - return loadRegister(rt, rn, args.offset); + return loadStoreRegister(rt, rn, args.offset, true); } else { return loadLiteral(rt, args.literal.?); } } + pub const StrArgs = struct { + offset: Offset = Offset.none, + }; + pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction { + return loadStoreRegister(rt, rn, args.offset, false); + } + // Exception generation pub fn svc(imm16: u16) Instruction { @@ -646,6 +656,14 @@ test "serialize instructions" { .inst = Instruction.ldr(.x2, .{ .rn = .x1 }), .expected = 0b11_111_0_01_01_000000000000_00001_00010, }, + .{ // ldr x2, [x1, #1]! + .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_pre_index(1) }), + .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010, + }, + .{ // ldr x2, [x1], #-1 + .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_post_index(-1) }), + .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010, + }, .{ // ldr x2, [x1], (x3) .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.reg(.x3) }), .expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010, @@ -654,6 +672,14 @@ test "serialize instructions" { .inst = Instruction.ldr(.x2, .{ .literal = 0x1 }), .expected = 0b01_011_0_00_0000000000000000001_00010, }, + .{ // str x2, [x1] + .inst = Instruction.str(.x2, .x1, .{}), + .expected = 0b11_111_0_01_00_000000000000_00001_00010, + }, + .{ // str x2, [x1], (x3) + .inst = Instruction.str(.x2, .x1, .{ .offset = Instruction.Offset.reg(.x3) }), + .expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010, + }, }; for (testcases) |case| { From 993eb22a772323bfeef87cf783672c33b6a2cfa2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 11 Nov 2020 00:38:49 +0100 Subject: [PATCH 12/16] stage2 aarch64: add .memory prong in genSetReg --- src/codegen.zig | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index d494476aad..eea87d70f1 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2572,17 +2572,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32()); mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32()); } else if (x <= math.maxInt(u32)) { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 16), 16).toU32()); mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 32), 32).toU32()); } else { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 32), 32).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 16), 16).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 32), 32).toU32()); mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 48), 48).toU32()); } }, .register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), + .memory => |addr| { + // 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(src, reg, .{ .immediate = addr }); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32()); + }, else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), }, .riscv64 => switch (mcv) { From a6bc19ea2a2fb99ee7f09dd451b889b6a5d7f388 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 11 Nov 2020 12:09:39 +0100 Subject: [PATCH 13/16] stage2 aarch64: add genCall for aarch64 MachO --- src/codegen.zig | 97 +++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index eea87d70f1..53d065d26c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1643,61 +1643,64 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), } } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - switch (arch) { - .x86_64 => { - for (info.args) |mc_arg, arg_i| { - const arg = inst.args[arg_i]; - const arg_mcv = try self.resolveInst(inst.args[arg_i]); - // Here we do not use setRegOrMem even though the logic is similar, because - // the function call will move the stack pointer, so the offsets are different. - switch (mc_arg) { - .none => continue, - .register => |reg| { - try self.genSetReg(arg.src, reg, arg_mcv); - // TODO interact with the register allocator to mark the instruction as moved. - }, - .stack_offset => { - // Here we need to emit instructions like this: - // mov qword ptr [rsp + stack_offset], x - return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); - }, - .ptr_stack_offset => { - return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); - }, - .ptr_embedded_in_code => { - return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); - }, - .undef => unreachable, - .immediate => unreachable, - .unreach => unreachable, - .dead => unreachable, - .embedded_in_code => unreachable, - .memory => unreachable, - .compare_flags_signed => unreachable, - .compare_flags_unsigned => unreachable, - } - } + for (info.args) |mc_arg, arg_i| { + const arg = inst.args[arg_i]; + const arg_mcv = try self.resolveInst(inst.args[arg_i]); + // Here we do not use setRegOrMem even though the logic is similar, because + // the function call will move the stack pointer, so the offsets are different. + switch (mc_arg) { + .none => continue, + .register => |reg| { + try self.genSetReg(arg.src, reg, arg_mcv); + // TODO interact with the register allocator to mark the instruction as moved. + }, + .stack_offset => { + // Here we need to emit instructions like this: + // mov qword ptr [rsp + stack_offset], x + return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); + }, + .ptr_stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); + }, + .ptr_embedded_in_code => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); + }, + .undef => unreachable, + .immediate => unreachable, + .unreach => unreachable, + .dead => unreachable, + .embedded_in_code => unreachable, + .memory => unreachable, + .compare_flags_signed => unreachable, + .compare_flags_unsigned => unreachable, + } + } - if (inst.func.cast(ir.Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; - const got = &macho_file.sections.items[macho_file.got_section_index.?]; - const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); + if (inst.func.cast(ir.Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const func = func_val.func; + const got = &macho_file.sections.items[macho_file.got_section_index.?]; + 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 }); - } else { - return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); - } - } else { - return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); + }, + .aarch64 => { + try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr }); + writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32()); + }, + else => unreachable, // unsupported architecture on MachO } - }, - .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}), - else => unreachable, + } else { + return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); + } + } else { + return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); } } else { unreachable; From 5b92d0ea45af155a7d3d70aff56301e4c0aee338 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 11 Nov 2020 15:36:47 +0100 Subject: [PATCH 14/16] stage2 aarch64: add macOS incremental test --- test/stage2/aarch64.zig | 115 ++++++++++++++++++++++++++++++++++++++++ test/stage2/test.zig | 1 + 2 files changed, 116 insertions(+) create mode 100644 test/stage2/aarch64.zig diff --git a/test/stage2/aarch64.zig b/test/stage2/aarch64.zig new file mode 100644 index 0000000000..48cf7700c1 --- /dev/null +++ b/test/stage2/aarch64.zig @@ -0,0 +1,115 @@ +const std = @import("std"); +const TestContext = @import("../../src/test.zig").TestContext; + +const macos_aarch64 = std.zig.CrossTarget{ + .cpu_arch = .aarch64, + .os_tag = .macos, +}; + +pub fn addCases(ctx: *TestContext) !void { + // TODO enable when we add codesigning to the self-hosted linker + // related to #6971 + if (false) { + var case = ctx.exe("hello world with updates", macos_aarch64); + + // Regular old hello world + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("svc #0x80" + \\ : + \\ : [number] "{x16}" (4), + \\ [arg1] "{x0}" (1), + \\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{x2}" (14) + \\ : "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0x80" + \\ : + \\ : [number] "{x16}" (1), + \\ [arg1] "{x0}" (0) + \\ : "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + // Now change the message only + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("svc #0x80" + \\ : + \\ : [number] "{x16}" (4), + \\ [arg1] "{x0}" (1), + \\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), + \\ [arg3] "{x2}" (104) + \\ : "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0x80" + \\ : + \\ : [number] "{x16}" (1), + \\ [arg1] "{x0}" (0) + \\ : "memory" + \\ ); + \\ unreachable; + \\} + , + "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n", + ); + // Now we print it twice. + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("svc #0x80" + \\ : + \\ : [number] "{x16}" (4), + \\ [arg1] "{x0}" (1), + \\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), + \\ [arg3] "{x2}" (104) + \\ : "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0x80" + \\ : + \\ : [number] "{x16}" (1), + \\ [arg1] "{x0}" (0) + \\ : "memory" + \\ ); + \\ unreachable; + \\} + , + \\What is up? This is a longer message that will force the data to be relocated in virtual address space. + \\What is up? This is a longer message that will force the data to be relocated in virtual address space. + \\ + ); + } +} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 51bde87ab0..032838eaf9 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -31,6 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { try @import("cbe.zig").addCases(ctx); try @import("spu-ii.zig").addCases(ctx); try @import("arm.zig").addCases(ctx); + try @import("aarch64.zig").addCases(ctx); { var case = ctx.exe("hello world with updates", linux_x64); From b17859b56863d18ecadb3cf733799a4381e666ac Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Wed, 11 Nov 2020 23:29:18 +0100 Subject: [PATCH 15/16] stage2 AArch64: add Linux Hello World test --- test/stage2/aarch64.zig | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/stage2/aarch64.zig b/test/stage2/aarch64.zig index 48cf7700c1..323d63b9fe 100644 --- a/test/stage2/aarch64.zig +++ b/test/stage2/aarch64.zig @@ -6,6 +6,11 @@ const macos_aarch64 = std.zig.CrossTarget{ .os_tag = .macos, }; +const linux_aarch64 = std.zig.CrossTarget{ + .cpu_arch = .aarch64, + .os_tag = .linux, +}; + pub fn addCases(ctx: *TestContext) !void { // TODO enable when we add codesigning to the self-hosted linker // related to #6971 @@ -112,4 +117,44 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); } + + { + var case = ctx.exe("hello world", linux_aarch64); + // Regular old hello world + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ exit(); + \\} + \\ + \\fn doNothing() void {} + \\ + \\fn answer() u64 { + \\ return 0x1234abcd1234abcd; + \\} + \\ + \\fn print() void { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{x8}" (64), + \\ [arg1] "{x0}" (1), + \\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{x2}" ("Hello, World!\n".len) + \\ : "memory", "cc" + \\ ); + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{x8}" (93), + \\ [arg1] "{x0}" (0) + \\ : "memory", "cc" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + } } From c6d46a9b82f04aea439320f0ae8e8a4ed187040e Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Thu, 12 Nov 2020 16:22:25 +0100 Subject: [PATCH 16/16] stage2 ARM & AArch64: ensure correct function alignment --- src/type.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/type.zig b/src/type.zig index a706ad34bc..31f84cdec4 100644 --- a/src/type.zig +++ b/src/type.zig @@ -818,7 +818,8 @@ pub const Type = extern union { .fn_ccc_void_no_args, // represents machine code; not a pointer .function, // represents machine code; not a pointer => return switch (target.cpu.arch) { - .arm => 4, + .arm, .armeb => 4, + .aarch64, .aarch64_32, .aarch64_be => 4, .riscv64 => 2, else => 1, },