@@ -6,6 +6,7 @@ use std::cell::{Cell, RefCell};
66use std:: rc:: Rc ;
77use std:: ffi:: { c_int, c_long, c_void} ;
88use std:: slice;
9+ use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
910
1011use crate :: backend:: current:: ALLOC_REGS ;
1112use crate :: invariants:: {
@@ -14,7 +15,7 @@ use crate::invariants::{
1415 track_root_box_assumption
1516} ;
1617use crate :: gc:: append_gc_offsets;
17- use crate :: payload:: { get_or_create_iseq_payload, IseqCodePtrs , IseqVersion , IseqVersionRef , IseqStatus } ;
18+ use crate :: payload:: { get_or_create_iseq_payload, IseqCodePtrs , IseqPayload , IseqVersion , IseqVersionRef , IseqStatus } ;
1819use crate :: state:: ZJITState ;
1920use crate :: stats:: { CompileError , exit_counter_for_compile_error, exit_counter_for_unhandled_hir_insn, incr_counter, incr_counter_by, send_fallback_counter, send_fallback_counter_for_method_type, send_fallback_counter_for_super_method_type, send_fallback_counter_ptr_for_opcode, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type} ;
2021use crate :: stats:: { counter_ptr, with_time_stat, Counter , Counter :: { compile_time_ns, exit_compile_error} } ;
@@ -29,6 +30,52 @@ use crate::cast::IntoUsize;
2930/// At the moment, we support recompiling each ISEQ only once.
3031pub const MAX_ISEQ_VERSIONS : usize = 2 ;
3132
33+ /// Called from side-exit stubs to count exits for recompilation.
34+ #[ unsafe( no_mangle) ]
35+ pub extern "C" fn rb_zjit_count_side_exit ( payload_raw : * mut std:: ffi:: c_void ) {
36+ if payload_raw. is_null ( ) { return ; }
37+ let payload = unsafe { & mut * ( payload_raw as * mut IseqPayload ) } ;
38+ let threshold = get_option ! ( recompile_threshold) as u64 ;
39+ if threshold == 0 || payload. side_exit_count >= threshold { return ; }
40+ payload. side_exit_count += 1 ;
41+ if payload. side_exit_count == threshold && payload. versions . len ( ) < MAX_ISEQ_VERSIONS {
42+ let iseq = match payload. versions . last ( ) {
43+ Some ( version_ref) => unsafe { version_ref. as_ref ( ) } . iseq ,
44+ None => return ,
45+ } ;
46+ with_vm_lock ( src_loc ! ( ) , || {
47+ trigger_recompilation ( payload_raw, iseq) ;
48+ } ) ;
49+ }
50+ }
51+
52+ static GLOBAL_RECOMPILE_COUNT : AtomicU64 = AtomicU64 :: new ( 0 ) ;
53+ const MAX_GLOBAL_RECOMPILATIONS : u64 = 50 ;
54+
55+ fn trigger_recompilation ( payload_raw : * mut std:: ffi:: c_void , iseq : IseqPtr ) {
56+ if MAX_GLOBAL_RECOMPILATIONS > 0 {
57+ let prev = GLOBAL_RECOMPILE_COUNT . fetch_add ( 1 , Ordering :: Relaxed ) ;
58+ if prev >= MAX_GLOBAL_RECOMPILATIONS {
59+ GLOBAL_RECOMPILE_COUNT . fetch_sub ( 1 , Ordering :: Relaxed ) ;
60+ return ;
61+ }
62+ }
63+ let payload = unsafe { & mut * ( payload_raw as * mut IseqPayload ) } ;
64+ debug ! ( "trigger_recompilation: recompiling {}" , iseq_get_location( iseq, 0 ) ) ;
65+ incr_counter ! ( recompile_count) ;
66+ payload. profile . reset_for_recompile ( ) ;
67+ if let Some ( version) = payload. versions . last_mut ( ) {
68+ let version = unsafe { version. as_mut ( ) } ;
69+ version. status = IseqStatus :: Invalidated ;
70+ }
71+ unsafe { rb_iseq_reset_jit_func ( iseq) } ;
72+ unsafe { rb_zjit_profile_enable ( iseq) } ;
73+ }
74+
75+ unsafe extern "C" {
76+ fn rb_zjit_profile_enable ( iseq : IseqPtr ) ;
77+ }
78+
3279/// Sentinel program counter stored in C frames when runtime checks are enabled.
3380const PC_POISON : Option < * const VALUE > = if cfg ! ( feature = "runtime_checks" ) {
3481 Some ( usize:: MAX as * const VALUE )
@@ -55,18 +102,24 @@ struct JITState {
55102
56103 /// ISEQ calls that need to be compiled later
57104 iseq_calls : Vec < IseqCallRef > ,
105+ payload_ptr : usize ,
106+ has_version_budget : bool ,
58107}
59108
60109impl JITState {
61- /// Create a new JITState instance
62110 fn new ( iseq : IseqPtr , version : IseqVersionRef , num_insns : usize , num_blocks : usize ) -> Self {
111+ let payload_ptr = get_or_create_iseq_payload ( iseq) as * const _ as usize ;
112+ let payload = unsafe { & * ( payload_ptr as * const IseqPayload ) } ;
113+ let has_version_budget = payload. versions . len ( ) < MAX_ISEQ_VERSIONS ;
63114 JITState {
64115 iseq,
65116 version,
66117 opnds : vec ! [ None ; num_insns] ,
67118 labels : vec ! [ None ; num_blocks] ,
68119 jit_entries : Vec :: default ( ) ,
69120 iseq_calls : Vec :: default ( ) ,
121+ payload_ptr,
122+ has_version_budget,
70123 }
71124 }
72125
@@ -152,16 +205,15 @@ pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, jit_exception: boo
152205 let mut code_ptr = with_time_stat ( compile_time_ns, || gen_iseq_entry_point ( cb, iseq, jit_exception) ) ;
153206
154207 if let Err ( err) = & code_ptr {
155- // Assert that the ISEQ compiles if RubyVM::ZJIT.assert_compiles is enabled.
156- // We assert only `jit_exception: false` cases until we support exception handlers.
157- if ZJITState :: assert_compiles_enabled ( ) && !jit_exception {
158- let iseq_location = iseq_get_location ( iseq, 0 ) ;
159- panic ! ( "Failed to compile: {iseq_location}" ) ;
160- }
161-
162- // For --zjit-stats, generate an entry that just increments exit_compilation_failure and exits
163- if get_option ! ( stats) {
164- code_ptr = gen_compile_error_counter ( cb, err) ;
208+ // DeferredForReprofiling is not a real failure
209+ if * err != CompileError :: DeferredForReprofiling {
210+ if ZJITState :: assert_compiles_enabled ( ) && !jit_exception {
211+ let iseq_location = iseq_get_location ( iseq, 0 ) ;
212+ panic ! ( "Failed to compile: {iseq_location}" ) ;
213+ }
214+ if get_option ! ( stats) {
215+ code_ptr = gen_compile_error_counter ( cb, err) ;
216+ }
165217 }
166218 }
167219
@@ -319,6 +371,10 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
319371 let mut jit = JITState :: new ( iseq, version, function. num_insns ( ) , function. num_blocks ( ) ) ;
320372 let mut asm = Assembler :: new_with_stack_slots ( num_spilled_params) ;
321373
374+ if get_option ! ( recompile_threshold) > 0 && jit. payload_ptr != 0 {
375+ asm. payload_ptr = Some ( jit. payload_ptr ) ;
376+ }
377+
322378 // Mapping from HIR block IDs to LIR block IDs.
323379 // This is is a one-to-one mapping from HIR to LIR blocks used for finding
324380 // jump targets in LIR (LIR should always jump to the head of an HIR block)
0 commit comments