ZJIT: Fix clobbering register for self in gen_entry_params()

Previously, for 8+ params we wound up clobbering the self param when
putting the last param in memory in the JIT entry point:

    # ZJIT entry point: a@../test.rb:5
    <snip>
    ldur x0, [x19, #0x18]
    # set method params: 8
    ldur x1, [x21, #-0x58]
    ldur x2, [x21, #-0x50]
    ldur x3, [x21, #-0x48]
    ldur x4, [x21, #-0x40]
    ldur x5, [x21, #-0x38]
    ldur x11, [x21, #-0x30]
    ldur x12, [x21, #-0x28]
    ldur x0, [x21, #-0x20]
    stur x0, [sp, #-0x20]
    bl #0x11e17018c

Doing the memcpys for parameters in memory first avoids this clobbering.

    # set method params: 8
    ldur x0, [x21, #-0x20]
    stur x0, [sp, #-0x20]
    ldur x12, [x21, #-0x28]
    ldur x11, [x21, #-0x30]
    ldur x5, [x21, #-0x38]
    ldur x4, [x21, #-0x40]
    ldur x3, [x21, #-0x48]
    ldur x2, [x21, #-0x50]
    ldur x1, [x21, #-0x58]
    ldur x0, [x19, #0x18]
This commit is contained in:
Alan Wu 2025-07-22 22:54:54 -04:00
parent 5e5cec1b86
commit 41149a96ef
2 changed files with 28 additions and 13 deletions

View file

@ -187,6 +187,10 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt
asm.frame_teardown();
asm.cret(C_RET_OPND);
if get_option!(dump_lir) {
println!("LIR:\nJIT entry for {}:\n{:?}", iseq_name(iseq), asm);
}
let result = asm.compile(cb).map(|(start_ptr, _)| start_ptr);
if let Some(start_addr) = result {
if get_option!(perf) {
@ -584,16 +588,16 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) {
/// Assign method arguments to basic block arguments at JIT entry
fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block, c_stack_bytes: usize) {
let self_param = gen_param(asm, SELF_PARAM_IDX);
asm.mov(self_param, Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF));
let num_params = entry_block.params().len() - 1; // -1 to exclude self
if num_params > 0 {
asm_comment!(asm, "set method params: {num_params}");
// Allocate registers for basic block arguments
for idx in 0..num_params {
let param = gen_param(asm, idx + 1); // +1 for self
// Fill basic block parameters.
// Doing it in reverse is load-bearing. High index params have memory slots that might
// require using a register to fill. Filling them first avoids clobbering.
for idx in (0..num_params).rev() {
let param = param_opnd(idx + 1); // +1 for self
let local = gen_entry_param(asm, iseq, idx);
// Funky offset adjustment to write into the native stack frame of the
// HIR function we'll be calling into. This only makes sense in context
@ -611,17 +615,22 @@ fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block, c_s
// would be while │ │
// the HIR function ────────────► └────────────┘
// is running
let param = if let Opnd::Mem(lir::Mem { base, disp, num_bits }) = param {
Opnd::Mem(lir::Mem { num_bits, base, disp: disp - c_stack_bytes as i32 - Assembler::frame_size() })
} else {
param
};
match param {
Opnd::Mem(lir::Mem { base, disp, num_bits }) => {
let param_slot = Opnd::Mem(lir::Mem { num_bits, base, disp: disp - c_stack_bytes as i32 - Assembler::frame_size() });
asm.mov(param_slot, local);
}
// Prepare for parallel move for locals in registers
reg @ Opnd::Reg(_) => {
asm.load_into(reg, local);
}
_ => unreachable!("on entry, params are either in memory or in reg. Got {param:?}")
}
// Assign local variables to the basic block arguments
let local = gen_entry_param(asm, iseq, idx);
asm.mov(param, local);
}
}
asm.load_into(param_opnd(SELF_PARAM_IDX), Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF));
}
/// Set branch params to basic block arguments