Project

General

Profile

Actions

Bug #21452

open

ARGS_SPLAT bytecode regression between 3.3 and 3.4

Added by tekknolagi (Maxwell Bernstein) 1 day ago. Updated 1 day ago.

Status:
Open
Assignee:
-
Target version:
-
ruby -v:
ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin24]
[ruby-core:122604]

Description

On Ruby 3.3, f(1, 2, *[3, 4]) splats all arguments to the stack and uses ARGS_SIMPLE:

plum% ruby --version
ruby 3.3.2 (2024-05-30 revision e5a195edf6) [arm64-darwin24]
plum% ruby --dump=insns -e 'f(1, 2, *[3, 4])'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)>
0000 putself                                                          (   1)[Li]
0001 putobject_INT2FIX_1_
0002 putobject                              2
0004 putobject                              3
0006 putobject                              4
0008 opt_send_without_block                 <calldata!mid:f, argc:4, FCALL|ARGS_SIMPLE>
0010 leave
plum%

On Ruby 3.4, the same expression results in creating a new array and using ARGS_SPLAT:

plum% ruby --version
ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin24]
plum% ruby --dump=insns -e 'f(1, 2, *[3, 4])'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)>
0000 putself                                                          (   1)[Li]
0001 putobject_INT2FIX_1_
0002 putobject                              2
0004 duparray                               [3, 4]
0006 opt_send_without_block                 <calldata!mid:f, argc:3, ARGS_SPLAT|FCALL>
0008 leave
plum%

I think we should keep the behavior of 3.3.

Updated by jeremyevans0 (Jeremy Evans) 1 day ago

This only affects literal arrays, and there is no reason to write f(1, 2, *[3, 4]) instead of f(1, 2, 3, 4), so is it important whether this case is optimized? I'm not against the 3.3 behavior, but only if doing so is does not make the code more complex.

To avoid the array allocation, another approach is switching the duparray to putobject. This could be done in the peephole optimizer. We do a similar optimization for duphash here https://github.com/ruby/ruby/blob/242343ff801e35d19d81ec9d4ff3c32a36c00f06/compile.c#L4057-L4104. Maybe the resulting code is not as optimized, but I'm guessing the allocation is the cause of the majority of the slowdown.

If you decide to change this, make sure to consider the size of the literal array being splatted. As long as the allocation is avoided, I assume there is a point at which the extra VM instructions outweigh the cost of ARGS_SPLAT handling.

Also, be aware that using ARGS_SPLAT can be faster than using separate arguments in some cases (def f(a, b, *) for the above example).

Actions

Also available in: Atom PDF

Like0
Like0