<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ruby.git/test/ruby/test_allocation.rb, branch v3_4_9</title>
<subtitle>The Ruby Programming Language</subtitle>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/'/>
<entry>
<title>Fix allocationless anonymous splat keyword argument check</title>
<updated>2025-12-12T16:56:50+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2025-12-03T01:34:36+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=88f1d98676d435a79e2086ed8054b459f1b4bd2a'/>
<id>88f1d98676d435a79e2086ed8054b459f1b4bd2a</id>
<content type='text'>
Previously, if an argument splat and keywords are provided by
the caller, it did not check whether the method/proc accepted
keywords before avoiding the allocation. This is incorrect,
because if the method/proc does not accept keywords, the
keywords passed by the caller are added as a positional
argument, so there must be an allocation to avoid mutating
the positional splat argument.

Add a check that if the caller passes keywords, the
method/proc must accept keywords in order to optimize.
If the caller passes a keyword splat, either the
method/proc must accept keywords, or the keyword splat must
be empty in order to optimize.

If keywords are explicitly disallowed via `**nil`, the
optimization should be skipped, because the array is mutated
before the ArgumentError exception is raised.

In addition to a test for the correct behavior, add an
allocation test for a method that accepts an anonymous splat
without keywords.

Fixes [Bug #21757]
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Previously, if an argument splat and keywords are provided by
the caller, it did not check whether the method/proc accepted
keywords before avoiding the allocation. This is incorrect,
because if the method/proc does not accept keywords, the
keywords passed by the caller are added as a positional
argument, so there must be an allocation to avoid mutating
the positional splat argument.

Add a check that if the caller passes keywords, the
method/proc must accept keywords in order to optimize.
If the caller passes a keyword splat, either the
method/proc must accept keywords, or the keyword splat must
be empty in order to optimize.

If keywords are explicitly disallowed via `**nil`, the
optimization should be skipped, because the array is mutated
before the ArgumentError exception is raised.

In addition to a test for the correct behavior, add an
allocation test for a method that accepts an anonymous splat
without keywords.

Fixes [Bug #21757]
</pre>
</div>
</content>
</entry>
<entry>
<title>merge revision(s) 1d94a9e1a4351e01f851dad250ba97dad859ee70: [Backport #21447]</title>
<updated>2025-07-14T21:29:12+00:00</updated>
<author>
<name>Takashi Kokubun</name>
<email>takashikkbn@gmail.com</email>
</author>
<published>2025-07-14T21:29:12+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=f0f97886fc0ef805835f0a0d7553e2a8abf988de'/>
<id>f0f97886fc0ef805835f0a0d7553e2a8abf988de</id>
<content type='text'>
	Fix handling of PM_CONSTANT_PATH_NODE node in keyword arguments with ARGS_SPLAT

	This was handled correctly in parse.y (NODE_COLON2), but not in
	prism. This wasn't caught earlier, because I only added tests for
	the optimized case and not the unoptimized case. Add tests for
	the unoptimized case.

	In code terms:

	```ruby
	m(*a, kw: lvar::X)     # Does not require allocation for *a
	m(*a, kw: method()::X) # Requires allocation for *a
	```

	This commit fixes the second case when prism is used.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
	Fix handling of PM_CONSTANT_PATH_NODE node in keyword arguments with ARGS_SPLAT

	This was handled correctly in parse.y (NODE_COLON2), but not in
	prism. This wasn't caught earlier, because I only added tests for
	the optimized case and not the unoptimized case. Add tests for
	the unoptimized case.

	In code terms:

	```ruby
	m(*a, kw: lvar::X)     # Does not require allocation for *a
	m(*a, kw: method()::X) # Requires allocation for *a
	```

	This commit fixes the second case when prism is used.
</pre>
</div>
</content>
</entry>
<entry>
<title>Avoid hash allocation for certain proc calls</title>
<updated>2024-08-20T02:00:37+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-08-20T02:00:37+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=ea7ceff82cec98d0c419e9807dcb33dcc08b56fa'/>
<id>ea7ceff82cec98d0c419e9807dcb33dcc08b56fa</id>
<content type='text'>
Previously, proc calls such as:

```ruby
proc{|| }.(**empty_hash)
proc{|b: 1| }.(**r2k_array_with_empty_hash)
```

both allocated hashes unnecessarily, due to two separate code paths.

The first call goes through CALLER_SETUP_ARG/vm_caller_setup_keyword_hash,
and is simple to fix by not duping an empty keyword hash that will be
dropped.

The second case is more involved, in setup_parameters_complex, but is
fixed the exact same way as when the ruby2_keywords hash is not empty,
by flattening the rest array to the VM stack, ignoring the last
element (the empty keyword splat).  Add a flatten_rest_array static
function to handle this case.

Update test_allocation.rb to automatically convert the method call
allocation tests to proc allocation tests, at least for the calls
that can be converted.  With the code changes, all proc call
allocation tests pass, showing that proc calls and method calls
now allocate the same number of objects.

I've audited the allocation tests, and I believe that all of the low
hanging fruit has been collected.  All remaining allocations are
either caller side:

* Positional splat + post argument
* Multiple positional splats
* Literal keywords + keyword splat
* Multiple keyword splats

Or callee side:

* Positional splat parameter
* Keyword splat parameter
* Keyword to positional argument conversion for methods that don't accept keywords
* ruby2_keywords method called with keywords

Reapplies abc04e898b627ab37fa9dd5e330f239768778d8b, which was reverted at
d56470a27c5a8a2e7aee7a76cea445c2d29c0c59, with the addition of a bug fix and
test.

Fixes [Bug #20679]</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Previously, proc calls such as:

```ruby
proc{|| }.(**empty_hash)
proc{|b: 1| }.(**r2k_array_with_empty_hash)
```

both allocated hashes unnecessarily, due to two separate code paths.

The first call goes through CALLER_SETUP_ARG/vm_caller_setup_keyword_hash,
and is simple to fix by not duping an empty keyword hash that will be
dropped.

The second case is more involved, in setup_parameters_complex, but is
fixed the exact same way as when the ruby2_keywords hash is not empty,
by flattening the rest array to the VM stack, ignoring the last
element (the empty keyword splat).  Add a flatten_rest_array static
function to handle this case.

Update test_allocation.rb to automatically convert the method call
allocation tests to proc allocation tests, at least for the calls
that can be converted.  With the code changes, all proc call
allocation tests pass, showing that proc calls and method calls
now allocate the same number of objects.

I've audited the allocation tests, and I believe that all of the low
hanging fruit has been collected.  All remaining allocations are
either caller side:

* Positional splat + post argument
* Multiple positional splats
* Literal keywords + keyword splat
* Multiple keyword splats

Or callee side:

* Positional splat parameter
* Keyword splat parameter
* Keyword to positional argument conversion for methods that don't accept keywords
* ruby2_keywords method called with keywords

Reapplies abc04e898b627ab37fa9dd5e330f239768778d8b, which was reverted at
d56470a27c5a8a2e7aee7a76cea445c2d29c0c59, with the addition of a bug fix and
test.

Fixes [Bug #20679]</pre>
</div>
</content>
</entry>
<entry>
<title>Revert "Avoid hash allocation for certain proc calls"</title>
<updated>2024-08-17T00:59:05+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-08-16T23:47:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=d56470a27c5a8a2e7aee7a76cea445c2d29c0c59'/>
<id>d56470a27c5a8a2e7aee7a76cea445c2d29c0c59</id>
<content type='text'>
This reverts commit abc04e898b627ab37fa9dd5e330f239768778d8b.

This caused problems in a Rails test.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
This reverts commit abc04e898b627ab37fa9dd5e330f239768778d8b.

This caused problems in a Rails test.
</pre>
</div>
</content>
</entry>
<entry>
<title>Avoid hash allocation for certain proc calls</title>
<updated>2024-08-15T20:00:09+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-07-26T22:18:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=abc04e898b627ab37fa9dd5e330f239768778d8b'/>
<id>abc04e898b627ab37fa9dd5e330f239768778d8b</id>
<content type='text'>
Previous, proc calls such as:

```ruby
proc{|| }.(**empty_hash)
proc{|b: 1| }.(**r2k_array_with_empty_hash)
```

both allocated hashes unnecessarily, due to two separate code paths.

The first call goes through CALLER_SETUP_ARG/vm_caller_setup_keyword_hash,
and is simple to fix by not duping an empty keyword hash that will be
dropped.

The second case is more involved, in setup_parameters_complex, but is
fixed the exact same way as when the ruby2_keywords hash is not empty,
by flattening the rest array to the VM stack, ignoring the last
element (the empty keyword splat).  Add a flatten_rest_array static
function to handle this case.

Update test_allocation.rb to automatically convert the method call
allocation tests to proc allocation tests, at least for the calls
that can be converted.  With the code changes, all proc call
allocation tests pass, showing that proc calls and method calls
now allocate the same number of objects.

I've audited the allocation tests, and I believe that all of the low
hanging fruit has been collected.  All remaining allocations are
either caller side:

* Positional splat + post argument
* Multiple positional splats
* Literal keywords + keyword splat
* Multiple keyword splats

Or callee side:

* Positional splat parameter
* Keyword splat parameter
* Keyword to positional argument conversion for methods that don't accept keywords
* ruby2_keywords method called with keywords
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Previous, proc calls such as:

```ruby
proc{|| }.(**empty_hash)
proc{|b: 1| }.(**r2k_array_with_empty_hash)
```

both allocated hashes unnecessarily, due to two separate code paths.

The first call goes through CALLER_SETUP_ARG/vm_caller_setup_keyword_hash,
and is simple to fix by not duping an empty keyword hash that will be
dropped.

The second case is more involved, in setup_parameters_complex, but is
fixed the exact same way as when the ruby2_keywords hash is not empty,
by flattening the rest array to the VM stack, ignoring the last
element (the empty keyword splat).  Add a flatten_rest_array static
function to handle this case.

Update test_allocation.rb to automatically convert the method call
allocation tests to proc allocation tests, at least for the calls
that can be converted.  With the code changes, all proc call
allocation tests pass, showing that proc calls and method calls
now allocate the same number of objects.

I've audited the allocation tests, and I believe that all of the low
hanging fruit has been collected.  All remaining allocations are
either caller side:

* Positional splat + post argument
* Multiple positional splats
* Literal keywords + keyword splat
* Multiple keyword splats

Or callee side:

* Positional splat parameter
* Keyword splat parameter
* Keyword to positional argument conversion for methods that don't accept keywords
* ruby2_keywords method called with keywords
</pre>
</div>
</content>
</entry>
<entry>
<title>Avoid array allocation for f(*r2k_ary) when def f(x)</title>
<updated>2024-07-19T05:17:21+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-07-12T22:33:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=6428ce80f0ef85324a4d0a72ffa4c6fa2db37cdd'/>
<id>6428ce80f0ef85324a4d0a72ffa4c6fa2db37cdd</id>
<content type='text'>
When calling a method that does not accept a positional splat
parameter with a splatted array with a ruby2_keywords flagged hash,
there is no need to duplicate the splatted array.  Previously,
Ruby would duplicate the splatted array and potentially modify
it before flattening it to the VM stack

Use a similar approach as the f(*ary, **hash) optimization,
flattening the splatted array to the VM stack without modifying
it, and make any modifications needed to the VM stack.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
When calling a method that does not accept a positional splat
parameter with a splatted array with a ruby2_keywords flagged hash,
there is no need to duplicate the splatted array.  Previously,
Ruby would duplicate the splatted array and potentially modify
it before flattening it to the VM stack

Use a similar approach as the f(*ary, **hash) optimization,
flattening the splatted array to the VM stack without modifying
it, and make any modifications needed to the VM stack.
</pre>
</div>
</content>
</entry>
<entry>
<title>Avoid hash allocation for f(*r2k_ary) when def f(kw: 1)</title>
<updated>2024-07-19T05:17:21+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-07-12T21:19:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=1cc5a64dd87a6a474ec8387083d41a3c6c1c3ca5'/>
<id>1cc5a64dd87a6a474ec8387083d41a3c6c1c3ca5</id>
<content type='text'>
When calling a method that accepts keywords but not a keyword
splat with a splatted array with a ruby2_keywords flagged hash,
there is no need to duplicate the ruby2_keywords flagged hash,
since it will be accessed to get the keyword values, but it will
not be modified.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
When calling a method that accepts keywords but not a keyword
splat with a splatted array with a ruby2_keywords flagged hash,
there is no need to duplicate the ruby2_keywords flagged hash,
since it will be accessed to get the keyword values, but it will
not be modified.
</pre>
</div>
</content>
</entry>
<entry>
<title>Check for and remove duplicate checks in test_allocation</title>
<updated>2024-07-19T05:17:21+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-07-12T20:58:02+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=4a49b060ae04b3ccb3880854ba08c6629344f7b3'/>
<id>4a49b060ae04b3ccb3880854ba08c6629344f7b3</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Avoid array allocation for f(*empty_ary, **hash) when def f(x)</title>
<updated>2024-07-19T05:17:21+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-07-12T20:28:53+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=94e7d2664388039fc8c2f6a063017cae22193fa3'/>
<id>94e7d2664388039fc8c2f6a063017cae22193fa3</id>
<content type='text'>
This avoids an array allocation when calling a method that does
not accept a positional splat or keywords with both a positional
splat and keywords.  Previously, Ruby would dup the positional
splat to append the keyword splat to it. Then it would flatten
the dupped positional splat array to the VM stack.

This flattens the given positional splat to the VM stack, then
adds the keyword splat hash after the last positional splat
element on the VM stack, avoiding the need to modify
the positional splat array.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
This avoids an array allocation when calling a method that does
not accept a positional splat or keywords with both a positional
splat and keywords.  Previously, Ruby would dup the positional
splat to append the keyword splat to it. Then it would flatten
the dupped positional splat array to the VM stack.

This flattens the given positional splat to the VM stack, then
adds the keyword splat hash after the last positional splat
element on the VM stack, avoiding the need to modify
the positional splat array.
</pre>
</div>
</content>
</entry>
<entry>
<title>Avoid unnecessary array allocations for f(arg, *arg, **arg, **arg), f(*arg, a: lvar), and other calls</title>
<updated>2024-07-19T05:17:21+00:00</updated>
<author>
<name>Jeremy Evans</name>
<email>code@jeremyevans.net</email>
</author>
<published>2024-07-11T23:16:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=3de20efc308cccc38bf9dacfffca6c626d039a06'/>
<id>3de20efc308cccc38bf9dacfffca6c626d039a06</id>
<content type='text'>
The `f(arg, *arg, **arg, **arg)` case was previously not optimized.
The optimizer didn't optimize this case because of the multiple
keyword splats, and the compiler didn't optimize it because the
`f(*arg, **arg, **arg)` optimization added in
0ee3960685e283d8e75149a8777eb0109d41509a didn't apply.

I found it difficult to apply this optimization without changing
the `setup_args_core` API, since by the time you get to the ARGSCAT
case, you don't know whether you were called recursively or directly,
so I'm not sure if it was possible to know at that point whether the
array allocation could be avoided.

This changes the dup_rest argument in `setup_args_core` from an int
to a pointer to int.  This allows us to track whether we have allocated
a caller side array for multiple splats or splat+post across
recursive calls. Check the pointed value (*dup_rest) to determine the
`splatarray` argument. If dup_rest is 1, then use `splatarray true`
(caller-side array allocation), then set *dup_rest back to 0, ensuring
only a single `splatarray true` per method call.

Before calling `setup_args_core`, check whether the array allocation
can be avoided safely using `splatarray false`.  Optimizable cases are:

```
// f(*arg)
SPLAT

// f(1, *arg)
ARGSCAT
 LIST

// f(*arg, **arg)
ARGSPUSH
 SPLAT
 HASH nd_brace=0

// f(1, *arg, **arg)
ARGSPUSH
  ARGSCAT
   LIST
  HASH nd_brace=0
```

If so, dup_rest is set to 0 instead of 1 to avoid the allocation.

After calling `setup_args_core`, check the flag. If the flag
includes `VM_CALL_ARGS_SPLAT`, and the pointed value has changed,
indicating `splatarray true` was used, then also set
`VM_CALL_ARGS_SPLAT_MUT` in the flag.

My initial attempt at this broke the `f(*ary, &amp;ary.pop)` test,
because we were not duplicating the ary in the splat even though
it was modified later (evaluation order issue). The initial attempt
would also break `f(*ary, **ary.pop)` or `f(*ary, kw: ary.pop)` cases
for the same reason. I added test cases for those evaluation
order issues.

Add setup_args_dup_rest_p static function that checks that a given
node is safe.  Call that on the block pass node to determine if
the block pass node is safe.  Also call it on each of the hash
key/value nodes to test that they are safe.  If any are not safe,
then set dup_rest = 1 so that `splatarray true` will be used to
avoid the evaluation order issue.

This new approach has the affect of optimizing most cases of
literal keywords after positional splats.  Previously, only
static keyword hashes after positional splats avoided array
allocation for the splat.  Now, most dynamic keyword hashes
after positional splats also avoid array allocation.

Add allocation tests for dynamic keyword keyword hashes after
positional splats.

setup_args_dup_rest_p is currently fairly conservative. It
could definitely be expanded to handle additional node types
to reduce allocations in additional cases.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The `f(arg, *arg, **arg, **arg)` case was previously not optimized.
The optimizer didn't optimize this case because of the multiple
keyword splats, and the compiler didn't optimize it because the
`f(*arg, **arg, **arg)` optimization added in
0ee3960685e283d8e75149a8777eb0109d41509a didn't apply.

I found it difficult to apply this optimization without changing
the `setup_args_core` API, since by the time you get to the ARGSCAT
case, you don't know whether you were called recursively or directly,
so I'm not sure if it was possible to know at that point whether the
array allocation could be avoided.

This changes the dup_rest argument in `setup_args_core` from an int
to a pointer to int.  This allows us to track whether we have allocated
a caller side array for multiple splats or splat+post across
recursive calls. Check the pointed value (*dup_rest) to determine the
`splatarray` argument. If dup_rest is 1, then use `splatarray true`
(caller-side array allocation), then set *dup_rest back to 0, ensuring
only a single `splatarray true` per method call.

Before calling `setup_args_core`, check whether the array allocation
can be avoided safely using `splatarray false`.  Optimizable cases are:

```
// f(*arg)
SPLAT

// f(1, *arg)
ARGSCAT
 LIST

// f(*arg, **arg)
ARGSPUSH
 SPLAT
 HASH nd_brace=0

// f(1, *arg, **arg)
ARGSPUSH
  ARGSCAT
   LIST
  HASH nd_brace=0
```

If so, dup_rest is set to 0 instead of 1 to avoid the allocation.

After calling `setup_args_core`, check the flag. If the flag
includes `VM_CALL_ARGS_SPLAT`, and the pointed value has changed,
indicating `splatarray true` was used, then also set
`VM_CALL_ARGS_SPLAT_MUT` in the flag.

My initial attempt at this broke the `f(*ary, &amp;ary.pop)` test,
because we were not duplicating the ary in the splat even though
it was modified later (evaluation order issue). The initial attempt
would also break `f(*ary, **ary.pop)` or `f(*ary, kw: ary.pop)` cases
for the same reason. I added test cases for those evaluation
order issues.

Add setup_args_dup_rest_p static function that checks that a given
node is safe.  Call that on the block pass node to determine if
the block pass node is safe.  Also call it on each of the hash
key/value nodes to test that they are safe.  If any are not safe,
then set dup_rest = 1 so that `splatarray true` will be used to
avoid the evaluation order issue.

This new approach has the affect of optimizing most cases of
literal keywords after positional splats.  Previously, only
static keyword hashes after positional splats avoided array
allocation for the splat.  Now, most dynamic keyword hashes
after positional splats also avoid array allocation.

Add allocation tests for dynamic keyword keyword hashes after
positional splats.

setup_args_dup_rest_p is currently fairly conservative. It
could definitely be expanded to handle additional node types
to reduce allocations in additional cases.
</pre>
</div>
</content>
</entry>
</feed>
