@@ -455,6 +455,7 @@ pub enum SideExitReason {
455455 FixnumSubOverflow ,
456456 FixnumMultOverflow ,
457457 GuardType ( Type ) ,
458+ GuardTypeNot ( Type ) ,
458459 GuardShape ( ShapeId ) ,
459460 GuardBitEquals ( VALUE ) ,
460461 PatchPoint ( Invariant ) ,
@@ -474,6 +475,7 @@ impl std::fmt::Display for SideExitReason {
474475 SideExitReason :: UnknownNewarraySend ( VM_OPT_NEWARRAY_SEND_PACK_BUFFER ) => write ! ( f, "UnknownNewarraySend(PACK_BUFFER)" ) ,
475476 SideExitReason :: UnknownNewarraySend ( VM_OPT_NEWARRAY_SEND_INCLUDE_P ) => write ! ( f, "UnknownNewarraySend(INCLUDE_P)" ) ,
476477 SideExitReason :: GuardType ( guard_type) => write ! ( f, "GuardType({guard_type})" ) ,
478+ SideExitReason :: GuardTypeNot ( guard_type) => write ! ( f, "GuardTypeNot({guard_type})" ) ,
477479 SideExitReason :: GuardBitEquals ( value) => write ! ( f, "GuardBitEquals({})" , value. print( & PtrPrintMap :: identity( ) ) ) ,
478480 SideExitReason :: PatchPoint ( invariant) => write ! ( f, "PatchPoint({invariant})" ) ,
479481 _ => write ! ( f, "{self:?}" ) ,
@@ -623,6 +625,7 @@ pub enum Insn {
623625
624626 /// Side-exit if val doesn't have the expected type.
625627 GuardType { val : InsnId , guard_type : Type , state : InsnId } ,
628+ GuardTypeNot { val : InsnId , guard_type : Type , state : InsnId } ,
626629 /// Side-exit if val is not the expected VALUE.
627630 GuardBitEquals { val : InsnId , expected : VALUE , state : InsnId } ,
628631 /// Side-exit if val doesn't have the expected shape.
@@ -859,6 +862,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
859862 Insn :: FixnumAnd { left, right, .. } => { write ! ( f, "FixnumAnd {left}, {right}" ) } ,
860863 Insn :: FixnumOr { left, right, .. } => { write ! ( f, "FixnumOr {left}, {right}" ) } ,
861864 Insn :: GuardType { val, guard_type, .. } => { write ! ( f, "GuardType {val}, {}" , guard_type. print( self . ptr_map) ) } ,
865+ Insn :: GuardTypeNot { val, guard_type, .. } => { write ! ( f, "GuardTypeNot {val}, {}" , guard_type. print( self . ptr_map) ) } ,
862866 Insn :: GuardBitEquals { val, expected, .. } => { write ! ( f, "GuardBitEquals {val}, {}" , expected. print( self . ptr_map) ) } ,
863867 & Insn :: GuardShape { val, shape, .. } => { write ! ( f, "GuardShape {val}, {:p}" , self . ptr_map. map_shape( shape) ) } ,
864868 Insn :: PatchPoint { invariant, .. } => { write ! ( f, "PatchPoint {}" , invariant. print( self . ptr_map) ) } ,
@@ -1285,6 +1289,7 @@ impl Function {
12851289 & IfTrue { val, ref target } => IfTrue { val : find ! ( val) , target : find_branch_edge ! ( target) } ,
12861290 & IfFalse { val, ref target } => IfFalse { val : find ! ( val) , target : find_branch_edge ! ( target) } ,
12871291 & GuardType { val, guard_type, state } => GuardType { val : find ! ( val) , guard_type, state } ,
1292+ & GuardTypeNot { val, guard_type, state } => GuardTypeNot { val : find ! ( val) , guard_type, state } ,
12881293 & GuardBitEquals { val, expected, state } => GuardBitEquals { val : find ! ( val) , expected, state } ,
12891294 & GuardShape { val, shape, state } => GuardShape { val : find ! ( val) , shape, state } ,
12901295 & FixnumAdd { left, right, state } => FixnumAdd { left : find ! ( left) , right : find ! ( right) , state } ,
@@ -1430,6 +1435,7 @@ impl Function {
14301435 Insn :: ObjectAlloc { .. } => types:: HeapObject ,
14311436 Insn :: CCall { return_type, .. } => * return_type,
14321437 Insn :: GuardType { val, guard_type, .. } => self . type_of ( * val) . intersection ( * guard_type) ,
1438+ Insn :: GuardTypeNot { .. } => types:: BasicObject ,
14331439 Insn :: GuardBitEquals { val, expected, .. } => self . type_of ( * val) . intersection ( Type :: from_value ( * expected) ) ,
14341440 Insn :: GuardShape { val, .. } => self . type_of ( * val) ,
14351441 Insn :: FixnumAdd { .. } => types:: Fixnum ,
@@ -1546,9 +1552,10 @@ impl Function {
15461552 fn chase_insn ( & self , insn : InsnId ) -> InsnId {
15471553 let id = self . union_find . borrow ( ) . find_const ( insn) ;
15481554 match self . insns [ id. 0 ] {
1549- Insn :: GuardType { val, .. } => self . chase_insn ( val) ,
1550- Insn :: GuardShape { val, .. } => self . chase_insn ( val) ,
1551- Insn :: GuardBitEquals { val, .. } => self . chase_insn ( val) ,
1555+ Insn :: GuardType { val, .. }
1556+ | Insn :: GuardTypeNot { val, .. }
1557+ | Insn :: GuardShape { val, .. }
1558+ | Insn :: GuardBitEquals { val, .. } => self . chase_insn ( val) ,
15521559 _ => id,
15531560 }
15541561 }
@@ -1791,12 +1798,26 @@ impl Function {
17911798 self . insn_types [ replacement. 0 ] = self . infer_type ( replacement) ;
17921799 self . make_equal_to ( insn_id, replacement) ;
17931800 }
1794- Insn :: ObjToString { val, .. } => {
1801+ Insn :: ObjToString { val, cd , state , .. } => {
17951802 if self . is_a ( val, types:: String ) {
17961803 // behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined
1797- self . make_equal_to ( insn_id, val) ;
1804+ self . make_equal_to ( insn_id, val) ; continue ;
1805+ }
1806+
1807+ let frame_state = self . frame_state ( state) ;
1808+ let Some ( recv_type) = self . profiled_type_of_at ( val, frame_state. insn_idx ) else {
1809+ self . push_insn_id ( block, insn_id) ; continue
1810+ } ;
1811+
1812+ if recv_type. is_string ( ) {
1813+ let guard = self . push_insn ( block, Insn :: GuardType { val : val, guard_type : types:: String , state : state } ) ;
1814+ // Infer type so AnyToString can fold off this
1815+ self . insn_types [ guard. 0 ] = self . infer_type ( guard) ;
1816+ self . make_equal_to ( insn_id, guard) ;
17981817 } else {
1799- self . push_insn_id ( block, insn_id) ;
1818+ self . push_insn ( block, Insn :: GuardTypeNot { val : val, guard_type : types:: String , state : state} ) ;
1819+ let send_to_s = self . push_insn ( block, Insn :: SendWithoutBlock { self_val : val, cd : cd, args : vec ! [ ] , state : state} ) ;
1820+ self . make_equal_to ( insn_id, send_to_s) ;
18001821 }
18011822 }
18021823 Insn :: AnyToString { str, .. } => {
@@ -2193,6 +2214,7 @@ impl Function {
21932214 | & Insn :: StringCopy { val, state, .. }
21942215 | & Insn :: ObjectAlloc { val, state }
21952216 | & Insn :: GuardType { val, state, .. }
2217+ | & Insn :: GuardTypeNot { val, state, .. }
21962218 | & Insn :: GuardBitEquals { val, state, .. }
21972219 | & Insn :: GuardShape { val, state, .. }
21982220 | & Insn :: ToArray { val, state }
@@ -8281,6 +8303,71 @@ mod opt_tests {
82818303 " ) ;
82828304 }
82838305
8306+ #[ test]
8307+ fn test_optimize_objtostring_anytostring_recv_profiled ( ) {
8308+ eval ( "
8309+ def test(a)
8310+ \" #{a}\"
8311+ end
8312+ test('foo'); test('foo')
8313+ " ) ;
8314+
8315+ assert_snapshot ! ( hir_string( "test" ) , @r"
8316+ fn test@<compiled>:3:
8317+ bb0(v0:BasicObject, v1:BasicObject):
8318+ v5:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
8319+ v17:String = GuardType v1, String
8320+ v11:StringExact = StringConcat v5, v17
8321+ CheckInterrupts
8322+ Return v11
8323+ " ) ;
8324+ }
8325+
8326+ #[ test]
8327+ fn test_optimize_objtostring_anytostring_recv_profiled_string_subclass ( ) {
8328+ eval ( "
8329+ class MyString < String; end
8330+
8331+ def test(a)
8332+ \" #{a}\"
8333+ end
8334+ foo = MyString.new('foo')
8335+ test(MyString.new(foo)); test(MyString.new(foo))
8336+ " ) ;
8337+
8338+ assert_snapshot ! ( hir_string( "test" ) , @r"
8339+ fn test@<compiled>:5:
8340+ bb0(v0:BasicObject, v1:BasicObject):
8341+ v5:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
8342+ v17:String = GuardType v1, String
8343+ v11:StringExact = StringConcat v5, v17
8344+ CheckInterrupts
8345+ Return v11
8346+ " ) ;
8347+ }
8348+
8349+ #[ test]
8350+ fn test_optimize_objtostring_profiled_nonstring_falls_back_to_send ( ) {
8351+ eval ( "
8352+ def test(a)
8353+ \" #{a}\"
8354+ end
8355+ test([1,2,3]); test([1,2,3]) # No fast path for array
8356+ " ) ;
8357+
8358+ assert_snapshot ! ( hir_string( "test" ) , @r"
8359+ fn test@<compiled>:3:
8360+ bb0(v0:BasicObject, v1:BasicObject):
8361+ v5:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
8362+ v17:BasicObject = GuardTypeNot v1, String
8363+ v18:BasicObject = SendWithoutBlock v1, :to_s
8364+ v9:String = AnyToString v1, str: v18
8365+ v11:StringExact = StringConcat v5, v9
8366+ CheckInterrupts
8367+ Return v11
8368+ " ) ;
8369+ }
8370+
82848371 #[ test]
82858372 fn test_branchnil_nil ( ) {
82868373 eval ( "
0 commit comments