@@ -16,7 +16,7 @@ use crate::stats::{exit_counter_for_compile_error, incr_counter, incr_counter_by
1616use crate :: stats:: { counter_ptr, with_time_stat, Counter , send_fallback_counter, Counter :: { compile_time_ns, exit_compile_error} } ;
1717use crate :: { asm:: CodeBlock , cruby:: * , options:: debug, virtualmem:: CodePtr } ;
1818use crate :: backend:: lir:: { self , asm_comment, asm_ccall, Assembler , Opnd , Target , CFP , C_ARG_OPNDS , C_RET_OPND , EC , NATIVE_STACK_PTR , NATIVE_BASE_PTR , SCRATCH_OPND , SP } ;
19- use crate :: hir:: { iseq_to_hir, Block , BlockId , BranchEdge , Invariant , MethodType , RangeType , SideExitReason :: { self , * } , SpecialBackrefSymbol , SpecialObjectType , SELF_PARAM_IDX } ;
19+ use crate :: hir:: { iseq_to_hir, BlockId , BranchEdge , Invariant , MethodType , RangeType , SideExitReason :: { self , * } , SpecialBackrefSymbol , SpecialObjectType } ;
2020use crate :: hir:: { Const , FrameState , Function , Insn , InsnId } ;
2121use crate :: hir_type:: { types, Type } ;
2222use crate :: options:: get_option;
@@ -126,7 +126,7 @@ fn gen_iseq_entry_point(cb: &mut CodeBlock, iseq: IseqPtr, jit_exception: bool)
126126 } ) ?;
127127
128128 // Compile an entry point to the JIT code
129- gen_entry ( cb, iseq, & function , start_ptr) . inspect_err ( |err| {
129+ gen_entry ( cb, iseq, start_ptr) . inspect_err ( |err| {
130130 debug ! ( "{err:?}: gen_entry failed: {}" , iseq_get_location( iseq, 0 ) ) ;
131131 } )
132132}
@@ -164,11 +164,10 @@ fn register_with_perf(iseq_name: String, start_ptr: usize, code_size: usize) {
164164}
165165
166166/// Compile a JIT entry
167- fn gen_entry ( cb : & mut CodeBlock , iseq : IseqPtr , function : & Function , function_ptr : CodePtr ) -> Result < CodePtr , CompileError > {
167+ fn gen_entry ( cb : & mut CodeBlock , iseq : IseqPtr , function_ptr : CodePtr ) -> Result < CodePtr , CompileError > {
168168 // Set up registers for CFP, EC, SP, and basic block arguments
169169 let mut asm = Assembler :: new ( ) ;
170170 gen_entry_prologue ( & mut asm, iseq) ;
171- gen_entry_params ( & mut asm, iseq, function. entry_block ( ) ) ;
172171
173172 // Jump to the first block using a call instruction
174173 asm. ccall ( function_ptr. raw_ptr ( cb) , vec ! [ ] ) ;
@@ -409,8 +408,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
409408 Insn :: GetIvar { self_val, id, state : _ } => gen_getivar ( asm, opnd ! ( self_val) , * id) ,
410409 Insn :: SetGlobal { id, val, state } => no_output ! ( gen_setglobal( jit, asm, * id, opnd!( val) , & function. frame_state( * state) ) ) ,
411410 Insn :: GetGlobal { id, state } => gen_getglobal ( jit, asm, * id, & function. frame_state ( * state) ) ,
412- & Insn :: GetLocal { ep_offset, level } => gen_getlocal_with_ep ( asm, ep_offset, level) ,
413- & Insn :: SetLocal { val, ep_offset, level } => no_output ! ( gen_setlocal_with_ep ( asm, opnd!( val) , function. type_of( val) , ep_offset, level) ) ,
411+ & Insn :: GetLocal { ep_offset, level, use_sp , .. } => gen_getlocal ( asm, ep_offset, level, use_sp ) ,
412+ & Insn :: SetLocal { val, ep_offset, level } => no_output ! ( gen_setlocal ( asm, opnd!( val) , function. type_of( val) , ep_offset, level) ) ,
414413 Insn :: GetConstantPath { ic, state } => gen_get_constant_path ( jit, asm, * ic, & function. frame_state ( * state) ) ,
415414 Insn :: SetIvar { self_val, id, val, state : _ } => no_output ! ( gen_setivar( asm, opnd!( self_val) , * id, opnd!( val) ) ) ,
416415 Insn :: SideExit { state, reason } => no_output ! ( gen_side_exit( jit, asm, reason, & function. frame_state( * state) ) ) ,
@@ -430,6 +429,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
430429 & Insn :: ArrayExtend { left, right, state } => { no_output ! ( gen_array_extend( jit, asm, opnd!( left) , opnd!( right) , & function. frame_state( state) ) ) } ,
431430 & Insn :: GuardShape { val, shape, state } => gen_guard_shape ( jit, asm, opnd ! ( val) , shape, & function. frame_state ( state) ) ,
432431 Insn :: LoadPC => gen_load_pc ( asm) ,
432+ Insn :: LoadSelf => gen_load_self ( ) ,
433433 & Insn :: LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded ( asm, opnd ! ( self_val) , id, index) ,
434434 & Insn :: LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended ( asm, opnd ! ( self_val) , id, index) ,
435435 & Insn :: ArrayMax { state, .. }
@@ -537,19 +537,28 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE,
537537/// Get a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs.
538538/// We generate this instruction with level=0 only when the local variable is on the heap, so we
539539/// can't optimize the level=0 case using the SP register.
540- fn gen_getlocal_with_ep ( asm : & mut Assembler , local_ep_offset : u32 , level : u32 ) -> lir:: Opnd {
540+ fn gen_getlocal ( asm : & mut Assembler , local_ep_offset : u32 , level : u32 , use_sp : bool ) -> lir:: Opnd {
541+ let local_ep_offset = i32:: try_from ( local_ep_offset) . unwrap_or_else ( |_| panic ! ( "Could not convert local_ep_offset {local_ep_offset} to i32" ) ) ;
541542 if level > 0 {
542543 gen_incr_counter ( asm, Counter :: vm_read_from_parent_iseq_local_count) ;
543544 }
544- let ep = gen_get_ep ( asm, level) ;
545- let offset = -( SIZEOF_VALUE_I32 * i32:: try_from ( local_ep_offset) . unwrap_or_else ( |_| panic ! ( "Could not convert local_ep_offset {local_ep_offset} to i32" ) ) ) ;
546- asm. load ( Opnd :: mem ( 64 , ep, offset) )
545+ let local = if use_sp {
546+ assert_eq ! ( level, 0 , "use_sp optimization should be used only for level=0 locals" ) ;
547+ let offset = -( SIZEOF_VALUE_I32 * ( local_ep_offset + 1 ) ) ;
548+ Opnd :: mem ( 64 , SP , offset)
549+ } else {
550+ let ep = gen_get_ep ( asm, level) ;
551+ let offset = -( SIZEOF_VALUE_I32 * local_ep_offset) ;
552+ Opnd :: mem ( 64 , ep, offset)
553+ } ;
554+ asm. load ( local)
547555}
548556
549557/// Set a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs.
550558/// We generate this instruction with level=0 only when the local variable is on the heap, so we
551559/// can't optimize the level=0 case using the SP register.
552- fn gen_setlocal_with_ep ( asm : & mut Assembler , val : Opnd , val_type : Type , local_ep_offset : u32 , level : u32 ) {
560+ fn gen_setlocal ( asm : & mut Assembler , val : Opnd , val_type : Type , local_ep_offset : u32 , level : u32 ) {
561+ let local_ep_offset = c_int:: try_from ( local_ep_offset) . unwrap_or_else ( |_| panic ! ( "Could not convert local_ep_offset {local_ep_offset} to i32" ) ) ;
553562 if level > 0 {
554563 gen_incr_counter ( asm, Counter :: vm_write_to_parent_iseq_local_count) ;
555564 }
@@ -558,12 +567,12 @@ fn gen_setlocal_with_ep(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep
558567 // When we've proved that we're writing an immediate,
559568 // we can skip the write barrier.
560569 if val_type. is_immediate ( ) {
561- let offset = -( SIZEOF_VALUE_I32 * i32 :: try_from ( local_ep_offset) . unwrap_or_else ( |_| panic ! ( "Could not convert local_ep_offset {local_ep_offset} to i32" ) ) ) ;
570+ let offset = -( SIZEOF_VALUE_I32 * local_ep_offset) ;
562571 asm. mov ( Opnd :: mem ( 64 , ep, offset) , val) ;
563572 } else {
564573 // We're potentially writing a reference to an IMEMO/env object,
565574 // so take care of the write barrier with a function.
566- let local_index = c_int :: try_from ( local_ep_offset) . ok ( ) . and_then ( |idx| idx . checked_mul ( - 1 ) ) . unwrap_or_else ( || panic ! ( "Could not turn {local_ep_offset} into a negative c_int" ) ) ;
575+ let local_index = local_ep_offset * - 1 ;
567576 asm_ccall ! ( asm, rb_vm_env_write, ep, local_index. into( ) , val) ;
568577 }
569578}
@@ -834,6 +843,10 @@ fn gen_load_pc(asm: &mut Assembler) -> Opnd {
834843 asm. load ( Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_PC ) )
835844}
836845
846+ fn gen_load_self ( ) -> Opnd {
847+ Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_SELF )
848+ }
849+
837850fn gen_load_ivar_embedded ( asm : & mut Assembler , self_val : Opnd , id : ID , index : u16 ) -> Opnd {
838851 // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
839852
@@ -871,53 +884,6 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) {
871884 asm. mov ( SP , Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_SP ) ) ;
872885}
873886
874- /// Assign method arguments to basic block arguments at JIT entry
875- fn gen_entry_params ( asm : & mut Assembler , iseq : IseqPtr , entry_block : & Block ) {
876- let num_params = entry_block. params ( ) . len ( ) - 1 ; // -1 to exclude self
877- if num_params > 0 {
878- asm_comment ! ( asm, "set method params: {num_params}" ) ;
879-
880- // Fill basic block parameters.
881- // Doing it in reverse is load-bearing. High index params have memory slots that might
882- // require using a register to fill. Filling them first avoids clobbering.
883- for idx in ( 0 ..num_params) . rev ( ) {
884- let param = param_opnd ( idx + 1 ) ; // +1 for self
885- let local = gen_entry_param ( asm, iseq, idx) ;
886-
887- // Funky offset adjustment to write into the native stack frame of the
888- // HIR function we'll be calling into. This only makes sense in context
889- // of the schedule of instructions in gen_entry() for the JIT entry point.
890- //
891- // The entry point needs to load VALUEs into native stack slots _before_ the
892- // frame containing the slots exists. So, we anticipate the stack frame size
893- // of the Function and subtract offsets based on that.
894- //
895- // native SP at entry point ─────►┌────────────┐ Native SP grows downwards
896- // │ │ ↓ on all arches we support.
897- // SP-0x8 ├────────────┤
898- // │ │
899- // where native SP SP-0x10├────────────┤
900- // would be while │ │
901- // the HIR function ────────────► └────────────┘
902- // is running
903- match param {
904- Opnd :: Mem ( lir:: Mem { base : _, disp, num_bits } ) => {
905- let param_slot = Opnd :: mem ( num_bits, NATIVE_STACK_PTR , disp - Assembler :: frame_size ( ) ) ;
906- asm. mov ( param_slot, local) ;
907- }
908- // Prepare for parallel move for locals in registers
909- reg @ Opnd :: Reg ( _) => {
910- asm. load_into ( reg, local) ;
911- }
912- _ => unreachable ! ( "on entry, params are either in memory or in reg. Got {param:?}" )
913- }
914-
915- // Assign local variables to the basic block arguments
916- }
917- }
918- asm. load_into ( param_opnd ( SELF_PARAM_IDX ) , Opnd :: mem ( VALUE_BITS , CFP , RUBY_OFFSET_CFP_SELF ) ) ;
919- }
920-
921887/// Set branch params to basic block arguments
922888fn gen_branch_params ( jit : & mut JITState , asm : & mut Assembler , branch : & BranchEdge ) {
923889 if branch. args . is_empty ( ) {
@@ -941,28 +907,6 @@ fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdg
941907 asm. parallel_mov ( moves) ;
942908}
943909
944- /// Get a method parameter on JIT entry. As of entry, whether EP is escaped or not solely
945- /// depends on the ISEQ type.
946- fn gen_entry_param ( asm : & mut Assembler , iseq : IseqPtr , local_idx : usize ) -> lir:: Opnd {
947- let ep_offset = local_idx_to_ep_offset ( iseq, local_idx) ;
948-
949- // If the ISEQ does not escape EP, we can optimize the local variable access using the SP register.
950- if !iseq_entry_escapes_ep ( iseq) {
951- // Create a reference to the local variable using the SP register. We assume EP == BP.
952- // TODO: Implement the invalidation in rb_zjit_invalidate_no_ep_escape()
953- let offs = -( SIZEOF_VALUE_I32 * ( ep_offset + 1 ) ) ;
954- Opnd :: mem ( 64 , SP , offs)
955- } else {
956- // Get the EP of the current CFP
957- let ep_opnd = Opnd :: mem ( 64 , CFP , RUBY_OFFSET_CFP_EP ) ;
958- let ep_reg = asm. load ( ep_opnd) ;
959-
960- // Create a reference to the local variable using cfp->ep
961- let offs = -( SIZEOF_VALUE_I32 * ep_offset) ;
962- Opnd :: mem ( 64 , ep_reg, offs)
963- }
964- }
965-
966910/// Compile a constant
967911fn gen_const_value ( val : VALUE ) -> lir:: Opnd {
968912 // Just propagate the constant value and generate nothing
@@ -1817,20 +1761,6 @@ fn build_side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason, l
18171761 }
18181762}
18191763
1820- /// Return true if a given ISEQ is known to escape EP to the heap on entry.
1821- ///
1822- /// As of vm_push_frame(), EP is always equal to BP. However, after pushing
1823- /// a frame, some ISEQ setups call vm_bind_update_env(), which redirects EP.
1824- fn iseq_entry_escapes_ep ( iseq : IseqPtr ) -> bool {
1825- match unsafe { get_iseq_body_type ( iseq) } {
1826- // <main> frame is always associated to TOPLEVEL_BINDING.
1827- ISEQ_TYPE_MAIN |
1828- // Kernel#eval uses a heap EP when a Binding argument is not nil.
1829- ISEQ_TYPE_EVAL => true ,
1830- _ => false ,
1831- }
1832- }
1833-
18341764/// Returne the maximum number of arguments for a block in a given function
18351765fn max_num_params ( function : & Function ) -> usize {
18361766 let reverse_post_order = function. rpo ( ) ;
0 commit comments