diff options
-rw-r--r-- | ChangeLog | 84 | ||||
-rw-r--r-- | class.c | 10 | ||||
-rw-r--r-- | common.mk | 2 | ||||
-rw-r--r-- | compile.c | 403 | ||||
-rw-r--r-- | hash.c | 2 | ||||
-rw-r--r-- | insns.def | 40 | ||||
-rw-r--r-- | internal.h | 2 | ||||
-rw-r--r-- | iseq.c | 40 | ||||
-rw-r--r-- | parse.y | 64 | ||||
-rw-r--r-- | proc.c | 6 | ||||
-rw-r--r-- | vm.c | 2 | ||||
-rw-r--r-- | vm_args.c | 783 | ||||
-rw-r--r-- | vm_core.h | 46 | ||||
-rw-r--r-- | vm_eval.c | 1 | ||||
-rw-r--r-- | vm_insnhelper.c | 561 |
15 files changed, 1375 insertions, 671 deletions
@@ -1,3 +1,87 @@ +Mon Nov 03 03:02:38 2014 Koichi Sasada <ko1@atdot.net> + + * rewrite method/block parameter fitting logic to optimize + keyword arguments/parameters and a splat argument. + [Feature #10440] (Details are described in this ticket) + + Most of complex part is moved to vm_args.c. + + Now, ISeq#to_a does not catch up new instruction format. + + * vm_core.h: change iseq data structures. + + * introduce rb_call_info_kw_arg_t to represent keyword arguments. + * add rb_call_info_t::kw_arg. + * rename rb_iseq_t::arg_post_len to rb_iseq_t::arg_post_num. + * rename rb_iseq_t::arg_keywords to arg_keyword_num. + * rename rb_iseq_t::arg_keyword to rb_iseq_t::arg_keyword_bits. + to represent keyword bitmap parameter index. + This bitmap parameter shows that which keyword parameters are given + or not given (0 for given). + It is refered by `checkkeyword' instruction described bellow. + * rename rb_iseq_t::arg_keyword_check to rb_iseq_t::arg_keyword_rest + to represent keyword rest parameter index. + * add rb_iseq_t::arg_keyword_default_values to represent default + keyword values. + * rename VM_CALL_ARGS_SKIP_SETUP to VM_CALL_ARGS_SIMPLE + to represent + (ci->flag & (SPLAT|BLOCKARG)) && + ci->blockiseq == NULL && + ci->kw_arg == NULL. + + * vm_insnhelper.c, vm_args.c: rewrite with refactoring. + + * rewrite splat argument code. + * rewrite keyword arguments/parameters code. + * merge method and block parameter fitting code into one code base. + + * vm.c, vm_eval.c: catch up these changes. + + * compile.c (new_callinfo): callinfo requires kw_arg parameter. + + * compile.c (compile_array_): check the last argument Hash object or + not. If Hash object and all keys are Symbol literals, they are + compiled to keyword arguments. + + * insns.def (checkkeyword): add new instruction. + This instruction check the availability of corresponding keyword. + + For example, a method "def foo k1: 'v1'; end" is cimpiled to the + following instructions. + + 0000 checkkeyword 2, 0 # check k1 is given. + 0003 branchif 9 # if given, jump to address #9 + 0005 putstring "v1" + 0007 setlocal_OP__WC__0 3 # k1 = 'v1' + 0009 trace 8 + 0011 putnil + 0012 trace 16 + 0014 leave + + * insns.def (opt_send_simple): removed and add new instruction + "opt_send_without_block". + + * parse.y (new_args_tail_gen): reorder variables. + Before this patch, a method "def foo(k1: 1, kr1:, k2: 2, **krest, &b)" + has parameter variables "k1, kr1, k2, &b, internal_id, krest", + but this patch reorders to "kr1, k1, k2, internal_id, krest, &b". + (locate a block variable at last) + + * parse.y (vtable_pop): added. + This function remove latest `n' variables from vtable. + + * iseq.c: catch up iseq data changes. + + * proc.c: ditto. + + * class.c (keyword_error): export as rb_keyword_error(). + + * common.mk: depend vm_args.c for vm.o. + + * hash.c (rb_hash_has_key): export. + + * internal.h: ditto. + Mon Nov 3 02:35:32 2014 Koichi Sasada <ko1@atdot.net> * sample/simple-bench.rb: added to measure performance of simple @@ -1862,9 +1862,9 @@ rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) return argc; } -NORETURN(static void keyword_error(const char *error, VALUE keys)); -static void -keyword_error(const char *error, VALUE keys) +NORETURN(void rb_keyword_error(const char *error, VALUE keys)); +void +rb_keyword_error(const char *error, VALUE keys) { const char *msg = ""; if (RARRAY_LEN(keys) == 1) { @@ -1890,7 +1890,7 @@ unknown_keyword_error(VALUE hash, const ID *table, int keywords) } keys = rb_funcall(hash, rb_intern("keys"), 0, 0); if (!RB_TYPE_P(keys, T_ARRAY)) rb_raise(rb_eArgError, "unknown keyword"); - keyword_error("unknown", keys); + rb_keyword_error("unknown", keys); } static int @@ -1957,7 +1957,7 @@ rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, V rb_ary_push(missing, keyword); } if (!NIL_P(missing)) { - keyword_error("missing", missing); + rb_keyword_error("missing", missing); } } j = i; @@ -824,7 +824,7 @@ iseq.$(OBJEXT): {$(VPATH)}iseq.c {$(VPATH)}gc.h {$(VPATH)}iseq.h \ {$(VPATH)}insns_info.inc {$(VPATH)}node_name.inc {$(VPATH)}internal.h \ {$(VPATH)}vm_opts.h {$(VPATH)}ruby_atomic.h {$(VPATH)}eval_intern.h \ {$(VPATH)}util.h -vm.$(OBJEXT): {$(VPATH)}vm.c {$(VPATH)}gc.h {$(VPATH)}iseq.h \ +vm.$(OBJEXT): {$(VPATH)}vm.c {$(VPATH)}gc.h {$(VPATH)}iseq.h {$(VPATH)}vm_args.c \ {$(VPATH)}eval_intern.h $(RUBY_H_INCLUDES) $(ENCODING_H_INCLUDES) \ $(VM_CORE_H_INCLUDES) {$(VPATH)}vm_method.c {$(VPATH)}vm_eval.c \ {$(VPATH)}vm_insnhelper.c {$(VPATH)}vm_insnhelper.h {$(VPATH)}vm_exec.c \ @@ -202,21 +202,25 @@ r_value(VALUE value) /* Specific Insn factory */ #define ADD_SEND(seq, line, id, argc) \ - ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(0)) + ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(0), NULL) + +#define ADD_SEND_WITH_FLAG(seq, line, id, argc, flag) \ + ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)(flag), NULL) + +#define ADD_SEND_WITH_BLOCK(seq, line, id, argc, block) \ + ADD_SEND_R((seq), (line), (id), (argc), (VALUE)(block), (VALUE)INT2FIX(0), NULL) #define ADD_CALL_RECEIVER(seq, line) \ ADD_INSN((seq), (line), putself) #define ADD_CALL(seq, line, id, argc) \ - ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(VM_CALL_FCALL)) + ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(VM_CALL_FCALL), NULL) #define ADD_CALL_WITH_BLOCK(seq, line, id, argc, block) \ - ADD_SEND_R((seq), (line), (id), (argc), (block), (VALUE)INT2FIX(VM_CALL_FCALL)) + ADD_SEND_R((seq), (line), (id), (argc), (block), (VALUE)INT2FIX(VM_CALL_FCALL), NULL) -#define ADD_SEND_R(seq, line, id, argc, block, flag) \ - ADD_ELEM((seq), (LINK_ELEMENT *) \ - new_insn_send(iseq, (line), \ - (id), (VALUE)(argc), (VALUE)(block), (VALUE)(flag))) +#define ADD_SEND_R(seq, line, id, argc, block, flag, keywords) \ + ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_send(iseq, (line), (id), (VALUE)(argc), (VALUE)(block), (VALUE)(flag), (keywords))) #define ADD_TRACE(seq, line, event) \ do { \ @@ -870,23 +874,33 @@ new_insn_body(rb_iseq_t *iseq, int line_no, enum ruby_vminsn_type insn_id, int a } static rb_call_info_t * -new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned int flag) +new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned int flag, rb_call_info_kw_arg_t *kw_arg) { rb_call_info_t *ci = (rb_call_info_t *)compile_data_alloc(iseq, sizeof(rb_call_info_t)); + ci->mid = mid; ci->flag = flag; ci->orig_argc = argc; ci->argc = argc; + ci->kw_arg = kw_arg; + + if (kw_arg) { + ci->argc += kw_arg->keyword_len; + ci->orig_argc += kw_arg->keyword_len; + } if (block) { GetISeqPtr(block, ci->blockiseq); } else { ci->blockiseq = 0; - if (!(ci->flag & (VM_CALL_ARGS_SPLAT | VM_CALL_ARGS_BLOCKARG))) { - ci->flag |= VM_CALL_ARGS_SKIP_SETUP; - } } + + if (!(ci->flag & (VM_CALL_ARGS_SPLAT | VM_CALL_ARGS_BLOCKARG)) && + ci->blockiseq == NULL && ci->kw_arg == NULL) { + ci->flag |= VM_CALL_ARGS_SIMPLE; + } + ci->method_state = 0; ci->class_serial = 0; ci->blockptr = 0; @@ -899,10 +913,10 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned int flag) } static INSN * -new_insn_send(rb_iseq_t *iseq, int line_no, ID id, VALUE argc, VALUE block, VALUE flag) +new_insn_send(rb_iseq_t *iseq, int line_no, ID id, VALUE argc, VALUE block, VALUE flag, rb_call_info_kw_arg_t *keywords) { VALUE *operands = (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * 1); - operands[0] = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), block, FIX2INT(flag)); + operands[0] = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), block, FIX2INT(flag), keywords); return new_insn_core(iseq, line_no, BIN(send), 1, operands); } @@ -1074,7 +1088,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args) if (args->first_post_arg) { iseq->arg_post_start = get_dyna_var_idx_at_raw(iseq, args->first_post_arg); - iseq->arg_post_len = args->post_args_num; + iseq->arg_post_num = args->post_args_num; } if (args->opt_args) { @@ -1112,44 +1126,59 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args) if (args->kw_args) { NODE *node = args->kw_args; - VALUE keywords = rb_ary_tmp_new(1); - VALUE required = 0; - int i = 0, j, r = 0; + const VALUE default_values = rb_ary_tmp_new(1); + const VALUE complex_mark = rb_str_tmp_new(0); + int kw = 0, rkw = 0, di = 0, i; + + iseq->arg_keyword_bits = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid); - iseq->arg_keyword = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid); - COMPILE(optargs, "kwarg", args->kw_rest_arg); while (node) { - VALUE list = keywords; - if (node->nd_body->nd_value == (NODE *)-1) { - ++r; - if (!required) required = rb_ary_tmp_new(1); - list = required; + NODE *val_node = node->nd_body->nd_value; + VALUE dv; + + if (val_node == (NODE *)-1) { + ++rkw; } - rb_ary_push(list, ID2SYM(node->nd_body->nd_vid)); - COMPILE_POPED(optargs, "kwarg", node); /* nd_type(node) == NODE_KW_ARG */ + else { + if (nd_type(val_node) == NODE_LIT) { + dv = val_node->nd_lit; + iseq_add_mark_object(iseq, dv); + } + else if (nd_type(val_node) == NODE_NIL) { + dv = Qnil; + } + else { + COMPILE_POPED(optargs, "kwarg", node); /* nd_type(node) == NODE_KW_ARG */ + dv = complex_mark; + } + + iseq->arg_keyword_num = ++di; + rb_ary_push(default_values, dv); + } + + kw++; node = node->nd_next; - i += 1; } - iseq->arg_keyword_check = args->kw_rest_arg->nd_cflag != 0; - iseq->arg_keywords = i; - iseq->arg_keyword_required = r; - iseq->arg_keyword_table = ALLOC_N(ID, i); - if (r) { - rb_ary_concat(required, keywords); - keywords = required; - } - for (j = 0; j < i; j++) { - iseq->arg_keyword_table[j] = SYM2ID(RARRAY_AREF(keywords, j)); + + iseq->arg_keyword_num = kw; + iseq->arg_keyword_rest = args->kw_rest_arg->nd_cflag != 0 ? get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_cflag) : -1; + iseq->arg_keyword_required = rkw; + iseq->arg_keyword_table = &iseq->local_table[iseq->arg_keyword_bits - iseq->arg_keyword_num]; + iseq->arg_keyword_default_values = ALLOC_N(VALUE, RARRAY_LEN(default_values)); + + for (i = 0; i < RARRAY_LEN(default_values); i++) { + VALUE dv = RARRAY_AREF(default_values, i); + if (dv == complex_mark) dv = Qundef; + iseq->arg_keyword_default_values[i] = dv; } - ADD_INSN(optargs, nd_line(args->kw_args), pop); } else if (args->kw_rest_arg) { - iseq->arg_keyword = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid); - COMPILE(optargs, "kwarg", args->kw_rest_arg); - ADD_INSN(optargs, nd_line(args->kw_rest_arg), pop); + iseq->arg_keyword_bits = -1; + iseq->arg_keyword_rest = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid); } else { - iseq->arg_keyword = -1; + iseq->arg_keyword_bits = -1; + iseq->arg_keyword_rest = -1; } if (args->pre_init) { /* m_init */ @@ -1175,20 +1204,26 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args) iseq->arg_block = get_dyna_var_idx_at_raw(iseq, block_id); } - if (iseq->arg_opts != 0 || iseq->arg_post_len != 0 || - iseq->arg_rest != -1 || iseq->arg_block != -1 || - iseq->arg_keyword != -1) { + if (iseq->arg_opts != 0 || + iseq->arg_post_num != 0 || + iseq->arg_rest != -1 || + iseq->arg_block != -1 || + iseq->arg_keyword_bits != -1 || + iseq->arg_keyword_rest != -1) { iseq->arg_simple = 0; /* set arg_size: size of arguments */ - if (iseq->arg_keyword != -1) { - iseq->arg_size = iseq->arg_keyword + 1; - } - else if (iseq->arg_block != -1) { + if (iseq->arg_block != -1) { iseq->arg_size = iseq->arg_block + 1; } - else if (iseq->arg_post_len) { - iseq->arg_size = iseq->arg_post_start + iseq->arg_post_len; + else if (iseq->arg_keyword_rest != -1) { + iseq->arg_size = iseq->arg_keyword_rest + 1; + } + else if (iseq->arg_keyword_bits != -1) { + iseq->arg_size = iseq->arg_keyword_bits + 1; + } + else if (iseq->arg_post_num) { + iseq->arg_size = iseq->arg_post_start + iseq->arg_post_num; } else if (iseq->arg_rest != -1) { iseq->arg_size = iseq->arg_rest + 1; @@ -1206,8 +1241,14 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args) } if (iseq->type == ISEQ_TYPE_BLOCK) { - if (iseq->arg_opts == 0 && iseq->arg_post_len == 0 && - iseq->arg_rest == -1 && iseq->arg_keyword == -1) { + if (iseq->arg_opts == 0 && + iseq->arg_post_num == 0 && + iseq->arg_rest == -1 && + iseq->arg_keyword_bits == -1 && + iseq->arg_keyword_rest == -1) { + + /* TODO: why not check block? */ + if (iseq->argc == 1 && last_comma == 0) { /* {|a|} */ iseq->arg_simple |= 0x02; @@ -1812,7 +1853,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal INSN *piobj = (INSN *)get_prev_insn((INSN *)list); enum ruby_vminsn_type previ = piobj->insn_id; - if (previ == BIN(send) || previ == BIN(opt_send_simple) || previ == BIN(invokesuper)) { + if (previ == BIN(send) || previ == BIN(opt_send_without_block) || previ == BIN(invokesuper)) { rb_call_info_t *ci = (rb_call_info_t *)piobj->operands[0]; if (ci->blockiseq == 0) { ci->flag |= VM_CALL_TAILCALL; @@ -1836,7 +1877,7 @@ insn_set_specialized_instruction(rb_iseq_t *iseq, INSN *iobj, int insn_id) } iobj->operands = (VALUE *)compile_data_alloc(iseq, iobj->operand_size * sizeof(VALUE)); iobj->operands[0] = old_operands[0]; - iobj->operands[1] = (VALUE)new_callinfo(iseq, idEq, 1, 0, 0); + iobj->operands[1] = (VALUE)new_callinfo(iseq, idEq, 1, 0, 0, NULL); } return COMPILE_OK; @@ -1849,7 +1890,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) rb_call_info_t *ci = (rb_call_info_t *)OPERAND_AT(iobj, 0); #define SP_INSN(opt) insn_set_specialized_instruction(iseq, iobj, BIN(opt_##opt)) - if (ci->blockiseq == 0 && (ci->flag & ~VM_CALL_ARGS_SKIP_SETUP) == 0) { + if (ci->flag & VM_CALL_ARGS_SIMPLE) { switch (ci->orig_argc) { case 0: switch (ci->mid) { @@ -1884,8 +1925,9 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) break; } } - if (ci->flag & VM_CALL_ARGS_SKIP_SETUP) { - iobj->insn_id = BIN(opt_send_simple); + + if ((ci->flag & VM_CALL_ARGS_BLOCKARG) == 0 && ci->blockiseq == NULL) { + iobj->insn_id = BIN(opt_send_without_block); } } #undef SP_INSN @@ -2266,6 +2308,52 @@ compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * cond, return COMPILE_OK; } +static int +compile_array_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *ret, const NODE * const root_node, rb_call_info_kw_arg_t ** const kw_arg_ptr) +{ + if (kw_arg_ptr == NULL) return FALSE; + + if (nd_type(root_node) == NODE_HASH && root_node->nd_head && nd_type(root_node->nd_head) == NODE_ARRAY) { + NODE *node = root_node->nd_head; + + while (node) { + NODE *key_node = node->nd_head; + + assert(nd_type(node) == NODE_ARRAY); + if (key_node && nd_type(key_node) == NODE_LIT && RB_TYPE_P(key_node->nd_lit, T_SYMBOL)) { + /* can be keywords */ + } + else { + return FALSE; + } + node = node->nd_next; /* skip value node */ + node = node->nd_next; + } + + /* may be keywords */ + node = root_node->nd_head; + { + int len = (int)node->nd_alen / 2; + rb_call_info_kw_arg_t *kw_arg = (rb_call_info_kw_arg_t *)ruby_xmalloc(sizeof(rb_call_info_kw_arg_t) + sizeof(VALUE) * (len - 1)); + ID *keywords = kw_arg->keywords; + int i = 0; + kw_arg->keyword_len = len; + + *kw_arg_ptr = kw_arg; + + for (i=0; node != NULL; i++, node = node->nd_next->nd_next) { + NODE *key_node = node->nd_head; + NODE *val_node = node->nd_next->nd_head; + keywords[i] = SYM2ID(key_node->nd_lit); + COMPILE(ret, "keyword values", val_node); + } + assert(i == len); + return TRUE; + } + } + return FALSE; +} + enum compile_array_type_t { COMPILE_ARRAY_TYPE_ARRAY, COMPILE_ARRAY_TYPE_HASH, @@ -2274,7 +2362,7 @@ enum compile_array_type_t { static int compile_array_(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE* node_root, - enum compile_array_type_t type, int poped) + enum compile_array_type_t type, rb_call_info_kw_arg_t **keywords_ptr, int poped) { NODE *node = node_root; int line = (int)nd_line(node); @@ -2316,7 +2404,12 @@ compile_array_(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE* node_root, opt_p = 0; } - COMPILE_(anchor, "array element", node->nd_head, poped); + if (type == COMPILE_ARRAY_TYPE_ARGS && node->nd_next == NULL /* last node */ && compile_array_keyword_arg(iseq, anchor, node->nd_head, keywords_ptr)) { + len--; + } + else { + COMPILE_(anchor, "array element", node->nd_head, poped); + } } if (opt_p && type != COMPILE_ARRAY_TYPE_ARGS) { @@ -2423,7 +2516,7 @@ compile_array_(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE* node_root, static VALUE compile_array(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE* node_root, enum compile_array_type_t type) { - return compile_array_(iseq, ret, node_root, type, 0); + return compile_array_(iseq, ret, node_root, type, NULL, 0); } static VALUE @@ -3001,7 +3094,7 @@ add_ensure_iseq(LINK_ANCHOR *ret, rb_iseq_t *iseq, int is_return) } static VALUE -setup_args(rb_iseq_t *iseq, LINK_ANCHOR *args, NODE *argn, unsigned int *flag) +setup_args(rb_iseq_t *iseq, LINK_ANCHOR *args, NODE *argn, unsigned int *flag, rb_call_info_kw_arg_t **keywords) { VALUE argc = INT2FIX(0); int nsplat = 0; @@ -3021,6 +3114,7 @@ setup_args(rb_iseq_t *iseq, LINK_ANCHOR *args, NODE *argn, unsigned int *flag) switch (nd_type(argn)) { case NODE_SPLAT: { COMPILE(args, "args (splat)", argn->nd_head); + ADD_INSN1(args, nd_line(argn), splatarray, Qfalse); argc = INT2FIX(1); nsplat++; *flag |= VM_CALL_ARGS_SPLAT; @@ -3033,16 +3127,11 @@ setup_args(rb_iseq_t *iseq, LINK_ANCHOR *args, NODE *argn, unsigned int *flag) INIT_ANCHOR(tmp); COMPILE(tmp, "args (cat: splat)", argn->nd_body); - if (next_is_array && nsplat == 0) { - /* none */ + if (nd_type(argn) == NODE_ARGSCAT) { + ADD_INSN1(tmp, nd_line(argn), splatarray, Qfalse); } else { - if (nd_type(argn) == NODE_ARGSCAT) { - ADD_INSN1(tmp, nd_line(argn), splatarray, Qfalse); - } - else { - ADD_INSN1(tmp, nd_line(argn), newarray, INT2FIX(1)); - } + ADD_INSN1(tmp, nd_line(argn), newarray, INT2FIX(1)); } INSERT_LIST(args_splat, tmp); nsplat++; @@ -3057,10 +3146,11 @@ setup_args(rb_iseq_t *iseq, LINK_ANCHOR *args, NODE *argn, unsigned int *flag) } break; } - case NODE_ARRAY: { - argc = INT2FIX(compile_array(iseq, args, argn, COMPILE_ARRAY_TYPE_ARGS)); - break; - } + case NODE_ARRAY: + { + argc = INT2FIX(compile_array_(iseq, args, argn, COMPILE_ARRAY_TYPE_ARGS, keywords, FALSE)); + break; + } default: { rb_bug("setup_arg: unknown node: %s\n", ruby_node_name(nd_type(argn))); } @@ -3425,8 +3515,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) NEW_CHILD_ISEQVAL(node->nd_body, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, line); - ADD_SEND_R(ret, line, idEach, INT2FIX(0), - iseq->compile_data->current_block, INT2FIX(0)); + ADD_SEND_WITH_BLOCK(ret, line, idEach, INT2FIX(0), iseq->compile_data->current_block); } else { iseq->compile_data->current_block = @@ -3937,12 +4026,12 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) boff = 1; default: INIT_ANCHOR(args); - argc = setup_args(iseq, args, node->nd_args->nd_head, &flag); + argc = setup_args(iseq, args, node->nd_args->nd_head, &flag, NULL); ADD_SEQ(ret, args); } ADD_INSN1(ret, line, dupn, FIXNUM_INC(argc, 1 + boff)); flag |= asgnflag; - ADD_SEND_R(ret, line, idAREF, argc, Qfalse, INT2FIX(flag)); + ADD_SEND_WITH_FLAG(ret, line, idAREF, argc, INT2FIX(flag)); if (id == 0 || id == 1) { /* 0: or, 1: and @@ -3985,14 +4074,12 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop); } - ADD_SEND_R(ret, line, idASET, - argc, Qfalse, INT2FIX(flag)); + ADD_SEND_WITH_FLAG(ret, line, idASET, argc, INT2FIX(flag)); } else { if (boff > 0) ADD_INSN(ret, line, swap); - ADD_SEND_R(ret, line, idASET, - FIXNUM_INC(argc, 1), Qfalse, INT2FIX(flag)); + ADD_SEND_WITH_FLAG(ret, line, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag)); } ADD_INSN(ret, line, pop); ADD_INSNL(ret, line, jump, lfin); @@ -4022,14 +4109,12 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop); } - ADD_SEND_R(ret, line, idASET, - argc, Qfalse, INT2FIX(flag)); + ADD_SEND_WITH_FLAG(ret, line, idASET, argc, INT2FIX(flag)); } else { if (boff > 0) ADD_INSN(ret, line, swap); - ADD_SEND_R(ret, line, idASET, - FIXNUM_INC(argc, 1), Qfalse, INT2FIX(flag)); + ADD_SEND_WITH_FLAG(ret, line, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag)); } ADD_INSN(ret, line, pop); } @@ -4085,8 +4170,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node); ADD_INSN(ret, line, dup); - ADD_SEND_R(ret, line, node->nd_next->nd_vid, - INT2FIX(0), Qfalse, INT2FIX(asgnflag)); + ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_vid, INT2FIX(0), INT2FIX(asgnflag)); if (atype == 0 || atype == 1) { /* 0: OR or 1: AND */ ADD_INSN(ret, line, dup); @@ -4100,8 +4184,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value); ADD_INSN(ret, line, swap); ADD_INSN1(ret, line, topn, INT2FIX(1)); - ADD_SEND_R(ret, line, node->nd_next->nd_aid, - INT2FIX(1), Qfalse, INT2FIX(asgnflag)); + ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_aid, INT2FIX(1), INT2FIX(asgnflag)); ADD_INSNL(ret, line, jump, lfin); ADD_LABEL(ret, lcfin); @@ -4122,8 +4205,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ADD_INSN(ret, line, swap); ADD_INSN1(ret, line, topn, INT2FIX(1)); } - ADD_SEND_R(ret, line, node->nd_next->nd_aid, - INT2FIX(1), Qfalse, INT2FIX(asgnflag)); + ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_aid, INT2FIX(1), INT2FIX(asgnflag)); ADD_INSN(ret, line, pop); } break; @@ -4263,7 +4345,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) node->nd_args->nd_head->nd_lit = str; COMPILE(ret, "recv", node->nd_recv); ADD_INSN2(ret, line, opt_aref_with, - new_callinfo(iseq, idAREF, 1, 0, 0), str); + new_callinfo(iseq, idAREF, 1, 0, 0, NULL), str); if (poped) { ADD_INSN(ret, line, pop); } @@ -4281,6 +4363,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ID mid = node->nd_mid; VALUE argc; unsigned int flag = 0; + rb_call_info_kw_arg_t *keywords = NULL; VALUE parent_block = iseq->compile_data->current_block; iseq->compile_data->current_block = Qfalse; @@ -4360,7 +4443,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) /* args */ if (nd_type(node) != NODE_VCALL) { - argc = setup_args(iseq, args, node->nd_args, &flag); + argc = setup_args(iseq, args, node->nd_args, &flag, &keywords); } else { argc = INT2FIX(0); @@ -4380,8 +4463,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) flag |= VM_CALL_FCALL; } - ADD_SEND_R(ret, line, mid, - argc, parent_block, INT2FIX(flag)); + ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords); if (poped) { ADD_INSN(ret, line, pop); @@ -4393,12 +4475,13 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) DECL_ANCHOR(args); int argc; unsigned int flag = 0; + rb_call_info_kw_arg_t *keywords = NULL; VALUE parent_block = iseq->compile_data->current_block; INIT_ANCHOR(args); iseq->compile_data->current_block = Qfalse; if (nd_type(node) == NODE_SUPER) { - VALUE vargc = setup_args(iseq, args, node->nd_args, &flag); + VALUE vargc = setup_args(iseq, args, node->nd_args, &flag, &keywords); argc = FIX2INT(vargc); } else { @@ -4435,9 +4518,9 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) flag |= VM_CALL_ARGS_SPLAT; } - if (liseq->arg_post_len) { + if (liseq->arg_post_num > 0) { /* post arguments */ - int post_len = liseq->arg_post_len; + int post_len = liseq->arg_post_num; int post_start = liseq->arg_post_start; if (liseq->arg_rest != -1) { @@ -4460,16 +4543,22 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) } } - if (liseq->arg_keyword >= 0) { + if (liseq->arg_keyword_bits >= 0) { /* TODO: support keywords */ int local_size = liseq->local_size; - int idx = local_size - liseq->arg_keyword; argc++; + ADD_INSN1(args, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); - ADD_SEND (args, line, rb_intern("dup"), INT2FIX(0)); - for (i = 0; i < liseq->arg_keywords; ++i) { + + if (liseq->arg_keyword_rest > 0) { + ADD_INSN2(args, line, getlocal, INT2FIX(liseq->local_size - liseq->arg_keyword_rest), INT2FIX(lvar_level)); + ADD_SEND (args, line, rb_intern("dup"), INT2FIX(0)); + } + else { + ADD_INSN1(args, line, newhash, INT2FIX(0)); + } + for (i = 0; i < liseq->arg_keyword_num; ++i) { ID id = liseq->arg_keyword_table[i]; - idx = local_size - get_local_var_idx(liseq, id); + int idx = local_size - get_local_var_idx(liseq, id); ADD_INSN1(args, line, putobject, ID2SYM(id)); ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); } @@ -4480,6 +4569,17 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) --argc; } } + else if (liseq->arg_keyword_rest >= 0) { + ADD_INSN2(args, line, getlocal, INT2FIX(liseq->local_size - liseq->arg_keyword_rest), INT2FIX(lvar_level)); + ADD_SEND (args, line, rb_intern("dup"), INT2FIX(0)); + if (liseq->arg_rest != -1) { + ADD_INSN1(args, line, newarray, INT2FIX(1)); + ADD_INSN (args, line, concatarray); + } + else { + argc++; + } + } } } @@ -4487,7 +4587,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ADD_INSN1(ret, line, putobject, nd_type(node) == NODE_ZSUPER ? Qfalse : Qtrue); ADD_SEQ(ret, args); ADD_INSN1(ret, line, invokesuper, new_callinfo(iseq, 0, argc, parent_block, - flag | VM_CALL_SUPER | VM_CALL_FCALL)); + flag | VM_CALL_SUPER | VM_CALL_FCALL, keywords)); if (poped) { ADD_INSN(ret, line, pop); @@ -4495,7 +4595,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) break; } case NODE_ARRAY:{ - compile_array_(iseq, ret, node, COMPILE_ARRAY_TYPE_ARRAY, poped); + compile_array_(iseq, ret, node, COMPILE_ARRAY_TYPE_ARRAY, NULL, poped); break; } case NODE_ZARRAY:{ @@ -4582,6 +4682,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) DECL_ANCHOR(args); VALUE argc; unsigned int flag = 0; + rb_call_info_kw_arg_t *keywords = NULL; INIT_ANCHOR(args); if (iseq->type == ISEQ_TYPE_TOP) { @@ -4589,14 +4690,14 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) } if (node->nd_head) { - argc = setup_args(iseq, args, node->nd_head, &flag); + argc = setup_args(iseq, args, node->nd_head, &flag, &keywords); } else { argc = INT2FIX(0); } ADD_SEQ(ret, args); - ADD_INSN1(ret, line, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), 0, flag)); + ADD_INSN1(ret, line, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), 0, flag, keywords)); if (poped) { ADD_INSN(ret, line, pop); @@ -4720,7 +4821,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) else { ADD_SEQ(ret, recv); ADD_SEQ(ret, val); - ADD_INSN1(ret, line, opt_regexpmatch2, new_callinfo(iseq, idEqTilde, 1, 0, 0)); + ADD_INSN1(ret, line, opt_regexpmatch2, new_callinfo(iseq, idEqTilde, 1, 0, 0, NULL)); } } else { @@ -5012,8 +5113,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) /* function call */ ADD_CALL_RECEIVER(ret, line); COMPILE(ret, "colon2#nd_head", node->nd_head); - ADD_CALL(ret, line, node->nd_mid, - INT2FIX(1)); + ADD_CALL(ret, line, node->nd_mid, INT2FIX(1)); } if (poped) { ADD_INSN(ret, line, pop); @@ -5186,43 +5286,34 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) } break; } - case NODE_KW_ARG:{ - LABEL *default_label = NEW_LABEL(line); - LABEL *end_label = 0; - int idx, lv, ls; - ID id = node->nd_body->nd_vid; + case NODE_KW_ARG: + { + LABEL *end_label = NEW_LABEL(nd_line(node)); + NODE *default_value = node->nd_body->nd_value; + + if (default_value == (NODE *)-1) { + /* required argument. do nothing */ + rb_bug("unreachable"); + } + else if (nd_type(default_value) == NODE_LIT) { + rb_bug("unreachable"); + } + else { + /* if keywordcheck(_kw_bits, nth_keyword) + * kw = default_value + * end + */ + int kw_bits_idx = iseq->local_size - iseq->arg_keyword_bits; + int keyword_idx = iseq->arg_keyword_num; + + ADD_INSN2(ret, line, checkkeyword, INT2FIX(kw_bits_idx), INT2FIX(keyword_idx)); + ADD_INSNL(ret, line, branchif, end_label); + COMPILE_POPED(ret, "keyword default argument", node->nd_body); + ADD_LABEL(ret, end_label); + } - ADD_INSN(ret, line, dup); - ADD_INSN1(ret, line, putobject, ID2SYM(id)); - ADD_SEND(ret, line, rb_intern("key?"), INT2FIX(1)); - ADD_INSNL(ret, line, branchunless, default_label); - ADD_INSN(ret, line, dup); - ADD_INSN1(ret, line, putobject, ID2SYM(id)); - ADD_SEND(ret, line, rb_intern("delete"), INT2FIX(1)); - switch (nd_type(node->nd_body)) { - case NODE_LASGN: - idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id); - ADD_INSN2(ret, line, setlocal, INT2FIX(idx), INT2FIX(get_lvar_level(iseq))); - break; - case NODE_DASGN: - case NODE_DASGN_CURR: - idx = get_dyna_var_idx(iseq, id, &lv, &ls); - ADD_INSN2(ret, line, setlocal, INT2FIX(ls - idx), INT2FIX(lv)); break; - default: - rb_bug("iseq_compile_each (NODE_KW_ARG): unknown node: %s", ruby_node_name(nd_type(node->nd_body))); - } - if (node->nd_body->nd_value != (NODE *)-1) { - end_label = NEW_LABEL(nd_line(node)); - ADD_INSNL(ret, nd_line(node), jump, end_label); - } - ADD_LABEL(ret, default_label); - if (node->nd_body->nd_value != (NODE *)-1) { - COMPILE_POPED(ret, "keyword default argument", node->nd_body); - ADD_LABEL(ret, end_label); } - break; - } case NODE_DSYM:{ compile_dstr(iseq, ret, node); if (!poped) { @@ -5257,14 +5348,14 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ADD_INSN1(ret, line, topn, INT2FIX(1)); } ADD_INSN2(ret, line, opt_aset_with, - new_callinfo(iseq, idASET, 2, 0, 0), str); + new_callinfo(iseq, idASET, 2, 0, 0, NULL), str); ADD_INSN(ret, line, pop); break; } INIT_ANCHOR(recv); INIT_ANCHOR(args); - argc = setup_args(iseq, args, node->nd_args, &flag); + argc = setup_args(iseq, args, node->nd_args, &flag, NULL); flag |= (asgnflag = COMPILE_RECV(recv, "recv", node)); @@ -5280,7 +5371,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ADD_INSN1(ret, line, topn, INT2FIX(1)); if (flag & VM_CALL_ARGS_SPLAT) { ADD_INSN1(ret, line, putobject, INT2FIX(-1)); - ADD_SEND_R(ret, line, idAREF, INT2FIX(1), Qfalse, INT2FIX(asgnflag)); + ADD_SEND_WITH_FLAG(ret, line, idAREF, INT2FIX(1), INT2FIX(asgnflag)); } ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 3)); ADD_INSN (ret, line, pop); @@ -5288,7 +5379,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) else if (flag & VM_CALL_ARGS_SPLAT) { ADD_INSN(ret, line, dup); ADD_INSN1(ret, line, putobject, INT2FIX(-1)); - ADD_SEND_R(ret, line, idAREF, INT2FIX(1), Qfalse, INT2FIX(asgnflag)); + ADD_SEND_WITH_FLAG(ret, line, idAREF, INT2FIX(1), INT2FIX(asgnflag)); ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2)); ADD_INSN (ret, line, pop); } @@ -5300,7 +5391,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) ADD_SEQ(ret, recv); ADD_SEQ(ret, args); } - ADD_SEND_R(ret, line, node->nd_mid, argc, 0, INT2FIX(flag)); + ADD_SEND_WITH_FLAG(ret, line, node->nd_mid, argc, INT2FIX(flag)); ADD_INSN(ret, line, pop); break; @@ -5731,7 +5822,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *anchor, if (!NIL_P(vorig_argc)) orig_argc = FIX2INT(vorig_argc); if (!NIL_P(vblock)) block = iseq_build_load_iseq(iseq, vblock); } - argv[j] = (VALUE)new_callinfo(iseq, mid, orig_argc, block, flag); + argv[j] = (VALUE)new_callinfo(iseq, mid, orig_argc, block, flag, NULL /* TODO: support keywords */); } break; case TS_ID: @@ -5805,7 +5896,7 @@ rb_iseq_build_from_ary(rb_iseq_t *iseq, VALUE locals, VALUE args, int i = 0; VALUE argc = CHECK_INTEGER(rb_ary_entry(args, i++)); VALUE arg_opt_labels = CHECK_ARRAY(rb_ary_entry(args, i++)); - VALUE arg_post_len = CHECK_INTEGER(rb_ary_entry(args, i++)); + VALUE arg_post_num = CHECK_INTEGER(rb_ary_entry(args, i++)); VALUE arg_post_start = CHECK_INTEGER(rb_ary_entry(args, i++)); VALUE arg_rest = CHECK_INTEGER(rb_ary_entry(args, i++)); VALUE arg_block = CHECK_INTEGER(rb_ary_entry(args, i++)); @@ -5813,7 +5904,7 @@ rb_iseq_build_from_ary(rb_iseq_t *iseq, VALUE locals, VALUE args, iseq->argc = FIX2INT(argc); iseq->arg_rest = FIX2INT(arg_rest); - iseq->arg_post_len = FIX2INT(arg_post_len); + iseq->arg_post_num = FIX2INT(arg_post_num); iseq->arg_post_start = FIX2INT(arg_post_start); iseq->arg_block = FIX2INT(arg_block); iseq->arg_opts = RARRAY_LENINT(arg_opt_labels); @@ -5822,8 +5913,8 @@ rb_iseq_build_from_ary(rb_iseq_t *iseq, VALUE locals, VALUE args, if (iseq->arg_block != -1) { iseq->arg_size = iseq->arg_block + 1; } - else if (iseq->arg_post_len) { - iseq->arg_size = iseq->arg_post_start + iseq->arg_post_len; + else if (iseq->arg_post_num) { + iseq->arg_size = iseq->arg_post_start + iseq->arg_post_num; } else if (iseq->arg_rest != -1) { iseq->arg_size = iseq->arg_rest + 1; @@ -1854,7 +1854,7 @@ rb_hash_values(VALUE hash) * */ -static VALUE +VALUE rb_hash_has_key(VALUE hash, VALUE key) { if (!RHASH(hash)->ntbl) @@ -834,6 +834,30 @@ checkmatch /** @c setting + @e check keywords are specified or not. + @j キーワードが指定されているかどうかチェックする + */ +DEFINE_INSN +checkkeyword +(lindex_t kw_bits_index, rb_num_t keyword_index) +() +(VALUE ret) +{ + const VALUE *ep = GET_EP(); + const VALUE kw_bits = *(ep - kw_bits_index); + + if (FIXNUM_P(kw_bits)) { + int bits = FIX2INT(kw_bits); + ret = (bits & (0x01 << keyword_index)) ? Qfalse : Qtrue; + } + else { + assert(RB_TYPE_P(kw_bits, T_HASH)); + ret = rb_hash_has_key(kw_bits, INT2FIX(keyword_index)) ? Qfalse : Qtrue; + } +} + +/** + @c setting @e trace @j trace 用の命令。 */ @@ -995,8 +1019,7 @@ send (VALUE val) // inc += - (int)(ci->orig_argc + ((ci->flag & VM_CALL_ARGS_BLOCKARG) ? 1 : 0)); { ci->argc = ci->orig_argc; - ci->blockptr = 0; - vm_caller_setup_args(th, reg_cfp, ci); + vm_caller_setup_arg_block(th, reg_cfp, ci, FALSE); vm_search_method(ci, ci->recv = TOPN(ci->argc)); CALL_METHOD(ci); } @@ -1017,15 +1040,16 @@ opt_str_freeze /** @c optimize - @e Invoke method without block, splat - @j Invoke method without block, splat + @e Invoke method without block + @j Invoke method without block */ DEFINE_INSN -opt_send_simple +opt_send_without_block (CALL_INFO ci) (...) (VALUE val) // inc += -ci->orig_argc; { + ci->argc = ci->orig_argc; vm_search_method(ci, ci->recv = TOPN(ci->argc)); CALL_METHOD(ci); } @@ -1042,11 +1066,7 @@ invokesuper (VALUE val) // inc += - (int)(ci->orig_argc + ((ci->flag & VM_CALL_ARGS_BLOCKARG) ? 1 : 0)); { ci->argc = ci->orig_argc; - ci->blockptr = !(ci->flag & VM_CALL_ARGS_BLOCKARG) ? GET_BLOCK_PTR() : 0; - - if (UNLIKELY(!(ci->flag & VM_CALL_ARGS_SKIP_SETUP))) { - vm_caller_setup_args(th, reg_cfp, ci); - } + vm_caller_setup_arg_block(th, reg_cfp, ci, TRUE); ci->recv = GET_SELF(); vm_search_super_method(th, GET_CFP(), ci); CALL_METHOD(ci); diff --git a/internal.h b/internal.h index a076abf4e7..ca3649d0f6 100644 --- a/internal.h +++ b/internal.h @@ -668,6 +668,8 @@ void rb_gc_resurrect(VALUE ptr); /* hash.c */ struct st_table *rb_hash_tbl_raw(VALUE hash); +VALUE rb_hash_has_key(VALUE hash, VALUE key); + #define RHASH_TBL_RAW(h) rb_hash_tbl_raw(h) VALUE rb_hash_keys(VALUE hash); VALUE rb_hash_values(VALUE hash); @@ -67,6 +67,7 @@ iseq_free(void *ptr) RUBY_FREE_ENTER("iseq"); if (ptr) { + int i; iseq = ptr; if (!iseq->orig) { /* It's possible that strings are freed */ @@ -79,10 +80,14 @@ iseq_free(void *ptr) RUBY_FREE_UNLESS_NULL(iseq->line_info_table); RUBY_FREE_UNLESS_NULL(iseq->local_table); RUBY_FREE_UNLESS_NULL(iseq->is_entries); + for (i=0; i<iseq->callinfo_size; i++) { + /* TODO: revisit callinfo data structure */ + rb_call_info_kw_arg_t *kw_arg = iseq->callinfo_entries[i].kw_arg; + RUBY_FREE_UNLESS_NULL(kw_arg); + } RUBY_FREE_UNLESS_NULL(iseq->callinfo_entries); RUBY_FREE_UNLESS_NULL(iseq->catch_table); RUBY_FREE_UNLESS_NULL(iseq->arg_opt_table); - RUBY_FREE_UNLESS_NULL(iseq->arg_keyword_table); compile_data_free(iseq->compile_data); RUBY_FREE_UNLESS_NULL(iseq->iseq); } @@ -259,7 +264,8 @@ prepare_iseq_build(rb_iseq_t *iseq, iseq->type = type; iseq->arg_rest = -1; iseq->arg_block = -1; - iseq->arg_keyword = -1; + iseq->arg_keyword_bits = -1; + iseq->arg_keyword_rest = -1; RB_OBJ_WRITE(iseq->self, &iseq->klass, 0); set_relation(iseq, parent); @@ -1239,6 +1245,9 @@ rb_insn_operand_intern(const rb_iseq_t *iseq, rb_ary_push(ary, rb_sprintf("argc:%d", ci->orig_argc)); + if (ci->kw_arg) { + rb_ary_push(ary, rb_sprintf("kw:%d", ci->kw_arg->keyword_len)); + } if (ci->blockiseq) { if (child) { rb_ary_push(child, ci->blockiseq->self); @@ -1255,7 +1264,7 @@ rb_insn_operand_intern(const rb_iseq_t *iseq, if (ci->flag & VM_CALL_TAILCALL) rb_ary_push(flags, rb_str_new2("TAILCALL")); if (ci->flag & VM_CALL_SUPER) rb_ary_push(flags, rb_str_new2("SUPER")); if (ci->flag & VM_CALL_OPT_SEND) rb_ary_push(flags, rb_str_new2("SNED")); /* maybe not reachable */ - if (ci->flag & VM_CALL_ARGS_SKIP_SETUP) rb_ary_push(flags, rb_str_new2("ARGS_SKIP")); /* maybe not reachable */ + if (ci->flag & VM_CALL_ARGS_SIMPLE) rb_ary_push(flags, rb_str_new2("ARGS_SIMPLE")); /* maybe not reachable */ rb_ary_push(ary, rb_ary_join(flags, rb_str_new2("|"))); } ret = rb_sprintf("<callinfo!%"PRIsVALUE">", rb_ary_join(ary, rb_str_new2(", "))); @@ -1424,11 +1433,12 @@ rb_iseq_disasm(VALUE self) if (tbl) { rb_str_catf(str, "local table (size: %d, argc: %d " - "[opts: %d, rest: %d, post: %d, block: %d, keyword: %d@%d] s%d)\n", + "[opts: %d, rest: %d, post: %d, block: %d, kw: %d@%d, kwrest: %d] s%d)\n", iseqdat->local_size, iseqdat->argc, iseqdat->arg_opts, iseqdat->arg_rest, - iseqdat->arg_post_len, iseqdat->arg_block, - iseqdat->arg_keywords, iseqdat->local_size-iseqdat->arg_keyword, + iseqdat->arg_post_num, iseqdat->arg_block, + iseqdat->arg_keyword_num, iseqdat->local_size - iseqdat->arg_keyword_bits, + iseqdat->arg_keyword_rest, iseqdat->arg_simple); for (i = 0; i < iseqdat->local_table_size; i++) { @@ -1451,7 +1461,7 @@ rb_iseq_disasm(VALUE self) opti, iseqdat->arg_rest == i ? "Rest" : "", (iseqdat->arg_post_start <= i && - i < iseqdat->arg_post_start + iseqdat->arg_post_len) ? "Post" : "", + i < iseqdat->arg_post_start + iseqdat->arg_post_num) ? "Post" : "", iseqdat->arg_block == i ? "Block" : ""); rb_str_catf(str, "[%2d] ", iseqdat->local_size - i); @@ -1746,7 +1756,7 @@ iseq_data_to_ary(rb_iseq_t *iseq) else { rb_ary_push(args, INT2FIX(iseq->argc)); rb_ary_push(args, arg_opt_labels); - rb_ary_push(args, INT2FIX(iseq->arg_post_len)); + rb_ary_push(args, INT2FIX(iseq->arg_post_num)); rb_ary_push(args, INT2FIX(iseq->arg_post_start)); rb_ary_push(args, INT2FIX(iseq->arg_rest)); rb_ary_push(args, INT2FIX(iseq->arg_block)); @@ -1985,7 +1995,7 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) CONST_ID(rest, "rest"); rb_ary_push(args, PARAM(iseq->arg_rest, rest)); } - r = iseq->arg_post_start + iseq->arg_post_len; + r = iseq->arg_post_start + iseq->arg_post_num; if (is_proc) { for (i = iseq->arg_post_start; i < r; i++) { PARAM_TYPE(opt); @@ -1998,7 +2008,7 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) rb_ary_push(args, PARAM(i, req)); } } - if (iseq->arg_keyword != -1) { + if (iseq->arg_keyword_bits != -1) { i = 0; if (iseq->arg_keyword_required) { ID keyreq; @@ -2012,17 +2022,17 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) } } CONST_ID(key, "key"); - for (; i < iseq->arg_keywords; i++) { + for (; i < iseq->arg_keyword_num; i++) { PARAM_TYPE(key); if (rb_id2str(iseq->arg_keyword_table[i])) { rb_ary_push(a, ID2SYM(iseq->arg_keyword_table[i])); } rb_ary_push(args, a); } - if (!iseq->arg_keyword_check) { - CONST_ID(keyrest, "keyrest"); - rb_ary_push(args, PARAM(iseq->arg_keyword, keyrest)); - } + } + if (iseq->arg_keyword_rest >= 0) { + CONST_ID(keyrest, "keyrest"); + rb_ary_push(args, PARAM(iseq->arg_keyword_rest, keyrest)); } if (iseq->arg_block != -1) { CONST_ID(block, "block"); @@ -174,6 +174,15 @@ vtable_add(struct vtable *tbl, ID id) tbl->tbl[tbl->pos++] = id; } +#ifndef RIPPER +static void +vtable_pop(struct vtable *tbl, int n) +{ + if (tbl->pos < n) rb_bug("vtable_pop: unreachable"); + tbl->pos -= n; +} +#endif + static int vtable_included(const struct vtable * tbl, ID id) { @@ -9614,25 +9623,62 @@ new_args_tail_gen(struct parser_params *parser, NODE *k, ID kr, ID b) { int saved_line = ruby_sourceline; struct rb_args_info *args; - NODE *kw_rest_arg = 0; NODE *node; - int check = 0; args = ZALLOC(struct rb_args_info); node = NEW_NODE(NODE_ARGS, 0, 0, args); args->block_arg = b; args->kw_args = k; - if (k && !kr) { - check = 1; - kr = internal_id(); + + if (k) { + /* + * def foo(k1: 1, kr1:, k2: 2, **krest, &b) + * variable order: k1, kr1, k2, &b, internal_id, krest + * #=> <reorder> + * variable order: kr1, k1, k2, internal_id, krest, &b + */ + ID kw_bits; + NODE *kwn = k; + struct vtable *required_kw_vars = vtable_alloc(NULL); + struct vtable *kw_vars = vtable_alloc(NULL); + int i; + + while (kwn) { + NODE *val_node = kwn->nd_body->nd_value; + ID vid = kwn->nd_body->nd_vid; + + if (val_node == (NODE *)-1) { + vtable_add(required_kw_vars, vid); + } + else { + vtable_add(kw_vars, vid); + } + + kwn = kwn->nd_next; + } + + vtable_pop(lvtbl->args, vtable_size(required_kw_vars) + vtable_size(kw_vars) + (b != 0)); + + for (i=0; i<vtable_size(required_kw_vars); i++) arg_var(required_kw_vars->tbl[i]); + for (i=0; i<vtable_size(kw_vars); i++) arg_var(kw_vars->tbl[i]); + vtable_free(required_kw_vars); + vtable_free(kw_vars); + + kw_bits = internal_id(); + arg_var(kw_bits); + if (kr) arg_var(kr); + if (b) arg_var(b); + + args->kw_rest_arg = NEW_DVAR(kw_bits); + args->kw_rest_arg->nd_cflag = kr; } - if (kr) { + else if (kr) { + if (b) vtable_pop(lvtbl->args, 1); /* reorder */ arg_var(kr); - kw_rest_arg = NEW_DVAR(kr); - kw_rest_arg->nd_cflag = check; + if (b) arg_var(b); + args->kw_rest_arg = NEW_DVAR(kr); } - args->kw_rest_arg = kw_rest_arg; ruby_sourceline = saved_line; return node; @@ -848,10 +848,10 @@ static inline int rb_iseq_min_max_arity(const rb_iseq_t *iseq, int *max) { *max = iseq->arg_rest == -1 ? - iseq->argc + iseq->arg_post_len + iseq->arg_opts - - (iseq->arg_opts > 0) + (iseq->arg_keyword != -1) + iseq->argc + iseq->arg_post_num + iseq->arg_opts - + (iseq->arg_opts > 0) + (iseq->arg_keyword_num > 0) + (iseq->arg_keyword_rest >= 0) : UNLIMITED_ARGUMENTS; - return iseq->argc + iseq->arg_post_len + (iseq->arg_keyword_required > 0); + return iseq->argc + iseq->arg_post_num + (iseq->arg_keyword_required > 0); } static int @@ -787,7 +787,7 @@ invoke_block_from_c(rb_thread_t *th, const rb_block_t *block, } opt_pc = vm_yield_setup_args(th, iseq, argc, cfp->sp, blockptr, - (type == VM_FRAME_MAGIC_LAMBDA) ? splattable+1 : 0); + (type == VM_FRAME_MAGIC_LAMBDA ? (splattable ? arg_setup_lambda : arg_setup_method) : arg_setup_block)); if (me != 0) { /* bmethod */ diff --git a/vm_args.c b/vm_args.c new file mode 100644 index 0000000000..fd77bfa4d0 --- /dev/null +++ b/vm_args.c @@ -0,0 +1,783 @@ +/********************************************************************** + + vm_args.c - process method call arguments. + + $Author$ + + Copyright (C) 2014- Yukihiro Matsumoto + +**********************************************************************/ + +struct args_info { + /* basic args info */ + rb_call_info_t *ci; + int argc; + VALUE *argv; + + /* additional args info */ + VALUE *kw_argv; + int rest_index; + VALUE rest; +}; + +enum arg_setup_type { + arg_setup_method, + arg_setup_block, + arg_setup_lambda +}; + +static inline int +args_argc(struct args_info *args) +{ + if (args->rest == Qfalse) { + return args->argc; + } + else { + return args->argc + RARRAY_LENINT(args->rest) - args->rest_index; + } +} + +static inline void +args_extend(struct args_info *args, const int min_argc) +{ + int i; + + if (args->rest) { + args->rest = rb_ary_dup(args->rest); + assert(args->rest_index == 0); + for (i=args->argc + RARRAY_LENINT(args->rest); i<min_argc; i++) { + rb_ary_push(args->rest, Qnil); + } + } + else { + for (i=args->argc; i<min_argc; i++) { + args->argv[args->argc++] = Qnil; + } + } +} + +static inline void +args_reduce(struct args_info *args, int over_argc) +{ + if (args->rest) { + const long len = RARRAY_LEN(args->rest); + + if (len > over_argc) { + args->rest = rb_ary_dup(args->rest); + rb_ary_resize(args->rest, len - over_argc); + return; + } + else { + args->rest = Qfalse; + over_argc -= len; + } + } + + assert(args->argc >= over_argc); + args->argc -= over_argc; +} + +static inline int +args_check_block_arg0(struct args_info *args, rb_thread_t *th, const int msl) +{ + VALUE ary = Qnil; + + if (args->rest && RARRAY_LEN(args->rest) == 1) { + VALUE arg0 = RARRAY_AREF(args->rest, 0); + ary = rb_check_array_type(arg0); + th->mark_stack_len = msl; + } + else if (args->argc == 1) { + VALUE arg0 = args->argv[0]; + ary = rb_check_array_type(arg0); + th->mark_stack_len = msl; + args->argv[0] = arg0; /* see: https://bugs.ruby-lang.org/issues/8484 */ + } + + if (!NIL_P(ary)) { + args->rest = ary; + args->rest_index = 0; + args->argc = 0; + return TRUE; + } + + return FALSE; +} + +static inline void +args_copy(struct args_info *args) +{ + if (args->rest != Qfalse) { + int argc = args->argc; + args->argc = 0; + args->rest = rb_ary_dup(args->rest); /* make dup */ + + /* + * argv: [m0, m1, m2, m3] + * rest: [a0, a1, a2, a3, a4, a5] + * ^ + * rest_index + * + * #=> first loop + * + * argv: [m0, m1] + * rest: [m2, m3, a2, a3, a4, a5] + * ^ + * rest_index + * + * #=> 2nd loop + * + * argv: [] (argc == 0) + * rest: [m0, m1, m2, m3, a2, a3, a4, a5] + * ^ + * rest_index + */ + while (args->rest_index > 0 && argc > 0) { + RARRAY_ASET(args->rest, --args->rest_index, args->argv[--argc]); + } + while (argc > 0) { + rb_ary_unshift(args->rest, args->argv[--argc]); + } + } + else if (args->argc > 0) { + args->rest = rb_ary_new_from_values(args->argc, args->argv); + args->rest_index = 0; + args->argc = 0; + } +} + +static inline const VALUE * +args_rest_argv(struct args_info *args) +{ + return RARRAY_CONST_PTR(args->rest) + args->rest_index; +} + +static inline VALUE +args_rest_array(struct args_info *args) +{ + VALUE ary; + + if (args->rest) { + ary = rb_ary_subseq(args->rest, args->rest_index, RARRAY_LEN(args->rest) - args->rest_index); + args->rest = 0; + } + else { + ary = rb_ary_new(); + } + return ary; +} + +static int +keyword_hash_p(VALUE *kw_hash_ptr, VALUE *rest_hash_ptr, rb_thread_t *th, const int msl) +{ + *rest_hash_ptr = rb_check_hash_type(*kw_hash_ptr); + th->mark_stack_len = msl; + + if (!NIL_P(*rest_hash_ptr)) { + *kw_hash_ptr = rb_extract_keywords(rest_hash_ptr); + return TRUE; + } + else { + *kw_hash_ptr = Qnil; + return FALSE; + } +} + +static VALUE +args_pop_keyword_hash(struct args_info *args, VALUE *kw_hash_ptr, rb_thread_t *th, const int msl) +{ + VALUE rest_hash; + + if (args->rest == Qfalse) { + from_argv: + assert(args->argc > 0); + *kw_hash_ptr = args->argv[args->argc-1]; + + if (keyword_hash_p(kw_hash_ptr, &rest_hash, th, msl)) { + if (rest_hash) { + args->argv[args->argc-1] = rest_hash; + } + else { + args->argc--; + return TRUE; + } + } + } + else { + long len = RARRAY_LEN(args->rest); + + if (len > 0) { + *kw_hash_ptr = RARRAY_AREF(args->rest, len - 1); + + if (keyword_hash_p(kw_hash_ptr, &rest_hash, th, msl)) { + if (rest_hash) { + RARRAY_ASET(args->rest, len - 1, rest_hash); + } + else { + args->rest = rb_ary_dup(args->rest); + rb_ary_pop(args->rest); + return TRUE; + } + } + } + else { + goto from_argv; + } + } + + return FALSE; +} + +static int +args_kw_argv_to_hash(struct args_info *args) +{ + const ID * const passed_keywords = args->ci->kw_arg->keywords; + const int kw_len = args->ci->kw_arg->keyword_len; + VALUE h = rb_hash_new(); + const int kw_start = args->argc - kw_len; + const VALUE * const kw_argv = args->argv + kw_start; + int i; + + args->argc = kw_start + 1; + for (i=0; i<kw_len; i++) { + rb_hash_aset(h, ID2SYM(passed_keywords[i]), kw_argv[i]); + } + + args->argv[args->argc - 1] = h; + + return args->argc; +} + +static void +args_stored_kw_argv_to_hash(struct args_info *args) +{ + VALUE h = rb_hash_new(); + int i; + const ID * const passed_keywords = args->ci->kw_arg->keywords; + const int passed_keyword_len = args->ci->kw_arg->keyword_len; + + for (i=0; i<passed_keyword_len; i++) { + rb_hash_aset(h, ID2SYM(passed_keywords[i]), args->kw_argv[i]); + } + args->kw_argv = NULL; + + if (args->rest) { + args->rest = rb_ary_dup(args->rest); + rb_ary_push(args->rest, h); + } + else { + args->argv[args->argc++] = h; + } +} + +static inline void +args_setup_lead_parameters(struct args_info *args, int argc, VALUE *locals) +{ + if (args->argc >= argc) { + /* do noting */ + args->argc -= argc; + args->argv += argc; + } + else { + int i, j; + const VALUE *argv = args_rest_argv(args); + + for (i=args->argc, j=0; i<argc; i++, j++) { + locals[i] = argv[j]; + } + args->rest_index += argc - args->argc; + args->argc = 0; + } +} + +static inline void +args_setup_post_parameters(struct args_info *args, int argc, VALUE *locals) +{ + long len; + args_copy(args); + len = RARRAY_LEN(args->rest); + MEMCPY(locals, RARRAY_CONST_PTR(args->rest) + len - argc, VALUE, argc); + rb_ary_resize(args->rest, len - argc); +} + +static inline int +args_setup_opt_parameters(struct args_info *args, int opt_max, VALUE *locals) +{ + int i; + + if (args->argc >= opt_max) { + args->argc -= opt_max; + args->argv += opt_max; + i = opt_max; + } + else { + int j; + i = args->argc; + args->argc = 0; + + if (args->rest) { + int len = RARRAY_LENINT(args->rest); + const VALUE *argv = RARRAY_CONST_PTR(args->rest); + + for (; i<opt_max && args->rest_index < len; i++, args->rest_index++) { + locals[i] = argv[args->rest_index]; + } + } + + /* initialize by nil */ + for (j=i; j<opt_max; j++) { + locals[j] = Qnil; + } + } + + return i; +} + +static inline void +args_setup_rest_parameter(struct args_info *args, VALUE *locals) +{ + args_copy(args); + *locals = args_rest_array(args); +} + +static VALUE +make_unused_kw_hash(const ID *passed_keywords, int passed_keyword_len, const VALUE *kw_argv, const int key_only) +{ + int i; + VALUE obj = key_only ? rb_ary_tmp_new(1) : rb_hash_new(); + + for (i=0; i<passed_keyword_len; i++) { + if (kw_argv[i] != Qundef) { + if (key_only) { + rb_ary_push(obj, ID2SYM(passed_keywords[i])); + } + else { + rb_hash_aset(obj, ID2SYM(passed_keywords[i]), kw_argv[i]); + } + } + } + return obj; +} + +void rb_keyword_error(const char *error, VALUE keys); + +static inline int +args_setup_kw_parameters_lookup(const ID key, VALUE *ptr, const ID * const passed_keywords, VALUE *passed_values, const int passed_keyword_len) +{ + int i; + + for (i=0; i<passed_keyword_len; i++) { + if (key == passed_keywords[i]) { + *ptr = passed_values[i]; + passed_values[i] = Qundef; + return TRUE; + } + } + + return FALSE; +} + +static void +args_setup_kw_parameters(VALUE* const passed_values, const int passed_keyword_len, const ID * const passed_keywords, + const rb_iseq_t * const iseq, VALUE * const locals) +{ + const ID *acceptable_keywords = iseq->arg_keyword_table; + const int req_key_num = iseq->arg_keyword_required; + const int key_num = iseq->arg_keyword_num; + const VALUE * const default_values = iseq->arg_keyword_default_values; + VALUE missing = 0; + int i, di, found = 0; + int unspecified_bits = 0; + VALUE unspecified_bits_value = Qnil; + + for (i=0; i<req_key_num; i++) { + ID key = acceptable_keywords[i]; + if (args_setup_kw_parameters_lookup(key, &locals[i], passed_keywords, passed_values, passed_keyword_len)) { + found++; + } + else { + if (!missing) missing = rb_ary_tmp_new(1); + rb_ary_push(missing, ID2SYM(key)); + } + } + + if (missing) rb_keyword_error("missing", missing); + + for (di=0; i<key_num; i++, di++) { + if (args_setup_kw_parameters_lookup(acceptable_keywords[i], &locals[i], passed_keywords, passed_values, passed_keyword_len)) { + found++; + } + else { + if (default_values[di] == Qundef) { + locals[i] = Qnil; + + if (LIKELY(i < 32)) { /* TODO: 32 -> Fixnum's max bits */ + unspecified_bits |= 0x01 << di; + } + else { + if (NIL_P(unspecified_bits_value)) { + /* fixnum -> hash */ + int j; + unspecified_bits_value = rb_hash_new(); + + for (j=0; j<32; j++) { + if (unspecified_bits & (0x01 << j)) { + rb_hash_aset(unspecified_bits_value, INT2FIX(j), Qtrue); + } + } + } + rb_hash_aset(unspecified_bits_value, INT2FIX(di), Qtrue); + } + } + else { + locals[i] = default_values[di]; + } + } + } + + if (iseq->arg_keyword_rest >= 0) { + const int rest_hash_index = key_num + 1; + locals[rest_hash_index] = make_unused_kw_hash(passed_keywords, passed_keyword_len, passed_values, FALSE); + } + else { + if (found != passed_keyword_len) { + VALUE keys = make_unused_kw_hash(passed_keywords, passed_keyword_len, passed_values, TRUE); + rb_keyword_error("unknown", keys); + } + } + + if (NIL_P(unspecified_bits_value)) { + unspecified_bits_value = INT2FIX(unspecified_bits); + } + locals[key_num] = unspecified_bits_value; +} + +static inline void +args_setup_kw_rest_parameter(VALUE keyword_hash, VALUE *locals) +{ + locals[0] = NIL_P(keyword_hash) ? rb_hash_new() : rb_hash_dup(keyword_hash); +} + +static inline void +args_setup_block_parameter(rb_thread_t *th, rb_call_info_t *ci, VALUE *locals) +{ + VALUE blockval = Qnil; + const rb_block_t *blockptr = ci->blockptr; + + if (blockptr) { + /* make Proc object */ + if (blockptr->proc == 0) { + rb_proc_t *proc; + blockval = rb_vm_make_proc(th, blockptr, rb_cProc); + GetProcPtr(blockval, proc); + ci->blockptr = &proc->block; + } + else { + blockval = blockptr->proc; + } + } + *locals = blockval; +} + +struct fill_values_arg { + VALUE *keys; + VALUE *vals; + int argc; +}; + +static int +fill_keys_values(st_data_t key, st_data_t val, st_data_t ptr) +{ + struct fill_values_arg *arg = (struct fill_values_arg *)ptr; + int i = arg->argc++; + arg->keys[i] = SYM2ID((VALUE)key); + arg->vals[i] = (VALUE)val; + return ST_CONTINUE; +} + +NORETURN(static void argument_error(const rb_iseq_t *iseq, int miss_argc, int min_argc, int max_argc)); +static void +argument_error(const rb_iseq_t *iseq, int miss_argc, int min_argc, int max_argc) +{ + rb_thread_t *th = GET_THREAD(); + VALUE exc = rb_arg_error_new(miss_argc, min_argc, max_argc); + VALUE at; + + if (iseq) { + vm_push_frame(th, iseq, VM_FRAME_MAGIC_METHOD, Qnil /* self */, Qnil /* klass */, Qnil /* specval*/, + iseq->iseq_encoded, th->cfp->sp, 0 /* local_size */, 0 /* me */, 0 /* stack_max */); + at = rb_vm_backtrace_object(); + vm_pop_frame(th); + } + else { + at = rb_vm_backtrace_object(); + } + + rb_iv_set(exc, "bt_locations", at); + rb_funcall(exc, rb_intern("set_backtrace"), 1, at); + rb_exc_raise(exc); +} + +static int +setup_parameters_complex(rb_thread_t * const th, const rb_iseq_t * const iseq, rb_call_info_t * const ci, + VALUE * const locals, const enum arg_setup_type arg_setup_type) +{ + const int min_argc = iseq->argc + iseq->arg_post_num; + const int max_argc = (iseq->arg_rest == -1) ? min_argc + (iseq->arg_opts - (iseq->arg_opts > 0)) : UNLIMITED_ARGUMENTS; + int opt_pc = 0; + int given_argc; + struct args_info args_body, *args; + VALUE keyword_hash = Qnil; + const int msl = ci->argc + iseq->arg_size; + + th->mark_stack_len = msl; + + /* setup args */ + args = &args_body; + args->ci = ci; + given_argc = args->argc = ci->argc; + args->argv = locals; + + if (ci->kw_arg) { + if (iseq->arg_keyword_bits >= 0) { + int kw_len = ci->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); + } + } + else { + args->kw_argv = NULL; + } + + if (ci->flag & VM_CALL_ARGS_SPLAT) { + args->rest = locals[--args->argc]; + args->rest_index = 0; + given_argc += RARRAY_LENINT(args->rest) - 1; + } + else { + args->rest = Qfalse; + } + + switch (arg_setup_type) { + case arg_setup_method: + break; /* do nothing special */ + case arg_setup_block: + if (given_argc == 1 && + (min_argc > 0 || + iseq->arg_opts > 2 || iseq->arg_keyword_bits >= 0 || iseq->arg_keyword_rest >= 0) && /* TODO: can be shrink with flags */ + !(iseq->arg_simple & 0x02) && + args_check_block_arg0(args, th, msl)) { + given_argc = RARRAY_LEN(args->rest); + } + break; + case arg_setup_lambda: + if (given_argc == 1 && + given_argc != iseq->argc && + !(iseq->arg_rest >= 0) && + args_check_block_arg0(args, th, msl)) { + given_argc = RARRAY_LEN(args->rest); + } + } + + /* argc check */ + if (given_argc < min_argc) { + if (given_argc == min_argc - 1 && args->kw_argv) { + args_stored_kw_argv_to_hash(args); + given_argc = args_argc(args); + } + else { + if (arg_setup_type == arg_setup_block) { + CHECK_VM_STACK_OVERFLOW(th->cfp, min_argc); + given_argc = min_argc; + args_extend(args, min_argc); + } + else { + argument_error(iseq, given_argc, min_argc, max_argc); + } + } + } + + if (given_argc > min_argc && + (iseq->arg_keyword_bits >= 0 || iseq->arg_keyword_rest >= 0) && + args->kw_argv == NULL) { + if (args_pop_keyword_hash(args, &keyword_hash, th, msl)) { + given_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_error(iseq, given_argc, min_argc, max_argc); + } + } + + if (iseq->argc > 0) { + args_setup_lead_parameters(args, iseq->argc, locals + 0); + } + + if (iseq->arg_post_num > 0) { + args_setup_post_parameters(args, iseq->arg_post_num, locals + iseq->arg_post_start); + } + + if (iseq->arg_opts > 0) { + int opt = args_setup_opt_parameters(args, iseq->arg_opts - 1, locals + iseq->argc); + opt_pc = (int)iseq->arg_opt_table[opt]; + } + + if (iseq->arg_rest >= 0) { + args_setup_rest_parameter(args, locals + iseq->arg_rest); + } + + if (iseq->arg_keyword_bits >= 0) { + VALUE * const klocals = locals + iseq->arg_keyword_bits - iseq->arg_keyword_num; + + if (args->kw_argv != NULL) { + args_setup_kw_parameters(args->kw_argv, args->ci->kw_arg->keyword_len, args->ci->kw_arg->keywords, iseq, klocals); + } + else if (!NIL_P(keyword_hash)) { + int kw_len = rb_long2int(RHASH_SIZE(keyword_hash)); + struct fill_values_arg arg; + /* copy kw_argv */ + arg.keys = args->kw_argv = ALLOCA_N(VALUE, kw_len * 2); + arg.vals = arg.keys + kw_len; + arg.argc = 0; + rb_hash_foreach(keyword_hash, fill_keys_values, (VALUE)&arg); + assert(arg.argc == kw_len); + args_setup_kw_parameters(arg.vals, kw_len, arg.keys, iseq, klocals); + } + else { + assert(args_argc(args) == 0); + args_setup_kw_parameters(NULL, 0, NULL, iseq, klocals); + } + } + else if (iseq->arg_keyword_rest >= 0) { + args_setup_kw_rest_parameter(keyword_hash, locals + iseq->arg_keyword_rest); + } + + if (iseq->arg_block >= 0) { + args_setup_block_parameter(th, ci, locals + iseq->arg_block); + } + +#if 0 + { + int i; + for (i=0; i<iseq->arg_size; i++) { + fprintf(stderr, "local[%d] = %p\n", i, (void *)locals[i]); + } + } +#endif + + th->mark_stack_len = 0; + + return opt_pc; +} + +static inline void +vm_caller_setup_arg_splat(rb_control_frame_t *cfp, rb_call_info_t *ci) +{ + VALUE *argv = cfp->sp - ci->argc; + VALUE ary = argv[ci->argc-1]; + + cfp->sp--; + + if (!NIL_P(ary)) { + const VALUE *ptr = RARRAY_CONST_PTR(ary); + long len = RARRAY_LEN(ary), i; + + CHECK_VM_STACK_OVERFLOW(cfp, len); + + for (i = 0; i < len; i++) { + *cfp->sp++ = ptr[i]; + } + ci->argc += i - 1; + } +} + +static inline void +vm_caller_setup_arg_kw(rb_control_frame_t *cfp, rb_call_info_t *ci) +{ + const ID * const passed_keywords = ci->kw_arg->keywords; + const int kw_len = ci->kw_arg->keyword_len; + const VALUE h = rb_hash_new(); + VALUE *sp = cfp->sp; + int i; + + for (i=0; i<kw_len; i++) { + rb_hash_aset(h, ID2SYM(passed_keywords[i]), (sp - kw_len)[i]); + } + (sp-kw_len)[0] = h; + + cfp->sp -= kw_len - 1; + ci->argc -= kw_len - 1; +} + +#define SAVE_RESTORE_CI(expr, ci) do { \ + int saved_argc = (ci)->argc; rb_block_t *saved_blockptr = (ci)->blockptr; /* save */ \ + expr; \ + (ci)->argc = saved_argc; (ci)->blockptr = saved_blockptr; /* restore */ \ +} while (0) + +static void +vm_caller_setup_arg_block(const rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci, const int is_super) +{ + if (ci->flag & VM_CALL_ARGS_BLOCKARG) { + rb_proc_t *po; + VALUE proc; + + proc = *(--reg_cfp->sp); + + if (proc != Qnil) { + if (!rb_obj_is_proc(proc)) { + VALUE b; + + SAVE_RESTORE_CI(b = rb_check_convert_type(proc, T_DATA, "Proc", "to_proc"), ci); + + if (NIL_P(b) || !rb_obj_is_proc(b)) { + rb_raise(rb_eTypeError, + "wrong argument type %s (expected Proc)", + rb_obj_classname(proc)); + } + proc = b; + } + GetProcPtr(proc, po); + ci->blockptr = &po->block; + RUBY_VM_GET_BLOCK_PTR_IN_CFP(reg_cfp)->proc = proc; + } + else { + ci->blockptr = NULL; + } + } + else if (ci->blockiseq != 0) { /* likely */ + ci->blockptr = RUBY_VM_GET_BLOCK_PTR_IN_CFP(reg_cfp); + ci->blockptr->iseq = ci->blockiseq; + ci->blockptr->proc = 0; + } + else { + if (is_super) { + ci->blockptr = GET_BLOCK_PTR(); + } + else { + ci->blockptr = NULL; + } + } +} + +#define IS_ARGS_SPLAT(ci) ((ci)->flag & VM_CALL_ARGS_SPLAT) +#define IS_ARGS_KEYWORD(ci) ((ci)->kw_arg != NULL) + +#define CALLER_SETUP_ARG(cfp, ci) do { \ + if (UNLIKELY(IS_ARGS_SPLAT(ci))) vm_caller_setup_arg_splat((cfp), (ci)); \ + if (UNLIKELY(IS_ARGS_KEYWORD(ci))) vm_caller_setup_arg_kw((cfp), (ci)); \ +} while (0) @@ -135,13 +135,20 @@ union iseq_inline_storage_entry { struct rb_thread_struct; struct rb_control_frame_struct; +typedef struct rb_call_info_kw_arg_struct { + int keyword_len; + ID keywords[1]; +} rb_call_info_kw_arg_t; + /* rb_call_info_t contains calling information including inline cache */ typedef struct rb_call_info_struct { /* fixed at compile time */ ID mid; + unsigned int flag; int orig_argc; rb_iseq_t *blockiseq; + rb_call_info_kw_arg_t *kw_arg; /* inline cache: keys */ rb_serial_t method_state; @@ -240,23 +247,23 @@ struct rb_iseq_struct { * *c, # rest * d1, d2, ..., dO, # post * e1:(...), e2:(...), ..., eK:(...), # keyword - * **f, # keyword rest + * **f, # keyword_rest * &g) # block * => * - * argc = M // or 0 if no mandatory arg - * arg_opts = N+1 // or 0 if no optional arg - * arg_rest = M+N // or -1 if no rest arg - * arg_opt_table = [ (arg_opts entries) ] - * arg_post_start = M+N+(*1) // or 0 if no post arguments - * arg_post_len = O // or 0 if no post arguments - * arg_keywords = K // or 0 if no keyword arg - * arg_block = M+N+(*1)+O+K // or -1 if no block arg - * arg_keyword = M+N+(*1)+O+K+(&1) // or -1 if no keyword arg/rest - * arg_simple = 0 if not simple arguments. - * = 1 if no opt, rest, post, block. - * = 2 if ambiguous block parameter ({|a|}). - * arg_size = M+N+O+(*1)+K+(&1)+(**1) argument size. + * argc = M // or 0 if no mandatory arg + * arg_opts = N+1 // or 0 if no optional arg + * arg_rest = M+N // or -1 if no rest arg + * arg_opt_table = [ (arg_opts entries) ] + * arg_post_start = M+N+(*1) // or 0 if no post arguments + * arg_post_num = O // or 0 if no post arguments + * arg_keyword_num = K // or 0 if no keyword arg + * arg_block = M+N+(*1)+O+K // or -1 if no block arg + * arg_keyword_bits = M+N+(*1)+O+K+(&1) // or -1 if no keyword arg/rest + * arg_simple = 0 if not simple arguments. + * = 1 if no opt, rest, post, block. + * = 2 if ambiguous block parameter ({|a|}). + * arg_size = M+N+O+(*1)+K+(&1)+(**1) argument size. */ int argc; @@ -264,15 +271,16 @@ struct rb_iseq_struct { int arg_rest; int arg_block; int arg_opts; - int arg_post_len; + int arg_post_num; int arg_post_start; int arg_size; VALUE *arg_opt_table; - int arg_keyword; - int arg_keyword_check; /* if this is true, raise an ArgumentError when unknown keyword argument is passed */ - int arg_keywords; + int arg_keyword_num; + int arg_keyword_bits; + int arg_keyword_rest; int arg_keyword_required; ID *arg_keyword_table; + VALUE *arg_keyword_default_values; /* catch table */ struct iseq_catch_table *catch_table; @@ -793,7 +801,7 @@ enum vm_check_match_type { #define VM_CALL_TAILCALL (0x01 << 5) /* located at tail position */ #define VM_CALL_SUPER (0x01 << 6) /* super */ #define VM_CALL_OPT_SEND (0x01 << 7) /* internal flag */ -#define VM_CALL_ARGS_SKIP_SETUP (0x01 << 8) /* (flag & (SPLAT|BLOCKARG)) && blockiseq == 0 */ +#define VM_CALL_ARGS_SIMPLE (0x01 << 8) /* (ci->flag & (SPLAT|BLOCKARG)) && ci->blockiseq == NULL && ci->kw_arg == NULL */ enum vm_special_object_type { VM_SPECIAL_OBJECT_VMCORE = 1, @@ -50,6 +50,7 @@ vm_call0(rb_thread_t* th, VALUE recv, ID id, int argc, const VALUE *argv, ci->defined_class = defined_class; ci->argc = argc; ci->me = me; + ci->kw_arg = NULL; return vm_call0_body(th, ci, argv); } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index e3e91729b0..95d232e740 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -130,29 +130,6 @@ rb_arg_error_new(int argc, int min, int max) return rb_exc_new3(rb_eArgError, err_mess); } -NORETURN(static void argument_error(const rb_iseq_t *iseq, int miss_argc, int min_argc, int max_argc)); -static void -argument_error(const rb_iseq_t *iseq, int miss_argc, int min_argc, int max_argc) -{ - rb_thread_t *th = GET_THREAD(); - VALUE exc = rb_arg_error_new(miss_argc, min_argc, max_argc); - VALUE at; - - if (iseq) { - vm_push_frame(th, iseq, VM_FRAME_MAGIC_METHOD, Qnil /* self */, Qnil /* klass */, Qnil /* specval*/, - iseq->iseq_encoded, th->cfp->sp, 0 /* local_size */, 0 /* me */, 0 /* stack_max */); - at = rb_vm_backtrace_object(); - vm_pop_frame(th); - } - else { - at = rb_vm_backtrace_object(); - } - - rb_iv_set(exc, "bt_locations", at); - rb_funcall(exc, rb_intern("set_backtrace"), 1, at); - rb_exc_raise(exc); -} - void rb_error_arity(int argc, int min, int max) { @@ -1009,261 +986,110 @@ vm_base_ptr(rb_control_frame_t *cfp) /* method call processes with call_info */ -static void -vm_caller_setup_args(const rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) -{ -#define SAVE_RESTORE_CI(expr, ci) do { \ - int saved_argc = (ci)->argc; rb_block_t *saved_blockptr = (ci)->blockptr; /* save */ \ - expr; \ - (ci)->argc = saved_argc; (ci)->blockptr = saved_blockptr; /* restore */ \ -} while (0) - - if (UNLIKELY(ci->flag & VM_CALL_ARGS_BLOCKARG)) { - rb_proc_t *po; - VALUE proc; - - proc = *(--cfp->sp); - - if (proc != Qnil) { - if (!rb_obj_is_proc(proc)) { - VALUE b; +#include "vm_args.c" - SAVE_RESTORE_CI(b = rb_check_convert_type(proc, T_DATA, "Proc", "to_proc"), ci); - - if (NIL_P(b) || !rb_obj_is_proc(b)) { - rb_raise(rb_eTypeError, - "wrong argument type %s (expected Proc)", - rb_obj_classname(proc)); - } - proc = b; - } - GetProcPtr(proc, po); - ci->blockptr = &po->block; - RUBY_VM_GET_BLOCK_PTR_IN_CFP(cfp)->proc = proc; - } - } - else if (ci->blockiseq != 0) { /* likely */ - ci->blockptr = RUBY_VM_GET_BLOCK_PTR_IN_CFP(cfp); - ci->blockptr->iseq = ci->blockiseq; - ci->blockptr->proc = 0; - } - - /* expand top of stack? */ - - if (UNLIKELY(ci->flag & VM_CALL_ARGS_SPLAT)) { - VALUE ary = *(cfp->sp - 1); - const VALUE *ptr; - int i; - VALUE tmp; - - SAVE_RESTORE_CI(tmp = rb_check_convert_type(ary, T_ARRAY, "Array", "to_a"), ci); - - if (NIL_P(tmp)) { - /* do nothing */ - } - else { - long len = RARRAY_LEN(tmp); - ptr = RARRAY_CONST_PTR(tmp); - cfp->sp -= 1; +static VALUE vm_call_iseq_setup_2(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci); +static inline VALUE vm_call_iseq_setup_normal(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci); +static inline VALUE vm_call_iseq_setup_tailcall(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci); - CHECK_VM_STACK_OVERFLOW(cfp, len); - for (i = 0; i < len; i++) { - *cfp->sp++ = ptr[i]; - } - ci->argc += i-1; - } - } +static inline VALUE +vm_callee_setup_block_arg_arg0_check(VALUE *argv) +{ + VALUE ary, arg0 = argv[0]; + ary = rb_check_array_type(arg0); + argv[0] = arg0; + return ary; } static inline int -vm_callee_setup_keyword_arg(rb_thread_t *th, const rb_iseq_t *iseq, int argc, int m, VALUE *orig_argv, VALUE *kwd) +vm_callee_setup_block_arg_arg0_splat(rb_control_frame_t *cfp, const rb_iseq_t *iseq, VALUE *argv, VALUE ary) { - VALUE keyword_hash = 0, orig_hash; - int optional = iseq->arg_keywords - iseq->arg_keyword_required; - VALUE *const sp = th->cfp->sp; - const int mark_stack_len = th->mark_stack_len; - - th->cfp->sp += argc; - th->mark_stack_len -= argc; + int i; + long len = RARRAY_LEN(ary); - if (argc > m && - !NIL_P(orig_hash = rb_check_hash_type(orig_argv[argc-1])) && - (keyword_hash = rb_extract_keywords(&orig_hash)) != 0) { - if (!orig_hash) { - argc--; - } - else { - orig_argv[argc-1] = orig_hash; - } - } - rb_get_kwargs(keyword_hash, iseq->arg_keyword_table, iseq->arg_keyword_required, - (iseq->arg_keyword_check ? optional : -1-optional), - NULL); + CHECK_VM_STACK_OVERFLOW(cfp, iseq->argc); - if (!keyword_hash) { - keyword_hash = rb_hash_new(); + for (i=0; i<len && i<iseq->argc; i++) { + argv[i] = RARRAY_AREF(ary, i); } - th->cfp->sp = sp; - th->mark_stack_len = mark_stack_len; - - *kwd = keyword_hash; - - return argc; + return i; } -static inline int -vm_callee_setup_arg_complex(rb_thread_t *th, rb_call_info_t *ci, const rb_iseq_t *iseq, VALUE *orig_argv, - int splattable) +static inline void +vm_callee_setup_block_arg(rb_thread_t *th, rb_call_info_t *ci, const rb_iseq_t *iseq, VALUE *argv, const enum arg_setup_type arg_setup_type) { - const int m = iseq->argc; - const int opts = iseq->arg_opts - (iseq->arg_opts > 0); - const int min = m + iseq->arg_post_len; - const int max = (iseq->arg_rest == -1) ? m + opts + iseq->arg_post_len : UNLIMITED_ARGUMENTS; - int orig_argc = ci->argc; - int argc = orig_argc; - VALUE *argv = orig_argv; - VALUE keyword_hash = Qnil; - rb_num_t opt_pc = 0; - - th->mark_stack_len = argc + iseq->arg_size; - - /* keyword argument */ - if (iseq->arg_keyword != -1) { - argc = vm_callee_setup_keyword_arg(th, iseq, argc, min, orig_argv, &keyword_hash); - } - - /* mandatory */ - if ((argc < min) || (argc > max && max != UNLIMITED_ARGUMENTS)) { + if (LIKELY(iseq->arg_simple & 0x01)) { + rb_control_frame_t *cfp = th->cfp; VALUE arg0; - long len; - if (!splattable || - argc != 1 || - NIL_P(arg0 = rb_check_array_type(argv[0])) || - (len = RARRAY_LEN(arg0)) < (long)min || - (len > (long)max && max != UNLIMITED_ARGUMENTS)) { - argument_error(iseq, argc, min, max); - } - CHECK_VM_STACK_OVERFLOW(th->cfp, len - 1); - MEMCPY(argv, RARRAY_CONST_PTR(arg0), VALUE, len); - ci->argc = argc = orig_argc = (int)len; - } - argv += m; - argc -= m; + CALLER_SETUP_ARG(cfp, ci); /* splat arg */ - /* post arguments */ - if (iseq->arg_post_len) { - if (!(orig_argc < iseq->arg_post_start)) { - VALUE *new_argv = ALLOCA_N(VALUE, argc); - MEMCPY(new_argv, argv, VALUE, argc); - argv = new_argv; + if (arg_setup_type == arg_setup_block && + ci->argc == 1 && + iseq->argc > 0 && !(iseq->arg_simple & 0x02) && + !NIL_P(arg0 = vm_callee_setup_block_arg_arg0_check(argv))) { + ci->argc = vm_callee_setup_block_arg_arg0_splat(cfp, iseq, argv, arg0); } - MEMCPY(&orig_argv[iseq->arg_post_start], &argv[argc -= iseq->arg_post_len], - VALUE, iseq->arg_post_len); - } - - /* opt arguments */ - if (iseq->arg_opts) { - if (argc > opts) { - argc -= opts; - argv += opts; - opt_pc = iseq->arg_opt_table[opts]; /* no opt */ - } - else { - int i; - for (i = argc; i<opts; i++) { - orig_argv[i + m] = Qnil; + if (ci->argc != iseq->argc) { + if (arg_setup_type == arg_setup_block) { + if (ci->argc < iseq->argc) { + int i; + CHECK_VM_STACK_OVERFLOW(cfp, iseq->argc); + for (i=ci->argc; i<iseq->argc; i++) argv[i] = Qnil; + ci->argc = iseq->argc; /* fill rest parameters */ + } + else if (ci->argc > iseq->argc) { + ci->argc = iseq->argc; /* simply truncate arguments */ + } } - opt_pc = iseq->arg_opt_table[argc]; - argc = 0; - } - } - - /* rest arguments */ - if (iseq->arg_rest != -1) { - orig_argv[iseq->arg_rest] = rb_ary_new4(argc, argv); - argc = 0; - } - - /* keyword argument */ - if (iseq->arg_keyword != -1) { - int i; - int arg_keywords_end = iseq->arg_keyword - (iseq->arg_block != -1); - for (i = iseq->arg_keywords; 0 < i; i--) { - orig_argv[arg_keywords_end - i] = Qnil; - } - orig_argv[iseq->arg_keyword] = keyword_hash; - } - - /* block arguments */ - if (iseq->arg_block != -1) { - VALUE blockval = Qnil; - const rb_block_t *blockptr = ci->blockptr; - - if (blockptr) { - /* make Proc object */ - if (blockptr->proc == 0) { - rb_proc_t *proc; - blockval = rb_vm_make_proc(th, blockptr, rb_cProc); - GetProcPtr(blockval, proc); - ci->blockptr = &proc->block; + else if (arg_setup_type == arg_setup_lambda && + ci->argc == 1 && + !NIL_P(arg0 = vm_callee_setup_block_arg_arg0_check(argv)) && + RARRAY_LEN(arg0) == iseq->argc) { + ci->argc = vm_callee_setup_block_arg_arg0_splat(cfp, iseq, argv, arg0); } else { - blockval = blockptr->proc; + argument_error(iseq, ci->argc, iseq->argc, iseq->argc); } } - orig_argv[iseq->arg_block] = blockval; /* Proc or nil */ + ci->aux.opt_pc = 0; + } + else { + ci->aux.opt_pc = setup_parameters_complex(th, iseq, ci, argv, arg_setup_type); } - - th->mark_stack_len = 0; - return (int)opt_pc; } -static VALUE vm_call_iseq_setup_2(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci); -static inline VALUE vm_call_iseq_setup_normal(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci); -static inline VALUE vm_call_iseq_setup_tailcall(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci); - static inline void -vm_callee_setup_arg(rb_thread_t *th, rb_call_info_t *ci, const rb_iseq_t *iseq, - VALUE *argv, int is_lambda) +vm_callee_setup_arg(rb_thread_t *th, rb_call_info_t *ci, const rb_iseq_t *iseq, VALUE *argv) { if (LIKELY(iseq->arg_simple & 0x01)) { - /* simple check */ + rb_control_frame_t *cfp = th->cfp; + + CALLER_SETUP_ARG(cfp, ci); /* splat arg */ + if (ci->argc != iseq->argc) { - VALUE arg0; - long len; - if (!(is_lambda > 1) || - ci->argc != 1 || - NIL_P(arg0 = rb_check_array_type(argv[0])) || - (len = RARRAY_LEN(arg0)) != (long)iseq->argc) { - argument_error(iseq, ci->argc, iseq->argc, iseq->argc); - } - CHECK_VM_STACK_OVERFLOW(th->cfp, len - 1); - MEMCPY(argv, RARRAY_CONST_PTR(arg0), VALUE, len); - ci->argc = (int)len; + argument_error(iseq, ci->argc, iseq->argc, iseq->argc); } + ci->aux.opt_pc = 0; + CI_SET_FASTPATH(ci, - (UNLIKELY(ci->flag & VM_CALL_TAILCALL) ? - vm_call_iseq_setup_tailcall : - vm_call_iseq_setup_normal), - (!is_lambda && - !(ci->flag & VM_CALL_ARGS_SPLAT) && /* argc may differ for each calls */ - !(ci->me->flag & NOEX_PROTECTED))); + (UNLIKELY(ci->flag & VM_CALL_TAILCALL) ? vm_call_iseq_setup_tailcall : vm_call_iseq_setup_normal), + (!IS_ARGS_SPLAT(ci) && !IS_ARGS_KEYWORD(ci) && !(ci->me->flag & NOEX_PROTECTED))); } else { - ci->aux.opt_pc = vm_callee_setup_arg_complex(th, ci, iseq, argv, is_lambda > 1); + ci->aux.opt_pc = setup_parameters_complex(th, iseq, ci, argv, arg_setup_method); } } static VALUE vm_call_iseq_setup(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) { - vm_callee_setup_arg(th, ci, ci->me->def->body.iseq, cfp->sp - ci->argc, 0); + vm_callee_setup_arg(th, ci, ci->me->def->body.iseq, cfp->sp - ci->argc); return vm_call_iseq_setup_2(th, cfp, ci); } @@ -1573,13 +1399,15 @@ vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci) int len = vm_method_cfunc_entry(me)->argc; VALUE recv = ci->recv; + CALLER_SETUP_ARG(reg_cfp, ci); if (len >= 0) rb_check_arity(ci->argc, len, len); RUBY_DTRACE_CMETHOD_ENTRY_HOOK(th, me->klass, me->called_id); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, me->called_id, me->klass, Qnil); if (!(ci->me->flag & NOEX_PROTECTED) && - !(ci->flag & VM_CALL_ARGS_SPLAT)) { + !(ci->flag & VM_CALL_ARGS_SPLAT) && + !(ci->kw_arg != NULL)) { CI_SET_FASTPATH(ci, vm_call_cfunc_latter, 1); } val = vm_call_cfunc_latter(th, reg_cfp, ci); @@ -1608,6 +1436,7 @@ vm_call_cfunc_push_frame(rb_thread_t *th) static VALUE vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci) { + CALLER_SETUP_ARG(reg_cfp, ci); return vm_call_cfunc_with_frame(th, reg_cfp, ci); } #endif @@ -1645,7 +1474,11 @@ vm_call_bmethod_body(rb_thread_t *th, rb_call_info_t *ci, const VALUE *argv) static VALUE vm_call_bmethod(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) { - VALUE *argv = ALLOCA_N(VALUE, ci->argc); + VALUE *argv; + + CALLER_SETUP_ARG(cfp, ci); + + argv = ALLOCA_N(VALUE, ci->argc); MEMCPY(argv, cfp->sp - ci->argc, VALUE, ci->argc); cfp->sp += - ci->argc - 1; @@ -1663,16 +1496,21 @@ VALUE vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *c static VALUE vm_call_opt_send(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci) { - int i = ci->argc - 1; + int i; VALUE sym; rb_call_info_t ci_entry; + CALLER_SETUP_ARG(reg_cfp, ci); + + i = ci->argc - 1; + if (ci->argc == 0) { rb_raise(rb_eArgError, "no method name given"); } ci_entry = *ci; /* copy ci entry */ ci = &ci_entry; + ci->kw_arg = NULL; /* TODO: delegate kw_arg without making a Hash object */ sym = TOPN(i); @@ -1691,9 +1529,7 @@ vm_call_opt_send(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *c if (i > 0) { MEMMOVE(&TOPN(i), &TOPN(i-1), VALUE, i); } - ci->me = - rb_method_entry_without_refinements(CLASS_OF(ci->recv), - ci->mid, &ci->defined_class); + ci->me = rb_method_entry_without_refinements(CLASS_OF(ci->recv), ci->mid, &ci->defined_class); ci->argc -= 1; DEC_SP(1); @@ -1706,8 +1542,13 @@ static VALUE vm_call_opt_call(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) { rb_proc_t *proc; - int argc = ci->argc; - VALUE *argv = ALLOCA_N(VALUE, argc); + int argc; + VALUE *argv; + + CALLER_SETUP_ARG(cfp, ci); + + argc = ci->argc; + argv = ALLOCA_N(VALUE, argc); GetProcPtr(ci->recv, proc); MEMCPY(argv, cfp->sp - argc, VALUE, argc); cfp->sp -= argc + 1; @@ -1721,12 +1562,15 @@ vm_call_method_missing(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_inf VALUE *argv = STACK_ADDR_FROM_TOP(ci->argc); rb_call_info_t ci_entry; + CALLER_SETUP_ARG(reg_cfp, ci); + ci_entry.flag = VM_CALL_FCALL | VM_CALL_OPT_SEND; ci_entry.argc = ci->argc+1; ci_entry.mid = idMethodMissing; ci_entry.blockptr = ci->blockptr; ci_entry.recv = ci->recv; ci_entry.me = rb_method_entry(CLASS_OF(ci_entry.recv), idMethodMissing, &ci_entry.defined_class); + ci_entry.kw_arg = NULL; /* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */ CHECK_VM_STACK_OVERFLOW(reg_cfp, 1); @@ -1798,12 +1642,14 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci) CI_SET_FASTPATH(ci, vm_call_cfunc, enable_fastpath); return vm_call_cfunc(th, cfp, ci); case VM_METHOD_TYPE_ATTRSET:{ + CALLER_SETUP_ARG(cfp, ci); rb_check_arity(ci->argc, 1, 1); ci->aux.index = 0; CI_SET_FASTPATH(ci, vm_call_attrset, enable_fastpath && !(ci->flag & VM_CALL_ARGS_SPLAT)); return vm_call_attrset(th, cfp, ci); } case VM_METHOD_TYPE_IVAR:{ + CALLER_SETUP_ARG(cfp, ci); rb_check_arity(ci->argc, 0, 0); ci->aux.index = 0; CI_SET_FASTPATH(ci, vm_call_ivar, enable_fastpath && !(ci->flag & VM_CALL_ARGS_SPLAT)); @@ -2150,214 +1996,27 @@ vm_yield_with_cfunc(rb_thread_t *th, const rb_block_t *block, return val; } - -/*-- - * @brief on supplied all of optional, rest and post parameters. - * @pre iseq is block style (not lambda style) - */ -static inline int -vm_yield_setup_block_args_complex(rb_thread_t *th, const rb_iseq_t *iseq, - int argc, VALUE *argv) -{ - rb_num_t opt_pc = 0; - int i; - const int m = iseq->argc; - const int r = iseq->arg_rest; - int len = iseq->arg_post_len; - int start = iseq->arg_post_start; - int rsize = argc > m ? argc - m : 0; /* # of arguments which did not consumed yet */ - int psize = rsize > len ? len : rsize; /* # of post arguments */ - int osize = 0; /* # of opt arguments */ - VALUE ary; - - /* reserves arguments for post parameters */ - rsize -= psize; - - if (iseq->arg_opts) { - const int opts = iseq->arg_opts - 1; - if (rsize > opts) { - osize = opts; - opt_pc = iseq->arg_opt_table[opts]; - } - else { - osize = rsize; - opt_pc = iseq->arg_opt_table[rsize]; - } - } - rsize -= osize; - - if (0) { - printf(" argc: %d\n", argc); - printf(" len: %d\n", len); - printf("start: %d\n", start); - printf("rsize: %d\n", rsize); - } - - if (r == -1) { - /* copy post argument */ - MEMMOVE(&argv[start], &argv[m+osize], VALUE, psize); - } - else { - ary = rb_ary_new4(rsize, &argv[r]); - - /* copy post argument */ - MEMMOVE(&argv[start], &argv[m+rsize+osize], VALUE, psize); - argv[r] = ary; - } - - for (i=psize; i<len; i++) { - argv[start + i] = Qnil; - } - - return (int)opt_pc; -} - -static inline int -vm_yield_setup_block_args(rb_thread_t *th, const rb_iseq_t * iseq, - int orig_argc, VALUE *argv, - const rb_block_t *blockptr) +static int +vm_yield_callee_setup_arg(rb_thread_t *th, rb_call_info_t *ci, const rb_iseq_t *iseq, VALUE *argv, enum arg_setup_type arg_setup_type) { - int i; - int argc = orig_argc; - const int m = iseq->argc; - const int min = m + iseq->arg_post_len; - VALUE ary, arg0; - VALUE keyword_hash = Qnil; - int opt_pc = 0; - - th->mark_stack_len = argc; - - /* - * yield [1, 2] - * => {|a|} => a = [1, 2] - * => {|a, b|} => a, b = [1, 2] - */ - arg0 = argv[0]; - if (!(iseq->arg_simple & 0x02) && /* exclude {|a|} */ - (min > 0 || /* positional arguments exist */ - iseq->arg_opts > 2 || /* multiple optional arguments exist */ - iseq->arg_keyword != -1 || /* any keyword arguments */ - 0) && - argc == 1 && !NIL_P(ary = rb_check_array_type(arg0))) { /* rhs is only an array */ - th->mark_stack_len = argc = RARRAY_LENINT(ary); - - CHECK_VM_STACK_OVERFLOW(th->cfp, argc); - - MEMCPY(argv, RARRAY_CONST_PTR(ary), VALUE, argc); - } - else { - /* vm_push_frame current argv is at the top of sp because vm_invoke_block - * set sp at the first element of argv. - * Therefore when rb_check_array_type(arg0) called to_ary and called to_ary - * or method_missing run vm_push_frame, it initializes local variables. - * see also https://bugs.ruby-lang.org/issues/8484 - */ - argv[0] = arg0; - } - - /* keyword argument */ - if (iseq->arg_keyword != -1) { - argc = vm_callee_setup_keyword_arg(th, iseq, argc, min, argv, &keyword_hash); - } - - for (i=argc; i<m; i++) { - argv[i] = Qnil; - } - - if (iseq->arg_rest == -1 && iseq->arg_opts == 0) { - const int arg_size = iseq->arg_size; - if (arg_size < argc) { - /* - * yield 1, 2 - * => {|a|} # truncate - */ - th->mark_stack_len = argc = arg_size; - } - } - else { - int r = iseq->arg_rest; - - if (iseq->arg_post_len || - iseq->arg_opts) { /* TODO: implement simple version for (iseq->arg_post_len==0 && iseq->arg_opts > 0) */ - opt_pc = vm_yield_setup_block_args_complex(th, iseq, argc, argv); - } - else { - if (argc < r) { - /* yield 1 - * => {|a, b, *r|} - */ - for (i=argc; i<r; i++) { - argv[i] = Qnil; - } - argv[r] = rb_ary_new(); - } - else { - argv[r] = rb_ary_new4(argc-r, &argv[r]); - } - } - - th->mark_stack_len = iseq->arg_size; - } - - /* keyword argument */ - if (iseq->arg_keyword != -1) { - int arg_keywords_end = iseq->arg_keyword - (iseq->arg_block != -1); - for (i = iseq->arg_keywords; 0 < i; i--) { - argv[arg_keywords_end - i] = Qnil; - } - argv[iseq->arg_keyword] = keyword_hash; - } - - /* {|&b|} */ - if (iseq->arg_block != -1) { - VALUE procval = Qnil; - - if (blockptr) { - if (blockptr->proc == 0) { - procval = rb_vm_make_proc(th, blockptr, rb_cProc); - } - else { - procval = blockptr->proc; - } - } - - argv[iseq->arg_block] = procval; - } - - th->mark_stack_len = 0; - return opt_pc; + vm_callee_setup_block_arg(th, ci, iseq, argv, arg_setup_type); + return ci->aux.opt_pc; } -static inline int -vm_yield_setup_args(rb_thread_t * const th, const rb_iseq_t *iseq, - int argc, VALUE *argv, const rb_block_t *blockptr, - int lambda) +static int +vm_yield_setup_args(rb_thread_t *th, const rb_iseq_t *iseq, const int argc, VALUE *argv, const rb_block_t *blockptr, enum arg_setup_type arg_setup_type) { - if (0) { /* for debug */ - printf(" argc: %d\n", argc); - printf("iseq argc: %d\n", iseq->argc); - printf("iseq opts: %d\n", iseq->arg_opts); - printf("iseq rest: %d\n", iseq->arg_rest); - printf("iseq post: %d\n", iseq->arg_post_len); - printf("iseq blck: %d\n", iseq->arg_block); - printf("iseq smpl: %d\n", iseq->arg_simple); - printf(" lambda: %s\n", lambda ? "true" : "false"); - } + rb_call_info_t ci_entry; + ci_entry.argc = argc; + ci_entry.blockptr = (rb_block_t *)blockptr; + ci_entry.flag = 0; + ci_entry.kw_arg = NULL; + ci_entry.me = NULL; - if (lambda) { - /* call as method */ - rb_call_info_t ci_entry; - ci_entry.flag = 0; - ci_entry.argc = argc; - ci_entry.blockptr = (rb_block_t *)blockptr; - vm_callee_setup_arg(th, &ci_entry, iseq, argv, lambda); - return ci_entry.aux.opt_pc; - } - else { - return vm_yield_setup_block_args(th, iseq, argc, argv, blockptr); - } + return vm_yield_callee_setup_arg(th, &ci_entry, iseq, argv, arg_setup_type); } +/* ruby iseq -> ruby block iseq */ static VALUE vm_invoke_block(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci) { @@ -2370,18 +2029,15 @@ vm_invoke_block(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci } iseq = block->iseq; - if (UNLIKELY(ci->flag & VM_CALL_ARGS_SPLAT)) { - vm_caller_setup_args(th, GET_CFP(), ci); - } - if (BUILTIN_TYPE(iseq) != T_NODE) { int opt_pc; const int arg_size = iseq->arg_size; int is_lambda = block_proc_is_lambda(block->proc); VALUE * const rsp = GET_SP() - ci->argc; - SET_SP(rsp); - opt_pc = vm_yield_setup_args(th, iseq, ci->argc, rsp, 0, is_lambda * 2); + opt_pc = vm_yield_callee_setup_arg(th, ci, iseq, rsp, is_lambda ? arg_setup_lambda : arg_setup_block); + + SET_SP(rsp); vm_push_frame(th, iseq, is_lambda ? VM_FRAME_MAGIC_LAMBDA : VM_FRAME_MAGIC_BLOCK, @@ -2395,7 +2051,9 @@ vm_invoke_block(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci return Qundef; } else { - VALUE val = vm_yield_with_cfunc(th, block, block->self, ci->argc, STACK_ADDR_FROM_TOP(ci->argc), 0); + VALUE val; + CALLER_SETUP_ARG(th->cfp, ci); + val = vm_yield_with_cfunc(th, block, block->self, ci->argc, STACK_ADDR_FROM_TOP(ci->argc), 0); POPN(ci->argc); /* TODO: should put before C/yield? */ return val; } @@ -2433,3 +2091,4 @@ vm_once_clear(VALUE data) is->once.running_thread = NULL; return Qnil; } + |