Skip to content

Commit 8d14d6e

Browse files
committed
ZJIT: Spill to the stack using arguments instead of FrameState
The FrameState on the SendWithoutBlock represents the entry state, but for instructions that push to the stack in the middle of the instruction before actually doing the send like opt_aref_with, the FrameState is incorrect. We need to write to the stack using the arguments for the instruction.
1 parent 6f0f84e commit 8d14d6e

File tree

2 files changed

+17
-7
lines changed

2 files changed

+17
-7
lines changed

test/ruby/test_zjit.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,14 @@ def fib(n)
487487
}, call_threshold: 5, num_profiles: 3
488488
end
489489

490+
def test_opt_aref_with
491+
assert_compiles ':ok', %q{
492+
def aref_with(hash) = hash["key"]
493+
494+
aref_with({ "key" => :ok })
495+
}
496+
end
497+
490498
# tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and
491499
# b) being reliably ordered after all the other instructions.
492500
def test_instruction_order

zjit/src/codegen.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
257257
Insn::Jump(branch) => return gen_jump(jit, asm, branch),
258258
Insn::IfTrue { val, target } => return gen_if_true(jit, asm, opnd!(val), target),
259259
Insn::IfFalse { val, target } => return gen_if_false(jit, asm, opnd!(val), target),
260-
Insn::SendWithoutBlock { call_info, cd, state, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state))?,
260+
Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), self_val, args)?,
261261
Insn::SendWithoutBlockDirect { iseq, self_val, args, .. } => gen_send_without_block_direct(cb, jit, asm, *iseq, opnd!(self_val), args)?,
262262
Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?),
263263
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
@@ -443,18 +443,20 @@ fn gen_send_without_block(
443443
call_info: &CallInfo,
444444
cd: *const rb_call_data,
445445
state: &FrameState,
446+
self_val: &InsnId,
447+
args: &Vec<InsnId>,
446448
) -> Option<lir::Opnd> {
447-
// Spill the virtual stack onto the stack. They need to be marked by GC and may be caller-saved registers.
449+
// Spill the receiver and the arguments onto the stack. They need to be marked by GC and may be caller-saved registers.
448450
// TODO: Avoid spilling operands that have been spilled before.
449-
for (idx, &insn_id) in state.stack().enumerate() {
451+
for (idx, &insn_id) in [*self_val].iter().chain(args.iter()).enumerate() {
450452
// Currently, we don't move the SP register. So it's equal to the base pointer.
451453
let stack_opnd = Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32);
452454
asm.mov(stack_opnd, jit.get_opnd(insn_id)?);
453455
}
454456

455457
// Save PC and SP
456458
gen_save_pc(asm, state);
457-
gen_save_sp(asm, state);
459+
gen_save_sp(asm, 1 + args.len()); // +1 for receiver
458460

459461
asm_comment!(asm, "call #{} with dynamic dispatch", call_info.method_name);
460462
unsafe extern "C" {
@@ -678,13 +680,13 @@ fn gen_save_pc(asm: &mut Assembler, state: &FrameState) {
678680
}
679681

680682
/// Save the current SP on the CFP
681-
fn gen_save_sp(asm: &mut Assembler, state: &FrameState) {
683+
fn gen_save_sp(asm: &mut Assembler, stack_size: usize) {
682684
// Update cfp->sp which will be read by the interpreter. We also have the SP register in JIT
683685
// code, and ZJIT's codegen currently assumes the SP register doesn't move, e.g. gen_param().
684686
// So we don't update the SP register here. We could update the SP register to avoid using
685687
// an extra register for asm.lea(), but you'll need to manage the SP offset like YJIT does.
686-
asm_comment!(asm, "save SP to CFP: {}", state.stack_size());
687-
let sp_addr = asm.lea(Opnd::mem(64, SP, state.stack_size() as i32 * SIZEOF_VALUE_I32));
688+
asm_comment!(asm, "save SP to CFP: {}", stack_size);
689+
let sp_addr = asm.lea(Opnd::mem(64, SP, stack_size as i32 * SIZEOF_VALUE_I32));
688690
let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP);
689691
asm.mov(cfp_sp, sp_addr);
690692
}

0 commit comments

Comments
 (0)