Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement splat for cfuncs. Split exit exit cases to better capture where we are exiting #6929

Merged
merged 1 commit into from Jan 19, 2023

Conversation

jimmyhmiller
Copy link
Contributor

(Putting out draft PR but would rather hold off on this until after 3.2 release)

@jimmyhmiller jimmyhmiller force-pushed the yjit-cfunc-splat branch 3 times, most recently from 8eb27f3 to a6ea33b Compare Dec 16, 2022
@jeremyevans
Copy link
Contributor

Can we please make sure that before this is committed, it works with the changes in #6816 (or if it won't be affected by the change, that it handles the same issue)? That won't be committed before 3.2, but hopefully it will be committed later this month or next month.

@jimmyhmiller
Copy link
Contributor Author

@jeremyevans This PR doesn't deal with cfuncs that take varargs. But once I get there, I'll make sure it is compatible. Thanks for the heads up

@jimmyhmiller jimmyhmiller marked this pull request as ready for review Jan 9, 2023
@matzbot matzbot requested a review from a team Jan 9, 2023
@noahgibbs
Copy link
Contributor

Yay for additional tracking of which func types can't handle splats!

@jimmyhmiller
Copy link
Contributor Author

Will take a look at this failure in CI this afternoon

yjit/src/codegen.rs Outdated Show resolved Hide resolved
@jimmyhmiller jimmyhmiller force-pushed the yjit-cfunc-splat branch 2 times, most recently from 367c9ec to fde1926 Compare Jan 10, 2023
@jimmyhmiller
Copy link
Contributor Author

jimmyhmiller commented Jan 10, 2023

I looked into the original failure and wasn't able to recreate it. I can't think of a way of causing the failure given the code I have. Now I'm just getting http errors all the time. So CI is passing for real running things, but github actions is having issues.

(I ran the test that failed in a loop for hours)

@jimmyhmiller
Copy link
Contributor Author

I take it back. The error came back. Will keep trying to track it down :(

@jimmyhmiller jimmyhmiller marked this pull request as draft Jan 10, 2023
@XrXr
Copy link
Member

XrXr commented Jan 11, 2023

Ugh, I just noticed that we have missed a ruby2_keywords corner case. I knew something might be off when I saw push_splat_args() doesn't have any checking for the flag on the last argument in the array, but I didn't say anything because I couldn't come up with an example. I should've said something, my bad.

The PR uses push_splat_args() too, so now it repros for both C and ISEQ calls:

# YJIT splat ruby2_keywords edge case

# ISEQ call
def foo(args)
  wrap(*args)
rescue ArgumentError => e
  e.message
end

def wrap(a)
  [a]
end

# C call
def bar(args)
  Array(*args)
rescue ArgumentError => e
  e.message
end

p bar([Hash.ruby2_keywords_hash({})])
p bar([Hash.ruby2_keywords_hash({})])

p foo([Hash.ruby2_keywords_hash({})])
p foo([Hash.ruby2_keywords_hash({})])

__END__
$ ./miniruby --yjit-call-threshold=1 test.rb
[]
[]
[{}]
[{}]

$ ./miniruby test.rb
"wrong number of arguments (given 0, expected 1)"
"wrong number of arguments (given 0, expected 1)"
"wrong number of arguments (given 0, expected 1)"
"wrong number of arguments (given 0, expected 1)"

@jimmyhmiller
Copy link
Contributor Author

There is a second way to do ruby2keywords?? Okay. I'll go look into that.

@jimmyhmiller
Copy link
Contributor Author

Once I get that issue dealt with, I will clean up this PR and rebase

@XrXr
Copy link
Member

XrXr commented Jan 11, 2023

There is a second way to do ruby2keywords?

It's not really a second way since Hash.ruby2_keywords_hash is designed for testing and getting the product out of possible usages of ruby2_keyword. We can get an empty keywords hash with just ruby2_keyword by doing:

def empty_ruby2_keywords_hash = mark_hash(dummy: 0)

ruby2_keywords def mark_hash(*args) = extract(*args)

ruby2_keywords def extract(*args) = args.last.clear

ruby2_keywords is really only used for methods that delegate, and I think you'd need to combine it with mutating the keywords hash to actually get a marked empty hash, so it seems hard to run into this issue in practice in the released version. 🤞

@jimmyhmiller
Copy link
Contributor Author

@XrXr Got some clean up todo. But I think I got the ruby2keywords stuff working. Took me longer than I hoped because of register allocation. Which means it is not as efficient as it should be. But quick benchmarks don't show a big impact.

Mind checking it out and seeing if I missed anything?

@XrXr
Copy link
Member

XrXr commented Jan 12, 2023

The new guard looks good

@jimmyhmiller jimmyhmiller marked this pull request as ready for review Jan 17, 2023
@matzbot matzbot requested a review from a team Jan 17, 2023
@jimmyhmiller
Copy link
Contributor Author

Fixing the conflict and rebasing. Other than that, all good for review.

@jimmyhmiller jimmyhmiller force-pushed the yjit-cfunc-splat branch 3 times, most recently from 5384a42 to 56c8ed8 Compare Jan 17, 2023
bootstraptest/test_yjit.rb Outdated Show resolved Hide resolved
yjit/bindgen/src/main.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
@maximecb
Copy link
Contributor

I'm again scared by the complexity of our method call handling, but I don't see anything wrong with your changes. Would like Alan or Kokubun to take a final look at it if possible.

@jimmyhmiller can you include stats/perf from railsbench/liquid before and after?

yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
@jimmyhmiller
Copy link
Contributor Author

@maximecb
Not a whole lot changed in terms of stats. Which isn't super surprising and there are lots of other reasons to side exit. The next few PRs will try to take care of some of those.

Rails Bench Before
ruby 3.3.0dev (2023-01-17T13:01:19Z upstream/master df6b72b8ff) +YJIT dev [arm64-darwin22]
Command: bundle check 2> /dev/null || bundle install
The Gemfile's dependencies are satisfied
Command: bin/rails db:migrate db:seed
Using 100 posts in the database
***YJIT: Printing YJIT statistics on exit***
method call exit reasons: 
               iseq_has_rest     107985 (31.0%)
                 iseq_zsuper      46081 (13.2%)
         args_splat_non_iseq      36983 (10.6%)
                   block_arg      34048 ( 9.8%)
            iseq_arity_error      28708 ( 8.2%)
         iseq_ruby2_keywords      24994 ( 7.2%)
              iseq_has_no_kw      15971 ( 4.6%)
         iseq_splat_with_opt      15448 ( 4.4%)
            args_splat_cfunc      13934 ( 4.0%)
                    kw_splat      12995 ( 3.7%)
    iseq_missing_optional_kw       4054 ( 1.2%)
             iseq_has_kwrest       3294 ( 0.9%)
               iseq_has_post       1987 ( 0.6%)
              refined_method        890 ( 0.3%)
       cfunc_ruby_array_varg        649 ( 0.2%)
                    keywords        102 ( 0.0%)
                 send_getter         12 ( 0.0%)
               zsuper_method          9 ( 0.0%)
           bmethod_block_arg          2 ( 0.0%)
             ivar_set_method          2 ( 0.0%)
invokeblock exit reasons: 
               ifunc       5798 (56.3%)
                proc       2341 (22.7%)
     iseq_arg0_splat       2000 (19.4%)
    iseq_tag_changed        100 ( 1.0%)
              symbol         68 ( 0.7%)
invokesuper exit reasons: 
         block       4844 (98.2%)
    me_changed         87 ( 1.8%)
leave exit reasons: 
        interp_return    1846823 (97.7%)
    start_pc_non_zero      43887 ( 2.3%)
         se_interrupt         30 ( 0.0%)
getblockparamproxy exit reasons: 
    block_param_modified          3 (100.0%)
getinstancevariable exit reasons:
    megamorphic      13685 (100.0%)
setinstancevariable exit reasons:
    (all relevant counters are zero)
opt_aref exit reasons: 
    (all relevant counters are zero)
expandarray exit reasons: 
            splat       9973 (99.8%)
    rhs_too_small         22 ( 0.2%)
opt_getinlinecache exit reasons: 
    miss         11 (100.0%)
invalidation reasons: 
       constant_ic_fill       2001 (59.2%)
          method_lookup       1044 (30.9%)
    constant_state_bump        335 ( 9.9%)
bindings_allocations:         178
bindings_set:                   0
compiled_iseq_count:         9278
compiled_block_count:       51901
compiled_branch_count:      82206
block_next_count:           15149
defer_count:                16322
freed_iseq_count:            4048
invalidation_count:          3380
constant_state_bumps:           0
inline_code_size:         8844176
outlined_code_size:       8843368
freed_code_size:                0
code_region_size:        17743872
yjit_alloc_size:         99750214
live_page_count:             1083
freed_page_count:               0
code_gc_count:                  0
num_gc_obj_refs:            41292
object_shape_count:          2396
side_exit_count:           522732
total_exit_count:         2369555
total_insns_count:       89870478
vm_insns_count:           8733450
yjit_insns_count:        81659760
ratio_in_yjit:              90.3%
avg_len_in_yjit:             34.2
Top-20 most frequent exit ops (100.0% of exits):
    opt_send_without_block:     155291 (29.7%)
               invokesuper:     117050 (22.4%)
                      send:     104055 (19.9%)
      opt_getconstant_path:      53734 (10.3%)
               invokeblock:      36991 (7.1%)
                  opt_aref:      14087 (2.7%)
                     throw:      10587 (2.0%)
               expandarray:       9995 (1.9%)
             setlocal_WC_0:       8358 (1.6%)
                    opt_eq:       5008 (1.0%)
       getinstancevariable:       3485 (0.7%)
        getblockparamproxy:       2271 (0.4%)
               objtostring:        448 (0.1%)
          putspecialobject:        386 (0.1%)
                 opt_nil_p:        327 (0.1%)
             definesmethod:        240 (0.0%)
                checkmatch:        204 (0.0%)
                      once:        104 (0.0%)
               opt_empty_p:         31 (0.0%)
                     leave:         31 (0.0%)
Rails Bench After
ruby 3.3.0dev (2023-01-18T05:41:09Z yjit-cfunc-splat 01941cfb9c) +YJIT dev [arm64-darwin22]
Command: bundle check 2> /dev/null || bundle install
The Gemfile's dependencies are satisfied
Command: bin/rails db:migrate db:seed
Using 100 posts in the database
***YJIT: Printing YJIT statistics on exit***
method call exit reasons: 
                      iseq_has_rest     107987 (31.0%)
                          block_arg      64598 (18.6%)
                        iseq_zsuper      46081 (13.2%)
                   iseq_arity_error      28708 ( 8.2%)
                iseq_ruby2_keywords      24995 ( 7.2%)
          args_splat_cfunc_var_args      16250 ( 4.7%)
                     iseq_has_no_kw      15971 ( 4.6%)
                iseq_splat_with_opt      15448 ( 4.4%)
                           kw_splat      12994 ( 3.7%)
                    iseq_has_kwrest       5294 ( 1.5%)
           iseq_missing_optional_kw       4054 ( 1.2%)
             args_splat_cfunc_zuper       2002 ( 0.6%)
                      iseq_has_post       1987 ( 0.6%)
                     refined_method        891 ( 0.3%)
              cfunc_ruby_array_varg        649 ( 0.2%)
                           keywords        102 ( 0.0%)
    args_splat_cfunc_ruby2_keywords         62 ( 0.0%)
                    args_splat_ivar         49 ( 0.0%)
                        send_getter         12 ( 0.0%)
                      zsuper_method          9 ( 0.0%)
               args_splat_optimized          5 ( 0.0%)
                    ivar_set_method          2 ( 0.0%)
                  bmethod_block_arg          2 ( 0.0%)
invokeblock exit reasons: 
               ifunc       5798 (56.3%)
                proc       2341 (22.7%)
     iseq_arg0_splat       2000 (19.4%)
    iseq_tag_changed        100 ( 1.0%)
              symbol         68 ( 0.7%)
invokesuper exit reasons: 
         block       4844 (98.2%)
    me_changed         87 ( 1.8%)
leave exit reasons: 
        interp_return    1846785 (97.7%)
    start_pc_non_zero      43885 ( 2.3%)
         se_interrupt         17 ( 0.0%)
getblockparamproxy exit reasons: 
    block_param_modified          3 (100.0%)
getinstancevariable exit reasons:
    megamorphic      13685 (100.0%)
setinstancevariable exit reasons:
    (all relevant counters are zero)
opt_aref exit reasons: 
    (all relevant counters are zero)
expandarray exit reasons: 
            splat       9974 (99.8%)
    rhs_too_small         22 ( 0.2%)
opt_getinlinecache exit reasons: 
    miss         11 (100.0%)
invalidation reasons: 
       constant_ic_fill       2001 (59.1%)
          method_lookup       1046 (30.9%)
    constant_state_bump        339 (10.0%)
bindings_allocations:         178
bindings_set:                   0
compiled_iseq_count:         9278
compiled_block_count:       51918
compiled_branch_count:      82232
block_next_count:           15151
defer_count:                16331
freed_iseq_count:            4048
invalidation_count:          3386
constant_state_bumps:           0
inline_code_size:         8847540
outlined_code_size:       8844964
freed_code_size:                0
code_region_size:        17743872
yjit_alloc_size:         99765374
live_page_count:             1083
freed_page_count:               0
code_gc_count:                  0
num_gc_obj_refs:            41311
object_shape_count:          2396
side_exit_count:           522713
total_exit_count:         2369498
total_insns_count:       89860430
vm_insns_count:           8733116
yjit_insns_count:        81650027
ratio_in_yjit:              90.3%
avg_len_in_yjit:             34.2
Top-20 most frequent exit ops (100.0% of exits):
    opt_send_without_block:     155292 (29.7%)
               invokesuper:     117052 (22.4%)
                      send:     104049 (19.9%)
      opt_getconstant_path:      53733 (10.3%)
               invokeblock:      36991 (7.1%)
                  opt_aref:      14087 (2.7%)
                     throw:      10587 (2.0%)
               expandarray:       9996 (1.9%)
             setlocal_WC_0:       8358 (1.6%)
                    opt_eq:       5008 (1.0%)
       getinstancevariable:       3485 (0.7%)
        getblockparamproxy:       2271 (0.4%)
               objtostring:        448 (0.1%)
          putspecialobject:        384 (0.1%)
                 opt_nil_p:        327 (0.1%)
             definesmethod:        240 (0.0%)
                checkmatch:        204 (0.0%)
                      once:        104 (0.0%)
               opt_empty_p:         31 (0.0%)
             setblockparam:         25 (0.0%)
Liquid Render Before
ruby 3.3.0dev (2023-01-17T13:01:19Z upstream/master df6b72b8ff) +YJIT dev [arm64-darwin22]
***YJIT: Printing YJIT statistics on exit***
method call exit reasons: 
               iseq_has_rest      41227 (66.1%)
         args_splat_non_iseq      18989 (30.4%)
                   block_arg       1320 ( 2.1%)
               zsuper_method        792 ( 1.3%)
    iseq_missing_optional_kw         39 ( 0.1%)
       cfunc_ruby_array_varg         39 ( 0.1%)
             ivar_set_method          1 ( 0.0%)
invokeblock exit reasons: 
                proc         89 (95.7%)
    iseq_tag_changed          4 ( 4.3%)
invokesuper exit reasons: 
    block          5 (100.0%)
leave exit reasons: 
        interp_return     287893 (100.0%)
    start_pc_non_zero        105 ( 0.0%)
         se_interrupt         17 ( 0.0%)
getblockparamproxy exit reasons: 
    (all relevant counters are zero)
getinstancevariable exit reasons:
    megamorphic          8 (100.0%)
setinstancevariable exit reasons:
    (all relevant counters are zero)
opt_aref exit reasons: 
    (all relevant counters are zero)
expandarray exit reasons: 
    (all relevant counters are zero)
opt_getinlinecache exit reasons: 
    (all relevant counters are zero)
invalidation reasons: 
       constant_ic_fill        149 (85.1%)
    constant_state_bump         17 ( 9.7%)
          method_lookup          9 ( 5.1%)
bindings_allocations:          77
bindings_set:                   0
compiled_iseq_count:          694
compiled_block_count:        5878
compiled_branch_count:       9764
block_next_count:            2015
defer_count:                 1982
freed_iseq_count:             181
invalidation_count:           175
constant_state_bumps:           0
inline_code_size:          922204
outlined_code_size:        919664
freed_code_size:                0
code_region_size:         1851392
yjit_alloc_size:         12321441
live_page_count:              113
freed_page_count:               0
code_gc_count:                  0
num_gc_obj_refs:             3885
object_shape_count:           538
side_exit_count:            84920
total_exit_count:          372813
total_insns_count:       19297282
vm_insns_count:           2599567
yjit_insns_count:        16782635
ratio_in_yjit:              86.5%
avg_len_in_yjit:             44.8
Top-15 most frequent exit ops (100.0% of exits):
    opt_send_without_block:      61360 (72.3%)
                     throw:      21516 (25.3%)
                      send:       1321 (1.6%)
      opt_getconstant_path:        369 (0.4%)
               invokesuper:        125 (0.1%)
               invokeblock:         93 (0.1%)
                    opt_eq:         87 (0.1%)
                     leave:         17 (0.0%)
          putspecialobject:         12 (0.0%)
                      once:         12 (0.0%)
                  opt_ltlt:          3 (0.0%)
                   opt_div:          2 (0.0%)
                  opt_aref:          1 (0.0%)
                  opt_aset:          1 (0.0%)
               opt_empty_p:          1 (0.0%)
Liquid Render After
ruby 3.3.0dev (2023-01-18T05:41:09Z yjit-cfunc-splat 01941cfb9c) +YJIT dev [arm64-darwin22]
***YJIT: Printing YJIT statistics on exit***
method call exit reasons: 
                iseq_has_rest      41228 (66.1%)
         args_splat_optimized      18975 (30.4%)
                    block_arg       1320 ( 2.1%)
                zsuper_method        792 ( 1.3%)
        cfunc_ruby_array_varg         39 ( 0.1%)
     iseq_missing_optional_kw         39 ( 0.1%)
    args_splat_cfunc_var_args         11 ( 0.0%)
              ivar_set_method          1 ( 0.0%)
invokeblock exit reasons: 
                proc         89 (95.7%)
    iseq_tag_changed          4 ( 4.3%)
invokesuper exit reasons: 
    block          5 (100.0%)
leave exit reasons: 
        interp_return     287908 (100.0%)
    start_pc_non_zero        105 ( 0.0%)
         se_interrupt         10 ( 0.0%)
getblockparamproxy exit reasons: 
    (all relevant counters are zero)
getinstancevariable exit reasons:
    megamorphic          8 (100.0%)
setinstancevariable exit reasons:
    (all relevant counters are zero)
opt_aref exit reasons: 
    (all relevant counters are zero)
expandarray exit reasons: 
    (all relevant counters are zero)
opt_getinlinecache exit reasons: 
    (all relevant counters are zero)
invalidation reasons: 
       constant_ic_fill        149 (85.1%)
    constant_state_bump         17 ( 9.7%)
          method_lookup          9 ( 5.1%)
bindings_allocations:          77
bindings_set:                   0
compiled_iseq_count:          694
compiled_block_count:        5872
compiled_branch_count:       9750
block_next_count:            2013
defer_count:                 1980
freed_iseq_count:             181
invalidation_count:           175
constant_state_bumps:           0
inline_code_size:          920960
outlined_code_size:        918844
freed_code_size:                0
code_region_size:         1851392
yjit_alloc_size:         12308283
live_page_count:              113
freed_page_count:               0
code_gc_count:                  0
num_gc_obj_refs:             3880
object_shape_count:           538
side_exit_count:            84903
total_exit_count:          372811
total_insns_count:       19297262
vm_insns_count:           2599586
yjit_insns_count:        16782579
ratio_in_yjit:              86.5%
avg_len_in_yjit:             44.8
Top-14 most frequent exit ops (100.0% of exits):
    opt_send_without_block:      61350 (72.3%)
                     throw:      21516 (25.3%)
                      send:       1320 (1.6%)
      opt_getconstant_path:        369 (0.4%)
               invokesuper:        125 (0.1%)
               invokeblock:         93 (0.1%)
                    opt_eq:         87 (0.1%)
          putspecialobject:         12 (0.0%)
                      once:         12 (0.0%)
                     leave:         10 (0.0%)
                  opt_ltlt:          5 (0.0%)
                  opt_aref:          2 (0.0%)
                   opt_neq:          1 (0.0%)
                   opt_div:          1 (0.0%)

yjit/bindgen/src/main.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
Copy link
Member

@XrXr XrXr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good. Just a few nitpicks.
By the way, can you line wrap at 72 in your commit message? Not sure how to configure VSCode to do that. Maybe @k0kubun knows. Also, we like prefixing YJIT commit titles with YJIT:. I also like to golf commit titles under 60, but you don't have to do that.

yjit/src/codegen.rs Show resolved Hide resolved
yjit/src/codegen.rs Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
bootstraptest/test_yjit.rb Outdated Show resolved Hide resolved
bootstraptest/test_yjit.rb Outdated Show resolved Hide resolved
yjit/src/codegen.rs Outdated Show resolved Hide resolved
@jimmyhmiller jimmyhmiller force-pushed the yjit-cfunc-splat branch 2 times, most recently from 051cdd2 to 47c77a4 Compare Jan 19, 2023
@jimmyhmiller
Copy link
Contributor Author

@XrXr Fixed the commit message.

This also implements a new check for ruby2keywords as the last
argument of a splat. This does mean that we generate more code, but in
actual benchmarks where we gained speed from this (binarytrees) I
don't see any significant slow down. I did have to struggle here with
the register allocator to find code that didn't allocate too many
registers. It's a bit hard when everything is implicit. But I think I
got to the minimal amount of copying and stuff given our current
allocation strategy.
@maximecb maximecb merged commit 762a3d8 into ruby:master Jan 19, 2023
93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
6 participants