@@ -518,7 +518,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
518518 Insn :: InvokeBuiltin { .. } => SideExitReason :: UnhandledHIRInvokeBuiltin ,
519519 _ => SideExitReason :: UnhandledHIRUnknown ( insn_id) ,
520520 } ;
521- gen_side_exit ( & mut jit, & mut asm, & reason, & None , & function. frame_state ( last_snapshot) ) ;
521+ gen_side_exit ( & mut jit, & mut asm, & reason, None , & function. frame_state ( last_snapshot) ) ;
522522 // Don't bother generating code after a side-exit. We won't run it.
523523 // TODO(max): Generate ud2 or equivalent.
524524 break ;
@@ -689,7 +689,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
689689 Insn :: HasType { val, expected } => gen_has_type ( jit, asm, opnd ! ( val) , * expected) ,
690690 Insn :: GuardType { val, guard_type, state } => gen_guard_type ( jit, asm, opnd ! ( val) , * guard_type, & function. frame_state ( * state) ) ,
691691 Insn :: GuardTypeNot { val, guard_type, state } => gen_guard_type_not ( jit, asm, opnd ! ( val) , * guard_type, & function. frame_state ( * state) ) ,
692- & Insn :: GuardBitEquals { val, expected, reason, state } => gen_guard_bit_equals ( jit, asm, opnd ! ( val) , expected, reason, & function. frame_state ( state) ) ,
692+ & Insn :: GuardBitEquals { val, expected, reason, state, recompile } => gen_guard_bit_equals ( jit, asm, opnd ! ( val) , expected, reason, recompile , & function. frame_state ( state) ) ,
693693 & Insn :: GuardAnyBitSet { val, mask, reason, state, .. } => gen_guard_any_bit_set ( jit, asm, opnd ! ( val) , mask, reason, & function. frame_state ( state) ) ,
694694 & Insn :: GuardNoBitsSet { val, mask, reason, state, .. } => gen_guard_no_bits_set ( jit, asm, opnd ! ( val) , mask, reason, & function. frame_state ( state) ) ,
695695 & Insn :: GuardLess { left, right, state } => gen_guard_less ( jit, asm, opnd ! ( left) , opnd ! ( right) , & function. frame_state ( state) ) ,
@@ -717,7 +717,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
717717 Insn :: SetClassVar { id, val, ic, state } => no_output ! ( gen_setclassvar( jit, asm, * id, opnd!( val) , * ic, & function. frame_state( * state) ) ) ,
718718 Insn :: SetIvar { self_val, id, ic, val, state } => no_output ! ( gen_setivar( jit, asm, opnd!( self_val) , * id, * ic, opnd!( val) , & function. frame_state( * state) ) ) ,
719719 Insn :: FixnumBitCheck { val, index } => gen_fixnum_bit_check ( asm, opnd ! ( val) , * index) ,
720- Insn :: SideExit { state, reason, recompile } => no_output ! ( gen_side_exit( jit, asm, reason, recompile, & function. frame_state( * state) ) ) ,
720+ Insn :: SideExit { state, reason, recompile } => no_output ! ( gen_side_exit( jit, asm, reason, * recompile, & function. frame_state( * state) ) ) ,
721721 Insn :: PutSpecialObject { value_type } => gen_putspecialobject ( asm, * value_type) ,
722722 Insn :: AnyToString { val, str, state } => gen_anytostring ( asm, opnd ! ( val) , opnd ! ( str ) , & function. frame_state ( * state) ) ,
723723 Insn :: Defined { op_type, obj, pushval, v, state } => gen_defined ( jit, asm, * op_type, * obj, * pushval, opnd ! ( v) , & function. frame_state ( * state) ) ,
@@ -1238,14 +1238,8 @@ fn gen_setglobal(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, sta
12381238}
12391239
12401240/// Side-exit into the interpreter
1241- fn gen_side_exit ( jit : & mut JITState , asm : & mut Assembler , reason : & SideExitReason , recompile : & Option < i32 > , state : & FrameState ) {
1242- let mut exit = build_side_exit ( jit, state) ;
1243- exit. recompile = recompile. map ( |argc| SideExitRecompile {
1244- iseq : Opnd :: Value ( VALUE :: from ( jit. iseq ) ) ,
1245- insn_idx : state. insn_idx ( ) as u32 ,
1246- argc,
1247- } ) ;
1248- asm. jmp ( Target :: SideExit { exit, reason : * reason } ) ;
1241+ fn gen_side_exit ( jit : & mut JITState , asm : & mut Assembler , reason : & SideExitReason , recompile : Option < i32 > , state : & FrameState ) {
1242+ asm. jmp ( side_exit_with_recompile ( jit, state, * reason, recompile) ) ;
12491243}
12501244
12511245/// Emit a special object lookup
@@ -2637,7 +2631,7 @@ fn gen_guard_type_not(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, g
26372631}
26382632
26392633/// Compile an identity check with a side exit
2640- fn gen_guard_bit_equals ( jit : & mut JITState , asm : & mut Assembler , val : lir:: Opnd , expected : crate :: hir:: Const , reason : SideExitReason , state : & FrameState ) -> lir:: Opnd {
2634+ fn gen_guard_bit_equals ( jit : & mut JITState , asm : & mut Assembler , val : lir:: Opnd , expected : crate :: hir:: Const , reason : SideExitReason , recompile : Option < i32 > , state : & FrameState ) -> lir:: Opnd {
26412635 if matches ! ( reason, SideExitReason :: GuardShape ( _) ) {
26422636 gen_incr_counter ( asm, Counter :: guard_shape_count) ;
26432637 }
@@ -2648,7 +2642,7 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd,
26482642 _ => panic ! ( "gen_guard_bit_equals: unexpected hir::Const {expected:?}" ) ,
26492643 } ;
26502644 asm. cmp ( val, expected_opnd) ;
2651- asm. jnz ( jit, side_exit ( jit, state, reason) ) ;
2645+ asm. jnz ( jit, side_exit_with_recompile ( jit, state, reason, recompile ) ) ;
26522646 val
26532647}
26542648
@@ -2982,6 +2976,18 @@ fn side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason) -> Targ
29822976 Target :: SideExit { exit, reason }
29832977}
29842978
2979+ /// Build a Target::SideExit that optionally triggers exit_recompile on the exit path.
2980+ /// When `recompile` is Some(argc), the side exit calls exit_recompile with that argc.
2981+ fn side_exit_with_recompile ( jit : & JITState , state : & FrameState , reason : SideExitReason , recompile : Option < i32 > ) -> Target {
2982+ let mut exit = build_side_exit ( jit, state) ;
2983+ exit. recompile = recompile. map ( |argc| SideExitRecompile {
2984+ iseq : Opnd :: Value ( VALUE :: from ( jit. iseq ) ) ,
2985+ insn_idx : state. insn_idx ( ) as u32 ,
2986+ argc,
2987+ } ) ;
2988+ Target :: SideExit { exit, reason }
2989+ }
2990+
29852991/// Build a side-exit context
29862992fn build_side_exit ( jit : & JITState , state : & FrameState ) -> SideExit {
29872993 let mut stack = Vec :: new ( ) ;
@@ -3037,26 +3043,48 @@ macro_rules! c_callable {
30373043pub ( crate ) use c_callable;
30383044
30393045c_callable ! {
3040- /// Called from JIT side-exit code when a send instruction had no profile data. This function
3041- /// profiles the receiver and arguments on the stack, then (once enough profiles are gathered)
3042- /// invalidates the current ISEQ version so that the ISEQ will be recompiled with the new
3043- /// profile data on the next call.
3044- pub ( crate ) fn no_profile_send_recompile( ec: EcPtr , iseq_raw: VALUE , insn_idx: u32 , argc: i32 ) {
3046+ /// Called from JIT side-exit code to profile operands and trigger recompilation.
3047+ /// For send instructions (argc >= 0): profiles receiver + args from the stack.
3048+ /// For shape guard exits (argc == -1): profiles self from the CFP.
3049+ /// Once enough profiles are gathered, invalidates the ISEQ for recompilation.
3050+ pub ( crate ) fn exit_recompile( ec: EcPtr , iseq_raw: VALUE , insn_idx: u32 , argc: i32 ) {
3051+ // Fast check before taking the VM lock: skip if already invalidated or
3052+ // at the version limit. This avoids expensive lock acquisition on every
3053+ // shape guard exit after the recompile has already been triggered.
3054+ {
3055+ let iseq: IseqPtr = iseq_raw. as_iseq( ) ;
3056+ let payload = get_or_create_iseq_payload( iseq) ;
3057+ let already_done = payload. versions. last( )
3058+ . map_or( false , |v| unsafe { v. as_ref( ) } . status == IseqStatus :: Invalidated )
3059+ || payload. versions. len( ) >= max_iseq_versions( ) ;
3060+ if already_done {
3061+ return ;
3062+ }
3063+ }
3064+
30453065 with_vm_lock( src_loc!( ) , || {
30463066 let iseq: IseqPtr = iseq_raw. as_iseq( ) ;
30473067 let payload = get_or_create_iseq_payload( iseq) ;
30483068
3049- // Already gathered enough profiles; nothing to do
3050- if payload. profile. done_profiling_at( insn_idx as usize ) {
3069+ // For no-profile sends, skip if already profiled at this insn_idx.
3070+ // For shape guard exits (argc == -1), always re-profile because the
3071+ // original YARV profiles were monomorphic but runtime showed new shapes.
3072+ if argc >= 0 && payload. profile. done_profiling_at( insn_idx as usize ) {
30513073 return ;
30523074 }
30533075
30543076 with_time_stat( Counter :: profile_time_ns, || {
30553077 let cfp = unsafe { get_ec_cfp( ec) } ;
3056- let sp = unsafe { get_cfp_sp( cfp) } ;
30573078
3058- // Profile the receiver and arguments for this send instruction
3059- let should_recompile = payload. profile. profile_send_at( iseq, insn_idx as usize , sp, argc as usize ) ;
3079+ let should_recompile = if argc >= 0 {
3080+ let sp = unsafe { get_cfp_sp( cfp) } ;
3081+ // Profile the receiver and arguments for this send instruction
3082+ payload. profile. profile_send_at( iseq, insn_idx as usize , sp, argc as usize )
3083+ } else {
3084+ // Profile self for shape guard exits (argc == -1)
3085+ let self_val = unsafe { get_cfp_self( cfp) } ;
3086+ payload. profile. profile_self_at( iseq, insn_idx as usize , self_val)
3087+ } ;
30603088
30613089 // Once we have enough profiles, invalidate and recompile the ISEQ
30623090 if should_recompile {
0 commit comments