@@ -978,18 +978,22 @@ impl Function {
978978 self . try_rewrite_fixnum_op ( block, insn_id, & |left, right| Insn :: FixnumGt { left, right } , BOP_GT , self_val, args[ 0 ] , payload, state) ,
979979 Insn :: SendWithoutBlock { self_val, call_info : CallInfo { method_name } , args, state, .. } if method_name == ">=" && args. len ( ) == 1 =>
980980 self . try_rewrite_fixnum_op ( block, insn_id, & |left, right| Insn :: FixnumGe { left, right } , BOP_GE , self_val, args[ 0 ] , payload, state) ,
981- Insn :: SendWithoutBlock { self_val, call_info, cd, args, state } => {
981+ Insn :: SendWithoutBlock { mut self_val, call_info, cd, args, state } => {
982982 let frame_state = self . frame_state ( state) ;
983- let self_type = match payload. get_operand_types ( frame_state. insn_idx ) {
984- Some ( [ self_type, ..] ) if self_type. is_top_self ( ) => self_type,
985- _ => { self . push_insn_id ( block, insn_id) ; continue ; }
983+ let ( klass, guard_equal_to) = if let Some ( klass) = self . type_of ( self_val) . runtime_exact_ruby_class ( ) {
984+ // If we know the class statically, use it to fold the lookup at compile-time.
985+ ( klass, None )
986+ } else {
987+ // If we know that self is top-self from profile information, guard and use it to fold the lookup at compile-time.
988+ match payload. get_operand_types ( frame_state. insn_idx ) {
989+ Some ( [ self_type, ..] ) if self_type. is_top_self ( ) => ( self_type. exact_ruby_class ( ) . unwrap ( ) , self_type. ruby_object ( ) ) ,
990+ _ => { self . push_insn_id ( block, insn_id) ; continue ; }
991+ }
986992 } ;
987- let top_self = self_type. ruby_object ( ) . unwrap ( ) ;
988- let top_self_klass = top_self. class_of ( ) ;
989993 let ci = unsafe { get_call_data_ci ( cd) } ; // info about the call site
990994 let mid = unsafe { vm_ci_mid ( ci) } ;
991995 // Do method lookup
992- let mut cme = unsafe { rb_callable_method_entry ( top_self_klass , mid) } ;
996+ let mut cme = unsafe { rb_callable_method_entry ( klass , mid) } ;
993997 if cme. is_null ( ) {
994998 self . push_insn_id ( block, insn_id) ; continue ;
995999 }
@@ -998,11 +1002,14 @@ impl Function {
9981002 cme = unsafe { rb_check_overloaded_cme ( cme, ci) } ;
9991003 let def_type = unsafe { get_cme_def_type ( cme) } ;
10001004 if def_type != VM_METHOD_TYPE_ISEQ {
1005+ // TODO(max): Allow non-iseq; cache cme
10011006 self . push_insn_id ( block, insn_id) ; continue ;
10021007 }
1003- self . push_insn ( block, Insn :: PatchPoint ( Invariant :: MethodRedefined { klass : top_self_klass , method : mid } ) ) ;
1008+ self . push_insn ( block, Insn :: PatchPoint ( Invariant :: MethodRedefined { klass, method : mid } ) ) ;
10041009 let iseq = unsafe { get_def_iseq_ptr ( ( * cme) . def ) } ;
1005- let self_val = self . push_insn ( block, Insn :: GuardBitEquals { val : self_val, expected : top_self, state } ) ;
1010+ if let Some ( expected) = guard_equal_to {
1011+ self_val = self . push_insn ( block, Insn :: GuardBitEquals { val : self_val, expected, state } ) ;
1012+ }
10061013 let send_direct = self . push_insn ( block, Insn :: SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state } ) ;
10071014 self . make_equal_to ( insn_id, send_direct) ;
10081015 }
@@ -1038,6 +1045,9 @@ impl Function {
10381045 // TODO(alan): there was a seemingly a miscomp here if you swap with
10391046 // `inexact_ruby_class`. Theoretically it can call a method too general
10401047 // for the receiver. Confirm and add a test.
1048+ //
1049+ // TODO(max): Use runtime_exact_ruby_class so we can also specialize on known (not just
1050+ // profiled) types.
10411051 let ( recv_class, recv_type) = payload. get_operand_types ( iseq_insn_idx)
10421052 . and_then ( |types| types. get ( argc as usize ) )
10431053 . and_then ( |recv_type| recv_type. exact_ruby_class ( ) . and_then ( |class| Some ( ( class, recv_type) ) ) )
@@ -3354,6 +3364,41 @@ mod opt_tests {
33543364 "# ] ] ) ;
33553365 }
33563366
3367+ #[ test]
3368+ fn const_send_direct_integer ( ) {
3369+ eval ( "
3370+ def test(x) = 1.zero?
3371+ " ) ;
3372+ assert_optimized_method_hir ( "test" , expect ! [ [ r#"
3373+ fn test:
3374+ bb0(v0:BasicObject):
3375+ v2:Fixnum[1] = Const Value(1)
3376+ PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008)
3377+ v7:BasicObject = SendWithoutBlockDirect v2, :zero? (0x1010)
3378+ Return v7
3379+ "# ] ] ) ;
3380+ }
3381+
3382+ #[ test]
3383+ fn class_known_send_direct_array ( ) {
3384+ eval ( "
3385+ def test(x)
3386+ a = [1,2,3]
3387+ a.first
3388+ end
3389+ " ) ;
3390+ assert_optimized_method_hir ( "test" , expect ! [ [ r#"
3391+ fn test:
3392+ bb0(v0:BasicObject):
3393+ v1:NilClassExact = Const Value(nil)
3394+ v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
3395+ v5:ArrayExact = ArrayDup v3
3396+ PatchPoint MethodRedefined(Array@0x1008, first@0x1010)
3397+ v10:BasicObject = SendWithoutBlockDirect v5, :first (0x1018)
3398+ Return v10
3399+ "# ] ] ) ;
3400+ }
3401+
33573402 #[ test]
33583403 fn string_bytesize_simple ( ) {
33593404 eval ( "
0 commit comments