From 0e5cb74a0016b3facd3055fefc439ea6472981fd Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 17 Oct 2025 13:26:03 -0400 Subject: [PATCH] ZJIT: Don't push frame for String#empty? (#14836) lobsters before: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (71.9% of total 15,247,103): Hash#[]: 4,516,006 (29.6%) Class#current: 1,154,273 ( 7.6%) Kernel#is_a?: 1,027,952 ( 6.7%) Regexp#match?: 398,256 ( 2.6%) String#empty?: 353,775 ( 2.3%) Hash#key?: 349,154 ( 2.3%) Hash#[]=: 344,347 ( 2.3%) String#start_with?: 337,386 ( 2.2%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,339 ( 1.4%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 181,789 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,336 ( 1.2%) BasicObject#!=: 174,429 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,600 ( 1.1%) Top-20 not annotated C methods (72.5% of total 15,409,355): Hash#[]: 4,516,016 (29.3%) Kernel#is_a?: 1,209,970 ( 7.9%) Class#current: 1,154,273 ( 7.5%) Regexp#match?: 398,256 ( 2.6%) String#empty?: 361,013 ( 2.3%) Hash#key?: 349,154 ( 2.3%) Hash#[]=: 344,347 ( 2.2%) String#start_with?: 337,386 ( 2.2%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.5%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,339 ( 1.4%) Hash#fetch: 204,702 ( 1.3%) Kernel#block_given?: 191,658 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,343 ( 1.2%) BasicObject#!=: 174,613 ( 1.1%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,634 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 71,083): cfunc: 47,637 (67.0%) iseq: 23,446 (33.0%) Top-6 not optimized method types for send_without_block (100.0% of total 4,482,446): iseq: 2,227,443 (49.7%) bmethod: 985,679 (22.0%) optimized: 952,914 (21.3%) alias: 310,750 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,264,922): invokesuper: 2,346,296 (55.0%) invokeblock: 809,163 (19.0%) sendforward: 505,446 (11.9%) opt_eq: 454,244 (10.7%) opt_plus: 74,059 ( 1.7%) opt_minus: 36,227 ( 0.8%) opt_send_without_block: 21,396 ( 0.5%) opt_neq: 7,247 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,617 ( 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 27,366,538): send_without_block_polymorphic: 9,222,828 (33.7%) send_no_profiles: 5,892,897 (21.5%) send_without_block_not_optimized_method_type: 4,482,446 (16.4%) not_optimized_instruction: 4,264,922 (15.6%) send_without_block_no_profiles: 3,407,046 (12.4%) send_not_optimized_method_type: 71,083 ( 0.3%) send_without_block_cfunc_array_variadic: 15,135 ( 0.1%) obj_to_string_not_string: 9,919 ( 0.0%) send_without_block_direct_too_many_args: 262 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 688,292): expandarray: 328,369 (47.7%) checkkeyword: 190,697 (27.7%) getclassvariable: 59,286 ( 8.6%) getblockparam: 48,651 ( 7.1%) invokesuperforward: 48,162 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 840 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 3,675,808): register_spill_on_alloc: 3,459,950 (94.1%) register_spill_on_ccall: 215,858 ( 5.9%) Top-14 side exit reasons (100.0% of total 10,732,532): compile_error: 3,675,808 (34.2%) guard_type_failure: 2,616,693 (24.4%) guard_shape_failure: 1,902,102 (17.7%) unhandled_yarv_insn: 688,292 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 534,943 ( 5.0%) unhandled_kwarg: 421,996 ( 3.9%) patchpoint: 359,831 ( 3.4%) unknown_newarray_send: 314,665 ( 2.9%) unhandled_splat: 121,910 ( 1.1%) unhandled_hir_insn: 76,393 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) interrupt: 528 ( 0.0%) obj_to_string_fallback: 156 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,343,482 dynamic_send_count: 27,366,538 (41.2%) optimized_send_count: 38,976,944 (58.8%) iseq_optimized_send_count: 17,935,768 (27.0%) inline_cfunc_optimized_send_count: 5,794,073 ( 8.7%) non_variadic_cfunc_optimized_send_count: 12,588,582 (19.0%) variadic_cfunc_optimized_send_count: 2,658,521 ( 4.0%) dynamic_getivar_count: 7,321,990 dynamic_setivar_count: 7,231,183 compiled_iseq_count: 4,770 failed_iseq_count: 468 compile_time: 7,466ms profile_time: 52ms gc_time: 33ms invalidation_time: 116ms vm_write_pc_count: 64,768,186 vm_write_sp_count: 63,445,066 vm_write_locals_count: 63,445,066 vm_write_stack_count: 63,445,066 vm_write_to_parent_iseq_local_count: 292,445 vm_read_from_parent_iseq_local_count: 6,461,354 code_region_bytes: 22,446,080 side_exit_count: 10,732,532 total_insn_count: 515,600,654 vm_insn_count: 163,640,874 zjit_insn_count: 351,959,780 ratio_in_zjit: 68.3% ``` lobsters after: ``` ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (72.3% of total 14,893,304): Hash#[]: 4,515,997 (30.3%) Class#current: 1,154,273 ( 7.8%) Kernel#is_a?: 1,027,957 ( 6.9%) Regexp#match?: 398,259 ( 2.7%) Hash#key?: 349,149 ( 2.3%) Hash#[]=: 344,347 ( 2.3%) String#start_with?: 337,386 ( 2.3%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.6%) FalseClass#===: 231,144 ( 1.6%) Array#include?: 211,333 ( 1.4%) Hash#fetch: 204,703 ( 1.4%) Kernel#block_given?: 181,781 ( 1.2%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,337 ( 1.2%) BasicObject#!=: 174,429 ( 1.2%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,600 ( 1.1%) String#==: 154,751 ( 1.0%) Top-20 not annotated C methods (72.9% of total 15,048,318): Hash#[]: 4,516,007 (30.0%) Kernel#is_a?: 1,209,975 ( 8.0%) Class#current: 1,154,273 ( 7.7%) Regexp#match?: 398,259 ( 2.6%) Hash#key?: 349,149 ( 2.3%) Hash#[]=: 344,347 ( 2.3%) String#start_with?: 337,386 ( 2.2%) Kernel#respond_to?: 316,003 ( 2.1%) ObjectSpace::WeakKeyMap#[]: 238,978 ( 1.6%) TrueClass#===: 235,771 ( 1.6%) FalseClass#===: 231,144 ( 1.5%) Array#include?: 211,333 ( 1.4%) Hash#fetch: 204,703 ( 1.4%) Kernel#block_given?: 191,650 ( 1.3%) ActiveSupport::OrderedOptions#_get: 181,272 ( 1.2%) Kernel#dup: 179,344 ( 1.2%) BasicObject#!=: 174,613 ( 1.2%) Class#new: 168,079 ( 1.1%) Kernel#kind_of?: 165,634 ( 1.1%) String#==: 160,682 ( 1.1%) Top-2 not optimized method types for send (100.0% of total 71,084): cfunc: 47,638 (67.0%) iseq: 23,446 (33.0%) Top-6 not optimized method types for send_without_block (100.0% of total 4,482,444): iseq: 2,227,440 (49.7%) bmethod: 985,679 (22.0%) optimized: 952,916 (21.3%) alias: 310,749 ( 6.9%) null: 5,106 ( 0.1%) cfunc: 554 ( 0.0%) Top-13 not optimized instructions (100.0% of total 4,264,913): invokesuper: 2,346,301 (55.0%) invokeblock: 809,153 (19.0%) sendforward: 505,445 (11.9%) opt_eq: 454,244 (10.7%) opt_plus: 74,056 ( 1.7%) opt_minus: 36,227 ( 0.8%) opt_send_without_block: 21,396 ( 0.5%) opt_neq: 7,247 ( 0.2%) opt_mult: 6,752 ( 0.2%) opt_or: 3,617 ( 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 27,366,491): send_without_block_polymorphic: 9,222,820 (33.7%) send_no_profiles: 5,892,885 (21.5%) send_without_block_not_optimized_method_type: 4,482,444 (16.4%) not_optimized_instruction: 4,264,913 (15.6%) send_without_block_no_profiles: 3,407,030 (12.4%) send_not_optimized_method_type: 71,084 ( 0.3%) send_without_block_cfunc_array_variadic: 15,134 ( 0.1%) obj_to_string_not_string: 9,919 ( 0.0%) send_without_block_direct_too_many_args: 262 ( 0.0%) Top-9 unhandled YARV insns (100.0% of total 688,291): expandarray: 328,368 (47.7%) checkkeyword: 190,697 (27.7%) getclassvariable: 59,286 ( 8.6%) getblockparam: 48,651 ( 7.1%) invokesuperforward: 48,162 ( 7.0%) opt_duparray_send: 11,978 ( 1.7%) getconstant: 840 ( 0.1%) checkmatch: 290 ( 0.0%) once: 19 ( 0.0%) Top-2 compile error reasons (100.0% of total 3,675,807): register_spill_on_alloc: 3,459,949 (94.1%) register_spill_on_ccall: 215,858 ( 5.9%) Top-14 side exit reasons (100.0% of total 10,732,546): compile_error: 3,675,807 (34.2%) guard_type_failure: 2,616,699 (24.4%) guard_shape_failure: 1,902,100 (17.7%) unhandled_yarv_insn: 688,291 ( 6.4%) block_param_proxy_not_iseq_or_ifunc: 534,950 ( 5.0%) unhandled_kwarg: 421,993 ( 3.9%) patchpoint: 359,837 ( 3.4%) unknown_newarray_send: 314,667 ( 2.9%) unhandled_splat: 121,913 ( 1.1%) unhandled_hir_insn: 76,393 ( 0.7%) block_param_proxy_modified: 19,193 ( 0.2%) interrupt: 525 ( 0.0%) obj_to_string_fallback: 156 ( 0.0%) guard_type_not_failure: 22 ( 0.0%) send_count: 66,343,388 dynamic_send_count: 27,366,491 (41.2%) optimized_send_count: 38,976,897 (58.8%) iseq_optimized_send_count: 17,935,730 (27.0%) inline_cfunc_optimized_send_count: 6,147,863 ( 9.3%) non_variadic_cfunc_optimized_send_count: 12,234,780 (18.4%) variadic_cfunc_optimized_send_count: 2,658,524 ( 4.0%) dynamic_getivar_count: 7,321,987 dynamic_setivar_count: 7,231,160 compiled_iseq_count: 4,770 failed_iseq_count: 468 compile_time: 7,223ms profile_time: 51ms gc_time: 32ms invalidation_time: 107ms vm_write_pc_count: 64,414,293 vm_write_sp_count: 63,091,183 vm_write_locals_count: 63,091,183 vm_write_stack_count: 63,091,183 vm_write_to_parent_iseq_local_count: 292,443 vm_read_from_parent_iseq_local_count: 6,461,326 code_region_bytes: 22,446,080 side_exit_count: 10,732,546 total_insn_count: 515,600,823 vm_insn_count: 163,641,263 zjit_insn_count: 351,959,560 ratio_in_zjit: 68.3% ``` --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index ea9f1beffce3f8..f6c439ad985783 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -191,6 +191,7 @@ pub fn init() -> Annotations { annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cString, "getbyte", inline_string_getbyte); + annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 48011bd0883e5b..f0ada408e36793 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -13255,6 +13255,65 @@ mod opt_tests { "); } + #[test] + fn test_specialize_string_empty() { + eval(r#" + def test(s) + s.empty? + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + 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): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_string_empty() { + eval(r#" + def test(s) + s.empty? + 4 + end + test("this should get removed") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + 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): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v19 + "); + } + #[test] fn test_inline_integer_succ_with_fixnum() { eval("