summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <[email protected]>2024-07-12 13:28:53 -0700
committerJeremy Evans <[email protected]>2024-07-18 22:17:21 -0700
commit94e7d2664388039fc8c2f6a063017cae22193fa3 (patch)
tree16eac64dc63bc08a75563fbc71ed1c0e1f1ec99f
parent2c79a7641ffbd5da7d5215a218e0d6866f061423 (diff)
Avoid array allocation for f(*empty_ary, **hash) when def f(x)
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.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/11161
-rw-r--r--test/ruby/test_allocation.rb6
-rw-r--r--vm_args.c30
2 files changed, 29 insertions, 7 deletions
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
index 42a3cdeead..3f83b98470 100644
--- a/test/ruby/test_allocation.rb
+++ b/test/ruby/test_allocation.rb
@@ -123,8 +123,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 0, "required(*r2k_empty_array1#{block})")
check_allocations(0, 1, "required(*r2k_array#{block})")
- # Currently allocates 1 array unnecessarily
- check_allocations(1, 1, "required(*empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "required(*empty_array, **hash1, **empty_hash#{block})")
RUBY
end
@@ -148,8 +147,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 0, "optional(*r2k_empty_array1#{block})")
check_allocations(0, 1, "optional(*r2k_array#{block})")
- # Currently allocates 1 array unnecessarily
- check_allocations(1, 1, "optional(*empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 1, "optional(*empty_array, **hash1, **empty_hash#{block})")
RUBY
end
diff --git a/vm_args.c b/vm_args.c
index 90105f6900..4449113790 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -672,9 +672,32 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
}
else if (!ISEQ_BODY(iseq)->param.flags.has_kwrest && !ISEQ_BODY(iseq)->param.flags.has_kw) {
converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag);
- arg_rest_dup(args);
- rb_ary_push(args->rest, converted_keyword_hash);
- keyword_hash = Qnil;
+ if (ISEQ_BODY(iseq)->param.flags.has_rest) {
+ arg_rest_dup(args);
+ rb_ary_push(args->rest, converted_keyword_hash);
+ keyword_hash = Qnil;
+ }
+ else {
+ // Avoid duping rest when not necessary
+ // Copy rest elements and converted keyword hash directly to VM stack
+ const VALUE *argv = RARRAY_CONST_PTR(args->rest);
+ int j, i=args->argc, rest_len = RARRAY_LENINT(args->rest);
+ if (rest_len) {
+ CHECK_VM_STACK_OVERFLOW(ec->cfp, rest_len+1);
+ given_argc += rest_len;
+ args->argc += rest_len;
+ for (j=0; rest_len > 0; rest_len--, i++, j++) {
+ locals[i] = argv[j];
+ }
+ }
+ locals[i] = converted_keyword_hash;
+ given_argc--;
+ args->argc++;
+ args->rest = Qfalse;
+ ci_flag &= ~(VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT);
+ keyword_hash = Qnil;
+ goto arg_splat_and_kw_splat_flattened;
+ }
}
else {
keyword_hash = converted_keyword_hash;
@@ -768,6 +791,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
FL_SET_RAW(flag_keyword_hash, RHASH_PASS_AS_KEYWORDS);
}
+ arg_splat_and_kw_splat_flattened:
if (kw_flag && ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg) {
rb_raise(rb_eArgError, "no keywords accepted");
}