@@ -61,9 +61,10 @@ struct JITState {
6161 iseq_calls : Vec < IseqCallRef > ,
6262
6363 /// The number of native stack slots reserved for JITFrame, one per
64- /// simultaneously live frame (`inlining_depth() + 1`). gen_write_jit_frame()
65- /// and the inlined frame push write a JITFrame into the slot selected by the
66- /// current frame's depth.
64+ /// simultaneously live frame (`inlining_depth() + 1`), plus one shared slot
65+ /// for the saved SP register at the bottom. gen_write_jit_frame() and the
66+ /// inlined frame push write a JITFrame into the slot selected by the current
67+ /// frame's depth; gen_prepare_non_leaf_call() writes the SP slot.
6768 jit_frame_size : usize ,
6869}
6970
@@ -107,6 +108,18 @@ impl JITState {
107108 }
108109 }
109110
111+ /// Byte offset from [NATIVE_BASE_PTR] to the slot holding the SP VM stack base pointer.
112+ fn base_ptr_slot_native_base_ptr_offset ( & self ) -> i32 {
113+ -( i32:: try_from ( SIZEOF_VALUE * self . jit_frame_size ) . expect ( "base_ptr_slot_index overflow" ) )
114+ }
115+
116+ /// The VALUE index a frame at `depth` uses to read the saved SP register as
117+ /// `((VALUE **)cfp->jit_return)[-index]`. Distance between this inline frame's
118+ /// `jit_return` and first slot past all `(jit_return, jit_frame)` tuples.
119+ /// Encoded into [`StackMapEntry::BasePtr`].
120+ fn base_ptr_slot_index ( & self , depth : InlineDepth ) -> u32 {
121+ ( self . jit_frame_size - depth) . try_into ( ) . expect ( "base_ptr slot index overflow" )
122+ }
110123}
111124
112125impl Assembler {
@@ -406,7 +419,10 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
406419 // frame push select among these slots by the frame's depth, keeping each
407420 // frame's `cfp->jit_return` pointed at its own slot rather than a shared
408421 // one.
409- let jit_frame_size = function. inlining_depth ( ) + 1 ;
422+ //
423+ // One more slot below those holds the saved SP register that stack maps
424+ // are anchored on (see base_ptr_slot_offset()).
425+ let jit_frame_size = function. inlining_depth ( ) + 2 ;
410426 let mut jit = JITState :: new ( version, function. num_insns ( ) , function. num_blocks ( ) , jit_frame_size) ;
411427 let mut asm = Assembler :: new_with_stack_slots ( jit_frame_size) ;
412428
@@ -3243,8 +3259,9 @@ pub(crate) fn block_iseq_may_throw(iseq: IseqPtr) -> bool {
32433259/// Byte offset from NATIVE_BASE_PTR of the JITFrame storage slot for a frame at
32443260/// the given inlining depth. Depth 0 (the top-level frame) lives at
32453261/// `[NATIVE_BASE_PTR - 8]`; each deeper inlined frame gets the next slot below.
3246- /// gen_function() reserves `inlining_depth() + 1` slots, so every live frame's
3247- /// depth maps to a distinct slot inside that reserved region.
3262+ /// gen_function() reserves `inlining_depth() + 1` of these, so every live
3263+ /// frame's depth maps to a distinct slot, followed by the single saved-SP slot
3264+ /// of base_ptr_slot_offset().
32483265fn jit_frame_slot_offset ( depth : InlineDepth ) -> i32 {
32493266 -( SIZEOF_VALUE_I32 * ( depth as i32 + 1 ) )
32503267}
@@ -3365,6 +3382,8 @@ fn gen_spill_stack(jit: &JITState, asm: &mut Assembler, function: &Function, sta
33653382 StackMapEntry :: Skip ( skip) => {
33663383 offset -= skip as i32 ;
33673384 }
3385+ // Only gen_prepare_non_leaf_call() prepends this, and it doesn't spill.
3386+ StackMapEntry :: BasePtr { .. } => unreachable ! ( "build_stack_map() does not emit BasePtr" ) ,
33683387 }
33693388 }
33703389}
@@ -3416,12 +3435,21 @@ fn inline_frame_stack_gap(iseq: IseqPtr) -> usize {
34163435/// Prepare for calling a C function that may call an arbitrary method.
34173436/// Use gen_prepare_leaf_call_with_gc() if the method is leaf but allocates objects.
34183437fn gen_prepare_non_leaf_call ( jit : & JITState , asm : & mut Assembler , function : & Function , state : & FrameState ) {
3419- // TODO: Lazily materialize caller frames when needed
3420- // Save PC for backtraces and allocation tracing
3421- // and SP to avoid marking uninitialized stack slots
3422- let stack_map = build_stack_map ( jit, function, state) ;
3438+ // Anchor the stack map on a private copy of SP rather than on cfp->sp. The callee is free to
3439+ // use the stack map after pushing through and moving cfp->sp (e.g. rb_funcall() + a raise in
3440+ // vm_callee_setup_arg()).
3441+ let mut stack_map = vec ! [ StackMapEntry :: BasePtr {
3442+ slot_index: jit. base_ptr_slot_index( state. depth) ,
3443+ stack_size: state. stack_size( ) . try_into( ) . expect( "stack size overflow" ) ,
3444+ } ] ;
3445+ stack_map. extend ( build_stack_map ( jit, function, state) ) ;
34233446 let jit_frame = gen_prepare_call_with_gc ( asm, state, false , stack_map. len ( ) ) ;
34243447
3448+ // NOTE(alan): This store can be done once on function entry, but analysis is required
3449+ // to avoid the store in functions that make no non-leaf call.
3450+ asm_comment ! ( asm, "save SP as the stack map anchor" ) ;
3451+ asm. mov ( Opnd :: mem ( 64 , NATIVE_BASE_PTR , jit. base_ptr_slot_native_base_ptr_offset ( ) ) , SP ) ;
3452+
34253453 // Remember the stack map in case it raises an exception
34263454 // and the interpreter uses the stack for handling the exception
34273455 asm. stack_map ( stack_map, jit_frame, state. depth ) ;
0 commit comments