Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ jobs:
- test_task: check
os: ubuntu-24.04
extra_checks: [capi]
- test_task: check
os: ubuntu-24.04-arm
extra_checks: [capi]
# ubuntu-24.04-arm jobs don't start on ruby/ruby as of 2025-10-29
#- test_task: check
# os: ubuntu-24.04-arm
fail-fast: false

env: &make-env
Expand Down
78 changes: 75 additions & 3 deletions zjit/src/asm/arm64/opnd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

use std::fmt;

/// This operand represents a register.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
Expand All @@ -18,7 +18,7 @@ impl A64Reg {
}
}

#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct A64Mem
{
// Size in bits
Expand All @@ -42,7 +42,7 @@ impl A64Mem {
}
}

#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum A64Opnd
{
// Dummy operand
Expand Down Expand Up @@ -196,3 +196,75 @@ pub const W31: A64Opnd = A64Opnd::Reg(A64Reg { num_bits: 32, reg_no: 31 });
// C argument registers
pub const C_ARG_REGS: [A64Opnd; 4] = [X0, X1, X2, X3];
pub const C_ARG_REGREGS: [A64Reg; 4] = [X0_REG, X1_REG, X2_REG, X3_REG];

impl fmt::Display for A64Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match A64Opnd::Reg(*self) {
X0 => write!(f, "x0"),
X1 => write!(f, "x1"),
X2 => write!(f, "x2"),
X3 => write!(f, "x3"),
X4 => write!(f, "x4"),
X5 => write!(f, "x5"),
X6 => write!(f, "x6"),
X7 => write!(f, "x7"),
X8 => write!(f, "x8"),
X9 => write!(f, "x9"),
X10 => write!(f, "x10"),
X11 => write!(f, "x11"),
X12 => write!(f, "x12"),
X13 => write!(f, "x13"),
X14 => write!(f, "x14"),
X15 => write!(f, "x15"),
X16 => write!(f, "x16"),
X17 => write!(f, "x17"),
X18 => write!(f, "x18"),
X19 => write!(f, "x19"),
X20 => write!(f, "x20"),
X21 => write!(f, "x21"),
X22 => write!(f, "x22"),
X23 => write!(f, "x23"),
X24 => write!(f, "x24"),
X25 => write!(f, "x25"),
X26 => write!(f, "x26"),
X27 => write!(f, "x27"),
X28 => write!(f, "x28"),
X29 => write!(f, "x29"),
X30 => write!(f, "x30"),
X31 => write!(f, "x31"),
W0 => write!(f, "w0"),
W1 => write!(f, "w1"),
W2 => write!(f, "w2"),
W3 => write!(f, "w3"),
W4 => write!(f, "w4"),
W5 => write!(f, "w5"),
W6 => write!(f, "w6"),
W7 => write!(f, "w7"),
W8 => write!(f, "w8"),
W9 => write!(f, "w9"),
W10 => write!(f, "w10"),
W11 => write!(f, "w11"),
W12 => write!(f, "w12"),
W13 => write!(f, "w13"),
W14 => write!(f, "w14"),
W15 => write!(f, "w15"),
W16 => write!(f, "w16"),
W17 => write!(f, "w17"),
W18 => write!(f, "w18"),
W19 => write!(f, "w19"),
W20 => write!(f, "w20"),
W21 => write!(f, "w21"),
W22 => write!(f, "w22"),
W23 => write!(f, "w23"),
W24 => write!(f, "w24"),
W25 => write!(f, "w25"),
W26 => write!(f, "w26"),
W27 => write!(f, "w27"),
W28 => write!(f, "w28"),
W29 => write!(f, "w29"),
W30 => write!(f, "w30"),
W31 => write!(f, "w31"),
_ => write!(f, "{self:?}"),
}
}
}
75 changes: 73 additions & 2 deletions zjit/src/asm/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub struct X86Reg
pub reg_no: u8,
}

#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct X86Mem
{
// Size in bits
Expand All @@ -66,7 +66,7 @@ pub struct X86Mem
pub disp: i32,
}

#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum X86Opnd
{
// Dummy operand
Expand Down Expand Up @@ -1380,3 +1380,74 @@ pub fn xor(cb: &mut CodeBlock, opnd0: X86Opnd, opnd1: X86Opnd) {
opnd1
);
}

impl fmt::Display for X86Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match X86Opnd::Reg(*self) {
RAX => write!(f, "rax"),
RCX => write!(f, "rcx"),
RDX => write!(f, "rdx"),
RBX => write!(f, "rbx"),
RSP => write!(f, "rsp"),
RBP => write!(f, "rbp"),
RSI => write!(f, "rsi"),
RDI => write!(f, "rdi"),
R8 => write!(f, "r8"),
R9 => write!(f, "r9"),
R10 => write!(f, "r10"),
R11 => write!(f, "r11"),
R12 => write!(f, "r12"),
R13 => write!(f, "r13"),
R14 => write!(f, "r14"),
R15 => write!(f, "r15"),
EAX => write!(f, "eax"),
ECX => write!(f, "ecx"),
EDX => write!(f, "edx"),
EBX => write!(f, "ebx"),
ESP => write!(f, "esp"),
EBP => write!(f, "ebp"),
ESI => write!(f, "esi"),
EDI => write!(f, "edi"),
R8D => write!(f, "r8d"),
R9D => write!(f, "r9d"),
R10D => write!(f, "r10d"),
R11D => write!(f, "r11d"),
R12D => write!(f, "r12d"),
R13D => write!(f, "r13d"),
R14D => write!(f, "r14d"),
R15D => write!(f, "r15d"),
AX => write!(f, "ax"),
CX => write!(f, "cx"),
DX => write!(f, "dx"),
BX => write!(f, "bx"),
BP => write!(f, "bp"),
SI => write!(f, "si"),
DI => write!(f, "di"),
R8W => write!(f, "r8w"),
R9W => write!(f, "r9w"),
R10W => write!(f, "r10w"),
R11W => write!(f, "r11w"),
R12W => write!(f, "r12w"),
R13W => write!(f, "r13w"),
R14W => write!(f, "r14w"),
R15W => write!(f, "r15w"),
AL => write!(f, "al"),
CL => write!(f, "cl"),
DL => write!(f, "dl"),
BL => write!(f, "bl"),
SPL => write!(f, "spl"),
BPL => write!(f, "bpl"),
SIL => write!(f, "sil"),
DIL => write!(f, "dil"),
R8B => write!(f, "r8b"),
R9B => write!(f, "r9b"),
R10B => write!(f, "r10b"),
R11B => write!(f, "r11b"),
R12B => write!(f, "r12b"),
R13B => write!(f, "r13b"),
R14B => write!(f, "r14b"),
R15B => write!(f, "r15b"),
_ => write!(f, "{self:?}"),
}
}
}
67 changes: 60 additions & 7 deletions zjit/src/backend/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ use crate::asm::{CodeBlock, Label};
use crate::asm::arm64::*;
use crate::cruby::*;
use crate::backend::lir::*;
use crate::options::asm_dump;
use crate::stats::CompileError;
use crate::virtualmem::CodePtr;
use crate::cast::*;

// Use the arm64 register type for this platform
pub type Reg = A64Reg;

/// Convert reg_no for MemBase::Reg into Reg, assuming it's a 64-bit register
pub fn mem_base_reg(reg_no: u8) -> Reg {
Reg { num_bits: 64, reg_no }
}

// Callee-saved registers
pub const CFP: Opnd = Opnd::Reg(X19_REG);
pub const EC: Opnd = Opnd::Reg(X20_REG);
Expand Down Expand Up @@ -194,7 +200,7 @@ pub const ALLOC_REGS: &[Reg] = &[
];

/// Special scratch registers for intermediate processing. They should be used only by
/// [`Assembler::arm64_split_with_scratch_reg`] or [`Assembler::new_with_scratch_reg`].
/// [`Assembler::arm64_scratch_split`] or [`Assembler::new_with_scratch_reg`].
const SCRATCH0_OPND: Opnd = Opnd::Reg(X15_REG);
const SCRATCH1_OPND: Opnd = Opnd::Reg(X17_REG);

Expand Down Expand Up @@ -683,7 +689,7 @@ impl Assembler {
/// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions
/// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this
/// splits them and uses scratch registers for it.
fn arm64_split_with_scratch_reg(self) -> Assembler {
fn arm64_scratch_split(self) -> Assembler {
let mut asm = Assembler::new_with_asm(&self);
asm.accept_scratch_reg = true;
let mut iterator = self.insns.into_iter().enumerate().peekable();
Expand Down Expand Up @@ -971,6 +977,9 @@ impl Assembler {
// For each instruction
let mut insn_idx: usize = 0;
while let Some(insn) = self.insns.get(insn_idx) {
// Dump Assembler with insn_idx if --zjit-dump-lir=panic is given
let _hook = AssemblerPanicHook::new(self, insn_idx);

match insn {
Insn::Comment(text) => {
cb.add_comment(text);
Expand Down Expand Up @@ -1070,7 +1079,7 @@ impl Assembler {
}
},
Insn::Mul { left, right, out } => {
// If the next instruction is JoMul with RShift created by arm64_split_with_scratch_reg
// If the next instruction is JoMul with RShift created by arm64_scratch_split
match (self.insns.get(insn_idx + 1), self.insns.get(insn_idx + 2)) {
(Some(Insn::RShift { out: out_sign, opnd: out_opnd, shift: out_shift }), Some(Insn::JoMul(_))) => {
// Compute the high 64 bits
Expand All @@ -1081,7 +1090,7 @@ impl Assembler {
// so we do it after smulh
mul(cb, out.into(), left.into(), right.into());

// Insert the shift instruction created by arm64_split_with_scratch_reg
// Insert the shift instruction created by arm64_scratch_split
// to prepare the register that has the sign bit of the high 64 bits after mul.
asr(cb, out_sign.into(), out_opnd.into(), out_shift.into());
insn_idx += 1; // skip the next Insn::RShift
Expand Down Expand Up @@ -1382,12 +1391,12 @@ impl Assembler {
last_patch_pos = Some(cb.get_write_pos());
},
Insn::IncrCounter { mem, value } => {
// Get the status register allocated by arm64_split_with_scratch_reg
// Get the status register allocated by arm64_scratch_split
let Some(Insn::Cmp {
left: status_reg @ Opnd::Reg(_),
right: Opnd::UImm(_) | Opnd::Imm(_),
}) = self.insns.get(insn_idx + 1) else {
panic!("arm64_split_with_scratch_reg should add Cmp after IncrCounter: {:?}", self.insns.get(insn_idx + 1));
panic!("arm64_scratch_split should add Cmp after IncrCounter: {:?}", self.insns.get(insn_idx + 1));
};

// Attempt to increment a counter
Expand Down Expand Up @@ -1451,13 +1460,21 @@ impl Assembler {
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Result<(CodePtr, Vec<CodePtr>), CompileError> {
// The backend is allowed to use scratch registers only if it has not accepted them so far.
let use_scratch_reg = !self.accept_scratch_reg;
asm_dump!(self, init);

let asm = self.arm64_split();
asm_dump!(asm, split);

let mut asm = asm.alloc_regs(regs)?;
asm_dump!(asm, alloc_regs);

// We put compile_side_exits after alloc_regs to avoid extending live ranges for VRegs spilled on side exits.
asm.compile_side_exits();
asm_dump!(asm, compile_side_exits);

if use_scratch_reg {
asm = asm.arm64_split_with_scratch_reg();
asm = asm.arm64_scratch_split();
asm_dump!(asm, scratch_split);
}

// Create label instances in the code block
Expand Down Expand Up @@ -1528,6 +1545,42 @@ mod tests {
(Assembler::new(), CodeBlock::new_dummy())
}

#[test]
fn test_lir_string() {
use crate::hir::SideExitReason;

let mut asm = Assembler::new();
asm.stack_base_idx = 1;

let label = asm.new_label("bb0");
asm.write_label(label.clone());
asm.push_insn(Insn::Comment("bb0(): foo@/tmp/a.rb:1".into()));
asm.frame_setup(JIT_PRESERVED_REGS);

let val64 = asm.add(CFP, Opnd::UImm(64));
asm.store(Opnd::mem(64, SP, 0x10), val64);
let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, pc: 0 as _, stack: vec![], locals: vec![], label: None };
asm.push_insn(Insn::Joz(val64, side_exit));

let val32 = asm.sub(Opnd::Value(Qtrue), Opnd::Imm(1));
asm.store(Opnd::mem(64, EC, 0x10).with_num_bits(32), val32.with_num_bits(32));
asm.cret(val64);

asm.frame_teardown(JIT_PRESERVED_REGS);
assert_disasm_snapshot!(lir_string(&mut asm), @r"
bb0:
# bb0(): foo@/tmp/a.rb:1
FrameSetup 1, x19, x21, x20
v0 = Add x19, 0x40
Store [x21 + 0x10], v0
Joz Exit(Interrupt), v0
v1 = Sub Value(0x14), Imm(1)
Store Mem32[x20 + 0x10], VReg32(v1)
CRet v0
FrameTeardown x19, x21, x20
");
}

#[test]
fn test_mul_with_immediate() {
let (mut asm, mut cb) = setup_asm();
Expand Down
Loading