Files
zig/src/codegen/aarch64.zig
2020-11-18 08:46:52 +01:00

746 lines
23 KiB
Zig

const std = @import("std");
const DW = std.dwarf;
const assert = std.debug.assert;
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,
};
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());
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) {
MoveWideImmediate: packed struct {
rd: u5,
imm16: u16,
hw: u2,
fixed: u6 = 0b100101,
opc: u2,
sf: u1,
},
PCRelativeAddress: packed struct {
rd: u5,
immhi: u19,
fixed: u5 = 0b10000,
immlo: u2,
op: u1,
},
LoadStoreRegister: packed struct {
rt: u5,
rn: u5,
offset: u12,
opc: u2,
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,
imm16: u16,
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,
},
NoOperation: packed struct {
fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111,
},
pub fn toU32(self: Instruction) u32 {
return switch (self) {
.MoveWideImmediate => |v| @bitCast(u32, v),
.PCRelativeAddress => |v| @bitCast(u32, v),
.LoadStoreRegister => |v| @bitCast(u32, v),
.LoadLiteral => |v| @bitCast(u32, v),
.ExceptionGeneration => |v| @bitCast(u32, v),
.UnconditionalBranchRegister => |v| @bitCast(u32, v),
.UnconditionalBranchImmediate => |v| @bitCast(u32, v),
.NoOperation => |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(
opc: u2,
rd: Register,
imm16: u16,
shift: u6,
) Instruction {
switch (rd.size()) {
32 => {
assert(shift % 16 == 0 and shift <= 16);
return Instruction{
.MoveWideImmediate = .{
.rd = rd.id(),
.imm16 = imm16,
.hw = @intCast(u2, shift / 16),
.opc = opc,
.sf = 0,
},
};
},
64 => {
assert(shift % 16 == 0 and shift <= 48);
return Instruction{
.MoveWideImmediate = .{
.rd = rd.id(),
.imm16 = imm16,
.hw = @intCast(u2, shift / 16),
.opc = opc,
.sf = 1,
},
};
},
else => unreachable, // unexpected register size
}
}
fn pcRelativeAddress(rd: Register, imm21: i21, op: u1) Instruction {
const imm21_u = @bitCast(u21, imm21);
return Instruction{
.PCRelativeAddress = .{
.rd = rd.id(),
.immlo = @truncate(u2, imm21_u),
.immhi = @truncate(u19, imm21_u >> 2),
.op = op,
},
};
}
fn loadStoreRegister(rt: Register, rn: Register, offset: Offset, load: bool) Instruction {
const off = offset.toU12();
const op1: u2 = blk: {
switch (offset) {
.Immediate => |imm| switch (imm) {
.Unsigned => break :blk 0b01,
else => {},
},
else => {},
}
break :blk 0b00;
};
const opc: u2 = if (load) 0b01 else 0b00;
switch (rt.size()) {
32 => {
return Instruction{
.LoadStoreRegister = .{
.rt = rt.id(),
.rn = rn.id(),
.offset = offset.toU12(),
.opc = opc,
.op1 = op1,
.size = 0b10,
},
};
},
64 => {
return Instruction{
.LoadStoreRegister = .{
.rt = rt.id(),
.rn = rn.id(),
.offset = offset.toU12(),
.opc = opc,
.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,
ll: u2,
imm16: u16,
) Instruction {
return Instruction{
.ExceptionGeneration = .{
.ll = ll,
.op2 = op2,
.imm16 = imm16,
.opc = opc,
},
};
}
fn unconditionalBranchRegister(
opc: u4,
op2: u5,
op3: u6,
rn: Register,
op4: u5,
) Instruction {
assert(rn.size() == 64);
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, offset >> 2)),
.op = op,
},
};
}
// Move wide (immediate)
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);
}
// PC relative address
pub fn adr(rd: Register, imm21: i21) Instruction {
return pcRelativeAddress(rd, imm21, 0b0);
}
pub fn adrp(rd: Register, imm21: i21) Instruction {
return pcRelativeAddress(rd, imm21, 0b1);
}
// Load or store 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 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 {
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 {
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);
}
// Nop
pub fn nop() Instruction {
return Instruction{ .NoOperation = {} };
}
};
test "" {
testing.refAllDecls(@This());
}
test "serialize instructions" {
const Testcase = struct {
inst: Instruction,
expected: u32,
};
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,
},
.{ // svc #0x80 ; typical on Darwin
.inst = Instruction.svc(0x80),
.expected = 0b1101_0100_000_0000000010000000_00001,
},
.{ // ret
.inst = Instruction.ret(null),
.expected = 0b1101_011_00_10_11111_0000_00_11110_00000,
},
.{ // bl #0x10
.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, #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,
},
.{ // ldr x2, label
.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,
},
.{ // adr x2, #0x8
.inst = Instruction.adr(.x2, 0x8),
.expected = 0b0_00_10000_0000000000000000010_00010,
},
.{ // adr x2, -#0x8
.inst = Instruction.adr(.x2, -0x8),
.expected = 0b0_00_10000_1111111111111111110_00010,
},
.{ // adrp x2, #0x8
.inst = Instruction.adrp(.x2, 0x8),
.expected = 0b1_00_10000_0000000000000000010_00010,
},
.{ // adrp x2, -#0x8
.inst = Instruction.adrp(.x2, -0x8),
.expected = 0b1_00_10000_1111111111111111110_00010,
},
};
for (testcases) |case| {
const actual = case.inst.toU32();
testing.expectEqual(case.expected, actual);
}
}