summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Newton <kddnewton@gmail.com>2022-06-08 15:19:53 -0400
committerTakashi Kokubun <takashikkbn@gmail.com>2022-08-29 08:46:55 -0700
commit1daa5942b83ede3e504f9952a1f705b763e59893 (patch)
tree1153d21c0bd01ddd54c3c45c8752def4cab3eaaf
parent0000984fed1be885ad51845477f4e475d1b07fab (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.rs85
-rw-r--r--yjit/src/asm/arm64/inst/branches_and_system.rs62
-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.rs34
-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.rs175
-rw-r--r--yjit/src/asm/arm64/inst/mov.rs155
-rw-r--r--yjit/src/asm/mod.rs12
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 {