From 4652879f4360c5a7a9255e6a0dd75688f8ef47f9 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 27 Aug 2025 12:03:32 -0700 Subject: [PATCH] ZJIT: Specialize some Sends (#14363) * ZJIT: Profile and specialize Array#empty? * ZJIT: Specialize BasicObject#== * ZJIT: Specialize Hash#empty? * ZJIT: Specialize BasicObject#! Co-authored-by: Takashi Kokubun --- insns.def | 2 + zjit/src/cruby_bindings.inc.rs | 4 +- zjit/src/cruby_methods.rs | 4 ++ zjit/src/hir.rs | 73 ++++++++++++++++++++++++++++++++++ zjit/src/hir_type/mod.rs | 2 + zjit/src/profile.rs | 2 + 6 files changed, 86 insertions(+), 1 deletion(-) diff --git a/insns.def b/insns.def index c35869eb09..c9de612912 100644 --- a/insns.def +++ b/insns.def @@ -1575,6 +1575,7 @@ opt_empty_p (CALL_DATA cd) (VALUE recv) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_empty_p(recv); @@ -1603,6 +1604,7 @@ opt_not (CALL_DATA cd) (VALUE recv) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_not(GET_ISEQ(), cd, recv); diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index c804ecce86..d10e3cc8a0 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -696,7 +696,9 @@ pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 229; pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 230; pub const YARVINSN_zjit_opt_and: ruby_vminsn_type = 231; pub const YARVINSN_zjit_opt_or: ruby_vminsn_type = 232; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_empty_p: ruby_vminsn_type = 233; +pub const YARVINSN_zjit_opt_not: ruby_vminsn_type = 234; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 235; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index c9ebcebc86..36965c2c00 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -171,8 +171,12 @@ pub fn init() -> Annotations { annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); + annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d269baf884..deed997b7d 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -8117,6 +8117,79 @@ mod opt_tests { "); } + #[test] + fn test_specialize_basicobject_not_to_ccall() { + eval(" + def test(a) = !a + + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(v0:BasicObject, v1:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) + v9:ArrayExact = GuardType v1, ArrayExact + v10:BoolExact = CCall !@0x1038, v9 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_specialize_array_empty_p_to_ccall() { + eval(" + def test(a) = a.empty? + + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(v0:BasicObject, v1:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) + v9:ArrayExact = GuardType v1, ArrayExact + v10:BoolExact = CCall empty?@0x1038, v9 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_specialize_hash_empty_p_to_ccall() { + eval(" + def test(a) = a.empty? + + test({}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(v0:BasicObject, v1:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) + v9:HashExact = GuardType v1, HashExact + v10:BoolExact = CCall empty?@0x1038, v9 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_specialize_basic_object_eq_to_ccall() { + eval(" + class C; end + def test(a, b) = a == b + + test(C.new, C.new) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) + v10:HeapObject[class_exact:C] = GuardType v1, HeapObject[class_exact:C] + v11:BoolExact = CCall ==@0x1038, v10, v2 + CheckInterrupts + Return v11 + "); + } + #[test] fn test_guard_fixnum_and_fixnum() { eval(" diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index c18b2735be..d6d4398425 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -246,6 +246,8 @@ impl Type { else if val.is_true() { types::TrueClass } else if val.is_false() { types::FalseClass } else if val.class() == unsafe { rb_cString } { types::StringExact } + else if val.class() == unsafe { rb_cArray } { types::ArrayExact } + else if val.class() == unsafe { rb_cHash } { types::HashExact } else { // TODO(max): Add more cases for inferring type bits from built-in types Type { bits: bits::HeapObject, spec: Specialization::TypeExact(val.class()) } diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 771d90cb0e..b311f0ba94 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -66,6 +66,8 @@ fn profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) { YARVINSN_opt_ge => profile_operands(profiler, profile, 2), YARVINSN_opt_and => profile_operands(profiler, profile, 2), YARVINSN_opt_or => profile_operands(profiler, profile, 2), + YARVINSN_opt_empty_p => profile_operands(profiler, profile, 1), + YARVINSN_opt_not => profile_operands(profiler, profile, 1), YARVINSN_opt_send_without_block => { let cd: *const rb_call_data = profiler.insn_opnd(0).as_ptr(); let argc = unsafe { vm_ci_argc((*cd).ci) };