From 1daa5942b83ede3e504f9952a1f705b763e59893 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Jun 2022 15:19:53 -0400 Subject: MOVK, MOVZ, BR (https://github.com/Shopify/ruby/pull/296) * MOVK instruction * More tests for the A64 entrypoints * Finish testing entrypoints * MOVZ * BR instruction --- yjit/src/asm/arm64/inst/branch.rs | 85 ++++++++++ yjit/src/asm/arm64/inst/branches_and_system.rs | 62 ------- yjit/src/asm/arm64/inst/data_imm.rs | 172 ++++++++++++++++++++ .../asm/arm64/inst/data_processing_immediate.rs | 169 ------------------- .../src/asm/arm64/inst/data_processing_register.rs | 178 -------------------- yjit/src/asm/arm64/inst/data_reg.rs | 181 +++++++++++++++++++++ yjit/src/asm/arm64/inst/family.rs | 34 ---- yjit/src/asm/arm64/inst/load.rs | 100 ++++++++++++ yjit/src/asm/arm64/inst/loads_and_stores.rs | 99 ----------- yjit/src/asm/arm64/inst/mod.rs | 175 +++++++++++++++++--- yjit/src/asm/arm64/inst/mov.rs | 155 ++++++++++++++++++ yjit/src/asm/mod.rs | 12 ++ 12 files changed, 853 insertions(+), 569 deletions(-) create mode 100644 yjit/src/asm/arm64/inst/branch.rs delete mode 100644 yjit/src/asm/arm64/inst/branches_and_system.rs create mode 100644 yjit/src/asm/arm64/inst/data_imm.rs delete mode 100644 yjit/src/asm/arm64/inst/data_processing_immediate.rs delete mode 100644 yjit/src/asm/arm64/inst/data_processing_register.rs create mode 100644 yjit/src/asm/arm64/inst/data_reg.rs delete mode 100644 yjit/src/asm/arm64/inst/family.rs create mode 100644 yjit/src/asm/arm64/inst/load.rs delete mode 100644 yjit/src/asm/arm64/inst/loads_and_stores.rs create mode 100644 yjit/src/asm/arm64/inst/mov.rs diff --git a/yjit/src/asm/arm64/inst/branch.rs b/yjit/src/asm/arm64/inst/branch.rs new file mode 100644 index 0000000000..7f93f5e201 --- /dev/null +++ b/yjit/src/asm/arm64/inst/branch.rs @@ -0,0 +1,85 @@ +/// Which operation to perform. +enum Op { + /// Perform a BR instruction. + Br = 0b00, + + /// Perform a RET instruction. + Ret = 0b10 +} + +/// The struct that represents an A64 branch instruction that can be encoded. +/// +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | +/// | 1 1 0 1 0 1 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 | +/// | op... rn.............. rm.............. | +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// +pub struct Branch { + /// The register holding the address to be branched to. + rn: u8, + + /// The operation to perform. + op: Op +} + +impl Branch { + /// BR + /// https://developer.arm.com/documentation/ddi0602/2022-03/Base-Instructions/BR--Branch-to-Register-?lang=en + pub fn br(rn: u8) -> Self { + Self { rn, op: Op::Br } + } + + /// RET + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/RET--Return-from-subroutine-?lang=en + pub fn ret(rn: u8) -> Self { + Self { rn, op: Op::Ret } + } +} + +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Branches--Exception-Generating-and-System-instructions?lang=en +const FAMILY: u32 = 0b101; + +impl From for u32 { + /// Convert an instruction into a 32-bit value. + fn from(inst: Branch) -> Self { + 0 + | (0b11 << 30) + | (FAMILY << 26) + | (1 << 25) + | ((inst.op as u32) << 21) + | (0b11111 << 16) + | ((inst.rn as u32) << 5) + } +} + +impl From for [u8; 4] { + /// Convert an instruction into a 4 byte array. + fn from(inst: Branch) -> [u8; 4] { + let result: u32 = inst.into(); + result.to_le_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_br() { + let result: u32 = Branch::br(0).into(); + assert_eq!(0xd61f0000, result); + } + + #[test] + fn test_ret() { + let result: u32 = Branch::ret(30).into(); + assert_eq!(0xd65f03C0, result); + } + + #[test] + fn test_ret_rn() { + let result: u32 = Branch::ret(20).into(); + assert_eq!(0xd65f0280, result); + } +} diff --git a/yjit/src/asm/arm64/inst/branches_and_system.rs b/yjit/src/asm/arm64/inst/branches_and_system.rs deleted file mode 100644 index 77e99c112a..0000000000 --- a/yjit/src/asm/arm64/inst/branches_and_system.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::family::Family; - -/// The struct that represents an A64 branches and system instruction that can -/// be encoded. -/// -/// RET -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | -/// | 1 1 0 1 0 1 1 0 0 1 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 | -/// | rn.............. rm.............. | -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// -pub struct BranchesAndSystem { - /// The register holding the address to be branched to. - rn: u8 -} - -impl BranchesAndSystem { - /// RET - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/RET--Return-from-subroutine-?lang=en - pub fn ret(rn: u8) -> Self { - Self { rn } - } -} - -impl From for u32 { - /// Convert an instruction into a 32-bit value. - fn from(inst: BranchesAndSystem) -> Self { - 0 - | (0b11 << 30) - | ((Family::BranchesAndSystem as u32) << 25) - | (0b1001011111 << 16) - | ((inst.rn as u32) << 5) - } -} - -impl From for [u8; 4] { - /// Convert an instruction into a 4 byte array. - fn from(inst: BranchesAndSystem) -> [u8; 4] { - let result: u32 = inst.into(); - result.to_le_bytes() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ret() { - let inst = BranchesAndSystem::ret(30); - let result: u32 = inst.into(); - assert_eq!(0xd65f03C0, result); - } - - #[test] - fn test_ret_rn() { - let inst = BranchesAndSystem::ret(20); - let result: u32 = inst.into(); - assert_eq!(0xd65f0280, result); - } -} diff --git a/yjit/src/asm/arm64/inst/data_imm.rs b/yjit/src/asm/arm64/inst/data_imm.rs new file mode 100644 index 0000000000..0d0a6ff325 --- /dev/null +++ b/yjit/src/asm/arm64/inst/data_imm.rs @@ -0,0 +1,172 @@ +use super::sf::Sf; + +/// The operation being performed by this instruction. +enum Op { + Add = 0b0, + Sub = 0b1 +} + +// Whether or not to update the flags when this instruction is performed. +enum S { + LeaveFlags = 0b0, + UpdateFlags = 0b1 +} + +/// How much to shift the immediate by. +enum Shift { + LSL0 = 0b0, // no shift + LSL12 = 0b1 // logical shift left by 12 bits +} + +/// The struct that represents an A64 data processing -- immediate instruction +/// that can be encoded. +/// +/// Add/subtract (immediate) +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | +/// | 1 0 0 0 1 0 | +/// | sf op S sh imm12.................................... rn.............. rd.............. | +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// +pub struct DataImm { + /// The register number of the destination register. + rd: u8, + + /// The register number of the first operand register. + rn: u8, + + /// The value of the immediate. + imm12: u16, + + /// How much to shift the immediate by. + shift: Shift, + + /// Whether or not to update the flags when this instruction is performed. + s: S, + + /// The opcode for this instruction. + op: Op, + + /// Whether or not this instruction is operating on 64-bit operands. + sf: Sf +} + +impl DataImm { + /// ADD (immediate) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--?lang=en + pub fn add(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { + Self { + rd, + rn, + imm12, + shift: Shift::LSL0, + s: S::LeaveFlags, + op: Op::Add, + sf: num_bits.into() + } + } + + /// ADDS (immediate, set flags) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADDS--immediate---Add--immediate---setting-flags-?lang=en + pub fn adds(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { + Self { + rd, + rn, + imm12, + shift: Shift::LSL0, + s: S::UpdateFlags, + op: Op::Add, + sf: num_bits.into() + } + } + + /// SUB (immediate) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUB--immediate---Subtract--immediate--?lang=en + pub fn sub(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { + Self { + rd, + rn, + imm12, + shift: Shift::LSL0, + s: S::LeaveFlags, + op: Op::Sub, + sf: num_bits.into() + } + } + + /// SUBS (immediate, set flags) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUBS--immediate---Subtract--immediate---setting-flags-?lang=en + pub fn subs(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { + Self { + rd, + rn, + imm12, + shift: Shift::LSL0, + s: S::UpdateFlags, + op: Op::Sub, + sf: num_bits.into() + } + } +} + +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Immediate?lang=en +const FAMILY: u32 = 0b1000; + +impl From for u32 { + /// Convert an instruction into a 32-bit value. + fn from(inst: DataImm) -> Self { + let imm12 = (inst.imm12 as u32) & ((1 << 12) - 1); + + 0 + | ((inst.sf as u32) << 31) + | ((inst.op as u32) << 30) + | ((inst.s as u32) << 29) + | (FAMILY << 25) + | (1 << 24) + | ((inst.shift as u32) << 22) + | (imm12 << 10) + | ((inst.rn as u32) << 5) + | inst.rd as u32 + } +} + +impl From for [u8; 4] { + /// Convert an instruction into a 4 byte array. + fn from(inst: DataImm) -> [u8; 4] { + let result: u32 = inst.into(); + result.to_le_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add() { + let inst = DataImm::add(0, 1, 7, 64); + let result: u32 = inst.into(); + assert_eq!(0x91001c20, result); + } + + #[test] + fn test_adds() { + let inst = DataImm::adds(0, 1, 7, 64); + let result: u32 = inst.into(); + assert_eq!(0xb1001c20, result); + } + + #[test] + fn test_sub() { + let inst = DataImm::sub(0, 1, 7, 64); + let result: u32 = inst.into(); + assert_eq!(0xd1001c20, result); + } + + #[test] + fn test_subs() { + let inst = DataImm::subs(0, 1, 7, 64); + let result: u32 = inst.into(); + assert_eq!(0xf1001c20, result); + } +} diff --git a/yjit/src/asm/arm64/inst/data_processing_immediate.rs b/yjit/src/asm/arm64/inst/data_processing_immediate.rs deleted file mode 100644 index 25117efc22..0000000000 --- a/yjit/src/asm/arm64/inst/data_processing_immediate.rs +++ /dev/null @@ -1,169 +0,0 @@ -use super::{family::Family, sf::Sf}; - -/// The operation being performed by this instruction. -enum Op { - Add = 0b0, - Sub = 0b1 -} - -// Whether or not to update the flags when this instruction is performed. -enum S { - LeaveFlags = 0b0, - UpdateFlags = 0b1 -} - -/// How much to shift the immediate by. -enum Shift { - LSL0 = 0b0, // no shift - LSL12 = 0b1 // logical shift left by 12 bits -} - -/// The struct that represents an A64 data processing -- immediate instruction -/// that can be encoded. -/// -/// Add/subtract (immediate) -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | -/// | 1 0 0 0 1 0 | -/// | sf op S sh imm12.................................... rn.............. rd.............. | -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// -pub struct DataProcessingImmediate { - /// The register number of the destination register. - rd: u8, - - /// The register number of the first operand register. - rn: u8, - - /// The value of the immediate. - imm12: u16, - - /// How much to shift the immediate by. - shift: Shift, - - /// Whether or not to update the flags when this instruction is performed. - s: S, - - /// The opcode for this instruction. - op: Op, - - /// Whether or not this instruction is operating on 64-bit operands. - sf: Sf -} - -impl DataProcessingImmediate { - /// ADD (immediate) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--?lang=en - pub fn add(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { - Self { - rd, - rn, - imm12, - shift: Shift::LSL0, - s: S::LeaveFlags, - op: Op::Add, - sf: num_bits.into() - } - } - - /// ADDS (immediate, set flags) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADDS--immediate---Add--immediate---setting-flags-?lang=en - pub fn adds(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { - Self { - rd, - rn, - imm12, - shift: Shift::LSL0, - s: S::UpdateFlags, - op: Op::Add, - sf: num_bits.into() - } - } - - /// SUB (immediate) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUB--immediate---Subtract--immediate--?lang=en - pub fn sub(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { - Self { - rd, - rn, - imm12, - shift: Shift::LSL0, - s: S::LeaveFlags, - op: Op::Sub, - sf: num_bits.into() - } - } - - /// SUBS (immediate, set flags) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUBS--immediate---Subtract--immediate---setting-flags-?lang=en - pub fn subs(rd: u8, rn: u8, imm12: u16, num_bits: u8) -> Self { - Self { - rd, - rn, - imm12, - shift: Shift::LSL0, - s: S::UpdateFlags, - op: Op::Sub, - sf: num_bits.into() - } - } -} - -impl From for u32 { - /// Convert an instruction into a 32-bit value. - fn from(inst: DataProcessingImmediate) -> Self { - let imm12 = (inst.imm12 as u32) & ((1 << 12) - 1); - - 0 - | ((inst.sf as u32) << 31) - | ((inst.op as u32) << 30) - | ((inst.s as u32) << 29) - | ((Family::DataProcessingImmediate as u32) << 25) - | (1 << 24) - | ((inst.shift as u32) << 22) - | (imm12 << 10) - | ((inst.rn as u32) << 5) - | inst.rd as u32 - } -} - -impl From for [u8; 4] { - /// Convert an instruction into a 4 byte array. - fn from(inst: DataProcessingImmediate) -> [u8; 4] { - let result: u32 = inst.into(); - result.to_le_bytes() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_add() { - let inst = DataProcessingImmediate::add(0, 1, 7, 64); - let result: u32 = inst.into(); - assert_eq!(0x91001c20, result); - } - - #[test] - fn test_adds() { - let inst = DataProcessingImmediate::adds(0, 1, 7, 64); - let result: u32 = inst.into(); - assert_eq!(0xb1001c20, result); - } - - #[test] - fn test_sub() { - let inst = DataProcessingImmediate::sub(0, 1, 7, 64); - let result: u32 = inst.into(); - assert_eq!(0xd1001c20, result); - } - - #[test] - fn test_subs() { - let inst = DataProcessingImmediate::subs(0, 1, 7, 64); - let result: u32 = inst.into(); - assert_eq!(0xf1001c20, result); - } -} diff --git a/yjit/src/asm/arm64/inst/data_processing_register.rs b/yjit/src/asm/arm64/inst/data_processing_register.rs deleted file mode 100644 index 7e9f37ab8e..0000000000 --- a/yjit/src/asm/arm64/inst/data_processing_register.rs +++ /dev/null @@ -1,178 +0,0 @@ -use super::{family::Family, sf::Sf}; - -/// The operation being performed by this instruction. -enum Op { - Add = 0b0, - Sub = 0b1 -} - -// Whether or not to update the flags when this instruction is performed. -enum S { - LeaveFlags = 0b0, - UpdateFlags = 0b1 -} - -/// The type of shift to perform on the second operand register. -enum Shift { - LSL = 0b00, // logical shift left (unsigned) - LSR = 0b01, // logical shift right (unsigned) - ASR = 0b10 // arithmetic shift right (signed) -} - -/// The struct that represents an A64 data processing -- register instruction -/// that can be encoded. -/// -/// Add/subtract (shifted register) -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | -/// | 0 1 0 1 1 0 | -/// | sf op S shift rm.............. imm6............... rn.............. rd.............. | -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// -pub struct DataProcessingRegister { - /// The register number of the destination register. - rd: u8, - - /// The register number of the first operand register. - rn: u8, - - /// The amount to shift the second operand register by. - imm6: u8, - - /// The register number of the second operand register. - rm: u8, - - /// The type of shift to perform on the second operand register. - shift: Shift, - - /// Whether or not to update the flags when this instruction is performed. - s: S, - - /// The opcode for this instruction. - op: Op, - - /// Whether or not this instruction is operating on 64-bit operands. - sf: Sf -} - -impl DataProcessingRegister { - /// ADD (shifted register) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--shifted-register---Add--shifted-register--?lang=en - pub fn add(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { - Self { - rd, - rn, - imm6: 0, - rm, - shift: Shift::LSL, - s: S::LeaveFlags, - op: Op::Add, - sf: num_bits.into() - } - } - - /// ADDS (shifted register, set flags) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADDS--shifted-register---Add--shifted-register---setting-flags-?lang=en - pub fn adds(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { - Self { - rd, - rn, - imm6: 0, - rm, - shift: Shift::LSL, - s: S::UpdateFlags, - op: Op::Add, - sf: num_bits.into() - } - } - - /// SUB (shifted register) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUB--shifted-register---Subtract--shifted-register--?lang=en - pub fn sub(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { - Self { - rd, - rn, - imm6: 0, - rm, - shift: Shift::LSL, - s: S::LeaveFlags, - op: Op::Sub, - sf: num_bits.into() - } - } - - /// SUBS (shifted register, set flags) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUBS--shifted-register---Subtract--shifted-register---setting-flags-?lang=en - pub fn subs(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { - Self { - rd, - rn, - imm6: 0, - rm, - shift: Shift::LSL, - s: S::UpdateFlags, - op: Op::Sub, - sf: num_bits.into() - } - } -} - -impl From for u32 { - /// Convert an instruction into a 32-bit value. - fn from(inst: DataProcessingRegister) -> Self { - let imm6 = (inst.imm6 as u32) & ((1 << 6) - 1); - - 0 - | ((inst.sf as u32) << 31) - | ((inst.op as u32) << 30) - | ((inst.s as u32) << 29) - | ((Family::DataProcessingRegister as u32) << 25) - | (1 << 24) - | ((inst.shift as u32) << 22) - | ((inst.rm as u32) << 16) - | (imm6 << 10) - | ((inst.rn as u32) << 5) - | inst.rd as u32 - } -} - -impl From for [u8; 4] { - /// Convert an instruction into a 4 byte array. - fn from(inst: DataProcessingRegister) -> [u8; 4] { - let result: u32 = inst.into(); - result.to_le_bytes() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_add() { - let inst = DataProcessingRegister::add(0, 1, 2, 64); - let result: u32 = inst.into(); - assert_eq!(0x8b020020, result); - } - - #[test] - fn test_adds() { - let inst = DataProcessingRegister::adds(0, 1, 2, 64); - let result: u32 = inst.into(); - assert_eq!(0xab020020, result); - } - - #[test] - fn test_sub() { - let inst = DataProcessingRegister::sub(0, 1, 2, 64); - let result: u32 = inst.into(); - assert_eq!(0xcb020020, result); - } - - #[test] - fn test_subs() { - let inst = DataProcessingRegister::subs(0, 1, 2, 64); - let result: u32 = inst.into(); - assert_eq!(0xeb020020, result); - } -} diff --git a/yjit/src/asm/arm64/inst/data_reg.rs b/yjit/src/asm/arm64/inst/data_reg.rs new file mode 100644 index 0000000000..8635ab804b --- /dev/null +++ b/yjit/src/asm/arm64/inst/data_reg.rs @@ -0,0 +1,181 @@ +use super::sf::Sf; + +/// The operation being performed by this instruction. +enum Op { + Add = 0b0, + Sub = 0b1 +} + +// Whether or not to update the flags when this instruction is performed. +enum S { + LeaveFlags = 0b0, + UpdateFlags = 0b1 +} + +/// The type of shift to perform on the second operand register. +enum Shift { + LSL = 0b00, // logical shift left (unsigned) + LSR = 0b01, // logical shift right (unsigned) + ASR = 0b10 // arithmetic shift right (signed) +} + +/// The struct that represents an A64 data processing -- register instruction +/// that can be encoded. +/// +/// Add/subtract (shifted register) +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | +/// | 0 1 0 1 1 0 | +/// | sf op S shift rm.............. imm6............... rn.............. rd.............. | +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// +pub struct DataReg { + /// The register number of the destination register. + rd: u8, + + /// The register number of the first operand register. + rn: u8, + + /// The amount to shift the second operand register by. + imm6: u8, + + /// The register number of the second operand register. + rm: u8, + + /// The type of shift to perform on the second operand register. + shift: Shift, + + /// Whether or not to update the flags when this instruction is performed. + s: S, + + /// The opcode for this instruction. + op: Op, + + /// Whether or not this instruction is operating on 64-bit operands. + sf: Sf +} + +impl DataReg { + /// ADD (shifted register) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--shifted-register---Add--shifted-register--?lang=en + pub fn add(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { + Self { + rd, + rn, + imm6: 0, + rm, + shift: Shift::LSL, + s: S::LeaveFlags, + op: Op::Add, + sf: num_bits.into() + } + } + + /// ADDS (shifted register, set flags) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADDS--shifted-register---Add--shifted-register---setting-flags-?lang=en + pub fn adds(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { + Self { + rd, + rn, + imm6: 0, + rm, + shift: Shift::LSL, + s: S::UpdateFlags, + op: Op::Add, + sf: num_bits.into() + } + } + + /// SUB (shifted register) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUB--shifted-register---Subtract--shifted-register--?lang=en + pub fn sub(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { + Self { + rd, + rn, + imm6: 0, + rm, + shift: Shift::LSL, + s: S::LeaveFlags, + op: Op::Sub, + sf: num_bits.into() + } + } + + /// SUBS (shifted register, set flags) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SUBS--shifted-register---Subtract--shifted-register---setting-flags-?lang=en + pub fn subs(rd: u8, rn: u8, rm: u8, num_bits: u8) -> Self { + Self { + rd, + rn, + imm6: 0, + rm, + shift: Shift::LSL, + s: S::UpdateFlags, + op: Op::Sub, + sf: num_bits.into() + } + } +} + +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Register?lang=en +const FAMILY: u32 = 0b0101; + +impl From for u32 { + /// Convert an instruction into a 32-bit value. + fn from(inst: DataReg) -> Self { + let imm6 = (inst.imm6 as u32) & ((1 << 6) - 1); + + 0 + | ((inst.sf as u32) << 31) + | ((inst.op as u32) << 30) + | ((inst.s as u32) << 29) + | (FAMILY << 25) + | (1 << 24) + | ((inst.shift as u32) << 22) + | ((inst.rm as u32) << 16) + | (imm6 << 10) + | ((inst.rn as u32) << 5) + | inst.rd as u32 + } +} + +impl From for [u8; 4] { + /// Convert an instruction into a 4 byte array. + fn from(inst: DataReg) -> [u8; 4] { + let result: u32 = inst.into(); + result.to_le_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add() { + let inst = DataReg::add(0, 1, 2, 64); + let result: u32 = inst.into(); + assert_eq!(0x8b020020, result); + } + + #[test] + fn test_adds() { + let inst = DataReg::adds(0, 1, 2, 64); + let result: u32 = inst.into(); + assert_eq!(0xab020020, result); + } + + #[test] + fn test_sub() { + let inst = DataReg::sub(0, 1, 2, 64); + let result: u32 = inst.into(); + assert_eq!(0xcb020020, result); + } + + #[test] + fn test_subs() { + let inst = DataReg::subs(0, 1, 2, 64); + let result: u32 = inst.into(); + assert_eq!(0xeb020020, result); + } +} diff --git a/yjit/src/asm/arm64/inst/family.rs b/yjit/src/asm/arm64/inst/family.rs deleted file mode 100644 index ff5a335406..0000000000 --- a/yjit/src/asm/arm64/inst/family.rs +++ /dev/null @@ -1,34 +0,0 @@ -/// These are the top-level encodings. They're effectively the family of -/// instructions, as each instruction within those groups shares these same -/// bits (28-25). -/// -/// In the documentation, you can see that some of the bits are -/// optional (e.g., x1x0 for loads and stores). We represent that here as 0100 -/// since we're bitwise ORing the family into the resulting encoding. -/// -/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding?lang=en -pub enum Family { - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Reserved?lang=en - Reserved = 0b0000, - - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/SME-encodings?lang=en - SMEEncodings = 0b0001, - - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/SVE-encodings?lang=en - SVEEncodings = 0b0010, - - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Immediate?lang=en - DataProcessingImmediate = 0b1000, - - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Branches--Exception-Generating-and-System-instructions?lang=en - BranchesAndSystem = 0b1010, - - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Loads-and-Stores?lang=en - LoadsAndStores = 0b0100, - - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Register?lang=en - DataProcessingRegister = 0b0101, - - /// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en - DataProcessingScalar = 0b0111 -} diff --git a/yjit/src/asm/arm64/inst/load.rs b/yjit/src/asm/arm64/inst/load.rs new file mode 100644 index 0000000000..727dad52f7 --- /dev/null +++ b/yjit/src/asm/arm64/inst/load.rs @@ -0,0 +1,100 @@ +/// The size of the operands being operated on. +enum Size { + Size32 = 0b10, + Size64 = 0b11, +} + +/// A convenience function so that we can convert the number of bits of an +/// register operand directly into an Sf enum variant. +impl From for Size { + fn from(num_bits: u8) -> Self { + match num_bits { + 64 => Size::Size64, + 32 => Size::Size32, + _ => panic!("Invalid number of bits: {}", num_bits) + } + } +} + +/// The struct that represents an A64 data processing -- immediate instruction +/// that can be encoded. +/// +/// LDUR +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | +/// | 1 1 1 0 0 0 0 1 0 0 0 | +/// | size. imm9.......................... rn.............. rt.............. | +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// +pub struct Load { + /// The number of the register to load the value into. + rt: u8, + + /// The base register with which to form the address. + rn: u8, + + /// The optional signed immediate byte offset from the base register. + imm9: i16, + + /// The size of the operands being operated on. + size: Size +} + +impl Load { + /// LDUR (load register, unscaled) + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDUR--Load-Register--unscaled--?lang=en + pub fn ldur(rt: u8, rn: u8, imm9: i16, num_bits: u8) -> Self { + Self { + rt, + rn, + imm9, + size: num_bits.into() + } + } +} + +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Loads-and-Stores?lang=en +const FAMILY: u32 = 0b0100; + +impl From for u32 { + /// Convert an instruction into a 32-bit value. + fn from(inst: Load) -> Self { + let imm9 = (inst.imm9 as u32) & ((1 << 9) - 1); + + 0 + | ((inst.size as u32) << 30) + | (0b11 << 28) + | (FAMILY << 25) + | (1 << 22) + | (imm9 << 12) + | ((inst.rn as u32) << 5) + | (inst.rt as u32) + } +} + +impl From for [u8; 4] { + /// Convert an instruction into a 4 byte array. + fn from(inst: Load) -> [u8; 4] { + let result: u32 = inst.into(); + result.to_le_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ldur() { + let inst = Load::ldur(0, 1, 0, 64); + let result: u32 = inst.into(); + assert_eq!(0xf8400020, result); + } + + #[test] + fn test_ldur_with_imm() { + let inst = Load::ldur(0, 1, 123, 64); + let result: u32 = inst.into(); + assert_eq!(0xf847b020, result); + } +} diff --git a/yjit/src/asm/arm64/inst/loads_and_stores.rs b/yjit/src/asm/arm64/inst/loads_and_stores.rs deleted file mode 100644 index 5fb8b7a6fb..0000000000 --- a/yjit/src/asm/arm64/inst/loads_and_stores.rs +++ /dev/null @@ -1,99 +0,0 @@ -use super::family::Family; - -/// The size of the operands being operated on. -enum Size { - Size32 = 0b10, - Size64 = 0b11, -} - -/// A convenience function so that we can convert the number of bits of an -/// register operand directly into an Sf enum variant. -impl From for Size { - fn from(num_bits: u8) -> Self { - match num_bits { - 64 => Size::Size64, - 32 => Size::Size32, - _ => panic!("Invalid number of bits: {}", num_bits) - } - } -} - -/// The struct that represents an A64 data processing -- immediate instruction -/// that can be encoded. -/// -/// LDUR -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | -/// | 1 1 1 0 0 0 0 1 0 0 0 | -/// | size. imm9.......................... rn.............. rt.............. | -/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ -/// -pub struct LoadsAndStores { - /// The number of the register to load the value into. - rt: u8, - - /// The base register with which to form the address. - rn: u8, - - /// The optional signed immediate byte offset from the base register. - imm9: i16, - - /// The size of the operands being operated on. - size: Size -} - -impl LoadsAndStores { - /// LDUR (load register, unscaled) - /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDUR--Load-Register--unscaled--?lang=en - pub fn ldur(rt: u8, rn: u8, imm9: i16, num_bits: u8) -> Self { - Self { - rt, - rn, - imm9, - size: num_bits.into() - } - } -} - -impl From for u32 { - /// Convert an instruction into a 32-bit value. - fn from(inst: LoadsAndStores) -> Self { - let imm9 = (inst.imm9 as u32) & ((1 << 9) - 1); - - 0 - | ((inst.size as u32) << 30) - | (0b11 << 28) - | ((Family::LoadsAndStores as u32) << 25) - | (1 << 22) - | (imm9 << 12) - | ((inst.rn as u32) << 5) - | (inst.rt as u32) - } -} - -impl From for [u8; 4] { - /// Convert an instruction into a 4 byte array. - fn from(inst: LoadsAndStores) -> [u8; 4] { - let result: u32 = inst.into(); - result.to_le_bytes() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ldur() { - let inst = LoadsAndStores::ldur(0, 1, 0, 64); - let result: u32 = inst.into(); - assert_eq!(0xf8400020, result); - } - - #[test] - fn test_ldur_with_imm() { - let inst = LoadsAndStores::ldur(0, 1, 123, 64); - let result: u32 = inst.into(); - assert_eq!(0xf847b020, result); - } -} diff --git a/yjit/src/asm/arm64/inst/mod.rs b/yjit/src/asm/arm64/inst/mod.rs index 9c5b53b0ac..eec9d116b2 100644 --- a/yjit/src/asm/arm64/inst/mod.rs +++ b/yjit/src/asm/arm64/inst/mod.rs @@ -1,19 +1,20 @@ -mod branches_and_system; -mod data_processing_immediate; -mod data_processing_register; -mod family; -mod loads_and_stores; +mod branch; +mod data_imm; +mod data_reg; +mod load; +mod mov; mod sf; -use branches_and_system::BranchesAndSystem; -use data_processing_immediate::DataProcessingImmediate; -use data_processing_register::DataProcessingRegister; -use loads_and_stores::LoadsAndStores; +use branch::Branch; +use data_imm::DataImm; +use data_reg::DataReg; +use load::Load; +use mov::Mov; use crate::asm::{CodeBlock, imm_num_bits}; use super::opnd::*; -/// ADD +/// ADD - add rn and rm, put the result in rd, don't update flags pub fn add(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { let bytes: [u8; 4] = match (rd, rn, rm) { (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => { @@ -22,13 +23,13 @@ pub fn add(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { "All operands must be of the same size." ); - DataProcessingRegister::add(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() + DataReg::add(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() }, (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(imm12)) => { assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); assert!(imm12.num_bits <= 12, "The immediate operand must be 12 bits or less."); - DataProcessingImmediate::add(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() + DataImm::add(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() }, _ => panic!("Invalid operand combination to add instruction."), }; @@ -36,7 +37,7 @@ pub fn add(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { cb.write_bytes(&bytes); } -/// ADDS +/// ADDS - add rn and rm, put the result in rd, update flags pub fn adds(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { let bytes: [u8; 4] = match (rd, rn, rm) { (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => { @@ -45,13 +46,13 @@ pub fn adds(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { "All operands must be of the same size." ); - DataProcessingRegister::adds(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() + DataReg::adds(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() }, (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(imm12)) => { assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); assert!(imm12.num_bits <= 12, "The immediate operand must be 12 bits or less."); - DataProcessingImmediate::adds(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() + DataImm::adds(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() }, _ => panic!("Invalid operand combination to adds instruction."), }; @@ -59,14 +60,24 @@ pub fn adds(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { cb.write_bytes(&bytes); } -/// LDUR +/// BR - branch to a register +pub fn br(cb: &mut CodeBlock, rn: A64Opnd) { + let bytes: [u8; 4] = match rn { + A64Opnd::Reg(rn) => Branch::br(rn.reg_no).into(), + _ => panic!("Invalid operand to br instruction."), + }; + + cb.write_bytes(&bytes); +} + +/// LDUR - load a memory address into a register pub fn ldur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt, rn) { (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { assert!(rt.num_bits == rn.num_bits, "Expected registers to be the same size"); assert!(imm_num_bits(rn.disp.into()) <= 9, "Expected displacement to be 9 bits or less"); - LoadsAndStores::ldur(rt.reg_no, rn.base_reg_no, rn.disp.try_into().unwrap(), rt.num_bits).into() + Load::ldur(rt.reg_no, rn.base_reg_no, rn.disp.try_into().unwrap(), rt.num_bits).into() }, _ => panic!("Invalid operands for LDUR") }; @@ -74,7 +85,35 @@ pub fn ldur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { cb.write_bytes(&bytes); } -/// SUB +/// MOVK - move a 16 bit immediate into a register, keep the other bits in place +pub fn movk(cb: &mut CodeBlock, rd: A64Opnd, imm16: A64Opnd, shift: u8) { + let bytes: [u8; 4] = match (rd, imm16) { + (A64Opnd::Reg(rd), A64Opnd::UImm(imm16)) => { + assert!(imm16.num_bits <= 16, "The immediate operand must be 16 bits or less."); + + Mov::movk(rd.reg_no, imm16.value as u16, shift, rd.num_bits).into() + }, + _ => panic!("Invalid operand combination to movk instruction.") + }; + + cb.write_bytes(&bytes); +} + +/// MOVZ - move a 16 bit immediate into a register, zero the other bits +pub fn movz(cb: &mut CodeBlock, rd: A64Opnd, imm16: A64Opnd, shift: u8) { + let bytes: [u8; 4] = match (rd, imm16) { + (A64Opnd::Reg(rd), A64Opnd::UImm(imm16)) => { + assert!(imm16.num_bits <= 16, "The immediate operand must be 16 bits or less."); + + Mov::movz(rd.reg_no, imm16.value as u16, shift, rd.num_bits).into() + }, + _ => panic!("Invalid operand combination to movz instruction.") + }; + + cb.write_bytes(&bytes); +} + +/// SUB - subtract rm from rn, put the result in rd, don't update flags pub fn sub(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { let bytes: [u8; 4] = match (rd, rn, rm) { (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => { @@ -83,13 +122,13 @@ pub fn sub(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { "All operands must be of the same size." ); - DataProcessingRegister::sub(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() + DataReg::sub(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() }, (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(imm12)) => { assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); assert!(imm12.num_bits <= 12, "The immediate operand must be 12 bits or less."); - DataProcessingImmediate::sub(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() + DataImm::sub(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() }, _ => panic!("Invalid operand combination to sub instruction."), }; @@ -97,7 +136,7 @@ pub fn sub(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { cb.write_bytes(&bytes); } -/// SUBS +/// SUBS - subtract rm from rn, put the result in rd, update flags pub fn subs(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { let bytes: [u8; 4] = match (rd, rn, rm) { (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => { @@ -106,13 +145,13 @@ pub fn subs(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { "All operands must be of the same size." ); - DataProcessingRegister::subs(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() + DataReg::subs(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() }, (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(imm12)) => { assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); assert!(imm12.num_bits <= 12, "The immediate operand must be 12 bits or less."); - DataProcessingImmediate::subs(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() + DataImm::subs(rd.reg_no, rn.reg_no, imm12.value as u16, rd.num_bits).into() }, _ => panic!("Invalid operand combination to subs instruction."), }; @@ -120,13 +159,95 @@ pub fn subs(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { cb.write_bytes(&bytes); } -/// RET +/// RET - unconditionally return to a location in a register, defaults to X30 pub fn ret(cb: &mut CodeBlock, rn: A64Opnd) { let bytes: [u8; 4] = match rn { - A64Opnd::None => BranchesAndSystem::ret(30).into(), - A64Opnd::Reg(reg) => BranchesAndSystem::ret(reg.reg_no).into(), - _ => panic!("Invalid operand for RET") + A64Opnd::None => Branch::ret(30).into(), + A64Opnd::Reg(reg) => Branch::ret(reg.reg_no).into(), + _ => panic!("Invalid operand to ret instruction.") }; cb.write_bytes(&bytes); } + +#[cfg(test)] +mod tests { + use super::*; + + /// Check that the bytes for an instruction sequence match a hex string + fn check_bytes(bytes: &str, run: R) where R: FnOnce(&mut super::CodeBlock) { + let mut cb = super::CodeBlock::new_dummy(128); + run(&mut cb); + assert_eq!(format!("{:x}", cb), bytes); + } + + #[test] + fn test_add_register() { + check_bytes("2000028b", |cb| add(cb, X0, X1, X2)); + } + + #[test] + fn test_add_immediate() { + check_bytes("201c0091", |cb| add(cb, X0, X1, A64Opnd::new_uimm(7))); + } + + #[test] + fn test_adds_register() { + check_bytes("200002ab", |cb| adds(cb, X0, X1, X2)); + } + + #[test] + fn test_adds_immediate() { + check_bytes("201c00b1", |cb| adds(cb, X0, X1, A64Opnd::new_uimm(7))); + } + + #[test] + fn test_br() { + check_bytes("80021fd6", |cb| br(cb, X20)); + } + + #[test] + fn test_ldur() { + check_bytes("20b047f8", |cb| ldur(cb, X0, A64Opnd::new_mem(X1, 123))); + } + + #[test] + fn test_movk() { + check_bytes("600fa0f2", |cb| movk(cb, X0, A64Opnd::new_uimm(123), 16)); + } + + #[test] + fn test_movz() { + check_bytes("600fa0d2", |cb| movz(cb, X0, A64Opnd::new_uimm(123), 16)); + } + + #[test] + fn test_ret_none() { + check_bytes("c0035fd6", |cb| ret(cb, A64Opnd::None)); + } + + #[test] + fn test_ret_register() { + check_bytes("80025fd6", |cb| ret(cb, X20)); + } + + #[test] + fn test_sub_register() { + check_bytes("200002cb", |cb| sub(cb, X0, X1, X2)); + } + + #[test] + fn test_sub_immediate() { + check_bytes("201c00d1", |cb| sub(cb, X0, X1, A64Opnd::new_uimm(7))); + } + + #[test] + fn test_subs_register() { + check_bytes("200002eb", |cb| subs(cb, X0, X1, X2)); + } + + #[test] + fn test_subs_immediate() { + check_bytes("201c00f1", |cb| subs(cb, X0, X1, A64Opnd::new_uimm(7))); + } +} diff --git a/yjit/src/asm/arm64/inst/mov.rs b/yjit/src/asm/arm64/inst/mov.rs new file mode 100644 index 0000000000..0d68ffd206 --- /dev/null +++ b/yjit/src/asm/arm64/inst/mov.rs @@ -0,0 +1,155 @@ +use super::sf::Sf; + +/// Which operation is being performed. +enum Op { + /// A movz operation which zeroes out the other bits. + MOVZ = 0b10, + + /// A movk operation which keeps the other bits in place. + MOVK = 0b11 +} + +/// How much to shift the immediate by. +enum Hw { + LSL0 = 0b00, + LSL16 = 0b01, + LSL32 = 0b10, + LSL48 = 0b11 +} + +impl From for Hw { + fn from(shift: u8) -> Self { + match shift { + 0 => Hw::LSL0, + 16 => Hw::LSL16, + 32 => Hw::LSL32, + 48 => Hw::LSL48, + _ => panic!("Invalid value for shift: {}", shift) + } + } +} + +/// The struct that represents a MOVK or MOVZ instruction. +/// +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 | +/// | 1 0 0 1 0 1 | +/// | sf op... hw... imm16.................................................. rd.............. | +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// +pub struct Mov { + /// The register number of the destination register. + rd: u8, + + /// The value to move into the register. + imm16: u16, + + /// The shift of the value to move. + hw: Hw, + + /// Which operation is being performed. + op: Op, + + /// Whether or not this instruction is operating on 64-bit operands. + sf: Sf +} + +impl Mov { + /// MOVK + /// https://developer.arm.com/documentation/ddi0602/2022-03/Base-Instructions/MOVK--Move-wide-with-keep-?lang=en + pub fn movk(rd: u8, imm16: u16, hw: u8, num_bits: u8) -> Self { + Self { rd, imm16, hw: hw.into(), op: Op::MOVK, sf: num_bits.into() } + } + + /// MOVZ + /// https://developer.arm.com/documentation/ddi0602/2022-03/Base-Instructions/MOVZ--Move-wide-with-zero-?lang=en + pub fn movz(rd: u8, imm16: u16, hw: u8, num_bits: u8) -> Self { + Self { rd, imm16, hw: hw.into(), op: Op::MOVZ, sf: num_bits.into() } + } +} + +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Immediate?lang=en +const FAMILY: u32 = 0b1000; + +impl From for u32 { + /// Convert an instruction into a 32-bit value. + fn from(inst: Mov) -> Self { + 0 + | ((inst.sf as u32) << 31) + | ((inst.op as u32) << 29) + | (FAMILY << 25) + | (0b101 << 23) + | ((inst.hw as u32) << 21) + | ((inst.imm16 as u32) << 5) + | inst.rd as u32 + } +} + +impl From for [u8; 4] { + /// Convert an instruction into a 4 byte array. + fn from(inst: Mov) -> [u8; 4] { + let result: u32 = inst.into(); + result.to_le_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_movk_unshifted() { + let inst = Mov::movk(0, 123, 0, 64); + let result: u32 = inst.into(); + assert_eq!(0xf2800f60, result); + } + + #[test] + fn test_movk_shifted_16() { + let inst = Mov::movk(0, 123, 16, 64); + let result: u32 = inst.into(); + assert_eq!(0xf2A00f60, result); + } + + #[test] + fn test_movk_shifted_32() { + let inst = Mov::movk(0, 123, 32, 64); + let result: u32 = inst.into(); + assert_eq!(0xf2C00f60, result); + } + + #[test] + fn test_movk_shifted_48() { + let inst = Mov::movk(0, 123, 48, 64); + let result: u32 = inst.into(); + assert_eq!(0xf2e00f60, result); + } + + #[test] + fn test_movz_unshifted() { + let inst = Mov::movz(0, 123, 0, 64); + let result: u32 = inst.into(); + assert_eq!(0xd2800f60, result); + } + + #[test] + fn test_movz_shifted_16() { + let inst = Mov::movz(0, 123, 16, 64); + let result: u32 = inst.into(); + assert_eq!(0xd2a00f60, result); + } + + #[test] + fn test_movz_shifted_32() { + let inst = Mov::movz(0, 123, 32, 64); + let result: u32 = inst.into(); + assert_eq!(0xd2c00f60, result); + } + + #[test] + fn test_movz_shifted_48() { + let inst = Mov::movz(0, 123, 48, 64); + let result: u32 = inst.into(); + assert_eq!(0xd2e00f60, result); + } +} diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs index 9f518398b7..b54fc362b4 100644 --- a/yjit/src/asm/mod.rs +++ b/yjit/src/asm/mod.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::mem; #[cfg(feature = "asm_comments")] @@ -276,6 +277,17 @@ impl CodeBlock { } } +/// Produce hex string output from the bytes in a code block +impl<'a> fmt::LowerHex for CodeBlock { + fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { + for pos in 0..self.write_pos { + let byte = unsafe { self.mem_block.start_ptr().raw_ptr().add(pos).read() }; + fmtr.write_fmt(format_args!("{:02x}", byte))?; + } + Ok(()) + } +} + /// Wrapper struct so we can use the type system to distinguish /// Between the inlined and outlined code blocks pub struct OutlinedCb { -- cgit v1.2.3