diff options
author | Koichi Sasada <[email protected]> | 2023-01-13 17:52:59 +0900 |
---|---|---|
committer | Koichi Sasada <[email protected]> | 2023-03-06 15:03:06 +0900 |
commit | e87d0882910001ef3b0c2ccd43bf00cee8c34a0c (patch) | |
tree | 85a4cab782f6660530e100117183e0cd7d583130 | |
parent | 0463c5806ac63bbd082f4abb1e3ceeae6ffc39ce (diff) |
Change bytecode of `f(*a, **kw)`
`f(*a, **kw)` is compiled to `f([*a, kw])` but it makes an dummy
array, so change it to pass two arguments `a` and `kw` with calling
flags.
```
ruby 3.2.0 (2022-12-29 revision a7d467a792) [x86_64-linux]
Calculating -------------------------------------
foo() 15.354M (± 4.2%) i/s - 77.295M in 5.043650s
dele() 13.439M (± 3.9%) i/s - 67.109M in 5.001974s
dele(*) 6.265M (± 4.5%) i/s - 31.730M in 5.075649s
dele(*a) 6.286M (± 3.3%) i/s - 31.719M in 5.051516s
dele(*a, **kw) 1.926M (± 4.5%) i/s - 9.753M in 5.076487s
dele(*, **) 1.927M (± 4.2%) i/s - 9.710M in 5.048224s
dele(...) 5.871M (± 3.9%) i/s - 29.471M in 5.028023s
forwardable 4.969M (± 4.1%) i/s - 25.233M in 5.087498s
ruby 3.3.0dev (2023-01-13T01:28:00Z master 7e8802fa5b) [x86_64-linux]
Calculating -------------------------------------
foo() 16.354M (± 4.7%) i/s - 81.799M in 5.014561s
dele() 14.256M (± 3.5%) i/s - 71.656M in 5.032883s
dele(*) 6.701M (± 3.8%) i/s - 33.948M in 5.074938s
dele(*a) 6.681M (± 3.3%) i/s - 33.578M in 5.031720s
dele(*a, **kw) 4.200M (± 4.4%) i/s - 21.258M in 5.072583s
dele(*, **) 4.197M (± 5.3%) i/s - 21.322M in 5.096684s
dele(...) 6.039M (± 6.8%) i/s - 30.355M in 5.052662s
forwardable 4.788M (± 3.2%) i/s - 24.033M in 5.024875s
```
-rw-r--r-- | compile.c | 194 | ||||
-rw-r--r-- | gems/bundled_gems | 2 | ||||
-rw-r--r-- | vm_args.c | 128 | ||||
-rw-r--r-- | vm_insnhelper.c | 265 |
4 files changed, 332 insertions, 257 deletions
@@ -4318,11 +4318,13 @@ keyword_node_p(const NODE *const node) static int compile_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, - const NODE *const root_node, - struct rb_callinfo_kwarg **const kw_arg_ptr, - unsigned int *flag) + const NODE *const root_node, + struct rb_callinfo_kwarg **const kw_arg_ptr, + unsigned int *flag) { - if (kw_arg_ptr == NULL) return FALSE; + RUBY_ASSERT(nd_type_p(root_node, NODE_HASH)); + RUBY_ASSERT(kw_arg_ptr != NULL); + RUBY_ASSERT(flag != NULL); if (root_node->nd_head && nd_type_p(root_node->nd_head, NODE_LIST)) { const NODE *node = root_node->nd_head; @@ -4379,8 +4381,7 @@ compile_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, } static int -compile_args(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, - struct rb_callinfo_kwarg **keywords_ptr, unsigned int *flag) +compile_args(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, NODE **kwnode_ptr) { int len = 0; @@ -4389,15 +4390,11 @@ compile_args(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, EXPECT_NODE("compile_args", node, NODE_LIST, -1); } - if (node->nd_next == NULL && keyword_node_p(node->nd_head)) { /* last node */ - if (compile_keyword_arg(iseq, ret, node->nd_head, keywords_ptr, flag)) { - len--; - } - else { - compile_hash(iseq, ret, node->nd_head, TRUE, FALSE); - } + if (node->nd_next == NULL && keyword_node_p(node->nd_head)) { /* last node is kwnode */ + *kwnode_ptr = node->nd_head; } else { + RUBY_ASSERT(!keyword_node_p(node->nd_head)); NO_CHECK(COMPILE_(ret, "array element", node->nd_head, FALSE)); } } @@ -5776,6 +5773,7 @@ add_ensure_iseq(LINK_ANCHOR *const ret, rb_iseq_t *iseq, int is_return) ADD_SEQ(ret, ensure); } +#if RUBY_DEBUG static int check_keyword(const NODE *node) { @@ -5790,65 +5788,111 @@ check_keyword(const NODE *node) return keyword_node_p(node); } +#endif -static VALUE +static int setup_args_core(rb_iseq_t *iseq, LINK_ANCHOR *const args, const NODE *argn, - int dup_rest, unsigned int *flag, struct rb_callinfo_kwarg **keywords) -{ - if (argn) { - switch (nd_type(argn)) { - case NODE_SPLAT: { - NO_CHECK(COMPILE(args, "args (splat)", argn->nd_head)); - ADD_INSN1(args, argn, splatarray, RBOOL(dup_rest)); - if (flag) *flag |= VM_CALL_ARGS_SPLAT; - return INT2FIX(1); + int dup_rest, unsigned int *flag_ptr, struct rb_callinfo_kwarg **kwarg_ptr) +{ + if (!argn) return 0; + + NODE *kwnode = NULL; + + switch (nd_type(argn)) { + case NODE_LIST: { + // f(x, y, z) + int len = compile_args(iseq, args, argn, &kwnode); + RUBY_ASSERT(flag_ptr == NULL || (*flag_ptr & VM_CALL_ARGS_SPLAT) == 0); + + if (kwnode) { + if (compile_keyword_arg(iseq, args, kwnode, kwarg_ptr, flag_ptr)) { + len -= 1; + } + else { + compile_hash(iseq, args, kwnode, TRUE, FALSE); + } } - case NODE_ARGSCAT: - case NODE_ARGSPUSH: { - int next_is_list = (nd_type_p(argn->nd_head, NODE_LIST)); - VALUE argc = setup_args_core(iseq, args, argn->nd_head, 1, NULL, NULL); - if (nd_type_p(argn->nd_body, NODE_LIST)) { - /* This branch is needed to avoid "newarraykwsplat" [Bug #16442] */ - int rest_len = compile_args(iseq, args, argn->nd_body, NULL, NULL); - ADD_INSN1(args, argn, newarray, INT2FIX(rest_len)); - } - else { - NO_CHECK(COMPILE(args, "args (cat: splat)", argn->nd_body)); - } - if (flag) { - *flag |= VM_CALL_ARGS_SPLAT; - /* This is a dirty hack. It traverses the AST twice. - * In a long term, it should be fixed by a redesign of keyword arguments */ - if (check_keyword(argn->nd_body)) - *flag |= VM_CALL_KW_SPLAT; - } - if (nd_type_p(argn, NODE_ARGSCAT)) { - if (next_is_list) { - ADD_INSN1(args, argn, splatarray, Qtrue); - return INT2FIX(FIX2INT(argc) + 1); - } - else { - ADD_INSN1(args, argn, splatarray, Qfalse); - ADD_INSN(args, argn, concatarray); - return argc; - } - } - else { - ADD_INSN1(args, argn, newarray, INT2FIX(1)); - ADD_INSN(args, argn, concatarray); - return argc; - } + + return len; + } + case NODE_SPLAT: { + // f(*a) + NO_CHECK(COMPILE(args, "args (splat)", argn->nd_head)); + ADD_INSN1(args, argn, splatarray, RBOOL(dup_rest)); + if (flag_ptr) *flag_ptr |= VM_CALL_ARGS_SPLAT; + RUBY_ASSERT(flag_ptr == NULL || (*flag_ptr & VM_CALL_KW_SPLAT) == 0); + return 1; + } + case NODE_ARGSCAT: { + if (flag_ptr) *flag_ptr |= VM_CALL_ARGS_SPLAT; + int argc = setup_args_core(iseq, args, argn->nd_head, 1, NULL, NULL); + + if (nd_type_p(argn->nd_body, NODE_LIST)) { + int rest_len = compile_args(iseq, args, argn->nd_body, &kwnode); + if (kwnode) rest_len--; + ADD_INSN1(args, argn, newarray, INT2FIX(rest_len)); } - case NODE_LIST: { - int len = compile_args(iseq, args, argn, keywords, flag); - return INT2FIX(len); + else { + RUBY_ASSERT(!check_keyword(argn->nd_body)); + NO_CHECK(COMPILE(args, "args (cat: splat)", argn->nd_body)); } - default: { - UNKNOWN_NODE("setup_arg", argn, Qnil); + + if (nd_type_p(argn->nd_head, NODE_LIST)) { + ADD_INSN1(args, argn, splatarray, Qtrue); + argc += 1; } - } + else { + ADD_INSN1(args, argn, splatarray, Qfalse); + ADD_INSN(args, argn, concatarray); + } + + // f(..., *a, ..., k1:1, ...) #=> f(..., *[*a, ...], **{k1:1, ...}) + if (kwnode) { + // kwsplat + *flag_ptr |= VM_CALL_KW_SPLAT; + *flag_ptr |= VM_CALL_KW_SPLAT_MUT; + compile_hash(iseq, args, kwnode, TRUE, FALSE); + argc += 1; + } + + return argc; + } + case NODE_ARGSPUSH: { + if (flag_ptr) *flag_ptr |= VM_CALL_ARGS_SPLAT; + int argc = setup_args_core(iseq, args, argn->nd_head, 1, NULL, NULL); + + if (nd_type_p(argn->nd_body, NODE_LIST)) { + int rest_len = compile_args(iseq, args, argn->nd_body, &kwnode); + if (kwnode) rest_len--; + ADD_INSN1(args, argn, newarray, INT2FIX(rest_len)); + ADD_INSN1(args, argn, newarray, INT2FIX(1)); + ADD_INSN(args, argn, concatarray); + } + else { + if (keyword_node_p(argn->nd_body)) { + kwnode = argn->nd_body; + } + else { + NO_CHECK(COMPILE(args, "args (cat: splat)", argn->nd_body)); + ADD_INSN1(args, argn, newarray, INT2FIX(1)); + ADD_INSN(args, argn, concatarray); + } + } + + if (kwnode) { + // f(*a, k:1) + *flag_ptr |= VM_CALL_KW_SPLAT; + *flag_ptr |= VM_CALL_KW_SPLAT_MUT; + compile_hash(iseq, args, kwnode, TRUE, FALSE); + argc += 1; + } + + return argc; + } + default: { + UNKNOWN_NODE("setup_arg", argn, Qnil); + } } - return INT2FIX(0); } static VALUE @@ -5874,11 +5918,11 @@ setup_args(rb_iseq_t *iseq, LINK_ANCHOR *const args, const NODE *argn, dup_rest = 0; } } - ret = setup_args_core(iseq, args, argn->nd_head, dup_rest, flag, keywords); + ret = INT2FIX(setup_args_core(iseq, args, argn->nd_head, dup_rest, flag, keywords)); ADD_SEQ(args, arg_block); } else { - ret = setup_args_core(iseq, args, argn, 0, flag, keywords); + ret = INT2FIX(setup_args_core(iseq, args, argn, 0, flag, keywords)); } return ret; } @@ -8990,25 +9034,13 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i ADD_GETLOCAL(args, node, idx, lvar_level); } ADD_SEND(args, node, id_core_hash_merge_ptr, INT2FIX(i * 2 + 1)); - if (local_body->param.flags.has_rest) { - ADD_INSN1(args, node, newarray, INT2FIX(1)); - ADD_INSN (args, node, concatarray); - --argc; - } flag |= VM_CALL_KW_SPLAT; } else if (local_body->param.flags.has_kwrest) { int idx = local_body->local_table_size - local_kwd->rest_start; ADD_GETLOCAL(args, node, idx, lvar_level); - - if (local_body->param.flags.has_rest) { - ADD_INSN1(args, node, newarray, INT2FIX(1)); - ADD_INSN (args, node, concatarray); - } - else { - argc++; - } - flag |= VM_CALL_KW_SPLAT; + argc++; + flag |= VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT; } } diff --git a/gems/bundled_gems b/gems/bundled_gems index 77ffc52ae0..35ef0a72e9 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -18,5 +18,5 @@ net-smtp 0.3.3 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.2 https://github.com/ruby/prime rbs 3.0.2 https://github.com/ruby/rbs 7a5663f0f433ccd24db32e4dda9dc7754c269247 -typeprof 0.21.6 https://github.com/ruby/typeprof +typeprof 0.21.7 https://github.com/ruby/typeprof debug 1.7.1 https://github.com/ruby/debug 65197361213529fb5f0b5f6ec111b4d2688a3887 @@ -440,9 +440,10 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned if (!RB_TYPE_P(keyword_hash, T_HASH)) { keyword_hash = rb_to_hash_type(keyword_hash); } + if (!(*kw_flag & VM_CALL_KW_SPLAT_MUT) && - (ISEQ_BODY(iseq)->param.flags.has_kwrest || - ISEQ_BODY(iseq)->param.flags.ruby2_keywords)) { + (ISEQ_BODY(iseq)->param.flags.has_kwrest || + ISEQ_BODY(iseq)->param.flags.ruby2_keywords)) { *kw_flag |= VM_CALL_KW_SPLAT_MUT; keyword_hash = rb_hash_dup(keyword_hash); } @@ -520,54 +521,78 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co args->kw_argv = NULL; } - if (vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) { - int len; + if ((vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) && (vm_ci_flag(ci) & 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)) { + flag_keyword_hash = keyword_hash; + rb_ary_push(args->rest, keyword_hash); + keyword_hash = Qnil; + } + else if (!ISEQ_BODY(iseq)->param.flags.has_kwrest && !ISEQ_BODY(iseq)->param.flags.has_kw) { + rb_ary_push(args->rest, keyword_hash); + keyword_hash = Qnil; + } + + int len = RARRAY_LENINT(args->rest); + given_argc += len - 2; + } + else if (vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) { + // f(*a) args->rest_index = 0; - len = RARRAY_LENINT(args->rest); + args->rest = locals[--args->argc]; + int len = RARRAY_LENINT(args->rest); given_argc += len - 1; rest_last = RARRAY_AREF(args->rest, len - 1); if (!kw_flag && len > 0) { if (RB_TYPE_P(rest_last, T_HASH) && (((struct RHash *)rest_last)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { + // def f(**kw); a = [..., kw]; g(*a) splat_flagged_keyword_hash = rest_last; rest_last = rb_hash_dup(rest_last); kw_flag |= VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT; - } - else { - rest_last = 0; - } - } - if (kw_flag & VM_CALL_KW_SPLAT) { - if (ignore_keyword_hash_p(rest_last, iseq, &kw_flag, &converted_keyword_hash)) { - arg_rest_dup(args); - rb_ary_pop(args->rest); - given_argc--; - kw_flag &= ~(VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT); - } - 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) { + if (ignore_keyword_hash_p(rest_last, iseq, &kw_flag, &converted_keyword_hash)) { arg_rest_dup(args); rb_ary_pop(args->rest); given_argc--; - keyword_hash = rest_last; + kw_flag &= ~(VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT); + } + 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 { + rest_last = 0; + } } else { - if (kw_flag & VM_CALL_KW_SPLAT) { + 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--; @@ -590,7 +615,6 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co } } } - args->rest = Qfalse; } if (flag_keyword_hash && RB_TYPE_P(flag_keyword_hash, T_HASH)) { @@ -778,48 +802,6 @@ argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const char raise_argument_error(ec, iseq, rb_keyword_error_new(error, keys)); } -static inline void -vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling) -{ - int argc = calling->argc; - VALUE *argv = cfp->sp - argc; - VALUE ary = argv[argc-1]; - - vm_check_canary(GET_EC(), cfp->sp); - cfp->sp--; - - if (!NIL_P(ary)) { - const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary); - long len = RARRAY_LEN(ary), i; - - CHECK_VM_STACK_OVERFLOW(cfp, len); - - for (i = 0; i < len; i++) { - *cfp->sp++ = ptr[i]; - } - calling->argc += i - 1; - } -} - -static inline void -vm_caller_setup_arg_kw(rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci) -{ - const VALUE *const passed_keywords = vm_ci_kwarg(ci)->keywords; - const int kw_len = vm_ci_kwarg(ci)->keyword_len; - const VALUE h = rb_hash_new_with_size(kw_len); - VALUE *sp = cfp->sp; - int i; - - for (i=0; i<kw_len; i++) { - rb_hash_aset(h, passed_keywords[i], (sp - kw_len)[i]); - } - (sp-kw_len)[0] = h; - - cfp->sp -= kw_len - 1; - calling->argc -= kw_len - 1; - calling->kw_splat = 1; -} - static VALUE vm_to_proc(VALUE proc) { diff --git a/vm_insnhelper.c b/vm_insnhelper.c index a43f6ef94d..b2321ed7c8 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2540,71 +2540,138 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq) ISEQ_BODY(iseq)->param.flags.has_block == FALSE; } -// If true, cc->call needs to include `CALLER_SETUP_ARG` (i.e. can't be skipped in fastpath) -MJIT_STATIC bool -rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci) +static inline void +vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary) { - return IS_ARGS_SPLAT(ci) || IS_ARGS_KW_OR_KW_SPLAT(ci); + vm_check_canary(GET_EC(), cfp->sp); + + if (!NIL_P(ary)) { + const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary); + long len = RARRAY_LEN(ary), i; + + CHECK_VM_STACK_OVERFLOW(cfp, len); + + for (i = 0; i < len; i++) { + *cfp->sp++ = ptr[i]; + } + calling->argc += i; + } } +static inline void +vm_caller_setup_arg_kw(rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci) +{ + const VALUE *const passed_keywords = vm_ci_kwarg(ci)->keywords; + const int kw_len = vm_ci_kwarg(ci)->keyword_len; + const VALUE h = rb_hash_new_with_size(kw_len); + VALUE *sp = cfp->sp; + int i; + + for (i=0; i<kw_len; i++) { + rb_hash_aset(h, passed_keywords[i], (sp - kw_len)[i]); + } + (sp-kw_len)[0] = h; + + cfp->sp -= kw_len - 1; + calling->argc -= kw_len - 1; + calling->kw_splat = 1; +} + +static inline VALUE +vm_caller_setup_keyword_hash(const struct rb_callinfo *ci, VALUE keyword_hash) +{ + if (UNLIKELY(!RB_TYPE_P(keyword_hash, T_HASH))) { + /* Convert a non-hash keyword splat to a new hash */ + keyword_hash = rb_hash_dup(rb_to_hash_type(keyword_hash)); + } + else if (!IS_ARGS_KW_SPLAT_MUT(ci)) { + /* Convert a hash keyword splat to a new hash unless + * a mutable keyword splat was passed. + */ + keyword_hash = rb_hash_dup(keyword_hash); + } + return keyword_hash; +} static inline void CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp, struct rb_calling_info *restrict calling, const struct rb_callinfo *restrict ci) { - if (UNLIKELY(IS_ARGS_SPLAT(ci))) { - VALUE final_hash; - /* This expands the rest argument to the stack. - * So, vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT is now inconsistent. - */ - vm_caller_setup_arg_splat(cfp, calling); - if (!IS_ARGS_KW_OR_KW_SPLAT(ci) && - calling->argc > 0 && - RB_TYPE_P((final_hash = *(cfp->sp - 1)), T_HASH) && - (((struct RHash *)final_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { - *(cfp->sp - 1) = rb_hash_dup(final_hash); - calling->kw_splat = 1; - } - } - if (UNLIKELY(IS_ARGS_KW_OR_KW_SPLAT(ci))) { - if (IS_ARGS_KEYWORD(ci)) { - /* This converts VM_CALL_KWARG style to VM_CALL_KW_SPLAT style - * by creating a keyword hash. - * So, vm_ci_flag(ci) & VM_CALL_KWARG is now inconsistent. - */ - vm_caller_setup_arg_kw(cfp, calling, ci); + if (UNLIKELY(IS_ARGS_SPLAT(ci) && IS_ARGS_KW_SPLAT(ci))) { + // f(*a, **kw) + VM_ASSERT(calling->kw_splat == 1); + + cfp->sp -= 2; + calling->argc -= 2; + VALUE ary = cfp->sp[0]; + VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]); + + // splat a + vm_caller_setup_arg_splat(cfp, calling, ary); + + // put kw + if (!RHASH_EMPTY_P(kwh)) { + cfp->sp[0] = kwh; + cfp->sp++; + calling->argc++; + + VM_ASSERT(calling->kw_splat == 1); } else { - VALUE keyword_hash = cfp->sp[-1]; - if (!RB_TYPE_P(keyword_hash, T_HASH)) { - /* Convert a non-hash keyword splat to a new hash */ - cfp->sp[-1] = rb_hash_dup(rb_to_hash_type(keyword_hash)); + calling->kw_splat = 0; + } + } + else if (UNLIKELY(IS_ARGS_SPLAT(ci))) { + // f(*a) + VM_ASSERT(calling->kw_splat == 0); + + cfp->sp -= 1; + calling->argc -= 1; + VALUE ary = cfp->sp[0]; + + vm_caller_setup_arg_splat(cfp, calling, ary); + + // check the last argument + VALUE last_hash; + if (!IS_ARGS_KEYWORD(ci) && + calling->argc > 0 && + RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) && + (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { + + if (RHASH_EMPTY_P(last_hash)) { + calling->argc--; + cfp->sp -= 1; } - else if (!IS_ARGS_KW_SPLAT_MUT(ci)) { - /* Convert a hash keyword splat to a new hash unless - * a mutable keyword splat was passed. - */ - cfp->sp[-1] = rb_hash_dup(keyword_hash); + else { + cfp->sp[-1] = rb_hash_dup(last_hash); + calling->kw_splat = 1; } } } -} + else if (UNLIKELY(IS_ARGS_KW_SPLAT(ci))) { + // f(**kw) + VM_ASSERT(calling->kw_splat == 1); + VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[-1]); -static inline void -CALLER_REMOVE_EMPTY_KW_SPLAT(struct rb_control_frame_struct *restrict cfp, - struct rb_calling_info *restrict calling, - const struct rb_callinfo *restrict ci) -{ - if (UNLIKELY(calling->kw_splat)) { - /* This removes the last Hash object if it is empty. - * So, vm_ci_flag(ci) & VM_CALL_KW_SPLAT is now inconsistent. - */ - if (RHASH_EMPTY_P(cfp->sp[-1])) { + if (RHASH_EMPTY_P(kwh)) { cfp->sp--; calling->argc--; calling->kw_splat = 0; } + else { + cfp->sp[-1] = kwh; + } + } + else if (UNLIKELY(IS_ARGS_KEYWORD(ci))) { + // f(k1:1, k2:2) + VM_ASSERT(calling->kw_splat == 0); + + /* This converts VM_CALL_KWARG style to VM_CALL_KW_SPLAT style + * by creating a keyword hash. + * So, vm_ci_flag(ci) & VM_CALL_KWARG is now inconsistent. + */ + vm_caller_setup_arg_kw(cfp, calling, ci); } } @@ -2749,7 +2816,6 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, if (LIKELY(rb_simple_iseq_p(iseq))) { rb_control_frame_t *cfp = ec->cfp; CALLER_SETUP_ARG(cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); if (calling->argc != ISEQ_BODY(iseq)->param.lead_num) { argument_arity_error(ec, iseq, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num); @@ -2763,7 +2829,6 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, else if (rb_iseq_only_optparam_p(iseq)) { rb_control_frame_t *cfp = ec->cfp; CALLER_SETUP_ARG(cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); const int lead_num = ISEQ_BODY(iseq)->param.lead_num; const int opt_num = ISEQ_BODY(iseq)->param.opt_num; @@ -3296,6 +3361,13 @@ vm_call_cfunc_with_frame_(rb_execution_context_t *ec, rb_control_frame_t *reg_cf return val; } +// If true, cc->call needs to include `CALLER_SETUP_ARG` (i.e. can't be skipped in fastpath) +MJIT_STATIC bool +rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci) +{ + return IS_ARGS_SPLAT(ci) || IS_ARGS_KW_OR_KW_SPLAT(ci); +} + static VALUE vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling) { @@ -3311,14 +3383,27 @@ vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp #endif static VALUE -vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling) +vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci) { + const bool kwsplat_p = IS_ARGS_KW_SPLAT(ci); int argc = calling->argc; VALUE *argv = cfp->sp - argc; - VALUE ary = argv[argc-1]; + VALUE ary = argv[argc - (kwsplat_p ? 2 : 1)]; long len = RARRAY_LEN(ary); if (UNLIKELY(len + argc > VM_ARGC_STACK_MAX)) { + VALUE kwhash; + + if (kwsplat_p) { + // the last argument is kwhash + cfp->sp--; + kwhash = vm_caller_setup_keyword_hash(ci, cfp->sp[0]); + calling->argc--; + argc--; + + VM_ASSERT(calling->kw_splat); + } + vm_check_canary(ec, cfp->sp); const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary); VALUE argv_ary = rb_ary_new_capa(len + argc - 1); @@ -3329,6 +3414,32 @@ vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp cfp->sp[-1] = argv_ary; calling->argc = 1; + if (kwsplat_p) { + if (!RHASH_EMPTY_P(kwhash)) { + rb_ary_push(argv_ary, kwhash); + } + else { + calling->kw_splat = false; + } + } + else if (RARRAY_LEN(argv_ary) > 0) { + // check the last argument + long hash_idx = RARRAY_LEN(argv_ary) - 1; + VALUE last_hash = RARRAY_AREF(argv_ary, hash_idx); + + if (RB_TYPE_P(last_hash, T_HASH) && + (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { + if (RHASH_EMPTY_P(last_hash)) { + rb_ary_pop(argv_ary); + } + else { + last_hash = rb_hash_dup(last_hash); + RARRAY_ASET(argv_ary, hash_idx, last_hash); + calling->kw_splat = 1; + } + } + } + return argv_ary; } else { @@ -3344,54 +3455,11 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb VALUE argv_ary; - if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling))) { - // special case of CALLER_SETUP_ARG - if (!IS_ARGS_KW_OR_KW_SPLAT(ci)) { - long hash_idx = RARRAY_LEN(argv_ary) - 1; - VALUE final_hash = RARRAY_AREF(argv_ary, hash_idx); - - if (RB_TYPE_P(final_hash, T_HASH) && - (((struct RHash *)final_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { - if (RHASH_EMPTY_P(final_hash)) { - rb_ary_pop(argv_ary); - } - else { - final_hash = rb_hash_dup(final_hash); - RARRAY_ASET(argv_ary, hash_idx, final_hash); - calling->kw_splat = 1; - } - } - } - - if (UNLIKELY(IS_ARGS_KW_OR_KW_SPLAT(ci))) { - VM_ASSERT(!IS_ARGS_KEYWORD(ci)); // should be KW_SPLAT - - long hash_idx = RARRAY_LEN(argv_ary) - 1; - VALUE keyword_hash = RARRAY_AREF(argv_ary, hash_idx); - - if (!RB_TYPE_P(keyword_hash, T_HASH)) { - /* Convert a non-hash keyword splat to a new hash */ - RARRAY_ASET(argv_ary, hash_idx, rb_hash_dup(rb_to_hash_type(keyword_hash))); - } - else if (!IS_ARGS_KW_SPLAT_MUT(ci)) { - /* Convert a hash keyword splat to a new hash unless - * a mutable keyword splat was passed. - */ - RARRAY_ASET(argv_ary, hash_idx, rb_hash_dup(keyword_hash)); - } - } - - // special case of CALLER_REMOVE_EMPTY_KW_SPLAT() - if (UNLIKELY(calling->kw_splat)) { - VALUE kw_hash = RARRAY_AREF(argv_ary, RARRAY_LEN(argv_ary)-1); - if (RHASH_EMPTY_P(kw_hash)) { - rb_ary_pop(argv_ary); - calling->kw_splat = false; - } - } + if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling, ci))) { + VM_ASSERT(!IS_ARGS_KEYWORD(ci)); int argc = RARRAY_LENINT(argv_ary); - VALUE *argv = (void *)RARRAY_CONST_PTR_TRANSIENT(argv_ary); + VALUE *argv = (VALUE *)RARRAY_CONST_PTR(argv_ary); VALUE *stack_bottom = reg_cfp->sp - 2; VM_ASSERT(calling->argc == 1); @@ -3402,7 +3470,6 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb } else { CALLER_SETUP_ARG(reg_cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(reg_cfp, calling, ci); CC_SET_FASTPATH(calling->cc, vm_call_cfunc_with_frame, !rb_splat_or_kwargs_p(ci) && !calling->kw_splat); return vm_call_cfunc_with_frame(ec, reg_cfp, calling); @@ -3992,14 +4059,12 @@ vm_call_optimized(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb return vm_call_opt_block_call(ec, cfp, calling); case OPTIMIZED_METHOD_TYPE_STRUCT_AREF: CALLER_SETUP_ARG(cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); rb_check_arity(calling->argc, 0, 0); CC_SET_FASTPATH(cc, vm_call_opt_struct_aref, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE)); return vm_call_opt_struct_aref(ec, cfp, calling); case OPTIMIZED_METHOD_TYPE_STRUCT_ASET: CALLER_SETUP_ARG(cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); rb_check_arity(calling->argc, 1, 1); CC_SET_FASTPATH(cc, vm_call_opt_struct_aset, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE)); return vm_call_opt_struct_aset(ec, cfp, calling); @@ -4041,7 +4106,6 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st case VM_METHOD_TYPE_ATTRSET: CALLER_SETUP_ARG(cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); rb_check_arity(calling->argc, 1, 1); @@ -4077,7 +4141,6 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st case VM_METHOD_TYPE_IVAR: CALLER_SETUP_ARG(cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); rb_check_arity(calling->argc, 0, 0); vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID); const unsigned int ivar_mask = (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT); @@ -4458,7 +4521,6 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca VALUE arg0; CALLER_SETUP_ARG(cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); if (arg_setup_type == arg_setup_block && calling->argc == 1 && @@ -4558,7 +4620,6 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, int argc; const struct rb_captured_block *captured = VM_BH_TO_IFUNC_BLOCK(block_handler); CALLER_SETUP_ARG(ec->cfp, calling, ci); - CALLER_REMOVE_EMPTY_KW_SPLAT(ec->cfp, calling, ci); argc = calling->argc; val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL); POPN(argc); /* TODO: should put before C/yield? */ |