ruby-changes:73173
From: Kevin <ko1@a...>
Date: Tue, 30 Aug 2022 00:57:59 +0900 (JST)
Subject: [ruby-changes:73173] 6773832ab9 (master): More Arm64 lowering/backend work (https://github.com/Shopify/ruby/pull/307)
https://git.ruby-lang.org/ruby.git/commit/?id=6773832ab9 From 6773832ab9cad3c7bcb3b93ef85a4bcfc9b3a4e3 Mon Sep 17 00:00:00 2001 From: Kevin Newton <kddnewton@g...> Date: Fri, 8 Jul 2022 13:01:21 -0400 Subject: More Arm64 lowering/backend work (https://github.com/Shopify/ruby/pull/307) * More Arm64 lowering/backend work * We now have encoding support for the LDR instruction for loading a PC-relative memory location * You can now call add/adds/sub/subs with signed immediates, which switches appropriately based on sign * We can now load immediates into registers appropriately, attempting to keep the minimal number of instructions: * If it fits into 16 bytes, we use just a single movz. * Else if it can be encoded into a bitmask immediate, we use a single mov. * Otherwise we use a movz, a movk, and then optionally another one or two movks. * Fixed a bunch of code to do with the Op::Load opcode. * We now handle GC-offsets properly for Op::Load by skipping around them with a jump instruction. (This will be made better by constant pools in the future.) * Op::Lea is doing what it's supposed to do now. * Fixed a bug in the backend tests to do with not using the result of an Op::Add. * Fix the remaining tests for Arm64 * Move split loads logic into each backend --- yjit/src/asm/arm64/inst/load_literal.rs | 89 +++++++++++++++++ yjit/src/asm/arm64/inst/mod.rs | 2 + yjit/src/asm/arm64/mod.rs | 131 ++++++++++++++++++++---- yjit/src/asm/arm64/opnd.rs | 1 + yjit/src/asm/mod.rs | 2 +- yjit/src/backend/arm64/mod.rs | 172 +++++++++++++++++++++++++------- yjit/src/backend/ir.rs | 65 ------------ yjit/src/backend/tests.rs | 27 ++--- yjit/src/backend/x86_64/mod.rs | 105 ++++++++++++++----- 9 files changed, 431 insertions(+), 163 deletions(-) create mode 100644 yjit/src/asm/arm64/inst/load_literal.rs diff --git a/yjit/src/asm/arm64/inst/load_literal.rs b/yjit/src/asm/arm64/inst/load_literal.rs new file mode 100644 index 0000000000..a49130c3eb --- /dev/null +++ b/yjit/src/asm/arm64/inst/load_literal.rs @@ -0,0 +1,89 @@ https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/load_literal.rs#L1 +/// The size of the operands being operated on. +enum Opc { + Size32 = 0b00, + Size64 = 0b01, +} + +/// A convenience function so that we can convert the number of bits of an +/// register operand directly into an Sf enum variant. +impl From<u8> for Opc { + fn from(num_bits: u8) -> Self { + match num_bits { + 64 => Opc::Size64, + 32 => Opc::Size32, + _ => panic!("Invalid number of bits: {}", num_bits) + } + } +} + +/// The struct that represents an A64 load literal 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 | +/// | 0 1 1 0 0 0 | +/// | opc.. imm19........................................................... rt.............. | +/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ +/// +pub struct LoadLiteral { + /// The number of the register to load the value into. + rt: u8, + + /// The PC-relative number of instructions to load the value from. + imm19: i32, + + /// The size of the operands being operated on. + opc: Opc +} + +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 { + Self { rt, imm19, opc: 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<LoadLiteral> for u32 { + /// Convert an instruction into a 32-bit value. + fn from(inst: LoadLiteral) -> Self { + let imm19 = (inst.imm19 as u32) & ((1 << 19) - 1); + + 0 + | ((inst.opc as u32) << 30) + | (1 << 28) + | (FAMILY << 25) + | (imm19 << 5) + | (inst.rt as u32) + } +} + +impl From<LoadLiteral> for [u8; 4] { + /// Convert an instruction into a 4 byte array. + fn from(inst: LoadLiteral) -> [u8; 4] { + let result: u32 = inst.into(); + result.to_le_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ldr_positive() { + let inst = LoadLiteral::ldr(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 result: u32 = inst.into(); + assert_eq!(0x58ffff60, result); + } +} diff --git a/yjit/src/asm/arm64/inst/mod.rs b/yjit/src/asm/arm64/inst/mod.rs index ae589ca564..f402f6765a 100644 --- a/yjit/src/asm/arm64/inst/mod.rs +++ b/yjit/src/asm/arm64/inst/mod.rs @@ -9,6 +9,7 @@ mod call; https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/mod.rs#L9 mod data_imm; mod data_reg; mod load; +mod load_literal; mod logical_imm; mod logical_reg; mod mov; @@ -24,6 +25,7 @@ pub use call::Call; https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/inst/mod.rs#L25 pub use data_imm::DataImm; pub use data_reg::DataReg; pub use load::Load; +pub use load_literal::LoadLiteral; pub use logical_imm::LogicalImm; pub use logical_reg::LogicalReg; pub use mov::Mov; diff --git a/yjit/src/asm/arm64/mod.rs b/yjit/src/asm/arm64/mod.rs index ced8b262c5..2dc5aa9388 100644 --- a/yjit/src/asm/arm64/mod.rs +++ b/yjit/src/asm/arm64/mod.rs @@ -39,11 +39,21 @@ pub fn add(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L39 DataReg::add(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() }, - (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(imm12)) => { + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(uimm12)) => { assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); - assert!(uimm_fits_bits(imm12, 12), "The immediate operand must be 12 bits or less."); + assert!(uimm_fits_bits(uimm12, 12), "The immediate operand must be 12 bits or less."); - DataImm::add(rd.reg_no, rn.reg_no, imm12 as u16, rd.num_bits).into() + DataImm::add(rd.reg_no, rn.reg_no, uimm12 as u16, rd.num_bits).into() + }, + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Imm(imm12)) => { + assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); + assert!(imm_fits_bits(imm12, 12), "The immediate operand must be 12 bits or less."); + + if imm12 < 0 { + DataImm::sub(rd.reg_no, rn.reg_no, -imm12 as u16, rd.num_bits).into() + } else { + DataImm::add(rd.reg_no, rn.reg_no, imm12 as u16, rd.num_bits).into() + } }, _ => panic!("Invalid operand combination to add instruction."), }; @@ -68,6 +78,16 @@ pub fn adds(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L78 DataImm::adds(rd.reg_no, rn.reg_no, imm12 as u16, rd.num_bits).into() }, + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Imm(imm12)) => { + assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); + assert!(imm_fits_bits(imm12, 12), "The immediate operand must be 12 bits or less."); + + if imm12 < 0 { + DataImm::subs(rd.reg_no, rn.reg_no, -imm12 as u16, rd.num_bits).into() + } else { + DataImm::adds(rd.reg_no, rn.reg_no, imm12 as u16, rd.num_bits).into() + } + }, _ => panic!("Invalid operand combination to adds instruction."), }; @@ -237,6 +257,18 @@ pub fn ldaddal(cb: &mut CodeBlock, rs: A64Opnd, rt: A64Opnd, rn: A64Opnd) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L257 cb.write_bytes(&bytes); } +/// LDR - load a PC-relative memory address into a register +pub fn ldr(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() + }, + _ => panic!("Invalid operand combination to ldr 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) { @@ -415,11 +447,21 @@ pub fn sub(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) { https://github.com/ruby/ruby/blob/trunk/yjit/src/asm/arm64/mod.rs#L447 DataReg::sub(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into() }, - (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(imm12)) => { + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::UImm(uimm12)) => { assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the same size."); - assert!(uimm_fits_bits(imm12, 12), "The immediate operand must be 12 bits or less."); + assert!(uimm_fits_bits(uimm12, 12), "The immediate operand must be 12 bits or less."); - DataImm::sub(rd.reg_no, rn.reg_no, imm12 as u16, rd.num_bits).into() + DataImm::sub(rd.reg_no, rn.reg_no, uimm12 as u16, rd.num_bits).into() + }, + (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Imm(imm12)) => { + assert!(rd.num_bits == rn.num_bits, "rd and rn must be of the (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/