diff options
| author | Aiden Fox Ivey <aiden@aidenfoxivey.com> | 2025-10-17 18:37:22 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-17 22:37:22 +0000 |
| commit | a0bf6d349856dfca22798a49c5b4e05162edaf3c (patch) | |
| tree | 931c956ce48eb4a72a73b8c9af168ed1d6324e4d | |
| parent | cb55043383cbf39ac1df1d227836080a3d7cef33 (diff) | |
ZJIT: Add inlining for Kernel#respond_to? (#14873)
lobsters before:
<details>
```
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (61.1% of total 10,568,718):
Kernel#is_a?: 1,030,925 ( 9.8%)
String#<<: 851,954 ( 8.1%)
Hash#[]=: 742,942 ( 7.0%)
Regexp#match?: 399,898 ( 3.8%)
Hash#key?: 349,146 ( 3.3%)
String#start_with?: 334,963 ( 3.2%)
Kernel#respond_to?: 316,528 ( 3.0%)
ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%)
TrueClass#===: 235,771 ( 2.2%)
FalseClass#===: 231,144 ( 2.2%)
Array#include?: 211,386 ( 2.0%)
Hash#fetch: 204,702 ( 1.9%)
Kernel#block_given?: 181,796 ( 1.7%)
Kernel#dup: 179,341 ( 1.7%)
BasicObject#!=: 175,997 ( 1.7%)
String.new: 166,696 ( 1.6%)
Kernel#kind_of?: 165,600 ( 1.6%)
String#==: 157,746 ( 1.5%)
Process.clock_gettime: 144,992 ( 1.4%)
Array#any?: 138,310 ( 1.3%)
Top-20 not annotated C methods (62.1% of total 10,723,613):
Kernel#is_a?: 1,212,816 (11.3%)
String#<<: 851,954 ( 7.9%)
Hash#[]=: 743,121 ( 6.9%)
Regexp#match?: 399,898 ( 3.7%)
Hash#key?: 349,146 ( 3.3%)
String#start_with?: 334,963 ( 3.1%)
Kernel#respond_to?: 316,528 ( 3.0%)
ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.2%)
TrueClass#===: 235,771 ( 2.2%)
FalseClass#===: 231,144 ( 2.2%)
Array#include?: 211,386 ( 2.0%)
Hash#fetch: 204,702 ( 1.9%)
Kernel#block_given?: 191,665 ( 1.8%)
Kernel#dup: 179,348 ( 1.7%)
BasicObject#!=: 176,181 ( 1.6%)
String.new: 166,696 ( 1.6%)
Kernel#kind_of?: 165,634 ( 1.5%)
String#==: 163,678 ( 1.5%)
Process.clock_gettime: 144,992 ( 1.4%)
Array#any?: 138,310 ( 1.3%)
Top-2 not optimized method types for send (100.0% of total 72,324):
cfunc: 48,057 (66.4%)
iseq: 24,267 (33.6%)
Top-6 not optimized method types for send_without_block (100.0% of total 4,523,699):
iseq: 2,271,952 (50.2%)
bmethod: 985,636 (21.8%)
optimized: 949,704 (21.0%)
alias: 310,747 ( 6.9%)
null: 5,106 ( 0.1%)
cfunc: 554 ( 0.0%)
Top-13 not optimized instructions (100.0% of total 4,293,340):
invokesuper: 2,373,561 (55.3%)
invokeblock: 811,934 (18.9%)
sendforward: 505,452 (11.8%)
opt_eq: 451,756 (10.5%)
opt_plus: 74,406 ( 1.7%)
opt_minus: 36,228 ( 0.8%)
opt_send_without_block: 21,792 ( 0.5%)
opt_neq: 7,231 ( 0.2%)
opt_mult: 6,752 ( 0.2%)
opt_or: 3,753 ( 0.1%)
opt_lt: 348 ( 0.0%)
opt_ge: 91 ( 0.0%)
opt_gt: 36 ( 0.0%)
Top-9 send fallback reasons (100.0% of total 25,481,476):
send_without_block_polymorphic: 9,722,801 (38.2%)
send_no_profiles: 5,894,799 (23.1%)
send_without_block_not_optimized_method_type: 4,523,699 (17.8%)
not_optimized_instruction: 4,293,340 (16.8%)
send_without_block_no_profiles: 948,985 ( 3.7%)
send_not_optimized_method_type: 72,324 ( 0.3%)
send_without_block_cfunc_array_variadic: 15,134 ( 0.1%)
obj_to_string_not_string: 9,765 ( 0.0%)
send_without_block_direct_too_many_args: 629 ( 0.0%)
Top-9 unhandled YARV insns (100.0% of total 690,957):
expandarray: 328,491 (47.5%)
checkkeyword: 190,694 (27.6%)
getclassvariable: 59,907 ( 8.7%)
invokesuperforward: 49,503 ( 7.2%)
getblockparam: 49,119 ( 7.1%)
opt_duparray_send: 11,978 ( 1.7%)
getconstant: 952 ( 0.1%)
checkmatch: 290 ( 0.0%)
once: 23 ( 0.0%)
Top-3 compile error reasons (100.0% of total 3,718,841):
register_spill_on_alloc: 3,418,472 (91.9%)
register_spill_on_ccall: 182,023 ( 4.9%)
exception_handler: 118,346 ( 3.2%)
Top-17 side exit reasons (100.0% of total 10,861,013):
compile_error: 3,718,841 (34.2%)
guard_type_failure: 2,638,940 (24.3%)
guard_shape_failure: 1,917,541 (17.7%)
unhandled_yarv_insn: 690,957 ( 6.4%)
block_param_proxy_not_iseq_or_ifunc: 535,789 ( 4.9%)
unhandled_kwarg: 455,351 ( 4.2%)
unknown_newarray_send: 314,786 ( 2.9%)
patchpoint_stable_constant_names: 235,507 ( 2.2%)
unhandled_splat: 122,071 ( 1.1%)
patchpoint_no_singleton_class: 109,668 ( 1.0%)
unhandled_hir_insn: 76,397 ( 0.7%)
patchpoint_method_redefined: 21,598 ( 0.2%)
block_param_proxy_modified: 19,193 ( 0.2%)
patchpoint_no_ep_escape: 3,765 ( 0.0%)
obj_to_string_fallback: 568 ( 0.0%)
guard_type_not_failure: 22 ( 0.0%)
interrupt: 19 ( 0.0%)
send_count: 68,205,150
dynamic_send_count: 25,481,476 (37.4%)
optimized_send_count: 42,723,674 (62.6%)
iseq_optimized_send_count: 18,588,101 (27.3%)
inline_cfunc_optimized_send_count: 13,566,855 (19.9%)
non_variadic_cfunc_optimized_send_count: 7,904,518 (11.6%)
variadic_cfunc_optimized_send_count: 2,664,200 ( 3.9%)
dynamic_getivar_count: 7,366,650
dynamic_setivar_count: 7,245,122
compiled_iseq_count: 4,796
failed_iseq_count: 447
compile_time: 778ms
profile_time: 9ms
gc_time: 11ms
invalidation_time: 77ms
vm_write_pc_count: 63,636,742
vm_write_sp_count: 62,292,946
vm_write_locals_count: 62,292,946
vm_write_stack_count: 62,292,946
vm_write_to_parent_iseq_local_count: 292,458
vm_read_from_parent_iseq_local_count: 6,600,017
code_region_bytes: 22,970,368
side_exit_count: 10,861,013
total_insn_count: 517,633,620
vm_insn_count: 162,995,567
zjit_insn_count: 354,638,053
ratio_in_zjit: 68.5%
```
</details>
lobsters after:
<details>
```
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (61.1% of total 10,239,008):
Kernel#is_a?: 1,030,914 (10.1%)
String#<<: 851,954 ( 8.3%)
Hash#[]=: 742,942 ( 7.3%)
Regexp#match?: 376,144 ( 3.7%)
Hash#key?: 349,147 ( 3.4%)
String#start_with?: 334,963 ( 3.3%)
ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%)
TrueClass#===: 235,771 ( 2.3%)
FalseClass#===: 231,144 ( 2.3%)
Array#include?: 211,386 ( 2.1%)
Hash#fetch: 204,702 ( 2.0%)
Kernel#block_given?: 181,797 ( 1.8%)
Kernel#dup: 179,341 ( 1.8%)
BasicObject#!=: 175,997 ( 1.7%)
String.new: 166,696 ( 1.6%)
Kernel#kind_of?: 165,600 ( 1.6%)
String#==: 157,751 ( 1.5%)
Process.clock_gettime: 144,992 ( 1.4%)
Array#any?: 138,311 ( 1.4%)
Set#include?: 134,362 ( 1.3%)
Top-20 not annotated C methods (62.2% of total 10,372,753):
Kernel#is_a?: 1,212,805 (11.7%)
String#<<: 851,954 ( 8.2%)
Hash#[]=: 743,121 ( 7.2%)
Regexp#match?: 376,144 ( 3.6%)
Hash#key?: 349,147 ( 3.4%)
String#start_with?: 334,963 ( 3.2%)
ObjectSpace::WeakKeyMap#[]: 238,978 ( 2.3%)
TrueClass#===: 235,771 ( 2.3%)
FalseClass#===: 231,144 ( 2.2%)
Array#include?: 211,386 ( 2.0%)
Hash#fetch: 204,702 ( 2.0%)
Kernel#block_given?: 191,666 ( 1.8%)
Kernel#dup: 179,348 ( 1.7%)
BasicObject#!=: 176,181 ( 1.7%)
String.new: 166,696 ( 1.6%)
Kernel#kind_of?: 165,634 ( 1.6%)
String#==: 163,683 ( 1.6%)
Process.clock_gettime: 144,992 ( 1.4%)
Array#any?: 138,311 ( 1.3%)
Integer#<=>: 135,056 ( 1.3%)
Top-2 not optimized method types for send (100.0% of total 72,324):
cfunc: 48,057 (66.4%)
iseq: 24,267 (33.6%)
Top-6 not optimized method types for send_without_block (100.0% of total 4,523,699):
iseq: 2,271,952 (50.2%)
bmethod: 985,636 (21.8%)
optimized: 949,704 (21.0%)
alias: 310,747 ( 6.9%)
null: 5,106 ( 0.1%)
cfunc: 554 ( 0.0%)
Top-13 not optimized instructions (100.0% of total 4,293,339):
invokesuper: 2,373,561 (55.3%)
invokeblock: 811,933 (18.9%)
sendforward: 505,452 (11.8%)
opt_eq: 451,756 (10.5%)
opt_plus: 74,406 ( 1.7%)
opt_minus: 36,228 ( 0.8%)
opt_send_without_block: 21,792 ( 0.5%)
opt_neq: 7,231 ( 0.2%)
opt_mult: 6,752 ( 0.2%)
opt_or: 3,753 ( 0.1%)
opt_lt: 348 ( 0.0%)
opt_ge: 91 ( 0.0%)
opt_gt: 36 ( 0.0%)
Top-9 send fallback reasons (100.0% of total 25,457,719):
send_without_block_polymorphic: 9,699,046 (38.1%)
send_no_profiles: 5,894,798 (23.2%)
send_without_block_not_optimized_method_type: 4,523,699 (17.8%)
not_optimized_instruction: 4,293,339 (16.9%)
send_without_block_no_profiles: 948,985 ( 3.7%)
send_not_optimized_method_type: 72,324 ( 0.3%)
send_without_block_cfunc_array_variadic: 15,134 ( 0.1%)
obj_to_string_not_string: 9,765 ( 0.0%)
send_without_block_direct_too_many_args: 629 ( 0.0%)
Top-9 unhandled YARV insns (100.0% of total 690,957):
expandarray: 328,491 (47.5%)
checkkeyword: 190,694 (27.6%)
getclassvariable: 59,907 ( 8.7%)
invokesuperforward: 49,503 ( 7.2%)
getblockparam: 49,119 ( 7.1%)
opt_duparray_send: 11,978 ( 1.7%)
getconstant: 952 ( 0.1%)
checkmatch: 290 ( 0.0%)
once: 23 ( 0.0%)
Top-3 compile error reasons (100.0% of total 3,706,981):
register_spill_on_alloc: 3,406,595 (91.9%)
register_spill_on_ccall: 182,023 ( 4.9%)
exception_handler: 118,363 ( 3.2%)
Top-17 side exit reasons (100.0% of total 10,837,266):
compile_error: 3,706,981 (34.2%)
guard_type_failure: 2,638,921 (24.4%)
guard_shape_failure: 1,917,552 (17.7%)
unhandled_yarv_insn: 690,957 ( 6.4%)
block_param_proxy_not_iseq_or_ifunc: 535,789 ( 4.9%)
unhandled_kwarg: 455,351 ( 4.2%)
unknown_newarray_send: 314,786 ( 2.9%)
patchpoint_stable_constant_names: 223,630 ( 2.1%)
unhandled_splat: 122,071 ( 1.1%)
patchpoint_no_singleton_class: 109,668 ( 1.0%)
unhandled_hir_insn: 76,397 ( 0.7%)
patchpoint_method_redefined: 21,598 ( 0.2%)
block_param_proxy_modified: 19,193 ( 0.2%)
patchpoint_no_ep_escape: 3,765 ( 0.0%)
obj_to_string_fallback: 568 ( 0.0%)
guard_type_not_failure: 22 ( 0.0%)
interrupt: 17 ( 0.0%)
send_count: 68,157,710
dynamic_send_count: 25,457,719 (37.4%)
optimized_send_count: 42,699,991 (62.6%)
iseq_optimized_send_count: 18,588,067 (27.3%)
inline_cfunc_optimized_send_count: 13,872,916 (20.4%)
non_variadic_cfunc_optimized_send_count: 7,904,566 (11.6%)
variadic_cfunc_optimized_send_count: 2,334,442 ( 3.4%)
dynamic_getivar_count: 7,342,896
dynamic_setivar_count: 7,245,126
compiled_iseq_count: 4,796
failed_iseq_count: 447
compile_time: 791ms
profile_time: 9ms
gc_time: 9ms
invalidation_time: 68ms
vm_write_pc_count: 63,283,243
vm_write_sp_count: 61,939,447
vm_write_locals_count: 61,939,447
vm_write_stack_count: 61,939,447
vm_write_to_parent_iseq_local_count: 292,458
vm_read_from_parent_iseq_local_count: 6,576,263
code_region_bytes: 22,872,064
side_exit_count: 10,837,266
total_insn_count: 517,075,555
vm_insn_count: 162,674,783
zjit_insn_count: 354,400,772
ratio_in_zjit: 68.5%
```
</details>
---------
Co-authored-by: Max Bernstein <ruby@bernsteinbear.com>
| -rw-r--r-- | zjit/src/cruby_methods.rs | 112 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 341 |
2 files changed, 452 insertions, 1 deletions
diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 0609d37b1b..40fb0cbe44 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -205,6 +205,7 @@ pub fn init() -> Annotations { 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_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); @@ -290,3 +291,114 @@ fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); Some(result) } + +fn inline_kernel_respond_to_p( + fun: &mut hir::Function, + block: hir::BlockId, + recv: hir::InsnId, + args: &[hir::InsnId], + state: hir::InsnId, +) -> Option<hir::InsnId> { + // Parse arguments: respond_to?(method_name, allow_priv = false) + let (method_name, allow_priv) = match *args { + [method_name] => (method_name, false), + [method_name, arg] => match fun.type_of(arg) { + t if t.is_known_truthy() => (method_name, true), + t if t.is_known_falsy() => (method_name, false), + // Unknown type; bail out + _ => return None, + }, + // Unknown args; bail out + _ => return None, + }; + + // Method name must be a static symbol + let method_name = fun.type_of(method_name).ruby_object()?; + if !method_name.static_sym_p() { + return None; + } + + // The receiver must have a known class to call `respond_to?` on + // TODO: This is technically overly strict. This would also work if all of the + // observed objects at this point agree on `respond_to?` and we can add many patchpoints. + let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?; + + // Get the method ID and its corresponding callable method entry + let mid = unsafe { rb_sym2id(method_name) }; + let target_cme = unsafe { rb_callable_method_entry_or_negative(recv_class, mid) }; + assert!( + !target_cme.is_null(), + "Should never be null, as in that case we will be returned a \"negative CME\"" + ); + + let cme_def_type = unsafe { get_cme_def_type(target_cme) }; + + // Cannot inline a refined method, since their refinement depends on lexical scope + if cme_def_type == VM_METHOD_TYPE_REFINED { + return None; + } + + let visibility = match cme_def_type { + VM_METHOD_TYPE_UNDEF => METHOD_VISI_UNDEF, + _ => unsafe { METHOD_ENTRY_VISI(target_cme) }, + }; + + let result = match (visibility, allow_priv) { + // Method undefined; check `respond_to_missing?` + (METHOD_VISI_UNDEF, _) => { + let respond_to_missing = ID!(respond_to_missing); + if unsafe { rb_method_basic_definition_p(recv_class, respond_to_missing) } == 0 { + return None; // Custom definition of respond_to_missing?, so cannot inline + } + let respond_to_missing_cme = + unsafe { rb_callable_method_entry(recv_class, respond_to_missing) }; + // Protect against redefinition of `respond_to_missing?` + fun.push_insn( + block, + hir::Insn::PatchPoint { + invariant: hir::Invariant::NoTracePoint, + state, + }, + ); + fun.push_insn( + block, + hir::Insn::PatchPoint { + invariant: hir::Invariant::MethodRedefined { + klass: recv_class, + method: respond_to_missing, + cme: respond_to_missing_cme, + }, + state, + }, + ); + Qfalse + } + // Private method with allow priv=false, so `respond_to?` returns false + (METHOD_VISI_PRIVATE, false) => Qfalse, + // Public method or allow_priv=true: check if implemented + (METHOD_VISI_PUBLIC, _) | (_, true) => { + if cme_def_type == VM_METHOD_TYPE_NOTIMPLEMENTED { + // C method with rb_f_notimplement(). `respond_to?` returns false + // without consulting `respond_to_missing?`. See also: rb_add_method_cfunc() + Qfalse + } else { + Qtrue + } + } + (_, _) => return None, // not public and include_all not known, can't compile + }; + fun.push_insn(block, hir::Insn::PatchPoint { invariant: hir::Invariant::NoTracePoint, state }); + fun.push_insn(block, hir::Insn::PatchPoint { + invariant: hir::Invariant::MethodRedefined { + klass: recv_class, + method: mid, + cme: target_cme + }, state + }); + if recv_class.instance_can_have_singleton_class() { + fun.push_insn(block, hir::Insn::PatchPoint { + invariant: hir::Invariant::NoSingletonClass { klass: recv_class }, state + }); + } + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(result) })) +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index eca0e3a598..bccd27fc39 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1649,7 +1649,7 @@ impl Function { } /// Check if the type of `insn` is a subtype of `ty`. - fn is_a(&self, insn: InsnId, ty: Type) -> bool { + pub fn is_a(&self, insn: InsnId, ty: Type) -> bool { self.type_of(insn).is_subtype(ty) } @@ -2453,6 +2453,7 @@ impl Function { if let Some(profiled_type) = profiled_type { // Guard receiver class recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + fun.insn_types[recv.0] = fun.infer_type(recv); } let cfunc = unsafe { get_mct_func(cfunc) }.cast(); @@ -13447,4 +13448,342 @@ mod opt_tests { Return v19 "); } + + #[test] + fn test_optimize_respond_to_p_true() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_no_method() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_default_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, false) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:FalseClass = Const Value(false) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_true_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, true) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:TrueClass = Const Value(true) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_truthy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, 4) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_missing() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_do_not_optimize_redefined_respond_to_missing() { + eval(r#" + class C + def respond_to_missing?(method, include_private = false) + true + end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:BasicObject = CCallVariadic respond_to?@0x1040, v24, v13 + CheckInterrupts + Return v25 + "); + } } |
