-
Notifications
You must be signed in to change notification settings - Fork 13
Open
Open
Copy link
Milestone
Description
From liquid-render:
Top-6 popular complex argument-parameter features not optimized (100.0% of total 3,997,781):
param_kw: 1,476,228 (36.9%)
caller_splat: 1,461,387 (36.6%)
caller_kwarg: 808,697 (20.2%)
param_opt: 191,528 ( 4.8%)
caller_blockarg: 29,971 ( 0.7%)
param_rest: 29,970 ( 0.7%)
This involves several complex scenarios for when the callee takes keyword params.
I added some tests for these:
- ZJIT: Add individual tests for complex arg pass counters ruby/ruby#15159
- ZJIT: Add tests for sending to methods with keyword args ruby/ruby#15183
This will require setup like what is done in the interpreter's setup_parameters_complex:
Lines 590 to 976 in f100298
| 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; | |
| } |
and yjit's gen_iseq_kw_call:
Lines 8426 to 8663 in f100298
| // Codegen for keyword argument handling. Essentially private to gen_send_iseq() since | |
| // there are a lot of preconditions to check before reaching this code. | |
| fn gen_iseq_kw_call( | |
| jit: &mut JITState, | |
| asm: &mut Assembler, | |
| ci_kwarg: *const rb_callinfo_kwarg, | |
| iseq: *const rb_iseq_t, | |
| mut argc: i32, | |
| has_kwrest: bool, | |
| ) -> i32 { | |
| let caller_keyword_len_i32: i32 = if ci_kwarg.is_null() { | |
| 0 | |
| } else { | |
| unsafe { get_cikw_keyword_len(ci_kwarg) } | |
| }; | |
| let caller_keyword_len: usize = caller_keyword_len_i32.try_into().unwrap(); | |
| let anon_kwrest = unsafe { rb_get_iseq_flags_anon_kwrest(iseq) && !get_iseq_flags_has_kw(iseq) }; | |
| // This struct represents the metadata about the callee-specified | |
| // keyword parameters. | |
| let keyword = unsafe { get_iseq_body_param_keyword(iseq) }; | |
| asm_comment!(asm, "keyword args"); | |
| // This is the list of keyword arguments that the callee specified | |
| // in its initial declaration. | |
| let callee_kwargs = unsafe { (*keyword).table }; | |
| let callee_kw_count_i32: i32 = unsafe { (*keyword).num }; | |
| let callee_kw_count: usize = callee_kw_count_i32.try_into().unwrap(); | |
| let keyword_required_num: usize = unsafe { (*keyword).required_num }.try_into().unwrap(); | |
| // Here we're going to build up a list of the IDs that correspond to | |
| // the caller-specified keyword arguments. If they're not in the | |
| // same order as the order specified in the callee declaration, then | |
| // we're going to need to generate some code to swap values around | |
| // on the stack. | |
| let mut kwargs_order: Vec<ID> = vec![0; cmp::max(caller_keyword_len, callee_kw_count)]; | |
| for kwarg_idx in 0..caller_keyword_len { | |
| let sym = unsafe { get_cikw_keywords_idx(ci_kwarg, kwarg_idx.try_into().unwrap()) }; | |
| kwargs_order[kwarg_idx] = unsafe { rb_sym2id(sym) }; | |
| } | |
| let mut unspecified_bits = 0; | |
| // The stack_opnd() index to the 0th keyword argument. | |
| let kwargs_stack_base = caller_keyword_len_i32 - 1; | |
| // Build the keyword rest parameter hash before we make any changes to the order of | |
| // the supplied keyword arguments | |
| let kwrest_type = if has_kwrest { | |
| c_callable! { | |
| fn build_kw_rest(rest_mask: u64, stack_kwargs: *const VALUE, keywords: *const rb_callinfo_kwarg) -> VALUE { | |
| if keywords.is_null() { | |
| return unsafe { rb_hash_new() }; | |
| } | |
| // Use the total number of supplied keywords as a size upper bound | |
| let keyword_len = unsafe { (*keywords).keyword_len } as usize; | |
| let hash = unsafe { rb_hash_new_with_size(keyword_len as u64) }; | |
| // Put pairs into the kwrest hash as the mask describes | |
| for kwarg_idx in 0..keyword_len { | |
| if (rest_mask & (1 << kwarg_idx)) != 0 { | |
| unsafe { | |
| let keyword_symbol = (*keywords).keywords.as_ptr().add(kwarg_idx).read(); | |
| let keyword_value = stack_kwargs.add(kwarg_idx).read(); | |
| rb_hash_aset(hash, keyword_symbol, keyword_value); | |
| } | |
| } | |
| } | |
| return hash; | |
| } | |
| } | |
| asm_comment!(asm, "build kwrest hash"); | |
| // Make a bit mask describing which keywords should go into kwrest. | |
| let mut rest_mask: u64 = 0; | |
| // Index for one argument that will go into kwrest. | |
| let mut rest_collected_idx = None; | |
| for (supplied_kw_idx, &supplied_kw) in kwargs_order.iter().take(caller_keyword_len).enumerate() { | |
| let mut found = false; | |
| for callee_idx in 0..callee_kw_count { | |
| let callee_kw = unsafe { callee_kwargs.add(callee_idx).read() }; | |
| if callee_kw == supplied_kw { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if !found { | |
| rest_mask |= 1 << supplied_kw_idx; | |
| if rest_collected_idx.is_none() { | |
| rest_collected_idx = Some(supplied_kw_idx as i32); | |
| } | |
| } | |
| } | |
| let (kwrest, kwrest_type) = if rest_mask == 0 && anon_kwrest { | |
| // In case the kwrest hash should be empty and is anonymous in the callee, | |
| // we can pass nil instead of allocating. Anonymous kwrest can only be | |
| // delegated, and nil is the same as an empty hash when delegating. | |
| (Qnil.into(), Type::Nil) | |
| } else { | |
| // Save PC and SP before allocating | |
| jit_save_pc(jit, asm); | |
| gen_save_sp(asm); | |
| // Build the kwrest hash. `struct rb_callinfo_kwarg` is malloc'd, so no GC concerns. | |
| let kwargs_start = asm.lea(asm.ctx.sp_opnd(-caller_keyword_len_i32)); | |
| let hash = asm.ccall( | |
| build_kw_rest as _, | |
| vec![rest_mask.into(), kwargs_start, Opnd::const_ptr(ci_kwarg.cast())] | |
| ); | |
| (hash, Type::THash) | |
| }; | |
| // The kwrest parameter sits after `unspecified_bits` if the callee specifies any | |
| // keywords. | |
| let stack_kwrest_idx = kwargs_stack_base - callee_kw_count_i32 - i32::from(callee_kw_count > 0); | |
| let stack_kwrest = asm.stack_opnd(stack_kwrest_idx); | |
| // If `stack_kwrest` already has another argument there, we need to stow it elsewhere | |
| // first before putting kwrest there. Use `rest_collected_idx` because that value went | |
| // into kwrest so the slot is now free. | |
| let kwrest_idx = callee_kw_count + usize::from(callee_kw_count > 0); | |
| if let (Some(rest_collected_idx), true) = (rest_collected_idx, kwrest_idx < caller_keyword_len) { | |
| let rest_collected = asm.stack_opnd(kwargs_stack_base - rest_collected_idx); | |
| let mapping = asm.ctx.get_opnd_mapping(stack_kwrest.into()); | |
| asm.mov(rest_collected, stack_kwrest); | |
| asm.ctx.set_opnd_mapping(rest_collected.into(), mapping); | |
| // Update our bookkeeping to inform the reordering step later. | |
| kwargs_order[rest_collected_idx as usize] = kwargs_order[kwrest_idx]; | |
| kwargs_order[kwrest_idx] = 0; | |
| } | |
| // Put kwrest straight into memory, since we might pop it later | |
| asm.ctx.dealloc_reg(stack_kwrest.reg_opnd()); | |
| asm.mov(stack_kwrest, kwrest); | |
| if stack_kwrest_idx >= 0 { | |
| asm.ctx.set_opnd_mapping(stack_kwrest.into(), TempMapping::MapToStack(kwrest_type)); | |
| } | |
| Some(kwrest_type) | |
| } else { | |
| None | |
| }; | |
| // Ensure the stack is large enough for the callee | |
| for _ in caller_keyword_len..callee_kw_count { | |
| argc += 1; | |
| asm.stack_push(Type::Unknown); | |
| } | |
| // Now this is the stack_opnd() index to the 0th keyword argument. | |
| let kwargs_stack_base = kwargs_order.len() as i32 - 1; | |
| // Next, we're going to loop through every keyword that was | |
| // specified by the caller and make sure that it's in the correct | |
| // place. If it's not we're going to swap it around with another one. | |
| for kwarg_idx in 0..callee_kw_count { | |
| let callee_kwarg = unsafe { callee_kwargs.add(kwarg_idx).read() }; | |
| // If the argument is already in the right order, then we don't | |
| // need to generate any code since the expected value is already | |
| // in the right place on the stack. | |
| if callee_kwarg == kwargs_order[kwarg_idx] { | |
| continue; | |
| } | |
| // In this case the argument is not in the right place, so we | |
| // need to find its position where it _should_ be and swap with | |
| // that location. | |
| for swap_idx in 0..kwargs_order.len() { | |
| if callee_kwarg == kwargs_order[swap_idx] { | |
| // First we're going to generate the code that is going | |
| // to perform the actual swapping at runtime. | |
| let swap_idx_i32: i32 = swap_idx.try_into().unwrap(); | |
| let kwarg_idx_i32: i32 = kwarg_idx.try_into().unwrap(); | |
| let offset0 = kwargs_stack_base - swap_idx_i32; | |
| let offset1 = kwargs_stack_base - kwarg_idx_i32; | |
| stack_swap(asm, offset0, offset1); | |
| // Next we're going to do some bookkeeping on our end so | |
| // that we know the order that the arguments are | |
| // actually in now. | |
| kwargs_order.swap(kwarg_idx, swap_idx); | |
| break; | |
| } | |
| } | |
| } | |
| // Now that every caller specified kwarg is in the right place, filling | |
| // in unspecified default paramters won't overwrite anything. | |
| for kwarg_idx in keyword_required_num..callee_kw_count { | |
| if kwargs_order[kwarg_idx] != unsafe { callee_kwargs.add(kwarg_idx).read() } { | |
| let default_param_idx = kwarg_idx - keyword_required_num; | |
| let mut default_value = unsafe { (*keyword).default_values.add(default_param_idx).read() }; | |
| if default_value == Qundef { | |
| // Qundef means that this value is not constant and must be | |
| // recalculated at runtime, so we record it in unspecified_bits | |
| // (Qnil is then used as a placeholder instead of Qundef). | |
| unspecified_bits |= 0x01 << default_param_idx; | |
| default_value = Qnil; | |
| } | |
| let default_param = asm.stack_opnd(kwargs_stack_base - kwarg_idx as i32); | |
| let param_type = Type::from(default_value); | |
| asm.mov(default_param, default_value.into()); | |
| asm.ctx.set_opnd_mapping(default_param.into(), TempMapping::MapToStack(param_type)); | |
| } | |
| } | |
| // Pop extra arguments that went into kwrest now that they're at stack top | |
| if has_kwrest && caller_keyword_len > callee_kw_count { | |
| let extra_kwarg_count = caller_keyword_len - callee_kw_count; | |
| asm.stack_pop(extra_kwarg_count); | |
| argc = argc - extra_kwarg_count as i32; | |
| } | |
| // Keyword arguments cause a special extra local variable to be | |
| // pushed onto the stack that represents the parameters that weren't | |
| // explicitly given a value and have a non-constant default. | |
| if callee_kw_count > 0 { | |
| let unspec_opnd = VALUE::fixnum_from_usize(unspecified_bits).as_u64(); | |
| let top = asm.stack_push(Type::Fixnum); | |
| asm.mov(top, unspec_opnd.into()); | |
| argc += 1; | |
| } | |
| // The kwrest parameter sits after `unspecified_bits` | |
| if let Some(kwrest_type) = kwrest_type { | |
| let kwrest = asm.stack_push(kwrest_type); | |
| // We put the kwrest parameter in memory earlier | |
| asm.ctx.dealloc_reg(kwrest.reg_opnd()); | |
| argc += 1; | |
| } | |
| argc | |
| } |
Metadata
Metadata
Assignees
Labels
No labels