diff options
author | Kevin Newton <kddnewton@gmail.com> | 2022-06-08 15:19:53 -0400 |
---|---|---|
committer | Takashi Kokubun <takashikkbn@gmail.com> | 2022-08-29 08:46:55 -0700 |
commit | 1daa5942b83ede3e504f9952a1f705b763e59893 (patch) | |
tree | 1153d21c0bd01ddd54c3c45c8752def4cab3eaaf | |
parent | 0000984fed1be885ad51845477f4e475d1b07fab (diff) |
MOVK, MOVZ, BR (https://github.com/Shopify/ruby/pull/296)
* MOVK instruction
* More tests for the A64 entrypoints
* Finish testing entrypoints
* MOVZ
* BR instruction
-rw-r--r-- | yjit/src/asm/arm64/inst/branch.rs | 85 | ||||
-rw-r--r-- | yjit/src/asm/arm64/inst/branches_and_system.rs | 62 | ||||
-rw-r--r-- | yjit/src/asm/arm64/inst/data_imm.rs (renamed from yjit/src/asm/arm64/inst/data_processing_immediate.rs) | 27 | ||||
-rw-r--r-- | yjit/src/asm/arm64/inst/data_reg.rs (renamed from yjit/src/asm/arm64/inst/data_processing_register.rs) | 27 | ||||
-rw-r--r-- | yjit/src/asm/arm64/inst/family.rs | 34 | ||||
-rw-r--r-- | yjit/src/asm/arm64/inst/load.rs (renamed from yjit/src/asm/arm64/inst/loads_and_stores.rs) | 23 | ||||
-rw-r--r-- | yjit/src/asm/arm64/inst/mod.rs | 175 | ||||
-rw-r--r-- | yjit/src/asm/arm64/inst/mov.rs | 155 | ||||
-rw-r--r-- | yjit/src/asm/mod.rs | 12 |
9 files changed, 442 insertions, 158 deletions
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<Branch> 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<Branch> 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<BranchesAndSystem> 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<BranchesAndSystem> 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_processing_immediate.rs b/yjit/src/asm/arm64/inst/data_imm.rs index 25117efc22..0d0a6ff325 100644 --- a/yjit/src/asm/arm64/inst/data_processing_immediate.rs +++ b/yjit/src/asm/arm64/inst/data_imm.rs @@ -1,4 +1,4 @@ -use super::{family::Family, sf::Sf}; +use super::sf::Sf; /// The operation being performed by this instruction. enum Op { @@ -28,7 +28,7 @@ enum Shift { /// | sf op S sh imm12.................................... rn.............. rd.............. | /// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ /// -pub struct DataProcessingImmediate { +pub struct DataImm { /// The register number of the destination register. rd: u8, @@ -51,7 +51,7 @@ pub struct DataProcessingImmediate { sf: Sf } -impl DataProcessingImmediate { +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 { @@ -109,16 +109,19 @@ impl DataProcessingImmediate { } } -impl From<DataProcessingImmediate> for u32 { +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Immediate?lang=en +const FAMILY: u32 = 0b1000; + +impl From<DataImm> for u32 { /// Convert an instruction into a 32-bit value. - fn from(inst: DataProcessingImmediate) -> Self { + 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::DataProcessingImmediate as u32) << 25) + | (FAMILY << 25) | (1 << 24) | ((inst.shift as u32) << 22) | (imm12 << 10) @@ -127,9 +130,9 @@ impl From<DataProcessingImmediate> for u32 { } } -impl From<DataProcessingImmediate> for [u8; 4] { +impl From<DataImm> for [u8; 4] { /// Convert an instruction into a 4 byte array. - fn from(inst: DataProcessingImmediate) -> [u8; 4] { + fn from(inst: DataImm) -> [u8; 4] { let result: u32 = inst.into(); result.to_le_bytes() } @@ -141,28 +144,28 @@ mod tests { #[test] fn test_add() { - let inst = DataProcessingImmediate::add(0, 1, 7, 64); + let inst = DataImm::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 inst = DataImm::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 inst = DataImm::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 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_register.rs b/yjit/src/asm/arm64/inst/data_reg.rs index 7e9f37ab8e..8635ab804b 100644 --- a/yjit/src/asm/arm64/inst/data_processing_register.rs +++ b/yjit/src/asm/arm64/inst/data_reg.rs @@ -1,4 +1,4 @@ -use super::{family::Family, sf::Sf}; +use super::sf::Sf; /// The operation being performed by this instruction. enum Op { @@ -29,7 +29,7 @@ enum Shift { /// | sf op S shift rm.............. imm6............... rn.............. rd.............. | /// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ /// -pub struct DataProcessingRegister { +pub struct DataReg { /// The register number of the destination register. rd: u8, @@ -55,7 +55,7 @@ pub struct DataProcessingRegister { sf: Sf } -impl DataProcessingRegister { +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 { @@ -117,16 +117,19 @@ impl DataProcessingRegister { } } -impl From<DataProcessingRegister> for u32 { +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Data-Processing----Register?lang=en +const FAMILY: u32 = 0b0101; + +impl From<DataReg> for u32 { /// Convert an instruction into a 32-bit value. - fn from(inst: DataProcessingRegister) -> Self { + 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::DataProcessingRegister as u32) << 25) + | (FAMILY << 25) | (1 << 24) | ((inst.shift as u32) << 22) | ((inst.rm as u32) << 16) @@ -136,9 +139,9 @@ impl From<DataProcessingRegister> for u32 { } } -impl From<DataProcessingRegister> for [u8; 4] { +impl From<DataReg> for [u8; 4] { /// Convert an instruction into a 4 byte array. - fn from(inst: DataProcessingRegister) -> [u8; 4] { + fn from(inst: DataReg) -> [u8; 4] { let result: u32 = inst.into(); result.to_le_bytes() } @@ -150,28 +153,28 @@ mod tests { #[test] fn test_add() { - let inst = DataProcessingRegister::add(0, 1, 2, 64); + let inst = DataReg::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 inst = DataReg::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 inst = DataReg::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 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/loads_and_stores.rs b/yjit/src/asm/arm64/inst/load.rs index 5fb8b7a6fb..727dad52f7 100644 --- a/yjit/src/asm/arm64/inst/loads_and_stores.rs +++ b/yjit/src/asm/arm64/inst/load.rs @@ -1,5 +1,3 @@ -use super::family::Family; - /// The size of the operands being operated on. enum Size { Size32 = 0b10, @@ -28,7 +26,7 @@ impl From<u8> for Size { /// | size. imm9.......................... rn.............. rt.............. | /// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ /// -pub struct LoadsAndStores { +pub struct Load { /// The number of the register to load the value into. rt: u8, @@ -42,7 +40,7 @@ pub struct LoadsAndStores { size: Size } -impl LoadsAndStores { +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 { @@ -55,15 +53,18 @@ impl LoadsAndStores { } } -impl From<LoadsAndStores> for u32 { +/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Loads-and-Stores?lang=en +const FAMILY: u32 = 0b0100; + +impl From<Load> for u32 { /// Convert an instruction into a 32-bit value. - fn from(inst: LoadsAndStores) -> Self { + fn from(inst: Load) -> Self { let imm9 = (inst.imm9 as u32) & ((1 << 9) - 1); 0 | ((inst.size as u32) << 30) | (0b11 << 28) - | ((Family::LoadsAndStores as u32) << 25) + | (FAMILY << 25) | (1 << 22) | (imm9 << 12) | ((inst.rn as u32) << 5) @@ -71,9 +72,9 @@ impl From<LoadsAndStores> for u32 { } } -impl From<LoadsAndStores> for [u8; 4] { +impl From<Load> for [u8; 4] { /// Convert an instruction into a 4 byte array. - fn from(inst: LoadsAndStores) -> [u8; 4] { + fn from(inst: Load) -> [u8; 4] { let result: u32 = inst.into(); result.to_le_bytes() } @@ -85,14 +86,14 @@ mod tests { #[test] fn test_ldur() { - let inst = LoadsAndStores::ldur(0, 1, 0, 64); + 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 = LoadsAndStores::ldur(0, 1, 123, 64); + 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/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<R>(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<u8> 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<Mov> 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<Mov> 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 { |