Skip to content

Commit 27d6068

Browse files
committed
ZJIT: Use pre-Send snapshot for SendDirect callee bail exit
When a SendDirect callee bails, use the last snapshot's state (which has the pre-optimization stack without synthesized kwargs) to restore the caller frame. This prevents the interpreter from seeing extra stack items from the JIT optimizer's arg synthesis.
1 parent 8abc8b4 commit 27d6068

1 file changed

Lines changed: 36 additions & 20 deletions

File tree

zjit/src/codegen.rs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,18 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
414414
}
415415
}
416416

417+
// Track the last snapshot for each block — used for SendDirect exit
418+
let mut last_snapshot_id: Option<InsnId> = None;
419+
417420
// Compile all instructions in this block
418421
for &insn_id in block.insns() {
419422
let insn = function.find(insn_id);
420423

424+
// Track snapshots for SendDirect exit
425+
if matches!(insn, Insn::Snapshot { .. }) {
426+
last_snapshot_id = Some(insn_id);
427+
}
428+
421429
// Helper to get a previously compiled operand
422430
macro_rules! cl_opnd {
423431
($id:expr) => {
@@ -1005,35 +1013,43 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
10051013
builder.ins().brif(is_qundef, exit_block, &[], cont_block, &[]);
10061014

10071015
// Exit block: callee returned Qundef (side-exit or compilation failure).
1008-
// Pop the callee frame and restore the caller's state so the
1009-
// interpreter can re-execute the Send instruction correctly.
1016+
// Pop callee frame and use the LAST SNAPSHOT state (not the Send's
1017+
// synthesized state which includes kwargs the interpreter doesn't expect).
10101018
builder.seal_block(exit_block);
10111019
builder.switch_to_block(exit_block);
1020+
10121021
// Pop callee frame: ec->cfp = callee_cfp + SIZEOF_CONTROL_FRAME
10131022
let exit_ec = builder.use_var(ec_var);
10141023
let frame_sz = builder.ins().iconst(cl_types::I64, RUBY_SIZEOF_CONTROL_FRAME as i64);
10151024
let exit_callee_cfp = builder.use_var(cfp_var);
10161025
let exit_caller_cfp = builder.ins().iadd(exit_callee_cfp, frame_sz);
10171026
builder.ins().store(MemFlags::trusted(), exit_caller_cfp, exit_ec, Offset32::new(RUBY_OFFSET_EC_CFP as i32));
1018-
// Save caller PC (the Send instruction)
1019-
builder.ins().store(MemFlags::trusted(), pc_val, exit_caller_cfp, Offset32::new(RUBY_OFFSET_CFP_PC));
1020-
// Save caller SP at full stack size so the interpreter sees recv+args
1021-
let full_sp = builder.ins().iadd_imm(sp, (state.stack().count() * SIZEOF_VALUE) as i64);
1022-
builder.ins().store(MemFlags::trusted(), full_sp, exit_caller_cfp, Offset32::new(RUBY_OFFSET_CFP_SP));
1023-
// Spill caller stack (recv + args)
1024-
for (idx, &sid) in state.stack().enumerate() {
1025-
if let Some(val) = cl_opnds[sid.0] {
1026-
let offset = (idx as i32) * SIZEOF_VALUE_I32;
1027-
builder.ins().store(MemFlags::trusted(), val, sp, Offset32::new(offset));
1028-
}
1029-
}
1030-
// Spill caller locals
1031-
for (idx, &lid) in state.locals().enumerate() {
1032-
if let Some(val) = cl_opnds[lid.0] {
1033-
let ep_offset = local_idx_to_ep_offset(iseq, idx);
1034-
let byte_offset = -(ep_offset + 1) * SIZEOF_VALUE_I32;
1035-
builder.ins().store(MemFlags::trusted(), val, sp, Offset32::new(byte_offset));
1027+
1028+
// Use the last snapshot's state for the side exit — this has the
1029+
// original stack before kwargs were synthesized by SendDirect
1030+
if let Some(snap_id) = last_snapshot_id {
1031+
if let Insn::Snapshot { state: snap_state } = function.find(snap_id) {
1032+
let snap_pc = builder.ins().iconst(cl_types::I64, snap_state.pc as i64);
1033+
builder.ins().store(MemFlags::trusted(), snap_pc, exit_caller_cfp, Offset32::new(RUBY_OFFSET_CFP_PC));
1034+
let snap_sp = builder.ins().iadd_imm(sp, (snap_state.stack().count() * SIZEOF_VALUE) as i64);
1035+
builder.ins().store(MemFlags::trusted(), snap_sp, exit_caller_cfp, Offset32::new(RUBY_OFFSET_CFP_SP));
1036+
for (idx, &sid) in snap_state.stack().enumerate() {
1037+
if let Some(val) = cl_opnds[sid.0] {
1038+
let offset = (idx as i32) * SIZEOF_VALUE_I32;
1039+
builder.ins().store(MemFlags::trusted(), val, sp, Offset32::new(offset));
1040+
}
1041+
}
1042+
for (idx, &lid) in snap_state.locals().enumerate() {
1043+
if let Some(val) = cl_opnds[lid.0] {
1044+
let ep_offset = local_idx_to_ep_offset(iseq, idx);
1045+
let byte_offset = -(ep_offset + 1) * SIZEOF_VALUE_I32;
1046+
builder.ins().store(MemFlags::trusted(), val, sp, Offset32::new(byte_offset));
1047+
}
1048+
}
10361049
}
1050+
} else {
1051+
// No snapshot — just save PC
1052+
builder.ins().store(MemFlags::trusted(), pc_val, exit_caller_cfp, Offset32::new(RUBY_OFFSET_CFP_PC));
10371053
}
10381054
builder.ins().return_(&[qundef]);
10391055

0 commit comments

Comments
 (0)