summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Bernstein <rubybugs@bernsteinbear.com>2025-08-27 12:03:32 -0700
committerGitHub <noreply@github.com>2025-08-27 19:03:32 +0000
commit4652879f4360c5a7a9255e6a0dd75688f8ef47f9 (patch)
treef6b0d6870b47fed79d40a4bae98e5bbd6498ad58
parent886268856ba7c70a6eaf25eeb402e6ebed9e851e (diff)
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 <takashikkbn@gmail.com>
-rw-r--r--insns.def2
-rw-r--r--zjit/src/cruby_bindings.inc.rs4
-rw-r--r--zjit/src/cruby_methods.rs4
-rw-r--r--zjit/src/hir.rs73
-rw-r--r--zjit/src/hir_type/mod.rs2
-rw-r--r--zjit/src/profile.rs2
6 files changed, 86 insertions, 1 deletions
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
@@ -8118,6 +8118,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@<compiled>: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@<compiled>: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@<compiled>: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@<compiled>: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("
def test(x, y) = x & y
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) };