summaryrefslogtreecommitdiff
path: root/test/ruby/test_iseq.rb
AgeCommit message (Collapse)Author
12 daysFix memory leak in Prism's RubyVM::InstructionSequence.newPeter Zhu
[Bug #21394] There are two ways to make RubyVM::InstructionSequence.new raise which would cause the options->scopes to leak memory: 1. Passing in any (non T_FILE) object where the to_str raises. 2. Passing in a T_FILE object where String#initialize_dup raises. This is because rb_io_path dups the string. Example 1: 10.times do 100_000.times do RubyVM::InstructionSequence.new(nil) rescue TypeError end puts `ps -o rss= -p #{$$}` end Before: 13392 17104 20256 23920 27264 30432 33584 36752 40032 43232 After: 9392 11072 11648 11648 11648 11712 11712 11712 11744 11744 Example 2: require "tempfile" MyError = Class.new(StandardError) String.prepend(Module.new do def initialize_dup(_) if $raise_on_dup raise MyError else super end end end) Tempfile.create do |f| 10.times do 100_000.times do $raise_on_dup = true RubyVM::InstructionSequence.new(f) rescue MyError else raise "MyError was not raised during RubyVM::InstructionSequence.new" end puts `ps -o rss= -p #{$$}` ensure $raise_on_dup = false end end Before: 14080 18512 22000 25184 28320 31600 34736 37904 41088 44256 After: 12016 12464 12880 12880 12880 12912 12912 12912 12912 12912 Notes: Merged: https://github.com/ruby/ruby/pull/13496
13 daysFix test_loading_kwargs_memory_leakPeter Zhu
The test fails with: TestISeq#test_loading_kwargs_memory_leak [test/ruby/test_iseq.rb:882]: pid 18222 exit 1 | -:2:in '<main>': undefined method 'iseq_to_binary' for main (NoMethodError) Notes: Merged: https://github.com/ruby/ruby/pull/13494
13 dayscompile.c: Handle anonymous variables in `outer_variable_cmp`Jean Boussier
[Bug #21370] Notes: Merged: https://github.com/ruby/ruby/pull/13436
2025-05-31`Ractor::Port`Koichi Sasada
* Added `Ractor::Port` * `Ractor::Port#receive` (support multi-threads) * `Rcator::Port#close` * `Ractor::Port#closed?` * Added some methods * `Ractor#join` * `Ractor#value` * `Ractor#monitor` * `Ractor#unmonitor` * Removed some methods * `Ractor#take` * `Ractor.yield` * Change the spec * `Racotr.select` You can wait for multiple sequences of messages with `Ractor::Port`. ```ruby ports = 3.times.map{ Ractor::Port.new } ports.map.with_index do |port, ri| Ractor.new port,ri do |port, ri| 3.times{|i| port << "r#{ri}-#{i}"} end end p ports.each{|port| pp 3.times.map{port.receive}} ``` In this example, we use 3 ports, and 3 Ractors send messages to them respectively. We can receive a series of messages from each port. You can use `Ractor#value` to get the last value of a Ractor's block: ```ruby result = Ractor.new do heavy_task() end.value ``` You can wait for the termination of a Ractor with `Ractor#join` like this: ```ruby Ractor.new do some_task() end.join ``` `#value` and `#join` are similar to `Thread#value` and `Thread#join`. To implement `#join`, `Ractor#monitor` (and `Ractor#unmonitor`) is introduced. This commit changes `Ractor.select()` method. It now only accepts ports or Ractors, and returns when a port receives a message or a Ractor terminates. We removes `Ractor.yield` and `Ractor#take` because: * `Ractor::Port` supports most of similar use cases in a simpler manner. * Removing them significantly simplifies the code. We also change the internal thread scheduler code (thread_pthread.c): * During barrier synchronization, we keep the `ractor_sched` lock to avoid deadlocks. This lock is released by `rb_ractor_sched_barrier_end()` which is called at the end of operations that require the barrier. * fix potential deadlock issues by checking interrupts just before setting UBF. https://bugs.ruby-lang.org/issues/21262 Notes: Merged: https://github.com/ruby/ruby/pull/13445
2025-05-01Omit tests using ISeq#to_binary under coverage measurementYusuke Endoh
... because ISeq#to_binary does not work Notes: Merged: https://github.com/ruby/ruby/pull/13225
2025-03-27Avoid allocation for anonymous positional splat with no argumentsJeremy Evans
Anonymous positional splats cannot be directly accessed, they can only be passed as splats to other methods. So if an anonymous positional splat would be empty, you can use a shared frozen empty array to save an allocation. ```ruby def a(*) end a() ``` This is similar to how anonymous empty keyword splats are optimized, except those use `nil` instead of a shared empty frozen hash. This updates the allocation tests to check that the array allocations are avoided where possible. It also makes a small change to test_iseq.rb to ensure an unfrozen hash is passed as the value of an anonymous splat parameter. Notes: Merged: https://github.com/ruby/ruby/pull/12596
2025-03-12Have `ast` live longer in ISeq.compile_file to fix GC stress crashAlan Wu
Previously, live range of `ast_value` ended on the call right before rb_ast_dispose(), which led to premature collection and use-after-free. We observed this crashing on -O3, -DVM_CHECK_MODE, with GCC 11.4.0 on Ubuntu. Co-authored-by: Aaron Patterson <[email protected]> Notes: Merged: https://github.com/ruby/ruby/pull/12898
2024-10-16RubyVM::InstructionSequence.of Thread::Backtrace::LocationKevin Newton
This would be useful for debugging. Notes: Merged: https://github.com/ruby/ruby/pull/11896
2024-09-15Prevent warnings: assigned but unused variableYusuke Endoh
2024-08-21[PRISM] Implement unused block warningeileencodes
Related: ruby/prism#2935 Notes: Merged: https://github.com/ruby/ruby/pull/11415
2024-08-12Fix next inside block argument stack underflowtompng
[Bug #20344] Fix compile_next adding removable adjust label Notes: Merged: https://github.com/ruby/ruby/pull/11316
2024-07-30Fix wrong unreachable chunk remove when jump destination label is unremovabletomoya ishida
Notes: Merged: https://github.com/ruby/ruby/pull/11267 Merged-By: nobu <[email protected]>
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 <[email protected]> Co-Authored-By: Alan Wu <[email protected]>
2024-06-12[Bug #20572] Abandon if replacing destination is the sameNobuyoshi Nakada
2024-06-11compile.c: use putspecialobject for RubyVM::FrozenCoreJean Boussier
[Bug #20569] `putobject RubyVM::FrozenCore`, is not serializable, we have to use `putspecialobject VM_SPECIAL_OBJECT_VMCORE`.
2024-05-28Make ensure first lineno the first line of the ensureKevin Newton
Previously, ensure ISEQs took their first line number from the line number coming from the AST. However, if this is coming from an empty `begin`..`end` inside of a method, this can be all of the way back to the method declaration. Instead, this commit changes it to be the first line number of the ensure block itself. The first_lineno field is only accessible through manual ISEQ compilation or through tracepoint. Either way, this will be more accurate for targeting going forward.
2024-05-16[PRISM] Enable TestISeq#test_syntax_error_messageKevin Newton
2024-04-20ensure ibf_load_setup is only passed String paramsZack Deveau
In cases where RubyVM::InstructionSequence.load_from_binary() is passed a param other than a String, we attempt to call the RSTRING_LENINT macro on it which can cause a segfault. ex: ``` var_0 = 0 RubyVM::InstructionSequence.load_from_binary(var_0) ``` This commit adds a type check to raise unless we are provided a String.
2024-04-17skip on Prism generated iseqKoichi Sasada
2024-04-17`ISeq#to_a` respects `use_block` statusKoichi Sasada
```ruby b = RubyVM::InstructionSequence.compile('def f = yield; def g = nil').to_a pp b #=> ... {:use_block=>true}, ... ```
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-04Keep hidden local variables when dumping and loading iseqsJeremy Evans
Fixes [Bug #19975]
2024-02-15Do not include a backtick in error messages and backtracesYusuke Endoh
[Feature #16495]
2024-01-17[PRISM] Fix crash in compile_prismPeter Zhu
If the argument is not a file or a string, it assumes it's a string which will crash because RSTRING_PTR and RSTRING_LEN assumes it's a string.
2024-01-17[PRISM] Fix test_compile_prism_with_filePeter Zhu
The test should be testing RubyVM::InstructionSequence.compile_prism with a file but it is instead passing the file path (which is a string) which raises a SyntaxError because it is not Ruby syntax.
2024-01-11[PRISM] Raise syntax errors when foundKevin Newton
2023-12-15[PRISM] Fix `compile_prism` when src is a fileeileencodes
`compile_prism` can take a source and file (and other arguments) or a file as the source. `compile` checks if the source is a file and if it is converts it. `compile_prism` is now doing the same thing. On the Ruby side `compile` handles a file [here](https://github.com/ruby/ruby/blob/master/iseq.c#L1159-L1162). Before: ``` "********* Ruby *************" == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(26,21)> local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] name@0 0000 putstring "Prism" ( 25)[Li] 0002 setlocal name@0, 0 0005 putself ( 26)[Li] 0006 putobject "hello, " 0008 getlocal name@0, 0 0011 dup 0012 objtostring <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE> 0014 anytostring 0015 concatstrings 2 0017 send <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, nil 0020 leave hello, Prism "********* PRISM *************" ./test.rb:13:in `compile_prism': wrong argument type File (expected String) (TypeError) from ./test.rb:13:in `<main>' make: *** [run] Error 1 ``` After: ``` "********* Ruby *************" == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(26,21)> local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] name@0 0000 putstring "Prism" ( 25)[Li] 0002 setlocal name@0, 0 0005 putself ( 26)[Li] 0006 putobject "hello, " 0008 getlocal name@0, 0 0011 dup 0012 objtostring <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE> 0014 anytostring 0015 concatstrings 2 0017 send <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, nil 0020 leave "********* PRISM *************" == disasm: #<ISeq:<compiled>@test_code.rb:24 (24,0)-(25,21)> local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] name@0 0000 putstring "Prism" ( 24)[Li] 0002 setlocal name@0, 0 0005 putself ( 25)[Li] 0006 putobject "hello, " 0008 getlocal name@0, 0 0011 dup 0012 objtostring <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE> 0014 anytostring 0015 concatstrings 2 0017 send <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, nil 0020 leave ( 24) ``` Fixes ruby/prism#1609
2023-11-26Fix portability of bignum in ISeq Binary FormatNobuyoshi Nakada
- Unless `sizeof(BDIGIT) == 4`, (8-byte integer not available), the size to be loaded was wrong. - Since `BDIGIT`s are dumped as raw binary, the loaded byte order was inverted unless little-endian.
2023-10-01[Bug #19906] Add the testNobuyoshi Nakada
2023-09-13Suppress an unused variable warningNobuyoshi Nakada
2023-09-13[Bug #19862] Skip compiled result of never reachable expressionNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/8381
2023-08-01support `rescue` event for TracePointKoichi Sasada
fix [Feature #19572] Notes: Merged: https://github.com/ruby/ruby/pull/8150
2023-04-27[Bug #19611] Remove never-reachable branch in logical expressionNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/7754
2023-03-17Fix small issues concerning namespacing in test-all suitelukeg
* Fix temporary methods on Object leaking across test cases. * Remove temporary classes/modules leaking across test cases. Notes: Merged: https://github.com/ruby/ruby/pull/7513
2022-12-03return early if there is no is_entries bufferAaron Patterson
If there is a compilation error, is_entries may not be allocated, but ic_size could be greater than 0. If we don't have a buffer to iterate over, just return early. Otherwise GC could segv [Bug #19173] Notes: Merged: https://github.com/ruby/ruby/pull/6853
2022-01-04Use omit instead of skip: test/ruby/**/*.rbHiroshi SHIBATA
2021-12-30Add support for anonymous rest and keyword rest argument forwardingJeremy Evans
This allows for the following syntax: ```ruby def foo(*) bar(*) end def baz(**) quux(**) end ``` This is a natural addition after the introduction of anonymous block forwarding. Anonymous rest and keyword rest arguments were already supported in method parameters, this just allows them to be used as arguments to other methods. The same advantages of anonymous block forwarding apply to rest and keyword rest argument forwarding. This has some minor changes to #parameters output. Now, instead of `[:rest], [:keyrest]`, you get `[:rest, :*], [:keyrest, :**]`. These were already used for `...` forwarding, so I think it makes it more consistent to include them in other cases. If we want to use `[:rest], [:keyrest]` in both cases, that is also possible. I don't think the previous behavior of `[:rest], [:keyrest]` in the non-... case and `[:rest, :*], [:keyrest, :**]` in the ... case makes sense, but if we did want that behavior, we'll have to make more substantial changes, such as using a different ID in the ... forwarding case. Implements [Feature #18351] Notes: Merged: https://github.com/ruby/ruby/pull/5148
2021-12-21`mandatory_only_cme` should not be in `def`Koichi Sasada
`def` (`rb_method_definition_t`) is shared by multiple callable method entries (cme, `rb_callable_method_entry_t`). There are two issues: * old -> young reference: `cme1->def->mandatory_only_cme = monly_cme` if `cme1` is young and `monly_cme` is young, there is no problem. Howevr, another old `cme2` can refer `def`, in this case, old `cme2` points young `monly_cme` and it violates gengc assumption. * cme can have different `defined_class` but `monly_cme` only has one `defined_class`. It does not make sense and `monly_cme` should be created for a cme (not `def`). To solve these issues, this patch allocates `monly_cme` per `cme`. `cme` does not have another room to store a pointer to the `monly_cme`, so this patch introduces `overloaded_cme_table`, which is weak key map `[cme] -> [monly_cme]`. `def::body::iseqptr::monly_cme` is deleted. The first issue is reported by Alan Wu. Notes: Merged: https://github.com/ruby/ruby/pull/5311
2021-12-09`Ractor.make_shareable` checks proc's seflKoichi Sasada
`Ractor.make_shareable(proc_obj)` raises an `IsolationError` if the self of `proc_obj` is not a shareable object. [Bug #18243] Notes: Merged: https://github.com/ruby/ruby/pull/5232
2021-11-26fix to choose correct callcacheKoichi Sasada
It should retun general `cc`, not for overloaded (mandatory only) method call cache. This issue is reported by @shugo and @ktou https://twitter.com/shugomaeda/status/1463699797182119936 Notes: Merged: https://github.com/ruby/ruby/pull/5173
2021-11-25test/ruby/test_iseq.rb: Avoid pollution of method namespaceYusuke Endoh
http://rubyci.s3.amazonaws.com/ubuntu/ruby-master/log/20211125T003004Z.log.html.gz ``` [ 4780/21204] TestISeq#test_super_with_anonymous_block/home/chkbuild/chkbuild/tmp/build/20211125T003004Z/ruby/test/ruby/test_iseq.rb:141: warning: method redefined; discarding old touch3 /home/chkbuild/chkbuild/tmp/build/20211125T003004Z/ruby/test/ruby/test_iseq.rb:121: warning: previous definition of touch3 was here = 0.00 s ```
2021-11-24test/ruby/test_iseq.rb: Use __LINE__ to make the error log easy to seeYusuke Endoh
2021-11-23Add an extra failing test case for [Bug #18250]Jean Boussier
The parameter being called `req` specifically causes an assertion error: ``` Assertion failed: (key != 0), function hash_table_raw_insert, file id_table.c, line 153. ``` Renaming the parameter or removing the `*` doesn't reproduce. Notes: Merged: https://github.com/ruby/ruby/pull/5157
2021-11-19Fix test_super_with_anonymous_block test to use anonymous blockJeremy Evans
Notes: Merged: https://github.com/ruby/ruby/pull/5147
2021-11-18Anonymous block forwarding allows a method to forward a passedJeremy Evans
block to another method without having to provide a name for the block parameter. Implements [Feature #11256] Co-authored-by: Yusuke Endoh [email protected] Co-authored-by: Nobuyoshi Nakada [email protected] Notes: Merged: https://github.com/ruby/ruby/pull/5051
2021-11-09test/ruby/test_iseq.rb: Prevent a warningYusuke Endoh
http://rubyci.s3.amazonaws.com/ubuntu/ruby-master/log/20211109T063003Z.log.html.gz ``` [ 9898/21145] TestISeq#test_super_with_block_and_kwrest/home/chkbuild/chkbuild/tmp/build/20211109T063003Z/ruby/test/ruby/test_iseq.rb:1: warning: method redefined; discarding old touch /home/chkbuild/chkbuild/tmp/build/20211109T063003Z/ruby/test/ruby/test_iseq.rb:1: warning: previous definition of touch was here = 0.00 s ```
2021-11-07Refine the error message for hidden variablesNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/5035
2021-11-07rb_id_serial_to_id: return unregistered ID as an internal IDNobuyoshi Nakada
```ruby def foo(*); ->{ super }; end ``` This code makes anonymous parameters which is not registered as an ID. The problem is that when Ractors try to scan `getlocal` instructions, it puts the Symbol corresponding to the parameter in to a hash. Since it is not registered, we end up with a strange exception. This commit wraps the unregistered ID in an internal ID so that we get the same exception for `...` as `*`. Co-Authored-By: Aaron Patterson <[email protected]> Co-Authored-By: John Hawthorn <[email protected]> Notes: Merged: https://github.com/ruby/ruby/pull/5035
2021-11-02Fix typosNobuyoshi Nakada
2021-10-29Preserve the encoding of message from outer local variableNobuyoshi Nakada
In the case of read-only but refering an unshareable object.