Skip to content

Commit a0bf6d3

Browse files
ZJIT: Add inlining for Kernel#respond_to? (ruby#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 <[email protected]>
1 parent cb55043 commit a0bf6d3

File tree

2 files changed

+452
-1
lines changed

2 files changed

+452
-1
lines changed

zjit/src/cruby_methods.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ pub fn init() -> Annotations {
205205
annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable);
206206
annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable);
207207
annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable);
208+
annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p);
208209
annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable);
209210
annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable);
210211
annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize);
@@ -290,3 +291,114 @@ fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId,
290291
let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) });
291292
Some(result)
292293
}
294+
295+
fn inline_kernel_respond_to_p(
296+
fun: &mut hir::Function,
297+
block: hir::BlockId,
298+
recv: hir::InsnId,
299+
args: &[hir::InsnId],
300+
state: hir::InsnId,
301+
) -> Option<hir::InsnId> {
302+
// Parse arguments: respond_to?(method_name, allow_priv = false)
303+
let (method_name, allow_priv) = match *args {
304+
[method_name] => (method_name, false),
305+
[method_name, arg] => match fun.type_of(arg) {
306+
t if t.is_known_truthy() => (method_name, true),
307+
t if t.is_known_falsy() => (method_name, false),
308+
// Unknown type; bail out
309+
_ => return None,
310+
},
311+
// Unknown args; bail out
312+
_ => return None,
313+
};
314+
315+
// Method name must be a static symbol
316+
let method_name = fun.type_of(method_name).ruby_object()?;
317+
if !method_name.static_sym_p() {
318+
return None;
319+
}
320+
321+
// The receiver must have a known class to call `respond_to?` on
322+
// TODO: This is technically overly strict. This would also work if all of the
323+
// observed objects at this point agree on `respond_to?` and we can add many patchpoints.
324+
let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?;
325+
326+
// Get the method ID and its corresponding callable method entry
327+
let mid = unsafe { rb_sym2id(method_name) };
328+
let target_cme = unsafe { rb_callable_method_entry_or_negative(recv_class, mid) };
329+
assert!(
330+
!target_cme.is_null(),
331+
"Should never be null, as in that case we will be returned a \"negative CME\""
332+
);
333+
334+
let cme_def_type = unsafe { get_cme_def_type(target_cme) };
335+
336+
// Cannot inline a refined method, since their refinement depends on lexical scope
337+
if cme_def_type == VM_METHOD_TYPE_REFINED {
338+
return None;
339+
}
340+
341+
let visibility = match cme_def_type {
342+
VM_METHOD_TYPE_UNDEF => METHOD_VISI_UNDEF,
343+
_ => unsafe { METHOD_ENTRY_VISI(target_cme) },
344+
};
345+
346+
let result = match (visibility, allow_priv) {
347+
// Method undefined; check `respond_to_missing?`
348+
(METHOD_VISI_UNDEF, _) => {
349+
let respond_to_missing = ID!(respond_to_missing);
350+
if unsafe { rb_method_basic_definition_p(recv_class, respond_to_missing) } == 0 {
351+
return None; // Custom definition of respond_to_missing?, so cannot inline
352+
}
353+
let respond_to_missing_cme =
354+
unsafe { rb_callable_method_entry(recv_class, respond_to_missing) };
355+
// Protect against redefinition of `respond_to_missing?`
356+
fun.push_insn(
357+
block,
358+
hir::Insn::PatchPoint {
359+
invariant: hir::Invariant::NoTracePoint,
360+
state,
361+
},
362+
);
363+
fun.push_insn(
364+
block,
365+
hir::Insn::PatchPoint {
366+
invariant: hir::Invariant::MethodRedefined {
367+
klass: recv_class,
368+
method: respond_to_missing,
369+
cme: respond_to_missing_cme,
370+
},
371+
state,
372+
},
373+
);
374+
Qfalse
375+
}
376+
// Private method with allow priv=false, so `respond_to?` returns false
377+
(METHOD_VISI_PRIVATE, false) => Qfalse,
378+
// Public method or allow_priv=true: check if implemented
379+
(METHOD_VISI_PUBLIC, _) | (_, true) => {
380+
if cme_def_type == VM_METHOD_TYPE_NOTIMPLEMENTED {
381+
// C method with rb_f_notimplement(). `respond_to?` returns false
382+
// without consulting `respond_to_missing?`. See also: rb_add_method_cfunc()
383+
Qfalse
384+
} else {
385+
Qtrue
386+
}
387+
}
388+
(_, _) => return None, // not public and include_all not known, can't compile
389+
};
390+
fun.push_insn(block, hir::Insn::PatchPoint { invariant: hir::Invariant::NoTracePoint, state });
391+
fun.push_insn(block, hir::Insn::PatchPoint {
392+
invariant: hir::Invariant::MethodRedefined {
393+
klass: recv_class,
394+
method: mid,
395+
cme: target_cme
396+
}, state
397+
});
398+
if recv_class.instance_can_have_singleton_class() {
399+
fun.push_insn(block, hir::Insn::PatchPoint {
400+
invariant: hir::Invariant::NoSingletonClass { klass: recv_class }, state
401+
});
402+
}
403+
Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(result) }))
404+
}

0 commit comments

Comments
 (0)