diff options
| author | Nozomi Hijikata <121233810+nozomemein@users.noreply.github.com> | 2026-01-22 08:54:10 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-21 18:54:10 -0500 |
| commit | 436ec3a9d68ae9282fbc79e9400382d93d05e800 (patch) | |
| tree | 6ad4e076d609598dba81556304577e0b80af4083 | |
| parent | 965b16d766df66e296a8d8254263da3c1cf45717 (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.rb | 36 | ||||
| -rw-r--r-- | zjit/src/codegen.rs | 42 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 130 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 61 | ||||
| -rw-r--r-- | zjit/src/hir/tests.rs | 65 | ||||
| -rw-r--r-- | zjit/src/stats.rs | 2 |
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, |
