diff options
author | Jeremy Evans <code@jeremyevans.net> | 2024-01-26 11:53:49 -0800 |
---|---|---|
committer | Jeremy Evans <code@jeremyevans.net> | 2024-01-27 14:04:55 -0800 |
commit | d917bb8e60c283981626876c496cb2670f74ffb7 (patch) | |
tree | 0ef4e156f57f89d74927f6d7fd7ac19dd83279a0 /compile.c | |
parent | a591e11a7a6f6749044b6bef9c35633ad1930db5 (diff) |
Eliminate 1-2 array allocations for each splat used in a masgn method
Given code such as:
```ruby
h[*a, :a], h[*b] = v
```
Ruby would previously allocate 5 arrays for the mass assignment:
* splatarray true for a
* newarray for v[0]
* concatarray for [*a, a] and v[0]
* newarray for v[1]
* concatarray for b and v[1]
This optimizes it to only allocate 2 arrays:
* splatarray true for a
* splatarray true for b
Instead of the newarray/concatarray combination, pushtoarray is used.
Note above that there was no splatarray true for b originally. The
normal compilation uses splatarray false for b. Instead of trying
to find and modify the splatarray false to splatarray true, this
adds splatarray true for b, which requires a couple of swap
instructions, before the pushtoarray. This could be further
optimized to remove the need for those three instructions, but I'm
not sure if the complexity is worth it.
Additionally, this sets VM_CALL_ARGS_SPLAT_MUT on the call to
[]= in the h[*b] case, so that if []= has a splat parameter, the
new array can be used directly, without additional duplication.
Diffstat (limited to 'compile.c')
-rw-r--r-- | compile.c | 24 |
1 files changed, 22 insertions, 2 deletions
@@ -5401,11 +5401,31 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const ADD_ELEM(lhs, (LINK_ELEMENT *)iobj); if (vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) { int argc = vm_ci_argc(ci); + bool dupsplat = false; ci = ci_argc_set(iseq, ci, argc - 1); + if (!(vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT_MUT)) { + /* Given h[*a], _ = ary + * setup_args sets VM_CALL_ARGS_SPLAT and not VM_CALL_ARGS_SPLAT_MUT + * `a` must be dupped, because it will be appended with ary[0] + * Since you are dupping `a`, you can set VM_CALL_ARGS_SPLAT_MUT + */ + dupsplat = true; + ci = ci_flag_set(iseq, ci, VM_CALL_ARGS_SPLAT_MUT); + } OPERAND_AT(iobj, 0) = (VALUE)ci; RB_OBJ_WRITTEN(iseq, Qundef, iobj); - INSERT_BEFORE_INSN1(iobj, line_node, newarray, INT2FIX(1)); - INSERT_BEFORE_INSN(iobj, line_node, concatarray); + /* Given: h[*a], h[*b, 1] = ary + * h[*a] uses splatarray false and does not set VM_CALL_ARGS_SPLAT_MUT, + * so this uses splatarray true on a to dup it before using pushtoarray + * h[*b, 1] uses splatarray true and sets VM_CALL_ARGS_SPLAT_MUT, + * so you can use pushtoarray directly + */ + if (dupsplat) { + INSERT_BEFORE_INSN(iobj, line_node, swap); + INSERT_BEFORE_INSN1(iobj, line_node, splatarray, Qtrue); + INSERT_BEFORE_INSN(iobj, line_node, swap); + } + INSERT_BEFORE_INSN1(iobj, line_node, pushtoarray, INT2FIX(1)); } ADD_INSN(lhs, line_node, pop); if (argc != 1) { |