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.
This commit is contained in:
Jeremy Evans 2024-07-12 13:28:53 -07:00
Родитель 2c79a7641f
Коммит 94e7d26643
2 изменённых файлов: 29 добавлений и 7 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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");
}