@@ -397,6 +397,9 @@ pub enum Insn {
397397 GetIvar { self_val : InsnId , id : ID , state : InsnId } ,
398398 /// Set `self_val`'s instance variable `id` to `val`
399399 SetIvar { self_val : InsnId , id : ID , val : InsnId , state : InsnId } ,
400+ /// Check whether an instance variable exists on `self_val`
401+ DefinedIvar { self_val : InsnId , id : ID , pushval : VALUE , state : InsnId } ,
402+
400403
401404 /// Own a FrameState so that instructions can look up their dominating FrameState when
402405 /// generating deopt side-exits and frame reconstruction metadata. Does not directly generate
@@ -603,6 +606,23 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
603606 Ok ( ( ) )
604607 } ,
605608 Insn :: Snapshot { state } => write ! ( f, "Snapshot {}" , state) ,
609+ Insn :: Defined { op_type, v, .. } => {
610+ // op_type (enum defined_type) printing logic from iseq.c.
611+ // Not sure why rb_iseq_defined_string() isn't exhaustive.
612+ use std:: borrow:: Cow ;
613+ let op_type = * op_type as u32 ;
614+ let op_type = if op_type == DEFINED_FUNC {
615+ Cow :: Borrowed ( "func" )
616+ } else if op_type == DEFINED_REF {
617+ Cow :: Borrowed ( "ref" )
618+ } else if op_type == DEFINED_CONST_FROM {
619+ Cow :: Borrowed ( "constant-from" )
620+ } else {
621+ String :: from_utf8_lossy ( unsafe { rb_iseq_defined_string ( op_type) . as_rstring_byte_slice ( ) . unwrap ( ) } )
622+ } ;
623+ write ! ( f, "Defined {op_type}, {v}" )
624+ }
625+ Insn :: DefinedIvar { self_val, id, .. } => write ! ( f, "DefinedIvar {self_val}, :{}" , id. contents_lossy( ) . into_owned( ) ) ,
606626 Insn :: GetIvar { self_val, id, .. } => write ! ( f, "GetIvar {self_val}, :{}" , id. contents_lossy( ) . into_owned( ) ) ,
607627 Insn :: SetIvar { self_val, id, val, .. } => write ! ( f, "SetIvar {self_val}, :{}, {val}" , id. contents_lossy( ) . into_owned( ) ) ,
608628 Insn :: ToArray { val, .. } => write ! ( f, "ToArray {val}" ) ,
@@ -951,6 +971,7 @@ impl Function {
951971 & HashDup { val , state } => HashDup { val : find ! ( val) , state } ,
952972 & CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun : cfun, args : args. iter ( ) . map ( |arg| find ! ( * arg) ) . collect ( ) , name : name, return_type : return_type, elidable } ,
953973 & Defined { op_type, obj, pushval, v } => Defined { op_type, obj, pushval, v : find ! ( v) } ,
974+ & DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val : find ! ( self_val) , pushval, id, state } ,
954975 NewArray { elements, state } => NewArray { elements : find_vec ! ( * elements) , state : find ! ( * state) } ,
955976 & NewHash { ref elements, state } => {
956977 let mut found_elements = vec ! [ ] ;
@@ -1039,6 +1060,7 @@ impl Function {
10391060 Insn :: SendWithoutBlockDirect { .. } => types:: BasicObject ,
10401061 Insn :: Send { .. } => types:: BasicObject ,
10411062 Insn :: Defined { .. } => types:: BasicObject ,
1063+ Insn :: DefinedIvar { .. } => types:: BasicObject ,
10421064 Insn :: GetConstantPath { .. } => types:: BasicObject ,
10431065 Insn :: ArrayMax { .. } => types:: BasicObject ,
10441066 Insn :: GetIvar { .. } => types:: BasicObject ,
@@ -1599,7 +1621,7 @@ impl Function {
15991621 worklist. push_back ( state) ;
16001622 }
16011623 Insn :: CCall { args, .. } => worklist. extend ( args) ,
1602- Insn :: GetIvar { self_val, state, .. } => {
1624+ Insn :: GetIvar { self_val, state, .. } | Insn :: DefinedIvar { self_val , state , .. } => {
16031625 worklist. push_back ( self_val) ;
16041626 worklist. push_back ( state) ;
16051627 }
@@ -2159,12 +2181,20 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
21592181 state. stack_push ( fun. push_insn ( block, Insn :: Const { val : Const :: Value ( VALUE :: fixnum_from_usize ( 1 ) ) } ) ) ;
21602182 }
21612183 YARVINSN_defined => {
2184+ // (rb_num_t op_type, VALUE obj, VALUE pushval)
21622185 let op_type = get_arg ( pc, 0 ) . as_usize ( ) ;
2163- let obj = get_arg ( pc, 0 ) ;
2164- let pushval = get_arg ( pc, 0 ) ;
2186+ let obj = get_arg ( pc, 1 ) ;
2187+ let pushval = get_arg ( pc, 2 ) ;
21652188 let v = state. stack_pop ( ) ?;
21662189 state. stack_push ( fun. push_insn ( block, Insn :: Defined { op_type, obj, pushval, v } ) ) ;
21672190 }
2191+ YARVINSN_definedivar => {
2192+ // (ID id, IVC ic, VALUE pushval)
2193+ let id = ID ( get_arg ( pc, 0 ) . as_u64 ( ) ) ;
2194+ let pushval = get_arg ( pc, 2 ) ;
2195+ let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
2196+ state. stack_push ( fun. push_insn ( block, Insn :: DefinedIvar { self_val : self_param, id, pushval, state : exit_id } ) ) ;
2197+ }
21682198 YARVINSN_opt_getconstant_path => {
21692199 let ic = get_arg ( pc, 0 ) . as_ptr ( ) ;
21702200 state. stack_push ( fun. push_insn ( block, Insn :: GetConstantPath { ic } ) ) ;
@@ -2746,9 +2776,9 @@ mod tests {
27462776 }
27472777
27482778 #[ track_caller]
2749- fn assert_method_hir_with_opcodes ( method : & str , opcodes : Vec < u32 > , hir : Expect ) {
2779+ fn assert_method_hir_with_opcodes ( method : & str , opcodes : & [ u32 ] , hir : Expect ) {
27502780 let iseq = crate :: cruby:: with_rubyvm ( || get_method_iseq ( method) ) ;
2751- for opcode in opcodes {
2781+ for & opcode in opcodes {
27522782 assert ! ( iseq_contains_opcode( iseq, opcode) , "iseq {method} does not contain {}" , insn_name( opcode as usize ) ) ;
27532783 }
27542784 unsafe { crate :: cruby:: rb_zjit_profile_disable ( iseq) } ;
@@ -2758,7 +2788,7 @@ mod tests {
27582788
27592789 #[ track_caller]
27602790 fn assert_method_hir_with_opcode ( method : & str , opcode : u32 , hir : Expect ) {
2761- assert_method_hir_with_opcodes ( method, vec ! [ opcode] , hir)
2791+ assert_method_hir_with_opcodes ( method, & [ opcode] , hir)
27622792 }
27632793
27642794 #[ track_caller]
@@ -2992,7 +3022,7 @@ mod tests {
29923022 a
29933023 end
29943024 " ) ;
2995- assert_method_hir_with_opcodes ( "test" , vec ! [ YARVINSN_getlocal_WC_0 , YARVINSN_setlocal_WC_0 ] , expect ! [ [ r#"
3025+ assert_method_hir_with_opcodes ( "test" , & [ YARVINSN_getlocal_WC_0 , YARVINSN_setlocal_WC_0 ] , expect ! [ [ r#"
29963026 fn test:
29973027 bb0(v0:BasicObject):
29983028 v1:NilClassExact = Const Value(nil)
@@ -3001,6 +3031,37 @@ mod tests {
30013031 "# ] ] ) ;
30023032 }
30033033
3034+ #[ test]
3035+ fn defined_ivar ( ) {
3036+ eval ( "
3037+ def test = defined?(@foo)
3038+ " ) ;
3039+ assert_method_hir_with_opcode ( "test" , YARVINSN_definedivar , expect ! [ [ r#"
3040+ fn test:
3041+ bb0(v0:BasicObject):
3042+ v3:BasicObject = DefinedIvar v0, :@foo
3043+ Return v3
3044+ "# ] ] ) ;
3045+ }
3046+
3047+ #[ test]
3048+ fn defined ( ) {
3049+ eval ( "
3050+ def test = return defined?(SeaChange), defined?(favourite), defined?($ruby)
3051+ " ) ;
3052+ assert_method_hir_with_opcode ( "test" , YARVINSN_defined , expect ! [ [ r#"
3053+ fn test:
3054+ bb0(v0:BasicObject):
3055+ v2:NilClassExact = Const Value(nil)
3056+ v3:BasicObject = Defined constant, v2
3057+ v4:BasicObject = Defined func, v0
3058+ v5:NilClassExact = Const Value(nil)
3059+ v6:BasicObject = Defined global-variable, v5
3060+ v8:ArrayExact = NewArray v3, v4, v6
3061+ Return v8
3062+ "# ] ] ) ;
3063+ }
3064+
30043065 #[ test]
30053066 fn test_return_const ( ) {
30063067 eval ( "
0 commit comments