зеркало из https://github.com/github/ruby.git
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 revisiona7d467a792
) [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 master7e8802fa5b
) [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 ```
This commit is contained in:
Родитель
0463c5806a
Коммит
e87d088291
192
compile.c
192
compile.c
|
@ -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)
|
||||
int dup_rest, unsigned int *flag_ptr, struct rb_callinfo_kwarg **kwarg_ptr)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
130
vm_args.c
130
vm_args.c
|
@ -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;
|
||||
args->rest = locals[--args->argc];
|
||||
if ((vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) && (vm_ci_flag(ci) & VM_CALL_KW_SPLAT)) {
|
||||
// f(*a, **kw)
|
||||
args->rest_index = 0;
|
||||
len = RARRAY_LENINT(args->rest);
|
||||
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;
|
||||
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)
|
||||
{
|
||||
|
|
265
vm_insnhelper.c
265
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? */
|
||||
|
|
Загрузка…
Ссылка в новой задаче