@@ -7486,7 +7486,7 @@ fn gen_send_bmethod(
74867486 }
74877487
74887488 let frame_type = VM_FRAME_MAGIC_BLOCK | VM_FRAME_FLAG_BMETHOD | VM_FRAME_FLAG_LAMBDA ;
7489- perf_call ! { gen_send_iseq( jit, asm, iseq, ci, frame_type, Some ( capture. ep) , cme, block, flags, argc, None ) }
7489+ perf_call ! { gen_send_iseq( jit, asm, iseq, ci, frame_type, Some ( capture. ep) , cme, block, flags, argc, None , false ) }
74907490}
74917491
74927492/// The kind of a value an ISEQ returns
@@ -7582,6 +7582,10 @@ fn gen_send_iseq(
75827582 flags : u32 ,
75837583 argc : i32 ,
75847584 captured_opnd : Option < Opnd > ,
7585+ // The block param proxy receiver is on the stack below the args. self/EP come from
7586+ // the captured block (as for invokeblock); the proxy is shifted off after the
7587+ // overflow check so the rest of the frame setup is a plain invokeblock.
7588+ proxy_recv : bool ,
75857589) -> Option < CodegenStatus > {
75867590 // Argument count. We will change this as we gather values from
75877591 // sources to satisfy the callee's parameters. To help make sense
@@ -7747,6 +7751,13 @@ fn gen_send_iseq(
77477751 && !get_iseq_flags_ambiguous_param0 ( iseq)
77487752 } ;
77497753 if block_arg0_splat {
7754+ // The block param proxy removes its receiver from the stack only after the
7755+ // overflow check (see proxy_recv below). arg0 auto-splat needs a side exit past
7756+ // that point, which can't reconstruct the proxy at the opt_send_without_block PC.
7757+ if proxy_recv {
7758+ gen_counter_incr ( jit, asm, Counter :: send_bpp_arg0_splat) ;
7759+ return None ;
7760+ }
77507761 // If block_arg0_splat, we still need side exits after splat, but
77517762 // the splat modifies the stack which breaks side exits. So bail out.
77527763 if splat_call {
@@ -7876,7 +7887,7 @@ fn gen_send_iseq(
78767887 }
78777888 IseqReturn :: Value ( value) => {
78787889 // Pop receiver and arguments
7879- asm. stack_pop ( argc as usize + if captured_opnd. is_some ( ) { 0 } else { 1 } ) ;
7890+ asm. stack_pop ( argc as usize + if captured_opnd. is_some ( ) && !proxy_recv { 0 } else { 1 } ) ;
78807891
78817892 // Push the return value
78827893 let stack_ret = asm. stack_push ( Type :: from ( value) ) ;
@@ -7903,6 +7914,11 @@ fn gen_send_iseq(
79037914 asm. cmp ( CFP , stack_limit) ;
79047915 asm. jbe ( Target :: side_exit ( Counter :: guard_send_se_cf_overflow) ) ;
79057916
7917+ // Remove the block param proxy receiver from the stack, mirroring vm_invoke_block_opt_call
7918+ if proxy_recv {
7919+ handle_opt_send_shift_stack ( asm, argc) ;
7920+ }
7921+
79067922 if iseq_has_rest && splat_call {
79077923 // Insert length guard for a call to copy_splat_args_for_rest_callee()
79087924 // that will come later. We will have made changes to
@@ -9155,9 +9171,11 @@ fn gen_send_general(
91559171
91569172 // Don't compile calls through singleton classes to avoid retaining the receiver.
91579173 // Make an exception for class methods since classes tend to be retained anyways.
9158- // Also compile calls on top_self to help tests.
9174+ // Also compile calls on top_self to help tests. The block param proxy is an
9175+ // immortal global root, so retaining it is a non-issue.
91599176 if VALUE ( 0 ) != unsafe { FL_TEST ( comptime_recv_klass, VALUE ( RUBY_FL_SINGLETON as usize ) ) }
91609177 && comptime_recv != unsafe { rb_vm_top_self ( ) }
9178+ && comptime_recv != unsafe { rb_block_param_proxy }
91619179 && !unsafe { RB_TYPE_P ( comptime_recv, RUBY_T_CLASS ) }
91629180 && !unsafe { RB_TYPE_P ( comptime_recv, RUBY_T_MODULE ) } {
91639181 gen_counter_incr ( jit, asm, Counter :: send_singleton_class) ;
@@ -9244,7 +9262,7 @@ fn gen_send_general(
92449262 VM_METHOD_TYPE_ISEQ => {
92459263 let iseq = unsafe { get_def_iseq_ptr ( ( * cme) . def ) } ;
92469264 let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL ;
9247- return perf_call ! { gen_send_iseq( jit, asm, iseq, ci, frame_type, None , cme, block, flags, argc, None ) } ;
9265+ return perf_call ! { gen_send_iseq( jit, asm, iseq, ci, frame_type, None , cme, block, flags, argc, None , false ) } ;
92489266 }
92499267 VM_METHOD_TYPE_CFUNC => {
92509268 return perf_call ! { gen_send_cfunc(
@@ -9489,8 +9507,7 @@ fn gen_send_general(
94899507 return jump_to_next_insn ( jit, asm) ;
94909508 }
94919509 OPTIMIZED_METHOD_TYPE_BLOCK_CALL => {
9492- gen_counter_incr ( jit, asm, Counter :: send_optimized_method_block_call) ;
9493- return None ;
9510+ return gen_send_block_param_proxy ( jit, asm, ci, block, flags, argc) ;
94949511 }
94959512 OPTIMIZED_METHOD_TYPE_STRUCT_AREF => {
94969513 if flags & VM_CALL_ARGS_SPLAT != 0 {
@@ -9770,7 +9787,7 @@ fn gen_invokeblock_specialized(
97709787 Counter :: guard_invokeblock_iseq_block_changed,
97719788 ) ;
97729789
9773- perf_call ! { gen_send_iseq( jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK , None , 0 as _, None , flags, argc, Some ( captured_opnd) ) }
9790+ perf_call ! { gen_send_iseq( jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK , None , 0 as _, None , flags, argc, Some ( captured_opnd) , false ) }
97749791 } else if comptime_handler. 0 & 0x3 == 0x3 { // VM_BH_IFUNC_P
97759792 // We aren't handling CALLER_SETUP_ARG and CALLER_REMOVE_EMPTY_KW_SPLAT yet.
97769793 if flags & VM_CALL_ARGS_SPLAT != 0 {
@@ -9831,6 +9848,87 @@ fn gen_invokeblock_specialized(
98319848 }
98329849}
98339850
9851+ // blk.call where blk is the block param proxy. Inline the block invocation the way invokeblock does
9852+ // instead of dispatching through the proxy's singleton method. The receiver's singleton class was already
9853+ // guarded by the caller, which is enough to prove the receiver is the (unique) block param proxy.
9854+ fn gen_send_block_param_proxy (
9855+ jit : & mut JITState ,
9856+ asm : & mut Assembler ,
9857+ ci : * const rb_callinfo ,
9858+ block : Option < BlockHandler > ,
9859+ flags : u32 ,
9860+ argc : i32 ,
9861+ ) -> Option < CodegenStatus > {
9862+ // Anything fancier than a plain call falls back to dynamic dispatch, which
9863+ // materializes a Proc and dispatches normally (also handling redefinition).
9864+ if block. is_some ( ) {
9865+ gen_counter_incr ( jit, asm, Counter :: send_bpp_not_simple) ;
9866+ return None ;
9867+ }
9868+ if flags & ( VM_CALL_ARGS_SPLAT | VM_CALL_KWARG | VM_CALL_KW_SPLAT | VM_CALL_ARGS_BLOCKARG | VM_CALL_OPT_SEND ) != 0 {
9869+ gen_counter_incr ( jit, asm, Counter :: send_bpp_not_simple) ;
9870+ return None ;
9871+ }
9872+
9873+ // Fall back to dynamic dispatch if this callsite is megamorphic
9874+ if asm. ctx . get_chain_depth ( ) >= SEND_MAX_DEPTH {
9875+ gen_counter_incr ( jit, asm, Counter :: send_bpp_megamorphic) ;
9876+ return None ;
9877+ }
9878+
9879+ // The block handler lives in the local EP: the same source both the interpreter's
9880+ // vm_call_opt_block_call and the getblockparamproxy instruction read from.
9881+ let cfp = jit. get_cfp ( ) ;
9882+ let lep = unsafe { rb_vm_ep_local_ep ( get_cfp_ep ( cfp) ) } ;
9883+ let comptime_handler = unsafe { * lep. offset ( VM_ENV_DATA_INDEX_SPECVAL as isize ) } ;
9884+
9885+ // Only specialize an ISEQ block; other block handler types fall back to dynamic dispatch.
9886+ if comptime_handler. 0 & 0x3 != 0x1 { // VM_BH_ISEQ_BLOCK_P
9887+ gen_counter_incr ( jit, asm, Counter :: send_bpp_not_iseq_block) ;
9888+ return None ;
9889+ }
9890+
9891+ if !assume_bop_not_redefined ( jit, asm, PROC_REDEFINED_OP_FLAG , BOP_CALL ) {
9892+ return None ;
9893+ }
9894+
9895+ gen_counter_incr ( jit, asm, Counter :: send_bpp_dispatch) ;
9896+
9897+ asm_comment ! ( asm, "get local EP" ) ;
9898+ let ep_opnd = gen_get_lep ( jit, asm) ;
9899+ let block_handler_opnd = asm. load (
9900+ Opnd :: mem ( 64 , ep_opnd, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL )
9901+ ) ;
9902+
9903+ asm_comment ! ( asm, "guard block_handler type" ) ;
9904+ let tag_opnd = asm. and ( block_handler_opnd, 0x3 . into ( ) ) ;
9905+ asm. cmp ( tag_opnd, 0x1 . into ( ) ) ; // VM_BH_ISEQ_BLOCK_P
9906+ jit_chain_guard (
9907+ JCC_JNE ,
9908+ jit,
9909+ asm,
9910+ SEND_MAX_DEPTH ,
9911+ Counter :: guard_invokeblock_tag_changed,
9912+ ) ;
9913+
9914+ let comptime_captured = unsafe { ( ( comptime_handler. 0 & !0x3 ) as * const rb_captured_block ) . as_ref ( ) . unwrap ( ) } ;
9915+ let comptime_iseq = unsafe { * comptime_captured. code . iseq . as_ref ( ) } ;
9916+
9917+ asm_comment ! ( asm, "guard known ISEQ" ) ;
9918+ let captured_opnd = asm. and ( block_handler_opnd, Opnd :: Imm ( !0x3 ) ) ;
9919+ let iseq_opnd = asm. load ( Opnd :: mem ( 64 , captured_opnd, SIZEOF_VALUE_I32 * 2 ) ) ;
9920+ asm. cmp ( iseq_opnd, VALUE :: from ( comptime_iseq) . into ( ) ) ;
9921+ jit_chain_guard (
9922+ JCC_JNE ,
9923+ jit,
9924+ asm,
9925+ SEND_MAX_DEPTH ,
9926+ Counter :: guard_invokeblock_iseq_block_changed,
9927+ ) ;
9928+
9929+ perf_call ! { gen_send_iseq( jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK , None , 0 as _, None , flags, argc, Some ( captured_opnd) , true ) }
9930+ }
9931+
98349932fn gen_invokesuper (
98359933 jit : & mut JITState ,
98369934 asm : & mut Assembler ,
@@ -10000,7 +10098,7 @@ fn gen_invokesuper_specialized(
1000010098 VM_METHOD_TYPE_ISEQ => {
1000110099 let iseq = unsafe { get_def_iseq_ptr ( ( * cme) . def ) } ;
1000210100 let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL ;
10003- perf_call ! { gen_send_iseq( jit, asm, iseq, ci, frame_type, None , cme, Some ( block) , ci_flags, argc, None ) }
10101+ perf_call ! { gen_send_iseq( jit, asm, iseq, ci, frame_type, None , cme, Some ( block) , ci_flags, argc, None , false ) }
1000410102 }
1000510103 VM_METHOD_TYPE_CFUNC => {
1000610104 perf_call ! { gen_send_cfunc( jit, asm, ci, cme, Some ( block) , None , ci_flags, argc) }
0 commit comments