Age | Commit message (Collapse) | Author |
|
[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
|
|
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
|
|
[Bug #21370]
Notes:
Merged: https://github.com/ruby/ruby/pull/13436
|
|
* 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
|
|
... because ISeq#to_binary does not work
Notes:
Merged: https://github.com/ruby/ruby/pull/13225
|
|
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
|
|
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
|
|
This would be useful for debugging.
Notes:
Merged: https://github.com/ruby/ruby/pull/11896
|
|
|
|
Related: ruby/prism#2935
Notes:
Merged: https://github.com/ruby/ruby/pull/11415
|
|
[Bug #20344]
Fix compile_next adding removable adjust label
Notes:
Merged: https://github.com/ruby/ruby/pull/11316
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/11267
Merged-By: nobu <[email protected]>
|
|
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]>
|
|
|
|
[Bug #20569]
`putobject RubyVM::FrozenCore`, is not serializable, we
have to use `putspecialobject VM_SPECIAL_OBJECT_VMCORE`.
|
|
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.
|
|
|
|
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.
|
|
|
|
```ruby
b = RubyVM::InstructionSequence.compile('def f = yield; def g = nil').to_a
pp b
#=>
...
{:use_block=>true},
...
```
|
|
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.
|
|
Fixes [Bug #19975]
|
|
[Feature #16495]
|
|
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.
|
|
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.
|
|
|
|
`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
|
|
- 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.
|
|
|
|
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/8381
|
|
fix [Feature #19572]
Notes:
Merged: https://github.com/ruby/ruby/pull/8150
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/7754
|
|
* 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
|
|
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
|
|
|
|
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
|
|
`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
|
|
`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
|
|
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
|
|
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
```
|
|
|
|
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
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/5147
|
|
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
|
|
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
```
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/5035
|
|
```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
|
|
|
|
In the case of read-only but refering an unshareable object.
|