mirror of
https://github.com/ruby/ruby.git
synced 2026-01-26 20:19:19 +00:00
Since `Send` has a block iseq, I updated `CCallWithFrame` to take an optional `blockiseq` as well, and then generate `CCallWithFrame` for `Send` when the condition is right.
## Stats
`liquid-render` Benchmark
| Metric | Before | After | Change |
|----------------------|--------------------|--------------------|--------------------- |
| send_no_profiles | 3,209,418 (34.1%) | 4,119 (0.1%) | -3,205,299 (-99.9%) |
| dynamic_send_count | 9,410,758 (23.1%) | 6,459,678 (15.9%) | -2,951,080 (-31.4%) |
| optimized_send_count | 31,269,388 (76.9%) | 34,220,474 (84.1%) | +2,951,086 (+9.4%) |
`lobsters` Benchmark
| Metric | Before | After | Change |
|----------------------|------------|------------|---------------------|
| send_no_profiles | 10,769,052 | 2,902,865 | -7,866,187 (-73.0%) |
| dynamic_send_count | 45,673,185 | 42,880,160 | -2,793,025 (-6.1%) |
| optimized_send_count | 75,142,407 | 78,378,514 | +3,236,107 (+4.3%) |
### `liquid-render` Before
<details>
```
Average of last 22, non-warmup iters: 262ms
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (96.9% of total 10,370,809):
Kernel#respond_to?: 5,069,204 (48.9%)
Hash#key?: 2,394,488 (23.1%)
Set#include?: 778,429 ( 7.5%)
String#===: 326,134 ( 3.1%)
String#<<: 203,231 ( 2.0%)
Integer#<<: 166,768 ( 1.6%)
Kernel#is_a?: 164,272 ( 1.6%)
Kernel#format: 124,262 ( 1.2%)
Integer#/: 124,262 ( 1.2%)
Array#<<: 115,325 ( 1.1%)
Regexp.last_match: 94,862 ( 0.9%)
Hash#[]=: 88,485 ( 0.9%)
String#start_with?: 55,933 ( 0.5%)
CGI::EscapeExt#escapeHTML: 55,471 ( 0.5%)
Array#shift: 55,298 ( 0.5%)
Regexp#===: 48,928 ( 0.5%)
String#=~: 48,477 ( 0.5%)
Array#unshift: 47,331 ( 0.5%)
String#empty?: 42,870 ( 0.4%)
Array#push: 41,215 ( 0.4%)
Top-20 not annotated C methods (97.1% of total 10,394,421):
Kernel#respond_to?: 5,069,204 (48.8%)
Hash#key?: 2,394,488 (23.0%)
Set#include?: 778,429 ( 7.5%)
String#===: 326,134 ( 3.1%)
Kernel#is_a?: 208,664 ( 2.0%)
String#<<: 203,231 ( 2.0%)
Integer#<<: 166,768 ( 1.6%)
Integer#/: 124,262 ( 1.2%)
Kernel#format: 124,262 ( 1.2%)
Array#<<: 115,325 ( 1.1%)
Regexp.last_match: 94,862 ( 0.9%)
Hash#[]=: 88,485 ( 0.9%)
String#start_with?: 55,933 ( 0.5%)
CGI::EscapeExt#escapeHTML: 55,471 ( 0.5%)
Array#shift: 55,298 ( 0.5%)
Regexp#===: 48,928 ( 0.5%)
String#=~: 48,477 ( 0.5%)
Array#unshift: 47,331 ( 0.5%)
String#empty?: 42,870 ( 0.4%)
Array#push: 41,215 ( 0.4%)
Top-2 not optimized method types for send (100.0% of total 2,382):
cfunc: 1,196 (50.2%)
iseq: 1,186 (49.8%)
Top-4 not optimized method types for send_without_block (100.0% of total 2,561,006):
iseq: 2,442,091 (95.4%)
optimized: 118,882 ( 4.6%)
alias: 20 ( 0.0%)
null: 13 ( 0.0%)
Top-9 not optimized instructions (100.0% of total 685,128):
invokeblock: 227,376 (33.2%)
opt_neq: 166,471 (24.3%)
opt_and: 166,471 (24.3%)
opt_eq: 66,721 ( 9.7%)
invokesuper: 39,363 ( 5.7%)
opt_le: 16,278 ( 2.4%)
opt_minus: 1,574 ( 0.2%)
opt_send_without_block: 772 ( 0.1%)
opt_or: 102 ( 0.0%)
Top-8 send fallback reasons (100.0% of total 9,410,758):
send_no_profiles: 3,209,418 (34.1%)
send_without_block_polymorphic: 2,858,558 (30.4%)
send_without_block_not_optimized_method_type: 2,561,006 (27.2%)
not_optimized_instruction: 685,128 ( 7.3%)
send_without_block_no_profiles: 91,913 ( 1.0%)
send_not_optimized_method_type: 2,382 ( 0.0%)
obj_to_string_not_string: 2,352 ( 0.0%)
send_without_block_cfunc_array_variadic: 1 ( 0.0%)
Top-3 unhandled YARV insns (100.0% of total 83,682):
getclassvariable: 83,431 (99.7%)
once: 137 ( 0.2%)
getconstant: 114 ( 0.1%)
Top-3 compile error reasons (100.0% of total 5,431,910):
register_spill_on_alloc: 4,665,393 (85.9%)
exception_handler: 766,347 (14.1%)
register_spill_on_ccall: 170 ( 0.0%)
Top-11 side exit reasons (100.0% of total 14,635,508):
compile_error: 5,431,910 (37.1%)
guard_shape_failure: 3,436,341 (23.5%)
guard_type_failure: 2,545,791 (17.4%)
unhandled_splat: 2,162,907 (14.8%)
unhandled_kwarg: 952,568 ( 6.5%)
unhandled_yarv_insn: 83,682 ( 0.6%)
unhandled_hir_insn: 19,112 ( 0.1%)
patchpoint_stable_constant_names: 1,608 ( 0.0%)
obj_to_string_fallback: 902 ( 0.0%)
patchpoint_method_redefined: 599 ( 0.0%)
block_param_proxy_not_iseq_or_ifunc: 88 ( 0.0%)
send_count: 40,680,153
dynamic_send_count: 9,410,758 (23.1%)
optimized_send_count: 31,269,395 (76.9%)
iseq_optimized_send_count: 13,886,902 (34.1%)
inline_cfunc_optimized_send_count: 7,011,684 (17.2%)
non_variadic_cfunc_optimized_send_count: 4,670,333 (11.5%)
variadic_cfunc_optimized_send_count: 5,700,476 (14.0%)
dynamic_getivar_count: 1,144,613
dynamic_setivar_count: 950,830
compiled_iseq_count: 402
failed_iseq_count: 48
compile_time: 976ms
profile_time: 3,223ms
gc_time: 22ms
invalidation_time: 0ms
vm_write_pc_count: 37,744,491
vm_write_sp_count: 37,511,865
vm_write_locals_count: 37,511,865
vm_write_stack_count: 37,511,865
vm_write_to_parent_iseq_local_count: 558,177
vm_read_from_parent_iseq_local_count: 14,317,032
code_region_bytes: 2,211,840
side_exit_count: 14,635,508
total_insn_count: 476,097,972
vm_insn_count: 253,795,154
zjit_insn_count: 222,302,818
ratio_in_zjit: 46.7%
```
</details>
### `liquid-render` After
<details>
```
Average of last 21, non-warmup iters: 272ms
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (96.8% of total 10,093,966):
Kernel#respond_to?: 4,932,224 (48.9%)
Hash#key?: 2,329,928 (23.1%)
Set#include?: 757,389 ( 7.5%)
String#===: 317,494 ( 3.1%)
String#<<: 197,831 ( 2.0%)
Integer#<<: 162,268 ( 1.6%)
Kernel#is_a?: 159,892 ( 1.6%)
Kernel#format: 120,902 ( 1.2%)
Integer#/: 120,902 ( 1.2%)
Array#<<: 112,225 ( 1.1%)
Regexp.last_match: 92,382 ( 0.9%)
Hash#[]=: 86,145 ( 0.9%)
String#start_with?: 54,953 ( 0.5%)
Array#shift: 54,038 ( 0.5%)
CGI::EscapeExt#escapeHTML: 53,971 ( 0.5%)
Regexp#===: 47,848 ( 0.5%)
String#=~: 47,237 ( 0.5%)
Array#unshift: 46,051 ( 0.5%)
String#empty?: 41,750 ( 0.4%)
Array#push: 40,115 ( 0.4%)
Top-20 not annotated C methods (97.1% of total 10,116,938):
Kernel#respond_to?: 4,932,224 (48.8%)
Hash#key?: 2,329,928 (23.0%)
Set#include?: 757,389 ( 7.5%)
String#===: 317,494 ( 3.1%)
Kernel#is_a?: 203,084 ( 2.0%)
String#<<: 197,831 ( 2.0%)
Integer#<<: 162,268 ( 1.6%)
Kernel#format: 120,902 ( 1.2%)
Integer#/: 120,902 ( 1.2%)
Array#<<: 112,225 ( 1.1%)
Regexp.last_match: 92,382 ( 0.9%)
Hash#[]=: 86,145 ( 0.9%)
String#start_with?: 54,953 ( 0.5%)
Array#shift: 54,038 ( 0.5%)
CGI::EscapeExt#escapeHTML: 53,971 ( 0.5%)
Regexp#===: 47,848 ( 0.5%)
String#=~: 47,237 ( 0.5%)
Array#unshift: 46,051 ( 0.5%)
String#empty?: 41,750 ( 0.4%)
Array#push: 40,115 ( 0.4%)
Top-2 not optimized method types for send (100.0% of total 182,938):
iseq: 178,414 (97.5%)
cfunc: 4,524 ( 2.5%)
Top-4 not optimized method types for send_without_block (100.0% of total 2,492,246):
iseq: 2,376,511 (95.4%)
optimized: 115,702 ( 4.6%)
alias: 20 ( 0.0%)
null: 13 ( 0.0%)
Top-9 not optimized instructions (100.0% of total 667,727):
invokeblock: 221,375 (33.2%)
opt_neq: 161,971 (24.3%)
opt_and: 161,971 (24.3%)
opt_eq: 64,921 ( 9.7%)
invokesuper: 39,243 ( 5.9%)
opt_le: 15,838 ( 2.4%)
opt_minus: 1,534 ( 0.2%)
opt_send_without_block: 772 ( 0.1%)
opt_or: 102 ( 0.0%)
Top-9 send fallback reasons (100.0% of total 6,287,956):
send_without_block_polymorphic: 2,782,058 (44.2%)
send_without_block_not_optimized_method_type: 2,492,246 (39.6%)
not_optimized_instruction: 667,727 (10.6%)
send_not_optimized_method_type: 182,938 ( 2.9%)
send_without_block_no_profiles: 89,613 ( 1.4%)
send_polymorphic: 66,962 ( 1.1%)
send_no_profiles: 4,059 ( 0.1%)
obj_to_string_not_string: 2,352 ( 0.0%)
send_without_block_cfunc_array_variadic: 1 ( 0.0%)
Top-3 unhandled YARV insns (100.0% of total 81,482):
getclassvariable: 81,231 (99.7%)
once: 137 ( 0.2%)
getconstant: 114 ( 0.1%)
Top-3 compile error reasons (100.0% of total 5,286,310):
register_spill_on_alloc: 4,540,413 (85.9%)
exception_handler: 745,727 (14.1%)
register_spill_on_ccall: 170 ( 0.0%)
Top-12 side exit reasons (100.0% of total 14,244,881):
compile_error: 5,286,310 (37.1%)
guard_shape_failure: 3,346,873 (23.5%)
guard_type_failure: 2,477,071 (17.4%)
unhandled_splat: 2,104,447 (14.8%)
unhandled_kwarg: 926,828 ( 6.5%)
unhandled_yarv_insn: 81,482 ( 0.6%)
unhandled_hir_insn: 18,672 ( 0.1%)
patchpoint_stable_constant_names: 1,608 ( 0.0%)
obj_to_string_fallback: 902 ( 0.0%)
patchpoint_method_redefined: 599 ( 0.0%)
block_param_proxy_not_iseq_or_ifunc: 88 ( 0.0%)
interrupt: 1 ( 0.0%)
send_count: 39,591,410
dynamic_send_count: 6,287,956 (15.9%)
optimized_send_count: 33,303,454 (84.1%)
iseq_optimized_send_count: 13,514,283 (34.1%)
inline_cfunc_optimized_send_count: 6,823,745 (17.2%)
non_variadic_cfunc_optimized_send_count: 7,417,432 (18.7%)
variadic_cfunc_optimized_send_count: 5,547,994 (14.0%)
dynamic_getivar_count: 1,110,647
dynamic_setivar_count: 927,309
compiled_iseq_count: 403
failed_iseq_count: 48
compile_time: 968ms
profile_time: 3,547ms
gc_time: 22ms
invalidation_time: 0ms
vm_write_pc_count: 36,735,108
vm_write_sp_count: 36,508,262
vm_write_locals_count: 36,508,262
vm_write_stack_count: 36,508,262
vm_write_to_parent_iseq_local_count: 543,097
vm_read_from_parent_iseq_local_count: 13,930,672
code_region_bytes: 2,228,224
side_exit_count: 14,244,881
total_insn_count: 463,357,969
vm_insn_count: 247,003,727
zjit_insn_count: 216,354,242
ratio_in_zjit: 46.7%
```
</details>
### `lobsters` Before
<details>
```
Average of last 10, non-warmup iters: 898ms
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (61.3% of total 19,495,906):
String#<<: 1,764,437 ( 9.1%)
Kernel#is_a?: 1,615,120 ( 8.3%)
Hash#[]=: 1,159,455 ( 5.9%)
Regexp#match?: 777,496 ( 4.0%)
String#empty?: 722,953 ( 3.7%)
Hash#key?: 685,258 ( 3.5%)
Kernel#respond_to?: 602,017 ( 3.1%)
TrueClass#===: 447,671 ( 2.3%)
FalseClass#===: 439,276 ( 2.3%)
Array#include?: 426,758 ( 2.2%)
Kernel#block_given?: 405,271 ( 2.1%)
Hash#fetch: 382,302 ( 2.0%)
ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%)
String#start_with?: 353,793 ( 1.8%)
Kernel#kind_of?: 340,341 ( 1.7%)
Kernel#dup: 328,162 ( 1.7%)
String.new: 306,667 ( 1.6%)
String#==: 287,549 ( 1.5%)
BasicObject#!=: 284,642 ( 1.5%)
String#length: 256,070 ( 1.3%)
Top-20 not annotated C methods (62.4% of total 19,796,172):
Kernel#is_a?: 1,993,676 (10.1%)
String#<<: 1,764,437 ( 8.9%)
Hash#[]=: 1,159,634 ( 5.9%)
Regexp#match?: 777,496 ( 3.9%)
String#empty?: 738,030 ( 3.7%)
Hash#key?: 685,258 ( 3.5%)
Kernel#respond_to?: 602,017 ( 3.0%)
TrueClass#===: 447,671 ( 2.3%)
FalseClass#===: 439,276 ( 2.2%)
Array#include?: 426,758 ( 2.2%)
Kernel#block_given?: 425,813 ( 2.2%)
Hash#fetch: 382,302 ( 1.9%)
ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%)
String#start_with?: 353,793 ( 1.8%)
Kernel#kind_of?: 340,375 ( 1.7%)
Kernel#dup: 328,169 ( 1.7%)
String.new: 306,667 ( 1.5%)
String#==: 293,520 ( 1.5%)
BasicObject#!=: 284,825 ( 1.4%)
String#length: 256,070 ( 1.3%)
Top-2 not optimized method types for send (100.0% of total 115,007):
cfunc: 76,172 (66.2%)
iseq: 38,835 (33.8%)
Top-6 not optimized method types for send_without_block (100.0% of total 8,003,641):
iseq: 3,999,211 (50.0%)
bmethod: 1,750,271 (21.9%)
optimized: 1,653,426 (20.7%)
alias: 591,342 ( 7.4%)
null: 8,174 ( 0.1%)
cfunc: 1,217 ( 0.0%)
Top-13 not optimized instructions (100.0% of total 7,590,826):
invokesuper: 4,335,446 (57.1%)
invokeblock: 1,329,215 (17.5%)
sendforward: 841,463 (11.1%)
opt_eq: 810,614 (10.7%)
opt_plus: 141,773 ( 1.9%)
opt_minus: 52,270 ( 0.7%)
opt_send_without_block: 43,248 ( 0.6%)
opt_neq: 15,047 ( 0.2%)
opt_mult: 13,824 ( 0.2%)
opt_or: 7,451 ( 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 45,673,212):
send_without_block_polymorphic: 17,390,335 (38.1%)
send_no_profiles: 10,769,053 (23.6%)
send_without_block_not_optimized_method_type: 8,003,641 (17.5%)
not_optimized_instruction: 7,590,826 (16.6%)
send_without_block_no_profiles: 1,757,109 ( 3.8%)
send_not_optimized_method_type: 115,007 ( 0.3%)
send_without_block_cfunc_array_variadic: 31,149 ( 0.1%)
obj_to_string_not_string: 15,518 ( 0.0%)
send_without_block_direct_too_many_args: 574 ( 0.0%)
Top-9 unhandled YARV insns (100.0% of total 1,242,228):
expandarray: 622,203 (50.1%)
checkkeyword: 316,111 (25.4%)
getclassvariable: 120,540 ( 9.7%)
getblockparam: 88,480 ( 7.1%)
invokesuperforward: 78,842 ( 6.3%)
opt_duparray_send: 14,149 ( 1.1%)
getconstant: 1,588 ( 0.1%)
checkmatch: 288 ( 0.0%)
once: 27 ( 0.0%)
Top-3 compile error reasons (100.0% of total 6,769,693):
register_spill_on_alloc: 6,188,305 (91.4%)
register_spill_on_ccall: 347,108 ( 5.1%)
exception_handler: 234,280 ( 3.5%)
Top-17 side exit reasons (100.0% of total 20,142,827):
compile_error: 6,769,693 (33.6%)
guard_type_failure: 5,169,050 (25.7%)
guard_shape_failure: 3,726,362 (18.5%)
unhandled_yarv_insn: 1,242,228 ( 6.2%)
block_param_proxy_not_iseq_or_ifunc: 984,480 ( 4.9%)
unhandled_kwarg: 800,154 ( 4.0%)
unknown_newarray_send: 539,317 ( 2.7%)
patchpoint_stable_constant_names: 340,283 ( 1.7%)
unhandled_splat: 229,440 ( 1.1%)
unhandled_hir_insn: 147,351 ( 0.7%)
patchpoint_no_singleton_class: 128,856 ( 0.6%)
patchpoint_method_redefined: 32,718 ( 0.2%)
block_param_proxy_modified: 25,274 ( 0.1%)
patchpoint_no_ep_escape: 7,559 ( 0.0%)
obj_to_string_fallback: 24 ( 0.0%)
guard_type_not_failure: 22 ( 0.0%)
interrupt: 16 ( 0.0%)
send_count: 120,815,640
dynamic_send_count: 45,673,212 (37.8%)
optimized_send_count: 75,142,428 (62.2%)
iseq_optimized_send_count: 32,188,039 (26.6%)
inline_cfunc_optimized_send_count: 23,458,483 (19.4%)
non_variadic_cfunc_optimized_send_count: 14,809,797 (12.3%)
variadic_cfunc_optimized_send_count: 4,686,109 ( 3.9%)
dynamic_getivar_count: 13,023,437
dynamic_setivar_count: 12,311,158
compiled_iseq_count: 4,806
failed_iseq_count: 466
compile_time: 8,943ms
profile_time: 99ms
gc_time: 45ms
invalidation_time: 239ms
vm_write_pc_count: 113,652,291
vm_write_sp_count: 111,209,623
vm_write_locals_count: 111,209,623
vm_write_stack_count: 111,209,623
vm_write_to_parent_iseq_local_count: 516,800
vm_read_from_parent_iseq_local_count: 11,225,587
code_region_bytes: 22,609,920
side_exit_count: 20,142,827
total_insn_count: 926,088,942
vm_insn_count: 297,636,255
zjit_insn_count: 628,452,687
ratio_in_zjit: 67.9%
```
</details>
### `lobsters` After
<details>
```
Average of last 10, non-warmup iters: 919ms
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (61.3% of total 19,495,868):
String#<<: 1,764,437 ( 9.1%)
Kernel#is_a?: 1,615,110 ( 8.3%)
Hash#[]=: 1,159,455 ( 5.9%)
Regexp#match?: 777,496 ( 4.0%)
String#empty?: 722,953 ( 3.7%)
Hash#key?: 685,258 ( 3.5%)
Kernel#respond_to?: 602,016 ( 3.1%)
TrueClass#===: 447,671 ( 2.3%)
FalseClass#===: 439,276 ( 2.3%)
Array#include?: 426,758 ( 2.2%)
Kernel#block_given?: 405,271 ( 2.1%)
Hash#fetch: 382,302 ( 2.0%)
ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%)
String#start_with?: 353,793 ( 1.8%)
Kernel#kind_of?: 340,341 ( 1.7%)
Kernel#dup: 328,162 ( 1.7%)
String.new: 306,667 ( 1.6%)
String#==: 287,545 ( 1.5%)
BasicObject#!=: 284,642 ( 1.5%)
String#length: 256,070 ( 1.3%)
Top-20 not annotated C methods (62.4% of total 19,796,134):
Kernel#is_a?: 1,993,666 (10.1%)
String#<<: 1,764,437 ( 8.9%)
Hash#[]=: 1,159,634 ( 5.9%)
Regexp#match?: 777,496 ( 3.9%)
String#empty?: 738,030 ( 3.7%)
Hash#key?: 685,258 ( 3.5%)
Kernel#respond_to?: 602,016 ( 3.0%)
TrueClass#===: 447,671 ( 2.3%)
FalseClass#===: 439,276 ( 2.2%)
Array#include?: 426,758 ( 2.2%)
Kernel#block_given?: 425,813 ( 2.2%)
Hash#fetch: 382,302 ( 1.9%)
ObjectSpace::WeakKeyMap#[]: 356,654 ( 1.8%)
String#start_with?: 353,793 ( 1.8%)
Kernel#kind_of?: 340,375 ( 1.7%)
Kernel#dup: 328,169 ( 1.7%)
String.new: 306,667 ( 1.5%)
String#==: 293,516 ( 1.5%)
BasicObject#!=: 284,825 ( 1.4%)
String#length: 256,070 ( 1.3%)
Top-4 not optimized method types for send (100.0% of total 4,749,678):
iseq: 2,563,391 (54.0%)
cfunc: 2,064,888 (43.5%)
alias: 118,577 ( 2.5%)
null: 2,822 ( 0.1%)
Top-6 not optimized method types for send_without_block (100.0% of total 8,003,641):
iseq: 3,999,211 (50.0%)
bmethod: 1,750,271 (21.9%)
optimized: 1,653,426 (20.7%)
alias: 591,342 ( 7.4%)
null: 8,174 ( 0.1%)
cfunc: 1,217 ( 0.0%)
Top-13 not optimized instructions (100.0% of total 7,590,818):
invokesuper: 4,335,442 (57.1%)
invokeblock: 1,329,215 (17.5%)
sendforward: 841,463 (11.1%)
opt_eq: 810,610 (10.7%)
opt_plus: 141,773 ( 1.9%)
opt_minus: 52,270 ( 0.7%)
opt_send_without_block: 43,248 ( 0.6%)
opt_neq: 15,047 ( 0.2%)
opt_mult: 13,824 ( 0.2%)
opt_or: 7,451 ( 0.1%)
opt_lt: 348 ( 0.0%)
opt_ge: 91 ( 0.0%)
opt_gt: 36 ( 0.0%)
Top-10 send fallback reasons (100.0% of total 43,152,037):
send_without_block_polymorphic: 17,390,322 (40.3%)
send_without_block_not_optimized_method_type: 8,003,641 (18.5%)
not_optimized_instruction: 7,590,818 (17.6%)
send_not_optimized_method_type: 4,749,678 (11.0%)
send_no_profiles: 2,893,666 ( 6.7%)
send_without_block_no_profiles: 1,757,109 ( 4.1%)
send_polymorphic: 719,562 ( 1.7%)
send_without_block_cfunc_array_variadic: 31,149 ( 0.1%)
obj_to_string_not_string: 15,518 ( 0.0%)
send_without_block_direct_too_many_args: 574 ( 0.0%)
Top-9 unhandled YARV insns (100.0% of total 1,242,215):
expandarray: 622,203 (50.1%)
checkkeyword: 316,111 (25.4%)
getclassvariable: 120,540 ( 9.7%)
getblockparam: 88,467 ( 7.1%)
invokesuperforward: 78,842 ( 6.3%)
opt_duparray_send: 14,149 ( 1.1%)
getconstant: 1,588 ( 0.1%)
checkmatch: 288 ( 0.0%)
once: 27 ( 0.0%)
Top-3 compile error reasons (100.0% of total 6,769,688):
register_spill_on_alloc: 6,188,305 (91.4%)
register_spill_on_ccall: 347,108 ( 5.1%)
exception_handler: 234,275 ( 3.5%)
Top-17 side exit reasons (100.0% of total 20,144,372):
compile_error: 6,769,688 (33.6%)
guard_type_failure: 5,169,204 (25.7%)
guard_shape_failure: 3,726,374 (18.5%)
unhandled_yarv_insn: 1,242,215 ( 6.2%)
block_param_proxy_not_iseq_or_ifunc: 984,480 ( 4.9%)
unhandled_kwarg: 800,154 ( 4.0%)
unknown_newarray_send: 539,317 ( 2.7%)
patchpoint_stable_constant_names: 340,283 ( 1.7%)
unhandled_splat: 229,440 ( 1.1%)
unhandled_hir_insn: 147,351 ( 0.7%)
patchpoint_no_singleton_class: 130,252 ( 0.6%)
patchpoint_method_redefined: 32,716 ( 0.2%)
block_param_proxy_modified: 25,274 ( 0.1%)
patchpoint_no_ep_escape: 7,559 ( 0.0%)
obj_to_string_fallback: 24 ( 0.0%)
guard_type_not_failure: 22 ( 0.0%)
interrupt: 19 ( 0.0%)
send_count: 120,812,030
dynamic_send_count: 43,152,037 (35.7%)
optimized_send_count: 77,659,993 (64.3%)
iseq_optimized_send_count: 32,187,900 (26.6%)
inline_cfunc_optimized_send_count: 23,458,491 (19.4%)
non_variadic_cfunc_optimized_send_count: 17,327,499 (14.3%)
variadic_cfunc_optimized_send_count: 4,686,103 ( 3.9%)
dynamic_getivar_count: 13,023,424
dynamic_setivar_count: 12,310,991
compiled_iseq_count: 4,806
failed_iseq_count: 466
compile_time: 9,012ms
profile_time: 104ms
gc_time: 44ms
invalidation_time: 239ms
vm_write_pc_count: 113,648,665
vm_write_sp_count: 111,205,997
vm_write_locals_count: 111,205,997
vm_write_stack_count: 111,205,997
vm_write_to_parent_iseq_local_count: 516,800
vm_read_from_parent_iseq_local_count: 11,225,587
code_region_bytes: 23,052,288
side_exit_count: 20,144,372
total_insn_count: 926,090,214
vm_insn_count: 297,647,811
zjit_insn_count: 628,442,403
ratio_in_zjit: 67.9%
```
</details>
1709 lines
36 KiB
C
1709 lines
36 KiB
C
/* -*- C -*-
|
|
insns.def - YARV instruction definitions
|
|
|
|
$Author: $
|
|
created at: 04/01/01 01:17:55 JST
|
|
|
|
Copyright (C) 2004-2007 Koichi Sasada
|
|
Massive rewrite by @shyouhei in 2017.
|
|
*/
|
|
|
|
/* Some comments about this file's contents:
|
|
|
|
- The new format aims to be editable by C editor of your choice;
|
|
your mileage might vary of course.
|
|
|
|
- Each instructions are in following format:
|
|
|
|
DEFINE_INSN
|
|
instruction_name
|
|
(type operand, type operand, ..)
|
|
(pop_values, ..)
|
|
(return values ..)
|
|
// attr type name contents..
|
|
{
|
|
.. // insn body
|
|
}
|
|
|
|
- Unlike the old format which was line-oriented, you can now place
|
|
newlines and comments at liberal positions.
|
|
|
|
- `DEFINE_INSN` is a keyword.
|
|
|
|
- An instruction name must be a valid C identifier.
|
|
|
|
- Operands, pop values, return values are series of either variable
|
|
declarations, keyword `void`, or keyword `...`. They are much
|
|
like C function declarations.
|
|
|
|
- Attribute pragmas are optional, and can include arbitrary C
|
|
expressions. You can write anything there but as of writing,
|
|
supported attributes are:
|
|
|
|
* sp_inc: Used to dynamically calculate sp increase in
|
|
`insn_stack_increase`.
|
|
|
|
* handles_sp: If it is true, VM deals with sp in the insn.
|
|
Default is if the instruction takes ISEQ operand or not.
|
|
|
|
* leaf: indicates that the instruction is "leaf" i.e. it does
|
|
not introduce new stack frame on top of it.
|
|
If an instruction handles sp, that can never be a leaf.
|
|
|
|
- Attributes can access operands, but not stack (push/pop) variables.
|
|
|
|
- An instruction's body is a pure C block, copied verbatimly into
|
|
the generated C source code.
|
|
*/
|
|
|
|
/* nop */
|
|
DEFINE_INSN
|
|
nop
|
|
()
|
|
()
|
|
()
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with variables */
|
|
/**********************************************************/
|
|
|
|
/* Get local variable (pointed by `idx' and `level').
|
|
'level' indicates the nesting depth from the current block.
|
|
*/
|
|
DEFINE_INSN
|
|
getlocal
|
|
(lindex_t idx, rb_num_t level)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = *(vm_get_ep(GET_EP(), level) - idx);
|
|
RB_DEBUG_COUNTER_INC(lvar_get);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
|
|
}
|
|
|
|
/* Set a local variable (pointed to by 'idx') as val.
|
|
'level' indicates the nesting depth from the current block.
|
|
*/
|
|
DEFINE_INSN
|
|
setlocal
|
|
(lindex_t idx, rb_num_t level)
|
|
(VALUE val)
|
|
()
|
|
{
|
|
vm_env_write(vm_get_ep(GET_EP(), level), -(int)idx, val);
|
|
RB_DEBUG_COUNTER_INC(lvar_set);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
|
|
}
|
|
|
|
/* Get a block parameter. */
|
|
DEFINE_INSN
|
|
getblockparam
|
|
(lindex_t idx, rb_num_t level)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
const VALUE *ep = vm_get_ep(GET_EP(), level);
|
|
VM_ASSERT(VM_ENV_LOCAL_P(ep));
|
|
|
|
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
|
|
val = rb_vm_bh_to_procval(ec, VM_ENV_BLOCK_HANDLER(ep));
|
|
vm_env_write(ep, -(int)idx, val);
|
|
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
|
|
}
|
|
else {
|
|
val = *(ep - idx);
|
|
RB_DEBUG_COUNTER_INC(lvar_get);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
|
|
}
|
|
}
|
|
|
|
/* Set block parameter. */
|
|
DEFINE_INSN
|
|
setblockparam
|
|
(lindex_t idx, rb_num_t level)
|
|
(VALUE val)
|
|
()
|
|
{
|
|
const VALUE *ep = vm_get_ep(GET_EP(), level);
|
|
VM_ASSERT(VM_ENV_LOCAL_P(ep));
|
|
|
|
vm_env_write(ep, -(int)idx, val);
|
|
RB_DEBUG_COUNTER_INC(lvar_set);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
|
|
|
|
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
|
|
}
|
|
|
|
/* Get special proxy object which only responds to `call` method if the block parameter
|
|
represents a iseq/ifunc block. Otherwise, same as `getblockparam`.
|
|
*/
|
|
DEFINE_INSN
|
|
getblockparamproxy
|
|
(lindex_t idx, rb_num_t level)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
const VALUE *ep = vm_get_ep(GET_EP(), level);
|
|
VM_ASSERT(VM_ENV_LOCAL_P(ep));
|
|
|
|
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
|
|
VALUE block_handler = VM_ENV_BLOCK_HANDLER(ep);
|
|
|
|
if (block_handler) {
|
|
switch (vm_block_handler_type(block_handler)) {
|
|
case block_handler_type_iseq:
|
|
case block_handler_type_ifunc:
|
|
val = rb_block_param_proxy;
|
|
break;
|
|
case block_handler_type_symbol:
|
|
val = rb_sym_to_proc(VM_BH_TO_SYMBOL(block_handler));
|
|
goto INSN_LABEL(set);
|
|
case block_handler_type_proc:
|
|
val = VM_BH_TO_PROC(block_handler);
|
|
goto INSN_LABEL(set);
|
|
default:
|
|
VM_UNREACHABLE(getblockparamproxy);
|
|
}
|
|
}
|
|
else {
|
|
val = Qnil;
|
|
INSN_LABEL(set):
|
|
vm_env_write(ep, -(int)idx, val);
|
|
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
|
|
}
|
|
}
|
|
else {
|
|
val = *(ep - idx);
|
|
RB_DEBUG_COUNTER_INC(lvar_get);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
|
|
}
|
|
}
|
|
|
|
/* Get value of special local variable ($~, $_, ..). */
|
|
DEFINE_INSN
|
|
getspecial
|
|
(rb_num_t key, rb_num_t type)
|
|
()
|
|
(VALUE val)
|
|
/* `$~ = MatchData.allocate; $&` can raise. */
|
|
// attr bool leaf = (type == 0) ? true : false;
|
|
{
|
|
val = vm_getspecial(ec, GET_LEP(), key, type);
|
|
}
|
|
|
|
/* Set value of special local variable ($~, $_, ...) to obj. */
|
|
DEFINE_INSN
|
|
setspecial
|
|
(rb_num_t key)
|
|
(VALUE obj)
|
|
()
|
|
{
|
|
lep_svar_set(ec, GET_LEP(), key, obj);
|
|
}
|
|
|
|
/* Get value of instance variable id of self. */
|
|
DEFINE_INSN
|
|
getinstancevariable
|
|
(ID id, IVC ic)
|
|
()
|
|
(VALUE val)
|
|
/* Ractor crashes when it accesses class/module-level instances variables. */
|
|
// attr bool leaf = false; /* has IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR() */
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_getinstancevariable(GET_ISEQ(), GET_SELF(), id, ic);
|
|
}
|
|
|
|
/* Set value of instance variable id of self to val. */
|
|
DEFINE_INSN
|
|
setinstancevariable
|
|
(ID id, IVC ic)
|
|
(VALUE val)
|
|
()
|
|
// attr bool leaf = false; /* has rb_check_frozen() */
|
|
{
|
|
vm_setinstancevariable(GET_ISEQ(), GET_SELF(), id, val, ic);
|
|
}
|
|
|
|
/* Get value of class variable id of klass as val. */
|
|
DEFINE_INSN
|
|
getclassvariable
|
|
(ID id, ICVARC ic)
|
|
()
|
|
(VALUE val)
|
|
/* "class variable access from toplevel" warning can be hooked. */
|
|
// attr bool leaf = false; /* has rb_warning() */
|
|
{
|
|
rb_control_frame_t *cfp = GET_CFP();
|
|
val = vm_getclassvariable(GET_ISEQ(), cfp, id, ic);
|
|
}
|
|
|
|
/* Set value of class variable id of klass as val. */
|
|
DEFINE_INSN
|
|
setclassvariable
|
|
(ID id, ICVARC ic)
|
|
(VALUE val)
|
|
()
|
|
/* "class variable access from toplevel" warning can be hooked. */
|
|
// attr bool leaf = false; /* has rb_warning() */
|
|
{
|
|
vm_ensure_not_refinement_module(GET_SELF());
|
|
vm_setclassvariable(GET_ISEQ(), GET_CFP(), id, val, ic);
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_getconstant_path
|
|
(IC ic)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* may autoload or raise */
|
|
{
|
|
val = rb_vm_opt_getconstant_path(ec, GET_CFP(), ic);
|
|
}
|
|
|
|
/* Get constant variable id. If klass is Qnil and allow_nil is Qtrue, constants
|
|
are searched in the current scope. Otherwise, get constant under klass
|
|
class or module.
|
|
*/
|
|
DEFINE_INSN
|
|
getconstant
|
|
(ID id)
|
|
(VALUE klass, VALUE allow_nil)
|
|
(VALUE val)
|
|
/* getconstant can kick autoload */
|
|
// attr bool leaf = false; /* has rb_autoload_load() */
|
|
{
|
|
val = vm_get_ev_const(ec, klass, id, allow_nil == Qtrue, 0);
|
|
}
|
|
|
|
/* Set constant variable id under cbase class or module.
|
|
*/
|
|
DEFINE_INSN
|
|
setconstant
|
|
(ID id)
|
|
(VALUE val, VALUE cbase)
|
|
()
|
|
/* Assigning an object to a constant is basically a leaf operation.
|
|
* The problem is, assigning a Module instance to a constant _names_
|
|
* that module. Naming involves string manipulations, which are
|
|
* method calls. */
|
|
// attr bool leaf = false; /* has StringValue() */
|
|
{
|
|
vm_check_if_namespace(cbase);
|
|
vm_ensure_not_refinement_module(GET_SELF());
|
|
rb_const_set(cbase, id, val);
|
|
}
|
|
|
|
/* get global variable id. */
|
|
DEFINE_INSN
|
|
getglobal
|
|
(ID gid)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = false;
|
|
{
|
|
val = rb_gvar_get(gid);
|
|
}
|
|
|
|
/* set global variable id as val. */
|
|
DEFINE_INSN
|
|
setglobal
|
|
(ID gid)
|
|
(VALUE val)
|
|
()
|
|
// attr bool leaf = false;
|
|
{
|
|
rb_gvar_set(gid, val);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with values */
|
|
/**********************************************************/
|
|
|
|
/* put nil to stack. */
|
|
DEFINE_INSN
|
|
putnil
|
|
()
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = Qnil;
|
|
}
|
|
|
|
/* put self. */
|
|
DEFINE_INSN
|
|
putself
|
|
()
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = GET_SELF();
|
|
}
|
|
|
|
/* put some object.
|
|
i.e. Fixnum, true, false, nil, and so on.
|
|
*/
|
|
DEFINE_INSN
|
|
putobject
|
|
(VALUE val)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
/* */
|
|
}
|
|
|
|
/* put special object. "value_type" is for expansion. */
|
|
DEFINE_INSN
|
|
putspecialobject
|
|
(rb_num_t value_type)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = (value_type == VM_SPECIAL_OBJECT_VMCORE); /* others may raise when allocating singleton */
|
|
{
|
|
enum vm_special_object_type type;
|
|
|
|
type = (enum vm_special_object_type)value_type;
|
|
val = vm_get_special_object(GET_EP(), type);
|
|
}
|
|
|
|
/* put string val. string will be copied. */
|
|
DEFINE_INSN
|
|
putstring
|
|
(VALUE str)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = rb_ec_str_resurrect(ec, str, false);
|
|
}
|
|
|
|
/* put chilled string val. string will be copied but frozen in the future. */
|
|
DEFINE_INSN
|
|
putchilledstring
|
|
(VALUE str)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = rb_ec_str_resurrect(ec, str, true);
|
|
}
|
|
|
|
/* put concatenate strings */
|
|
DEFINE_INSN
|
|
concatstrings
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
/* This instruction can concat UTF-8 and binary strings, resulting in
|
|
* Encoding::CompatibilityError. */
|
|
// attr bool leaf = false; /* has rb_enc_cr_str_buf_cat() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
val = rb_str_concat_literals(num, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
|
|
/* Convert the result to string if not already a string.
|
|
This is used as a backup if to_s does not return a string. */
|
|
DEFINE_INSN
|
|
anytostring
|
|
()
|
|
(VALUE val, VALUE str)
|
|
(VALUE val)
|
|
{
|
|
val = rb_obj_as_string_result(str, val);
|
|
}
|
|
|
|
/* compile str to Regexp and push it.
|
|
opt is the option for the Regexp.
|
|
*/
|
|
DEFINE_INSN
|
|
toregexp
|
|
(rb_num_t opt, rb_num_t cnt)
|
|
(...)
|
|
(VALUE val)
|
|
/* This instruction can raise RegexpError, thus can call
|
|
* RegexpError#initialize */
|
|
// attr bool leaf = false;
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)cnt;
|
|
{
|
|
const VALUE ary = rb_ary_tmp_new_from_values(0, cnt, STACK_ADDR_FROM_TOP(cnt));
|
|
val = rb_reg_new_ary(ary, (int)opt);
|
|
rb_ary_clear(ary);
|
|
}
|
|
|
|
/* intern str to Symbol and push it. */
|
|
DEFINE_INSN
|
|
intern
|
|
()
|
|
(VALUE str)
|
|
(VALUE sym)
|
|
{
|
|
sym = rb_str_intern(str);
|
|
}
|
|
|
|
/* put new array initialized with num values on the stack. */
|
|
DEFINE_INSN
|
|
newarray
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
val = rb_ec_ary_new_from_values(ec, num, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
|
|
/* push hash onto array unless the hash is empty (as empty keyword
|
|
splats should be ignored).
|
|
*/
|
|
DEFINE_INSN
|
|
pushtoarraykwsplat
|
|
()
|
|
(VALUE ary, VALUE hash)
|
|
(VALUE ary)
|
|
{
|
|
if (!RHASH_EMPTY_P(hash)) {
|
|
rb_ary_push(ary, hash);
|
|
}
|
|
}
|
|
|
|
/* dup array */
|
|
DEFINE_INSN
|
|
duparray
|
|
(VALUE ary)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(ARRAY, RARRAY_LEN(ary));
|
|
val = rb_ary_resurrect(ary);
|
|
}
|
|
|
|
/* dup hash */
|
|
DEFINE_INSN
|
|
duphash
|
|
(VALUE hash)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(HASH, RHASH_SIZE(hash) << 1);
|
|
val = rb_hash_resurrect(hash);
|
|
}
|
|
|
|
/* if TOS is an array expand, expand it to num objects.
|
|
if the number of the array is less than num, push nils to fill.
|
|
if it is greater than num, exceeding elements are dropped.
|
|
unless TOS is an array, push num - 1 nils.
|
|
if flags is non-zero, push the array of the rest elements.
|
|
flag: 0x01 - rest args array
|
|
flag: 0x02 - for postarg
|
|
flag: 0x04 - reverse?
|
|
*/
|
|
DEFINE_INSN
|
|
expandarray
|
|
(rb_num_t num, rb_num_t flag)
|
|
(..., VALUE ary)
|
|
(...)
|
|
// attr bool handles_sp = true;
|
|
// attr bool leaf = false; /* has rb_check_array_type() */
|
|
// attr rb_snum_t sp_inc = (rb_snum_t)num - 1 + (flag & 1 ? 1 : 0);
|
|
{
|
|
vm_expandarray(GET_CFP(), ary, num, (int)flag);
|
|
}
|
|
|
|
/* concat two arrays, without modifying first array.
|
|
* attempts to convert both objects to arrays using to_a.
|
|
*/
|
|
DEFINE_INSN
|
|
concatarray
|
|
()
|
|
(VALUE ary1, VALUE ary2)
|
|
(VALUE ary)
|
|
// attr bool leaf = false; /* has rb_check_array_type() */
|
|
{
|
|
ary = vm_concat_array(ary1, ary2);
|
|
}
|
|
|
|
/* concat second array to first array.
|
|
* first argument must already be an array.
|
|
* attempts to convert second object to array using to_a.
|
|
*/
|
|
DEFINE_INSN
|
|
concattoarray
|
|
()
|
|
(VALUE ary1, VALUE ary2)
|
|
(VALUE ary)
|
|
// attr bool leaf = false; /* has rb_check_array_type() */
|
|
{
|
|
ary = vm_concat_to_array(ary1, ary2);
|
|
}
|
|
|
|
/* push given number of objects to array directly before. */
|
|
DEFINE_INSN
|
|
pushtoarray
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = -(rb_snum_t)num;
|
|
{
|
|
const VALUE *objp = STACK_ADDR_FROM_TOP(num);
|
|
val = rb_ary_cat(*(objp-1), objp, num);
|
|
}
|
|
|
|
/* call to_a on array ary to splat */
|
|
DEFINE_INSN
|
|
splatarray
|
|
(VALUE flag)
|
|
(VALUE ary)
|
|
(VALUE obj)
|
|
// attr bool leaf = false; /* has rb_check_array_type() */
|
|
{
|
|
obj = vm_splat_array(flag, ary);
|
|
}
|
|
|
|
/* call to_hash on hash to keyword splat before converting block */
|
|
DEFINE_INSN
|
|
splatkw
|
|
()
|
|
(VALUE hash, VALUE block)
|
|
(VALUE obj, VALUE block)
|
|
// attr bool leaf = false; /* has rb_to_hash_type() */
|
|
{
|
|
if (NIL_P(hash)) {
|
|
obj = Qnil;
|
|
}
|
|
else {
|
|
obj = rb_to_hash_type(hash);
|
|
}
|
|
}
|
|
|
|
/* put new Hash from n elements. n must be an even number. */
|
|
DEFINE_INSN
|
|
newhash
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* has rb_hash_key_str() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(HASH, num);
|
|
|
|
if (num) {
|
|
val = rb_hash_new_with_size(num / 2);
|
|
rb_hash_bulk_insert(num, STACK_ADDR_FROM_TOP(num), val);
|
|
}
|
|
else {
|
|
val = rb_hash_new();
|
|
}
|
|
}
|
|
|
|
/* put new Range object.(Range.new(low, high, flag)) */
|
|
DEFINE_INSN
|
|
newrange
|
|
(rb_num_t flag)
|
|
(VALUE low, VALUE high)
|
|
(VALUE val)
|
|
/* rb_range_new() exercises "bad value for range" check. */
|
|
// attr bool leaf = false; /* see also: range.c:range_init() */
|
|
{
|
|
val = rb_range_new(low, high, (int)flag);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with stack operation */
|
|
/**********************************************************/
|
|
|
|
/* pop from stack. */
|
|
DEFINE_INSN
|
|
pop
|
|
()
|
|
(VALUE val)
|
|
()
|
|
{
|
|
(void)val;
|
|
/* none */
|
|
}
|
|
|
|
/* duplicate stack top. */
|
|
DEFINE_INSN
|
|
dup
|
|
()
|
|
(VALUE val)
|
|
(VALUE val1, VALUE val2)
|
|
{
|
|
val1 = val2 = val;
|
|
}
|
|
|
|
/* duplicate stack top n elements */
|
|
DEFINE_INSN
|
|
dupn
|
|
(rb_num_t n)
|
|
(...)
|
|
(...)
|
|
// attr rb_snum_t sp_inc = n;
|
|
{
|
|
void *dst = GET_SP();
|
|
void *src = STACK_ADDR_FROM_TOP(n);
|
|
|
|
MEMCPY(dst, src, VALUE, n);
|
|
}
|
|
|
|
/* swap top 2 vals */
|
|
DEFINE_INSN
|
|
swap
|
|
()
|
|
(VALUE val, VALUE obj)
|
|
(VALUE obj, VALUE val)
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/* reverse stack top N order. */
|
|
DEFINE_INSN
|
|
opt_reverse
|
|
(rb_num_t n)
|
|
(...)
|
|
(...)
|
|
// attr rb_snum_t sp_inc = 0;
|
|
{
|
|
rb_num_t i;
|
|
VALUE *sp = STACK_ADDR_FROM_TOP(n);
|
|
|
|
for (i=0; i<n/2; i++) {
|
|
VALUE v0 = sp[i];
|
|
VALUE v1 = TOPN(i);
|
|
sp[i] = v1;
|
|
TOPN(i) = v0;
|
|
}
|
|
}
|
|
|
|
/* for stack caching. */
|
|
DEFINE_INSN_IF(STACK_CACHING)
|
|
reput
|
|
()
|
|
(..., VALUE val)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 0;
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/* get nth stack value from stack top */
|
|
DEFINE_INSN
|
|
topn
|
|
(rb_num_t n)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 1;
|
|
{
|
|
val = TOPN(n);
|
|
}
|
|
|
|
/* set Nth stack entry to stack top */
|
|
DEFINE_INSN
|
|
setn
|
|
(rb_num_t n)
|
|
(..., VALUE val)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 0;
|
|
{
|
|
TOPN(n) = val;
|
|
}
|
|
|
|
/* empty current stack */
|
|
DEFINE_INSN
|
|
adjuststack
|
|
(rb_num_t n)
|
|
(...)
|
|
(...)
|
|
// attr rb_snum_t sp_inc = -(rb_snum_t)n;
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with setting */
|
|
/**********************************************************/
|
|
|
|
/* defined? */
|
|
DEFINE_INSN
|
|
defined
|
|
(rb_num_t op_type, VALUE obj, VALUE pushval)
|
|
(VALUE v)
|
|
(VALUE val)
|
|
// attr bool leaf = leafness_of_defined(op_type);
|
|
{
|
|
val = Qnil;
|
|
if (vm_defined(ec, GET_CFP(), op_type, obj, v)) {
|
|
val = pushval;
|
|
}
|
|
}
|
|
|
|
/* defined?(@foo) */
|
|
DEFINE_INSN
|
|
definedivar
|
|
(ID id, IVC ic, VALUE pushval)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = false;
|
|
{
|
|
val = Qnil;
|
|
if (!UNDEF_P(vm_getivar(GET_SELF(), id, GET_ISEQ(), ic, NULL, FALSE, Qundef))) {
|
|
val = pushval;
|
|
}
|
|
}
|
|
|
|
/* check `target' matches `pattern'.
|
|
`flag & VM_CHECKMATCH_TYPE_MASK' describe how to check pattern.
|
|
VM_CHECKMATCH_TYPE_WHEN: ignore target and check pattern is truthy.
|
|
VM_CHECKMATCH_TYPE_CASE: check `patten === target'.
|
|
VM_CHECKMATCH_TYPE_RESCUE: check `pattern.kind_of?(Module) && pattern === target'.
|
|
if `flag & VM_CHECKMATCH_ARRAY' is not 0, then `patten' is array of patterns.
|
|
*/
|
|
DEFINE_INSN
|
|
checkmatch
|
|
(rb_num_t flag)
|
|
(VALUE target, VALUE pattern)
|
|
(VALUE result)
|
|
// attr bool leaf = leafness_of_checkmatch(flag);
|
|
{
|
|
result = vm_check_match(ec, target, pattern, flag);
|
|
}
|
|
|
|
/* check keywords are specified or not. */
|
|
DEFINE_INSN
|
|
checkkeyword
|
|
(lindex_t kw_bits_index, lindex_t keyword_index)
|
|
()
|
|
(VALUE ret)
|
|
{
|
|
ret = vm_check_keyword(kw_bits_index, keyword_index, GET_EP());
|
|
}
|
|
|
|
/* check if val is type. */
|
|
DEFINE_INSN
|
|
checktype
|
|
(rb_num_t type)
|
|
(VALUE val)
|
|
(VALUE ret)
|
|
{
|
|
ret = RBOOL(TYPE(val) == (int)type);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 1: class/module */
|
|
/**********************************************************/
|
|
|
|
/* enter class definition scope. if super is Qfalse, and class
|
|
"klass" is defined, it's redefined. Otherwise, define "klass" class.
|
|
*/
|
|
DEFINE_INSN
|
|
defineclass
|
|
(ID id, ISEQ class_iseq, rb_num_t flags)
|
|
(VALUE cbase, VALUE super)
|
|
(VALUE val)
|
|
{
|
|
VALUE klass = vm_find_or_create_class_by_id(id, flags, cbase, super);
|
|
const rb_namespace_t *ns = rb_current_namespace();
|
|
|
|
rb_iseq_check(class_iseq);
|
|
|
|
/* enter scope */
|
|
vm_push_frame(ec, class_iseq, VM_FRAME_MAGIC_CLASS | VM_ENV_FLAG_LOCAL, klass,
|
|
GC_GUARDED_PTR(ns),
|
|
(VALUE)vm_cref_push(ec, klass, NULL, FALSE, FALSE),
|
|
ISEQ_BODY(class_iseq)->iseq_encoded, GET_SP(),
|
|
ISEQ_BODY(class_iseq)->local_table_size,
|
|
ISEQ_BODY(class_iseq)->stack_max);
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
|
|
DEFINE_INSN
|
|
definemethod
|
|
(ID id, ISEQ iseq)
|
|
()
|
|
()
|
|
{
|
|
vm_define_method(ec, Qnil, id, (VALUE)iseq, FALSE);
|
|
}
|
|
|
|
DEFINE_INSN
|
|
definesmethod
|
|
(ID id, ISEQ iseq)
|
|
(VALUE obj)
|
|
()
|
|
{
|
|
vm_define_method(ec, obj, id, (VALUE)iseq, TRUE);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 2: method/iterator */
|
|
/**********************************************************/
|
|
|
|
/* invoke method. */
|
|
DEFINE_INSN
|
|
send
|
|
(CALL_DATA cd, ISEQ blockiseq)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, false);
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
|
|
JIT_EXEC(ec, val);
|
|
|
|
if (UNDEF_P(val)) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* invoke forward method. */
|
|
DEFINE_INSN
|
|
sendforward
|
|
(CALL_DATA cd, ISEQ blockiseq)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
struct rb_forwarding_call_data adjusted_cd;
|
|
struct rb_callinfo adjusted_ci;
|
|
|
|
VALUE bh = vm_caller_setup_fwd_args(ec, GET_CFP(), cd, blockiseq, 0, &adjusted_cd, &adjusted_ci);
|
|
|
|
val = vm_sendish(ec, GET_CFP(), &adjusted_cd.cd, bh, mexp_search_method);
|
|
JIT_EXEC(ec, val);
|
|
|
|
if (cd->cc != adjusted_cd.cd.cc && vm_cc_markable(adjusted_cd.cd.cc)) {
|
|
RB_OBJ_WRITE(GET_ISEQ(), &cd->cc, adjusted_cd.cd.cc);
|
|
}
|
|
|
|
if (UNDEF_P(val)) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* Invoke method without block */
|
|
DEFINE_INSN
|
|
opt_send_without_block
|
|
(CALL_DATA cd)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
// attr bool handles_sp = true;
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
VALUE bh = VM_BLOCK_HANDLER_NONE;
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
|
|
JIT_EXEC(ec, val);
|
|
|
|
if (UNDEF_P(val)) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* Jump if "new" method has been defined by user */
|
|
DEFINE_INSN
|
|
opt_new
|
|
(CALL_DATA cd, OFFSET dst)
|
|
()
|
|
()
|
|
// attr bool leaf = false;
|
|
{
|
|
VALUE argc = vm_ci_argc(cd->ci);
|
|
VALUE val = TOPN(argc);
|
|
|
|
// The bookkeeping slot should be empty.
|
|
RUBY_ASSERT(TOPN(argc + 1) == Qnil);
|
|
|
|
if (vm_method_cfunc_is(GET_ISEQ(), cd, val, rb_class_new_instance_pass_kw)) {
|
|
RB_DEBUG_COUNTER_INC(opt_new_hit);
|
|
val = rb_obj_alloc(val);
|
|
TOPN(argc) = val;
|
|
TOPN(argc + 1) = val;
|
|
}
|
|
else {
|
|
RB_DEBUG_COUNTER_INC(opt_new_miss);
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/* Convert object to string using to_s or equivalent. */
|
|
DEFINE_INSN
|
|
objtostring
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
// attr bool leaf = false;
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_objtostring(GET_ISEQ(), recv, cd);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_ary_freeze
|
|
(VALUE ary, CALL_DATA cd)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_ary_freeze(ary, BOP_FREEZE, idFreeze);
|
|
|
|
if (UNDEF_P(val)) {
|
|
RUBY_DTRACE_CREATE_HOOK(ARRAY, RARRAY_LEN(ary));
|
|
PUSH(rb_ary_resurrect(ary));
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_hash_freeze
|
|
(VALUE hash, CALL_DATA cd)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_hash_freeze(hash, BOP_FREEZE, idFreeze);
|
|
|
|
if (UNDEF_P(val)) {
|
|
RUBY_DTRACE_CREATE_HOOK(HASH, RHASH_SIZE(hash) << 1);
|
|
PUSH(rb_hash_resurrect(hash));
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_str_freeze
|
|
(VALUE str, CALL_DATA cd)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_str_freeze(str, BOP_FREEZE, idFreeze);
|
|
|
|
if (UNDEF_P(val)) {
|
|
PUSH(rb_str_resurrect(str));
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized nil? */
|
|
DEFINE_INSN
|
|
opt_nil_p
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_nil_p(GET_ISEQ(), cd, recv);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_str_uminus
|
|
(VALUE str, CALL_DATA cd)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_str_freeze(str, BOP_UMINUS, idUMinus);
|
|
|
|
if (UNDEF_P(val)) {
|
|
PUSH(rb_str_resurrect(str));
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_duparray_send
|
|
(VALUE ary, ID method, rb_num_t argc)
|
|
(...)
|
|
(VALUE val)
|
|
/* This instruction typically has no funcalls. But it may compare array
|
|
* contents to each other which may call methods when necessary.
|
|
* No way to detect such method calls beforehand.
|
|
* We must mark it as not leaf. */
|
|
// attr bool leaf = false; /* has rb_funcall() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)argc;
|
|
// attr rb_snum_t comptime_sp_inc = 1 - (rb_snum_t)argc;
|
|
{
|
|
switch (method) {
|
|
case idIncludeP:
|
|
val = vm_opt_duparray_include_p(ec, ary, TOPN(0));
|
|
break;
|
|
default:
|
|
rb_bug("unreachable");
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_newarray_send
|
|
(rb_num_t num, rb_num_t method)
|
|
(...)
|
|
(VALUE val)
|
|
/* This instruction typically has no funcalls. But it compares array
|
|
* contents each other by nature. That part could call methods when
|
|
* necessary. No way to detect such method calls beforehand. We
|
|
* cannot but mark it being not leaf. */
|
|
// attr bool leaf = false; /* has rb_funcall() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
// attr rb_snum_t comptime_sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
switch (method) {
|
|
case VM_OPT_NEWARRAY_SEND_HASH:
|
|
val = vm_opt_newarray_hash(ec, num, STACK_ADDR_FROM_TOP(num));
|
|
break;
|
|
case VM_OPT_NEWARRAY_SEND_MIN:
|
|
val = vm_opt_newarray_min(ec, num, STACK_ADDR_FROM_TOP(num));
|
|
break;
|
|
case VM_OPT_NEWARRAY_SEND_MAX:
|
|
val = vm_opt_newarray_max(ec, num, STACK_ADDR_FROM_TOP(num));
|
|
break;
|
|
case VM_OPT_NEWARRAY_SEND_INCLUDE_P:
|
|
val = vm_opt_newarray_include_p(ec, (long)num-1, STACK_ADDR_FROM_TOP(num), TOPN(0));
|
|
break;
|
|
case VM_OPT_NEWARRAY_SEND_PACK:
|
|
val = vm_opt_newarray_pack_buffer(ec, (long)num-1, STACK_ADDR_FROM_TOP(num), TOPN(0), Qundef);
|
|
break;
|
|
case VM_OPT_NEWARRAY_SEND_PACK_BUFFER:
|
|
val = vm_opt_newarray_pack_buffer(ec, (long)num-2, STACK_ADDR_FROM_TOP(num), TOPN(1), TOPN(0));
|
|
break;
|
|
default:
|
|
rb_bug("unreachable");
|
|
}
|
|
}
|
|
|
|
/* super(args) # args.size => num */
|
|
DEFINE_INSN
|
|
invokesuper
|
|
(CALL_DATA cd, ISEQ blockiseq)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, true);
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_super);
|
|
JIT_EXEC(ec, val);
|
|
|
|
if (UNDEF_P(val)) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* super(args) # args.size => num */
|
|
DEFINE_INSN
|
|
invokesuperforward
|
|
(CALL_DATA cd, ISEQ blockiseq)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
struct rb_forwarding_call_data adjusted_cd;
|
|
struct rb_callinfo adjusted_ci;
|
|
|
|
VALUE bh = vm_caller_setup_fwd_args(ec, GET_CFP(), cd, blockiseq, 1, &adjusted_cd, &adjusted_ci);
|
|
|
|
val = vm_sendish(ec, GET_CFP(), &adjusted_cd.cd, bh, mexp_search_super);
|
|
JIT_EXEC(ec, val);
|
|
|
|
if (cd->cc != adjusted_cd.cd.cc && vm_cc_markable(adjusted_cd.cd.cc)) {
|
|
RB_OBJ_WRITE(GET_ISEQ(), &cd->cc, adjusted_cd.cd.cc);
|
|
}
|
|
|
|
if (UNDEF_P(val)) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* yield(args) */
|
|
DEFINE_INSN
|
|
invokeblock
|
|
(CALL_DATA cd)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool handles_sp = true;
|
|
// attr rb_snum_t sp_inc = sp_inc_of_invokeblock(cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_invokeblock(ci);
|
|
{
|
|
VALUE bh = VM_BLOCK_HANDLER_NONE;
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_invokeblock);
|
|
JIT_EXEC(ec, val);
|
|
|
|
if (UNDEF_P(val)) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* return from this scope. */
|
|
DEFINE_INSN
|
|
leave
|
|
()
|
|
(VALUE val)
|
|
(VALUE val)
|
|
/* This is super surprising but when leaving from a frame, we check
|
|
* for interrupts. If any, that should be executed on top of the
|
|
* current execution context. This is a method call. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
// attr bool handles_sp = true;
|
|
{
|
|
if (OPT_CHECKED_RUN) {
|
|
const VALUE *const bp = vm_base_ptr(GET_CFP());
|
|
if (GET_SP() != bp) {
|
|
vm_stack_consistency_error(ec, GET_CFP(), bp);
|
|
}
|
|
}
|
|
|
|
if (vm_pop_frame(ec, GET_CFP(), GET_EP())) {
|
|
#if OPT_CALL_THREADED_CODE
|
|
rb_ec_thread_ptr(ec)->retval = val;
|
|
return 0;
|
|
#else
|
|
return val;
|
|
#endif
|
|
}
|
|
else {
|
|
RESTORE_REGS();
|
|
}
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 3: exception */
|
|
/**********************************************************/
|
|
|
|
/* longjump */
|
|
DEFINE_INSN
|
|
throw
|
|
(rb_num_t throw_state)
|
|
(VALUE throwobj)
|
|
(VALUE val)
|
|
/* Same discussion as leave. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
val = vm_throw(ec, GET_CFP(), throw_state, throwobj);
|
|
THROW_EXCEPTION(val);
|
|
/* unreachable */
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 4: local jump */
|
|
/**********************************************************/
|
|
|
|
/* set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
jump
|
|
(OFFSET dst)
|
|
()
|
|
()
|
|
/* Same discussion as leave. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
|
|
/* if val is not false or nil, set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
branchif
|
|
(OFFSET dst)
|
|
(VALUE val)
|
|
()
|
|
/* Same discussion as jump. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
if (RTEST(val)) {
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/* if val is false or nil, set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
branchunless
|
|
(OFFSET dst)
|
|
(VALUE val)
|
|
()
|
|
/* Same discussion as jump. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
if (!RTEST(val)) {
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/* if val is nil, set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
branchnil
|
|
(OFFSET dst)
|
|
(VALUE val)
|
|
()
|
|
/* Same discussion as jump. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
if (NIL_P(val)) {
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* for optimize */
|
|
/**********************************************************/
|
|
|
|
/* run iseq only once */
|
|
DEFINE_INSN
|
|
once
|
|
(ISEQ iseq, ISE ise)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_once_dispatch(ec, iseq, ise);
|
|
}
|
|
|
|
/* case dispatcher, jump by table if possible */
|
|
DEFINE_INSN
|
|
opt_case_dispatch
|
|
(CDHASH hash, OFFSET else_offset)
|
|
(..., VALUE key)
|
|
()
|
|
// attr rb_snum_t sp_inc = -1;
|
|
{
|
|
OFFSET dst = vm_case_dispatch(hash, else_offset, key);
|
|
|
|
if (dst) {
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/** simple functions */
|
|
|
|
/* optimized X+Y. */
|
|
DEFINE_INSN
|
|
opt_plus
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_plus(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X-Y. */
|
|
DEFINE_INSN
|
|
opt_minus
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_minus(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X*Y. */
|
|
DEFINE_INSN
|
|
opt_mult
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_mult(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X/Y. */
|
|
DEFINE_INSN
|
|
opt_div
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* In case of division by zero, it raises. Thus
|
|
* ZeroDivisionError#initialize is called. */
|
|
// attr bool leaf = false;
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_div(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X%Y. */
|
|
DEFINE_INSN
|
|
opt_mod
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* Same discussion as opt_div. */
|
|
// attr bool leaf = false;
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_mod(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X==Y. */
|
|
DEFINE_INSN
|
|
opt_eq
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = opt_equality(GET_ISEQ(), recv, obj, cd);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X!=Y. */
|
|
DEFINE_INSN
|
|
opt_neq
|
|
(CALL_DATA cd_eq, CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_neq(GET_ISEQ(), cd, cd_eq, recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X<Y. */
|
|
DEFINE_INSN
|
|
opt_lt
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_lt(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X<=Y. */
|
|
DEFINE_INSN
|
|
opt_le
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_le(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X>Y. */
|
|
DEFINE_INSN
|
|
opt_gt
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_gt(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X>=Y. */
|
|
DEFINE_INSN
|
|
opt_ge
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_ge(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* << */
|
|
DEFINE_INSN
|
|
opt_ltlt
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* This instruction can append an integer, as a codepoint, into a
|
|
* string. Then what happens if that codepoint does not exist in the
|
|
* string's encoding? Of course an exception. That's not a leaf. */
|
|
// attr bool leaf = false; /* has "invalid codepoint" exception */
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_ltlt(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X&Y. */
|
|
DEFINE_INSN
|
|
opt_and
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_and(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X|Y. */
|
|
DEFINE_INSN
|
|
opt_or
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_or(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* [] */
|
|
DEFINE_INSN
|
|
opt_aref
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* This is complicated. In case of hash, vm_opt_aref() resorts to
|
|
* rb_hash_aref(). If `recv` has no `obj`, this function then yields
|
|
* default_proc. This is a method call. So opt_aref is
|
|
* (surprisingly) not leaf. */
|
|
// attr bool leaf = false; /* has rb_funcall() */ /* calls #yield */
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_aref(recv, obj);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* recv[obj] = set */
|
|
DEFINE_INSN
|
|
opt_aset
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj, VALUE set)
|
|
(VALUE val)
|
|
/* This is another story than opt_aref. When vm_opt_aset() resorts
|
|
* to rb_hash_aset(), which should call #hash for `obj`. */
|
|
// attr bool leaf = false; /* has rb_funcall() */ /* calls #hash */
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_aset(recv, obj, set);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized length */
|
|
DEFINE_INSN
|
|
opt_length
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_length(recv, BOP_LENGTH);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized size */
|
|
DEFINE_INSN
|
|
opt_size
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_length(recv, BOP_SIZE);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized empty? */
|
|
DEFINE_INSN
|
|
opt_empty_p
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_empty_p(recv);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized succ */
|
|
DEFINE_INSN
|
|
opt_succ
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_succ(recv);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized not */
|
|
DEFINE_INSN
|
|
opt_not
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_not(GET_ISEQ(), cd, recv);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized regexp match 2 */
|
|
DEFINE_INSN
|
|
opt_regexpmatch2
|
|
(CALL_DATA cd)
|
|
(VALUE obj2, VALUE obj1)
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* match_at() has rb_thread_check_ints() */
|
|
// attr bool zjit_profile = true;
|
|
{
|
|
val = vm_opt_regexpmatch2(obj2, obj1);
|
|
|
|
if (UNDEF_P(val)) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* call specific function with args */
|
|
DEFINE_INSN
|
|
invokebuiltin
|
|
(RB_BUILTIN bf)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* anything can happen inside */
|
|
// attr rb_snum_t sp_inc = 1 - bf->argc;
|
|
{
|
|
val = vm_invoke_builtin(ec, reg_cfp, bf, STACK_ADDR_FROM_TOP(bf->argc));
|
|
}
|
|
|
|
/* call specific function with args (same parameters) */
|
|
DEFINE_INSN
|
|
opt_invokebuiltin_delegate
|
|
(RB_BUILTIN bf, rb_num_t index)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* anything can happen inside */
|
|
{
|
|
val = vm_invoke_builtin_delegate(ec, reg_cfp, bf, (unsigned int)index);
|
|
}
|
|
|
|
/* call specific function with args (same parameters) and leave */
|
|
DEFINE_INSN
|
|
opt_invokebuiltin_delegate_leave
|
|
(RB_BUILTIN bf, rb_num_t index)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* anything can happen inside */
|
|
{
|
|
val = vm_invoke_builtin_delegate(ec, reg_cfp, bf, (unsigned int)index);
|
|
|
|
/* leave fastpath */
|
|
/* TracePoint/return fallbacks this insn to opt_invokebuiltin_delegate */
|
|
if (vm_pop_frame(ec, GET_CFP(), GET_EP())) {
|
|
#if OPT_CALL_THREADED_CODE
|
|
rb_ec_thread_ptr(ec)->retval = val;
|
|
return 0;
|
|
#else
|
|
return val;
|
|
#endif
|
|
}
|
|
else {
|
|
RESTORE_REGS();
|
|
}
|
|
}
|
|
|
|
/* BLT */
|
|
DEFINE_INSN_IF(SUPPORT_JOKE)
|
|
bitblt
|
|
()
|
|
()
|
|
(VALUE ret)
|
|
{
|
|
ret = rb_str_new2("a bit of bacon, lettuce and tomato");
|
|
}
|
|
|
|
/* The Answer to Life, the Universe, and Everything */
|
|
DEFINE_INSN_IF(SUPPORT_JOKE)
|
|
answer
|
|
()
|
|
()
|
|
(VALUE ret)
|
|
{
|
|
ret = INT2FIX(42);
|
|
}
|