|
static int |
|
setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * const iseq, |
|
struct rb_calling_info *const calling, |
|
const struct rb_callinfo *ci, |
|
VALUE * const locals, const enum arg_setup_type arg_setup_type) |
|
{ |
|
const int min_argc = ISEQ_BODY(iseq)->param.lead_num + ISEQ_BODY(iseq)->param.post_num; |
|
const int max_argc = (ISEQ_BODY(iseq)->param.flags.has_rest == FALSE) ? min_argc + ISEQ_BODY(iseq)->param.opt_num : UNLIMITED_ARGUMENTS; |
|
int given_argc; |
|
unsigned int ci_flag = vm_ci_flag(ci); |
|
unsigned int kw_flag = ci_flag & (VM_CALL_KWARG | VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT); |
|
int opt_pc = 0, allow_autosplat = !kw_flag; |
|
struct args_info args_body, *args; |
|
VALUE keyword_hash = Qnil; |
|
VALUE * const orig_sp = ec->cfp->sp; |
|
unsigned int i; |
|
VALUE flag_keyword_hash = 0; |
|
VALUE splat_flagged_keyword_hash = 0; |
|
VALUE converted_keyword_hash = 0; |
|
VALUE rest_last = 0; |
|
const rb_callable_method_entry_t *cme = calling->cc ? vm_cc_cme(calling->cc) : NULL; |
|
|
|
vm_check_canary(ec, orig_sp); |
|
/* |
|
* Extend SP for GC. |
|
* |
|
* [pushed values] [uninitialized values] |
|
* <- ci->argc --> |
|
* <- ISEQ_BODY(iseq)->param.size------------> |
|
* ^ locals ^ sp |
|
* |
|
* => |
|
* [pushed values] [initialized values ] |
|
* <- ci->argc --> |
|
* <- ISEQ_BODY(iseq)->param.size------------> |
|
* ^ locals ^ sp |
|
*/ |
|
for (i=calling->argc; i<ISEQ_BODY(iseq)->param.size; i++) { |
|
locals[i] = Qnil; |
|
} |
|
ec->cfp->sp = &locals[i]; |
|
|
|
/* setup args */ |
|
args = &args_body; |
|
given_argc = args->argc = calling->argc; |
|
args->argv = locals; |
|
args->rest_dupped = ci_flag & VM_CALL_ARGS_SPLAT_MUT; |
|
|
|
if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.anon_rest)) { |
|
if ((ci_flag & VM_CALL_ARGS_SPLAT) && |
|
given_argc == ISEQ_BODY(iseq)->param.lead_num + (kw_flag ? 2 : 1) && |
|
!ISEQ_BODY(iseq)->param.flags.has_opt && |
|
!ISEQ_BODY(iseq)->param.flags.has_post && |
|
!ISEQ_BODY(iseq)->param.flags.ruby2_keywords && |
|
(!kw_flag || |
|
!ISEQ_BODY(iseq)->param.flags.has_kw || |
|
!ISEQ_BODY(iseq)->param.flags.has_kwrest || |
|
!ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg)) { |
|
args->rest_dupped = true; |
|
} |
|
} |
|
|
|
if (kw_flag & VM_CALL_KWARG) { |
|
args->kw_arg = vm_ci_kwarg(ci); |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_kw) { |
|
int kw_len = args->kw_arg->keyword_len; |
|
/* copy kw_argv */ |
|
args->kw_argv = ALLOCA_N(VALUE, kw_len); |
|
args->argc -= kw_len; |
|
given_argc -= kw_len; |
|
MEMCPY(args->kw_argv, locals + args->argc, VALUE, kw_len); |
|
} |
|
else { |
|
args->kw_argv = NULL; |
|
given_argc = args_kw_argv_to_hash(args); |
|
kw_flag |= VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT; |
|
} |
|
} |
|
else { |
|
args->kw_arg = NULL; |
|
args->kw_argv = NULL; |
|
} |
|
|
|
if ((ci_flag & VM_CALL_ARGS_SPLAT) && (ci_flag & VM_CALL_KW_SPLAT)) { |
|
// f(*a, **kw) |
|
args->rest_index = 0; |
|
keyword_hash = locals[--args->argc]; |
|
args->rest = locals[--args->argc]; |
|
|
|
if (ignore_keyword_hash_p(keyword_hash, iseq, &kw_flag, &converted_keyword_hash)) { |
|
keyword_hash = Qnil; |
|
} |
|
else if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.ruby2_keywords)) { |
|
converted_keyword_hash = check_kwrestarg(converted_keyword_hash, &kw_flag); |
|
flag_keyword_hash = converted_keyword_hash; |
|
arg_rest_dup(args); |
|
rb_ary_push(args->rest, converted_keyword_hash); |
|
keyword_hash = Qnil; |
|
} |
|
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); |
|
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; |
|
} |
|
|
|
int len = RARRAY_LENINT(args->rest); |
|
given_argc += len - 2; |
|
} |
|
else if (ci_flag & VM_CALL_ARGS_SPLAT) { |
|
// f(*a) |
|
args->rest_index = 0; |
|
args->rest = locals[--args->argc]; |
|
int len = RARRAY_LENINT(args->rest); |
|
given_argc += len - 1; |
|
|
|
if (!kw_flag && len > 0) { |
|
rest_last = RARRAY_AREF(args->rest, len - 1); |
|
if (RB_TYPE_P(rest_last, T_HASH) && FL_TEST_RAW(rest_last, RHASH_PASS_AS_KEYWORDS)) { |
|
// def f(**kw); a = [..., kw]; g(*a) |
|
splat_flagged_keyword_hash = rest_last; |
|
if (!(RHASH_EMPTY_P(rest_last) || ISEQ_BODY(iseq)->param.flags.has_kw) || (ISEQ_BODY(iseq)->param.flags.has_kwrest)) { |
|
rest_last = rb_hash_dup(rest_last); |
|
} |
|
kw_flag |= VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT; |
|
|
|
// Unset rest_dupped set by anon_rest as we may need to modify splat in this case |
|
args->rest_dupped = false; |
|
|
|
if (ignore_keyword_hash_p(rest_last, iseq, &kw_flag, &converted_keyword_hash)) { |
|
if (ISEQ_BODY(iseq)->param.flags.has_rest) { |
|
// Only duplicate/modify splat array if it will be used |
|
arg_rest_dup(args); |
|
rb_ary_pop(args->rest); |
|
} |
|
else if (arg_setup_type == arg_setup_block && !ISEQ_BODY(iseq)->param.flags.has_kwrest) { |
|
// Avoid hash allocation for empty hashes |
|
// Copy rest elements except empty keyword hash directly to VM stack |
|
flatten_rest_args(ec, args, locals, &ci_flag); |
|
keyword_hash = Qnil; |
|
kw_flag = 0; |
|
} |
|
given_argc--; |
|
} |
|
else if (!ISEQ_BODY(iseq)->param.flags.has_rest) { |
|
// Avoid duping rest when not necessary |
|
// Copy rest elements and converted keyword hash directly to VM stack |
|
flatten_rest_args(ec, args, locals, &ci_flag); |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_kw || ISEQ_BODY(iseq)->param.flags.has_kwrest) { |
|
given_argc--; |
|
keyword_hash = converted_keyword_hash; |
|
} |
|
else { |
|
locals[args->argc] = converted_keyword_hash; |
|
args->argc += 1; |
|
keyword_hash = Qnil; |
|
kw_flag = 0; |
|
} |
|
} |
|
else { |
|
if (rest_last != converted_keyword_hash) { |
|
rest_last = converted_keyword_hash; |
|
arg_rest_dup(args); |
|
RARRAY_ASET(args->rest, len - 1, rest_last); |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.ruby2_keywords && rest_last) { |
|
flag_keyword_hash = rest_last; |
|
} |
|
else if (ISEQ_BODY(iseq)->param.flags.has_kw || ISEQ_BODY(iseq)->param.flags.has_kwrest) { |
|
arg_rest_dup(args); |
|
rb_ary_pop(args->rest); |
|
given_argc--; |
|
keyword_hash = rest_last; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
else { |
|
args->rest = Qfalse; |
|
|
|
if (args->argc > 0 && (kw_flag & VM_CALL_KW_SPLAT)) { |
|
// f(**kw) |
|
VALUE last_arg = args->argv[args->argc-1]; |
|
if (ignore_keyword_hash_p(last_arg, iseq, &kw_flag, &converted_keyword_hash)) { |
|
args->argc--; |
|
given_argc--; |
|
} |
|
else { |
|
if (!(kw_flag & VM_CALL_KW_SPLAT_MUT) && !ISEQ_BODY(iseq)->param.flags.has_kw) { |
|
converted_keyword_hash = rb_hash_dup(converted_keyword_hash); |
|
kw_flag |= VM_CALL_KW_SPLAT_MUT; |
|
} |
|
|
|
if (last_arg != converted_keyword_hash) { |
|
last_arg = converted_keyword_hash; |
|
args->argv[args->argc-1] = last_arg; |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.ruby2_keywords) { |
|
flag_keyword_hash = last_arg; |
|
} |
|
else if (ISEQ_BODY(iseq)->param.flags.has_kw || ISEQ_BODY(iseq)->param.flags.has_kwrest) { |
|
args->argc--; |
|
given_argc--; |
|
keyword_hash = last_arg; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (flag_keyword_hash) { |
|
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"); |
|
} |
|
|
|
switch (arg_setup_type) { |
|
case arg_setup_method: |
|
break; /* do nothing special */ |
|
case arg_setup_block: |
|
if (given_argc == 1 && |
|
allow_autosplat && |
|
!splat_flagged_keyword_hash && |
|
(min_argc > 0 || ISEQ_BODY(iseq)->param.opt_num > 1) && |
|
!ISEQ_BODY(iseq)->param.flags.ambiguous_param0 && |
|
!((ISEQ_BODY(iseq)->param.flags.has_kw || |
|
ISEQ_BODY(iseq)->param.flags.has_kwrest) |
|
&& max_argc == 1) && |
|
args_check_block_arg0(args)) { |
|
given_argc = RARRAY_LENINT(args->rest); |
|
} |
|
break; |
|
} |
|
|
|
/* argc check */ |
|
if (given_argc < min_argc) { |
|
if (arg_setup_type == arg_setup_block) { |
|
CHECK_VM_STACK_OVERFLOW(ec->cfp, min_argc); |
|
given_argc = min_argc; |
|
args_extend(args, min_argc); |
|
} |
|
else { |
|
argument_arity_error(ec, iseq, cme, given_argc, min_argc, max_argc); |
|
} |
|
} |
|
|
|
if (given_argc > max_argc && max_argc != UNLIMITED_ARGUMENTS) { |
|
if (arg_setup_type == arg_setup_block) { |
|
/* truncate */ |
|
args_reduce(args, given_argc - max_argc); |
|
given_argc = max_argc; |
|
} |
|
else { |
|
argument_arity_error(ec, iseq, cme, given_argc, min_argc, max_argc); |
|
} |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_lead) { |
|
args_setup_lead_parameters(args, ISEQ_BODY(iseq)->param.lead_num, locals + 0); |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_rest || ISEQ_BODY(iseq)->param.flags.has_post){ |
|
args_copy(args); |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_post) { |
|
args_setup_post_parameters(args, ISEQ_BODY(iseq)->param.post_num, locals + ISEQ_BODY(iseq)->param.post_start); |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_opt) { |
|
int opt = args_setup_opt_parameters(args, ISEQ_BODY(iseq)->param.opt_num, locals + ISEQ_BODY(iseq)->param.lead_num); |
|
opt_pc = (int)ISEQ_BODY(iseq)->param.opt_table[opt]; |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_rest) { |
|
if (UNLIKELY(ISEQ_BODY(iseq)->param.flags.anon_rest && args->argc == 0 && !args->rest && !ISEQ_BODY(iseq)->param.flags.has_post)) { |
|
*(locals + ISEQ_BODY(iseq)->param.rest_start) = args->rest = rb_cArray_empty_frozen; |
|
args->rest_index = 0; |
|
} |
|
else { |
|
args_setup_rest_parameter(args, locals + ISEQ_BODY(iseq)->param.rest_start); |
|
VALUE ary = *(locals + ISEQ_BODY(iseq)->param.rest_start); |
|
VALUE index = RARRAY_LEN(ary) - 1; |
|
if (splat_flagged_keyword_hash && |
|
!ISEQ_BODY(iseq)->param.flags.ruby2_keywords && |
|
!ISEQ_BODY(iseq)->param.flags.has_kw && |
|
!ISEQ_BODY(iseq)->param.flags.has_kwrest && |
|
RARRAY_AREF(ary, index) == splat_flagged_keyword_hash) { |
|
((struct RHash *)rest_last)->basic.flags &= ~RHASH_PASS_AS_KEYWORDS; |
|
RARRAY_ASET(ary, index, rest_last); |
|
} |
|
} |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_kw) { |
|
VALUE * const klocals = locals + ISEQ_BODY(iseq)->param.keyword->bits_start - ISEQ_BODY(iseq)->param.keyword->num; |
|
|
|
if (args->kw_argv != NULL) { |
|
const struct rb_callinfo_kwarg *kw_arg = args->kw_arg; |
|
args_setup_kw_parameters(ec, iseq, cme, args->kw_argv, kw_arg->keyword_len, kw_arg->keywords, klocals); |
|
} |
|
else if (!NIL_P(keyword_hash)) { |
|
bool remove_hash_value = false; |
|
if (ISEQ_BODY(iseq)->param.flags.has_kwrest) { |
|
keyword_hash = check_kwrestarg(keyword_hash, &kw_flag); |
|
remove_hash_value = true; |
|
} |
|
args_setup_kw_parameters_from_kwsplat(ec, iseq, cme, keyword_hash, klocals, remove_hash_value); |
|
} |
|
else { |
|
#if VM_CHECK_MODE > 0 |
|
if (args_argc(args) != 0) { |
|
VM_ASSERT(ci_flag & VM_CALL_ARGS_SPLAT); |
|
VM_ASSERT(!(ci_flag & (VM_CALL_KWARG | VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT))); |
|
VM_ASSERT(!kw_flag); |
|
VM_ASSERT(!ISEQ_BODY(iseq)->param.flags.has_rest); |
|
VM_ASSERT(RARRAY_LENINT(args->rest) > 0); |
|
VM_ASSERT(RB_TYPE_P(rest_last, T_HASH)); |
|
VM_ASSERT(FL_TEST_RAW(rest_last, RHASH_PASS_AS_KEYWORDS)); |
|
VM_ASSERT(args_argc(args) == 1); |
|
} |
|
#endif |
|
args_setup_kw_parameters(ec, iseq, cme, NULL, 0, NULL, klocals); |
|
} |
|
} |
|
else if (ISEQ_BODY(iseq)->param.flags.has_kwrest) { |
|
args_setup_kw_rest_parameter(keyword_hash, locals + ISEQ_BODY(iseq)->param.keyword->rest_start, |
|
kw_flag, ISEQ_BODY(iseq)->param.flags.anon_kwrest); |
|
} |
|
else if (!NIL_P(keyword_hash) && RHASH_SIZE(keyword_hash) > 0 && arg_setup_type == arg_setup_method) { |
|
argument_kw_error(ec, iseq, cme, "unknown", rb_hash_keys(keyword_hash)); |
|
} |
|
|
|
if (ISEQ_BODY(iseq)->param.flags.has_block) { |
|
if (ISEQ_BODY(iseq)->local_iseq == iseq) { |
|
/* Do nothing */ |
|
} |
|
else { |
|
args_setup_block_parameter(ec, calling, locals + ISEQ_BODY(iseq)->param.block_start); |
|
} |
|
} |
|
|
|
#if 0 |
|
{ |
|
int i; |
|
for (i=0; i<ISEQ_BODY(iseq)->param.size; i++) { |
|
ruby_debug_printf("local[%d] = %p\n", i, (void *)locals[i]); |
|
} |
|
} |
|
#endif |
|
|
|
ec->cfp->sp = orig_sp; |
|
return opt_pc; |
|
} |
From liquid-render:
This involves several complex scenarios for when the callee takes keyword params.
I added some tests for these:
This will require setup like what is done in the interpreter's
setup_parameters_complex:ruby/vm_args.c
Lines 590 to 976 in f100298
and yjit's
gen_iseq_kw_call:ruby/yjit/src/codegen.rs
Lines 8426 to 8663 in f100298