@@ -531,6 +531,8 @@ pub enum SideExitReason {
531531 Interrupt ,
532532 BlockParamProxyNotIseqOrIfunc ,
533533 BlockParamProxyNotNil ,
534+ BlockParamProxyFallbackMiss ,
535+ BlockParamProxyProfileNotCovered ,
534536 BlockParamWbRequired ,
535537 StackOverflow ,
536538 FixnumModByZero ,
@@ -7575,17 +7577,31 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
75757577 } ) ;
75767578 }
75777579 YARVINSN_getblockparamproxy => {
7580+ #[ derive( Clone , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
7581+ enum ProfiledBlockHandlerFamily {
7582+ Nil ,
7583+ IseqOrIfunc ,
7584+ }
7585+ impl ProfiledBlockHandlerFamily {
7586+ fn from_profiled_type ( profiled_type : ProfiledType ) -> Option < Self > {
7587+ let obj = profiled_type. class ( ) ;
7588+ if obj. nil_p ( ) {
7589+ Some ( Self :: Nil )
7590+ } else if unsafe {
7591+ rb_IMEMO_TYPE_P ( obj, imemo_iseq) == 1
7592+ || rb_IMEMO_TYPE_P ( obj, imemo_ifunc) == 1
7593+ } {
7594+ Some ( Self :: IseqOrIfunc )
7595+ } else {
7596+ None
7597+ }
7598+ }
7599+ }
7600+
75787601 let ep_offset = get_arg ( pc, 0 ) . as_u32 ( ) ;
75797602 let level = get_arg ( pc, 1 ) . as_u32 ( ) ;
75807603 let branch_insn_idx = exit_state. insn_idx as u32 ;
75817604
7582- let profiled_block_type = if let Some ( [ block_handler_distribution] ) = profiles. payload . profile . get_operand_types ( exit_state. insn_idx ) {
7583- let summary = TypeDistributionSummary :: new ( block_handler_distribution) ;
7584- summary. is_monomorphic ( ) . then_some ( summary. bucket ( 0 ) . class ( ) )
7585- } else {
7586- None
7587- } ;
7588-
75897605 // `getblockparamproxy` has two semantic paths:
75907606 // - modified: return the already-materialized block local from EP
75917607 // - unmodified: inspect the block handler and produce proxy/nil
@@ -7611,30 +7627,136 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
76117627 // decide whether this path returns `nil` or `BlockParamProxy`.
76127628 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 } ) ;
76137629 let original_local = if level == 0 { Some ( state. getlocal ( ep_offset) ) } else { None } ;
7630+ // `block_handler & 1 == 1` accepts both ISEQ (0b01) and ifunc
7631+ // (0b11) handlers. Keep a compile-time check that this shortcut
7632+ // does not accidentally accept symbol block handlers.
7633+ const _: ( ) = assert ! ( RUBY_SYMBOL_FLAG & 1 == 0 , "guard below rejects symbol block handlers" ) ;
7634+
7635+
7636+ let profiled_block_summary = profiles. payload . profile . get_operand_types ( exit_state. insn_idx )
7637+ . and_then ( |types| types. first ( ) )
7638+ . map ( TypeDistributionSummary :: new) ;
76147639
7615- match profiled_block_type {
7616- Some ( ty) if ty. nil_p ( ) => {
7617- fun. push_insn ( unmodified_block, Insn :: GuardBitEquals { val : block_handler, expected : Const :: CInt64 ( VM_BLOCK_HANDLER_NONE . into ( ) ) , reason : SideExitReason :: BlockParamProxyNotNil , state : exit_id, recompile : None } ) ;
7618- let nil_val = fun. push_insn ( unmodified_block, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
7619- let mut unmodified_args = vec ! [ nil_val] ;
7620- if let Some ( local) = original_local { unmodified_args. push ( local) ; }
7621- fun. push_insn ( unmodified_block, Insn :: Jump ( BranchEdge { target : join_block, args : unmodified_args } ) ) ;
7640+ let mut profiled_handlers = Vec :: new ( ) ;
7641+ if let Some ( summary) = profiled_block_summary. as_ref ( ) {
7642+ if summary. is_monomorphic ( ) || summary. is_polymorphic ( ) || summary. is_skewed_polymorphic ( ) {
7643+ for & profiled_type in summary. buckets ( ) {
7644+ if profiled_type. is_empty ( ) {
7645+ break ;
7646+ }
7647+ if let Some ( profiled_handler) = ProfiledBlockHandlerFamily :: from_profiled_type ( profiled_type) {
7648+ if !profiled_handlers. contains ( & profiled_handler) {
7649+ profiled_handlers. push ( profiled_handler) ;
7650+ }
7651+ }
7652+ }
76227653 }
7623- _ => {
7624- // This handles two cases which are nearly identical
7654+ }
7655+
7656+ match profiled_handlers. as_slice ( ) {
7657+ // No supported profiled families. Keep the generic fallback iseq/ifunc fallback
7658+ // for sites we do not specialize, such as no-profile and megamorphic sites.
7659+ [ ] => {
7660+ // This handles two cases which are nearly identical.
76257661 // Block handler is a tagged pointer. Look at the tag.
76267662 // VM_BH_ISEQ_BLOCK_P(): block_handler & 0x03 == 0x01
76277663 // VM_BH_IFUNC_P(): block_handler & 0x03 == 0x03
76287664 // So to check for either of those cases we can use: val & 0x1 == 0x1
7629- const _: ( ) = assert ! ( RUBY_SYMBOL_FLAG & 1 == 0 , "guard below rejects symbol block handlers" ) ;
76307665
76317666 // Bail out if the block handler is neither ISEQ nor ifunc
7632- fun. push_insn ( unmodified_block, Insn :: GuardAnyBitSet { val : block_handler, mask : Const :: CUInt64 ( 0x1 ) , mask_name : None , reason : SideExitReason :: BlockParamProxyNotIseqOrIfunc , state : exit_id } ) ;
7667+ fun. push_insn ( unmodified_block, Insn :: GuardAnyBitSet { val : block_handler, mask : Const :: CUInt64 ( 0x1 ) , mask_name : None , reason : SideExitReason :: BlockParamProxyFallbackMiss , state : exit_id } ) ;
76337668 // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing
76347669 let proxy_val = fun. push_insn ( unmodified_block, Insn :: Const { val : Const :: Value ( unsafe { rb_block_param_proxy } ) } ) ;
7635- let mut unmodified_args = vec ! [ proxy_val] ;
7636- if let Some ( local) = original_local { unmodified_args. push ( local) ; }
7637- fun. push_insn ( unmodified_block, Insn :: Jump ( BranchEdge { target : join_block, args : unmodified_args } ) ) ;
7670+ let mut args = vec ! [ proxy_val] ;
7671+ if let Some ( local) = original_local {
7672+ args. push ( local) ;
7673+ }
7674+ fun. push_insn ( unmodified_block, Insn :: Jump ( BranchEdge { target : join_block, args } ) ) ;
7675+ }
7676+ // A single supported profiled family. Emit a monomorphic fast path
7677+ [ profiled_handler] => match profiled_handler {
7678+ ProfiledBlockHandlerFamily :: Nil => {
7679+ fun. push_insn ( unmodified_block, Insn :: GuardBitEquals { val : block_handler, expected : Const :: CInt64 ( VM_BLOCK_HANDLER_NONE . into ( ) ) , reason : SideExitReason :: BlockParamProxyNotNil , state : exit_id, recompile : None } ) ;
7680+ let nil_val = fun. push_insn ( unmodified_block, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
7681+ let mut args = vec ! [ nil_val] ;
7682+ if let Some ( local) = original_local {
7683+ args. push ( local) ;
7684+ }
7685+ fun. push_insn ( unmodified_block, Insn :: Jump ( BranchEdge { target : join_block, args } ) ) ;
7686+ }
7687+ ProfiledBlockHandlerFamily :: IseqOrIfunc => {
7688+ // This handles two cases which are nearly identical.
7689+ // Block handler is a tagged pointer. Look at the tag.
7690+ // VM_BH_ISEQ_BLOCK_P(): block_handler & 0x03 == 0x01
7691+ // VM_BH_IFUNC_P(): block_handler & 0x03 == 0x03
7692+ // So to check for either of those cases we can use: val & 0x1 == 0x1
7693+
7694+ // Bail out if the block handler is neither ISEQ nor ifunc
7695+ fun. push_insn ( unmodified_block, Insn :: GuardAnyBitSet { val : block_handler, mask : Const :: CUInt64 ( 0x1 ) , mask_name : None , reason : SideExitReason :: BlockParamProxyNotIseqOrIfunc , state : exit_id } ) ;
7696+ // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing
7697+ let proxy_val = fun. push_insn ( unmodified_block, Insn :: Const { val : Const :: Value ( unsafe { rb_block_param_proxy } ) } ) ;
7698+ let mut args = vec ! [ proxy_val] ;
7699+ if let Some ( local) = original_local {
7700+ args. push ( local) ;
7701+ }
7702+ fun. push_insn ( unmodified_block, Insn :: Jump ( BranchEdge { target : join_block, args } ) ) ;
7703+ }
7704+ } ,
7705+ // Multiple supported profiled families. Emit a polymorphic dispatch
7706+ _ => {
7707+ let profiled_blocks = profiled_handlers. iter ( )
7708+ . map ( |& kind| ( kind, fun. new_block ( branch_insn_idx) ) )
7709+ . collect :: < Vec < _ > > ( ) ;
7710+
7711+ for & ( kind, profiled_block) in & profiled_blocks {
7712+ match kind {
7713+ ProfiledBlockHandlerFamily :: Nil => {
7714+ let none_handler = fun. push_insn ( unmodified_block, Insn :: Const {
7715+ val : Const :: CInt64 ( VM_BLOCK_HANDLER_NONE . into ( ) ) ,
7716+ } ) ;
7717+ let is_none = fun. push_insn ( unmodified_block, Insn :: IsBitEqual {
7718+ left : block_handler,
7719+ right : none_handler,
7720+ } ) ;
7721+ fun. push_insn ( unmodified_block, Insn :: IfTrue {
7722+ val : is_none,
7723+ target : BranchEdge { target : profiled_block, args : vec ! [ ] } ,
7724+ } ) ;
7725+ let val = fun. push_insn ( profiled_block, Insn :: Const { val : Const :: Value ( Qnil ) } ) ;
7726+ let mut args = vec ! [ val] ;
7727+ if let Some ( local) = original_local { args. push ( local) ; }
7728+ fun. push_insn ( profiled_block, Insn :: Jump ( BranchEdge { target : join_block, args } ) ) ;
7729+
7730+ }
7731+ ProfiledBlockHandlerFamily :: IseqOrIfunc => {
7732+ // This handles two cases which are nearly identical.
7733+ // Block handler is a tagged pointer. Look at the tag.
7734+ // VM_BH_ISEQ_BLOCK_P(): block_handler & 0x03 == 0x01
7735+ // VM_BH_IFUNC_P(): block_handler & 0x03 == 0x03
7736+ // So to check for either of those cases we can use: val & 0x1 == 0x1
7737+ let tag_mask = fun. push_insn ( unmodified_block, Insn :: Const { val : Const :: CInt64 ( 0x1 ) } ) ;
7738+ let tag_bits = fun. push_insn ( unmodified_block, Insn :: IntAnd {
7739+ left : block_handler,
7740+ right : tag_mask,
7741+ } ) ;
7742+ let is_iseq_or_ifunc = fun. push_insn ( unmodified_block, Insn :: IsBitEqual {
7743+ left : tag_bits,
7744+ right : tag_mask,
7745+ } ) ;
7746+ fun. push_insn ( unmodified_block, Insn :: IfTrue {
7747+ val : is_iseq_or_ifunc,
7748+ target : BranchEdge { target : profiled_block, args : vec ! [ ] } ,
7749+ } ) ;
7750+ // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing
7751+ let val = fun. push_insn ( profiled_block, Insn :: Const { val : Const :: Value ( unsafe { rb_block_param_proxy } ) } ) ;
7752+ let mut args = vec ! [ val] ;
7753+ if let Some ( local) = original_local { args. push ( local) ; }
7754+ fun. push_insn ( profiled_block, Insn :: Jump ( BranchEdge { target : join_block, args } ) ) ;
7755+ } ,
7756+ }
7757+ }
7758+
7759+ fun. push_insn ( unmodified_block, Insn :: SideExit { state : exit_id, reason : SideExitReason :: BlockParamProxyProfileNotCovered , recompile : None } ) ;
76387760 }
76397761 }
76407762
0 commit comments