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>
This commit is contained in:
Max Bernstein 2025-08-27 12:03:32 -07:00 committed by GitHub
parent 886268856b
commit 4652879f43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 86 additions and 1 deletions

View File

@ -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);

View File

@ -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),

View File

@ -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);

View File

@ -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@<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("

View File

@ -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()) }

View File

@ -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) };