@@ -524,7 +524,6 @@ pub enum SideExitReason {
524524 CalleeSideExit ,
525525 ObjToStringFallback ,
526526 Interrupt ,
527- BlockParamProxyModified ,
528527 BlockParamProxyNotIseqOrIfunc ,
529528 BlockParamProxyNotNil ,
530529 BlockParamWbRequired ,
@@ -2611,6 +2610,24 @@ impl Function {
26112610 id
26122611 }
26132612
2613+ fn new_branch_block (
2614+ & mut self ,
2615+ insn_idx : u32 ,
2616+ exit_state : & FrameState ,
2617+ locals_count : usize ,
2618+ stack_count : usize ,
2619+ ) -> ( BlockId , InsnId , FrameState , InsnId ) {
2620+ let block = self . new_block ( insn_idx) ;
2621+ let self_param = self . push_insn ( block, Insn :: Param ) ;
2622+ let mut state = exit_state. clone ( ) ;
2623+ state. locals . clear ( ) ;
2624+ state. stack . clear ( ) ;
2625+ state. locals . extend ( ( 0 ..locals_count) . map ( |_| self . push_insn ( block, Insn :: Param ) ) ) ;
2626+ state. stack . extend ( ( 0 ..stack_count) . map ( |_| self . push_insn ( block, Insn :: Param ) ) ) ;
2627+ let snapshot = self . push_insn ( block, Insn :: Snapshot { state : state. clone ( ) } ) ;
2628+ ( block, self_param, state, snapshot)
2629+ }
2630+
26142631 fn remove_block ( & mut self , block_id : BlockId ) {
26152632 if BlockId ( self . blocks . len ( ) - 1 ) != block_id {
26162633 panic ! ( "Can only remove the last block" ) ;
@@ -7578,7 +7595,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
75787595 } ) ;
75797596 }
75807597 YARVINSN_getblockparamproxy => {
7598+ let ep_offset = get_arg ( pc, 0 ) . as_u32 ( ) ;
75817599 let level = get_arg ( pc, 1 ) . as_u32 ( ) ;
7600+ let branch_insn_idx = exit_state. insn_idx as u32 ;
75827601
75837602 let profiled_block_type = if let Some ( [ block_handler_distribution] ) = profiles. payload . profile . get_operand_types ( exit_state. insn_idx ) {
75847603 let summary = TypeDistributionSummary :: new ( block_handler_distribution) ;
@@ -7587,16 +7606,44 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
75877606 None
75887607 } ;
75897608
7609+ let locals_count = state. locals . len ( ) ;
7610+ let stack_count = state. stack . len ( ) ;
7611+ let entry_args = state. as_args ( self_param) ;
7612+
7613+ // `getblockparamproxy` has two semantic paths:
7614+ // - modified: return the already-materialized block local from EP
7615+ // - unmodified: inspect the block handler and produce proxy/nil
7616+ let ( modified_block, modified_self_param, mut modified_state, ..) =
7617+ fun. new_branch_block ( branch_insn_idx, & exit_state, locals_count, stack_count) ;
7618+ let ( unmodified_block, unmodified_self_param, mut unmodified_state, unmodified_exit_id) =
7619+ fun. new_branch_block ( branch_insn_idx, & exit_state, locals_count, stack_count) ;
7620+ let join_block = insn_idx_to_block. get ( & insn_idx) . copied ( ) . unwrap_or_else ( || fun. new_block ( insn_idx) ) ;
7621+
75907622 let ep = fun. push_insn ( block, Insn :: GetEP { level } ) ;
7591- let flags = fun. push_insn ( block, Insn :: LoadField { recv : ep, id : ID ! ( _env_data_index_flags) , offset : SIZEOF_VALUE_I32 * ( VM_ENV_DATA_INDEX_FLAGS as i32 ) , return_type : types:: CInt64 } ) ;
7592- fun. push_insn ( block, Insn :: GuardNoBitsSet { val : flags, mask : Const :: CUInt64 ( VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM . into ( ) ) , mask_name : Some ( ID ! ( VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM ) ) , reason : SideExitReason :: BlockParamProxyModified , state : exit_id } ) ;
7623+ let is_modified = fun. push_insn ( block, Insn :: IsBlockParamModified { ep } ) ;
7624+
7625+ fun. push_insn ( block, Insn :: IfTrue { val : is_modified, target : BranchEdge { target : modified_block, args : entry_args. clone ( ) } } ) ;
7626+ fun. push_insn ( block, Insn :: Jump ( BranchEdge { target : unmodified_block, args : entry_args } ) ) ;
7627+
7628+ // Push modified block: load the block local via EP.
7629+ let ep = fun. push_insn ( modified_block, Insn :: GetEP { level } ) ;
7630+ let modified_val = fun. get_local_from_ep ( modified_block, ep, ep_offset, level, types:: BasicObject ) ;
7631+ if level == 0 {
7632+ modified_state. setlocal ( ep_offset, modified_val) ;
7633+ }
7634+ modified_state. stack_push ( modified_val) ;
7635+ fun. push_insn ( modified_block, Insn :: Jump ( BranchEdge { target : join_block, args : modified_state. as_args ( modified_self_param) } ) ) ;
75937636
7594- let block_handler = fun. push_insn ( block, Insn :: LoadField { recv : ep, id : ID ! ( _env_data_index_specval) , offset : SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL , return_type : types:: CInt64 } ) ;
7637+ // Push unmodified block: inspect the current block handler to
7638+ // decide whether this path returns `nil` or `BlockParamProxy`.
7639+ let ep = fun. push_insn ( unmodified_block, Insn :: GetEP { level } ) ;
7640+ let block_handler = fun. push_insn ( unmodified_block, Insn :: LoadField { recv : ep, id : ID ! ( _env_data_index_specval) , offset : SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL , return_type : types:: CInt64 } ) ;
75957641
75967642 match profiled_block_type {
75977643 Some ( ty) if ty. nil_p ( ) => {
7598- fun. push_insn ( block, Insn :: GuardBitEquals { val : block_handler, expected : Const :: CInt64 ( VM_BLOCK_HANDLER_NONE . into ( ) ) , reason : SideExitReason :: BlockParamProxyNotNil , state : exit_id } ) ;
7599- state. stack_push ( fun. push_insn ( block, Insn :: Const { val : Const :: Value ( Qnil ) } ) ) ;
7644+ fun. push_insn ( unmodified_block, Insn :: GuardBitEquals { val : block_handler, expected : Const :: CInt64 ( VM_BLOCK_HANDLER_NONE . into ( ) ) , reason : SideExitReason :: BlockParamProxyNotNil , state : unmodified_exit_id } ) ;
7645+ unmodified_state. stack_push ( fun. push_insn ( unmodified_block, Insn :: Const { val : Const :: Value ( Qnil ) } ) ) ;
7646+ fun. push_insn ( unmodified_block, Insn :: Jump ( BranchEdge { target : join_block, args : unmodified_state. as_args ( unmodified_self_param) } ) ) ;
76007647 }
76017648 _ => {
76027649 // This handles two cases which are nearly identical
@@ -7607,31 +7654,19 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
76077654 const _: ( ) = assert ! ( RUBY_SYMBOL_FLAG & 1 == 0 , "guard below rejects symbol block handlers" ) ;
76087655
76097656 // Bail out if the block handler is neither ISEQ nor ifunc
7610- fun. push_insn ( block , Insn :: GuardAnyBitSet { val : block_handler, mask : Const :: CUInt64 ( 0x1 ) , mask_name : None , reason : SideExitReason :: BlockParamProxyNotIseqOrIfunc , state : exit_id } ) ;
7657+ fun. push_insn ( unmodified_block , Insn :: GuardAnyBitSet { val : block_handler, mask : Const :: CUInt64 ( 0x1 ) , mask_name : None , reason : SideExitReason :: BlockParamProxyNotIseqOrIfunc , state : unmodified_exit_id } ) ;
76117658 // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing
7612- state. stack_push ( fun. push_insn ( block, Insn :: Const { val : Const :: Value ( unsafe { rb_block_param_proxy } ) } ) ) ;
7659+ unmodified_state. stack_push ( fun. push_insn ( unmodified_block, Insn :: Const { val : Const :: Value ( unsafe { rb_block_param_proxy } ) } ) ) ;
7660+ fun. push_insn ( unmodified_block, Insn :: Jump ( BranchEdge { target : join_block, args : unmodified_state. as_args ( unmodified_self_param) } ) ) ;
76137661 }
76147662 }
7663+
7664+ // Continue compilation from the merged continuation block at the next
7665+ // instruction.
7666+ queue. push_back ( ( unmodified_state, join_block, insn_idx, local_inval) ) ;
7667+ break ;
76157668 }
76167669 YARVINSN_getblockparam => {
7617- fn new_branch_block (
7618- fun : & mut Function ,
7619- insn_idx : u32 ,
7620- exit_state : & FrameState ,
7621- locals_count : usize ,
7622- stack_count : usize ,
7623- ) -> ( BlockId , InsnId , FrameState , InsnId ) {
7624- let block = fun. new_block ( insn_idx) ;
7625- let self_param = fun. push_insn ( block, Insn :: Param ) ;
7626- let mut state = exit_state. clone ( ) ;
7627- state. locals . clear ( ) ;
7628- state. stack . clear ( ) ;
7629- state. locals . extend ( ( 0 ..locals_count) . map ( |_| fun. push_insn ( block, Insn :: Param ) ) ) ;
7630- state. stack . extend ( ( 0 ..stack_count) . map ( |_| fun. push_insn ( block, Insn :: Param ) ) ) ;
7631- let snapshot = fun. push_insn ( block, Insn :: Snapshot { state : state. clone ( ) } ) ;
7632- ( block, self_param, state, snapshot)
7633- }
7634-
76357670 fn finish_getblockparam_branch (
76367671 fun : & mut Function ,
76377672 block : BlockId ,
@@ -7667,9 +7702,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
76677702
76687703 // Set up branch and join blocks.
76697704 let ( modified_block, modified_self_param, mut modified_state, ..) =
7670- new_branch_block ( & mut fun , branch_insn_idx, & exit_state, locals_count, stack_count) ;
7705+ fun . new_branch_block ( branch_insn_idx, & exit_state, locals_count, stack_count) ;
76717706 let ( unmodified_block, unmodified_self_param, mut unmodified_state, unmodified_exit_id) =
7672- new_branch_block ( & mut fun , branch_insn_idx, & exit_state, locals_count, stack_count) ;
7707+ fun . new_branch_block ( branch_insn_idx, & exit_state, locals_count, stack_count) ;
76737708 let join_block = insn_idx_to_block. get ( & insn_idx) . copied ( ) . unwrap_or_else ( || fun. new_block ( insn_idx) ) ;
76747709
76757710 fun. push_insn ( block, Insn :: IfTrue {
0 commit comments