Skip to content

Commit 386637a

Browse files
committed
ZJIT: Add patch point support for Cranelift backend
Emit NOP sleds and side exit stubs after Cranelift code using the LIR Assembler, then register them with the invariant system. When an invariant fires, the version is invalidated and the ISEQ falls back to the interpreter.
1 parent ec93ba4 commit 386637a

1 file changed

Lines changed: 71 additions & 4 deletions

File tree

zjit/src/codegen.rs

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,15 +278,25 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, mut version: IseqVersionRef,
278278
Ok(iseq_code_ptrs)
279279
}
280280

281+
/// Info collected during Cranelift lowering for each PatchPoint instruction.
282+
/// After Cranelift compilation, we emit side exit stubs and NOP sleds using the
283+
/// LIR Assembler and register them with the invariant system.
284+
struct CLPatchPointInfo {
285+
invariant: Invariant,
286+
state: FrameState,
287+
}
288+
281289
/// Compile a function using the Cranelift backend
282-
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, _version: IseqVersionRef, function: &Function) -> Result<(IseqCodePtrs, Vec<CodePtr>, Vec<IseqCallRef>), CompileError> {
290+
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, function: &Function) -> Result<(IseqCodePtrs, Vec<CodePtr>, Vec<IseqCallRef>), CompileError> {
283291
// Count max params across all blocks to determine function ABI params
284292
let num_jit_args = max_num_params(function);
285293
let mut cl = CraneliftBuilder::new(num_jit_args);
286294

287295
// Cranelift operands indexed by HIR InsnId
288296
let mut cl_opnds: Vec<Option<CLValue>> = vec![None; function.num_insns()];
289297
let iseq_calls: Vec<IseqCallRef> = Vec::new();
298+
// Patch points to emit after Cranelift compilation
299+
let mut patch_points: Vec<CLPatchPointInfo> = Vec::new();
290300

291301
cl.build(|builder, isa, side_exit_blocks, value_pool, ec_var, cfp_var, sp_var, next_var| {
292302
// Map HIR blocks → Cranelift blocks
@@ -766,9 +776,12 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, _version: IseqVersionRef, fun
766776
}
767777

768778
// === Patch points ===
769-
Insn::PatchPoint { invariant: _, state: _ } => {
770-
// TODO: implement proper patch point support with NOPs
771-
// For now, patch points are no-ops (the invariant is assumed to hold)
779+
Insn::PatchPoint { invariant, state } => {
780+
// Collect info — actual NOP sled + side exit stub emitted after Cranelift compilation
781+
patch_points.push(CLPatchPointInfo {
782+
invariant,
783+
state: function.frame_state(state).clone(),
784+
});
772785
}
773786

774787
// === Counter increment (stats) ===
@@ -1069,6 +1082,60 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, _version: IseqVersionRef, fun
10691082
ZJITState::log_compile(iseq_name);
10701083
}
10711084

1085+
// Emit patch point stubs using the LIR Assembler after the Cranelift code.
1086+
// Each patch point gets a NOP sled (which can be overwritten with a jump)
1087+
// and a side exit stub.
1088+
for pp_info in patch_points.iter() {
1089+
// Emit side exit stub
1090+
let mut exit_asm = Assembler::new();
1091+
exit_asm.new_block_without_id();
1092+
// Save VM state for the side exit
1093+
let exit_state = &pp_info.state;
1094+
asm_comment!(exit_asm, "patch point side exit");
1095+
exit_asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(exit_state.pc));
1096+
exit_asm.lea_into(
1097+
Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP),
1098+
Opnd::mem(64, SP, exit_state.stack().count() as i32 * SIZEOF_VALUE_I32),
1099+
);
1100+
exit_asm.frame_teardown(&[]);
1101+
exit_asm.cret(Opnd::UImm(Qundef.as_u64()));
1102+
let (side_exit_ptr, _) = exit_asm.compile(cb)?;
1103+
1104+
// Emit NOP sled at the current write position — this is the patch point
1105+
let mut nop_asm = Assembler::new();
1106+
nop_asm.new_block_without_id();
1107+
nop_asm.pad_patch_point();
1108+
let (patch_point_ptr, _) = nop_asm.compile(cb)?;
1109+
1110+
// Register with the invariant system
1111+
match pp_info.invariant {
1112+
Invariant::BOPRedefined { klass, bop } => {
1113+
track_bop_assumption(klass, bop, patch_point_ptr, side_exit_ptr, version);
1114+
}
1115+
Invariant::MethodRedefined { klass: _, method: _, cme } => {
1116+
track_cme_assumption(cme, patch_point_ptr, side_exit_ptr, version);
1117+
}
1118+
Invariant::StableConstantNames { idlist } => {
1119+
track_stable_constant_names_assumption(idlist, patch_point_ptr, side_exit_ptr, version);
1120+
}
1121+
Invariant::NoTracePoint => {
1122+
track_no_trace_point_assumption(patch_point_ptr, side_exit_ptr, version);
1123+
}
1124+
Invariant::NoEPEscape(iseq_ptr) => {
1125+
track_no_ep_escape_assumption(iseq_ptr, patch_point_ptr, side_exit_ptr, version);
1126+
}
1127+
Invariant::SingleRactorMode => {
1128+
track_single_ractor_assumption(patch_point_ptr, side_exit_ptr, version);
1129+
}
1130+
Invariant::NoSingletonClass { klass } => {
1131+
track_no_singleton_class_assumption(klass, patch_point_ptr, side_exit_ptr, version);
1132+
}
1133+
Invariant::RootBoxOnly => {
1134+
track_root_box_assumption(patch_point_ptr, side_exit_ptr, version);
1135+
}
1136+
}
1137+
}
1138+
10721139
// TODO: support jit_entry_ptrs for JIT-to-JIT calls
10731140
Ok((IseqCodePtrs { start_ptr, jit_entry_ptrs: vec![] }, gc_offsets, iseq_calls))
10741141
}

0 commit comments

Comments
 (0)