Files
zig/src/codegen/aarch64.zig
2020-11-11 14:34:53 +01:00

247 lines
7.7 KiB
Zig

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,
};
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) {
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
pub 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,
},
.{ // svc #0x80 ; typical on Darwin
.inst = Instruction.svc(0x80),
.expected = 0b1101_0100_000_0000000010000000_00001,
},
};
for (testcases) |case| {
const actual = case.inst.toU32();
testing.expectEqual(case.expected, actual);
}
}