Files
zig/src/codegen/aarch64.zig
2020-12-09 17:21:17 +01:00

932 lines
29 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 const sp = Register.xzr;
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());
testing.expectEqual(@as(u5, 31), Register.sp.id());
testing.expectEqual(@as(u5, 31), Register.sp.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) {
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,
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,
},
LoadStorePairOfRegisters: packed struct {
rt1: u5,
rn: u5,
rt2: u5,
imm7: u7,
load: u1,
encoding: u2,
fixed: u5 = 0b101_0_0,
opc: 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) {
.OrShiftedRegister => |v| @bitCast(u32, v),
.MoveWideImmediate => |v| @bitCast(u32, v),
.PCRelativeAddress => |v| @bitCast(u32, v),
.LoadStoreRegister => |v| @bitCast(u32, v),
.LoadStorePairOfRegisters => |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,
},
},
};
}
};
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,
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 {
assert(rd.size() == 64);
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 loadStorePairOfRegisters(
rt1: Register,
rt2: Register,
rn: Register,
offset: i9,
encoding: u2,
load: bool,
) Instruction {
switch (rt1.size()) {
32 => {
assert(-256 <= offset and offset <= 252);
const imm7 = @truncate(u7, @bitCast(u9, offset >> 2));
return Instruction{
.LoadStorePairOfRegisters = .{
.rt1 = rt1.id(),
.rn = rn.id(),
.rt2 = rt2.id(),
.imm7 = imm7,
.load = @boolToInt(load),
.encoding = encoding,
.opc = 0b00,
},
};
},
64 => {
assert(-512 <= offset and offset <= 504);
const imm7 = @truncate(u7, @bitCast(u9, offset >> 3));
return Instruction{
.LoadStorePairOfRegisters = .{
.rt1 = rt1.id(),
.rn = rn.id(),
.rt2 = rt2.id(),
.imm7 = imm7,
.load = @boolToInt(load),
.encoding = encoding,
.opc = 0b10,
},
};
},
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,
},
};
}
// 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 {
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);
}
// Load or store pair of registers
pub const LoadStorePairEncoding = enum(u2) {
PostIndex = 0b01,
SignedOffset = 0b10,
PreIndex = 0b11,
};
pub fn ldp(rt1: Register, rt2: Register, rn: Register, offset: i9, encoding: LoadStorePairEncoding) Instruction {
return loadStorePairOfRegisters(rt1, rt2, rn, offset, @enumToInt(encoding), true);
}
pub fn ldnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, true);
}
pub fn stp(rt1: Register, rt2: Register, rn: Register, offset: i9, encoding: LoadStorePairEncoding) Instruction {
return loadStorePairOfRegisters(rt1, rt2, rn, offset, @enumToInt(encoding), false);
}
pub fn stnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, 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{
.{ // 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,
},
.{ // 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,
},
.{ // stp x1, x2, [sp, #8]
.inst = Instruction.stp(.x1, .x2, Register.sp, 8, .SignedOffset),
.expected = 0b10_101_0_010_0_0000001_00010_11111_00001,
},
.{ // stp x1, x2, [sp, #-16]
.inst = Instruction.stp(.x1, .x2, Register.sp, -16, .SignedOffset),
.expected = 0b10_101_0_010_0_1111110_00010_11111_00001,
},
.{ // ldp x1, x2, [sp, #8]
.inst = Instruction.ldp(.x1, .x2, Register.sp, 8, .SignedOffset),
.expected = 0b10_101_0_010_1_0000001_00010_11111_00001,
},
.{ // ldp x1, x2, [sp, #16]
.inst = Instruction.ldp(.x1, .x2, Register.sp, 16, .SignedOffset),
.expected = 0b10_101_0_010_1_0000010_00010_11111_00001,
},
};
for (testcases) |case| {
const actual = case.inst.toU32();
testing.expectEqual(case.expected, actual);
}
}