From 664941bf14cad3e62b453f83153ca4b65606707b Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Fri, 24 Sep 2021 13:39:20 -0400 Subject: Spelling corrections (#9833) Signed-off-by: Josh Soref Co-authored-by: Josh Soref --- src/codegen/arm.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/codegen/arm.zig') diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig index ec9152f96b..279ce58005 100644 --- a/src/codegen/arm.zig +++ b/src/codegen/arm.zig @@ -2,7 +2,7 @@ const std = @import("std"); const DW = std.dwarf; const testing = std.testing; -/// The condition field specifies the flags neccessary for an +/// The condition field specifies the flags necessary for an /// Instruction to be executed pub const Condition = enum(u4) { /// equal -- cgit v1.2.3 From 8f58e2d77951cdb046e365394c5f02c9b3a93a4f Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Thu, 23 Sep 2021 22:47:12 +0200 Subject: stage2 codegen: move bit definitions to src/arch --- CMakeLists.txt | 8 +- src/arch/aarch64/bits.zig | 1230 +++++++++++++++++++++++++++++++++++++++ src/arch/arm/bits.zig | 1408 +++++++++++++++++++++++++++++++++++++++++++++ src/arch/riscv64/bits.zig | 470 +++++++++++++++ src/arch/x86/bits.zig | 123 ++++ src/arch/x86_64/bits.zig | 716 +++++++++++++++++++++++ src/codegen.zig | 48 +- src/codegen/aarch64.zig | 1230 --------------------------------------- src/codegen/arm.zig | 1408 --------------------------------------------- src/codegen/riscv64.zig | 470 --------------- src/codegen/x86.zig | 123 ---- src/codegen/x86_64.zig | 716 ----------------------- src/link/MachO.zig | 4 +- src/link/MachO/Atom.zig | 2 +- 14 files changed, 3978 insertions(+), 3978 deletions(-) create mode 100644 src/arch/aarch64/bits.zig create mode 100644 src/arch/arm/bits.zig create mode 100644 src/arch/riscv64/bits.zig create mode 100644 src/arch/x86/bits.zig create mode 100644 src/arch/x86_64/bits.zig delete mode 100644 src/codegen/aarch64.zig delete mode 100644 src/codegen/arm.zig delete mode 100644 src/codegen/riscv64.zig delete mode 100644 src/codegen/x86.zig delete mode 100644 src/codegen/x86_64.zig (limited to 'src/codegen/arm.zig') diff --git a/CMakeLists.txt b/CMakeLists.txt index 42c0b7e0da..2839a3cffc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -551,18 +551,18 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/TypedValue.zig" "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig" "${CMAKE_SOURCE_DIR}/src/Zir.zig" + "${CMAKE_SOURCE_DIR}/src/arch/aarch64/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/arm/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/riscv64/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig" "${CMAKE_SOURCE_DIR}/src/clang.zig" "${CMAKE_SOURCE_DIR}/src/clang_options.zig" "${CMAKE_SOURCE_DIR}/src/clang_options_data.zig" "${CMAKE_SOURCE_DIR}/src/codegen.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/aarch64.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/arm.zig" "${CMAKE_SOURCE_DIR}/src/codegen/c.zig" "${CMAKE_SOURCE_DIR}/src/codegen/llvm.zig" "${CMAKE_SOURCE_DIR}/src/codegen/llvm/bindings.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/riscv64.zig" "${CMAKE_SOURCE_DIR}/src/codegen/wasm.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/x86_64.zig" "${CMAKE_SOURCE_DIR}/src/glibc.zig" "${CMAKE_SOURCE_DIR}/src/introspect.zig" "${CMAKE_SOURCE_DIR}/src/libc_installation.zig" diff --git a/src/arch/aarch64/bits.zig b/src/arch/aarch64/bits.zig new file mode 100644 index 0000000000..dfda04da85 --- /dev/null +++ b/src/arch/aarch64/bits.zig @@ -0,0 +1,1230 @@ +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, .x2, .x3, .x4, .x5, .x6, .x7 }; + +test "Register.id" { + try testing.expectEqual(@as(u5, 0), Register.x0.id()); + try testing.expectEqual(@as(u5, 0), Register.w0.id()); + + try testing.expectEqual(@as(u5, 31), Register.xzr.id()); + try testing.expectEqual(@as(u5, 31), Register.wzr.id()); + + try testing.expectEqual(@as(u5, 31), Register.sp.id()); + try testing.expectEqual(@as(u5, 31), Register.sp.id()); +} + +test "Register.size" { + try testing.expectEqual(@as(u7, 64), Register.x19.size()); + try testing.expectEqual(@as(u7, 32), Register.w3.size()); +} + +test "Register.to64/to32" { + try testing.expectEqual(Register.x0, Register.w0.to64()); + try testing.expectEqual(Register.x0, Register.x0.to64()); + + try testing.expectEqual(Register.w3, Register.w3.to32()); + try 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" { + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.b0.id()); + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.h0.id()); + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.s0.id()); + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.d0.id()); + try testing.expectEqual(@as(u5, 0), FloatingPointRegister.q0.id()); + + try testing.expectEqual(@as(u5, 2), FloatingPointRegister.q2.id()); + try testing.expectEqual(@as(u5, 31), FloatingPointRegister.d31.id()); +} + +test "FloatingPointRegister.size" { + try testing.expectEqual(@as(u8, 128), FloatingPointRegister.q1.size()); + try testing.expectEqual(@as(u8, 64), FloatingPointRegister.d2.size()); + try testing.expectEqual(@as(u8, 32), FloatingPointRegister.s3.size()); + try testing.expectEqual(@as(u8, 16), FloatingPointRegister.h4.size()); + try testing.expectEqual(@as(u8, 8), FloatingPointRegister.b5.size()); +} + +test "FloatingPointRegister.toX" { + try testing.expectEqual(FloatingPointRegister.q1, FloatingPointRegister.q1.to128()); + try testing.expectEqual(FloatingPointRegister.q2, FloatingPointRegister.b2.to128()); + try testing.expectEqual(FloatingPointRegister.q3, FloatingPointRegister.h3.to128()); + + try testing.expectEqual(FloatingPointRegister.d0, FloatingPointRegister.q0.to64()); + try testing.expectEqual(FloatingPointRegister.s1, FloatingPointRegister.d1.to32()); + try testing.expectEqual(FloatingPointRegister.h2, FloatingPointRegister.s2.to16()); + try testing.expectEqual(FloatingPointRegister.b3, FloatingPointRegister.h3.to8()); +} + +/// Represents an instruction in the AArch64 instruction set +pub const Instruction = union(enum) { + move_wide_immediate: packed struct { + rd: u5, + imm16: u16, + hw: u2, + fixed: u6 = 0b100101, + opc: u2, + sf: u1, + }, + pc_relative_address: packed struct { + rd: u5, + immhi: u19, + fixed: u5 = 0b10000, + immlo: u2, + op: u1, + }, + load_store_register: packed struct { + rt: u5, + rn: u5, + offset: u12, + opc: u2, + op1: u2, + v: u1, + fixed: u3 = 0b111, + size: u2, + }, + load_store_register_pair: packed struct { + rt1: u5, + rn: u5, + rt2: u5, + imm7: u7, + load: u1, + encoding: u2, + fixed: u5 = 0b101_0_0, + opc: u2, + }, + load_literal: packed struct { + rt: u5, + imm19: u19, + fixed: u6 = 0b011_0_00, + opc: u2, + }, + exception_generation: packed struct { + ll: u2, + op2: u3, + imm16: u16, + opc: u3, + fixed: u8 = 0b1101_0100, + }, + unconditional_branch_register: packed struct { + op4: u5, + rn: u5, + op3: u6, + op2: u5, + opc: u4, + fixed: u7 = 0b1101_011, + }, + unconditional_branch_immediate: packed struct { + imm26: u26, + fixed: u5 = 0b00101, + op: u1, + }, + no_operation: packed struct { + fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111, + }, + logical_shifted_register: packed struct { + rd: u5, + rn: u5, + imm6: u6, + rm: u5, + n: u1, + shift: u2, + fixed: u5 = 0b01010, + opc: u2, + sf: u1, + }, + add_subtract_immediate: packed struct { + rd: u5, + rn: u5, + imm12: u12, + sh: u1, + fixed: u6 = 0b100010, + s: u1, + op: u1, + sf: u1, + }, + conditional_branch: struct { + cond: u4, + o0: u1, + imm19: u19, + o1: u1, + fixed: u7 = 0b0101010, + }, + compare_and_branch: struct { + rt: u5, + imm19: u19, + op: u1, + fixed: u6 = 0b011010, + sf: u1, + }, + + pub const Shift = struct { + shift: Type = .lsl, + amount: u6 = 0, + + pub const Type = enum(u2) { + lsl, + lsr, + asr, + ror, + }; + + pub const none = Shift{ + .shift = .lsl, + .amount = 0, + }; + }; + + pub const Condition = enum(u4) { + /// Integer: Equal + /// Floating point: Equal + eq, + /// Integer: Not equal + /// Floating point: Not equal or unordered + ne, + /// Integer: Carry set + /// Floating point: Greater than, equal, or unordered + cs, + /// Integer: Carry clear + /// Floating point: Less than + cc, + /// Integer: Minus, negative + /// Floating point: Less than + mi, + /// Integer: Plus, positive or zero + /// Floating point: Greater than, equal, or unordered + pl, + /// Integer: Overflow + /// Floating point: Unordered + vs, + /// Integer: No overflow + /// Floating point: Ordered + vc, + /// Integer: Unsigned higher + /// Floating point: Greater than, or unordered + hi, + /// Integer: Unsigned lower or same + /// Floating point: Less than or equal + ls, + /// Integer: Signed greater than or equal + /// Floating point: Greater than or equal + ge, + /// Integer: Signed less than + /// Floating point: Less than, or unordered + lt, + /// Integer: Signed greater than + /// Floating point: Greater than + gt, + /// Integer: Signed less than or equal + /// Floating point: Less than, equal, or unordered + le, + /// Integer: Always + /// Floating point: Always + al, + /// Integer: Always + /// Floating point: Always + nv, + }; + + pub fn toU32(self: Instruction) u32 { + return switch (self) { + .move_wide_immediate => |v| @bitCast(u32, v), + .pc_relative_address => |v| @bitCast(u32, v), + .load_store_register => |v| @bitCast(u32, v), + .load_store_register_pair => |v| @bitCast(u32, v), + .load_literal => |v| @bitCast(u32, v), + .exception_generation => |v| @bitCast(u32, v), + .unconditional_branch_register => |v| @bitCast(u32, v), + .unconditional_branch_immediate => |v| @bitCast(u32, v), + .no_operation => |v| @bitCast(u32, v), + .logical_shifted_register => |v| @bitCast(u32, v), + .add_subtract_immediate => |v| @bitCast(u32, v), + // TODO once packed structs work, this can be refactored + .conditional_branch => |v| @as(u32, v.cond) | (@as(u32, v.o0) << 4) | (@as(u32, v.imm19) << 5) | (@as(u32, v.o1) << 24) | (@as(u32, v.fixed) << 25), + .compare_and_branch => |v| @as(u32, v.rt) | (@as(u32, v.imm19) << 5) | (@as(u32, v.op) << 24) | (@as(u32, v.fixed) << 25) | (@as(u32, v.sf) << 31), + }; + } + + fn moveWideImmediate( + opc: u2, + rd: Register, + imm16: u16, + shift: u6, + ) Instruction { + switch (rd.size()) { + 32 => { + assert(shift % 16 == 0 and shift <= 16); + return Instruction{ + .move_wide_immediate = .{ + .rd = rd.id(), + .imm16 = imm16, + .hw = @intCast(u2, shift / 16), + .opc = opc, + .sf = 0, + }, + }; + }, + 64 => { + assert(shift % 16 == 0 and shift <= 48); + return Instruction{ + .move_wide_immediate = .{ + .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{ + .pc_relative_address = .{ + .rd = rd.id(), + .immlo = @truncate(u2, imm21_u), + .immhi = @truncate(u19, imm21_u >> 2), + .op = op, + }, + }; + } + + /// 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 LoadStoreOffset = 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 = LoadStoreOffset{ + .Immediate = .{ .Unsigned = 0 }, + }; + + pub fn toU12(self: LoadStoreOffset) 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) LoadStoreOffset { + return .{ + .Immediate = .{ .Unsigned = offset }, + }; + } + + pub fn imm_post_index(offset: i9) LoadStoreOffset { + return .{ + .Immediate = .{ .PostIndex = offset }, + }; + } + + pub fn imm_pre_index(offset: i9) LoadStoreOffset { + return .{ + .Immediate = .{ .PreIndex = offset }, + }; + } + + pub fn reg(rm: Register) LoadStoreOffset { + return .{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Lsl = 0, + }, + }, + }; + } + + pub fn reg_uxtw(rm: Register, shift: u2) LoadStoreOffset { + assert(rm.size() == 32 and (shift == 0 or shift == 2)); + return .{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Uxtw = shift, + }, + }, + }; + } + + pub fn reg_lsl(rm: Register, shift: u2) LoadStoreOffset { + assert(rm.size() == 64 and (shift == 0 or shift == 3)); + return .{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Lsl = shift, + }, + }, + }; + } + + pub fn reg_sxtw(rm: Register, shift: u2) LoadStoreOffset { + assert(rm.size() == 32 and (shift == 0 or shift == 2)); + return .{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Sxtw = shift, + }, + }, + }; + } + + pub fn reg_sxtx(rm: Register, shift: u2) LoadStoreOffset { + assert(rm.size() == 64 and (shift == 0 or shift == 3)); + return .{ + .Register = .{ + .rm = rm.id(), + .shift = .{ + .Sxtx = shift, + }, + }, + }; + } + }; + + /// Which kind of load/store to perform + const LoadStoreVariant = enum { + /// 32-bit or 64-bit + str, + /// 16-bit, zero-extended + strh, + /// 8-bit, zero-extended + strb, + /// 32-bit or 64-bit + ldr, + /// 16-bit, zero-extended + ldrh, + /// 8-bit, zero-extended + ldrb, + }; + + fn loadStoreRegister( + rt: Register, + rn: Register, + offset: LoadStoreOffset, + variant: LoadStoreVariant, + ) 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 = switch (variant) { + .ldr, .ldrh, .ldrb => 0b01, + .str, .strh, .strb => 0b00, + }; + return Instruction{ + .load_store_register = .{ + .rt = rt.id(), + .rn = rn.id(), + .offset = off, + .opc = opc, + .op1 = op1, + .v = 0, + .size = blk: { + switch (variant) { + .ldr, .str => switch (rt.size()) { + 32 => break :blk 0b10, + 64 => break :blk 0b11, + else => unreachable, // unexpected register size + }, + .ldrh, .strh => break :blk 0b01, + .ldrb, .strb => break :blk 0b00, + } + }, + }, + }; + } + + fn loadStoreRegisterPair( + 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{ + .load_store_register_pair = .{ + .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{ + .load_store_register_pair = .{ + .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{ + .load_literal = .{ + .rt = rt.id(), + .imm19 = imm19, + .opc = 0b00, + }, + }; + }, + 64 => { + return Instruction{ + .load_literal = .{ + .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{ + .exception_generation = .{ + .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{ + .unconditional_branch_register = .{ + .op4 = op4, + .rn = rn.id(), + .op3 = op3, + .op2 = op2, + .opc = opc, + }, + }; + } + + fn unconditionalBranchImmediate( + op: u1, + offset: i28, + ) Instruction { + return Instruction{ + .unconditional_branch_immediate = .{ + .imm26 = @bitCast(u26, @intCast(i26, offset >> 2)), + .op = op, + }, + }; + } + + fn logicalShiftedRegister( + opc: u2, + n: u1, + shift: Shift, + rd: Register, + rn: Register, + rm: Register, + ) Instruction { + switch (rd.size()) { + 32 => { + assert(shift.amount < 32); + return Instruction{ + .logical_shifted_register = .{ + .rd = rd.id(), + .rn = rn.id(), + .imm6 = shift.amount, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .opc = opc, + .sf = 0b0, + }, + }; + }, + 64 => { + return Instruction{ + .logical_shifted_register = .{ + .rd = rd.id(), + .rn = rn.id(), + .imm6 = shift.amount, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .opc = opc, + .sf = 0b1, + }, + }; + }, + else => unreachable, // unexpected register size + } + } + + fn addSubtractImmediate( + op: u1, + s: u1, + rd: Register, + rn: Register, + imm12: u12, + shift: bool, + ) Instruction { + return Instruction{ + .add_subtract_immediate = .{ + .rd = rd.id(), + .rn = rn.id(), + .imm12 = imm12, + .sh = @boolToInt(shift), + .s = s, + .op = op, + .sf = switch (rd.size()) { + 32 => 0b0, + 64 => 0b1, + else => unreachable, // unexpected register size + }, + }, + }; + } + + fn conditionalBranch( + o0: u1, + o1: u1, + cond: Condition, + offset: i21, + ) Instruction { + assert(offset & 0b11 == 0b00); + return Instruction{ + .conditional_branch = .{ + .cond = @enumToInt(cond), + .o0 = o0, + .imm19 = @bitCast(u19, @intCast(i19, offset >> 2)), + .o1 = o1, + }, + }; + } + + fn compareAndBranch( + op: u1, + rt: Register, + offset: i21, + ) Instruction { + assert(offset & 0b11 == 0b00); + return Instruction{ + .compare_and_branch = .{ + .rt = rt.id(), + .imm19 = @bitCast(u19, @intCast(i19, offset >> 2)), + .op = op, + .sf = switch (rt.size()) { + 32 => 0b0, + 64 => 0b1, + else => unreachable, // unexpected register size + }, + }, + }; + } + + // Helper functions for assembly syntax functions + + // 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 = union(enum) { + register: struct { + rn: Register, + offset: LoadStoreOffset = LoadStoreOffset.none, + }, + literal: u19, + }; + + pub fn ldr(rt: Register, args: LdrArgs) Instruction { + switch (args) { + .register => |info| return loadStoreRegister(rt, info.rn, info.offset, .ldr), + .literal => |literal| return loadLiteral(rt, literal), + } + } + + pub fn ldrh(rt: Register, rn: Register, args: StrArgs) Instruction { + return loadStoreRegister(rt, rn, args.offset, .ldrh); + } + + pub fn ldrb(rt: Register, rn: Register, args: StrArgs) Instruction { + return loadStoreRegister(rt, rn, args.offset, .ldrb); + } + + pub const StrArgs = struct { + offset: LoadStoreOffset = LoadStoreOffset.none, + }; + + pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction { + return loadStoreRegister(rt, rn, args.offset, .str); + } + + pub fn strh(rt: Register, rn: Register, args: StrArgs) Instruction { + return loadStoreRegister(rt, rn, args.offset, .strh); + } + + pub fn strb(rt: Register, rn: Register, args: StrArgs) Instruction { + return loadStoreRegister(rt, rn, args.offset, .strb); + } + + // Load or store pair of registers + + pub const LoadStorePairOffset = struct { + encoding: enum(u2) { + PostIndex = 0b01, + Signed = 0b10, + PreIndex = 0b11, + }, + offset: i9, + + pub fn none() LoadStorePairOffset { + return .{ .encoding = .Signed, .offset = 0 }; + } + + pub fn post_index(imm: i9) LoadStorePairOffset { + return .{ .encoding = .PostIndex, .offset = imm }; + } + + pub fn pre_index(imm: i9) LoadStorePairOffset { + return .{ .encoding = .PreIndex, .offset = imm }; + } + + pub fn signed(imm: i9) LoadStorePairOffset { + return .{ .encoding = .Signed, .offset = imm }; + } + }; + + pub fn ldp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction { + return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), true); + } + + pub fn ldnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction { + return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, true); + } + + pub fn stp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction { + return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), false); + } + + pub fn stnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction { + return loadStoreRegisterPair(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{ .no_operation = .{} }; + } + + // Logical (shifted register) + + pub fn @"and"(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b00, 0b0, shift, rd, rn, rm); + } + + pub fn bic(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b00, 0b1, shift, rd, rn, rm); + } + + pub fn orr(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b01, 0b0, shift, rd, rn, rm); + } + + pub fn orn(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b01, 0b1, shift, rd, rn, rm); + } + + pub fn eor(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b10, 0b0, shift, rd, rn, rm); + } + + pub fn eon(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b10, 0b1, shift, rd, rn, rm); + } + + pub fn ands(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b11, 0b0, shift, rd, rn, rm); + } + + pub fn bics(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b11, 0b1, shift, rd, rn, rm); + } + + // Add/subtract (immediate) + + pub fn add(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b0, 0b0, rd, rn, imm, shift); + } + + pub fn adds(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b0, 0b1, rd, rn, imm, shift); + } + + pub fn sub(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b1, 0b0, rd, rn, imm, shift); + } + + pub fn subs(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b1, 0b1, rd, rn, imm, shift); + } + + // Conditional branch + + pub fn bCond(cond: Condition, offset: i21) Instruction { + return conditionalBranch(0b0, 0b0, cond, offset); + } + + // Compare and branch + + pub fn cbz(rt: Register, offset: i21) Instruction { + return compareAndBranch(0b0, rt, offset); + } + + pub fn cbnz(rt: Register, offset: i21) Instruction { + return compareAndBranch(0b1, rt, offset); + } +}; + +test { + testing.refAllDecls(@This()); +} + +test "serialize instructions" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + const testcases = [_]Testcase{ + .{ // orr x0, xzr, x1 + .inst = Instruction.orr(.x0, .xzr, .x1, Instruction.Shift.none), + .expected = 0b1_01_01010_00_0_00001_000000_11111_00000, + }, + .{ // orn x0, xzr, x1 + .inst = Instruction.orn(.x0, .xzr, .x1, Instruction.Shift.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, .{ .register = .{ .rn = .x1 } }), + .expected = 0b11_111_0_01_01_000000000000_00001_00010, + }, + .{ // ldr x2, [x1, #1]! + .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_pre_index(1) } }), + .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010, + }, + .{ // ldr x2, [x1], #-1 + .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_post_index(-1) } }), + .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010, + }, + .{ // ldr x2, [x1], (x3) + .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.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, + }, + .{ // ldrh x7, [x4], #0xaa + .inst = Instruction.ldrh(.x7, .x4, .{ .offset = Instruction.LoadStoreOffset.imm_post_index(0xaa) }), + .expected = 0b01_111_0_00_01_0_010101010_01_00100_00111, + }, + .{ // ldrb x9, [x15, #0xff]! + .inst = Instruction.ldrb(.x9, .x15, .{ .offset = Instruction.LoadStoreOffset.imm_pre_index(0xff) }), + .expected = 0b00_111_0_00_01_0_011111111_11_01111_01001, + }, + .{ // 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.LoadStoreOffset.reg(.x3) }), + .expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010, + }, + .{ // strh w0, [x1] + .inst = Instruction.strh(.w0, .x1, .{}), + .expected = 0b01_111_0_01_00_000000000000_00001_00000, + }, + .{ // strb w8, [x9] + .inst = Instruction.strb(.w8, .x9, .{}), + .expected = 0b00_111_0_01_00_000000000000_01001_01000, + }, + .{ // 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, Instruction.LoadStorePairOffset.signed(8)), + .expected = 0b10_101_0_010_0_0000001_00010_11111_00001, + }, + .{ // ldp x1, x2, [sp, #8] + .inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.signed(8)), + .expected = 0b10_101_0_010_1_0000001_00010_11111_00001, + }, + .{ // stp x1, x2, [sp, #-16]! + .inst = Instruction.stp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.pre_index(-16)), + .expected = 0b10_101_0_011_0_1111110_00010_11111_00001, + }, + .{ // ldp x1, x2, [sp], #16 + .inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.post_index(16)), + .expected = 0b10_101_0_001_1_0000010_00010_11111_00001, + }, + .{ // and x0, x4, x2 + .inst = Instruction.@"and"(.x0, .x4, .x2, .{}), + .expected = 0b1_00_01010_00_0_00010_000000_00100_00000, + }, + .{ // and x0, x4, x2, lsl #0x8 + .inst = Instruction.@"and"(.x0, .x4, .x2, .{ .shift = .lsl, .amount = 0x8 }), + .expected = 0b1_00_01010_00_0_00010_001000_00100_00000, + }, + .{ // add x0, x10, #10 + .inst = Instruction.add(.x0, .x10, 10, false), + .expected = 0b1_0_0_100010_0_0000_0000_1010_01010_00000, + }, + .{ // subs x0, x5, #11, lsl #12 + .inst = Instruction.subs(.x0, .x5, 11, true), + .expected = 0b1_1_1_100010_1_0000_0000_1011_00101_00000, + }, + .{ // b.hi #-4 + .inst = Instruction.bCond(.hi, -4), + .expected = 0b0101010_0_1111111111111111111_0_1000, + }, + .{ // cbz x10, #40 + .inst = Instruction.cbz(.x10, 40), + .expected = 0b1_011010_0_0000000000000001010_01010, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + try testing.expectEqual(case.expected, actual); + } +} diff --git a/src/arch/arm/bits.zig b/src/arch/arm/bits.zig new file mode 100644 index 0000000000..279ce58005 --- /dev/null +++ b/src/arch/arm/bits.zig @@ -0,0 +1,1408 @@ +const std = @import("std"); +const DW = std.dwarf; +const testing = std.testing; + +/// The condition field specifies the flags necessary for an +/// Instruction to be executed +pub const Condition = enum(u4) { + /// equal + eq, + /// not equal + ne, + /// unsigned higher or same + cs, + /// unsigned lower + cc, + /// negative + mi, + /// positive or zero + pl, + /// overflow + vs, + /// no overflow + vc, + /// unsigned higer + hi, + /// unsigned lower or same + ls, + /// greater or equal + ge, + /// less than + lt, + /// greater than + gt, + /// less than or equal + le, + /// always + al, + + /// Converts a std.math.CompareOperator into a condition flag, + /// i.e. returns the condition that is true iff the result of the + /// comparison is true. Assumes signed comparison + pub fn fromCompareOperatorSigned(op: std.math.CompareOperator) Condition { + return switch (op) { + .gte => .ge, + .gt => .gt, + .neq => .ne, + .lt => .lt, + .lte => .le, + .eq => .eq, + }; + } + + /// Converts a std.math.CompareOperator into a condition flag, + /// i.e. returns the condition that is true iff the result of the + /// comparison is true. Assumes unsigned comparison + pub fn fromCompareOperatorUnsigned(op: std.math.CompareOperator) Condition { + return switch (op) { + .gte => .cs, + .gt => .hi, + .neq => .ne, + .lt => .cc, + .lte => .ls, + .eq => .eq, + }; + } + + /// Returns the condition which is true iff the given condition is + /// false (if such a condition exists) + pub fn negate(cond: Condition) Condition { + return switch (cond) { + .eq => .ne, + .ne => .eq, + .cs => .cc, + .cc => .cs, + .mi => .pl, + .pl => .mi, + .vs => .vc, + .vc => .vs, + .hi => .ls, + .ls => .hi, + .ge => .lt, + .lt => .ge, + .gt => .le, + .le => .gt, + .al => unreachable, + }; + } +}; + +test "condition from CompareOperator" { + try testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorSigned(.eq)); + try testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorUnsigned(.eq)); + + try testing.expectEqual(@as(Condition, .gt), Condition.fromCompareOperatorSigned(.gt)); + try testing.expectEqual(@as(Condition, .hi), Condition.fromCompareOperatorUnsigned(.gt)); + + try testing.expectEqual(@as(Condition, .le), Condition.fromCompareOperatorSigned(.lte)); + try testing.expectEqual(@as(Condition, .ls), Condition.fromCompareOperatorUnsigned(.lte)); +} + +test "negate condition" { + try testing.expectEqual(@as(Condition, .eq), Condition.ne.negate()); + try testing.expectEqual(@as(Condition, .ne), Condition.eq.negate()); +} + +/// Represents a register in the ARM instruction set architecture +pub const Register = enum(u5) { + r0, + r1, + r2, + r3, + r4, + r5, + r6, + r7, + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + + /// Argument / result / scratch register 1 + a1, + /// Argument / result / scratch register 2 + a2, + /// Argument / scratch register 3 + a3, + /// Argument / scratch register 4 + a4, + /// Variable-register 1 + v1, + /// Variable-register 2 + v2, + /// Variable-register 3 + v3, + /// Variable-register 4 + v4, + /// Variable-register 5 + v5, + /// Platform register + v6, + /// Variable-register 7 + v7, + /// Frame pointer or Variable-register 8 + fp, + /// Intra-Procedure-call scratch register + ip, + /// Stack pointer + sp, + /// Link register + lr, + /// Program counter + pc, + + /// Returns the unique 4-bit ID of this register which is used in + /// the machine code + pub fn id(self: Register) u4 { + return @truncate(u4, @enumToInt(self)); + } + + /// 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; + } +}; + +test "Register.id" { + try testing.expectEqual(@as(u4, 15), Register.r15.id()); + try testing.expectEqual(@as(u4, 15), Register.pc.id()); +} + +/// Program status registers containing flags, mode bits and other +/// vital information +pub const Psr = enum { + cpsr, + spsr, +}; + +pub const callee_preserved_regs = [_]Register{ .r4, .r5, .r6, .r7, .r8, .r10 }; +pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 }; +pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 }; + +/// Represents an instruction in the ARM instruction set architecture +pub const Instruction = union(enum) { + data_processing: packed struct { + // Note to self: The order of the fields top-to-bottom is + // right-to-left in the actual 32-bit int representation + op2: u12, + rd: u4, + rn: u4, + s: u1, + opcode: u4, + i: u1, + fixed: u2 = 0b00, + cond: u4, + }, + multiply: packed struct { + rn: u4, + fixed_1: u4 = 0b1001, + rm: u4, + ra: u4, + rd: u4, + set_cond: u1, + accumulate: u1, + fixed_2: u6 = 0b000000, + cond: u4, + }, + multiply_long: packed struct { + rn: u4, + fixed_1: u4 = 0b1001, + rm: u4, + rdlo: u4, + rdhi: u4, + set_cond: u1, + accumulate: u1, + unsigned: u1, + fixed_2: u5 = 0b00001, + cond: u4, + }, + integer_saturating_arithmetic: packed struct { + rm: u4, + fixed_1: u8 = 0b0000_0101, + rd: u4, + rn: u4, + fixed_2: u1 = 0b0, + opc: u2, + fixed_3: u5 = 0b00010, + cond: u4, + }, + single_data_transfer: packed struct { + offset: u12, + rd: u4, + rn: u4, + load_store: u1, + write_back: u1, + byte_word: u1, + up_down: u1, + pre_post: u1, + imm: u1, + fixed: u2 = 0b01, + cond: u4, + }, + extra_load_store: packed struct { + imm4l: u4, + fixed_1: u1 = 0b1, + op2: u2, + fixed_2: u1 = 0b1, + imm4h: u4, + rt: u4, + rn: u4, + o1: u1, + write_back: u1, + imm: u1, + up_down: u1, + pre_index: u1, + fixed_3: u3 = 0b000, + cond: u4, + }, + block_data_transfer: packed struct { + register_list: u16, + rn: u4, + load_store: u1, + write_back: u1, + psr_or_user: u1, + up_down: u1, + pre_post: u1, + fixed: u3 = 0b100, + cond: u4, + }, + branch: packed struct { + offset: u24, + link: u1, + fixed: u3 = 0b101, + cond: u4, + }, + branch_exchange: packed struct { + rn: u4, + fixed_1: u1 = 0b1, + link: u1, + fixed_2: u22 = 0b0001_0010_1111_1111_1111_00, + cond: u4, + }, + supervisor_call: packed struct { + comment: u24, + fixed: u4 = 0b1111, + cond: u4, + }, + breakpoint: packed struct { + imm4: u4, + fixed_1: u4 = 0b0111, + imm12: u12, + fixed_2_and_cond: u12 = 0b1110_0001_0010, + }, + + /// Represents the possible operations which can be performed by a + /// Data Processing instruction + const Opcode = enum(u4) { + // Rd := Op1 AND Op2 + @"and", + // Rd := Op1 EOR Op2 + eor, + // Rd := Op1 - Op2 + sub, + // Rd := Op2 - Op1 + rsb, + // Rd := Op1 + Op2 + add, + // Rd := Op1 + Op2 + C + adc, + // Rd := Op1 - Op2 + C - 1 + sbc, + // Rd := Op2 - Op1 + C - 1 + rsc, + // set condition codes on Op1 AND Op2 + tst, + // set condition codes on Op1 EOR Op2 + teq, + // set condition codes on Op1 - Op2 + cmp, + // set condition codes on Op1 + Op2 + cmn, + // Rd := Op1 OR Op2 + orr, + // Rd := Op2 + mov, + // Rd := Op1 AND NOT Op2 + bic, + // Rd := NOT Op2 + mvn, + }; + + /// Represents the second operand to a data processing instruction + /// which can either be content from a register or an immediate + /// value + pub const Operand = union(enum) { + Register: packed struct { + rm: u4, + shift: u8, + }, + Immediate: packed struct { + imm: u8, + rotate: u4, + }, + + /// Represents multiple ways a register can be shifted. A + /// register can be shifted by a specific immediate value or + /// by the contents of another register + pub const Shift = union(enum) { + Immediate: packed struct { + fixed: u1 = 0b0, + typ: u2, + amount: u5, + }, + Register: packed struct { + fixed_1: u1 = 0b1, + typ: u2, + fixed_2: u1 = 0b0, + rs: u4, + }, + + pub const Type = enum(u2) { + logical_left, + logical_right, + arithmetic_right, + rotate_right, + }; + + pub const none = Shift{ + .Immediate = .{ + .amount = 0, + .typ = 0, + }, + }; + + pub fn toU8(self: Shift) u8 { + return switch (self) { + .Register => |v| @bitCast(u8, v), + .Immediate => |v| @bitCast(u8, v), + }; + } + + pub fn reg(rs: Register, typ: Type) Shift { + return Shift{ + .Register = .{ + .rs = rs.id(), + .typ = @enumToInt(typ), + }, + }; + } + + pub fn imm(amount: u5, typ: Type) Shift { + return Shift{ + .Immediate = .{ + .amount = amount, + .typ = @enumToInt(typ), + }, + }; + } + }; + + pub fn toU12(self: Operand) u12 { + return switch (self) { + .Register => |v| @bitCast(u12, v), + .Immediate => |v| @bitCast(u12, v), + }; + } + + pub fn reg(rm: Register, shift: Shift) Operand { + return Operand{ + .Register = .{ + .rm = rm.id(), + .shift = shift.toU8(), + }, + }; + } + + pub fn imm(immediate: u8, rotate: u4) Operand { + return Operand{ + .Immediate = .{ + .imm = immediate, + .rotate = rotate, + }, + }; + } + + /// Tries to convert an unsigned 32 bit integer into an + /// immediate operand using rotation. Returns null when there + /// is no conversion + pub fn fromU32(x: u32) ?Operand { + const masks = comptime blk: { + const base_mask: u32 = std.math.maxInt(u8); + var result = [_]u32{0} ** 16; + for (result) |*mask, i| mask.* = std.math.rotr(u32, base_mask, 2 * i); + break :blk result; + }; + + return for (masks) |mask, i| { + if (x & mask == x) { + break Operand{ + .Immediate = .{ + .imm = @intCast(u8, std.math.rotl(u32, x, 2 * i)), + .rotate = @intCast(u4, i), + }, + }; + } + } else null; + } + }; + + /// 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: u12, + Register: packed struct { + rm: u4, + shift: u8, + }, + + pub const none = Offset{ + .Immediate = 0, + }; + + pub fn toU12(self: Offset) u12 { + return switch (self) { + .Register => |v| @bitCast(u12, v), + .Immediate => |v| v, + }; + } + + pub fn reg(rm: Register, shift: u8) Offset { + return Offset{ + .Register = .{ + .rm = rm.id(), + .shift = shift, + }, + }; + } + + pub fn imm(immediate: u12) Offset { + return Offset{ + .Immediate = immediate, + }; + } + }; + + /// Represents the offset operand of an extra load or store + /// instruction. + pub const ExtraLoadStoreOffset = union(enum) { + immediate: u8, + register: u4, + + pub const none = ExtraLoadStoreOffset{ + .immediate = 0, + }; + + pub fn reg(register: Register) ExtraLoadStoreOffset { + return ExtraLoadStoreOffset{ + .register = register.id(), + }; + } + + pub fn imm(immediate: u8) ExtraLoadStoreOffset { + return ExtraLoadStoreOffset{ + .immediate = immediate, + }; + } + }; + + /// Represents the register list operand to a block data transfer + /// instruction + pub const RegisterList = packed struct { + r0: bool = false, + r1: bool = false, + r2: bool = false, + r3: bool = false, + r4: bool = false, + r5: bool = false, + r6: bool = false, + r7: bool = false, + r8: bool = false, + r9: bool = false, + r10: bool = false, + r11: bool = false, + r12: bool = false, + r13: bool = false, + r14: bool = false, + r15: bool = false, + }; + + pub fn toU32(self: Instruction) u32 { + return switch (self) { + .data_processing => |v| @bitCast(u32, v), + .multiply => |v| @bitCast(u32, v), + .multiply_long => |v| @bitCast(u32, v), + .integer_saturating_arithmetic => |v| @bitCast(u32, v), + .single_data_transfer => |v| @bitCast(u32, v), + .extra_load_store => |v| @bitCast(u32, v), + .block_data_transfer => |v| @bitCast(u32, v), + .branch => |v| @bitCast(u32, v), + .branch_exchange => |v| @bitCast(u32, v), + .supervisor_call => |v| @bitCast(u32, v), + .breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20), + }; + } + + // Helper functions for the "real" functions below + + fn dataProcessing( + cond: Condition, + opcode: Opcode, + s: u1, + rd: Register, + rn: Register, + op2: Operand, + ) Instruction { + return Instruction{ + .data_processing = .{ + .cond = @enumToInt(cond), + .i = @boolToInt(op2 == .Immediate), + .opcode = @enumToInt(opcode), + .s = s, + .rn = rn.id(), + .rd = rd.id(), + .op2 = op2.toU12(), + }, + }; + } + + fn specialMov( + cond: Condition, + rd: Register, + imm: u16, + top: bool, + ) Instruction { + return Instruction{ + .data_processing = .{ + .cond = @enumToInt(cond), + .i = 1, + .opcode = if (top) 0b1010 else 0b1000, + .s = 0, + .rn = @truncate(u4, imm >> 12), + .rd = rd.id(), + .op2 = @truncate(u12, imm), + }, + }; + } + + fn multiply( + cond: Condition, + set_cond: u1, + rd: Register, + rn: Register, + rm: Register, + ra: ?Register, + ) Instruction { + return Instruction{ + .multiply = .{ + .cond = @enumToInt(cond), + .accumulate = @boolToInt(ra != null), + .set_cond = set_cond, + .rd = rd.id(), + .rn = rn.id(), + .ra = if (ra) |reg| reg.id() else 0b0000, + .rm = rm.id(), + }, + }; + } + + fn multiplyLong( + cond: Condition, + signed: u1, + accumulate: u1, + set_cond: u1, + rdhi: Register, + rdlo: Register, + rm: Register, + rn: Register, + ) Instruction { + return Instruction{ + .multiply_long = .{ + .cond = @enumToInt(cond), + .unsigned = signed, + .accumulate = accumulate, + .set_cond = set_cond, + .rdlo = rdlo.id(), + .rdhi = rdhi.id(), + .rn = rn.id(), + .rm = rm.id(), + }, + }; + } + + fn integerSaturationArithmetic( + cond: Condition, + rd: Register, + rm: Register, + rn: Register, + opc: u2, + ) Instruction { + return Instruction{ + .integer_saturating_arithmetic = .{ + .rm = rm.id(), + .rd = rd.id(), + .rn = rn.id(), + .opc = opc, + .cond = @enumToInt(cond), + }, + }; + } + + fn singleDataTransfer( + cond: Condition, + rd: Register, + rn: Register, + offset: Offset, + pre_index: bool, + positive: bool, + byte_word: u1, + write_back: bool, + load_store: u1, + ) Instruction { + return Instruction{ + .single_data_transfer = .{ + .cond = @enumToInt(cond), + .rn = rn.id(), + .rd = rd.id(), + .offset = offset.toU12(), + .load_store = load_store, + .write_back = @boolToInt(write_back), + .byte_word = byte_word, + .up_down = @boolToInt(positive), + .pre_post = @boolToInt(pre_index), + .imm = @boolToInt(offset != .Immediate), + }, + }; + } + + fn extraLoadStore( + cond: Condition, + pre_index: bool, + positive: bool, + write_back: bool, + o1: u1, + op2: u2, + rn: Register, + rt: Register, + offset: ExtraLoadStoreOffset, + ) Instruction { + const imm4l: u4 = switch (offset) { + .immediate => |imm| @truncate(u4, imm), + .register => |reg| reg, + }; + const imm4h: u4 = switch (offset) { + .immediate => |imm| @truncate(u4, imm >> 4), + .register => 0b0000, + }; + + return Instruction{ + .extra_load_store = .{ + .imm4l = imm4l, + .op2 = op2, + .imm4h = imm4h, + .rt = rt.id(), + .rn = rn.id(), + .o1 = o1, + .write_back = @boolToInt(write_back), + .imm = @boolToInt(offset == .immediate), + .up_down = @boolToInt(positive), + .pre_index = @boolToInt(pre_index), + .cond = @enumToInt(cond), + }, + }; + } + + fn blockDataTransfer( + cond: Condition, + rn: Register, + reg_list: RegisterList, + pre_post: u1, + up_down: u1, + psr_or_user: u1, + write_back: bool, + load_store: u1, + ) Instruction { + return Instruction{ + .block_data_transfer = .{ + .register_list = @bitCast(u16, reg_list), + .rn = rn.id(), + .load_store = load_store, + .write_back = @boolToInt(write_back), + .psr_or_user = psr_or_user, + .up_down = up_down, + .pre_post = pre_post, + .cond = @enumToInt(cond), + }, + }; + } + + fn branch(cond: Condition, offset: i26, link: u1) Instruction { + return Instruction{ + .branch = .{ + .cond = @enumToInt(cond), + .link = link, + .offset = @bitCast(u24, @intCast(i24, offset >> 2)), + }, + }; + } + + fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction { + return Instruction{ + .branch_exchange = .{ + .cond = @enumToInt(cond), + .link = link, + .rn = rn.id(), + }, + }; + } + + fn supervisorCall(cond: Condition, comment: u24) Instruction { + return Instruction{ + .supervisor_call = .{ + .cond = @enumToInt(cond), + .comment = comment, + }, + }; + } + + fn breakpoint(imm: u16) Instruction { + return Instruction{ + .breakpoint = .{ + .imm12 = @truncate(u12, imm >> 4), + .imm4 = @truncate(u4, imm), + }, + }; + } + + // Public functions replicating assembler syntax as closely as + // possible + + // Data processing + + pub fn @"and"(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", 0, rd, rn, op2); + } + + pub fn ands(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", 1, rd, rn, op2); + } + + pub fn eor(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, 0, rd, rn, op2); + } + + pub fn eors(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, 1, rd, rn, op2); + } + + pub fn sub(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, 0, rd, rn, op2); + } + + pub fn subs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, 1, rd, rn, op2); + } + + pub fn rsb(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsb, 0, rd, rn, op2); + } + + pub fn rsbs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsb, 1, rd, rn, op2); + } + + pub fn add(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .add, 0, rd, rn, op2); + } + + pub fn adds(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .add, 1, rd, rn, op2); + } + + pub fn adc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .adc, 0, rd, rn, op2); + } + + pub fn adcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .adc, 1, rd, rn, op2); + } + + pub fn sbc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sbc, 0, rd, rn, op2); + } + + pub fn sbcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sbc, 1, rd, rn, op2); + } + + pub fn rsc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, 0, rd, rn, op2); + } + + pub fn rscs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, 1, rd, rn, op2); + } + + pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .tst, 1, .r0, rn, op2); + } + + pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .teq, 1, .r0, rn, op2); + } + + pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .cmp, 1, .r0, rn, op2); + } + + pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .cmn, 1, .r0, rn, op2); + } + + pub fn orr(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, 0, rd, rn, op2); + } + + pub fn orrs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, 1, rd, rn, op2); + } + + pub fn mov(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, 0, rd, .r0, op2); + } + + pub fn movs(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, 1, rd, .r0, op2); + } + + pub fn bic(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, 0, rd, rn, op2); + } + + pub fn bics(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, 1, rd, rn, op2); + } + + pub fn mvn(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mvn, 0, rd, .r0, op2); + } + + pub fn mvns(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mvn, 1, rd, .r0, op2); + } + + // Integer Saturating Arithmetic + + pub fn qadd(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { + return integerSaturationArithmetic(cond, rd, rm, rn, 0b00); + } + + pub fn qsub(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { + return integerSaturationArithmetic(cond, rd, rm, rn, 0b01); + } + + pub fn qdadd(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { + return integerSaturationArithmetic(cond, rd, rm, rn, 0b10); + } + + pub fn qdsub(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { + return integerSaturationArithmetic(cond, rd, rm, rn, 0b11); + } + + // movw and movt + + pub fn movw(cond: Condition, rd: Register, imm: u16) Instruction { + return specialMov(cond, rd, imm, false); + } + + pub fn movt(cond: Condition, rd: Register, imm: u16) Instruction { + return specialMov(cond, rd, imm, true); + } + + // PSR transfer + + pub fn mrs(cond: Condition, rd: Register, psr: Psr) Instruction { + return Instruction{ + .data_processing = .{ + .cond = @enumToInt(cond), + .i = 0, + .opcode = if (psr == .spsr) 0b1010 else 0b1000, + .s = 0, + .rn = 0b1111, + .rd = rd.id(), + .op2 = 0b0000_0000_0000, + }, + }; + } + + pub fn msr(cond: Condition, psr: Psr, op: Operand) Instruction { + return Instruction{ + .data_processing = .{ + .cond = @enumToInt(cond), + .i = 0, + .opcode = if (psr == .spsr) 0b1011 else 0b1001, + .s = 0, + .rn = 0b1111, + .rd = 0b1111, + .op2 = op.toU12(), + }, + }; + } + + // Multiply + + pub fn mul(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction { + return multiply(cond, 0, rd, rn, rm, null); + } + + pub fn muls(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction { + return multiply(cond, 1, rd, rn, rm, null); + } + + pub fn mla(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction { + return multiply(cond, 0, rd, rn, rm, ra); + } + + pub fn mlas(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction { + return multiply(cond, 1, rd, rn, rm, ra); + } + + // Multiply long + + pub fn umull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 0, 0, 0, rdhi, rdlo, rm, rn); + } + + pub fn umulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 0, 0, 1, rdhi, rdlo, rm, rn); + } + + pub fn umlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 0, 1, 0, rdhi, rdlo, rm, rn); + } + + pub fn umlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 0, 1, 1, rdhi, rdlo, rm, rn); + } + + pub fn smull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 1, 0, 0, rdhi, rdlo, rm, rn); + } + + pub fn smulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 1, 0, 1, rdhi, rdlo, rm, rn); + } + + pub fn smlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 1, 1, 0, rdhi, rdlo, rm, rn); + } + + pub fn smlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { + return multiplyLong(cond, 1, 1, 1, rdhi, rdlo, rm, rn); + } + + // Single data transfer + + pub const OffsetArgs = struct { + pre_index: bool = true, + positive: bool = true, + offset: Offset, + write_back: bool = false, + }; + + pub fn ldr(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 1); + } + + pub fn ldrb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 1); + } + + pub fn str(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 0); + } + + pub fn strb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 0); + } + + // Extra load/store + + pub const ExtraLoadStoreOffsetArgs = struct { + pre_index: bool = true, + positive: bool = true, + offset: ExtraLoadStoreOffset, + write_back: bool = false, + }; + + pub fn strh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction { + return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 0, 0b01, rn, rt, args.offset); + } + + pub fn ldrh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction { + return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 1, 0b01, rn, rt, args.offset); + } + + // Block data transfer + + pub fn ldmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 1); + } + + pub fn ldmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 1); + } + + pub fn ldmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 1); + } + + pub fn ldmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 1); + } + + pub const ldmfa = ldmda; + pub const ldmea = ldmdb; + pub const ldmed = ldmib; + pub const ldmfd = ldmia; + pub const ldm = ldmia; + + pub fn stmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 0); + } + + pub fn stmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 0); + } + + pub fn stmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 0); + } + + pub fn stmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 0); + } + + pub const stmed = stmda; + pub const stmfd = stmdb; + pub const stmfa = stmib; + pub const stmea = stmia; + pub const stm = stmia; + + // Branch + + pub fn b(cond: Condition, offset: i26) Instruction { + return branch(cond, offset, 0); + } + + pub fn bl(cond: Condition, offset: i26) Instruction { + return branch(cond, offset, 1); + } + + // Branch and exchange + + pub fn bx(cond: Condition, rn: Register) Instruction { + return branchExchange(cond, rn, 0); + } + + pub fn blx(cond: Condition, rn: Register) Instruction { + return branchExchange(cond, rn, 1); + } + + // Supervisor Call + + pub const swi = svc; + + pub fn svc(cond: Condition, comment: u24) Instruction { + return supervisorCall(cond, comment); + } + + // Breakpoint + + pub fn bkpt(imm: u16) Instruction { + return breakpoint(imm); + } + + // Aliases + + pub fn nop() Instruction { + return mov(.al, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)); + } + + pub fn pop(cond: Condition, args: anytype) Instruction { + if (@typeInfo(@TypeOf(args)) != .Struct) { + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); + } + + if (args.len < 1) { + @compileError("Expected at least one register"); + } else if (args.len == 1) { + const reg = args[0]; + return ldr(cond, reg, .sp, .{ + .pre_index = false, + .positive = true, + .offset = Offset.imm(4), + .write_back = false, + }); + } else { + var register_list: u16 = 0; + inline for (args) |arg| { + const reg = @as(Register, arg); + register_list |= @as(u16, 1) << reg.id(); + } + return ldm(cond, .sp, true, @bitCast(RegisterList, register_list)); + } + } + + pub fn push(cond: Condition, args: anytype) Instruction { + if (@typeInfo(@TypeOf(args)) != .Struct) { + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); + } + + if (args.len < 1) { + @compileError("Expected at least one register"); + } else if (args.len == 1) { + const reg = args[0]; + return str(cond, reg, .sp, .{ + .pre_index = true, + .positive = false, + .offset = Offset.imm(4), + .write_back = true, + }); + } else { + var register_list: u16 = 0; + inline for (args) |arg| { + const reg = @as(Register, arg); + register_list |= @as(u16, 1) << reg.id(); + } + return stmdb(cond, .sp, true, @bitCast(RegisterList, register_list)); + } + } + + pub const ShiftAmount = union(enum) { + immediate: u5, + register: Register, + + pub fn imm(immediate: u5) ShiftAmount { + return .{ + .immediate = immediate, + }; + } + + pub fn reg(register: Register) ShiftAmount { + return .{ + .register = register, + }; + } + }; + + pub fn lsl(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))), + }; + } + + pub fn lsr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))), + }; + } + + pub fn asr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))), + }; + } + + pub fn ror(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))), + }; + } + + pub fn lsls(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))), + }; + } + + pub fn lsrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))), + }; + } + + pub fn asrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))), + }; + } + + pub fn rors(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))), + }; + } +}; + +test "serialize instructions" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + const testcases = [_]Testcase{ + .{ // add r0, r0, r0 + .inst = Instruction.add(.al, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)), + .expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000, + }, + .{ // mov r4, r2 + .inst = Instruction.mov(.al, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)), + .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010, + }, + .{ // mov r0, #42 + .inst = Instruction.mov(.al, .r0, Instruction.Operand.imm(42, 0)), + .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010, + }, + .{ // mrs r5, cpsr + .inst = Instruction.mrs(.al, .r5, .cpsr), + .expected = 0b1110_00010_0_001111_0101_000000000000, + }, + .{ // mul r0, r1, r2 + .inst = Instruction.mul(.al, .r0, .r1, .r2), + .expected = 0b1110_000000_0_0_0000_0000_0010_1001_0001, + }, + .{ // umlal r0, r1, r5, r6 + .inst = Instruction.umlal(.al, .r0, .r1, .r5, .r6), + .expected = 0b1110_00001_0_1_0_0001_0000_0110_1001_0101, + }, + .{ // ldr r0, [r2, #42] + .inst = Instruction.ldr(.al, .r0, .r2, .{ + .offset = Instruction.Offset.imm(42), + }), + .expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010, + }, + .{ // str r0, [r3] + .inst = Instruction.str(.al, .r0, .r3, .{ + .offset = Instruction.Offset.none, + }), + .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000, + }, + .{ // strh r1, [r5] + .inst = Instruction.strh(.al, .r1, .r5, .{ + .offset = Instruction.ExtraLoadStoreOffset.none, + }), + .expected = 0b1110_000_1_1_1_0_0_0101_0001_0000_1011_0000, + }, + .{ // b #12 + .inst = Instruction.b(.al, 12), + .expected = 0b1110_101_0_0000_0000_0000_0000_0000_0011, + }, + .{ // bl #-4 + .inst = Instruction.bl(.al, -4), + .expected = 0b1110_101_1_1111_1111_1111_1111_1111_1111, + }, + .{ // bx lr + .inst = Instruction.bx(.al, .lr), + .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110, + }, + .{ // svc #0 + .inst = Instruction.svc(.al, 0), + .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000, + }, + .{ // bkpt #42 + .inst = Instruction.bkpt(42), + .expected = 0b1110_0001_0010_000000000010_0111_1010, + }, + .{ // stmdb r9, {r0} + .inst = Instruction.stmdb(.al, .r9, false, .{ .r0 = true }), + .expected = 0b1110_100_1_0_0_0_0_1001_0000000000000001, + }, + .{ // ldmea r4!, {r2, r5} + .inst = Instruction.ldmea(.al, .r4, true, .{ .r2 = true, .r5 = true }), + .expected = 0b1110_100_1_0_0_1_1_0100_0000000000100100, + }, + .{ // qadd r0, r7, r8 + .inst = Instruction.qadd(.al, .r0, .r7, .r8), + .expected = 0b1110_00010_00_0_1000_0000_0000_0101_0111, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + try testing.expectEqual(case.expected, actual); + } +} + +test "aliases" { + const Testcase = struct { + expected: Instruction, + actual: Instruction, + }; + + const testcases = [_]Testcase{ + .{ // pop { r6 } + .actual = Instruction.pop(.al, .{.r6}), + .expected = Instruction.ldr(.al, .r6, .sp, .{ + .pre_index = false, + .positive = true, + .offset = Instruction.Offset.imm(4), + .write_back = false, + }), + }, + .{ // pop { r1, r5 } + .actual = Instruction.pop(.al, .{ .r1, .r5 }), + .expected = Instruction.ldm(.al, .sp, true, .{ .r1 = true, .r5 = true }), + }, + .{ // push { r3 } + .actual = Instruction.push(.al, .{.r3}), + .expected = Instruction.str(.al, .r3, .sp, .{ + .pre_index = true, + .positive = false, + .offset = Instruction.Offset.imm(4), + .write_back = true, + }), + }, + .{ // push { r0, r2 } + .actual = Instruction.push(.al, .{ .r0, .r2 }), + .expected = Instruction.stmdb(.al, .sp, true, .{ .r0 = true, .r2 = true }), + }, + .{ // lsl r4, r5, #5 + .actual = Instruction.lsl(.al, .r4, .r5, Instruction.ShiftAmount.imm(5)), + .expected = Instruction.mov(.al, .r4, Instruction.Operand.reg( + .r5, + Instruction.Operand.Shift.imm(5, .logical_left), + )), + }, + .{ // asrs r1, r1, r3 + .actual = Instruction.asrs(.al, .r1, .r1, Instruction.ShiftAmount.reg(.r3)), + .expected = Instruction.movs(.al, .r1, Instruction.Operand.reg( + .r1, + Instruction.Operand.Shift.reg(.r3, .arithmetic_right), + )), + }, + }; + + for (testcases) |case| { + try testing.expectEqual(case.expected.toU32(), case.actual.toU32()); + } +} diff --git a/src/arch/riscv64/bits.zig b/src/arch/riscv64/bits.zig new file mode 100644 index 0000000000..b297737816 --- /dev/null +++ b/src/arch/riscv64/bits.zig @@ -0,0 +1,470 @@ +const std = @import("std"); +const DW = std.dwarf; +const assert = std.debug.assert; +const testing = std.testing; + +// TODO: this is only tagged to facilitate the monstrosity. +// Once packed structs work make it packed. +pub const Instruction = union(enum) { + R: packed struct { + opcode: u7, + rd: u5, + funct3: u3, + rs1: u5, + rs2: u5, + funct7: u7, + }, + I: packed struct { + opcode: u7, + rd: u5, + funct3: u3, + rs1: u5, + imm0_11: u12, + }, + S: packed struct { + opcode: u7, + imm0_4: u5, + funct3: u3, + rs1: u5, + rs2: u5, + imm5_11: u7, + }, + B: packed struct { + opcode: u7, + imm11: u1, + imm1_4: u4, + funct3: u3, + rs1: u5, + rs2: u5, + imm5_10: u6, + imm12: u1, + }, + U: packed struct { + opcode: u7, + rd: u5, + imm12_31: u20, + }, + J: packed struct { + opcode: u7, + rd: u5, + imm12_19: u8, + imm11: u1, + imm1_10: u10, + imm20: u1, + }, + + // TODO: once packed structs work we can remove this monstrosity. + pub fn toU32(self: Instruction) u32 { + return switch (self) { + .R => |v| @bitCast(u32, v), + .I => |v| @bitCast(u32, v), + .S => |v| @bitCast(u32, v), + .B => |v| @intCast(u32, v.opcode) + (@intCast(u32, v.imm11) << 7) + (@intCast(u32, v.imm1_4) << 8) + (@intCast(u32, v.funct3) << 12) + (@intCast(u32, v.rs1) << 15) + (@intCast(u32, v.rs2) << 20) + (@intCast(u32, v.imm5_10) << 25) + (@intCast(u32, v.imm12) << 31), + .U => |v| @bitCast(u32, v), + .J => |v| @bitCast(u32, v), + }; + } + + fn rType(op: u7, fn3: u3, fn7: u7, rd: Register, r1: Register, r2: Register) Instruction { + return Instruction{ + .R = .{ + .opcode = op, + .funct3 = fn3, + .funct7 = fn7, + .rd = @enumToInt(rd), + .rs1 = @enumToInt(r1), + .rs2 = @enumToInt(r2), + }, + }; + } + + // RISC-V is all signed all the time -- convert immediates to unsigned for processing + fn iType(op: u7, fn3: u3, rd: Register, r1: Register, imm: i12) Instruction { + const umm = @bitCast(u12, imm); + + return Instruction{ + .I = .{ + .opcode = op, + .funct3 = fn3, + .rd = @enumToInt(rd), + .rs1 = @enumToInt(r1), + .imm0_11 = umm, + }, + }; + } + + fn sType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i12) Instruction { + const umm = @bitCast(u12, imm); + + return Instruction{ + .S = .{ + .opcode = op, + .funct3 = fn3, + .rs1 = @enumToInt(r1), + .rs2 = @enumToInt(r2), + .imm0_4 = @truncate(u5, umm), + .imm5_11 = @truncate(u7, umm >> 5), + }, + }; + } + + // Use significance value rather than bit value, same for J-type + // -- less burden on callsite, bonus semantic checking + fn bType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i13) Instruction { + const umm = @bitCast(u13, imm); + assert(umm % 2 == 0); // misaligned branch target + + return Instruction{ + .B = .{ + .opcode = op, + .funct3 = fn3, + .rs1 = @enumToInt(r1), + .rs2 = @enumToInt(r2), + .imm1_4 = @truncate(u4, umm >> 1), + .imm5_10 = @truncate(u6, umm >> 5), + .imm11 = @truncate(u1, umm >> 11), + .imm12 = @truncate(u1, umm >> 12), + }, + }; + } + + // We have to extract the 20 bits anyway -- let's not make it more painful + fn uType(op: u7, rd: Register, imm: i20) Instruction { + const umm = @bitCast(u20, imm); + + return Instruction{ + .U = .{ + .opcode = op, + .rd = @enumToInt(rd), + .imm12_31 = umm, + }, + }; + } + + fn jType(op: u7, rd: Register, imm: i21) Instruction { + const umm = @bitCast(u21, imm); + assert(umm % 2 == 0); // misaligned jump target + + return Instruction{ + .J = .{ + .opcode = op, + .rd = @enumToInt(rd), + .imm1_10 = @truncate(u10, umm >> 1), + .imm11 = @truncate(u1, umm >> 11), + .imm12_19 = @truncate(u8, umm >> 12), + .imm20 = @truncate(u1, umm >> 20), + }, + }; + } + + // The meat and potatoes. Arguments are in the order in which they would appear in assembly code. + + // Arithmetic/Logical, Register-Register + + pub fn add(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b000, 0b0000000, rd, r1, r2); + } + + pub fn sub(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b000, 0b0100000, rd, r1, r2); + } + + pub fn @"and"(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b111, 0b0000000, rd, r1, r2); + } + + pub fn @"or"(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b110, 0b0000000, rd, r1, r2); + } + + pub fn xor(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b100, 0b0000000, rd, r1, r2); + } + + pub fn sll(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b001, 0b0000000, rd, r1, r2); + } + + pub fn srl(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b101, 0b0000000, rd, r1, r2); + } + + pub fn sra(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b101, 0b0100000, rd, r1, r2); + } + + pub fn slt(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b010, 0b0000000, rd, r1, r2); + } + + pub fn sltu(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0110011, 0b011, 0b0000000, rd, r1, r2); + } + + // Arithmetic/Logical, Register-Register (32-bit) + + pub fn addw(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0111011, 0b000, rd, r1, r2); + } + + pub fn subw(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0111011, 0b000, 0b0100000, rd, r1, r2); + } + + pub fn sllw(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0111011, 0b001, 0b0000000, rd, r1, r2); + } + + pub fn srlw(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0111011, 0b101, 0b0000000, rd, r1, r2); + } + + pub fn sraw(rd: Register, r1: Register, r2: Register) Instruction { + return rType(0b0111011, 0b101, 0b0100000, rd, r1, r2); + } + + // Arithmetic/Logical, Register-Immediate + + pub fn addi(rd: Register, r1: Register, imm: i12) Instruction { + return iType(0b0010011, 0b000, rd, r1, imm); + } + + pub fn andi(rd: Register, r1: Register, imm: i12) Instruction { + return iType(0b0010011, 0b111, rd, r1, imm); + } + + pub fn ori(rd: Register, r1: Register, imm: i12) Instruction { + return iType(0b0010011, 0b110, rd, r1, imm); + } + + pub fn xori(rd: Register, r1: Register, imm: i12) Instruction { + return iType(0b0010011, 0b100, rd, r1, imm); + } + + pub fn slli(rd: Register, r1: Register, shamt: u6) Instruction { + return iType(0b0010011, 0b001, rd, r1, shamt); + } + + pub fn srli(rd: Register, r1: Register, shamt: u6) Instruction { + return iType(0b0010011, 0b101, rd, r1, shamt); + } + + pub fn srai(rd: Register, r1: Register, shamt: u6) Instruction { + return iType(0b0010011, 0b101, rd, r1, (1 << 10) + shamt); + } + + pub fn slti(rd: Register, r1: Register, imm: i12) Instruction { + return iType(0b0010011, 0b010, rd, r1, imm); + } + + pub fn sltiu(rd: Register, r1: Register, imm: u12) Instruction { + return iType(0b0010011, 0b011, rd, r1, @bitCast(i12, imm)); + } + + // Arithmetic/Logical, Register-Immediate (32-bit) + + pub fn addiw(rd: Register, r1: Register, imm: i12) Instruction { + return iType(0b0011011, 0b000, rd, r1, imm); + } + + pub fn slliw(rd: Register, r1: Register, shamt: u5) Instruction { + return iType(0b0011011, 0b001, rd, r1, shamt); + } + + pub fn srliw(rd: Register, r1: Register, shamt: u5) Instruction { + return iType(0b0011011, 0b101, rd, r1, shamt); + } + + pub fn sraiw(rd: Register, r1: Register, shamt: u5) Instruction { + return iType(0b0011011, 0b101, rd, r1, (1 << 10) + shamt); + } + + // Upper Immediate + + pub fn lui(rd: Register, imm: i20) Instruction { + return uType(0b0110111, rd, imm); + } + + pub fn auipc(rd: Register, imm: i20) Instruction { + return uType(0b0010111, rd, imm); + } + + // Load + + pub fn ld(rd: Register, offset: i12, base: Register) Instruction { + return iType(0b0000011, 0b011, rd, base, offset); + } + + pub fn lw(rd: Register, offset: i12, base: Register) Instruction { + return iType(0b0000011, 0b010, rd, base, offset); + } + + pub fn lwu(rd: Register, offset: i12, base: Register) Instruction { + return iType(0b0000011, 0b110, rd, base, offset); + } + + pub fn lh(rd: Register, offset: i12, base: Register) Instruction { + return iType(0b0000011, 0b001, rd, base, offset); + } + + pub fn lhu(rd: Register, offset: i12, base: Register) Instruction { + return iType(0b0000011, 0b101, rd, base, offset); + } + + pub fn lb(rd: Register, offset: i12, base: Register) Instruction { + return iType(0b0000011, 0b000, rd, base, offset); + } + + pub fn lbu(rd: Register, offset: i12, base: Register) Instruction { + return iType(0b0000011, 0b100, rd, base, offset); + } + + // Store + + pub fn sd(rs: Register, offset: i12, base: Register) Instruction { + return sType(0b0100011, 0b011, base, rs, offset); + } + + pub fn sw(rs: Register, offset: i12, base: Register) Instruction { + return sType(0b0100011, 0b010, base, rs, offset); + } + + pub fn sh(rs: Register, offset: i12, base: Register) Instruction { + return sType(0b0100011, 0b001, base, rs, offset); + } + + pub fn sb(rs: Register, offset: i12, base: Register) Instruction { + return sType(0b0100011, 0b000, base, rs, offset); + } + + // Fence + // TODO: implement fence + + // Branch + + pub fn beq(r1: Register, r2: Register, offset: i13) Instruction { + return bType(0b1100011, 0b000, r1, r2, offset); + } + + pub fn bne(r1: Register, r2: Register, offset: i13) Instruction { + return bType(0b1100011, 0b001, r1, r2, offset); + } + + pub fn blt(r1: Register, r2: Register, offset: i13) Instruction { + return bType(0b1100011, 0b100, r1, r2, offset); + } + + pub fn bge(r1: Register, r2: Register, offset: i13) Instruction { + return bType(0b1100011, 0b101, r1, r2, offset); + } + + pub fn bltu(r1: Register, r2: Register, offset: i13) Instruction { + return bType(0b1100011, 0b110, r1, r2, offset); + } + + pub fn bgeu(r1: Register, r2: Register, offset: i13) Instruction { + return bType(0b1100011, 0b111, r1, r2, offset); + } + + // Jump + + pub fn jal(link: Register, offset: i21) Instruction { + return jType(0b1101111, link, offset); + } + + pub fn jalr(link: Register, offset: i12, base: Register) Instruction { + return iType(0b1100111, 0b000, link, base, offset); + } + + // System + + pub const ecall = iType(0b1110011, 0b000, .zero, .zero, 0x000); + pub const ebreak = iType(0b1110011, 0b000, .zero, .zero, 0x001); +}; + +// zig fmt: off +pub const RawRegister = enum(u5) { + 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, x31, + + pub fn dwarfLocOp(reg: RawRegister) u8 { + return @enumToInt(reg) + DW.OP.reg0; + } +}; + +pub const Register = enum(u5) { + // 64 bit registers + zero, // zero + ra, // return address. caller saved + sp, // stack pointer. callee saved. + gp, // global pointer + tp, // thread pointer + t0, t1, t2, // temporaries. caller saved. + s0, // s0/fp, callee saved. + s1, // callee saved. + a0, a1, // fn args/return values. caller saved. + a2, a3, a4, a5, a6, a7, // fn args. caller saved. + s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved. + t3, t4, t5, t6, // caller saved + + pub fn parseRegName(name: []const u8) ?Register { + if(std.meta.stringToEnum(Register, name)) |reg| return reg; + if(std.meta.stringToEnum(RawRegister, name)) |rawreg| return @intToEnum(Register, @enumToInt(rawreg)); + return null; + } + + /// Returns the index into `callee_preserved_regs`. + pub fn allocIndex(self: Register) ?u4 { + inline for(callee_preserved_regs) |cpreg, i| { + if(self == cpreg) return i; + } + return null; + } + + pub fn dwarfLocOp(reg: Register) u8 { + return @as(u8, @enumToInt(reg)) + DW.OP.reg0; + } +}; + +// zig fmt: on + +pub const callee_preserved_regs = [_]Register{ + .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11, +}; + +test "serialize instructions" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + const testcases = [_]Testcase{ + .{ // add t6, zero, zero + .inst = Instruction.add(.t6, .zero, .zero), + .expected = 0b0000000_00000_00000_000_11111_0110011, + }, + .{ // sd s0, 0x7f(s0) + .inst = Instruction.sd(.s0, 0x7f, .s0), + .expected = 0b0000011_01000_01000_011_11111_0100011, + }, + .{ // bne s0, s1, 0x42 + .inst = Instruction.bne(.s0, .s1, 0x42), + .expected = 0b0_000010_01001_01000_001_0001_0_1100011, + }, + .{ // j 0x1a + .inst = Instruction.jal(.zero, 0x1a), + .expected = 0b0_0000001101_0_00000000_00000_1101111, + }, + .{ // ebreak + .inst = Instruction.ebreak, + .expected = 0b000000000001_00000_000_00000_1110011, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + try testing.expectEqual(case.expected, actual); + } +} diff --git a/src/arch/x86/bits.zig b/src/arch/x86/bits.zig new file mode 100644 index 0000000000..5b981b9ef4 --- /dev/null +++ b/src/arch/x86/bits.zig @@ -0,0 +1,123 @@ +const std = @import("std"); +const DW = std.dwarf; + +// zig fmt: off +pub const Register = enum(u8) { + // 0 through 7, 32-bit registers. id is int value + eax, ecx, edx, ebx, esp, ebp, esi, edi, + + // 8-15, 16-bit registers. id is int value - 8. + ax, cx, dx, bx, sp, bp, si, di, + + // 16-23, 8-bit registers. id is int value - 16. + al, cl, dl, bl, ah, ch, dh, bh, + + /// Returns the bit-width of the register. + pub fn size(self: @This()) u7 { + return switch (@enumToInt(self)) { + 0...7 => 32, + 8...15 => 16, + 16...23 => 8, + else => unreachable, + }; + } + + /// Returns the register's id. This is used in practically every opcode the + /// x86 has. It is embedded in some instructions, such as the `B8 +rd` move + /// instruction, and is used in the R/M byte. + pub fn id(self: @This()) u3 { + return @truncate(u3, @enumToInt(self)); + } + + /// Returns the index into `callee_preserved_regs`. + pub fn allocIndex(self: Register) ?u4 { + return switch (self) { + .eax, .ax, .al => 0, + .ecx, .cx, .cl => 1, + .edx, .dx, .dl => 2, + .esi, .si => 3, + .edi, .di => 4, + else => null, + }; + } + + /// Convert from any register to its 32 bit alias. + pub fn to32(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id())); + } + + /// Convert from any register to its 16 bit alias. + pub fn to16(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id()) + 8); + } + + /// Convert from any register to its 8 bit alias. + pub fn to8(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id()) + 16); + } + + + pub fn dwarfLocOp(reg: Register) u8 { + return switch (reg.to32()) { + .eax => DW.OP.reg0, + .ecx => DW.OP.reg1, + .edx => DW.OP.reg2, + .ebx => DW.OP.reg3, + .esp => DW.OP.reg4, + .ebp => DW.OP.reg5, + .esi => DW.OP.reg6, + .edi => DW.OP.reg7, + else => unreachable, + }; + } +}; + +// zig fmt: on + +pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi }; + +// TODO add these to Register enum and corresponding dwarfLocOp +// // Return Address register. This is stored in `0(%esp, "")` and is not a physical register. +// RA = (8, "RA"), +// +// ST0 = (11, "st0"), +// ST1 = (12, "st1"), +// ST2 = (13, "st2"), +// ST3 = (14, "st3"), +// ST4 = (15, "st4"), +// ST5 = (16, "st5"), +// ST6 = (17, "st6"), +// ST7 = (18, "st7"), +// +// XMM0 = (21, "xmm0"), +// XMM1 = (22, "xmm1"), +// XMM2 = (23, "xmm2"), +// XMM3 = (24, "xmm3"), +// XMM4 = (25, "xmm4"), +// XMM5 = (26, "xmm5"), +// XMM6 = (27, "xmm6"), +// XMM7 = (28, "xmm7"), +// +// MM0 = (29, "mm0"), +// MM1 = (30, "mm1"), +// MM2 = (31, "mm2"), +// MM3 = (32, "mm3"), +// MM4 = (33, "mm4"), +// MM5 = (34, "mm5"), +// MM6 = (35, "mm6"), +// MM7 = (36, "mm7"), +// +// MXCSR = (39, "mxcsr"), +// +// ES = (40, "es"), +// CS = (41, "cs"), +// SS = (42, "ss"), +// DS = (43, "ds"), +// FS = (44, "fs"), +// GS = (45, "gs"), +// +// TR = (48, "tr"), +// LDTR = (49, "ldtr"), +// +// FS_BASE = (93, "fs.base"), +// GS_BASE = (94, "gs.base"), diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig new file mode 100644 index 0000000000..72a7468041 --- /dev/null +++ b/src/arch/x86_64/bits.zig @@ -0,0 +1,716 @@ +const std = @import("std"); +const testing = std.testing; +const mem = std.mem; +const assert = std.debug.assert; +const ArrayList = std.ArrayList; +const Allocator = std.mem.Allocator; +const DW = std.dwarf; + +// zig fmt: off + +/// Definitions of all of the x64 registers. The order is semantically meaningful. +/// The registers are defined such that IDs go in descending order of 64-bit, +/// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen +/// registers. This results in some useful properties: +/// +/// Any 64-bit register can be turned into its 32-bit form by adding 16, and +/// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it +/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit +/// form. +/// +/// If (register & 8) is set, the register is extended. +/// +/// The ID can be easily determined by figuring out what range the register is +/// in, and then subtracting the base. +pub const Register = enum(u8) { + // 0 through 15, 64-bit registers. 8-15 are extended. + // id is just the int value. + rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, + r8, r9, r10, r11, r12, r13, r14, r15, + + // 16 through 31, 32-bit registers. 24-31 are extended. + // id is int value - 16. + eax, ecx, edx, ebx, esp, ebp, esi, edi, + r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d, + + // 32-47, 16-bit registers. 40-47 are extended. + // id is int value - 32. + ax, cx, dx, bx, sp, bp, si, di, + r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w, + + // 48-63, 8-bit registers. 56-63 are extended. + // id is int value - 48. + al, cl, dl, bl, ah, ch, dh, bh, + r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b, + + /// Returns the bit-width of the register. + pub fn size(self: Register) u7 { + return switch (@enumToInt(self)) { + 0...15 => 64, + 16...31 => 32, + 32...47 => 16, + 48...64 => 8, + else => unreachable, + }; + } + + /// Returns whether the register is *extended*. Extended registers are the + /// new registers added with amd64, r8 through r15. This also includes any + /// other variant of access to those registers, such as r8b, r15d, and so + /// on. This is needed because access to these registers requires special + /// handling via the REX prefix, via the B or R bits, depending on context. + pub fn isExtended(self: Register) bool { + return @enumToInt(self) & 0x08 != 0; + } + + /// This returns the 4-bit register ID, which is used in practically every + /// opcode. Note that bit 3 (the highest bit) is *never* used directly in + /// an instruction (@see isExtended), and requires special handling. The + /// lower three bits are often embedded directly in instructions (such as + /// the B8 variant of moves), or used in R/M bytes. + pub fn id(self: Register) u4 { + return @truncate(u4, @enumToInt(self)); + } + + /// Like id, but only returns the lower 3 bits. + pub fn low_id(self: Register) u3 { + return @truncate(u3, @enumToInt(self)); + } + + /// Returns the index into `callee_preserved_regs`. + pub fn allocIndex(self: Register) ?u4 { + return switch (self) { + .rax, .eax, .ax, .al => 0, + .rcx, .ecx, .cx, .cl => 1, + .rdx, .edx, .dx, .dl => 2, + .rsi, .esi, .si => 3, + .rdi, .edi, .di => 4, + .r8, .r8d, .r8w, .r8b => 5, + .r9, .r9d, .r9w, .r9b => 6, + .r10, .r10d, .r10w, .r10b => 7, + .r11, .r11d, .r11w, .r11b => 8, + else => null, + }; + } + + /// 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(u8, self.id()) + 16); + } + + /// Convert from any register to its 16 bit alias. + pub fn to16(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id()) + 32); + } + + /// Convert from any register to its 8 bit alias. + pub fn to8(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id()) + 48); + } + + pub fn dwarfLocOp(self: Register) u8 { + return switch (self.to64()) { + .rax => DW.OP.reg0, + .rdx => DW.OP.reg1, + .rcx => DW.OP.reg2, + .rbx => DW.OP.reg3, + .rsi => DW.OP.reg4, + .rdi => DW.OP.reg5, + .rbp => DW.OP.reg6, + .rsp => DW.OP.reg7, + + .r8 => DW.OP.reg8, + .r9 => DW.OP.reg9, + .r10 => DW.OP.reg10, + .r11 => DW.OP.reg11, + .r12 => DW.OP.reg12, + .r13 => DW.OP.reg13, + .r14 => DW.OP.reg14, + .r15 => DW.OP.reg15, + + else => unreachable, + }; + } +}; + +// zig fmt: on + +/// These registers belong to the called function. +pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; +pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; +pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx }; + +/// Encoding helper functions for x86_64 instructions +/// +/// Many of these helpers do very little, but they can help make things +/// slightly more readable with more descriptive field names / function names. +/// +/// Some of them also have asserts to ensure that we aren't doing dumb things. +/// For example, trying to use register 4 (esp) in an indirect modr/m byte is illegal, +/// you need to encode it with an SIB byte. +/// +/// Note that ALL of these helper functions will assume capacity, +/// so ensure that the `code` has sufficient capacity before using them. +/// The `init` method is the recommended way to ensure capacity. +pub const Encoder = struct { + /// Non-owning reference to the code array + code: *ArrayList(u8), + + const Self = @This(); + + /// Wrap `code` in Encoder to make it easier to call these helper functions + /// + /// maximum_inst_size should contain the maximum number of bytes + /// that the encoded instruction will take. + /// This is because the helper functions will assume capacity + /// in order to avoid bounds checking. + pub fn init(code: *ArrayList(u8), maximum_inst_size: u8) !Self { + try code.ensureUnusedCapacity(maximum_inst_size); + return Self{ .code = code }; + } + + /// Directly write a number to the code array with big endianness + pub fn writeIntBig(self: Self, comptime T: type, value: T) void { + mem.writeIntBig( + T, + self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), + value, + ); + } + + /// Directly write a number to the code array with little endianness + pub fn writeIntLittle(self: Self, comptime T: type, value: T) void { + mem.writeIntLittle( + T, + self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), + value, + ); + } + + // -------- + // Prefixes + // -------- + + pub const LegacyPrefixes = packed struct { + /// LOCK + prefix_f0: bool = false, + /// REPNZ, REPNE, REP, Scalar Double-precision + prefix_f2: bool = false, + /// REPZ, REPE, REP, Scalar Single-precision + prefix_f3: bool = false, + + /// CS segment override or Branch not taken + prefix_2e: bool = false, + /// DS segment override + prefix_36: bool = false, + /// ES segment override + prefix_26: bool = false, + /// FS segment override + prefix_64: bool = false, + /// GS segment override + prefix_65: bool = false, + + /// Branch taken + prefix_3e: bool = false, + + /// Operand size override (enables 16 bit operation) + prefix_66: bool = false, + + /// Address size override (enables 16 bit address size) + prefix_67: bool = false, + + padding: u5 = 0, + }; + + /// Encodes legacy prefixes + pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) void { + if (@bitCast(u16, prefixes) != 0) { + // Hopefully this path isn't taken very often, so we'll do it the slow way for now + + // LOCK + if (prefixes.prefix_f0) self.code.appendAssumeCapacity(0xf0); + // REPNZ, REPNE, REP, Scalar Double-precision + if (prefixes.prefix_f2) self.code.appendAssumeCapacity(0xf2); + // REPZ, REPE, REP, Scalar Single-precision + if (prefixes.prefix_f3) self.code.appendAssumeCapacity(0xf3); + + // CS segment override or Branch not taken + if (prefixes.prefix_2e) self.code.appendAssumeCapacity(0x2e); + // DS segment override + if (prefixes.prefix_36) self.code.appendAssumeCapacity(0x36); + // ES segment override + if (prefixes.prefix_26) self.code.appendAssumeCapacity(0x26); + // FS segment override + if (prefixes.prefix_64) self.code.appendAssumeCapacity(0x64); + // GS segment override + if (prefixes.prefix_65) self.code.appendAssumeCapacity(0x65); + + // Branch taken + if (prefixes.prefix_3e) self.code.appendAssumeCapacity(0x3e); + + // Operand size override + if (prefixes.prefix_66) self.code.appendAssumeCapacity(0x66); + + // Address size override + if (prefixes.prefix_67) self.code.appendAssumeCapacity(0x67); + } + } + + /// Use 16 bit operand size + /// + /// Note that this flag is overridden by REX.W, if both are present. + pub fn prefix16BitMode(self: Self) void { + self.code.appendAssumeCapacity(0x66); + } + + /// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB + pub const Rex = struct { + /// Wide, enables 64-bit operation + w: bool = false, + /// Extends the reg field in the ModR/M byte + r: bool = false, + /// Extends the index field in the SIB byte + x: bool = false, + /// Extends the r/m field in the ModR/M byte, + /// or the base field in the SIB byte, + /// or the reg field in the Opcode byte + b: bool = false, + }; + + /// Encodes a REX prefix byte given all the fields + /// + /// Use this byte whenever you need 64 bit operation, + /// or one of reg, index, r/m, base, or opcode-reg might be extended. + /// + /// See struct `Rex` for a description of each field. + /// + /// Does not add a prefix byte if none of the fields are set! + pub fn rex(self: Self, byte: Rex) void { + var value: u8 = 0b0100_0000; + + if (byte.w) value |= 0b1000; + if (byte.r) value |= 0b0100; + if (byte.x) value |= 0b0010; + if (byte.b) value |= 0b0001; + + if (value != 0b0100_0000) { + self.code.appendAssumeCapacity(value); + } + } + + // ------ + // Opcode + // ------ + + /// Encodes a 1 byte opcode + pub fn opcode_1byte(self: Self, opcode: u8) void { + self.code.appendAssumeCapacity(opcode); + } + + /// Encodes a 2 byte opcode + /// + /// e.g. IMUL has the opcode 0x0f 0xaf, so you use + /// + /// encoder.opcode_2byte(0x0f, 0xaf); + pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) void { + self.code.appendAssumeCapacity(prefix); + self.code.appendAssumeCapacity(opcode); + } + + /// Encodes a 1 byte opcode with a reg field + /// + /// Remember to add a REX prefix byte if reg is extended! + pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) void { + assert(opcode & 0b111 == 0); + self.code.appendAssumeCapacity(opcode | reg); + } + + // ------ + // ModR/M + // ------ + + /// Construct a ModR/M byte given all the fields + /// + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) void { + self.code.appendAssumeCapacity( + @as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm, + ); + } + + /// Construct a ModR/M byte using direct r/m addressing + /// r/m effective address: r/m + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) void { + self.modRm(0b11, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect r/m addressing + /// r/m effective address: [r/m] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) void { + assert(rm != 4 and rm != 5); + self.modRm(0b00, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB addressing + /// r/m effective address: [SIB] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) void { + self.modRm(0b00, reg_or_opx, 0b100); + } + + /// Construct a ModR/M byte using RIP-relative addressing + /// r/m effective address: [RIP + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) void { + self.modRm(0b00, reg_or_opx, 0b101); + } + + /// Construct a ModR/M byte using indirect r/m with a 8bit displacement + /// r/m effective address: [r/m + disp8] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) void { + assert(rm != 4); + self.modRm(0b01, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB with a 8bit displacement + /// r/m effective address: [SIB + disp8] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) void { + self.modRm(0b01, reg_or_opx, 0b100); + } + + /// Construct a ModR/M byte using indirect r/m with a 32bit displacement + /// r/m effective address: [r/m + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) void { + assert(rm != 4); + self.modRm(0b10, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB with a 32bit displacement + /// r/m effective address: [SIB + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) void { + self.modRm(0b10, reg_or_opx, 0b100); + } + + // --- + // SIB + // --- + + /// Construct a SIB byte given all the fields + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib(self: Self, scale: u2, index: u3, base: u3) void { + self.code.appendAssumeCapacity( + @as(u8, scale) << 6 | @as(u8, index) << 3 | base, + ); + } + + /// Construct a SIB byte with scale * index + base, no frills. + /// r/m effective address: [base + scale * index] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) void { + assert(base != 5); + + self.sib(scale, index, base); + } + + /// Construct a SIB byte with scale * index + disp32 + /// r/m effective address: [scale * index + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) void { + assert(index != 4); + + // scale is actually ignored + // index = 4 means no index + // base = 5 means no base, if mod == 0. + self.sib(scale, index, 5); + } + + /// Construct a SIB byte with just base + /// r/m effective address: [base] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_base(self: Self, base: u3) void { + assert(base != 5); + + // scale is actually ignored + // index = 4 means no index + self.sib(0, 4, base); + } + + /// Construct a SIB byte with just disp32 + /// r/m effective address: [disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_disp32(self: Self) void { + // scale is actually ignored + // index = 4 means no index + // base = 5 means no base, if mod == 0. + self.sib(0, 4, 5); + } + + /// Construct a SIB byte with scale * index + base + disp8 + /// r/m effective address: [base + scale * index + disp8] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) void { + self.sib(scale, index, base); + } + + /// Construct a SIB byte with base + disp8, no index + /// r/m effective address: [base + disp8] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_baseDisp8(self: Self, base: u3) void { + // scale is ignored + // index = 4 means no index + self.sib(0, 4, base); + } + + /// Construct a SIB byte with scale * index + base + disp32 + /// r/m effective address: [base + scale * index + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) void { + self.sib(scale, index, base); + } + + /// Construct a SIB byte with base + disp32, no index + /// r/m effective address: [base + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_baseDisp32(self: Self, base: u3) void { + // scale is ignored + // index = 4 means no index + self.sib(0, 4, base); + } + + // ------------------------- + // Trivial (no bit fiddling) + // ------------------------- + + /// Encode an 8 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm8(self: Self, imm: i8) void { + self.code.appendAssumeCapacity(@bitCast(u8, imm)); + } + + /// Encode an 8 bit displacement + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn disp8(self: Self, disp: i8) void { + self.code.appendAssumeCapacity(@bitCast(u8, disp)); + } + + /// Encode an 16 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm16(self: Self, imm: i16) void { + self.writeIntLittle(i16, imm); + } + + /// Encode an 32 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm32(self: Self, imm: i32) void { + self.writeIntLittle(i32, imm); + } + + /// Encode an 32 bit displacement + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn disp32(self: Self, disp: i32) void { + self.writeIntLittle(i32, disp); + } + + /// Encode an 64 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm64(self: Self, imm: u64) void { + self.writeIntLittle(u64, imm); + } +}; + +test "x86_64 Encoder helpers" { + var code = ArrayList(u8).init(testing.allocator); + defer code.deinit(); + + // simple integer multiplication + + // imul eax,edi + // 0faf c7 + { + try code.resize(0); + const encoder = try Encoder.init(&code, 4); + encoder.rex(.{ + .r = Register.eax.isExtended(), + .b = Register.edi.isExtended(), + }); + encoder.opcode_2byte(0x0f, 0xaf); + encoder.modRm_direct( + Register.eax.low_id(), + Register.edi.low_id(), + ); + + try testing.expectEqualSlices(u8, &[_]u8{ 0x0f, 0xaf, 0xc7 }, code.items); + } + + // simple mov + + // mov eax,edi + // 89 f8 + { + try code.resize(0); + const encoder = try Encoder.init(&code, 3); + encoder.rex(.{ + .r = Register.edi.isExtended(), + .b = Register.eax.isExtended(), + }); + encoder.opcode_1byte(0x89); + encoder.modRm_direct( + Register.edi.low_id(), + Register.eax.low_id(), + ); + + try testing.expectEqualSlices(u8, &[_]u8{ 0x89, 0xf8 }, code.items); + } + + // signed integer addition of 32-bit sign extended immediate to 64 bit register + + // add rcx, 2147483647 + // + // Using the following opcode: REX.W + 81 /0 id, we expect the following encoding + // + // 48 : REX.W set for 64 bit operand (*r*cx) + // 81 : opcode for " with immediate" + // c1 : id = rcx, + // : c1 = 11 <-- mod = 11 indicates r/m is register (rcx) + // : 000 <-- opcode_extension = 0 because opcode extension is /0. /0 specifies ADD + // : 001 <-- 001 is rcx + // ffffff7f : 2147483647 + { + try code.resize(0); + const encoder = try Encoder.init(&code, 7); + encoder.rex(.{ .w = true }); // use 64 bit operation + encoder.opcode_1byte(0x81); + encoder.modRm_direct( + 0, + Register.rcx.low_id(), + ); + encoder.imm32(2147483647); + + try testing.expectEqualSlices(u8, &[_]u8{ 0x48, 0x81, 0xc1, 0xff, 0xff, 0xff, 0x7f }, code.items); + } +} + +// TODO add these registers to the enum and populate dwarfLocOp +// // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register. +// RA = (16, "RA"), +// +// XMM0 = (17, "xmm0"), +// XMM1 = (18, "xmm1"), +// XMM2 = (19, "xmm2"), +// XMM3 = (20, "xmm3"), +// XMM4 = (21, "xmm4"), +// XMM5 = (22, "xmm5"), +// XMM6 = (23, "xmm6"), +// XMM7 = (24, "xmm7"), +// +// XMM8 = (25, "xmm8"), +// XMM9 = (26, "xmm9"), +// XMM10 = (27, "xmm10"), +// XMM11 = (28, "xmm11"), +// XMM12 = (29, "xmm12"), +// XMM13 = (30, "xmm13"), +// XMM14 = (31, "xmm14"), +// XMM15 = (32, "xmm15"), +// +// ST0 = (33, "st0"), +// ST1 = (34, "st1"), +// ST2 = (35, "st2"), +// ST3 = (36, "st3"), +// ST4 = (37, "st4"), +// ST5 = (38, "st5"), +// ST6 = (39, "st6"), +// ST7 = (40, "st7"), +// +// MM0 = (41, "mm0"), +// MM1 = (42, "mm1"), +// MM2 = (43, "mm2"), +// MM3 = (44, "mm3"), +// MM4 = (45, "mm4"), +// MM5 = (46, "mm5"), +// MM6 = (47, "mm6"), +// MM7 = (48, "mm7"), +// +// RFLAGS = (49, "rFLAGS"), +// ES = (50, "es"), +// CS = (51, "cs"), +// SS = (52, "ss"), +// DS = (53, "ds"), +// FS = (54, "fs"), +// GS = (55, "gs"), +// +// FS_BASE = (58, "fs.base"), +// GS_BASE = (59, "gs.base"), +// +// TR = (62, "tr"), +// LDTR = (63, "ldtr"), +// MXCSR = (64, "mxcsr"), +// FCW = (65, "fcw"), +// FSW = (66, "fsw"), +// +// XMM16 = (67, "xmm16"), +// XMM17 = (68, "xmm17"), +// XMM18 = (69, "xmm18"), +// XMM19 = (70, "xmm19"), +// XMM20 = (71, "xmm20"), +// XMM21 = (72, "xmm21"), +// XMM22 = (73, "xmm22"), +// XMM23 = (74, "xmm23"), +// XMM24 = (75, "xmm24"), +// XMM25 = (76, "xmm25"), +// XMM26 = (77, "xmm26"), +// XMM27 = (78, "xmm27"), +// XMM28 = (79, "xmm28"), +// XMM29 = (80, "xmm29"), +// XMM30 = (81, "xmm30"), +// XMM31 = (82, "xmm31"), +// +// K0 = (118, "k0"), +// K1 = (119, "k1"), +// K2 = (120, "k2"), +// K3 = (121, "k3"), +// K4 = (122, "k4"), +// K5 = (123, "k5"), +// K6 = (124, "k6"), +// K7 = (125, "k7"), diff --git a/src/codegen.zig b/src/codegen.zig index 56580f91e1..f812cbc5d4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -21,7 +21,7 @@ const log = std.log.scoped(.codegen); const build_options = @import("build_options"); const RegisterManager = @import("register_manager.zig").RegisterManager; -const X8664Encoder = @import("codegen/x86_64.zig").Encoder; +const X8664Encoder = @import("arch/x86_64/bits.zig").Encoder; pub const FnResult = union(enum) { /// The `code` parameter passed to `generateSymbol` has the value appended. @@ -470,7 +470,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// A branch in the ARM instruction set arm_branch: struct { pos: usize, - cond: @import("codegen/arm.zig").Condition, + cond: @import("arch/arm/bits.zig").Condition, }, }; @@ -5336,11 +5336,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } const Register = switch (arch) { - .i386 => @import("codegen/x86.zig").Register, - .x86_64 => @import("codegen/x86_64.zig").Register, - .riscv64 => @import("codegen/riscv64.zig").Register, - .arm, .armeb => @import("codegen/arm.zig").Register, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").Register, + .i386 => @import("arch/x86/bits.zig").Register, + .x86_64 => @import("arch/x86_64/bits.zig").Register, + .riscv64 => @import("arch/riscv64/bits.zig").Register, + .arm, .armeb => @import("arch/arm/bits.zig").Register, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").Register, else => enum { dummy, @@ -5352,39 +5352,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }; const Instruction = switch (arch) { - .riscv64 => @import("codegen/riscv64.zig").Instruction, - .arm, .armeb => @import("codegen/arm.zig").Instruction, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").Instruction, + .riscv64 => @import("arch/riscv64/bits.zig").Instruction, + .arm, .armeb => @import("arch/arm/bits.zig").Instruction, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").Instruction, else => void, }; const Condition = switch (arch) { - .arm, .armeb => @import("codegen/arm.zig").Condition, + .arm, .armeb => @import("arch/arm/bits.zig").Condition, else => void, }; const callee_preserved_regs = switch (arch) { - .i386 => @import("codegen/x86.zig").callee_preserved_regs, - .x86_64 => @import("codegen/x86_64.zig").callee_preserved_regs, - .riscv64 => @import("codegen/riscv64.zig").callee_preserved_regs, - .arm, .armeb => @import("codegen/arm.zig").callee_preserved_regs, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").callee_preserved_regs, + .i386 => @import("arch/x86/bits.zig").callee_preserved_regs, + .x86_64 => @import("arch/x86_64/bits.zig").callee_preserved_regs, + .riscv64 => @import("arch/riscv64/bits.zig").callee_preserved_regs, + .arm, .armeb => @import("arch/arm/bits.zig").callee_preserved_regs, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").callee_preserved_regs, else => [_]Register{}, }; const c_abi_int_param_regs = switch (arch) { - .i386 => @import("codegen/x86.zig").c_abi_int_param_regs, - .x86_64 => @import("codegen/x86_64.zig").c_abi_int_param_regs, - .arm, .armeb => @import("codegen/arm.zig").c_abi_int_param_regs, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").c_abi_int_param_regs, + .i386 => @import("arch/x86/bits.zig").c_abi_int_param_regs, + .x86_64 => @import("arch/x86_64/bits.zig").c_abi_int_param_regs, + .arm, .armeb => @import("arch/arm/bits.zig").c_abi_int_param_regs, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").c_abi_int_param_regs, else => [_]Register{}, }; const c_abi_int_return_regs = switch (arch) { - .i386 => @import("codegen/x86.zig").c_abi_int_return_regs, - .x86_64 => @import("codegen/x86_64.zig").c_abi_int_return_regs, - .arm, .armeb => @import("codegen/arm.zig").c_abi_int_return_regs, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").c_abi_int_return_regs, + .i386 => @import("arch/x86/bits.zig").c_abi_int_return_regs, + .x86_64 => @import("arch/x86_64/bits.zig").c_abi_int_return_regs, + .arm, .armeb => @import("arch/arm/bits.zig").c_abi_int_return_regs, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").c_abi_int_return_regs, else => [_]Register{}, }; diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig deleted file mode 100644 index dfda04da85..0000000000 --- a/src/codegen/aarch64.zig +++ /dev/null @@ -1,1230 +0,0 @@ -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, .x2, .x3, .x4, .x5, .x6, .x7 }; - -test "Register.id" { - try testing.expectEqual(@as(u5, 0), Register.x0.id()); - try testing.expectEqual(@as(u5, 0), Register.w0.id()); - - try testing.expectEqual(@as(u5, 31), Register.xzr.id()); - try testing.expectEqual(@as(u5, 31), Register.wzr.id()); - - try testing.expectEqual(@as(u5, 31), Register.sp.id()); - try testing.expectEqual(@as(u5, 31), Register.sp.id()); -} - -test "Register.size" { - try testing.expectEqual(@as(u7, 64), Register.x19.size()); - try testing.expectEqual(@as(u7, 32), Register.w3.size()); -} - -test "Register.to64/to32" { - try testing.expectEqual(Register.x0, Register.w0.to64()); - try testing.expectEqual(Register.x0, Register.x0.to64()); - - try testing.expectEqual(Register.w3, Register.w3.to32()); - try 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" { - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.b0.id()); - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.h0.id()); - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.s0.id()); - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.d0.id()); - try testing.expectEqual(@as(u5, 0), FloatingPointRegister.q0.id()); - - try testing.expectEqual(@as(u5, 2), FloatingPointRegister.q2.id()); - try testing.expectEqual(@as(u5, 31), FloatingPointRegister.d31.id()); -} - -test "FloatingPointRegister.size" { - try testing.expectEqual(@as(u8, 128), FloatingPointRegister.q1.size()); - try testing.expectEqual(@as(u8, 64), FloatingPointRegister.d2.size()); - try testing.expectEqual(@as(u8, 32), FloatingPointRegister.s3.size()); - try testing.expectEqual(@as(u8, 16), FloatingPointRegister.h4.size()); - try testing.expectEqual(@as(u8, 8), FloatingPointRegister.b5.size()); -} - -test "FloatingPointRegister.toX" { - try testing.expectEqual(FloatingPointRegister.q1, FloatingPointRegister.q1.to128()); - try testing.expectEqual(FloatingPointRegister.q2, FloatingPointRegister.b2.to128()); - try testing.expectEqual(FloatingPointRegister.q3, FloatingPointRegister.h3.to128()); - - try testing.expectEqual(FloatingPointRegister.d0, FloatingPointRegister.q0.to64()); - try testing.expectEqual(FloatingPointRegister.s1, FloatingPointRegister.d1.to32()); - try testing.expectEqual(FloatingPointRegister.h2, FloatingPointRegister.s2.to16()); - try testing.expectEqual(FloatingPointRegister.b3, FloatingPointRegister.h3.to8()); -} - -/// Represents an instruction in the AArch64 instruction set -pub const Instruction = union(enum) { - move_wide_immediate: packed struct { - rd: u5, - imm16: u16, - hw: u2, - fixed: u6 = 0b100101, - opc: u2, - sf: u1, - }, - pc_relative_address: packed struct { - rd: u5, - immhi: u19, - fixed: u5 = 0b10000, - immlo: u2, - op: u1, - }, - load_store_register: packed struct { - rt: u5, - rn: u5, - offset: u12, - opc: u2, - op1: u2, - v: u1, - fixed: u3 = 0b111, - size: u2, - }, - load_store_register_pair: packed struct { - rt1: u5, - rn: u5, - rt2: u5, - imm7: u7, - load: u1, - encoding: u2, - fixed: u5 = 0b101_0_0, - opc: u2, - }, - load_literal: packed struct { - rt: u5, - imm19: u19, - fixed: u6 = 0b011_0_00, - opc: u2, - }, - exception_generation: packed struct { - ll: u2, - op2: u3, - imm16: u16, - opc: u3, - fixed: u8 = 0b1101_0100, - }, - unconditional_branch_register: packed struct { - op4: u5, - rn: u5, - op3: u6, - op2: u5, - opc: u4, - fixed: u7 = 0b1101_011, - }, - unconditional_branch_immediate: packed struct { - imm26: u26, - fixed: u5 = 0b00101, - op: u1, - }, - no_operation: packed struct { - fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111, - }, - logical_shifted_register: packed struct { - rd: u5, - rn: u5, - imm6: u6, - rm: u5, - n: u1, - shift: u2, - fixed: u5 = 0b01010, - opc: u2, - sf: u1, - }, - add_subtract_immediate: packed struct { - rd: u5, - rn: u5, - imm12: u12, - sh: u1, - fixed: u6 = 0b100010, - s: u1, - op: u1, - sf: u1, - }, - conditional_branch: struct { - cond: u4, - o0: u1, - imm19: u19, - o1: u1, - fixed: u7 = 0b0101010, - }, - compare_and_branch: struct { - rt: u5, - imm19: u19, - op: u1, - fixed: u6 = 0b011010, - sf: u1, - }, - - pub const Shift = struct { - shift: Type = .lsl, - amount: u6 = 0, - - pub const Type = enum(u2) { - lsl, - lsr, - asr, - ror, - }; - - pub const none = Shift{ - .shift = .lsl, - .amount = 0, - }; - }; - - pub const Condition = enum(u4) { - /// Integer: Equal - /// Floating point: Equal - eq, - /// Integer: Not equal - /// Floating point: Not equal or unordered - ne, - /// Integer: Carry set - /// Floating point: Greater than, equal, or unordered - cs, - /// Integer: Carry clear - /// Floating point: Less than - cc, - /// Integer: Minus, negative - /// Floating point: Less than - mi, - /// Integer: Plus, positive or zero - /// Floating point: Greater than, equal, or unordered - pl, - /// Integer: Overflow - /// Floating point: Unordered - vs, - /// Integer: No overflow - /// Floating point: Ordered - vc, - /// Integer: Unsigned higher - /// Floating point: Greater than, or unordered - hi, - /// Integer: Unsigned lower or same - /// Floating point: Less than or equal - ls, - /// Integer: Signed greater than or equal - /// Floating point: Greater than or equal - ge, - /// Integer: Signed less than - /// Floating point: Less than, or unordered - lt, - /// Integer: Signed greater than - /// Floating point: Greater than - gt, - /// Integer: Signed less than or equal - /// Floating point: Less than, equal, or unordered - le, - /// Integer: Always - /// Floating point: Always - al, - /// Integer: Always - /// Floating point: Always - nv, - }; - - pub fn toU32(self: Instruction) u32 { - return switch (self) { - .move_wide_immediate => |v| @bitCast(u32, v), - .pc_relative_address => |v| @bitCast(u32, v), - .load_store_register => |v| @bitCast(u32, v), - .load_store_register_pair => |v| @bitCast(u32, v), - .load_literal => |v| @bitCast(u32, v), - .exception_generation => |v| @bitCast(u32, v), - .unconditional_branch_register => |v| @bitCast(u32, v), - .unconditional_branch_immediate => |v| @bitCast(u32, v), - .no_operation => |v| @bitCast(u32, v), - .logical_shifted_register => |v| @bitCast(u32, v), - .add_subtract_immediate => |v| @bitCast(u32, v), - // TODO once packed structs work, this can be refactored - .conditional_branch => |v| @as(u32, v.cond) | (@as(u32, v.o0) << 4) | (@as(u32, v.imm19) << 5) | (@as(u32, v.o1) << 24) | (@as(u32, v.fixed) << 25), - .compare_and_branch => |v| @as(u32, v.rt) | (@as(u32, v.imm19) << 5) | (@as(u32, v.op) << 24) | (@as(u32, v.fixed) << 25) | (@as(u32, v.sf) << 31), - }; - } - - fn moveWideImmediate( - opc: u2, - rd: Register, - imm16: u16, - shift: u6, - ) Instruction { - switch (rd.size()) { - 32 => { - assert(shift % 16 == 0 and shift <= 16); - return Instruction{ - .move_wide_immediate = .{ - .rd = rd.id(), - .imm16 = imm16, - .hw = @intCast(u2, shift / 16), - .opc = opc, - .sf = 0, - }, - }; - }, - 64 => { - assert(shift % 16 == 0 and shift <= 48); - return Instruction{ - .move_wide_immediate = .{ - .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{ - .pc_relative_address = .{ - .rd = rd.id(), - .immlo = @truncate(u2, imm21_u), - .immhi = @truncate(u19, imm21_u >> 2), - .op = op, - }, - }; - } - - /// 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 LoadStoreOffset = 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 = LoadStoreOffset{ - .Immediate = .{ .Unsigned = 0 }, - }; - - pub fn toU12(self: LoadStoreOffset) 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) LoadStoreOffset { - return .{ - .Immediate = .{ .Unsigned = offset }, - }; - } - - pub fn imm_post_index(offset: i9) LoadStoreOffset { - return .{ - .Immediate = .{ .PostIndex = offset }, - }; - } - - pub fn imm_pre_index(offset: i9) LoadStoreOffset { - return .{ - .Immediate = .{ .PreIndex = offset }, - }; - } - - pub fn reg(rm: Register) LoadStoreOffset { - return .{ - .Register = .{ - .rm = rm.id(), - .shift = .{ - .Lsl = 0, - }, - }, - }; - } - - pub fn reg_uxtw(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 32 and (shift == 0 or shift == 2)); - return .{ - .Register = .{ - .rm = rm.id(), - .shift = .{ - .Uxtw = shift, - }, - }, - }; - } - - pub fn reg_lsl(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 64 and (shift == 0 or shift == 3)); - return .{ - .Register = .{ - .rm = rm.id(), - .shift = .{ - .Lsl = shift, - }, - }, - }; - } - - pub fn reg_sxtw(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 32 and (shift == 0 or shift == 2)); - return .{ - .Register = .{ - .rm = rm.id(), - .shift = .{ - .Sxtw = shift, - }, - }, - }; - } - - pub fn reg_sxtx(rm: Register, shift: u2) LoadStoreOffset { - assert(rm.size() == 64 and (shift == 0 or shift == 3)); - return .{ - .Register = .{ - .rm = rm.id(), - .shift = .{ - .Sxtx = shift, - }, - }, - }; - } - }; - - /// Which kind of load/store to perform - const LoadStoreVariant = enum { - /// 32-bit or 64-bit - str, - /// 16-bit, zero-extended - strh, - /// 8-bit, zero-extended - strb, - /// 32-bit or 64-bit - ldr, - /// 16-bit, zero-extended - ldrh, - /// 8-bit, zero-extended - ldrb, - }; - - fn loadStoreRegister( - rt: Register, - rn: Register, - offset: LoadStoreOffset, - variant: LoadStoreVariant, - ) 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 = switch (variant) { - .ldr, .ldrh, .ldrb => 0b01, - .str, .strh, .strb => 0b00, - }; - return Instruction{ - .load_store_register = .{ - .rt = rt.id(), - .rn = rn.id(), - .offset = off, - .opc = opc, - .op1 = op1, - .v = 0, - .size = blk: { - switch (variant) { - .ldr, .str => switch (rt.size()) { - 32 => break :blk 0b10, - 64 => break :blk 0b11, - else => unreachable, // unexpected register size - }, - .ldrh, .strh => break :blk 0b01, - .ldrb, .strb => break :blk 0b00, - } - }, - }, - }; - } - - fn loadStoreRegisterPair( - 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{ - .load_store_register_pair = .{ - .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{ - .load_store_register_pair = .{ - .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{ - .load_literal = .{ - .rt = rt.id(), - .imm19 = imm19, - .opc = 0b00, - }, - }; - }, - 64 => { - return Instruction{ - .load_literal = .{ - .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{ - .exception_generation = .{ - .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{ - .unconditional_branch_register = .{ - .op4 = op4, - .rn = rn.id(), - .op3 = op3, - .op2 = op2, - .opc = opc, - }, - }; - } - - fn unconditionalBranchImmediate( - op: u1, - offset: i28, - ) Instruction { - return Instruction{ - .unconditional_branch_immediate = .{ - .imm26 = @bitCast(u26, @intCast(i26, offset >> 2)), - .op = op, - }, - }; - } - - fn logicalShiftedRegister( - opc: u2, - n: u1, - shift: Shift, - rd: Register, - rn: Register, - rm: Register, - ) Instruction { - switch (rd.size()) { - 32 => { - assert(shift.amount < 32); - return Instruction{ - .logical_shifted_register = .{ - .rd = rd.id(), - .rn = rn.id(), - .imm6 = shift.amount, - .rm = rm.id(), - .n = n, - .shift = @enumToInt(shift.shift), - .opc = opc, - .sf = 0b0, - }, - }; - }, - 64 => { - return Instruction{ - .logical_shifted_register = .{ - .rd = rd.id(), - .rn = rn.id(), - .imm6 = shift.amount, - .rm = rm.id(), - .n = n, - .shift = @enumToInt(shift.shift), - .opc = opc, - .sf = 0b1, - }, - }; - }, - else => unreachable, // unexpected register size - } - } - - fn addSubtractImmediate( - op: u1, - s: u1, - rd: Register, - rn: Register, - imm12: u12, - shift: bool, - ) Instruction { - return Instruction{ - .add_subtract_immediate = .{ - .rd = rd.id(), - .rn = rn.id(), - .imm12 = imm12, - .sh = @boolToInt(shift), - .s = s, - .op = op, - .sf = switch (rd.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - fn conditionalBranch( - o0: u1, - o1: u1, - cond: Condition, - offset: i21, - ) Instruction { - assert(offset & 0b11 == 0b00); - return Instruction{ - .conditional_branch = .{ - .cond = @enumToInt(cond), - .o0 = o0, - .imm19 = @bitCast(u19, @intCast(i19, offset >> 2)), - .o1 = o1, - }, - }; - } - - fn compareAndBranch( - op: u1, - rt: Register, - offset: i21, - ) Instruction { - assert(offset & 0b11 == 0b00); - return Instruction{ - .compare_and_branch = .{ - .rt = rt.id(), - .imm19 = @bitCast(u19, @intCast(i19, offset >> 2)), - .op = op, - .sf = switch (rt.size()) { - 32 => 0b0, - 64 => 0b1, - else => unreachable, // unexpected register size - }, - }, - }; - } - - // Helper functions for assembly syntax functions - - // 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 = union(enum) { - register: struct { - rn: Register, - offset: LoadStoreOffset = LoadStoreOffset.none, - }, - literal: u19, - }; - - pub fn ldr(rt: Register, args: LdrArgs) Instruction { - switch (args) { - .register => |info| return loadStoreRegister(rt, info.rn, info.offset, .ldr), - .literal => |literal| return loadLiteral(rt, literal), - } - } - - pub fn ldrh(rt: Register, rn: Register, args: StrArgs) Instruction { - return loadStoreRegister(rt, rn, args.offset, .ldrh); - } - - pub fn ldrb(rt: Register, rn: Register, args: StrArgs) Instruction { - return loadStoreRegister(rt, rn, args.offset, .ldrb); - } - - pub const StrArgs = struct { - offset: LoadStoreOffset = LoadStoreOffset.none, - }; - - pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction { - return loadStoreRegister(rt, rn, args.offset, .str); - } - - pub fn strh(rt: Register, rn: Register, args: StrArgs) Instruction { - return loadStoreRegister(rt, rn, args.offset, .strh); - } - - pub fn strb(rt: Register, rn: Register, args: StrArgs) Instruction { - return loadStoreRegister(rt, rn, args.offset, .strb); - } - - // Load or store pair of registers - - pub const LoadStorePairOffset = struct { - encoding: enum(u2) { - PostIndex = 0b01, - Signed = 0b10, - PreIndex = 0b11, - }, - offset: i9, - - pub fn none() LoadStorePairOffset { - return .{ .encoding = .Signed, .offset = 0 }; - } - - pub fn post_index(imm: i9) LoadStorePairOffset { - return .{ .encoding = .PostIndex, .offset = imm }; - } - - pub fn pre_index(imm: i9) LoadStorePairOffset { - return .{ .encoding = .PreIndex, .offset = imm }; - } - - pub fn signed(imm: i9) LoadStorePairOffset { - return .{ .encoding = .Signed, .offset = imm }; - } - }; - - pub fn ldp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction { - return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), true); - } - - pub fn ldnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction { - return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, true); - } - - pub fn stp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction { - return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), false); - } - - pub fn stnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction { - return loadStoreRegisterPair(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{ .no_operation = .{} }; - } - - // Logical (shifted register) - - pub fn @"and"(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b00, 0b0, shift, rd, rn, rm); - } - - pub fn bic(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b00, 0b1, shift, rd, rn, rm); - } - - pub fn orr(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b01, 0b0, shift, rd, rn, rm); - } - - pub fn orn(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b01, 0b1, shift, rd, rn, rm); - } - - pub fn eor(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b10, 0b0, shift, rd, rn, rm); - } - - pub fn eon(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b10, 0b1, shift, rd, rn, rm); - } - - pub fn ands(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b11, 0b0, shift, rd, rn, rm); - } - - pub fn bics(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { - return logicalShiftedRegister(0b11, 0b1, shift, rd, rn, rm); - } - - // Add/subtract (immediate) - - pub fn add(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b0, 0b0, rd, rn, imm, shift); - } - - pub fn adds(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b0, 0b1, rd, rn, imm, shift); - } - - pub fn sub(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b1, 0b0, rd, rn, imm, shift); - } - - pub fn subs(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { - return addSubtractImmediate(0b1, 0b1, rd, rn, imm, shift); - } - - // Conditional branch - - pub fn bCond(cond: Condition, offset: i21) Instruction { - return conditionalBranch(0b0, 0b0, cond, offset); - } - - // Compare and branch - - pub fn cbz(rt: Register, offset: i21) Instruction { - return compareAndBranch(0b0, rt, offset); - } - - pub fn cbnz(rt: Register, offset: i21) Instruction { - return compareAndBranch(0b1, rt, offset); - } -}; - -test { - testing.refAllDecls(@This()); -} - -test "serialize instructions" { - const Testcase = struct { - inst: Instruction, - expected: u32, - }; - - const testcases = [_]Testcase{ - .{ // orr x0, xzr, x1 - .inst = Instruction.orr(.x0, .xzr, .x1, Instruction.Shift.none), - .expected = 0b1_01_01010_00_0_00001_000000_11111_00000, - }, - .{ // orn x0, xzr, x1 - .inst = Instruction.orn(.x0, .xzr, .x1, Instruction.Shift.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, .{ .register = .{ .rn = .x1 } }), - .expected = 0b11_111_0_01_01_000000000000_00001_00010, - }, - .{ // ldr x2, [x1, #1]! - .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_pre_index(1) } }), - .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010, - }, - .{ // ldr x2, [x1], #-1 - .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_post_index(-1) } }), - .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010, - }, - .{ // ldr x2, [x1], (x3) - .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.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, - }, - .{ // ldrh x7, [x4], #0xaa - .inst = Instruction.ldrh(.x7, .x4, .{ .offset = Instruction.LoadStoreOffset.imm_post_index(0xaa) }), - .expected = 0b01_111_0_00_01_0_010101010_01_00100_00111, - }, - .{ // ldrb x9, [x15, #0xff]! - .inst = Instruction.ldrb(.x9, .x15, .{ .offset = Instruction.LoadStoreOffset.imm_pre_index(0xff) }), - .expected = 0b00_111_0_00_01_0_011111111_11_01111_01001, - }, - .{ // 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.LoadStoreOffset.reg(.x3) }), - .expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010, - }, - .{ // strh w0, [x1] - .inst = Instruction.strh(.w0, .x1, .{}), - .expected = 0b01_111_0_01_00_000000000000_00001_00000, - }, - .{ // strb w8, [x9] - .inst = Instruction.strb(.w8, .x9, .{}), - .expected = 0b00_111_0_01_00_000000000000_01001_01000, - }, - .{ // 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, Instruction.LoadStorePairOffset.signed(8)), - .expected = 0b10_101_0_010_0_0000001_00010_11111_00001, - }, - .{ // ldp x1, x2, [sp, #8] - .inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.signed(8)), - .expected = 0b10_101_0_010_1_0000001_00010_11111_00001, - }, - .{ // stp x1, x2, [sp, #-16]! - .inst = Instruction.stp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.pre_index(-16)), - .expected = 0b10_101_0_011_0_1111110_00010_11111_00001, - }, - .{ // ldp x1, x2, [sp], #16 - .inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.post_index(16)), - .expected = 0b10_101_0_001_1_0000010_00010_11111_00001, - }, - .{ // and x0, x4, x2 - .inst = Instruction.@"and"(.x0, .x4, .x2, .{}), - .expected = 0b1_00_01010_00_0_00010_000000_00100_00000, - }, - .{ // and x0, x4, x2, lsl #0x8 - .inst = Instruction.@"and"(.x0, .x4, .x2, .{ .shift = .lsl, .amount = 0x8 }), - .expected = 0b1_00_01010_00_0_00010_001000_00100_00000, - }, - .{ // add x0, x10, #10 - .inst = Instruction.add(.x0, .x10, 10, false), - .expected = 0b1_0_0_100010_0_0000_0000_1010_01010_00000, - }, - .{ // subs x0, x5, #11, lsl #12 - .inst = Instruction.subs(.x0, .x5, 11, true), - .expected = 0b1_1_1_100010_1_0000_0000_1011_00101_00000, - }, - .{ // b.hi #-4 - .inst = Instruction.bCond(.hi, -4), - .expected = 0b0101010_0_1111111111111111111_0_1000, - }, - .{ // cbz x10, #40 - .inst = Instruction.cbz(.x10, 40), - .expected = 0b1_011010_0_0000000000000001010_01010, - }, - }; - - for (testcases) |case| { - const actual = case.inst.toU32(); - try testing.expectEqual(case.expected, actual); - } -} diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig deleted file mode 100644 index 279ce58005..0000000000 --- a/src/codegen/arm.zig +++ /dev/null @@ -1,1408 +0,0 @@ -const std = @import("std"); -const DW = std.dwarf; -const testing = std.testing; - -/// The condition field specifies the flags necessary for an -/// Instruction to be executed -pub const Condition = enum(u4) { - /// equal - eq, - /// not equal - ne, - /// unsigned higher or same - cs, - /// unsigned lower - cc, - /// negative - mi, - /// positive or zero - pl, - /// overflow - vs, - /// no overflow - vc, - /// unsigned higer - hi, - /// unsigned lower or same - ls, - /// greater or equal - ge, - /// less than - lt, - /// greater than - gt, - /// less than or equal - le, - /// always - al, - - /// Converts a std.math.CompareOperator into a condition flag, - /// i.e. returns the condition that is true iff the result of the - /// comparison is true. Assumes signed comparison - pub fn fromCompareOperatorSigned(op: std.math.CompareOperator) Condition { - return switch (op) { - .gte => .ge, - .gt => .gt, - .neq => .ne, - .lt => .lt, - .lte => .le, - .eq => .eq, - }; - } - - /// Converts a std.math.CompareOperator into a condition flag, - /// i.e. returns the condition that is true iff the result of the - /// comparison is true. Assumes unsigned comparison - pub fn fromCompareOperatorUnsigned(op: std.math.CompareOperator) Condition { - return switch (op) { - .gte => .cs, - .gt => .hi, - .neq => .ne, - .lt => .cc, - .lte => .ls, - .eq => .eq, - }; - } - - /// Returns the condition which is true iff the given condition is - /// false (if such a condition exists) - pub fn negate(cond: Condition) Condition { - return switch (cond) { - .eq => .ne, - .ne => .eq, - .cs => .cc, - .cc => .cs, - .mi => .pl, - .pl => .mi, - .vs => .vc, - .vc => .vs, - .hi => .ls, - .ls => .hi, - .ge => .lt, - .lt => .ge, - .gt => .le, - .le => .gt, - .al => unreachable, - }; - } -}; - -test "condition from CompareOperator" { - try testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorSigned(.eq)); - try testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorUnsigned(.eq)); - - try testing.expectEqual(@as(Condition, .gt), Condition.fromCompareOperatorSigned(.gt)); - try testing.expectEqual(@as(Condition, .hi), Condition.fromCompareOperatorUnsigned(.gt)); - - try testing.expectEqual(@as(Condition, .le), Condition.fromCompareOperatorSigned(.lte)); - try testing.expectEqual(@as(Condition, .ls), Condition.fromCompareOperatorUnsigned(.lte)); -} - -test "negate condition" { - try testing.expectEqual(@as(Condition, .eq), Condition.ne.negate()); - try testing.expectEqual(@as(Condition, .ne), Condition.eq.negate()); -} - -/// Represents a register in the ARM instruction set architecture -pub const Register = enum(u5) { - r0, - r1, - r2, - r3, - r4, - r5, - r6, - r7, - r8, - r9, - r10, - r11, - r12, - r13, - r14, - r15, - - /// Argument / result / scratch register 1 - a1, - /// Argument / result / scratch register 2 - a2, - /// Argument / scratch register 3 - a3, - /// Argument / scratch register 4 - a4, - /// Variable-register 1 - v1, - /// Variable-register 2 - v2, - /// Variable-register 3 - v3, - /// Variable-register 4 - v4, - /// Variable-register 5 - v5, - /// Platform register - v6, - /// Variable-register 7 - v7, - /// Frame pointer or Variable-register 8 - fp, - /// Intra-Procedure-call scratch register - ip, - /// Stack pointer - sp, - /// Link register - lr, - /// Program counter - pc, - - /// Returns the unique 4-bit ID of this register which is used in - /// the machine code - pub fn id(self: Register) u4 { - return @truncate(u4, @enumToInt(self)); - } - - /// 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; - } -}; - -test "Register.id" { - try testing.expectEqual(@as(u4, 15), Register.r15.id()); - try testing.expectEqual(@as(u4, 15), Register.pc.id()); -} - -/// Program status registers containing flags, mode bits and other -/// vital information -pub const Psr = enum { - cpsr, - spsr, -}; - -pub const callee_preserved_regs = [_]Register{ .r4, .r5, .r6, .r7, .r8, .r10 }; -pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 }; -pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 }; - -/// Represents an instruction in the ARM instruction set architecture -pub const Instruction = union(enum) { - data_processing: packed struct { - // Note to self: The order of the fields top-to-bottom is - // right-to-left in the actual 32-bit int representation - op2: u12, - rd: u4, - rn: u4, - s: u1, - opcode: u4, - i: u1, - fixed: u2 = 0b00, - cond: u4, - }, - multiply: packed struct { - rn: u4, - fixed_1: u4 = 0b1001, - rm: u4, - ra: u4, - rd: u4, - set_cond: u1, - accumulate: u1, - fixed_2: u6 = 0b000000, - cond: u4, - }, - multiply_long: packed struct { - rn: u4, - fixed_1: u4 = 0b1001, - rm: u4, - rdlo: u4, - rdhi: u4, - set_cond: u1, - accumulate: u1, - unsigned: u1, - fixed_2: u5 = 0b00001, - cond: u4, - }, - integer_saturating_arithmetic: packed struct { - rm: u4, - fixed_1: u8 = 0b0000_0101, - rd: u4, - rn: u4, - fixed_2: u1 = 0b0, - opc: u2, - fixed_3: u5 = 0b00010, - cond: u4, - }, - single_data_transfer: packed struct { - offset: u12, - rd: u4, - rn: u4, - load_store: u1, - write_back: u1, - byte_word: u1, - up_down: u1, - pre_post: u1, - imm: u1, - fixed: u2 = 0b01, - cond: u4, - }, - extra_load_store: packed struct { - imm4l: u4, - fixed_1: u1 = 0b1, - op2: u2, - fixed_2: u1 = 0b1, - imm4h: u4, - rt: u4, - rn: u4, - o1: u1, - write_back: u1, - imm: u1, - up_down: u1, - pre_index: u1, - fixed_3: u3 = 0b000, - cond: u4, - }, - block_data_transfer: packed struct { - register_list: u16, - rn: u4, - load_store: u1, - write_back: u1, - psr_or_user: u1, - up_down: u1, - pre_post: u1, - fixed: u3 = 0b100, - cond: u4, - }, - branch: packed struct { - offset: u24, - link: u1, - fixed: u3 = 0b101, - cond: u4, - }, - branch_exchange: packed struct { - rn: u4, - fixed_1: u1 = 0b1, - link: u1, - fixed_2: u22 = 0b0001_0010_1111_1111_1111_00, - cond: u4, - }, - supervisor_call: packed struct { - comment: u24, - fixed: u4 = 0b1111, - cond: u4, - }, - breakpoint: packed struct { - imm4: u4, - fixed_1: u4 = 0b0111, - imm12: u12, - fixed_2_and_cond: u12 = 0b1110_0001_0010, - }, - - /// Represents the possible operations which can be performed by a - /// Data Processing instruction - const Opcode = enum(u4) { - // Rd := Op1 AND Op2 - @"and", - // Rd := Op1 EOR Op2 - eor, - // Rd := Op1 - Op2 - sub, - // Rd := Op2 - Op1 - rsb, - // Rd := Op1 + Op2 - add, - // Rd := Op1 + Op2 + C - adc, - // Rd := Op1 - Op2 + C - 1 - sbc, - // Rd := Op2 - Op1 + C - 1 - rsc, - // set condition codes on Op1 AND Op2 - tst, - // set condition codes on Op1 EOR Op2 - teq, - // set condition codes on Op1 - Op2 - cmp, - // set condition codes on Op1 + Op2 - cmn, - // Rd := Op1 OR Op2 - orr, - // Rd := Op2 - mov, - // Rd := Op1 AND NOT Op2 - bic, - // Rd := NOT Op2 - mvn, - }; - - /// Represents the second operand to a data processing instruction - /// which can either be content from a register or an immediate - /// value - pub const Operand = union(enum) { - Register: packed struct { - rm: u4, - shift: u8, - }, - Immediate: packed struct { - imm: u8, - rotate: u4, - }, - - /// Represents multiple ways a register can be shifted. A - /// register can be shifted by a specific immediate value or - /// by the contents of another register - pub const Shift = union(enum) { - Immediate: packed struct { - fixed: u1 = 0b0, - typ: u2, - amount: u5, - }, - Register: packed struct { - fixed_1: u1 = 0b1, - typ: u2, - fixed_2: u1 = 0b0, - rs: u4, - }, - - pub const Type = enum(u2) { - logical_left, - logical_right, - arithmetic_right, - rotate_right, - }; - - pub const none = Shift{ - .Immediate = .{ - .amount = 0, - .typ = 0, - }, - }; - - pub fn toU8(self: Shift) u8 { - return switch (self) { - .Register => |v| @bitCast(u8, v), - .Immediate => |v| @bitCast(u8, v), - }; - } - - pub fn reg(rs: Register, typ: Type) Shift { - return Shift{ - .Register = .{ - .rs = rs.id(), - .typ = @enumToInt(typ), - }, - }; - } - - pub fn imm(amount: u5, typ: Type) Shift { - return Shift{ - .Immediate = .{ - .amount = amount, - .typ = @enumToInt(typ), - }, - }; - } - }; - - pub fn toU12(self: Operand) u12 { - return switch (self) { - .Register => |v| @bitCast(u12, v), - .Immediate => |v| @bitCast(u12, v), - }; - } - - pub fn reg(rm: Register, shift: Shift) Operand { - return Operand{ - .Register = .{ - .rm = rm.id(), - .shift = shift.toU8(), - }, - }; - } - - pub fn imm(immediate: u8, rotate: u4) Operand { - return Operand{ - .Immediate = .{ - .imm = immediate, - .rotate = rotate, - }, - }; - } - - /// Tries to convert an unsigned 32 bit integer into an - /// immediate operand using rotation. Returns null when there - /// is no conversion - pub fn fromU32(x: u32) ?Operand { - const masks = comptime blk: { - const base_mask: u32 = std.math.maxInt(u8); - var result = [_]u32{0} ** 16; - for (result) |*mask, i| mask.* = std.math.rotr(u32, base_mask, 2 * i); - break :blk result; - }; - - return for (masks) |mask, i| { - if (x & mask == x) { - break Operand{ - .Immediate = .{ - .imm = @intCast(u8, std.math.rotl(u32, x, 2 * i)), - .rotate = @intCast(u4, i), - }, - }; - } - } else null; - } - }; - - /// 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: u12, - Register: packed struct { - rm: u4, - shift: u8, - }, - - pub const none = Offset{ - .Immediate = 0, - }; - - pub fn toU12(self: Offset) u12 { - return switch (self) { - .Register => |v| @bitCast(u12, v), - .Immediate => |v| v, - }; - } - - pub fn reg(rm: Register, shift: u8) Offset { - return Offset{ - .Register = .{ - .rm = rm.id(), - .shift = shift, - }, - }; - } - - pub fn imm(immediate: u12) Offset { - return Offset{ - .Immediate = immediate, - }; - } - }; - - /// Represents the offset operand of an extra load or store - /// instruction. - pub const ExtraLoadStoreOffset = union(enum) { - immediate: u8, - register: u4, - - pub const none = ExtraLoadStoreOffset{ - .immediate = 0, - }; - - pub fn reg(register: Register) ExtraLoadStoreOffset { - return ExtraLoadStoreOffset{ - .register = register.id(), - }; - } - - pub fn imm(immediate: u8) ExtraLoadStoreOffset { - return ExtraLoadStoreOffset{ - .immediate = immediate, - }; - } - }; - - /// Represents the register list operand to a block data transfer - /// instruction - pub const RegisterList = packed struct { - r0: bool = false, - r1: bool = false, - r2: bool = false, - r3: bool = false, - r4: bool = false, - r5: bool = false, - r6: bool = false, - r7: bool = false, - r8: bool = false, - r9: bool = false, - r10: bool = false, - r11: bool = false, - r12: bool = false, - r13: bool = false, - r14: bool = false, - r15: bool = false, - }; - - pub fn toU32(self: Instruction) u32 { - return switch (self) { - .data_processing => |v| @bitCast(u32, v), - .multiply => |v| @bitCast(u32, v), - .multiply_long => |v| @bitCast(u32, v), - .integer_saturating_arithmetic => |v| @bitCast(u32, v), - .single_data_transfer => |v| @bitCast(u32, v), - .extra_load_store => |v| @bitCast(u32, v), - .block_data_transfer => |v| @bitCast(u32, v), - .branch => |v| @bitCast(u32, v), - .branch_exchange => |v| @bitCast(u32, v), - .supervisor_call => |v| @bitCast(u32, v), - .breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20), - }; - } - - // Helper functions for the "real" functions below - - fn dataProcessing( - cond: Condition, - opcode: Opcode, - s: u1, - rd: Register, - rn: Register, - op2: Operand, - ) Instruction { - return Instruction{ - .data_processing = .{ - .cond = @enumToInt(cond), - .i = @boolToInt(op2 == .Immediate), - .opcode = @enumToInt(opcode), - .s = s, - .rn = rn.id(), - .rd = rd.id(), - .op2 = op2.toU12(), - }, - }; - } - - fn specialMov( - cond: Condition, - rd: Register, - imm: u16, - top: bool, - ) Instruction { - return Instruction{ - .data_processing = .{ - .cond = @enumToInt(cond), - .i = 1, - .opcode = if (top) 0b1010 else 0b1000, - .s = 0, - .rn = @truncate(u4, imm >> 12), - .rd = rd.id(), - .op2 = @truncate(u12, imm), - }, - }; - } - - fn multiply( - cond: Condition, - set_cond: u1, - rd: Register, - rn: Register, - rm: Register, - ra: ?Register, - ) Instruction { - return Instruction{ - .multiply = .{ - .cond = @enumToInt(cond), - .accumulate = @boolToInt(ra != null), - .set_cond = set_cond, - .rd = rd.id(), - .rn = rn.id(), - .ra = if (ra) |reg| reg.id() else 0b0000, - .rm = rm.id(), - }, - }; - } - - fn multiplyLong( - cond: Condition, - signed: u1, - accumulate: u1, - set_cond: u1, - rdhi: Register, - rdlo: Register, - rm: Register, - rn: Register, - ) Instruction { - return Instruction{ - .multiply_long = .{ - .cond = @enumToInt(cond), - .unsigned = signed, - .accumulate = accumulate, - .set_cond = set_cond, - .rdlo = rdlo.id(), - .rdhi = rdhi.id(), - .rn = rn.id(), - .rm = rm.id(), - }, - }; - } - - fn integerSaturationArithmetic( - cond: Condition, - rd: Register, - rm: Register, - rn: Register, - opc: u2, - ) Instruction { - return Instruction{ - .integer_saturating_arithmetic = .{ - .rm = rm.id(), - .rd = rd.id(), - .rn = rn.id(), - .opc = opc, - .cond = @enumToInt(cond), - }, - }; - } - - fn singleDataTransfer( - cond: Condition, - rd: Register, - rn: Register, - offset: Offset, - pre_index: bool, - positive: bool, - byte_word: u1, - write_back: bool, - load_store: u1, - ) Instruction { - return Instruction{ - .single_data_transfer = .{ - .cond = @enumToInt(cond), - .rn = rn.id(), - .rd = rd.id(), - .offset = offset.toU12(), - .load_store = load_store, - .write_back = @boolToInt(write_back), - .byte_word = byte_word, - .up_down = @boolToInt(positive), - .pre_post = @boolToInt(pre_index), - .imm = @boolToInt(offset != .Immediate), - }, - }; - } - - fn extraLoadStore( - cond: Condition, - pre_index: bool, - positive: bool, - write_back: bool, - o1: u1, - op2: u2, - rn: Register, - rt: Register, - offset: ExtraLoadStoreOffset, - ) Instruction { - const imm4l: u4 = switch (offset) { - .immediate => |imm| @truncate(u4, imm), - .register => |reg| reg, - }; - const imm4h: u4 = switch (offset) { - .immediate => |imm| @truncate(u4, imm >> 4), - .register => 0b0000, - }; - - return Instruction{ - .extra_load_store = .{ - .imm4l = imm4l, - .op2 = op2, - .imm4h = imm4h, - .rt = rt.id(), - .rn = rn.id(), - .o1 = o1, - .write_back = @boolToInt(write_back), - .imm = @boolToInt(offset == .immediate), - .up_down = @boolToInt(positive), - .pre_index = @boolToInt(pre_index), - .cond = @enumToInt(cond), - }, - }; - } - - fn blockDataTransfer( - cond: Condition, - rn: Register, - reg_list: RegisterList, - pre_post: u1, - up_down: u1, - psr_or_user: u1, - write_back: bool, - load_store: u1, - ) Instruction { - return Instruction{ - .block_data_transfer = .{ - .register_list = @bitCast(u16, reg_list), - .rn = rn.id(), - .load_store = load_store, - .write_back = @boolToInt(write_back), - .psr_or_user = psr_or_user, - .up_down = up_down, - .pre_post = pre_post, - .cond = @enumToInt(cond), - }, - }; - } - - fn branch(cond: Condition, offset: i26, link: u1) Instruction { - return Instruction{ - .branch = .{ - .cond = @enumToInt(cond), - .link = link, - .offset = @bitCast(u24, @intCast(i24, offset >> 2)), - }, - }; - } - - fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction { - return Instruction{ - .branch_exchange = .{ - .cond = @enumToInt(cond), - .link = link, - .rn = rn.id(), - }, - }; - } - - fn supervisorCall(cond: Condition, comment: u24) Instruction { - return Instruction{ - .supervisor_call = .{ - .cond = @enumToInt(cond), - .comment = comment, - }, - }; - } - - fn breakpoint(imm: u16) Instruction { - return Instruction{ - .breakpoint = .{ - .imm12 = @truncate(u12, imm >> 4), - .imm4 = @truncate(u4, imm), - }, - }; - } - - // Public functions replicating assembler syntax as closely as - // possible - - // Data processing - - pub fn @"and"(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .@"and", 0, rd, rn, op2); - } - - pub fn ands(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .@"and", 1, rd, rn, op2); - } - - pub fn eor(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .eor, 0, rd, rn, op2); - } - - pub fn eors(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .eor, 1, rd, rn, op2); - } - - pub fn sub(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .sub, 0, rd, rn, op2); - } - - pub fn subs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .sub, 1, rd, rn, op2); - } - - pub fn rsb(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .rsb, 0, rd, rn, op2); - } - - pub fn rsbs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .rsb, 1, rd, rn, op2); - } - - pub fn add(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .add, 0, rd, rn, op2); - } - - pub fn adds(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .add, 1, rd, rn, op2); - } - - pub fn adc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .adc, 0, rd, rn, op2); - } - - pub fn adcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .adc, 1, rd, rn, op2); - } - - pub fn sbc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .sbc, 0, rd, rn, op2); - } - - pub fn sbcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .sbc, 1, rd, rn, op2); - } - - pub fn rsc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .rsc, 0, rd, rn, op2); - } - - pub fn rscs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .rsc, 1, rd, rn, op2); - } - - pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .tst, 1, .r0, rn, op2); - } - - pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .teq, 1, .r0, rn, op2); - } - - pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .cmp, 1, .r0, rn, op2); - } - - pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .cmn, 1, .r0, rn, op2); - } - - pub fn orr(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .orr, 0, rd, rn, op2); - } - - pub fn orrs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .orr, 1, rd, rn, op2); - } - - pub fn mov(cond: Condition, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mov, 0, rd, .r0, op2); - } - - pub fn movs(cond: Condition, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mov, 1, rd, .r0, op2); - } - - pub fn bic(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .bic, 0, rd, rn, op2); - } - - pub fn bics(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .bic, 1, rd, rn, op2); - } - - pub fn mvn(cond: Condition, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mvn, 0, rd, .r0, op2); - } - - pub fn mvns(cond: Condition, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mvn, 1, rd, .r0, op2); - } - - // Integer Saturating Arithmetic - - pub fn qadd(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { - return integerSaturationArithmetic(cond, rd, rm, rn, 0b00); - } - - pub fn qsub(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { - return integerSaturationArithmetic(cond, rd, rm, rn, 0b01); - } - - pub fn qdadd(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { - return integerSaturationArithmetic(cond, rd, rm, rn, 0b10); - } - - pub fn qdsub(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction { - return integerSaturationArithmetic(cond, rd, rm, rn, 0b11); - } - - // movw and movt - - pub fn movw(cond: Condition, rd: Register, imm: u16) Instruction { - return specialMov(cond, rd, imm, false); - } - - pub fn movt(cond: Condition, rd: Register, imm: u16) Instruction { - return specialMov(cond, rd, imm, true); - } - - // PSR transfer - - pub fn mrs(cond: Condition, rd: Register, psr: Psr) Instruction { - return Instruction{ - .data_processing = .{ - .cond = @enumToInt(cond), - .i = 0, - .opcode = if (psr == .spsr) 0b1010 else 0b1000, - .s = 0, - .rn = 0b1111, - .rd = rd.id(), - .op2 = 0b0000_0000_0000, - }, - }; - } - - pub fn msr(cond: Condition, psr: Psr, op: Operand) Instruction { - return Instruction{ - .data_processing = .{ - .cond = @enumToInt(cond), - .i = 0, - .opcode = if (psr == .spsr) 0b1011 else 0b1001, - .s = 0, - .rn = 0b1111, - .rd = 0b1111, - .op2 = op.toU12(), - }, - }; - } - - // Multiply - - pub fn mul(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction { - return multiply(cond, 0, rd, rn, rm, null); - } - - pub fn muls(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction { - return multiply(cond, 1, rd, rn, rm, null); - } - - pub fn mla(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction { - return multiply(cond, 0, rd, rn, rm, ra); - } - - pub fn mlas(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction { - return multiply(cond, 1, rd, rn, rm, ra); - } - - // Multiply long - - pub fn umull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 0, 0, 0, rdhi, rdlo, rm, rn); - } - - pub fn umulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 0, 0, 1, rdhi, rdlo, rm, rn); - } - - pub fn umlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 0, 1, 0, rdhi, rdlo, rm, rn); - } - - pub fn umlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 0, 1, 1, rdhi, rdlo, rm, rn); - } - - pub fn smull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 1, 0, 0, rdhi, rdlo, rm, rn); - } - - pub fn smulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 1, 0, 1, rdhi, rdlo, rm, rn); - } - - pub fn smlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 1, 1, 0, rdhi, rdlo, rm, rn); - } - - pub fn smlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction { - return multiplyLong(cond, 1, 1, 1, rdhi, rdlo, rm, rn); - } - - // Single data transfer - - pub const OffsetArgs = struct { - pre_index: bool = true, - positive: bool = true, - offset: Offset, - write_back: bool = false, - }; - - pub fn ldr(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { - return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 1); - } - - pub fn ldrb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { - return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 1); - } - - pub fn str(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { - return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 0); - } - - pub fn strb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { - return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 0); - } - - // Extra load/store - - pub const ExtraLoadStoreOffsetArgs = struct { - pre_index: bool = true, - positive: bool = true, - offset: ExtraLoadStoreOffset, - write_back: bool = false, - }; - - pub fn strh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction { - return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 0, 0b01, rn, rt, args.offset); - } - - pub fn ldrh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction { - return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 1, 0b01, rn, rt, args.offset); - } - - // Block data transfer - - pub fn ldmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 1); - } - - pub fn ldmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 1); - } - - pub fn ldmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 1); - } - - pub fn ldmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 1); - } - - pub const ldmfa = ldmda; - pub const ldmea = ldmdb; - pub const ldmed = ldmib; - pub const ldmfd = ldmia; - pub const ldm = ldmia; - - pub fn stmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 0); - } - - pub fn stmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 0); - } - - pub fn stmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 0); - } - - pub fn stmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { - return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 0); - } - - pub const stmed = stmda; - pub const stmfd = stmdb; - pub const stmfa = stmib; - pub const stmea = stmia; - pub const stm = stmia; - - // Branch - - pub fn b(cond: Condition, offset: i26) Instruction { - return branch(cond, offset, 0); - } - - pub fn bl(cond: Condition, offset: i26) Instruction { - return branch(cond, offset, 1); - } - - // Branch and exchange - - pub fn bx(cond: Condition, rn: Register) Instruction { - return branchExchange(cond, rn, 0); - } - - pub fn blx(cond: Condition, rn: Register) Instruction { - return branchExchange(cond, rn, 1); - } - - // Supervisor Call - - pub const swi = svc; - - pub fn svc(cond: Condition, comment: u24) Instruction { - return supervisorCall(cond, comment); - } - - // Breakpoint - - pub fn bkpt(imm: u16) Instruction { - return breakpoint(imm); - } - - // Aliases - - pub fn nop() Instruction { - return mov(.al, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)); - } - - pub fn pop(cond: Condition, args: anytype) Instruction { - if (@typeInfo(@TypeOf(args)) != .Struct) { - @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); - } - - if (args.len < 1) { - @compileError("Expected at least one register"); - } else if (args.len == 1) { - const reg = args[0]; - return ldr(cond, reg, .sp, .{ - .pre_index = false, - .positive = true, - .offset = Offset.imm(4), - .write_back = false, - }); - } else { - var register_list: u16 = 0; - inline for (args) |arg| { - const reg = @as(Register, arg); - register_list |= @as(u16, 1) << reg.id(); - } - return ldm(cond, .sp, true, @bitCast(RegisterList, register_list)); - } - } - - pub fn push(cond: Condition, args: anytype) Instruction { - if (@typeInfo(@TypeOf(args)) != .Struct) { - @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); - } - - if (args.len < 1) { - @compileError("Expected at least one register"); - } else if (args.len == 1) { - const reg = args[0]; - return str(cond, reg, .sp, .{ - .pre_index = true, - .positive = false, - .offset = Offset.imm(4), - .write_back = true, - }); - } else { - var register_list: u16 = 0; - inline for (args) |arg| { - const reg = @as(Register, arg); - register_list |= @as(u16, 1) << reg.id(); - } - return stmdb(cond, .sp, true, @bitCast(RegisterList, register_list)); - } - } - - pub const ShiftAmount = union(enum) { - immediate: u5, - register: Register, - - pub fn imm(immediate: u5) ShiftAmount { - return .{ - .immediate = immediate, - }; - } - - pub fn reg(register: Register) ShiftAmount { - return .{ - .register = register, - }; - } - }; - - pub fn lsl(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))), - .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))), - }; - } - - pub fn lsr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))), - .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))), - }; - } - - pub fn asr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))), - .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))), - }; - } - - pub fn ror(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))), - .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))), - }; - } - - pub fn lsls(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))), - .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))), - }; - } - - pub fn lsrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))), - .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))), - }; - } - - pub fn asrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))), - .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))), - }; - } - - pub fn rors(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { - return switch (shift) { - .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))), - .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))), - }; - } -}; - -test "serialize instructions" { - const Testcase = struct { - inst: Instruction, - expected: u32, - }; - - const testcases = [_]Testcase{ - .{ // add r0, r0, r0 - .inst = Instruction.add(.al, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)), - .expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000, - }, - .{ // mov r4, r2 - .inst = Instruction.mov(.al, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)), - .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010, - }, - .{ // mov r0, #42 - .inst = Instruction.mov(.al, .r0, Instruction.Operand.imm(42, 0)), - .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010, - }, - .{ // mrs r5, cpsr - .inst = Instruction.mrs(.al, .r5, .cpsr), - .expected = 0b1110_00010_0_001111_0101_000000000000, - }, - .{ // mul r0, r1, r2 - .inst = Instruction.mul(.al, .r0, .r1, .r2), - .expected = 0b1110_000000_0_0_0000_0000_0010_1001_0001, - }, - .{ // umlal r0, r1, r5, r6 - .inst = Instruction.umlal(.al, .r0, .r1, .r5, .r6), - .expected = 0b1110_00001_0_1_0_0001_0000_0110_1001_0101, - }, - .{ // ldr r0, [r2, #42] - .inst = Instruction.ldr(.al, .r0, .r2, .{ - .offset = Instruction.Offset.imm(42), - }), - .expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010, - }, - .{ // str r0, [r3] - .inst = Instruction.str(.al, .r0, .r3, .{ - .offset = Instruction.Offset.none, - }), - .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000, - }, - .{ // strh r1, [r5] - .inst = Instruction.strh(.al, .r1, .r5, .{ - .offset = Instruction.ExtraLoadStoreOffset.none, - }), - .expected = 0b1110_000_1_1_1_0_0_0101_0001_0000_1011_0000, - }, - .{ // b #12 - .inst = Instruction.b(.al, 12), - .expected = 0b1110_101_0_0000_0000_0000_0000_0000_0011, - }, - .{ // bl #-4 - .inst = Instruction.bl(.al, -4), - .expected = 0b1110_101_1_1111_1111_1111_1111_1111_1111, - }, - .{ // bx lr - .inst = Instruction.bx(.al, .lr), - .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110, - }, - .{ // svc #0 - .inst = Instruction.svc(.al, 0), - .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000, - }, - .{ // bkpt #42 - .inst = Instruction.bkpt(42), - .expected = 0b1110_0001_0010_000000000010_0111_1010, - }, - .{ // stmdb r9, {r0} - .inst = Instruction.stmdb(.al, .r9, false, .{ .r0 = true }), - .expected = 0b1110_100_1_0_0_0_0_1001_0000000000000001, - }, - .{ // ldmea r4!, {r2, r5} - .inst = Instruction.ldmea(.al, .r4, true, .{ .r2 = true, .r5 = true }), - .expected = 0b1110_100_1_0_0_1_1_0100_0000000000100100, - }, - .{ // qadd r0, r7, r8 - .inst = Instruction.qadd(.al, .r0, .r7, .r8), - .expected = 0b1110_00010_00_0_1000_0000_0000_0101_0111, - }, - }; - - for (testcases) |case| { - const actual = case.inst.toU32(); - try testing.expectEqual(case.expected, actual); - } -} - -test "aliases" { - const Testcase = struct { - expected: Instruction, - actual: Instruction, - }; - - const testcases = [_]Testcase{ - .{ // pop { r6 } - .actual = Instruction.pop(.al, .{.r6}), - .expected = Instruction.ldr(.al, .r6, .sp, .{ - .pre_index = false, - .positive = true, - .offset = Instruction.Offset.imm(4), - .write_back = false, - }), - }, - .{ // pop { r1, r5 } - .actual = Instruction.pop(.al, .{ .r1, .r5 }), - .expected = Instruction.ldm(.al, .sp, true, .{ .r1 = true, .r5 = true }), - }, - .{ // push { r3 } - .actual = Instruction.push(.al, .{.r3}), - .expected = Instruction.str(.al, .r3, .sp, .{ - .pre_index = true, - .positive = false, - .offset = Instruction.Offset.imm(4), - .write_back = true, - }), - }, - .{ // push { r0, r2 } - .actual = Instruction.push(.al, .{ .r0, .r2 }), - .expected = Instruction.stmdb(.al, .sp, true, .{ .r0 = true, .r2 = true }), - }, - .{ // lsl r4, r5, #5 - .actual = Instruction.lsl(.al, .r4, .r5, Instruction.ShiftAmount.imm(5)), - .expected = Instruction.mov(.al, .r4, Instruction.Operand.reg( - .r5, - Instruction.Operand.Shift.imm(5, .logical_left), - )), - }, - .{ // asrs r1, r1, r3 - .actual = Instruction.asrs(.al, .r1, .r1, Instruction.ShiftAmount.reg(.r3)), - .expected = Instruction.movs(.al, .r1, Instruction.Operand.reg( - .r1, - Instruction.Operand.Shift.reg(.r3, .arithmetic_right), - )), - }, - }; - - for (testcases) |case| { - try testing.expectEqual(case.expected.toU32(), case.actual.toU32()); - } -} diff --git a/src/codegen/riscv64.zig b/src/codegen/riscv64.zig deleted file mode 100644 index b297737816..0000000000 --- a/src/codegen/riscv64.zig +++ /dev/null @@ -1,470 +0,0 @@ -const std = @import("std"); -const DW = std.dwarf; -const assert = std.debug.assert; -const testing = std.testing; - -// TODO: this is only tagged to facilitate the monstrosity. -// Once packed structs work make it packed. -pub const Instruction = union(enum) { - R: packed struct { - opcode: u7, - rd: u5, - funct3: u3, - rs1: u5, - rs2: u5, - funct7: u7, - }, - I: packed struct { - opcode: u7, - rd: u5, - funct3: u3, - rs1: u5, - imm0_11: u12, - }, - S: packed struct { - opcode: u7, - imm0_4: u5, - funct3: u3, - rs1: u5, - rs2: u5, - imm5_11: u7, - }, - B: packed struct { - opcode: u7, - imm11: u1, - imm1_4: u4, - funct3: u3, - rs1: u5, - rs2: u5, - imm5_10: u6, - imm12: u1, - }, - U: packed struct { - opcode: u7, - rd: u5, - imm12_31: u20, - }, - J: packed struct { - opcode: u7, - rd: u5, - imm12_19: u8, - imm11: u1, - imm1_10: u10, - imm20: u1, - }, - - // TODO: once packed structs work we can remove this monstrosity. - pub fn toU32(self: Instruction) u32 { - return switch (self) { - .R => |v| @bitCast(u32, v), - .I => |v| @bitCast(u32, v), - .S => |v| @bitCast(u32, v), - .B => |v| @intCast(u32, v.opcode) + (@intCast(u32, v.imm11) << 7) + (@intCast(u32, v.imm1_4) << 8) + (@intCast(u32, v.funct3) << 12) + (@intCast(u32, v.rs1) << 15) + (@intCast(u32, v.rs2) << 20) + (@intCast(u32, v.imm5_10) << 25) + (@intCast(u32, v.imm12) << 31), - .U => |v| @bitCast(u32, v), - .J => |v| @bitCast(u32, v), - }; - } - - fn rType(op: u7, fn3: u3, fn7: u7, rd: Register, r1: Register, r2: Register) Instruction { - return Instruction{ - .R = .{ - .opcode = op, - .funct3 = fn3, - .funct7 = fn7, - .rd = @enumToInt(rd), - .rs1 = @enumToInt(r1), - .rs2 = @enumToInt(r2), - }, - }; - } - - // RISC-V is all signed all the time -- convert immediates to unsigned for processing - fn iType(op: u7, fn3: u3, rd: Register, r1: Register, imm: i12) Instruction { - const umm = @bitCast(u12, imm); - - return Instruction{ - .I = .{ - .opcode = op, - .funct3 = fn3, - .rd = @enumToInt(rd), - .rs1 = @enumToInt(r1), - .imm0_11 = umm, - }, - }; - } - - fn sType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i12) Instruction { - const umm = @bitCast(u12, imm); - - return Instruction{ - .S = .{ - .opcode = op, - .funct3 = fn3, - .rs1 = @enumToInt(r1), - .rs2 = @enumToInt(r2), - .imm0_4 = @truncate(u5, umm), - .imm5_11 = @truncate(u7, umm >> 5), - }, - }; - } - - // Use significance value rather than bit value, same for J-type - // -- less burden on callsite, bonus semantic checking - fn bType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i13) Instruction { - const umm = @bitCast(u13, imm); - assert(umm % 2 == 0); // misaligned branch target - - return Instruction{ - .B = .{ - .opcode = op, - .funct3 = fn3, - .rs1 = @enumToInt(r1), - .rs2 = @enumToInt(r2), - .imm1_4 = @truncate(u4, umm >> 1), - .imm5_10 = @truncate(u6, umm >> 5), - .imm11 = @truncate(u1, umm >> 11), - .imm12 = @truncate(u1, umm >> 12), - }, - }; - } - - // We have to extract the 20 bits anyway -- let's not make it more painful - fn uType(op: u7, rd: Register, imm: i20) Instruction { - const umm = @bitCast(u20, imm); - - return Instruction{ - .U = .{ - .opcode = op, - .rd = @enumToInt(rd), - .imm12_31 = umm, - }, - }; - } - - fn jType(op: u7, rd: Register, imm: i21) Instruction { - const umm = @bitCast(u21, imm); - assert(umm % 2 == 0); // misaligned jump target - - return Instruction{ - .J = .{ - .opcode = op, - .rd = @enumToInt(rd), - .imm1_10 = @truncate(u10, umm >> 1), - .imm11 = @truncate(u1, umm >> 11), - .imm12_19 = @truncate(u8, umm >> 12), - .imm20 = @truncate(u1, umm >> 20), - }, - }; - } - - // The meat and potatoes. Arguments are in the order in which they would appear in assembly code. - - // Arithmetic/Logical, Register-Register - - pub fn add(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b000, 0b0000000, rd, r1, r2); - } - - pub fn sub(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b000, 0b0100000, rd, r1, r2); - } - - pub fn @"and"(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b111, 0b0000000, rd, r1, r2); - } - - pub fn @"or"(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b110, 0b0000000, rd, r1, r2); - } - - pub fn xor(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b100, 0b0000000, rd, r1, r2); - } - - pub fn sll(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b001, 0b0000000, rd, r1, r2); - } - - pub fn srl(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b101, 0b0000000, rd, r1, r2); - } - - pub fn sra(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b101, 0b0100000, rd, r1, r2); - } - - pub fn slt(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b010, 0b0000000, rd, r1, r2); - } - - pub fn sltu(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0110011, 0b011, 0b0000000, rd, r1, r2); - } - - // Arithmetic/Logical, Register-Register (32-bit) - - pub fn addw(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0111011, 0b000, rd, r1, r2); - } - - pub fn subw(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0111011, 0b000, 0b0100000, rd, r1, r2); - } - - pub fn sllw(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0111011, 0b001, 0b0000000, rd, r1, r2); - } - - pub fn srlw(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0111011, 0b101, 0b0000000, rd, r1, r2); - } - - pub fn sraw(rd: Register, r1: Register, r2: Register) Instruction { - return rType(0b0111011, 0b101, 0b0100000, rd, r1, r2); - } - - // Arithmetic/Logical, Register-Immediate - - pub fn addi(rd: Register, r1: Register, imm: i12) Instruction { - return iType(0b0010011, 0b000, rd, r1, imm); - } - - pub fn andi(rd: Register, r1: Register, imm: i12) Instruction { - return iType(0b0010011, 0b111, rd, r1, imm); - } - - pub fn ori(rd: Register, r1: Register, imm: i12) Instruction { - return iType(0b0010011, 0b110, rd, r1, imm); - } - - pub fn xori(rd: Register, r1: Register, imm: i12) Instruction { - return iType(0b0010011, 0b100, rd, r1, imm); - } - - pub fn slli(rd: Register, r1: Register, shamt: u6) Instruction { - return iType(0b0010011, 0b001, rd, r1, shamt); - } - - pub fn srli(rd: Register, r1: Register, shamt: u6) Instruction { - return iType(0b0010011, 0b101, rd, r1, shamt); - } - - pub fn srai(rd: Register, r1: Register, shamt: u6) Instruction { - return iType(0b0010011, 0b101, rd, r1, (1 << 10) + shamt); - } - - pub fn slti(rd: Register, r1: Register, imm: i12) Instruction { - return iType(0b0010011, 0b010, rd, r1, imm); - } - - pub fn sltiu(rd: Register, r1: Register, imm: u12) Instruction { - return iType(0b0010011, 0b011, rd, r1, @bitCast(i12, imm)); - } - - // Arithmetic/Logical, Register-Immediate (32-bit) - - pub fn addiw(rd: Register, r1: Register, imm: i12) Instruction { - return iType(0b0011011, 0b000, rd, r1, imm); - } - - pub fn slliw(rd: Register, r1: Register, shamt: u5) Instruction { - return iType(0b0011011, 0b001, rd, r1, shamt); - } - - pub fn srliw(rd: Register, r1: Register, shamt: u5) Instruction { - return iType(0b0011011, 0b101, rd, r1, shamt); - } - - pub fn sraiw(rd: Register, r1: Register, shamt: u5) Instruction { - return iType(0b0011011, 0b101, rd, r1, (1 << 10) + shamt); - } - - // Upper Immediate - - pub fn lui(rd: Register, imm: i20) Instruction { - return uType(0b0110111, rd, imm); - } - - pub fn auipc(rd: Register, imm: i20) Instruction { - return uType(0b0010111, rd, imm); - } - - // Load - - pub fn ld(rd: Register, offset: i12, base: Register) Instruction { - return iType(0b0000011, 0b011, rd, base, offset); - } - - pub fn lw(rd: Register, offset: i12, base: Register) Instruction { - return iType(0b0000011, 0b010, rd, base, offset); - } - - pub fn lwu(rd: Register, offset: i12, base: Register) Instruction { - return iType(0b0000011, 0b110, rd, base, offset); - } - - pub fn lh(rd: Register, offset: i12, base: Register) Instruction { - return iType(0b0000011, 0b001, rd, base, offset); - } - - pub fn lhu(rd: Register, offset: i12, base: Register) Instruction { - return iType(0b0000011, 0b101, rd, base, offset); - } - - pub fn lb(rd: Register, offset: i12, base: Register) Instruction { - return iType(0b0000011, 0b000, rd, base, offset); - } - - pub fn lbu(rd: Register, offset: i12, base: Register) Instruction { - return iType(0b0000011, 0b100, rd, base, offset); - } - - // Store - - pub fn sd(rs: Register, offset: i12, base: Register) Instruction { - return sType(0b0100011, 0b011, base, rs, offset); - } - - pub fn sw(rs: Register, offset: i12, base: Register) Instruction { - return sType(0b0100011, 0b010, base, rs, offset); - } - - pub fn sh(rs: Register, offset: i12, base: Register) Instruction { - return sType(0b0100011, 0b001, base, rs, offset); - } - - pub fn sb(rs: Register, offset: i12, base: Register) Instruction { - return sType(0b0100011, 0b000, base, rs, offset); - } - - // Fence - // TODO: implement fence - - // Branch - - pub fn beq(r1: Register, r2: Register, offset: i13) Instruction { - return bType(0b1100011, 0b000, r1, r2, offset); - } - - pub fn bne(r1: Register, r2: Register, offset: i13) Instruction { - return bType(0b1100011, 0b001, r1, r2, offset); - } - - pub fn blt(r1: Register, r2: Register, offset: i13) Instruction { - return bType(0b1100011, 0b100, r1, r2, offset); - } - - pub fn bge(r1: Register, r2: Register, offset: i13) Instruction { - return bType(0b1100011, 0b101, r1, r2, offset); - } - - pub fn bltu(r1: Register, r2: Register, offset: i13) Instruction { - return bType(0b1100011, 0b110, r1, r2, offset); - } - - pub fn bgeu(r1: Register, r2: Register, offset: i13) Instruction { - return bType(0b1100011, 0b111, r1, r2, offset); - } - - // Jump - - pub fn jal(link: Register, offset: i21) Instruction { - return jType(0b1101111, link, offset); - } - - pub fn jalr(link: Register, offset: i12, base: Register) Instruction { - return iType(0b1100111, 0b000, link, base, offset); - } - - // System - - pub const ecall = iType(0b1110011, 0b000, .zero, .zero, 0x000); - pub const ebreak = iType(0b1110011, 0b000, .zero, .zero, 0x001); -}; - -// zig fmt: off -pub const RawRegister = enum(u5) { - 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, x31, - - pub fn dwarfLocOp(reg: RawRegister) u8 { - return @enumToInt(reg) + DW.OP.reg0; - } -}; - -pub const Register = enum(u5) { - // 64 bit registers - zero, // zero - ra, // return address. caller saved - sp, // stack pointer. callee saved. - gp, // global pointer - tp, // thread pointer - t0, t1, t2, // temporaries. caller saved. - s0, // s0/fp, callee saved. - s1, // callee saved. - a0, a1, // fn args/return values. caller saved. - a2, a3, a4, a5, a6, a7, // fn args. caller saved. - s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved. - t3, t4, t5, t6, // caller saved - - pub fn parseRegName(name: []const u8) ?Register { - if(std.meta.stringToEnum(Register, name)) |reg| return reg; - if(std.meta.stringToEnum(RawRegister, name)) |rawreg| return @intToEnum(Register, @enumToInt(rawreg)); - return null; - } - - /// Returns the index into `callee_preserved_regs`. - pub fn allocIndex(self: Register) ?u4 { - inline for(callee_preserved_regs) |cpreg, i| { - if(self == cpreg) return i; - } - return null; - } - - pub fn dwarfLocOp(reg: Register) u8 { - return @as(u8, @enumToInt(reg)) + DW.OP.reg0; - } -}; - -// zig fmt: on - -pub const callee_preserved_regs = [_]Register{ - .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11, -}; - -test "serialize instructions" { - const Testcase = struct { - inst: Instruction, - expected: u32, - }; - - const testcases = [_]Testcase{ - .{ // add t6, zero, zero - .inst = Instruction.add(.t6, .zero, .zero), - .expected = 0b0000000_00000_00000_000_11111_0110011, - }, - .{ // sd s0, 0x7f(s0) - .inst = Instruction.sd(.s0, 0x7f, .s0), - .expected = 0b0000011_01000_01000_011_11111_0100011, - }, - .{ // bne s0, s1, 0x42 - .inst = Instruction.bne(.s0, .s1, 0x42), - .expected = 0b0_000010_01001_01000_001_0001_0_1100011, - }, - .{ // j 0x1a - .inst = Instruction.jal(.zero, 0x1a), - .expected = 0b0_0000001101_0_00000000_00000_1101111, - }, - .{ // ebreak - .inst = Instruction.ebreak, - .expected = 0b000000000001_00000_000_00000_1110011, - }, - }; - - for (testcases) |case| { - const actual = case.inst.toU32(); - try testing.expectEqual(case.expected, actual); - } -} diff --git a/src/codegen/x86.zig b/src/codegen/x86.zig deleted file mode 100644 index 5b981b9ef4..0000000000 --- a/src/codegen/x86.zig +++ /dev/null @@ -1,123 +0,0 @@ -const std = @import("std"); -const DW = std.dwarf; - -// zig fmt: off -pub const Register = enum(u8) { - // 0 through 7, 32-bit registers. id is int value - eax, ecx, edx, ebx, esp, ebp, esi, edi, - - // 8-15, 16-bit registers. id is int value - 8. - ax, cx, dx, bx, sp, bp, si, di, - - // 16-23, 8-bit registers. id is int value - 16. - al, cl, dl, bl, ah, ch, dh, bh, - - /// Returns the bit-width of the register. - pub fn size(self: @This()) u7 { - return switch (@enumToInt(self)) { - 0...7 => 32, - 8...15 => 16, - 16...23 => 8, - else => unreachable, - }; - } - - /// Returns the register's id. This is used in practically every opcode the - /// x86 has. It is embedded in some instructions, such as the `B8 +rd` move - /// instruction, and is used in the R/M byte. - pub fn id(self: @This()) u3 { - return @truncate(u3, @enumToInt(self)); - } - - /// Returns the index into `callee_preserved_regs`. - pub fn allocIndex(self: Register) ?u4 { - return switch (self) { - .eax, .ax, .al => 0, - .ecx, .cx, .cl => 1, - .edx, .dx, .dl => 2, - .esi, .si => 3, - .edi, .di => 4, - else => null, - }; - } - - /// Convert from any register to its 32 bit alias. - pub fn to32(self: Register) Register { - return @intToEnum(Register, @as(u8, self.id())); - } - - /// Convert from any register to its 16 bit alias. - pub fn to16(self: Register) Register { - return @intToEnum(Register, @as(u8, self.id()) + 8); - } - - /// Convert from any register to its 8 bit alias. - pub fn to8(self: Register) Register { - return @intToEnum(Register, @as(u8, self.id()) + 16); - } - - - pub fn dwarfLocOp(reg: Register) u8 { - return switch (reg.to32()) { - .eax => DW.OP.reg0, - .ecx => DW.OP.reg1, - .edx => DW.OP.reg2, - .ebx => DW.OP.reg3, - .esp => DW.OP.reg4, - .ebp => DW.OP.reg5, - .esi => DW.OP.reg6, - .edi => DW.OP.reg7, - else => unreachable, - }; - } -}; - -// zig fmt: on - -pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi }; - -// TODO add these to Register enum and corresponding dwarfLocOp -// // Return Address register. This is stored in `0(%esp, "")` and is not a physical register. -// RA = (8, "RA"), -// -// ST0 = (11, "st0"), -// ST1 = (12, "st1"), -// ST2 = (13, "st2"), -// ST3 = (14, "st3"), -// ST4 = (15, "st4"), -// ST5 = (16, "st5"), -// ST6 = (17, "st6"), -// ST7 = (18, "st7"), -// -// XMM0 = (21, "xmm0"), -// XMM1 = (22, "xmm1"), -// XMM2 = (23, "xmm2"), -// XMM3 = (24, "xmm3"), -// XMM4 = (25, "xmm4"), -// XMM5 = (26, "xmm5"), -// XMM6 = (27, "xmm6"), -// XMM7 = (28, "xmm7"), -// -// MM0 = (29, "mm0"), -// MM1 = (30, "mm1"), -// MM2 = (31, "mm2"), -// MM3 = (32, "mm3"), -// MM4 = (33, "mm4"), -// MM5 = (34, "mm5"), -// MM6 = (35, "mm6"), -// MM7 = (36, "mm7"), -// -// MXCSR = (39, "mxcsr"), -// -// ES = (40, "es"), -// CS = (41, "cs"), -// SS = (42, "ss"), -// DS = (43, "ds"), -// FS = (44, "fs"), -// GS = (45, "gs"), -// -// TR = (48, "tr"), -// LDTR = (49, "ldtr"), -// -// FS_BASE = (93, "fs.base"), -// GS_BASE = (94, "gs.base"), diff --git a/src/codegen/x86_64.zig b/src/codegen/x86_64.zig deleted file mode 100644 index 72a7468041..0000000000 --- a/src/codegen/x86_64.zig +++ /dev/null @@ -1,716 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; -const assert = std.debug.assert; -const ArrayList = std.ArrayList; -const Allocator = std.mem.Allocator; -const DW = std.dwarf; - -// zig fmt: off - -/// Definitions of all of the x64 registers. The order is semantically meaningful. -/// The registers are defined such that IDs go in descending order of 64-bit, -/// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen -/// registers. This results in some useful properties: -/// -/// Any 64-bit register can be turned into its 32-bit form by adding 16, and -/// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it -/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit -/// form. -/// -/// If (register & 8) is set, the register is extended. -/// -/// The ID can be easily determined by figuring out what range the register is -/// in, and then subtracting the base. -pub const Register = enum(u8) { - // 0 through 15, 64-bit registers. 8-15 are extended. - // id is just the int value. - rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, - r8, r9, r10, r11, r12, r13, r14, r15, - - // 16 through 31, 32-bit registers. 24-31 are extended. - // id is int value - 16. - eax, ecx, edx, ebx, esp, ebp, esi, edi, - r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d, - - // 32-47, 16-bit registers. 40-47 are extended. - // id is int value - 32. - ax, cx, dx, bx, sp, bp, si, di, - r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w, - - // 48-63, 8-bit registers. 56-63 are extended. - // id is int value - 48. - al, cl, dl, bl, ah, ch, dh, bh, - r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b, - - /// Returns the bit-width of the register. - pub fn size(self: Register) u7 { - return switch (@enumToInt(self)) { - 0...15 => 64, - 16...31 => 32, - 32...47 => 16, - 48...64 => 8, - else => unreachable, - }; - } - - /// Returns whether the register is *extended*. Extended registers are the - /// new registers added with amd64, r8 through r15. This also includes any - /// other variant of access to those registers, such as r8b, r15d, and so - /// on. This is needed because access to these registers requires special - /// handling via the REX prefix, via the B or R bits, depending on context. - pub fn isExtended(self: Register) bool { - return @enumToInt(self) & 0x08 != 0; - } - - /// This returns the 4-bit register ID, which is used in practically every - /// opcode. Note that bit 3 (the highest bit) is *never* used directly in - /// an instruction (@see isExtended), and requires special handling. The - /// lower three bits are often embedded directly in instructions (such as - /// the B8 variant of moves), or used in R/M bytes. - pub fn id(self: Register) u4 { - return @truncate(u4, @enumToInt(self)); - } - - /// Like id, but only returns the lower 3 bits. - pub fn low_id(self: Register) u3 { - return @truncate(u3, @enumToInt(self)); - } - - /// Returns the index into `callee_preserved_regs`. - pub fn allocIndex(self: Register) ?u4 { - return switch (self) { - .rax, .eax, .ax, .al => 0, - .rcx, .ecx, .cx, .cl => 1, - .rdx, .edx, .dx, .dl => 2, - .rsi, .esi, .si => 3, - .rdi, .edi, .di => 4, - .r8, .r8d, .r8w, .r8b => 5, - .r9, .r9d, .r9w, .r9b => 6, - .r10, .r10d, .r10w, .r10b => 7, - .r11, .r11d, .r11w, .r11b => 8, - else => null, - }; - } - - /// 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(u8, self.id()) + 16); - } - - /// Convert from any register to its 16 bit alias. - pub fn to16(self: Register) Register { - return @intToEnum(Register, @as(u8, self.id()) + 32); - } - - /// Convert from any register to its 8 bit alias. - pub fn to8(self: Register) Register { - return @intToEnum(Register, @as(u8, self.id()) + 48); - } - - pub fn dwarfLocOp(self: Register) u8 { - return switch (self.to64()) { - .rax => DW.OP.reg0, - .rdx => DW.OP.reg1, - .rcx => DW.OP.reg2, - .rbx => DW.OP.reg3, - .rsi => DW.OP.reg4, - .rdi => DW.OP.reg5, - .rbp => DW.OP.reg6, - .rsp => DW.OP.reg7, - - .r8 => DW.OP.reg8, - .r9 => DW.OP.reg9, - .r10 => DW.OP.reg10, - .r11 => DW.OP.reg11, - .r12 => DW.OP.reg12, - .r13 => DW.OP.reg13, - .r14 => DW.OP.reg14, - .r15 => DW.OP.reg15, - - else => unreachable, - }; - } -}; - -// zig fmt: on - -/// These registers belong to the called function. -pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; -pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; -pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx }; - -/// Encoding helper functions for x86_64 instructions -/// -/// Many of these helpers do very little, but they can help make things -/// slightly more readable with more descriptive field names / function names. -/// -/// Some of them also have asserts to ensure that we aren't doing dumb things. -/// For example, trying to use register 4 (esp) in an indirect modr/m byte is illegal, -/// you need to encode it with an SIB byte. -/// -/// Note that ALL of these helper functions will assume capacity, -/// so ensure that the `code` has sufficient capacity before using them. -/// The `init` method is the recommended way to ensure capacity. -pub const Encoder = struct { - /// Non-owning reference to the code array - code: *ArrayList(u8), - - const Self = @This(); - - /// Wrap `code` in Encoder to make it easier to call these helper functions - /// - /// maximum_inst_size should contain the maximum number of bytes - /// that the encoded instruction will take. - /// This is because the helper functions will assume capacity - /// in order to avoid bounds checking. - pub fn init(code: *ArrayList(u8), maximum_inst_size: u8) !Self { - try code.ensureUnusedCapacity(maximum_inst_size); - return Self{ .code = code }; - } - - /// Directly write a number to the code array with big endianness - pub fn writeIntBig(self: Self, comptime T: type, value: T) void { - mem.writeIntBig( - T, - self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), - value, - ); - } - - /// Directly write a number to the code array with little endianness - pub fn writeIntLittle(self: Self, comptime T: type, value: T) void { - mem.writeIntLittle( - T, - self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), - value, - ); - } - - // -------- - // Prefixes - // -------- - - pub const LegacyPrefixes = packed struct { - /// LOCK - prefix_f0: bool = false, - /// REPNZ, REPNE, REP, Scalar Double-precision - prefix_f2: bool = false, - /// REPZ, REPE, REP, Scalar Single-precision - prefix_f3: bool = false, - - /// CS segment override or Branch not taken - prefix_2e: bool = false, - /// DS segment override - prefix_36: bool = false, - /// ES segment override - prefix_26: bool = false, - /// FS segment override - prefix_64: bool = false, - /// GS segment override - prefix_65: bool = false, - - /// Branch taken - prefix_3e: bool = false, - - /// Operand size override (enables 16 bit operation) - prefix_66: bool = false, - - /// Address size override (enables 16 bit address size) - prefix_67: bool = false, - - padding: u5 = 0, - }; - - /// Encodes legacy prefixes - pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) void { - if (@bitCast(u16, prefixes) != 0) { - // Hopefully this path isn't taken very often, so we'll do it the slow way for now - - // LOCK - if (prefixes.prefix_f0) self.code.appendAssumeCapacity(0xf0); - // REPNZ, REPNE, REP, Scalar Double-precision - if (prefixes.prefix_f2) self.code.appendAssumeCapacity(0xf2); - // REPZ, REPE, REP, Scalar Single-precision - if (prefixes.prefix_f3) self.code.appendAssumeCapacity(0xf3); - - // CS segment override or Branch not taken - if (prefixes.prefix_2e) self.code.appendAssumeCapacity(0x2e); - // DS segment override - if (prefixes.prefix_36) self.code.appendAssumeCapacity(0x36); - // ES segment override - if (prefixes.prefix_26) self.code.appendAssumeCapacity(0x26); - // FS segment override - if (prefixes.prefix_64) self.code.appendAssumeCapacity(0x64); - // GS segment override - if (prefixes.prefix_65) self.code.appendAssumeCapacity(0x65); - - // Branch taken - if (prefixes.prefix_3e) self.code.appendAssumeCapacity(0x3e); - - // Operand size override - if (prefixes.prefix_66) self.code.appendAssumeCapacity(0x66); - - // Address size override - if (prefixes.prefix_67) self.code.appendAssumeCapacity(0x67); - } - } - - /// Use 16 bit operand size - /// - /// Note that this flag is overridden by REX.W, if both are present. - pub fn prefix16BitMode(self: Self) void { - self.code.appendAssumeCapacity(0x66); - } - - /// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB - pub const Rex = struct { - /// Wide, enables 64-bit operation - w: bool = false, - /// Extends the reg field in the ModR/M byte - r: bool = false, - /// Extends the index field in the SIB byte - x: bool = false, - /// Extends the r/m field in the ModR/M byte, - /// or the base field in the SIB byte, - /// or the reg field in the Opcode byte - b: bool = false, - }; - - /// Encodes a REX prefix byte given all the fields - /// - /// Use this byte whenever you need 64 bit operation, - /// or one of reg, index, r/m, base, or opcode-reg might be extended. - /// - /// See struct `Rex` for a description of each field. - /// - /// Does not add a prefix byte if none of the fields are set! - pub fn rex(self: Self, byte: Rex) void { - var value: u8 = 0b0100_0000; - - if (byte.w) value |= 0b1000; - if (byte.r) value |= 0b0100; - if (byte.x) value |= 0b0010; - if (byte.b) value |= 0b0001; - - if (value != 0b0100_0000) { - self.code.appendAssumeCapacity(value); - } - } - - // ------ - // Opcode - // ------ - - /// Encodes a 1 byte opcode - pub fn opcode_1byte(self: Self, opcode: u8) void { - self.code.appendAssumeCapacity(opcode); - } - - /// Encodes a 2 byte opcode - /// - /// e.g. IMUL has the opcode 0x0f 0xaf, so you use - /// - /// encoder.opcode_2byte(0x0f, 0xaf); - pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) void { - self.code.appendAssumeCapacity(prefix); - self.code.appendAssumeCapacity(opcode); - } - - /// Encodes a 1 byte opcode with a reg field - /// - /// Remember to add a REX prefix byte if reg is extended! - pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) void { - assert(opcode & 0b111 == 0); - self.code.appendAssumeCapacity(opcode | reg); - } - - // ------ - // ModR/M - // ------ - - /// Construct a ModR/M byte given all the fields - /// - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) void { - self.code.appendAssumeCapacity( - @as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm, - ); - } - - /// Construct a ModR/M byte using direct r/m addressing - /// r/m effective address: r/m - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) void { - self.modRm(0b11, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect r/m addressing - /// r/m effective address: [r/m] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4 and rm != 5); - self.modRm(0b00, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB addressing - /// r/m effective address: [SIB] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) void { - self.modRm(0b00, reg_or_opx, 0b100); - } - - /// Construct a ModR/M byte using RIP-relative addressing - /// r/m effective address: [RIP + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) void { - self.modRm(0b00, reg_or_opx, 0b101); - } - - /// Construct a ModR/M byte using indirect r/m with a 8bit displacement - /// r/m effective address: [r/m + disp8] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4); - self.modRm(0b01, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB with a 8bit displacement - /// r/m effective address: [SIB + disp8] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) void { - self.modRm(0b01, reg_or_opx, 0b100); - } - - /// Construct a ModR/M byte using indirect r/m with a 32bit displacement - /// r/m effective address: [r/m + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4); - self.modRm(0b10, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB with a 32bit displacement - /// r/m effective address: [SIB + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) void { - self.modRm(0b10, reg_or_opx, 0b100); - } - - // --- - // SIB - // --- - - /// Construct a SIB byte given all the fields - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib(self: Self, scale: u2, index: u3, base: u3) void { - self.code.appendAssumeCapacity( - @as(u8, scale) << 6 | @as(u8, index) << 3 | base, - ); - } - - /// Construct a SIB byte with scale * index + base, no frills. - /// r/m effective address: [base + scale * index] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) void { - assert(base != 5); - - self.sib(scale, index, base); - } - - /// Construct a SIB byte with scale * index + disp32 - /// r/m effective address: [scale * index + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) void { - assert(index != 4); - - // scale is actually ignored - // index = 4 means no index - // base = 5 means no base, if mod == 0. - self.sib(scale, index, 5); - } - - /// Construct a SIB byte with just base - /// r/m effective address: [base] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_base(self: Self, base: u3) void { - assert(base != 5); - - // scale is actually ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - /// Construct a SIB byte with just disp32 - /// r/m effective address: [disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_disp32(self: Self) void { - // scale is actually ignored - // index = 4 means no index - // base = 5 means no base, if mod == 0. - self.sib(0, 4, 5); - } - - /// Construct a SIB byte with scale * index + base + disp8 - /// r/m effective address: [base + scale * index + disp8] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) void { - self.sib(scale, index, base); - } - - /// Construct a SIB byte with base + disp8, no index - /// r/m effective address: [base + disp8] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_baseDisp8(self: Self, base: u3) void { - // scale is ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - /// Construct a SIB byte with scale * index + base + disp32 - /// r/m effective address: [base + scale * index + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) void { - self.sib(scale, index, base); - } - - /// Construct a SIB byte with base + disp32, no index - /// r/m effective address: [base + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_baseDisp32(self: Self, base: u3) void { - // scale is ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - // ------------------------- - // Trivial (no bit fiddling) - // ------------------------- - - /// Encode an 8 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm8(self: Self, imm: i8) void { - self.code.appendAssumeCapacity(@bitCast(u8, imm)); - } - - /// Encode an 8 bit displacement - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn disp8(self: Self, disp: i8) void { - self.code.appendAssumeCapacity(@bitCast(u8, disp)); - } - - /// Encode an 16 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm16(self: Self, imm: i16) void { - self.writeIntLittle(i16, imm); - } - - /// Encode an 32 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm32(self: Self, imm: i32) void { - self.writeIntLittle(i32, imm); - } - - /// Encode an 32 bit displacement - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn disp32(self: Self, disp: i32) void { - self.writeIntLittle(i32, disp); - } - - /// Encode an 64 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm64(self: Self, imm: u64) void { - self.writeIntLittle(u64, imm); - } -}; - -test "x86_64 Encoder helpers" { - var code = ArrayList(u8).init(testing.allocator); - defer code.deinit(); - - // simple integer multiplication - - // imul eax,edi - // 0faf c7 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 4); - encoder.rex(.{ - .r = Register.eax.isExtended(), - .b = Register.edi.isExtended(), - }); - encoder.opcode_2byte(0x0f, 0xaf); - encoder.modRm_direct( - Register.eax.low_id(), - Register.edi.low_id(), - ); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x0f, 0xaf, 0xc7 }, code.items); - } - - // simple mov - - // mov eax,edi - // 89 f8 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 3); - encoder.rex(.{ - .r = Register.edi.isExtended(), - .b = Register.eax.isExtended(), - }); - encoder.opcode_1byte(0x89); - encoder.modRm_direct( - Register.edi.low_id(), - Register.eax.low_id(), - ); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x89, 0xf8 }, code.items); - } - - // signed integer addition of 32-bit sign extended immediate to 64 bit register - - // add rcx, 2147483647 - // - // Using the following opcode: REX.W + 81 /0 id, we expect the following encoding - // - // 48 : REX.W set for 64 bit operand (*r*cx) - // 81 : opcode for " with immediate" - // c1 : id = rcx, - // : c1 = 11 <-- mod = 11 indicates r/m is register (rcx) - // : 000 <-- opcode_extension = 0 because opcode extension is /0. /0 specifies ADD - // : 001 <-- 001 is rcx - // ffffff7f : 2147483647 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 7); - encoder.rex(.{ .w = true }); // use 64 bit operation - encoder.opcode_1byte(0x81); - encoder.modRm_direct( - 0, - Register.rcx.low_id(), - ); - encoder.imm32(2147483647); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x48, 0x81, 0xc1, 0xff, 0xff, 0xff, 0x7f }, code.items); - } -} - -// TODO add these registers to the enum and populate dwarfLocOp -// // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register. -// RA = (16, "RA"), -// -// XMM0 = (17, "xmm0"), -// XMM1 = (18, "xmm1"), -// XMM2 = (19, "xmm2"), -// XMM3 = (20, "xmm3"), -// XMM4 = (21, "xmm4"), -// XMM5 = (22, "xmm5"), -// XMM6 = (23, "xmm6"), -// XMM7 = (24, "xmm7"), -// -// XMM8 = (25, "xmm8"), -// XMM9 = (26, "xmm9"), -// XMM10 = (27, "xmm10"), -// XMM11 = (28, "xmm11"), -// XMM12 = (29, "xmm12"), -// XMM13 = (30, "xmm13"), -// XMM14 = (31, "xmm14"), -// XMM15 = (32, "xmm15"), -// -// ST0 = (33, "st0"), -// ST1 = (34, "st1"), -// ST2 = (35, "st2"), -// ST3 = (36, "st3"), -// ST4 = (37, "st4"), -// ST5 = (38, "st5"), -// ST6 = (39, "st6"), -// ST7 = (40, "st7"), -// -// MM0 = (41, "mm0"), -// MM1 = (42, "mm1"), -// MM2 = (43, "mm2"), -// MM3 = (44, "mm3"), -// MM4 = (45, "mm4"), -// MM5 = (46, "mm5"), -// MM6 = (47, "mm6"), -// MM7 = (48, "mm7"), -// -// RFLAGS = (49, "rFLAGS"), -// ES = (50, "es"), -// CS = (51, "cs"), -// SS = (52, "ss"), -// DS = (53, "ds"), -// FS = (54, "fs"), -// GS = (55, "gs"), -// -// FS_BASE = (58, "fs.base"), -// GS_BASE = (59, "gs.base"), -// -// TR = (62, "tr"), -// LDTR = (63, "ldtr"), -// MXCSR = (64, "mxcsr"), -// FCW = (65, "fcw"), -// FSW = (66, "fsw"), -// -// XMM16 = (67, "xmm16"), -// XMM17 = (68, "xmm17"), -// XMM18 = (69, "xmm18"), -// XMM19 = (70, "xmm19"), -// XMM20 = (71, "xmm20"), -// XMM21 = (72, "xmm21"), -// XMM22 = (73, "xmm22"), -// XMM23 = (74, "xmm23"), -// XMM24 = (75, "xmm24"), -// XMM25 = (76, "xmm25"), -// XMM26 = (77, "xmm26"), -// XMM27 = (78, "xmm27"), -// XMM28 = (79, "xmm28"), -// XMM29 = (80, "xmm29"), -// XMM30 = (81, "xmm30"), -// XMM31 = (82, "xmm31"), -// -// K0 = (118, "k0"), -// K1 = (119, "k1"), -// K2 = (120, "k2"), -// K3 = (121, "k3"), -// K4 = (122, "k4"), -// K5 = (123, "k5"), -// K6 = (124, "k6"), -// K7 = (125, "k7"), diff --git a/src/link/MachO.zig b/src/link/MachO.zig index dc44474c0a..9a2c462d55 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -12,7 +12,7 @@ const math = std.math; const mem = std.mem; const meta = std.meta; -const aarch64 = @import("../codegen/aarch64.zig"); +const aarch64 = @import("../arch/aarch64/bits.zig"); const bind = @import("MachO/bind.zig"); const codegen = @import("../codegen.zig"); const commands = @import("MachO/commands.zig"); @@ -200,7 +200,7 @@ atoms: std.AutoHashMapUnmanaged(MatchingSection, *Atom) = .{}, /// List of atoms that are owned directly by the linker. /// Currently these are only atoms that are the result of linking -/// object files. Atoms which take part in incremental linking are +/// object files. Atoms which take part in incremental linking are /// at present owned by Module.Decl. /// TODO consolidate this. managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index 46e5191ab3..a98f624176 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -2,7 +2,7 @@ const Atom = @This(); const std = @import("std"); const build_options = @import("build_options"); -const aarch64 = @import("../../codegen/aarch64.zig"); +const aarch64 = @import("../../arch/aarch64/bits.zig"); const assert = std.debug.assert; const commands = @import("commands.zig"); const log = std.log.scoped(.text_block); -- cgit v1.2.3