ZJIT: Profile nil? calls

This allows ZJIT to profile `nil?` calls and create type guards for
its receiver.

- Add `zjit_profile` to `opt_nil_p` insn
- Start profiling `opt_nil_p` calls
- Use `runtime_exact_ruby_class` instead of `exact_ruby_class` to determine
  the profiled receiver class
This commit is contained in:
Stan Lo 2025-07-08 17:38:44 +01:00 committed by Max Bernstein
parent b16047088a
commit 79915e6f78
4 changed files with 33 additions and 13 deletions

View File

@ -996,6 +996,7 @@ opt_nil_p
(CALL_DATA cd)
(VALUE recv)
(VALUE val)
// attr bool zjit_profile = true;
{
val = vm_opt_nil_p(GET_ISEQ(), cd, recv);

View File

@ -683,18 +683,19 @@ pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 219;
pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 220;
pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 221;
pub const YARVINSN_zjit_opt_send_without_block: ruby_vminsn_type = 222;
pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 223;
pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 224;
pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 225;
pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 226;
pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 227;
pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 228;
pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 229;
pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 230;
pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 231;
pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 232;
pub const YARVINSN_zjit_opt_ge: ruby_vminsn_type = 233;
pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 234;
pub const YARVINSN_zjit_opt_nil_p: ruby_vminsn_type = 223;
pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 224;
pub const YARVINSN_zjit_opt_minus: ruby_vminsn_type = 225;
pub const YARVINSN_zjit_opt_mult: ruby_vminsn_type = 226;
pub const YARVINSN_zjit_opt_div: ruby_vminsn_type = 227;
pub const YARVINSN_zjit_opt_mod: ruby_vminsn_type = 228;
pub const YARVINSN_zjit_opt_eq: ruby_vminsn_type = 229;
pub const YARVINSN_zjit_opt_neq: ruby_vminsn_type = 230;
pub const YARVINSN_zjit_opt_lt: ruby_vminsn_type = 231;
pub const YARVINSN_zjit_opt_le: ruby_vminsn_type = 232;
pub const YARVINSN_zjit_opt_gt: ruby_vminsn_type = 233;
pub const YARVINSN_zjit_opt_ge: 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

@ -1547,7 +1547,7 @@ impl Function {
} else {
let iseq_insn_idx = fun.frame_state(state).insn_idx;
let Some(recv_type) = fun.profiled_type_of_at(self_val, iseq_insn_idx) else { return Err(()) };
let Some(recv_class) = recv_type.exact_ruby_class() else { return Err(()) };
let Some(recv_class) = recv_type.runtime_exact_ruby_class() else { return Err(()) };
(recv_class, Some(recv_type.unspecialized()))
};
@ -6659,4 +6659,21 @@ mod opt_tests {
Return v5
"#]]);
}
#[test]
fn test_guard_nil_for_nil_opt() {
eval("
def test(val) = val.nil?
test(nil)
");
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject, v1:BasicObject):
PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008)
v7:NilClassExact = GuardType v1, NilClassExact
v8:TrueClassExact = CCall nil?@0x1010, v7
Return v8
"#]]);
}
}

View File

@ -51,6 +51,7 @@ pub extern "C" fn rb_zjit_profile_insn(opcode: ruby_vminsn_type, ec: EcPtr) {
/// Profile a YARV instruction
fn profile_insn(profiler: &mut Profiler, opcode: ruby_vminsn_type) {
match opcode {
YARVINSN_opt_nil_p => profile_operands(profiler, 1),
YARVINSN_opt_plus => profile_operands(profiler, 2),
YARVINSN_opt_minus => profile_operands(profiler, 2),
YARVINSN_opt_mult => profile_operands(profiler, 2),