ruby-changes:73268
From: Kevin <ko1@a...>
Date: Tue, 30 Aug 2022 01:05:16 +0900 (JST)
Subject: [ruby-changes:73268] b8846dd2f8 (master): Load mem displacement when necessary on AArch64 (https://github.com/Shopify/ruby/pull/382)
https://git.ruby-lang.org/ruby.git/commit/?id=b8846dd2f8 From b8846dd2f8042fc13a0f5ae17e2e2a6f400074dd Mon Sep 17 00:00:00 2001 From: Kevin Newton <kddnewton@g...> Date: Tue, 9 Aug 2022 10:27:21 -0400 Subject: Load mem displacement when necessary on AArch64 (https://github.com/Shopify/ruby/pull/382) * LDR instruction for AArch64 * Split loads in arm64_split when memory address displacements do not fit --- yjit/src/asm/arm64/inst/load_literal.rs | 6 +- yjit/src/asm/arm64/inst/load_register.rs | 108 ++++++++++++++++++++++ yjit/src/asm/arm64/inst/mod.rs | 2 + yjit/src/asm/arm64/mod.rs | 46 ++++++++-- yjit/src/backend/arm64/mod.rs | 152 +++++++++++++++++++++++-------- 5 files changed, 261 insertions(+), 53 deletions(-) create mode 100644 yjit/src/asm/arm64/inst/load_register.rs diff --git a/yjit/src/asm/arm64/inst/load_literal.rs b/yjit/src/asm/arm64/inst/load_literal.rs index a49130c3eb..d2a5d57eea 100644 --- a/yjit/src/asm/arm64/inst/load_literal.rs +++ b/yjit/src/asm/arm64/inst/load_literal.rs @@ -39,7 +39,7 @@ pub struct LoadLiteral { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/load_literal.rs#L39 impl LoadLiteral { /// LDR (load literal) /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--literal---Load-Register--literal--?lang=en - pub fn ldr(rt: u8, imm19: i32, num_bits: u8) -> Self { + pub fn ldr_literal(rt: u8, imm19: i32, num_bits: u8) -> Self { Self { rt, imm19, opc: num_bits.into() } } } @@ -75,14 +75,14 @@ mod tests { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/load_literal.rs#L75 #[test] fn test_ldr_positive() { - let inst = LoadLiteral::ldr(0, 5, 64); + let inst = LoadLiteral::ldr_literal(0, 5, 64); let result: u32 = inst.into(); assert_eq!(0x580000a0, result); } #[test] fn test_ldr_negative() { - let inst = LoadLiteral::ldr(0, -5, 64); + let inst = LoadLiteral::ldr_literal(0, -5, 64); let result: u32 = inst.into(); assert_eq!(0x58ffff60, result); } diff --git a/yjit/src/asm/arm64/inst/load_register.rs b/yjit/src/asm/arm64/inst/load_register.rs new file mode 100644 index 0000000000..3426b9ba5f --- /dev/null +++ b/yjit/src/asm/arm64/inst/load_register.rs @@ -0,0 +1,108 @@ https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/load_register.rs#L1 +/// Whether or not to shift the register. +enum S { + Shift = 1, + NoShift = 0 +} + +/// The option for this instruction. +enum Option { + UXTW = 0b010, + LSL = 0b011, + SXTW = 0b110, + SXTX = 0b111 +} + +/// The size of the operands of this instruction. +enum Size { + Size32 = 0b10, + Size64 = 0b11 +} + +/// A convenience function so that we can convert the number of bits of an +/// register operand directly into a Size enum variant. +impl From<u8> 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 load instruction that can be encoded. +/// +/// LDR +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// | 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 1 1 0 | +/// | size. rm.............. option.. S rn.............. rt.............. | +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// +pub struct LoadRegister { + /// The number of the register to load the value into. + rt: u8, + + /// The base register with which to form the address. + rn: u8, + + /// Whether or not to shift the value of the register. + s: S, + + /// The option associated with this instruction that controls the shift. + option: Option, + + /// The number of the offset register. + rm: u8, + + /// The size of the operands. + size: Size +} + +impl LoadRegister { + /// LDR + /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--register---Load-Register--register--?lang=en + pub fn ldr(rt: u8, rn: u8, rm: u8, num_bits: u8) -> Self { + Self { rt, rn, s: S::NoShift, option: Option::LSL, rm, 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<LoadRegister> for u32 { + /// Convert an instruction into a 32-bit value. + fn from(inst: LoadRegister) -> Self { + 0 + | ((inst.size as u32) << 30) + | (0b11 << 28) + | (FAMILY << 25) + | (0b11 << 21) + | ((inst.rm as u32) << 16) + | ((inst.option as u32) << 13) + | ((inst.s as u32) << 12) + | (0b10 << 10) + | ((inst.rn as u32) << 5) + | (inst.rt as u32) + } +} + +impl From<LoadRegister> for [u8; 4] { + /// Convert an instruction into a 4 byte array. + fn from(inst: LoadRegister) -> [u8; 4] { + let result: u32 = inst.into(); + result.to_le_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ldr() { + let inst = LoadRegister::ldr(0, 1, 2, 64); + let result: u32 = inst.into(); + assert_eq!(0xf8626820, result); + } +} diff --git a/yjit/src/asm/arm64/inst/mod.rs b/yjit/src/asm/arm64/inst/mod.rs index 42df2d137a..ab41464013 100644 --- a/yjit/src/asm/arm64/inst/mod.rs +++ b/yjit/src/asm/arm64/inst/mod.rs @@ -10,6 +10,7 @@ mod conditional; https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/mod.rs#L10 mod data_imm; mod data_reg; mod load_literal; +mod load_register; mod load_store; mod logical_imm; mod logical_reg; @@ -30,6 +31,7 @@ pub use conditional::Conditional; https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/mod.rs#L31 pub use data_imm::DataImm; pub use data_reg::DataReg; pub use load_literal::LoadLiteral; +pub use load_register::LoadRegister; pub use load_store::LoadStore; pub use logical_imm::LogicalImm; pub use logical_reg::LogicalReg; diff --git a/yjit/src/asm/arm64/mod.rs b/yjit/src/asm/arm64/mod.rs index 68be36c256..93b44dba4b 100644 --- a/yjit/src/asm/arm64/mod.rs +++ b/yjit/src/asm/arm64/mod.rs @@ -374,11 +374,26 @@ pub fn ldp_post(cb: &mut CodeBlock, rt1: A64Opnd, rt2: A64Opnd, rn: A64Opnd) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L374 cb.write_bytes(&bytes); } +/// LDR - load a memory address into a register with a register offset +pub fn ldr(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd, rm: A64Opnd) { + let bytes: [u8; 4] = match (rt, rn, rm) { + (A64Opnd::Reg(rt), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => { + assert!(rt.num_bits == rn.num_bits, "Expected registers to be the same size"); + assert!(rn.num_bits == rm.num_bits, "Expected registers to be the same size"); + + LoadRegister::ldr(rt.reg_no, rn.reg_no, rm.reg_no, rt.num_bits).into() + }, + _ => panic!("Invalid operand combination to ldr instruction.") + }; + + cb.write_bytes(&bytes); +} + /// LDR - load a PC-relative memory address into a register -pub fn ldr(cb: &mut CodeBlock, rt: A64Opnd, rn: i32) { +pub fn ldr_literal(cb: &mut CodeBlock, rt: A64Opnd, rn: i32) { let bytes: [u8; 4] = match rt { A64Opnd::Reg(rt) => { - LoadLiteral::ldr(rt.reg_no, rn, rt.num_bits).into() + LoadLiteral::ldr_literal(rt.reg_no, rn, rt.num_bits).into() }, _ => panic!("Invalid operand combination to ldr instruction."), }; @@ -386,12 +401,18 @@ pub fn ldr(cb: &mut CodeBlock, rt: A64Opnd, rn: i32) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L401 cb.write_bytes(&bytes); } +/// Whether or not a memory address displacement fits into the maximum number of +/// bits such that it can be used without loading it into a register first. +pub fn mem_disp_fits_bits(disp: i32) -> bool { + imm_fits_bits(disp.into(), 9) +} + /// LDR (post-index) - load a register from memory, update the base pointer after loading it pub fn ldr_post(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, "All operands must be of the same size."); - assert!(imm_fits_bits(rn.disp.into(), 9), "The displacement must be 9 bits or less."); + assert!(mem_disp_fits_bits(rn.disp), "The displacement must be 9 bits or less."); LoadStore::ldr_post(rt.reg_no, rn.base_reg_no, rn.disp as i16, rt.num_bits).into() }, @@ -406,7 +427,7 @@ pub fn ldr_pre(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L427 let bytes: [u8; 4] = match (rt, rn) { (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { assert!(rt.num_bits == rn.num_bits, "All operands must be of the same size."); - assert!(imm_fits_bits(rn.disp.into(), 9), "The displacement must be 9 bits or less."); + assert!(mem_disp_fits_bits(rn.disp), "The displacement must be 9 bits or less."); LoadStore::ldr_pre(rt.reg_no, rn.base_reg_no, rn.disp as i16, rt.num_bits).into() }, @@ -426,7 +447,7 @@ pub fn ldur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L447 }, (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { assert!(rt.num_bits == rn.num_bits, "Expected registers to be the same size"); - assert!(imm_fits_bits(rn.di (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/