summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNozomi Hijikata <121233810+nozomemein@users.noreply.github.com>2026-01-22 08:54:10 +0900
committerGitHub <noreply@github.com>2026-01-21 18:54:10 -0500
commit436ec3a9d68ae9282fbc79e9400382d93d05e800 (patch)
tree6ad4e076d609598dba81556304577e0b80af4083
parent965b16d766df66e296a8d8254263da3c1cf45717 (diff)
ZJIT: Compile getblockparam (#15896)
Closes: https://github.com/Shopify/ruby/issues/863 Compile `getblockparam` insn to `GetBlockParam` HIR so that we can handle it in ZJIT. ## Benchmark ### lobsters <details> <summary>before patch</summary> ``` Average of last 10, non-warmup iters: 778ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (58.4% of total 16,091,748): Hash#fetch: 3,237,974 (20.1%) Regexp#match?: 708,838 ( 4.4%) Hash#key?: 702,565 ( 4.4%) String#sub!: 489,843 ( 3.0%) Set#include?: 402,395 ( 2.5%) String#<<: 396,364 ( 2.5%) String#start_with?: 379,338 ( 2.4%) Hash#delete: 331,679 ( 2.1%) String.new: 308,268 ( 1.9%) Integer#===: 279,074 ( 1.7%) Symbol#end_with?: 255,538 ( 1.6%) Kernel#is_a?: 250,000 ( 1.6%) Process.clock_gettime: 221,598 ( 1.4%) Integer#>: 219,718 ( 1.4%) String#match?: 218,057 ( 1.4%) String#downcase: 213,127 ( 1.3%) Integer#<=: 202,617 ( 1.3%) Time#to_i: 195,248 ( 1.2%) Time#subsec: 192,277 ( 1.2%) Time#utc?: 188,500 ( 1.2%) Top-20 calls to C functions from JIT code (83.4% of total 126,501,142): rb_vm_opt_send_without_block: 35,338,443 (27.9%) rb_vm_send: 10,126,272 ( 8.0%) rb_hash_aref: 9,221,146 ( 7.3%) rb_vm_env_write: 8,615,394 ( 6.8%) rb_zjit_writebarrier_check_immediate: 7,666,061 ( 6.1%) rb_vm_getinstancevariable: 5,902,473 ( 4.7%) rb_ivar_get_at_no_ractor_check: 4,775,750 ( 3.8%) rb_obj_is_kind_of: 3,718,303 ( 2.9%) rb_vm_invokesuper: 2,705,394 ( 2.1%) rb_hash_aset: 2,422,892 ( 1.9%) rb_vm_setinstancevariable: 2,385,262 ( 1.9%) rb_vm_opt_getconstant_path: 2,321,875 ( 1.8%) Hash#fetch: 1,819,675 ( 1.4%) fetch: 1,418,299 ( 1.1%) rb_vm_invokeblock: 1,387,466 ( 1.1%) rb_str_buf_append: 1,378,634 ( 1.1%) rb_ec_ary_new_from_values: 1,338,599 ( 1.1%) rb_class_allocate_instance: 1,300,827 ( 1.0%) rb_hash_new_with_size: 906,352 ( 0.7%) rb_vm_sendforward: 799,626 ( 0.6%) Top-2 not optimized method types for send (100.0% of total 5,166,211): iseq: 5,163,389 (99.9%) null: 2,822 ( 0.1%) Top-3 not optimized method types for send_without_block (100.0% of total 526,119): optimized_send: 479,643 (91.2%) null: 42,176 ( 8.0%) optimized_block_call: 4,300 ( 0.8%) Top-3 not optimized method types for super (100.0% of total 2,365,999): cfunc: 2,251,438 (95.2%) alias: 111,257 ( 4.7%) attrset: 3,304 ( 0.1%) Top-3 instructions with uncategorized fallback reason (100.0% of total 2,214,821): invokeblock: 1,387,466 (62.6%) sendforward: 799,626 (36.1%) opt_send_without_block: 27,729 ( 1.3%) Top-20 send fallback reasons (100.0% of total 50,357,201): send_without_block_polymorphic: 18,307,466 (36.4%) singleton_class_seen: 9,310,336 (18.5%) send_not_optimized_method_type: 5,166,211 (10.3%) send_without_block_no_profiles: 4,756,165 ( 9.4%) one_or_more_complex_arg_pass: 2,906,412 ( 5.8%) send_no_profiles: 2,864,323 ( 5.7%) super_not_optimized_method_type: 2,365,999 ( 4.7%) uncategorized: 2,214,821 ( 4.4%) send_without_block_megamorphic: 581,552 ( 1.2%) send_without_block_not_optimized_method_type_optimized: 483,943 ( 1.0%) send_without_block_not_optimized_need_permission: 390,364 ( 0.8%) send_polymorphic: 329,064 ( 0.7%) too_many_args_for_lir: 173,570 ( 0.3%) super_target_complex_args_pass: 131,841 ( 0.3%) super_complex_args_pass: 111,056 ( 0.2%) super_polymorphic: 86,986 ( 0.2%) argc_param_mismatch: 48,546 ( 0.1%) send_without_block_not_optimized_method_type: 42,176 ( 0.1%) send_without_block_direct_keyword_mismatch: 37,484 ( 0.1%) obj_to_string_not_string: 34,865 ( 0.1%) Top-4 setivar fallback reasons (100.0% of total 2,385,262): not_monomorphic: 2,162,525 (90.7%) not_t_object: 125,178 ( 5.2%) too_complex: 97,538 ( 4.1%) new_shape_needs_extension: 21 ( 0.0%) Top-2 getivar fallback reasons (100.0% of total 6,027,586): not_monomorphic: 5,776,418 (95.8%) too_complex: 251,168 ( 4.2%) Top-3 definedivar fallback reasons (100.0% of total 406,027): not_monomorphic: 397,876 (98.0%) too_complex: 5,122 ( 1.3%) not_t_object: 3,029 ( 0.7%) Top-6 invokeblock handler (100.0% of total 1,387,466): monomorphic_iseq: 700,051 (50.5%) polymorphic: 513,455 (37.0%) monomorphic_other: 106,268 ( 7.7%) monomorphic_ifunc: 55,505 ( 4.0%) megamorphic: 6,762 ( 0.5%) no_profiles: 5,425 ( 0.4%) Top-9 popular complex argument-parameter features not optimized (100.0% of total 3,353,961): param_kw_opt: 1,408,663 (42.0%) param_forwardable: 697,209 (20.8%) param_block: 632,488 (18.9%) param_rest: 346,363 (10.3%) param_kwrest: 139,856 ( 4.2%) caller_kw_splat: 79,861 ( 2.4%) caller_splat: 43,585 ( 1.3%) caller_blockarg: 5,826 ( 0.2%) caller_kwarg: 110 ( 0.0%) Top-1 compile error reasons (100.0% of total 188,362): exception_handler: 188,362 (100.0%) Top-7 unhandled YARV insns (100.0% of total 184,408): getblockparam: 95,129 (51.6%) invokesuperforward: 81,668 (44.3%) getconstant: 3,318 ( 1.8%) setblockparam: 2,837 ( 1.5%) checkmatch: 929 ( 0.5%) expandarray: 360 ( 0.2%) once: 167 ( 0.1%) Top-3 unhandled HIR insns (100.0% of total 237,876): throw: 199,380 (83.8%) invokebuiltin: 35,775 (15.0%) array_max: 2,721 ( 1.1%) Top-20 side exit reasons (100.0% of total 15,592,861): guard_type_failure: 6,993,070 (44.8%) guard_shape_failure: 6,862,785 (44.0%) block_param_proxy_not_iseq_or_ifunc: 1,006,781 ( 6.5%) unhandled_hir_insn: 237,876 ( 1.5%) compile_error: 188,362 ( 1.2%) unhandled_yarv_insn: 184,408 ( 1.2%) block_param_proxy_modified: 29,130 ( 0.2%) patchpoint_stable_constant_names: 22,145 ( 0.1%) unhandled_newarray_send_pack: 14,481 ( 0.1%) unhandled_block_arg: 13,788 ( 0.1%) fixnum_mult_overflow: 10,866 ( 0.1%) fixnum_lshift_overflow: 10,085 ( 0.1%) patchpoint_no_ep_escape: 7,815 ( 0.1%) expandarray_failure: 4,533 ( 0.0%) guard_super_method_entry: 4,475 ( 0.0%) patchpoint_method_redefined: 1,212 ( 0.0%) patchpoint_no_singleton_class: 423 ( 0.0%) obj_to_string_fallback: 330 ( 0.0%) guard_less_failure: 163 ( 0.0%) interrupt: 114 ( 0.0%) send_count: 152,442,683 dynamic_send_count: 50,357,201 (33.0%) optimized_send_count: 102,085,482 (67.0%) dynamic_setivar_count: 2,385,262 ( 1.6%) dynamic_getivar_count: 6,027,586 ( 4.0%) dynamic_definedivar_count: 406,027 ( 0.3%) iseq_optimized_send_count: 39,671,621 (26.0%) inline_cfunc_optimized_send_count: 42,053,762 (27.6%) inline_iseq_optimized_send_count: 3,462,562 ( 2.3%) non_variadic_cfunc_optimized_send_count: 9,195,248 ( 6.0%) variadic_cfunc_optimized_send_count: 7,702,289 ( 5.1%) compiled_iseq_count: 5,552 failed_iseq_count: 0 compile_time: 1,926ms profile_time: 20ms gc_time: 27ms invalidation_time: 531ms vm_write_pc_count: 132,750,117 vm_write_sp_count: 132,750,117 vm_write_locals_count: 128,780,465 vm_write_stack_count: 128,780,465 vm_write_to_parent_iseq_local_count: 694,799 vm_read_from_parent_iseq_local_count: 14,812,747 guard_type_count: 159,813,452 guard_type_exit_ratio: 4.4% guard_shape_count: 0 code_region_bytes: 29,425,664 zjit_alloc_bytes: 44,592,776 total_mem_bytes: 74,018,440 side_exit_count: 15,592,861 total_insn_count: 938,453,078 vm_insn_count: 167,693,539 zjit_insn_count: 770,759,539 ratio_in_zjit: 82.1% ``` </details> <details> <summary>after patch</summary> ``` Average of last 10, non-warmup iters: 725ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (58.2% of total 16,004,664): Hash#fetch: 3,185,115 (19.9%) Regexp#match?: 708,806 ( 4.4%) Hash#key?: 702,551 ( 4.4%) String#sub!: 489,841 ( 3.1%) Set#include?: 396,625 ( 2.5%) String#<<: 396,279 ( 2.5%) String#start_with?: 379,337 ( 2.4%) Hash#delete: 331,667 ( 2.1%) String.new: 307,248 ( 1.9%) Integer#===: 279,054 ( 1.7%) Symbol#end_with?: 255,538 ( 1.6%) Kernel#is_a?: 246,961 ( 1.5%) Process.clock_gettime: 221,588 ( 1.4%) Integer#>: 219,718 ( 1.4%) String#match?: 218,059 ( 1.4%) String#downcase: 213,109 ( 1.3%) Integer#<=: 202,617 ( 1.3%) Time#to_i: 192,211 ( 1.2%) Time#subsec: 189,240 ( 1.2%) String#to_sym: 185,947 ( 1.2%) Top-20 calls to C functions from JIT code (83.4% of total 126,772,007): rb_vm_opt_send_without_block: 35,829,863 (28.3%) rb_vm_send: 10,108,894 ( 8.0%) rb_hash_aref: 9,009,231 ( 7.1%) rb_vm_env_write: 8,571,665 ( 6.8%) rb_zjit_writebarrier_check_immediate: 7,702,599 ( 6.1%) rb_vm_getinstancevariable: 5,930,325 ( 4.7%) rb_ivar_get_at_no_ractor_check: 4,764,439 ( 3.8%) rb_obj_is_kind_of: 3,722,865 ( 2.9%) rb_vm_invokesuper: 2,687,484 ( 2.1%) rb_hash_aset: 2,421,186 ( 1.9%) rb_vm_setinstancevariable: 2,355,461 ( 1.9%) rb_vm_opt_getconstant_path: 2,295,528 ( 1.8%) Hash#fetch: 1,779,524 ( 1.4%) fetch: 1,405,591 ( 1.1%) rb_vm_invokeblock: 1,385,989 ( 1.1%) rb_str_buf_append: 1,369,177 ( 1.1%) rb_ec_ary_new_from_values: 1,337,865 ( 1.1%) rb_class_allocate_instance: 1,295,755 ( 1.0%) rb_hash_new_with_size: 902,684 ( 0.7%) rb_vm_sendforward: 798,572 ( 0.6%) Top-2 not optimized method types for send (100.0% of total 4,902,716): iseq: 4,899,894 (99.9%) null: 2,822 ( 0.1%) Top-3 not optimized method types for send_without_block (100.0% of total 526,064): optimized_send: 479,589 (91.2%) null: 42,176 ( 8.0%) optimized_block_call: 4,299 ( 0.8%) Top-3 not optimized method types for super (100.0% of total 2,350,245): cfunc: 2,239,567 (95.3%) alias: 107,374 ( 4.6%) attrset: 3,304 ( 0.1%) Top-3 instructions with uncategorized fallback reason (100.0% of total 2,216,683): invokeblock: 1,385,989 (62.5%) sendforward: 798,572 (36.0%) opt_send_without_block: 32,122 ( 1.4%) Top-20 send fallback reasons (99.9% of total 50,810,802): send_without_block_polymorphic: 18,668,686 (36.7%) singleton_class_seen: 9,323,039 (18.3%) send_not_optimized_method_type: 4,902,716 ( 9.6%) send_without_block_no_profiles: 4,824,297 ( 9.5%) send_no_profiles: 2,853,944 ( 5.6%) one_or_more_complex_arg_pass: 2,829,717 ( 5.6%) super_not_optimized_method_type: 2,350,245 ( 4.6%) uncategorized: 2,216,683 ( 4.4%) send_without_block_megamorphic: 723,037 ( 1.4%) send_polymorphic: 544,026 ( 1.1%) send_without_block_not_optimized_method_type_optimized: 483,888 ( 1.0%) send_without_block_not_optimized_need_permission: 390,364 ( 0.8%) too_many_args_for_lir: 172,809 ( 0.3%) super_target_complex_args_pass: 128,824 ( 0.3%) super_complex_args_pass: 111,053 ( 0.2%) super_polymorphic: 87,851 ( 0.2%) argc_param_mismatch: 50,382 ( 0.1%) send_without_block_not_optimized_method_type: 42,176 ( 0.1%) obj_to_string_not_string: 34,861 ( 0.1%) send_without_block_direct_keyword_mismatch: 32,436 ( 0.1%) Top-4 setivar fallback reasons (100.0% of total 2,355,461): not_monomorphic: 2,132,746 (90.5%) not_t_object: 125,163 ( 5.3%) too_complex: 97,531 ( 4.1%) new_shape_needs_extension: 21 ( 0.0%) Top-2 getivar fallback reasons (100.0% of total 6,055,438): not_monomorphic: 5,806,179 (95.9%) too_complex: 249,259 ( 4.1%) Top-3 definedivar fallback reasons (100.0% of total 405,302): not_monomorphic: 397,150 (98.0%) too_complex: 5,122 ( 1.3%) not_t_object: 3,030 ( 0.7%) Top-6 invokeblock handler (100.0% of total 1,385,989): monomorphic_iseq: 688,167 (49.7%) polymorphic: 523,864 (37.8%) monomorphic_other: 106,268 ( 7.7%) monomorphic_ifunc: 55,505 ( 4.0%) megamorphic: 6,761 ( 0.5%) no_profiles: 5,424 ( 0.4%) Top-9 popular complex argument-parameter features not optimized (100.0% of total 3,234,958): param_kw_opt: 1,381,881 (42.7%) param_forwardable: 685,939 (21.2%) param_block: 640,948 (19.8%) param_rest: 327,046 (10.1%) param_kwrest: 120,209 ( 3.7%) caller_kw_splat: 38,970 ( 1.2%) caller_splat: 34,029 ( 1.1%) caller_blockarg: 5,826 ( 0.2%) caller_kwarg: 110 ( 0.0%) Top-1 compile error reasons (100.0% of total 187,347): exception_handler: 187,347 (100.0%) Top-6 unhandled YARV insns (100.0% of total 89,278): invokesuperforward: 81,667 (91.5%) getconstant: 3,318 ( 3.7%) setblockparam: 2,837 ( 3.2%) checkmatch: 929 ( 1.0%) expandarray: 360 ( 0.4%) once: 167 ( 0.2%) Top-3 unhandled HIR insns (100.0% of total 236,977): throw: 198,481 (83.8%) invokebuiltin: 35,775 (15.1%) array_max: 2,721 ( 1.1%) Top-20 side exit reasons (100.0% of total 15,458,443): guard_type_failure: 6,918,397 (44.8%) guard_shape_failure: 6,859,686 (44.4%) block_param_proxy_not_iseq_or_ifunc: 1,008,346 ( 6.5%) unhandled_hir_insn: 236,977 ( 1.5%) compile_error: 187,347 ( 1.2%) unhandled_yarv_insn: 89,278 ( 0.6%) fixnum_mult_overflow: 50,739 ( 0.3%) block_param_proxy_modified: 28,119 ( 0.2%) patchpoint_stable_constant_names: 22,145 ( 0.1%) unhandled_newarray_send_pack: 14,481 ( 0.1%) unhandled_block_arg: 13,787 ( 0.1%) fixnum_lshift_overflow: 10,085 ( 0.1%) patchpoint_no_ep_escape: 7,815 ( 0.1%) expandarray_failure: 4,533 ( 0.0%) guard_super_method_entry: 4,475 ( 0.0%) patchpoint_method_redefined: 1,212 ( 0.0%) patchpoint_no_singleton_class: 423 ( 0.0%) obj_to_string_fallback: 330 ( 0.0%) guard_less_failure: 163 ( 0.0%) interrupt: 86 ( 0.0%) send_count: 151,889,096 dynamic_send_count: 50,810,802 (33.5%) optimized_send_count: 101,078,294 (66.5%) dynamic_setivar_count: 2,355,461 ( 1.6%) dynamic_getivar_count: 6,055,438 ( 4.0%) dynamic_definedivar_count: 405,302 ( 0.3%) iseq_optimized_send_count: 39,470,508 (26.0%) inline_cfunc_optimized_send_count: 41,381,565 (27.2%) inline_iseq_optimized_send_count: 3,370,961 ( 2.2%) non_variadic_cfunc_optimized_send_count: 9,210,651 ( 6.1%) variadic_cfunc_optimized_send_count: 7,644,609 ( 5.0%) compiled_iseq_count: 5,552 failed_iseq_count: 0 compile_time: 1,809ms profile_time: 15ms gc_time: 21ms invalidation_time: 526ms vm_write_pc_count: 132,774,559 vm_write_sp_count: 132,774,559 vm_write_locals_count: 128,748,998 vm_write_stack_count: 128,748,998 vm_write_to_parent_iseq_local_count: 693,262 vm_read_from_parent_iseq_local_count: 14,737,431 guard_type_count: 158,811,089 guard_type_exit_ratio: 4.4% guard_shape_count: 0 code_region_bytes: 29,458,432 zjit_alloc_bytes: 44,650,569 total_mem_bytes: 74,109,001 side_exit_count: 15,458,443 total_insn_count: 934,491,306 vm_insn_count: 166,025,364 zjit_insn_count: 768,465,942 ratio_in_zjit: 82.2% ``` </details> ### rails-bench <details> <summary>before patch</summary> ``` Average of last 10, non-warmup iters: 1254ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (52.8% of total 39,182,033): Hash#key?: 3,141,634 ( 8.0%) Regexp#match?: 2,420,227 ( 6.2%) Hash#fetch: 2,245,557 ( 5.7%) Array#any?: 1,157,418 ( 3.0%) Hash#delete: 1,114,346 ( 2.8%) Integer#===: 1,098,163 ( 2.8%) String.new: 1,004,713 ( 2.6%) MatchData#[]: 831,442 ( 2.1%) String#b: 797,913 ( 2.0%) String#to_sym: 680,943 ( 1.7%) Kernel#dup: 680,022 ( 1.7%) Array#all?: 650,132 ( 1.7%) Fiber.current: 649,003 ( 1.7%) Array#join: 641,038 ( 1.6%) Array#include?: 613,837 ( 1.6%) Kernel#Array: 610,311 ( 1.6%) String#<<: 606,240 ( 1.5%) Symbol#end_with?: 598,807 ( 1.5%) String#force_encoding: 593,535 ( 1.5%) Kernel#respond_to?: 550,441 ( 1.4%) Top-20 calls to C functions from JIT code (75.2% of total 260,204,372): rb_vm_opt_send_without_block: 52,620,850 (20.2%) rb_hash_aref: 22,920,184 ( 8.8%) rb_vm_env_write: 19,484,445 ( 7.5%) rb_vm_send: 16,570,926 ( 6.4%) rb_zjit_writebarrier_check_immediate: 13,628,686 ( 5.2%) rb_vm_getinstancevariable: 12,378,112 ( 4.8%) rb_ivar_get_at_no_ractor_check: 12,208,856 ( 4.7%) rb_vm_invokesuper: 8,086,664 ( 3.1%) rb_hash_aset: 5,043,532 ( 1.9%) rb_obj_is_kind_of: 4,431,294 ( 1.7%) rb_vm_invokeblock: 4,036,483 ( 1.6%) Hash#key?: 3,141,634 ( 1.2%) rb_vm_opt_getconstant_path: 3,051,909 ( 1.2%) rb_class_allocate_instance: 2,878,743 ( 1.1%) rb_hash_new_with_size: 2,873,398 ( 1.1%) rb_ec_ary_new_from_values: 2,584,790 ( 1.0%) rb_str_concat_literals: 2,450,752 ( 0.9%) Regexp#match?: 2,420,227 ( 0.9%) rb_obj_alloc: 2,419,180 ( 0.9%) rb_vm_setinstancevariable: 2,357,067 ( 0.9%) Top-2 not optimized method types for send (100.0% of total 8,550,761): iseq: 8,518,290 (99.6%) optimized: 32,471 ( 0.4%) Top-2 not optimized method types for send_without_block (100.0% of total 790,792): optimized_send: 608,036 (76.9%) null: 182,756 (23.1%) Top-2 not optimized method types for super (100.0% of total 6,689,860): cfunc: 6,640,181 (99.3%) attrset: 49,679 ( 0.7%) Top-3 instructions with uncategorized fallback reason (100.0% of total 5,911,882): invokeblock: 4,036,483 (68.3%) sendforward: 1,871,601 (31.7%) opt_send_without_block: 3,798 ( 0.1%) Top-20 send fallback reasons (100.0% of total 83,186,524): send_without_block_polymorphic: 33,814,235 (40.6%) send_not_optimized_method_type: 8,550,761 (10.3%) send_without_block_no_profiles: 8,405,471 (10.1%) super_not_optimized_method_type: 6,689,860 ( 8.0%) uncategorized: 5,911,882 ( 7.1%) one_or_more_complex_arg_pass: 5,502,146 ( 6.6%) send_no_profiles: 4,700,820 ( 5.7%) send_polymorphic: 3,318,564 ( 4.0%) send_without_block_not_optimized_need_permission: 1,274,177 ( 1.5%) singleton_class_seen: 1,101,973 ( 1.3%) too_many_args_for_lir: 905,412 ( 1.1%) super_complex_args_pass: 829,842 ( 1.0%) send_without_block_not_optimized_method_type_optimized: 608,036 ( 0.7%) send_without_block_megamorphic: 565,874 ( 0.7%) super_target_complex_args_pass: 414,600 ( 0.5%) send_without_block_not_optimized_method_type: 182,756 ( 0.2%) obj_to_string_not_string: 158,141 ( 0.2%) super_call_with_block: 100,004 ( 0.1%) send_without_block_direct_keyword_mismatch: 99,588 ( 0.1%) super_polymorphic: 52,358 ( 0.1%) Top-2 setivar fallback reasons (100.0% of total 2,357,067): not_monomorphic: 2,255,283 (95.7%) not_t_object: 101,784 ( 4.3%) Top-1 getivar fallback reasons (100.0% of total 12,378,137): not_monomorphic: 12,378,137 (100.0%) Top-2 definedivar fallback reasons (100.0% of total 350,548): not_monomorphic: 350,461 (100.0%) not_t_object: 87 ( 0.0%) Top-6 invokeblock handler (100.0% of total 4,036,483): monomorphic_iseq: 2,189,057 (54.2%) polymorphic: 1,207,002 (29.9%) monomorphic_other: 334,248 ( 8.3%) monomorphic_ifunc: 221,225 ( 5.5%) megamorphic: 84,439 ( 2.1%) no_profiles: 512 ( 0.0%) Top-9 popular complex argument-parameter features not optimized (100.0% of total 7,096,505): param_kw_opt: 1,834,705 (25.9%) param_forwardable: 1,824,953 (25.7%) param_block: 1,792,214 (25.3%) param_rest: 861,894 (12.1%) caller_kw_splat: 297,937 ( 4.2%) caller_splat: 283,669 ( 4.0%) param_kwrest: 200,208 ( 2.8%) caller_blockarg: 752 ( 0.0%) caller_kwarg: 173 ( 0.0%) Top-1 compile error reasons (100.0% of total 391,562): exception_handler: 391,562 (100.0%) Top-7 unhandled YARV insns (100.0% of total 1,899,393): getblockparam: 898,862 (47.3%) invokesuperforward: 498,993 (26.3%) getconstant: 400,945 (21.1%) expandarray: 49,985 ( 2.6%) setblockparam: 49,972 ( 2.6%) checkmatch: 480 ( 0.0%) once: 156 ( 0.0%) Top-2 unhandled HIR insns (100.0% of total 268,151): throw: 232,560 (86.7%) invokebuiltin: 35,591 (13.3%) Top-19 side exit reasons (100.0% of total 9,609,677): guard_shape_failure: 2,498,160 (26.0%) block_param_proxy_not_iseq_or_ifunc: 1,988,408 (20.7%) unhandled_yarv_insn: 1,899,393 (19.8%) guard_type_failure: 1,722,167 (17.9%) compile_error: 391,562 ( 4.1%) unhandled_newarray_send_pack: 298,017 ( 3.1%) unhandled_hir_insn: 268,151 ( 2.8%) patchpoint_method_redefined: 200,632 ( 2.1%) unhandled_block_arg: 151,295 ( 1.6%) block_param_proxy_modified: 124,245 ( 1.3%) guard_less_failure: 50,126 ( 0.5%) fixnum_lshift_overflow: 9,985 ( 0.1%) patchpoint_stable_constant_names: 6,366 ( 0.1%) fixnum_mult_overflow: 570 ( 0.0%) obj_to_string_fallback: 429 ( 0.0%) patchpoint_no_ep_escape: 109 ( 0.0%) interrupt: 48 ( 0.0%) guard_super_method_entry: 8 ( 0.0%) guard_greater_eq_failure: 6 ( 0.0%) send_count: 328,547,991 dynamic_send_count: 83,186,524 (25.3%) optimized_send_count: 245,361,467 (74.7%) dynamic_setivar_count: 2,357,067 ( 0.7%) dynamic_getivar_count: 12,378,137 ( 3.8%) dynamic_definedivar_count: 350,548 ( 0.1%) iseq_optimized_send_count: 93,424,465 (28.4%) inline_cfunc_optimized_send_count: 98,338,280 (29.9%) inline_iseq_optimized_send_count: 9,338,763 ( 2.8%) non_variadic_cfunc_optimized_send_count: 26,452,910 ( 8.1%) variadic_cfunc_optimized_send_count: 17,807,049 ( 5.4%) compiled_iseq_count: 2,887 failed_iseq_count: 0 compile_time: 877ms profile_time: 32ms gc_time: 11ms invalidation_time: 15ms vm_write_pc_count: 284,341,923 vm_write_sp_count: 284,341,923 vm_write_locals_count: 272,137,494 vm_write_stack_count: 272,137,494 vm_write_to_parent_iseq_local_count: 1,079,867 vm_read_from_parent_iseq_local_count: 30,816,135 guard_type_count: 313,667,907 guard_type_exit_ratio: 0.5% guard_shape_count: 0 code_region_bytes: 14,417,920 zjit_alloc_bytes: 19,075,183 total_mem_bytes: 33,493,103 side_exit_count: 9,609,677 total_insn_count: 1,706,360,231 vm_insn_count: 124,793,155 zjit_insn_count: 1,581,567,076 ratio_in_zjit: 92.7% ``` </details> <details> <summary>after patch</summary> ``` Average of last 10, non-warmup iters: 1136ms ***ZJIT: Printing ZJIT statistics on exit*** Top-20 not inlined C methods (52.8% of total 39,182,033): Hash#key?: 3,141,634 ( 8.0%) Regexp#match?: 2,420,227 ( 6.2%) Hash#fetch: 2,245,557 ( 5.7%) Array#any?: 1,157,418 ( 3.0%) Hash#delete: 1,114,346 ( 2.8%) Integer#===: 1,098,163 ( 2.8%) String.new: 1,004,713 ( 2.6%) MatchData#[]: 831,442 ( 2.1%) String#b: 797,913 ( 2.0%) String#to_sym: 680,943 ( 1.7%) Kernel#dup: 680,022 ( 1.7%) Array#all?: 650,132 ( 1.7%) Fiber.current: 649,003 ( 1.7%) Array#join: 641,038 ( 1.6%) Array#include?: 613,837 ( 1.6%) Kernel#Array: 610,311 ( 1.6%) String#<<: 606,240 ( 1.5%) Symbol#end_with?: 598,807 ( 1.5%) String#force_encoding: 593,535 ( 1.5%) Kernel#respond_to?: 550,441 ( 1.4%) Top-20 calls to C functions from JIT code (74.8% of total 261,805,313): rb_vm_opt_send_without_block: 52,621,173 (20.1%) rb_hash_aref: 22,920,184 ( 8.8%) rb_vm_env_write: 19,484,925 ( 7.4%) rb_vm_send: 16,571,020 ( 6.3%) rb_zjit_writebarrier_check_immediate: 13,780,332 ( 5.3%) rb_vm_getinstancevariable: 12,378,114 ( 4.7%) rb_ivar_get_at_no_ractor_check: 12,208,856 ( 4.7%) rb_vm_invokesuper: 8,086,666 ( 3.1%) rb_hash_aset: 5,043,537 ( 1.9%) rb_obj_is_kind_of: 4,431,299 ( 1.7%) rb_vm_invokeblock: 4,036,481 ( 1.5%) Hash#key?: 3,141,634 ( 1.2%) rb_vm_opt_getconstant_path: 3,051,909 ( 1.2%) rb_class_allocate_instance: 2,878,746 ( 1.1%) rb_hash_new_with_size: 2,873,398 ( 1.1%) rb_ec_ary_new_from_values: 2,585,224 ( 1.0%) rb_str_concat_literals: 2,450,752 ( 0.9%) Regexp#match?: 2,420,227 ( 0.9%) rb_obj_alloc: 2,419,182 ( 0.9%) rb_vm_setinstancevariable: 2,357,067 ( 0.9%) Top-2 not optimized method types for send (100.0% of total 8,550,761): iseq: 8,518,290 (99.6%) optimized: 32,471 ( 0.4%) Top-2 not optimized method types for send_without_block (100.0% of total 790,792): optimized_send: 608,036 (76.9%) null: 182,756 (23.1%) Top-2 not optimized method types for super (100.0% of total 6,689,860): cfunc: 6,640,181 (99.3%) attrset: 49,679 ( 0.7%) Top-3 instructions with uncategorized fallback reason (100.0% of total 5,911,883): invokeblock: 4,036,481 (68.3%) sendforward: 1,871,601 (31.7%) opt_send_without_block: 3,801 ( 0.1%) Top-20 send fallback reasons (100.0% of total 83,186,941): send_without_block_polymorphic: 33,814,528 (40.6%) send_not_optimized_method_type: 8,550,761 (10.3%) send_without_block_no_profiles: 8,405,497 (10.1%) super_not_optimized_method_type: 6,689,860 ( 8.0%) uncategorized: 5,911,883 ( 7.1%) one_or_more_complex_arg_pass: 5,502,147 ( 6.6%) send_no_profiles: 4,700,820 ( 5.7%) send_polymorphic: 3,318,658 ( 4.0%) send_without_block_not_optimized_need_permission: 1,274,177 ( 1.5%) singleton_class_seen: 1,101,973 ( 1.3%) too_many_args_for_lir: 905,412 ( 1.1%) super_complex_args_pass: 829,842 ( 1.0%) send_without_block_not_optimized_method_type_optimized: 608,036 ( 0.7%) send_without_block_megamorphic: 565,874 ( 0.7%) super_target_complex_args_pass: 414,600 ( 0.5%) send_without_block_not_optimized_method_type: 182,756 ( 0.2%) obj_to_string_not_string: 158,141 ( 0.2%) super_call_with_block: 100,004 ( 0.1%) send_without_block_direct_keyword_mismatch: 99,588 ( 0.1%) super_polymorphic: 52,360 ( 0.1%) Top-2 setivar fallback reasons (100.0% of total 2,357,067): not_monomorphic: 2,255,283 (95.7%) not_t_object: 101,784 ( 4.3%) Top-1 getivar fallback reasons (100.0% of total 12,378,139): not_monomorphic: 12,378,139 (100.0%) Top-2 definedivar fallback reasons (100.0% of total 350,548): not_monomorphic: 350,461 (100.0%) not_t_object: 87 ( 0.0%) Top-6 invokeblock handler (100.0% of total 4,036,481): monomorphic_iseq: 2,189,057 (54.2%) polymorphic: 1,207,002 (29.9%) monomorphic_other: 334,248 ( 8.3%) monomorphic_ifunc: 221,223 ( 5.5%) megamorphic: 84,439 ( 2.1%) no_profiles: 512 ( 0.0%) Top-9 popular complex argument-parameter features not optimized (100.0% of total 7,096,506): param_kw_opt: 1,834,706 (25.9%) param_forwardable: 1,824,953 (25.7%) param_block: 1,792,214 (25.3%) param_rest: 861,894 (12.1%) caller_kw_splat: 297,937 ( 4.2%) caller_splat: 283,669 ( 4.0%) param_kwrest: 200,208 ( 2.8%) caller_blockarg: 752 ( 0.0%) caller_kwarg: 173 ( 0.0%) Top-1 compile error reasons (100.0% of total 391,562): exception_handler: 391,562 (100.0%) Top-6 unhandled YARV insns (100.0% of total 1,000,531): invokesuperforward: 498,993 (49.9%) getconstant: 400,945 (40.1%) expandarray: 49,985 ( 5.0%) setblockparam: 49,972 ( 5.0%) checkmatch: 480 ( 0.0%) once: 156 ( 0.0%) Top-2 unhandled HIR insns (100.0% of total 268,154): throw: 232,560 (86.7%) invokebuiltin: 35,594 (13.3%) Top-19 side exit reasons (100.0% of total 8,710,811): guard_shape_failure: 2,498,161 (28.7%) block_param_proxy_not_iseq_or_ifunc: 1,988,408 (22.8%) guard_type_failure: 1,722,168 (19.8%) unhandled_yarv_insn: 1,000,531 (11.5%) compile_error: 391,562 ( 4.5%) unhandled_newarray_send_pack: 298,017 ( 3.4%) unhandled_hir_insn: 268,154 ( 3.1%) patchpoint_method_redefined: 200,632 ( 2.3%) unhandled_block_arg: 151,295 ( 1.7%) block_param_proxy_modified: 124,245 ( 1.4%) guard_less_failure: 50,126 ( 0.6%) fixnum_lshift_overflow: 9,985 ( 0.1%) patchpoint_stable_constant_names: 6,366 ( 0.1%) fixnum_mult_overflow: 570 ( 0.0%) obj_to_string_fallback: 429 ( 0.0%) patchpoint_no_ep_escape: 109 ( 0.0%) interrupt: 39 ( 0.0%) guard_super_method_entry: 8 ( 0.0%) guard_greater_eq_failure: 6 ( 0.0%) send_count: 328,747,903 dynamic_send_count: 83,186,941 (25.3%) optimized_send_count: 245,560,962 (74.7%) dynamic_setivar_count: 2,357,067 ( 0.7%) dynamic_getivar_count: 12,378,139 ( 3.8%) dynamic_definedivar_count: 350,548 ( 0.1%) iseq_optimized_send_count: 93,623,831 (28.5%) inline_cfunc_optimized_send_count: 98,338,311 (29.9%) inline_iseq_optimized_send_count: 9,338,766 ( 2.8%) non_variadic_cfunc_optimized_send_count: 26,453,005 ( 8.0%) variadic_cfunc_optimized_send_count: 17,807,049 ( 5.4%) compiled_iseq_count: 2,888 failed_iseq_count: 0 compile_time: 858ms profile_time: 29ms gc_time: 59ms invalidation_time: 15ms vm_write_pc_count: 285,990,091 vm_write_sp_count: 285,990,091 vm_write_locals_count: 272,886,376 vm_write_stack_count: 272,886,376 vm_write_to_parent_iseq_local_count: 1,079,877 vm_read_from_parent_iseq_local_count: 30,816,135 guard_type_count: 314,169,071 guard_type_exit_ratio: 0.5% guard_shape_count: 0 code_region_bytes: 14,401,536 zjit_alloc_bytes: 19,128,598 total_mem_bytes: 33,530,134 side_exit_count: 8,710,811 total_insn_count: 1,705,461,649 vm_insn_count: 121,244,824 zjit_insn_count: 1,584,216,825 ratio_in_zjit: 92.9% ``` </details>
-rw-r--r--test/ruby/test_zjit.rb36
-rw-r--r--zjit/src/codegen.rs42
-rw-r--r--zjit/src/hir.rs130
-rw-r--r--zjit/src/hir/opt_tests.rs61
-rw-r--r--zjit/src/hir/tests.rs65
-rw-r--r--zjit/src/stats.rs2
6 files changed, 336 insertions, 0 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index e347986abc..2066610cb2 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -470,6 +470,42 @@ class TestZJIT < Test::Unit::TestCase
}, insns: [:getblockparamproxy]
end
+ def test_getblockparam
+ assert_compiles '2', %q{
+ def test(&blk)
+ blk
+ end
+ test { 2 }.call
+ test { 2 }.call
+ }, insns: [:getblockparam]
+ end
+
+ def test_getblockparam_proxy_side_exit_restores_block_local
+ assert_compiles '2', %q{
+ def test(&block)
+ b = block
+ # sideexits here
+ raise "test" unless block
+ b ? 2 : 3
+ end
+ test {}
+ test {}
+ }, insns: [:getblockparam, :getblockparamproxy]
+ end
+
+ def test_getblockparam_used_twice_in_args
+ assert_compiles '1', %q{
+ def f(*args) = args
+ def test(&blk)
+ b = blk
+ f(*[1], blk)
+ blk
+ end
+ test {1}.call
+ test {1}.call
+ }, insns: [:getblockparam]
+ end
+
def test_optimized_method_call_proc_call
assert_compiles '2', %q{
p = proc { |x| x * 2 }
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 0030493ddf..870fe7584a 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -550,6 +550,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))),
Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)),
&Insn::GetLocal { ep_offset, level, use_sp, .. } => gen_getlocal(asm, ep_offset, level, use_sp),
+ &Insn::IsBlockParamModified { level } => gen_is_block_param_modified(asm, level),
+ &Insn::GetBlockParam { ep_offset, level, state } => gen_getblockparam(jit, asm, ep_offset, level, &function.frame_state(state)),
&Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)),
Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)),
Insn::GetClassVar { id, ic, state } => gen_getclassvar(jit, asm, *id, *ic, &function.frame_state(*state)),
@@ -743,6 +745,46 @@ fn gen_setlocal(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep_offset:
}
}
+/// Returns 1 (as CBool) when VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM is set; returns 0 otherwise.
+fn gen_is_block_param_modified(asm: &mut Assembler, level: u32) -> Opnd {
+ let ep = gen_get_ep(asm, level);
+ let flags = asm.load(Opnd::mem(VALUE_BITS, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32)));
+ asm.test(flags, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into());
+ asm.csel_nz(Opnd::Imm(1), Opnd::Imm(0))
+}
+
+/// Get the block parameter as a Proc, write it to the environment,
+/// and mark the flag as modified.
+fn gen_getblockparam(jit: &mut JITState, asm: &mut Assembler, ep_offset: u32, level: u32, state: &FrameState) -> Opnd {
+ gen_prepare_leaf_call_with_gc(asm, state);
+ // Bail out if write barrier is required.
+ let ep = gen_get_ep(asm, level);
+ let flags = Opnd::mem(VALUE_BITS, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32));
+ asm.test(flags, VM_ENV_FLAG_WB_REQUIRED.into());
+ asm.jnz(side_exit(jit, state, SideExitReason::BlockParamWbRequired));
+
+ // Convert block handler to Proc.
+ let block_handler = asm.load(Opnd::mem(VALUE_BITS, ep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL));
+ let proc = asm_ccall!(asm, rb_vm_bh_to_procval, EC, block_handler);
+
+ // Write Proc to EP and mark modified.
+ let ep = gen_get_ep(asm, level);
+ let local_ep_offset = c_int::try_from(ep_offset).unwrap_or_else(|_| {
+ panic!("Could not convert local_ep_offset {ep_offset} to i32")
+ });
+ let offset = -(SIZEOF_VALUE_I32 * local_ep_offset);
+ asm.mov(Opnd::mem(VALUE_BITS, ep, offset), proc);
+
+ let flags = Opnd::mem(VALUE_BITS, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32));
+ let flags_val = asm.load(flags);
+ let modified = asm.or(flags_val, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into());
+ asm.store(flags, modified);
+
+ // Read the Proc from EP.
+ let ep = gen_get_ep(asm, level);
+ asm.load(Opnd::mem(VALUE_BITS, ep, offset))
+}
+
fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, state: &FrameState) {
// Bail out if the `&block` local variable has been modified
let ep = gen_get_ep(asm, level);
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 4326d37b34..b4f78c025d 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -506,6 +506,7 @@ pub enum SideExitReason {
Interrupt,
BlockParamProxyModified,
BlockParamProxyNotIseqOrIfunc,
+ BlockParamWbRequired,
StackOverflow,
FixnumModByZero,
FixnumDivByZero,
@@ -839,6 +840,11 @@ pub enum Insn {
/// If `use_sp` is true, it uses the SP register to optimize the read.
/// `rest_param` is used by infer_types to infer the ArrayExact type.
GetLocal { level: u32, ep_offset: u32, use_sp: bool, rest_param: bool },
+ /// Check whether VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM is set in the environment flags.
+ /// Returns CBool (0/1).
+ IsBlockParamModified { level: u32 },
+ /// Get the block parameter as a Proc.
+ GetBlockParam { level: u32, ep_offset: u32, state: InsnId },
/// Set a local variable in a higher scope or the heap
SetLocal { level: u32, ep_offset: u32, val: InsnId },
GetSpecialSymbol { symbol_type: SpecialBackrefSymbol, state: InsnId },
@@ -1150,6 +1156,8 @@ impl Insn {
Insn::GetSpecialNumber { .. } => effects::Any,
Insn::GetClassVar { .. } => effects::Any,
Insn::SetClassVar { .. } => effects::Any,
+ Insn::IsBlockParamModified { .. } => effects::Any,
+ Insn::GetBlockParam { .. } => effects::Any,
Insn::Snapshot { .. } => effects::Empty,
Insn::Jump(_) => effects::Any,
Insn::IfTrue { .. } => effects::Any,
@@ -1523,6 +1531,11 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"),
Insn::GuardSuperMethodEntry { lep, cme, .. } => write!(f, "GuardSuperMethodEntry {lep}, {:p}", self.ptr_map.map_ptr(cme)),
Insn::GetBlockHandler { lep } => write!(f, "GetBlockHandler {lep}"),
+ &Insn::GetBlockParam { level, ep_offset, .. } => {
+ let name = get_local_var_name_for_printer(self.iseq, level, ep_offset)
+ .map_or(String::new(), |x| format!("{x}, "));
+ write!(f, "GetBlockParam {name}l{level}, EP@{ep_offset}")
+ },
Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) },
Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) },
Insn::IsBlockGiven { lep } => { write!(f, "IsBlockGiven {lep}") },
@@ -1589,6 +1602,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, "));
write!(f, "GetLocal {name}l{level}, EP@{ep_offset}{}", if rest_param { ", *" } else { "" })
},
+ &Insn::IsBlockParamModified { level } => {
+ write!(f, "IsBlockParamModified l{level}")
+ },
&Insn::SetLocal { val, level, ep_offset } => {
let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, "));
write!(f, "SetLocal {name}l{level}, EP@{ep_offset}, {val}")
@@ -2139,6 +2155,7 @@ impl Function {
| PutSpecialObject {..}
| GetGlobal {..}
| GetLocal {..}
+ | IsBlockParamModified {..}
| SideExit {..}
| EntryPoint {..}
| LoadPC
@@ -2193,6 +2210,7 @@ impl Function {
&GuardSuperMethodEntry { lep, cme, state } => GuardSuperMethodEntry { lep: find!(lep), cme, state },
&GetBlockHandler { lep } => GetBlockHandler { lep: find!(lep) },
&IsBlockGiven { lep } => IsBlockGiven { lep: find!(lep) },
+ &GetBlockParam { level, ep_offset, state } => GetBlockParam { level, ep_offset, state: find!(state) },
&FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state },
&FixnumSub { left, right, state } => FixnumSub { left: find!(left), right: find!(right), state },
&FixnumMult { left, right, state } => FixnumMult { left: find!(left), right: find!(right), state },
@@ -2488,6 +2506,8 @@ impl Function {
Insn::AnyToString { .. } => types::String,
Insn::GetLocal { rest_param: true, .. } => types::ArrayExact,
Insn::GetLocal { .. } => types::BasicObject,
+ Insn::IsBlockParamModified { .. } => types::CBool,
+ Insn::GetBlockParam { .. } => types::BasicObject,
Insn::GetBlockHandler { .. } => types::RubyValue,
// The type of Snapshot doesn't really matter; it's never materialized. It's used only
// as a reference for FrameState, which we use to generate side-exit code.
@@ -4386,6 +4406,7 @@ impl Function {
| &Insn::GetLEP
| &Insn::LoadSelf
| &Insn::GetLocal { .. }
+ | &Insn::IsBlockParamModified { .. }
| &Insn::PutSpecialObject { .. }
| &Insn::IncrCounter(_)
| &Insn::IncrCounterPtr { .. } =>
@@ -4396,6 +4417,7 @@ impl Function {
}
&Insn::PatchPoint { state, .. }
| &Insn::CheckInterrupts { state }
+ | &Insn::GetBlockParam { state, .. }
| &Insn::GetConstantPath { ic: _, state } => {
worklist.push_back(state);
}
@@ -5153,6 +5175,8 @@ impl Function {
| Insn::GetSpecialNumber { .. }
| Insn::GetSpecialSymbol { .. }
| Insn::GetLocal { .. }
+ | Insn::GetBlockParam { .. }
+ | Insn::IsBlockParamModified { .. }
| Insn::StoreField { .. } => {
Ok(())
}
@@ -6428,6 +6452,112 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
// TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing
state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) }));
}
+ YARVINSN_getblockparam => {
+ fn new_branch_block(
+ fun: &mut Function,
+ insn_idx: u32,
+ exit_state: &FrameState,
+ locals_count: usize,
+ stack_count: usize,
+ ) -> (BlockId, InsnId, FrameState, InsnId) {
+ let block = fun.new_block(insn_idx);
+ let self_param = fun.push_insn(block, Insn::Param);
+ let mut state = exit_state.clone();
+ state.locals.clear();
+ state.stack.clear();
+ state.locals.extend((0..locals_count).map(|_| fun.push_insn(block, Insn::Param)));
+ state.stack.extend((0..stack_count).map(|_| fun.push_insn(block, Insn::Param)));
+ let snapshot = fun.push_insn(block, Insn::Snapshot { state: state.clone() });
+ (block, self_param, state, snapshot)
+ }
+
+ fn finish_getblockparam_branch(
+ fun: &mut Function,
+ block: BlockId,
+ self_param: InsnId,
+ state: &mut FrameState,
+ join_block: BlockId,
+ ep_offset: u32,
+ level: u32,
+ val: InsnId,
+ ) {
+ if level == 0 {
+ state.setlocal(ep_offset, val);
+ }
+ state.stack_push(val);
+ fun.push_insn(block, Insn::Jump(BranchEdge {
+ target: join_block,
+ args: state.as_args(self_param),
+ }));
+ }
+
+ let ep_offset = get_arg(pc, 0).as_u32();
+ let level = get_arg(pc, 1).as_u32();
+ let branch_insn_idx = exit_state.insn_idx as u32;
+
+ // If the block param is already a Proc (modified), read it from EP.
+ // Otherwise, convert it to a Proc and store it to EP.
+ let is_modified = fun.push_insn(block, Insn::IsBlockParamModified { level });
+
+ let locals_count = state.locals.len();
+ let stack_count = state.stack.len();
+ let entry_args = state.as_args(self_param);
+
+ // Set up branch and join blocks.
+ let (modified_block, modified_self_param, mut modified_state, ..) =
+ new_branch_block(&mut fun, branch_insn_idx, &exit_state, locals_count, stack_count);
+ let (unmodified_block, unmodified_self_param, mut unmodified_state, unmodified_exit_id) =
+ new_branch_block(&mut fun, branch_insn_idx, &exit_state, locals_count, stack_count);
+ let join_block = insn_idx_to_block.get(&insn_idx).copied().unwrap_or_else(|| fun.new_block(insn_idx));
+
+ fun.push_insn(block, Insn::IfTrue {
+ val: is_modified,
+ target: BranchEdge { target: modified_block, args: entry_args.clone() },
+ });
+ fun.push_insn(block, Insn::Jump(BranchEdge {
+ target: unmodified_block,
+ args: entry_args,
+ }));
+
+ // Push modified block: read Proc from EP.
+ let modified_val = fun.push_insn(modified_block, Insn::GetLocal {
+ ep_offset,
+ level,
+ use_sp: false,
+ rest_param: false,
+ });
+ finish_getblockparam_branch(
+ &mut fun,
+ modified_block,
+ modified_self_param,
+ &mut modified_state,
+ join_block,
+ ep_offset,
+ level,
+ modified_val,
+ );
+
+ // Push unmodified block: convert block handler to Proc.
+ let unmodified_val = fun.push_insn(unmodified_block, Insn::GetBlockParam {
+ ep_offset,
+ level,
+ state: unmodified_exit_id,
+ });
+ finish_getblockparam_branch(
+ &mut fun,
+ unmodified_block,
+ unmodified_self_param,
+ &mut unmodified_state,
+ join_block,
+ ep_offset,
+ level,
+ unmodified_val,
+ );
+
+ // Continue compilation from the join block at the next instruction.
+ queue.push_back((unmodified_state, join_block, insn_idx, local_inval));
+ break;
+ }
YARVINSN_pop => { state.stack_pop()?; }
YARVINSN_dup => { state.stack_push(state.stack_top()?); }
YARVINSN_dupn => {
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index da24025010..0a42652993 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -3853,6 +3853,67 @@ mod hir_opt_tests {
}
#[test]
+ fn test_getblockparam() {
+ eval("
+ def test(&block) = block
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:2:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal :block, 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:CBool = IsBlockParamModified l0
+ IfTrue v13, bb3(v8, v9)
+ v24:BasicObject = GetBlockParam :block, l0, EP@3
+ Jump bb5(v8, v24, v24)
+ bb3(v14:BasicObject, v15:BasicObject):
+ v22:BasicObject = GetLocal :block, l0, EP@3
+ Jump bb5(v14, v22, v22)
+ bb5(v26:BasicObject, v27:BasicObject, v28:BasicObject):
+ CheckInterrupts
+ Return v28
+ ");
+ }
+
+ #[test]
+ fn test_getblockparam_nested_block() {
+ eval("
+ def test(&block)
+ proc do
+ block
+ end
+ end
+ ");
+ assert_snapshot!(hir_string_proc("test"), @r"
+ fn block in test@<compiled>:4:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb2(v1)
+ bb1(v4:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v4)
+ bb2(v6:BasicObject):
+ v10:CBool = IsBlockParamModified l1
+ IfTrue v10, bb3(v6)
+ v19:BasicObject = GetBlockParam :block, l1, EP@3
+ Jump bb5(v6, v19)
+ bb3(v11:BasicObject):
+ v17:BasicObject = GetLocal :block, l1, EP@3
+ Jump bb5(v11, v17)
+ bb5(v21:BasicObject, v22:BasicObject):
+ CheckInterrupts
+ Return v22
+ ");
+ }
+
+ #[test]
fn test_getinstancevariable() {
eval("
def test = @foo
diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs
index 3e28178273..44082ce908 100644
--- a/zjit/src/hir/tests.rs
+++ b/zjit/src/hir/tests.rs
@@ -2684,6 +2684,71 @@ pub mod hir_build_tests {
}
#[test]
+ fn test_getblockparam() {
+ eval("
+ def test(&block) = block
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:2:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal :block, 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:CBool = IsBlockParamModified l0
+ IfTrue v13, bb3(v8, v9)
+ Jump bb4(v8, v9)
+ bb3(v14:BasicObject, v15:BasicObject):
+ v22:BasicObject = GetLocal :block, l0, EP@3
+ Jump bb5(v14, v22, v22)
+ bb4(v17:BasicObject, v18:BasicObject):
+ v24:BasicObject = GetBlockParam :block, l0, EP@3
+ Jump bb5(v17, v24, v24)
+ bb5(v26:BasicObject, v27:BasicObject, v28:BasicObject):
+ CheckInterrupts
+ Return v28
+ ");
+ }
+
+ #[test]
+ fn test_getblockparam_nested_block() {
+ eval("
+ def test(&block)
+ proc do
+ block
+ end
+ end
+ ");
+ assert_snapshot!(hir_string_proc("test"), @r"
+ fn block in test@<compiled>:4:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb2(v1)
+ bb1(v4:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v4)
+ bb2(v6:BasicObject):
+ v10:CBool = IsBlockParamModified l1
+ IfTrue v10, bb3(v6)
+ Jump bb4(v6)
+ bb3(v11:BasicObject):
+ v17:BasicObject = GetLocal :block, l1, EP@3
+ Jump bb5(v11, v17)
+ bb4(v13:BasicObject):
+ v19:BasicObject = GetBlockParam :block, l1, EP@3
+ Jump bb5(v13, v19)
+ bb5(v21:BasicObject, v22:BasicObject):
+ CheckInterrupts
+ Return v22
+ ");
+ }
+
+ #[test]
fn test_splatarray_mut() {
eval("
def test(a) = [*a]
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index cf100dcda2..556a1417a4 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -210,6 +210,7 @@ make_counters! {
exit_stackoverflow,
exit_block_param_proxy_modified,
exit_block_param_proxy_not_iseq_or_ifunc,
+ exit_block_param_wb_required,
exit_too_many_keyword_parameters,
}
@@ -557,6 +558,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter {
StackOverflow => exit_stackoverflow,
BlockParamProxyModified => exit_block_param_proxy_modified,
BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc,
+ BlockParamWbRequired => exit_block_param_wb_required,
TooManyKeywordParameters => exit_too_many_keyword_parameters,
PatchPoint(Invariant::BOPRedefined { .. })
=> exit_patchpoint_bop_redefined,