diff options
-rw-r--r-- | bootstraptest/test_yjit.rb | 27 | ||||
-rw-r--r-- | yjit/bindgen/src/main.rs | 1 | ||||
-rw-r--r-- | yjit/src/codegen.rs | 119 | ||||
-rw-r--r-- | yjit/src/cruby_bindings.inc.rs | 1 | ||||
-rw-r--r-- | yjit/src/stats.rs | 2 |
5 files changed, 150 insertions, 0 deletions
diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index ebb19c8d3a..f79f047928 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -3552,3 +3552,30 @@ assert_equal 'threw', %q{ bar([Hash.ruby2_keywords_hash({})]) } + +# Test instance_of? and is_a? +assert_equal 'true', %q{ + 1.instance_of?(Integer) && 1.is_a?(Integer) +} + +# Test instance_of? and is_a? for singleton classes +assert_equal 'true', %q{ + a = [] + def a.test = :test + a.instance_of?(Array) && a.is_a?(Array) +} + +# Test instance_of? for singleton_class +# Yes this does really return false +assert_equal 'false', %q{ + a = [] + def a.test = :test + a.instance_of?(a.singleton_class) +} + +# Test is_a? for singleton_class +assert_equal 'true', %q{ + a = [] + def a.test = :test + a.is_a?(a.singleton_class) +} diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 74f5a52742..a961c1290a 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -419,6 +419,7 @@ fn main() { .allowlist_function("rb_RCLASS_ORIGIN") .allowlist_function("rb_method_basic_definition_p") .allowlist_function("rb_yjit_array_len") + .allowlist_function("rb_obj_class") // We define VALUE manually, don't import it .blocklist_type("VALUE") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 1616f90eb1..15d2c0598f 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -4020,6 +4020,122 @@ fn jit_rb_false( true } +/// Codegen for Kernel#is_a? +fn jit_rb_kernel_is_a( + jit: &mut JITState, + ctx: &mut Context, + asm: &mut Assembler, + ocb: &mut OutlinedCb, + _ci: *const rb_callinfo, + _cme: *const rb_callable_method_entry_t, + _block: Option<IseqPtr>, + argc: i32, + known_recv_class: *const VALUE, +) -> bool { + if argc != 1 { + return false; + } + + // If this is a super call we might not know the class + if known_recv_class.is_null() { + return false; + } + + // Important note: The output code will simply `return true/false`. + // Correctness follows from: + // - `known_recv_class` implies there is a guard scheduled before here + // for a particular `CLASS_OF(lhs)`. + // - We guard that rhs is identical to the compile-time sample + // - In general, for any two Class instances A, B, `A < B` does not change at runtime. + // Class#superclass is stable. + + let sample_rhs = jit_peek_at_stack(jit, ctx, 0); + let sample_lhs = jit_peek_at_stack(jit, ctx, 1); + + // We are not allowing module here because the module hierachy can change at runtime. + if !unsafe { RB_TYPE_P(sample_rhs, RUBY_T_CLASS) } { + return false; + } + let sample_is_a = unsafe { rb_obj_is_kind_of(sample_lhs, sample_rhs) == Qtrue }; + + let side_exit = get_side_exit(jit, ocb, ctx); + asm.comment("Kernel#is_a?"); + asm.cmp(ctx.stack_opnd(0), sample_rhs.into()); + asm.jne(counted_exit!(ocb, side_exit, send_is_a_class_mismatch)); + + ctx.stack_pop(2); + + if sample_is_a { + let stack_ret = ctx.stack_push(Type::True); + asm.mov(stack_ret, Qtrue.into()); + } else { + let stack_ret = ctx.stack_push(Type::False); + asm.mov(stack_ret, Qfalse.into()); + } + return true; +} + +/// Codegen for Kernel#instance_of? +fn jit_rb_kernel_instance_of( + jit: &mut JITState, + ctx: &mut Context, + asm: &mut Assembler, + ocb: &mut OutlinedCb, + _ci: *const rb_callinfo, + _cme: *const rb_callable_method_entry_t, + _block: Option<IseqPtr>, + argc: i32, + known_recv_class: *const VALUE, +) -> bool { + if argc != 1 { + return false; + } + + // If this is a super call we might not know the class + if known_recv_class.is_null() { + return false; + } + + // Important note: The output code will simply `return true/false`. + // Correctness follows from: + // - `known_recv_class` implies there is a guard scheduled before here + // for a particular `CLASS_OF(lhs)`. + // - We guard that rhs is identical to the compile-time sample + // - For a particular `CLASS_OF(lhs)`, `rb_obj_class(lhs)` does not change. + // (because for any singleton class `s`, `s.superclass.equal?(s.attached_object.class)`) + + let sample_rhs = jit_peek_at_stack(jit, ctx, 0); + let sample_lhs = jit_peek_at_stack(jit, ctx, 1); + + // Filters out cases where the C implementation raises + if unsafe { !(RB_TYPE_P(sample_rhs, RUBY_T_CLASS) || RB_TYPE_P(sample_rhs, RUBY_T_MODULE)) } { + return false; + } + + // We need to grab the class here to deal with singleton classes. + // Instance of grabs the "real class" of the object rather than the + // singleton class. + let sample_lhs_real_class = unsafe { rb_obj_class(sample_lhs) }; + + let sample_instance_of = sample_lhs_real_class == sample_rhs; + + let side_exit = get_side_exit(jit, ocb, ctx); + asm.comment("Kernel#instance_of?"); + asm.cmp(ctx.stack_opnd(0), sample_rhs.into()); + asm.jne(counted_exit!(ocb, side_exit, send_instance_of_class_mismatch)); + + ctx.stack_pop(2); + + if sample_instance_of { + let stack_ret = ctx.stack_push(Type::True); + asm.mov(stack_ret, Qtrue.into()); + } else { + let stack_ret = ctx.stack_push(Type::False); + asm.mov(stack_ret, Qfalse.into()); + } + return true; +} + // Codegen for rb_obj_equal() // object identity comparison fn jit_rb_obj_equal( @@ -7727,6 +7843,9 @@ impl CodegenGlobals { self.yjit_reg_method(rb_cNilClass, "nil?", jit_rb_true); self.yjit_reg_method(rb_mKernel, "nil?", jit_rb_false); + self.yjit_reg_method(rb_mKernel, "is_a?", jit_rb_kernel_is_a); + self.yjit_reg_method(rb_mKernel, "kind_of?", jit_rb_kernel_is_a); + self.yjit_reg_method(rb_mKernel, "instance_of?", jit_rb_kernel_instance_of); self.yjit_reg_method(rb_cBasicObject, "==", jit_rb_obj_equal); self.yjit_reg_method(rb_cBasicObject, "equal?", jit_rb_obj_equal); diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 4c1176e628..8cd61ac8c4 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1090,6 +1090,7 @@ extern "C" { pub static mut rb_cSymbol: VALUE; pub static mut rb_cThread: VALUE; pub static mut rb_cTrueClass: VALUE; + pub fn rb_obj_class(obj: VALUE) -> VALUE; pub fn rb_ary_new_capa(capa: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_store(ary: VALUE, key: ::std::os::raw::c_long, val: VALUE); pub fn rb_ary_resurrect(ary: VALUE) -> VALUE; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 5ca1fe55ae..2ea5d3da75 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -237,6 +237,8 @@ make_counters! { send_send_chain_not_string_or_sym, send_send_getter, send_send_builtin, + send_is_a_class_mismatch, + send_instance_of_class_mismatch, send_bmethod_ractor, send_bmethod_block_arg, |