@@ -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