summaryrefslogtreecommitdiff
path: root/test/ruby/test_yjit.rb
AgeCommit message (Collapse)Author
11 hoursYJIT: Add crashing test for --yjit-dump-insnsAlan Wu
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
6 daysYJIT: Add frozen guard for struct aset (#15835)Max Bernstein
We used to just skip this check (oops), but we should not allow modifying frozen objects.
6 daysUse `assert_ruby_status` if no assertionNobuyoshi Nakada
2025-12-15YJIT: Bail out if proc would be stored above stack topRandy Stauner
Fixes [Bug #21266].
2025-08-26Remove `opt_aref_with` and `opt_aset_with`Aaron Patterson
When these instructions were introduced it was common to read from a hash with mutable string literals. However, these days, I think these instructions are fairly rare. I tested this with the lobsters benchmark, and saw no difference in speed. In order to be sure, I tracked down every use of this instruction in the lobsters benchmark, and there were only 4 places where it was used. Additionally, this patch fixes a case where "chilled strings" should emit a warning but they don't. ```ruby class Foo def self.[](x)= x.gsub!(/hello/, "hi") end Foo["hello world"] ``` Removing these instructions shows this warning: ``` > ./miniruby -vw test.rb ruby 3.5.0dev (2025-08-25T21:36:50Z rm-opt_aref_with dca08e286c) +PRISM [arm64-darwin24] test.rb:2: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information) ``` [Feature #21553]
2025-05-16Prevent enabling yjit when zjit enabled (GH-13358)Daniel Colson
`ruby --yjit --zjit` already warns and exits, but it was still possible to enable both with `ruby --zjit -e 'RubyVM:YJIT.enable`. This commit prevents that with a warning and an early return. (We could also exit, but that seems a bit unfriendly once we're already running the program.) Co-authored-by: ywenc <ywenc@github.com> Notes: Merged: https://github.com/ruby/ruby/pull/13358 Merged-By: XrXr
2025-03-20Remove leading `nop` from block when we don't need itAaron Patterson
Blocks insert a leading `nop` instruction in order to execute a "block call" tracepoint. Block compilation unconditionally inserts a leading `nop` plus a label after the instruction: https://github.com/ruby/ruby/blob/641f15b1c6bd8921407a1f045573d3b0605f00d3/prism_compile.c#L6867-L6869 This `nop` instruction is used entirely for firing the block entry tracepoint. The label exists so that the block can contain a loop but the block entry tracepoint is executed only once. For example, the following code is an infinite loop, but should only execute the b_call tracepoint once: ```ruby -> { redo }.call ``` Previous to this commit, we would eliminate the `nop` instruction, but only if there were no other jump instructions inside the block. This means that the following code would still contain a leading `nop` even though the label following the `nop` is unused: ```ruby -> { nil if bar } ``` ``` == disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)> (catch: FALSE) 0000 nop ( 1)[Bc] 0001 putself [Li] 0002 opt_send_without_block <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0004 branchunless 8 0006 putnil 0007 leave [Br] 0008 putnil 0009 leave [Br] ``` This commit checks to see if the label inserted after the `nop` is actually a jump target. If it's not a jump target, then we should be safe to eliminate the leading `nop`: ``` > build-master/miniruby --dump=insns test.rb == disasm: #<ISeq:<main>@test.rb:1 (1,0)-(1,17)> 0000 putspecialobject 1 ( 1)[Li] 0002 send <calldata!mid:lambda, argc:0, FCALL>, block in <main> 0005 leave == disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)> 0000 putself ( 1)[LiBc] 0001 opt_send_without_block <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0003 branchunless 7 0005 putnil 0006 leave [Br] 0007 putnil 0008 leave [Br] ``` We have a test for b_call tracepoints that use `redo` here: https://github.com/ruby/ruby/blob/aebf96f371c8d874398e0041b798892e545fa206/test/ruby/test_settracefunc.rb#L1728-L1736 Notes: Merged: https://github.com/ruby/ruby/pull/12957
2025-03-03Allow YJIT `mem-size` and `call-threshold` to be set at runtime via ↵annichai-stripe
`YJIT.enable()` (#12505) * first commit * yjit.rb change * revert formatting * rename mem-size to exec-mem-size for correctness * wip, move setting into rb_yjit_enable directly * remove unused helper functions * add in call threshold * input validation with extensive eprintln * delete test script * exec-mem-size -> mem-size * handle input validation with asserts * add test cases related to input validation * modify test cases * move validation out of rs, into rb * add comments * remove trailing spaces * remove logging Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> * remove helper fn * Update test/ruby/test_yjit.rb Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> * trailing white space --------- Co-authored-by: Alan Wu <XrXr@users.noreply.github.com> Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2025-02-14Only count VM instructions in YJIT stats buildsAaron Patterson
The instruction counter is slowing multi-Ractor applications. I had changed it to use a thread local, but using a thread local is slowing single threaded applications. This commit only enables the instruction counter in YJIT stats builds until we can figure out a way to gather the information with lower overhead. Co-authored-by: Randy Stauner <randy.stauner@shopify.com> Notes: Merged: https://github.com/ruby/ruby/pull/12670
2025-01-04YJIT: Fix crash when yielding keyword argumentsAlan Wu
Previously, the code for dropping surplus arguments when yielding into blocks erroneously attempted to drop keyword arguments when there is in fact no surplus arguments. Fix the condition and test that supplying the exact number of keyword arguments as require compiles without fallback. Notes: Merged: https://github.com/ruby/ruby/pull/12499
2024-11-04YJIT: Replace Array#each only when YJIT is enabled (#11955)Takashi Kokubun
* YJIT: Replace Array#each only when YJIT is enabled * Add comments about BUILTIN_ATTR_C_TRACE * Make Ruby Array#each available with --yjit as well * Fix all paths that expect a C location * Use method_basic_definition_p to detect patches * Copy a comment about C_TRACE flag to compilers * Rephrase a comment about add_yjit_hook * Give METHOD_ENTRY_BASIC flag to Array#each * Add --yjit-c-builtin option * Allow inconsistent source_location in test-spec * Refactor a check of BUILTIN_ATTR_C_TRACE * Set METHOD_ENTRY_BASIC without touching vm->running Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-10-16Skip GC compaction tests in test_yjit.rb when not supportedPeter Zhu
Notes: Merged: https://github.com/ruby/ruby/pull/11905
2024-09-17YJIT: Accept key for runtime_stats to return only that stat (#11536)Randy Stauner
Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-08-27YJIT: Encode doubles to VALUE objects and move stat generation to rust (#11388)Randy Stauner
* YJIT: Encode doubles to VALUE objects and move stat generation to rust Stats that can now be generated from rust have been moved there. * Move object_shape_count call for runtime_stats to rust This reduces the ruby method to a single primitive. * Change hash_aset_usize from macro to function Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2024-06-18Optimized forwarding callers and calleesAaron Patterson
This patch optimizes forwarding callers and callees. It only optimizes methods that only take `...` as their parameter, and then pass `...` to other calls. Calls it optimizes look like this: ```ruby def bar(a) = a def foo(...) = bar(...) # optimized foo(123) ``` ```ruby def bar(a) = a def foo(...) = bar(1, 2, ...) # optimized foo(123) ``` ```ruby def bar(*a) = a def foo(...) list = [1, 2] bar(*list, ...) # optimized end foo(123) ``` All variants of the above but using `super` are also optimized, including a bare super like this: ```ruby def foo(...) super end ``` This patch eliminates intermediate allocations made when calling methods that accept `...`. We can observe allocation elimination like this: ```ruby def m x = GC.stat(:total_allocated_objects) yield GC.stat(:total_allocated_objects) - x end def bar(a) = a def foo(...) = bar(...) def test m { foo(123) } end test p test # allocates 1 object on master, but 0 objects with this patch ``` ```ruby def bar(a, b:) = a + b def foo(...) = bar(...) def test m { foo(1, b: 2) } end test p test # allocates 2 objects on master, but 0 objects with this patch ``` How does it work? ----------------- This patch works by using a dynamic stack size when passing forwarded parameters to callees. The caller's info object (known as the "CI") contains the stack size of the parameters, so we pass the CI object itself as a parameter to the callee. When forwarding parameters, the forwarding ISeq uses the caller's CI to determine how much stack to copy, then copies the caller's stack before calling the callee. The CI at the forwarded call site is adjusted using information from the caller's CI. I think this description is kind of confusing, so let's walk through an example with code. ```ruby def delegatee(a, b) = a + b def delegator(...) delegatee(...) # CI2 (FORWARDING) end def caller delegator(1, 2) # CI1 (argc: 2) end ``` Before we call the delegator method, the stack looks like this: ``` Executing Line | Code | Stack ---------------+---------------------------------------+-------- 1| def delegatee(a, b) = a + b | self 2| | 1 3| def delegator(...) | 2 4| # | 5| delegatee(...) # CI2 (FORWARDING) | 6| end | 7| | 8| def caller | -> 9| delegator(1, 2) # CI1 (argc: 2) | 10| end | ``` The ISeq for `delegator` is tagged as "forwardable", so when `caller` calls in to `delegator`, it writes `CI1` on to the stack as a local variable for the `delegator` method. The `delegator` method has a special local called `...` that holds the caller's CI object. Here is the ISeq disasm fo `delegator`: ``` == disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)> local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] "..."@0 0000 putself ( 1)[LiCa] 0001 getlocal_WC_0 "..."@0 0003 send <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil 0006 leave [Re] ``` The local called `...` will contain the caller's CI: CI1. Here is the stack when we enter `delegator`: ``` Executing Line | Code | Stack ---------------+---------------------------------------+-------- 1| def delegatee(a, b) = a + b | self 2| | 1 3| def delegator(...) | 2 -> 4| # | CI1 (argc: 2) 5| delegatee(...) # CI2 (FORWARDING) | cref_or_me 6| end | specval 7| | type 8| def caller | 9| delegator(1, 2) # CI1 (argc: 2) | 10| end | ``` The CI at `delegatee` on line 5 is tagged as "FORWARDING", so it knows to memcopy the caller's stack before calling `delegatee`. In this case, it will memcopy self, 1, and 2 to the stack before calling `delegatee`. It knows how much memory to copy from the caller because `CI1` contains stack size information (argc: 2). Before executing the `send` instruction, we push `...` on the stack. The `send` instruction pops `...`, and because it is tagged with `FORWARDING`, it knows to memcopy (using the information in the CI it just popped): ``` == disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)> local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] "..."@0 0000 putself ( 1)[LiCa] 0001 getlocal_WC_0 "..."@0 0003 send <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil 0006 leave [Re] ``` Instruction 001 puts the caller's CI on the stack. `send` is tagged with FORWARDING, so it reads the CI and _copies_ the callers stack to this stack: ``` Executing Line | Code | Stack ---------------+---------------------------------------+-------- 1| def delegatee(a, b) = a + b | self 2| | 1 3| def delegator(...) | 2 4| # | CI1 (argc: 2) -> 5| delegatee(...) # CI2 (FORWARDING) | cref_or_me 6| end | specval 7| | type 8| def caller | self 9| delegator(1, 2) # CI1 (argc: 2) | 1 10| end | 2 ``` The "FORWARDING" call site combines information from CI1 with CI2 in order to support passing other values in addition to the `...` value, as well as perfectly forward splat args, kwargs, etc. Since we're able to copy the stack from `caller` in to `delegator`'s stack, we can avoid allocating objects. I want to do this to eliminate object allocations for delegate methods. My long term goal is to implement `Class#new` in Ruby and it uses `...`. I was able to implement `Class#new` in Ruby [here](https://github.com/ruby/ruby/pull/9289). If we adopt the technique in this patch, then we can optimize allocating objects that take keyword parameters for `initialize`. For example, this code will allocate 2 objects: one for `SomeObject`, and one for the kwargs: ```ruby SomeObject.new(foo: 1) ``` If we combine this technique, plus implement `Class#new` in Ruby, then we can reduce allocations for this common operation. Co-Authored-By: John Hawthorn <john@hawthorn.email> Co-Authored-By: Alan Wu <XrXr@users.noreply.github.com>
2024-06-05Don't add `+YJIT` to `RUBY_DESCRIPTION` until it's actually enabledJean Boussier
If you start Ruby with `--yjit-disable`, the `+YJIT` shouldn't be added until `RubyVM::YJIT.enable` is actually called. Otherwise it's confusing in crash reports etc.
2024-04-18Add a hint of `ASCII-8BIT` being `BINARY`Jean Boussier
[Feature #18576] Since outright renaming `ASCII-8BIT` is deemed to backward incompatible, the next best thing would be to only change its `#inspect`, particularly in exception messages.
2024-03-14Ensure test suite is compatible with --frozen-string-literalJean Boussier
As preparation for https://bugs.ruby-lang.org/issues/20205 making sure the test suite is compatible with frozen string literals is making things easier.
2024-03-01YJIT: Support opt_invokebuiltin_delegate for leaf builtin (#10152)Takashi Kokubun
2024-02-21YJIT: Grab stack operands after SP change in String#byteslice (#10060)Alan Wu
Previously, `StackOperand`s caching `sp_offset` was held across a jit_prepare_call_with_gc(), which invalidates the offsets. With the right register allocation state, the canary overlapped with the old address of the receiver and YJIT clobbered the receiver writing the canary.
2024-02-20YJIT: Support `**nil` for cfuncsAlan Wu
Similar to the iseq call support. Fairly straight forward.
2024-02-16YJIT: Support empty splatAlan Wu
Previously we rejected empty splat calls to methods with no parameters as `iseq_arity_error` which didn't work well with delegated calls.
2024-02-16YJIT: Support `**nil`Alan Wu
This adds YJIT support for VM_CALL_KW_SPLAT with nil, specifically for when we already know from the context that it's done with a nil. This is enough to support forwarding with `...` when there no keyword arguments are present. Amend the kw_rest support to propagate the type of the parameter to help with this. Test interactions with splat, since the splat array sits lower on the stack when a kw_splat argument is present.
2024-02-14YJIT: Simplify Kernel#send guards and admit more cases (#9956)Alan Wu
Previously, our compile time check rejected dynamic symbols (e.g. what String#to_sym could return) even though we could handle them just fine. The runtime guards for the type of method name was also overly restrictive and didn't accept dynamic symbols. Fold the type check into the rb_get_symbol_id() and take advantage of the guard already checking for 0. This also avoids generating the same call twice in case the same method name is presented as different types.
2024-02-12YJIT: Add support for `**kwrest` parametersAlan Wu
Now that `...` uses `**kwrest` instead of regular splat and ruby2keywords, we need to support these type of methods to support `...` well.
2024-01-17YJIT: Support empty splat and some block_arg calls to ivar getters (#9567)Alan Wu
These seem odd at first glance, but they're used with `...` calls with `Module#delegate` from Active Support. These account for ~3% of fallback reasons in the `lobsters` benchmark.
2024-01-05YJIT: Let RubyVM::YJIT.enable respect --yjit-stats (#9415)Takashi Kokubun
2023-12-19YJIT: Add stats option to RubyVM::YJIT.enable (#9297)Takashi Kokubun
2023-12-07YJIT: Fix on-stack ISEQ comparison for auto_compact (#9164)Takashi Kokubun
2023-11-30YJIT: Cancel on-stack jit_return on invalidation (#9086)Takashi Kokubun
* YJIT: Cancel on-stack jit_return on invalidation Co-authored-by: Alan Wu <alansi.xingwu@shopify.com> * Use RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P --------- Co-authored-by: Alan Wu <alansi.xingwu@shopify.com>
2023-11-30YJIT: Use `stats[:live_page_count]`, renamed from :compiled_page_countAlan Wu
Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
2023-11-08YJIT: Disable code GC (#8865)Takashi Kokubun
Co-authored-by: Alan Wu <alansi.xingwu@shopify.com> Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
2023-11-02YJIT: Always define method codegen table at boot (#8807)Takashi Kokubun
2023-10-20YJIT: Skip printing stats at exit if --yjit-disable (#8727)Takashi Kokubun
2023-10-20YJIT: On test_bug_19316, only check the resultAlan Wu
Because the `&` call checks for interrupts, the test was accidentally timing dependent. Stop checking for exits. [Bug #19921] Reported-by: Vít Ondruch <vondruch@redhat.com> Reported-by: Mamoru Tasaka <mtasaka@fedoraproject.org>
2023-10-19YJIT: Make test_yjit.rb faster with --yjit-stats=quietAlan Wu
The for-human stats summaries are not relevant for the children `test_yjit.rb` spawns. Avoid compiling and running the printing code. On a -O0 dev build this halves the time for `test_yjit.rb` on my machine.
2023-10-19YJIT: Print exit reasons on failure in test_yjit.rbAlan Wu
For <https://bugs.ruby-lang.org/issues/19921>, I suspect the test is failing due to a timing related interrupt, which on paper could happen with slow-enough GC runs. In any case, it's helpful for debugging to have more information when tests fail. Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
2023-10-19YJIT: Add RubyVM::YJIT.enable (#8705)Takashi Kokubun
2023-10-18YJIT: Use RbConfig.ruby instead of EnvUtil.rubybin (#8689)Takashi Kokubun
Some people encounter an issue that test_yjit uses the installed Ruby instead of the currently-running Ruby. It's fixed when they remove the installed Ruby. However, test_yjit should run the currently-running Ruby for testing YJIT in subprocesses. EnvUtil is unfortunately used outside tests as well, so for compatibility reasons, this commit only changes the argument given to EnvUtil.invoke_ruby to always use RbConfig.ruby. Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2023-10-13YJIT: Fallback opt_getconstant_path for const_missing (#8623)Takashi Kokubun
* YJIT: Fallback opt_getconstant_path for const_missing * Fix a comment [ci skip] * Remove a wrapper function
2023-10-02YJIT: Fix assert_no_exits (#8579)Takashi Kokubun
2023-09-29YJIT: Chain-guard opt_mult overflow (#8554)Takashi Kokubun
* YJIT: Chain-guard opt_mult overflow * YJIT: Support regenerating Jo after Mul
2023-08-31YJIT: Handle getblockparamproxy with ifuncJohn Hawthorn
getblockparamproxy for "ifunc" behaves identically to iseq, in just pushing rb_block_param_proxy. Notes: Merged: https://github.com/ruby/ruby/pull/8300
2023-08-23YJIT: Implement VM_CALL_ARGS_BLOCKARG with Proc for ISeq callsAlan Wu
Rack uses this. Speculate that the `obj` in `the_call(&obj)` will be a proc when the compile-time sample is a proc. Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org> Notes: Merged: https://github.com/ruby/ruby/pull/8117 Merged-By: XrXr
2023-08-17YJIT: implement side chain fallback for setlocal to avoid exiting (#8227)Maxime Chevalier-Boisvert
* YJIT: implement side chain fallback for setlocal to avoid exiting * Update yjit/src/codegen.rs Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> --------- Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2023-08-02YJIT: Fallback setivar if the receiver isn't T_OBJECT (#8160)Jean byroot Boussier
Followup: https://github.com/ruby/ruby/pull/8152 If the receiver is a T_MODULE or T_CLASS and has a lot of ivars, `get_next_shape_internal` will return `NULL`. Co-authored-by: Jean Boussier <byroot@ruby-lang.org> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2023-07-26Implement `opt_aref_with` instruction (#8118)ywenc
Implement gen_opt_aref_with Vm opt_aref_with is available Test opt_aref_with Stats for opt_aref_with Co-authored-by: jhawthorn <jhawthorn@github.com> Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2023-07-24YJIT: Fallback send instructions to vm_sendish (#8106)Takashi Kokubun
Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
2023-06-06YJIT: Avoid identity-based known-class guards for IO objects (#7911)Alan Wu
`IO#reopen` is very special in that it is able to change the class and singleton class of IO instances. In its presence, it is not correct to assume that IO instances has a stable class/singleton class and guard by comparing identity. Notes: Merged-By: maximecb <maximecb@ruby-lang.org>
2023-04-20YJIT: invokesuper: Remove cme mid matching checkJohn Hawthorn
This check was introduced to match an assertion in the C YJIT when this was originally introduced. I don't believe it's necessary for correctness of the generated code. Co-authored-by: Adam Hess <HParker@github.com> Co-authored-by: Daniel Colson <danieljamescolson@gmail.com> Co-authored-by: Luan Vieira <luanzeba@github.com> Notes: Merged: https://github.com/ruby/ruby/pull/7740