@@ -234,20 +234,36 @@ impl<'a> JITState<'a> {
234234 result
235235 }
236236
237- /// Return true if the current ISEQ could escape an environment.
237+ /// Return true if the JIT code can use [`Self::assume_no_ep_escape`]
238+ /// and will run with an on-stack (`!VM_ENV_ESCAPED_P`) environment.
238239 ///
239- /// As of vm_push_frame(), EP is always equal to BP. However, after pushing
240- /// a frame, some ISEQ setups call vm_bind_update_env(), which redirects EP.
241- /// Also, some method calls escape the environment to the heap.
242- fn escapes_ep ( & self ) -> bool {
240+ /// ## Reasoning about ISEQs that are not currently running
241+ ///
242+ /// As of vm_push_frame() and its JIT code equivalent, EP is always equal to BP (the
243+ /// environment is on-stack and has not escaped). We can usually assume this is the starting
244+ /// condition upon entry into JIT code. However, after pushing a frame and before entry into
245+ /// JIT code, some ISEQ setups call vm_bind_update_env(), which redirects EP.
246+ ///
247+ /// ## After making the assumption
248+ ///
249+ /// After JIT code entry, many ruby operations can have the environment escape to heap. These
250+ /// are handled by [`crate::invariants`].
251+ ///
252+ /// Exceptional entry through jit_exec_exception() is an extreme case of the environment state
253+ /// changing between vm_push_frame() and entry into JIT code. We reject exceptional entries
254+ /// with an escaped environment.
255+ fn can_assume_on_stack_env ( & self ) -> bool {
243256 match unsafe { get_iseq_body_type ( self . iseq ) } {
244257 // <main> frame is always associated to TOPLEVEL_BINDING.
245258 ISEQ_TYPE_MAIN |
246259 // Kernel#eval uses a heap EP when a Binding argument is not nil.
247- ISEQ_TYPE_EVAL => true ,
248- // If this ISEQ has previously escaped EP, give up the optimization.
249- _ if iseq_escapes_ep ( self . iseq ) => true ,
250- _ => false ,
260+ ISEQ_TYPE_EVAL => false ,
261+ // When compiling for an already escaped environment, it's not on-stack.
262+ _ if unsafe { self . iseq == get_cfp_iseq ( self . get_cfp ( ) ) && cfp_env_has_escaped ( self . get_cfp ( ) ) } => false ,
263+ // If we've seen this ISEQ run with an escaped environment, give up the optimization
264+ // to avoid excessive invalidations (even though it may be fine for soundness).
265+ _ if seen_escaped_env ( self . iseq ) => false ,
266+ _ => true ,
251267 }
252268 }
253269
@@ -376,8 +392,8 @@ impl<'a> JITState<'a> {
376392 if jit_ensure_block_entry_exit ( self , asm) . is_none ( ) {
377393 return false ; // out of space, give up
378394 }
379- if self . escapes_ep ( ) {
380- return false ; // EP has been escaped in this ISEQ. disable the optimization to avoid an invalidation loop.
395+ if ! self . can_assume_on_stack_env ( ) {
396+ return false ; // Unsound or unprofitable to make the assumption
381397 }
382398 self . no_ep_escape = true ;
383399 true
@@ -2509,7 +2525,7 @@ fn gen_getlocal_generic(
25092525 level : u32 ,
25102526) -> Option < CodegenStatus > {
25112527 // Split the block if we need to invalidate this instruction when EP escapes
2512- if level == 0 && ! jit. escapes_ep ( ) && !jit. at_compile_target ( ) {
2528+ if level == 0 && jit. can_assume_on_stack_env ( ) && !jit. at_compile_target ( ) {
25132529 return jit. defer_compilation ( asm) ;
25142530 }
25152531
@@ -2610,7 +2626,7 @@ fn gen_setlocal_generic(
26102626 }
26112627
26122628 // Split the block if we need to invalidate this instruction when EP escapes
2613- if level == 0 && ! jit. escapes_ep ( ) && !jit. at_compile_target ( ) {
2629+ if level == 0 && jit. can_assume_on_stack_env ( ) && !jit. at_compile_target ( ) {
26142630 return jit. defer_compilation ( asm) ;
26152631 }
26162632
0 commit comments