@@ -563,6 +563,22 @@ impl std::fmt::Display for SideExitReason {
563563 }
564564}
565565
566+ /// Result of resolving the receiver type for method dispatch optimization.
567+ /// Represents whether we know the receiver's class statically at compile-time,
568+ /// have profiled type information, or know nothing about it.
569+ pub enum ReceiverTypeResolution {
570+ /// The receiver's class is statically known at JIT compile-time (no guard needed)
571+ StaticallyKnown { class : VALUE } ,
572+ /// The receiver has a monomorphic profile (single type observed, guard needed)
573+ Monomorphic { class : VALUE , profiled_type : ProfiledType } ,
574+ /// The receiver has a skewed polymorphic profile (dominant type with some other types, guard needed)
575+ SkewedPolymorphic { class : VALUE , profiled_type : ProfiledType } ,
576+ /// The receiver is polymorphic (multiple types, none dominant)
577+ Polymorphic ,
578+ /// No profile information available for the receiver
579+ NoProfile ,
580+ }
581+
566582/// Reason why a send-ish instruction cannot be optimized from a fallback instruction
567583#[ derive( Debug , Clone , Copy ) ]
568584pub enum SendFallbackReason {
@@ -2133,26 +2149,49 @@ impl Function {
21332149 None
21342150 }
21352151
2136- /// Return whether a given HIR instruction as profiled by the interpreter is polymorphic or
2137- /// whether it lacks a profile entirely.
2152+ /// Resolve the receiver type for method dispatch optimization.
21382153 ///
2139- /// * `Some(true)` if polymorphic
2140- /// * `Some(false)` if monomorphic
2141- /// * `None` if no profiled information so far
2142- fn is_polymorphic_at ( & self , insn : InsnId , iseq_insn_idx : usize ) -> Option < bool > {
2143- let profiles = self . profiles . as_ref ( ) ?;
2144- let entries = profiles. types . get ( & iseq_insn_idx) ?;
2145- let insn = self . chase_insn ( insn) ;
2154+ /// Takes the receiver's Type, receiver HIR instruction, and ISEQ instruction index.
2155+ /// Performs a single iteration through profile data to determine the receiver type.
2156+ ///
2157+ /// Returns:
2158+ /// - `StaticallyKnown` if the receiver's exact class is known at compile-time
2159+ /// - `Monomorphic`/`SkewedPolymorphic` if we have usable profile data
2160+ /// - `Polymorphic` if the receiver has multiple types
2161+ /// - `NoProfile` if we have no type information
2162+ fn resolve_receiver_type ( & self , recv : InsnId , recv_type : Type , insn_idx : usize ) -> ReceiverTypeResolution {
2163+ if let Some ( class) = recv_type. runtime_exact_ruby_class ( ) {
2164+ return ReceiverTypeResolution :: StaticallyKnown { class } ;
2165+ }
2166+ let Some ( profiles) = self . profiles . as_ref ( ) else {
2167+ return ReceiverTypeResolution :: NoProfile ;
2168+ } ;
2169+ let Some ( entries) = profiles. types . get ( & insn_idx) else {
2170+ return ReceiverTypeResolution :: NoProfile ;
2171+ } ;
2172+ let recv = self . chase_insn ( recv) ;
2173+
21462174 for ( entry_insn, entry_type_summary) in entries {
2147- if self . union_find . borrow ( ) . find_const ( * entry_insn) == insn {
2148- if !entry_type_summary. is_monomorphic ( ) && !entry_type_summary. is_skewed_polymorphic ( ) {
2149- return Some ( true ) ;
2150- } else {
2151- return Some ( false ) ;
2175+ if self . union_find . borrow ( ) . find_const ( * entry_insn) == recv {
2176+ if entry_type_summary. is_monomorphic ( ) {
2177+ let profiled_type = entry_type_summary. bucket ( 0 ) ;
2178+ return ReceiverTypeResolution :: Monomorphic {
2179+ class : profiled_type. class ( ) ,
2180+ profiled_type,
2181+ } ;
2182+ } else if entry_type_summary. is_skewed_polymorphic ( ) {
2183+ let profiled_type = entry_type_summary. bucket ( 0 ) ;
2184+ return ReceiverTypeResolution :: SkewedPolymorphic {
2185+ class : profiled_type. class ( ) ,
2186+ profiled_type,
2187+ } ;
2188+ } else if entry_type_summary. is_polymorphic ( ) {
2189+ return ReceiverTypeResolution :: Polymorphic ;
21522190 }
21532191 }
21542192 }
2155- None
2193+
2194+ ReceiverTypeResolution :: NoProfile
21562195 }
21572196
21582197 pub fn assume_expected_cfunc ( & mut self , block : BlockId , class : VALUE , method_id : ID , cfunc : * mut c_void , state : InsnId ) -> bool {
@@ -2299,24 +2338,24 @@ impl Function {
22992338 self . try_rewrite_uminus ( block, insn_id, recv, state) ,
23002339 Insn :: SendWithoutBlock { mut recv, cd, args, state, .. } => {
23012340 let frame_state = self . frame_state ( state) ;
2302- let ( klass, profiled_type) = if let Some ( klass) = self . type_of ( recv) . runtime_exact_ruby_class ( ) {
2303- // If we know the class statically, use it to fold the lookup at compile-time.
2304- ( klass, None )
2305- } else {
2306- // If we know that self is reasonably monomorphic from profile information, guard and use it to fold the lookup at compile-time.
2307- // TODO(max): Figure out how to handle top self?
2308- let Some ( recv_type) = self . profiled_type_of_at ( recv, frame_state. insn_idx ) else {
2341+ let ( klass, profiled_type) = match self . resolve_receiver_type ( recv, self . type_of ( recv) , frame_state. insn_idx ) {
2342+ ReceiverTypeResolution :: StaticallyKnown { class } => ( class, None ) ,
2343+ ReceiverTypeResolution :: Monomorphic { class, profiled_type }
2344+ | ReceiverTypeResolution :: SkewedPolymorphic { class, profiled_type } => ( class, Some ( profiled_type) ) ,
2345+ ReceiverTypeResolution :: Polymorphic => {
23092346 if get_option ! ( stats) {
2310- match self . is_polymorphic_at ( recv, frame_state. insn_idx ) {
2311- Some ( true ) => self . set_dynamic_send_reason ( insn_id, SendWithoutBlockPolymorphic ) ,
2312- // If the class isn't known statically, then it should not also be monomorphic
2313- Some ( false ) => panic ! ( "Should not have monomorphic profile at this point in this branch" ) ,
2314- None => self . set_dynamic_send_reason ( insn_id, SendWithoutBlockNoProfiles ) ,
2315- }
2347+ self . set_dynamic_send_reason ( insn_id, SendWithoutBlockPolymorphic ) ;
23162348 }
2317- self . push_insn_id ( block, insn_id) ; continue ;
2318- } ;
2319- ( recv_type. class ( ) , Some ( recv_type) )
2349+ self . push_insn_id ( block, insn_id) ;
2350+ continue ;
2351+ }
2352+ ReceiverTypeResolution :: NoProfile => {
2353+ if get_option ! ( stats) {
2354+ self . set_dynamic_send_reason ( insn_id, SendWithoutBlockNoProfiles ) ;
2355+ }
2356+ self . push_insn_id ( block, insn_id) ;
2357+ continue ;
2358+ }
23202359 } ;
23212360 let ci = unsafe { get_call_data_ci ( cd) } ; // info about the call site
23222361
@@ -2492,22 +2531,24 @@ impl Function {
24922531 // The actual optimization is done in reduce_send_to_ccall.
24932532 Insn :: Send { recv, cd, state, .. } => {
24942533 let frame_state = self . frame_state ( state) ;
2495- let klass = if let Some ( klass ) = self . type_of ( recv) . runtime_exact_ruby_class ( ) {
2496- // If we know the class statically, use it to fold the lookup at compile-time.
2497- klass
2498- } else {
2499- let Some ( recv_type ) = self . profiled_type_of_at ( recv , frame_state . insn_idx ) else {
2534+ let klass = match self . resolve_receiver_type ( recv , self . type_of ( recv) , frame_state . insn_idx ) {
2535+ ReceiverTypeResolution :: StaticallyKnown { class } => class,
2536+ ReceiverTypeResolution :: Monomorphic { class , .. }
2537+ | ReceiverTypeResolution :: SkewedPolymorphic { class , .. } => class ,
2538+ ReceiverTypeResolution :: Polymorphic => {
25002539 if get_option ! ( stats) {
2501- match self . is_polymorphic_at ( recv, frame_state. insn_idx ) {
2502- Some ( true ) => self . set_dynamic_send_reason ( insn_id, SendPolymorphic ) ,
2503- // If the class isn't known statically, then it should not also be monomorphic
2504- Some ( false ) => panic ! ( "Should not have monomorphic profile at this point in this branch" ) ,
2505- None => self . set_dynamic_send_reason ( insn_id, SendNoProfiles ) ,
2506- }
2540+ self . set_dynamic_send_reason ( insn_id, SendPolymorphic ) ;
25072541 }
2508- self . push_insn_id ( block, insn_id) ; continue ;
2509- } ;
2510- recv_type. class ( )
2542+ self . push_insn_id ( block, insn_id) ;
2543+ continue ;
2544+ }
2545+ ReceiverTypeResolution :: NoProfile => {
2546+ if get_option ! ( stats) {
2547+ self . set_dynamic_send_reason ( insn_id, SendNoProfiles ) ;
2548+ }
2549+ self . push_insn_id ( block, insn_id) ;
2550+ continue ;
2551+ }
25112552 } ;
25122553 let ci = unsafe { get_call_data_ci ( cd) } ; // info about the call site
25132554 let mid = unsafe { vm_ci_mid ( ci) } ;
@@ -2769,12 +2810,12 @@ impl Function {
27692810 let method_id = unsafe { rb_vm_ci_mid ( call_info) } ;
27702811
27712812 // If we have info about the class of the receiver
2772- let ( recv_class , profiled_type ) = if let Some ( class ) = self_type . runtime_exact_ruby_class ( ) {
2773- ( class , None )
2774- } else {
2775- let iseq_insn_idx = fun . frame_state ( state ) . insn_idx ;
2776- let Some ( recv_type ) = fun . profiled_type_of_at ( recv , iseq_insn_idx ) else { return Err ( ( ) ) } ;
2777- ( recv_type . class ( ) , Some ( recv_type ) )
2813+ let iseq_insn_idx = fun . frame_state ( state ) . insn_idx ;
2814+ let ( recv_class , profiled_type ) = match fun . resolve_receiver_type ( recv , self_type , iseq_insn_idx ) {
2815+ ReceiverTypeResolution :: StaticallyKnown { class } => ( class , None ) ,
2816+ ReceiverTypeResolution :: Monomorphic { class , profiled_type }
2817+ | ReceiverTypeResolution :: SkewedPolymorphic { class , profiled_type } => ( class , Some ( profiled_type ) ) ,
2818+ ReceiverTypeResolution :: Polymorphic | ReceiverTypeResolution :: NoProfile => return Err ( ( ) ) ,
27782819 } ;
27792820
27802821 // Do method lookup
@@ -2874,12 +2915,12 @@ impl Function {
28742915 let method_id = unsafe { rb_vm_ci_mid ( call_info) } ;
28752916
28762917 // If we have info about the class of the receiver
2877- let ( recv_class , profiled_type ) = if let Some ( class ) = self_type . runtime_exact_ruby_class ( ) {
2878- ( class , None )
2879- } else {
2880- let iseq_insn_idx = fun . frame_state ( state ) . insn_idx ;
2881- let Some ( recv_type ) = fun . profiled_type_of_at ( recv , iseq_insn_idx ) else { return Err ( ( ) ) } ;
2882- ( recv_type . class ( ) , Some ( recv_type ) )
2918+ let iseq_insn_idx = fun . frame_state ( state ) . insn_idx ;
2919+ let ( recv_class , profiled_type ) = match fun . resolve_receiver_type ( recv , self_type , iseq_insn_idx ) {
2920+ ReceiverTypeResolution :: StaticallyKnown { class } => ( class , None ) ,
2921+ ReceiverTypeResolution :: Monomorphic { class , profiled_type }
2922+ | ReceiverTypeResolution :: SkewedPolymorphic { class , profiled_type } => ( class , Some ( profiled_type ) ) ,
2923+ ReceiverTypeResolution :: Polymorphic | ReceiverTypeResolution :: NoProfile => return Err ( ( ) ) ,
28832924 } ;
28842925
28852926 // Do method lookup
0 commit comments