summaryrefslogtreecommitdiff
path: root/vm_insnhelper.c
diff options
context:
space:
mode:
authorKoichi Sasada <ko1@atdot.net>2023-01-13 17:52:59 +0900
committerKoichi Sasada <ko1@atdot.net>2023-03-06 15:03:06 +0900
commite87d0882910001ef3b0c2ccd43bf00cee8c34a0c (patch)
tree85a4cab782f6660530e100117183e0cd7d583130 /vm_insnhelper.c
parent0463c5806ac63bbd082f4abb1e3ceeae6ffc39ce (diff)
downloadruby-e87d0882910001ef3b0c2ccd43bf00cee8c34a0c.tar.gz
Change bytecode of `f(*a, **kw)`
`f(*a, **kw)` is compiled to `f([*a, kw])` but it makes an dummy array, so change it to pass two arguments `a` and `kw` with calling flags. ``` ruby 3.2.0 (2022-12-29 revision a7d467a792) [x86_64-linux] Calculating ------------------------------------- foo() 15.354M (± 4.2%) i/s - 77.295M in 5.043650s dele() 13.439M (± 3.9%) i/s - 67.109M in 5.001974s dele(*) 6.265M (± 4.5%) i/s - 31.730M in 5.075649s dele(*a) 6.286M (± 3.3%) i/s - 31.719M in 5.051516s dele(*a, **kw) 1.926M (± 4.5%) i/s - 9.753M in 5.076487s dele(*, **) 1.927M (± 4.2%) i/s - 9.710M in 5.048224s dele(...) 5.871M (± 3.9%) i/s - 29.471M in 5.028023s forwardable 4.969M (± 4.1%) i/s - 25.233M in 5.087498s ruby 3.3.0dev (2023-01-13T01:28:00Z master 7e8802fa5b) [x86_64-linux] Calculating ------------------------------------- foo() 16.354M (± 4.7%) i/s - 81.799M in 5.014561s dele() 14.256M (± 3.5%) i/s - 71.656M in 5.032883s dele(*) 6.701M (± 3.8%) i/s - 33.948M in 5.074938s dele(*a) 6.681M (± 3.3%) i/s - 33.578M in 5.031720s dele(*a, **kw) 4.200M (± 4.4%) i/s - 21.258M in 5.072583s dele(*, **) 4.197M (± 5.3%) i/s - 21.322M in 5.096684s dele(...) 6.039M (± 6.8%) i/s - 30.355M in 5.052662s forwardable 4.788M (± 3.2%) i/s - 24.033M in 5.024875s ```
Diffstat (limited to 'vm_insnhelper.c')
-rw-r--r--vm_insnhelper.c265
1 files changed, 163 insertions, 102 deletions
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index a43f6ef94d..b2321ed7c8 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -2540,71 +2540,138 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
}
-// If true, cc->call needs to include `CALLER_SETUP_ARG` (i.e. can't be skipped in fastpath)
-MJIT_STATIC bool
-rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci)
+static inline void
+vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary)
{
- return IS_ARGS_SPLAT(ci) || IS_ARGS_KW_OR_KW_SPLAT(ci);
+ vm_check_canary(GET_EC(), cfp->sp);
+
+ if (!NIL_P(ary)) {
+ const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary);
+ long len = RARRAY_LEN(ary), i;
+
+ CHECK_VM_STACK_OVERFLOW(cfp, len);
+
+ for (i = 0; i < len; i++) {
+ *cfp->sp++ = ptr[i];
+ }
+ calling->argc += i;
+ }
}
+static inline void
+vm_caller_setup_arg_kw(rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci)
+{
+ const VALUE *const passed_keywords = vm_ci_kwarg(ci)->keywords;
+ const int kw_len = vm_ci_kwarg(ci)->keyword_len;
+ const VALUE h = rb_hash_new_with_size(kw_len);
+ VALUE *sp = cfp->sp;
+ int i;
+
+ for (i=0; i<kw_len; i++) {
+ rb_hash_aset(h, passed_keywords[i], (sp - kw_len)[i]);
+ }
+ (sp-kw_len)[0] = h;
+
+ cfp->sp -= kw_len - 1;
+ calling->argc -= kw_len - 1;
+ calling->kw_splat = 1;
+}
+
+static inline VALUE
+vm_caller_setup_keyword_hash(const struct rb_callinfo *ci, VALUE keyword_hash)
+{
+ if (UNLIKELY(!RB_TYPE_P(keyword_hash, T_HASH))) {
+ /* Convert a non-hash keyword splat to a new hash */
+ keyword_hash = rb_hash_dup(rb_to_hash_type(keyword_hash));
+ }
+ else if (!IS_ARGS_KW_SPLAT_MUT(ci)) {
+ /* Convert a hash keyword splat to a new hash unless
+ * a mutable keyword splat was passed.
+ */
+ keyword_hash = rb_hash_dup(keyword_hash);
+ }
+ return keyword_hash;
+}
static inline void
CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
struct rb_calling_info *restrict calling,
const struct rb_callinfo *restrict ci)
{
- if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
- VALUE final_hash;
- /* This expands the rest argument to the stack.
- * So, vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT is now inconsistent.
- */
- vm_caller_setup_arg_splat(cfp, calling);
- if (!IS_ARGS_KW_OR_KW_SPLAT(ci) &&
- calling->argc > 0 &&
- RB_TYPE_P((final_hash = *(cfp->sp - 1)), T_HASH) &&
- (((struct RHash *)final_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
- *(cfp->sp - 1) = rb_hash_dup(final_hash);
- calling->kw_splat = 1;
- }
- }
- if (UNLIKELY(IS_ARGS_KW_OR_KW_SPLAT(ci))) {
- if (IS_ARGS_KEYWORD(ci)) {
- /* This converts VM_CALL_KWARG style to VM_CALL_KW_SPLAT style
- * by creating a keyword hash.
- * So, vm_ci_flag(ci) & VM_CALL_KWARG is now inconsistent.
- */
- vm_caller_setup_arg_kw(cfp, calling, ci);
+ if (UNLIKELY(IS_ARGS_SPLAT(ci) && IS_ARGS_KW_SPLAT(ci))) {
+ // f(*a, **kw)
+ VM_ASSERT(calling->kw_splat == 1);
+
+ cfp->sp -= 2;
+ calling->argc -= 2;
+ VALUE ary = cfp->sp[0];
+ VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]);
+
+ // splat a
+ vm_caller_setup_arg_splat(cfp, calling, ary);
+
+ // put kw
+ if (!RHASH_EMPTY_P(kwh)) {
+ cfp->sp[0] = kwh;
+ cfp->sp++;
+ calling->argc++;
+
+ VM_ASSERT(calling->kw_splat == 1);
}
else {
- VALUE keyword_hash = cfp->sp[-1];
- if (!RB_TYPE_P(keyword_hash, T_HASH)) {
- /* Convert a non-hash keyword splat to a new hash */
- cfp->sp[-1] = rb_hash_dup(rb_to_hash_type(keyword_hash));
+ calling->kw_splat = 0;
+ }
+ }
+ else if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
+ // f(*a)
+ VM_ASSERT(calling->kw_splat == 0);
+
+ cfp->sp -= 1;
+ calling->argc -= 1;
+ VALUE ary = cfp->sp[0];
+
+ vm_caller_setup_arg_splat(cfp, calling, ary);
+
+ // check the last argument
+ VALUE last_hash;
+ if (!IS_ARGS_KEYWORD(ci) &&
+ calling->argc > 0 &&
+ RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) &&
+ (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
+
+ if (RHASH_EMPTY_P(last_hash)) {
+ calling->argc--;
+ cfp->sp -= 1;
}
- else if (!IS_ARGS_KW_SPLAT_MUT(ci)) {
- /* Convert a hash keyword splat to a new hash unless
- * a mutable keyword splat was passed.
- */
- cfp->sp[-1] = rb_hash_dup(keyword_hash);
+ else {
+ cfp->sp[-1] = rb_hash_dup(last_hash);
+ calling->kw_splat = 1;
}
}
}
-}
+ else if (UNLIKELY(IS_ARGS_KW_SPLAT(ci))) {
+ // f(**kw)
+ VM_ASSERT(calling->kw_splat == 1);
+ VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[-1]);
-static inline void
-CALLER_REMOVE_EMPTY_KW_SPLAT(struct rb_control_frame_struct *restrict cfp,
- struct rb_calling_info *restrict calling,
- const struct rb_callinfo *restrict ci)
-{
- if (UNLIKELY(calling->kw_splat)) {
- /* This removes the last Hash object if it is empty.
- * So, vm_ci_flag(ci) & VM_CALL_KW_SPLAT is now inconsistent.
- */
- if (RHASH_EMPTY_P(cfp->sp[-1])) {
+ if (RHASH_EMPTY_P(kwh)) {
cfp->sp--;
calling->argc--;
calling->kw_splat = 0;
}
+ else {
+ cfp->sp[-1] = kwh;
+ }
+ }
+ else if (UNLIKELY(IS_ARGS_KEYWORD(ci))) {
+ // f(k1:1, k2:2)
+ VM_ASSERT(calling->kw_splat == 0);
+
+ /* This converts VM_CALL_KWARG style to VM_CALL_KW_SPLAT style
+ * by creating a keyword hash.
+ * So, vm_ci_flag(ci) & VM_CALL_KWARG is now inconsistent.
+ */
+ vm_caller_setup_arg_kw(cfp, calling, ci);
}
}
@@ -2749,7 +2816,6 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
if (LIKELY(rb_simple_iseq_p(iseq))) {
rb_control_frame_t *cfp = ec->cfp;
CALLER_SETUP_ARG(cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
if (calling->argc != ISEQ_BODY(iseq)->param.lead_num) {
argument_arity_error(ec, iseq, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num);
@@ -2763,7 +2829,6 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
else if (rb_iseq_only_optparam_p(iseq)) {
rb_control_frame_t *cfp = ec->cfp;
CALLER_SETUP_ARG(cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
const int lead_num = ISEQ_BODY(iseq)->param.lead_num;
const int opt_num = ISEQ_BODY(iseq)->param.opt_num;
@@ -3296,6 +3361,13 @@ vm_call_cfunc_with_frame_(rb_execution_context_t *ec, rb_control_frame_t *reg_cf
return val;
}
+// If true, cc->call needs to include `CALLER_SETUP_ARG` (i.e. can't be skipped in fastpath)
+MJIT_STATIC bool
+rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci)
+{
+ return IS_ARGS_SPLAT(ci) || IS_ARGS_KW_OR_KW_SPLAT(ci);
+}
+
static VALUE
vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling)
{
@@ -3311,14 +3383,27 @@ vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp
#endif
static VALUE
-vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling)
+vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci)
{
+ const bool kwsplat_p = IS_ARGS_KW_SPLAT(ci);
int argc = calling->argc;
VALUE *argv = cfp->sp - argc;
- VALUE ary = argv[argc-1];
+ VALUE ary = argv[argc - (kwsplat_p ? 2 : 1)];
long len = RARRAY_LEN(ary);
if (UNLIKELY(len + argc > VM_ARGC_STACK_MAX)) {
+ VALUE kwhash;
+
+ if (kwsplat_p) {
+ // the last argument is kwhash
+ cfp->sp--;
+ kwhash = vm_caller_setup_keyword_hash(ci, cfp->sp[0]);
+ calling->argc--;
+ argc--;
+
+ VM_ASSERT(calling->kw_splat);
+ }
+
vm_check_canary(ec, cfp->sp);
const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary);
VALUE argv_ary = rb_ary_new_capa(len + argc - 1);
@@ -3329,6 +3414,32 @@ vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp
cfp->sp[-1] = argv_ary;
calling->argc = 1;
+ if (kwsplat_p) {
+ if (!RHASH_EMPTY_P(kwhash)) {
+ rb_ary_push(argv_ary, kwhash);
+ }
+ else {
+ calling->kw_splat = false;
+ }
+ }
+ else if (RARRAY_LEN(argv_ary) > 0) {
+ // check the last argument
+ long hash_idx = RARRAY_LEN(argv_ary) - 1;
+ VALUE last_hash = RARRAY_AREF(argv_ary, hash_idx);
+
+ if (RB_TYPE_P(last_hash, T_HASH) &&
+ (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
+ if (RHASH_EMPTY_P(last_hash)) {
+ rb_ary_pop(argv_ary);
+ }
+ else {
+ last_hash = rb_hash_dup(last_hash);
+ RARRAY_ASET(argv_ary, hash_idx, last_hash);
+ calling->kw_splat = 1;
+ }
+ }
+ }
+
return argv_ary;
}
else {
@@ -3344,54 +3455,11 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb
VALUE argv_ary;
- if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling))) {
- // special case of CALLER_SETUP_ARG
- if (!IS_ARGS_KW_OR_KW_SPLAT(ci)) {
- long hash_idx = RARRAY_LEN(argv_ary) - 1;
- VALUE final_hash = RARRAY_AREF(argv_ary, hash_idx);
-
- if (RB_TYPE_P(final_hash, T_HASH) &&
- (((struct RHash *)final_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
- if (RHASH_EMPTY_P(final_hash)) {
- rb_ary_pop(argv_ary);
- }
- else {
- final_hash = rb_hash_dup(final_hash);
- RARRAY_ASET(argv_ary, hash_idx, final_hash);
- calling->kw_splat = 1;
- }
- }
- }
-
- if (UNLIKELY(IS_ARGS_KW_OR_KW_SPLAT(ci))) {
- VM_ASSERT(!IS_ARGS_KEYWORD(ci)); // should be KW_SPLAT
-
- long hash_idx = RARRAY_LEN(argv_ary) - 1;
- VALUE keyword_hash = RARRAY_AREF(argv_ary, hash_idx);
-
- if (!RB_TYPE_P(keyword_hash, T_HASH)) {
- /* Convert a non-hash keyword splat to a new hash */
- RARRAY_ASET(argv_ary, hash_idx, rb_hash_dup(rb_to_hash_type(keyword_hash)));
- }
- else if (!IS_ARGS_KW_SPLAT_MUT(ci)) {
- /* Convert a hash keyword splat to a new hash unless
- * a mutable keyword splat was passed.
- */
- RARRAY_ASET(argv_ary, hash_idx, rb_hash_dup(keyword_hash));
- }
- }
-
- // special case of CALLER_REMOVE_EMPTY_KW_SPLAT()
- if (UNLIKELY(calling->kw_splat)) {
- VALUE kw_hash = RARRAY_AREF(argv_ary, RARRAY_LEN(argv_ary)-1);
- if (RHASH_EMPTY_P(kw_hash)) {
- rb_ary_pop(argv_ary);
- calling->kw_splat = false;
- }
- }
+ if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling, ci))) {
+ VM_ASSERT(!IS_ARGS_KEYWORD(ci));
int argc = RARRAY_LENINT(argv_ary);
- VALUE *argv = (void *)RARRAY_CONST_PTR_TRANSIENT(argv_ary);
+ VALUE *argv = (VALUE *)RARRAY_CONST_PTR(argv_ary);
VALUE *stack_bottom = reg_cfp->sp - 2;
VM_ASSERT(calling->argc == 1);
@@ -3402,7 +3470,6 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb
}
else {
CALLER_SETUP_ARG(reg_cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(reg_cfp, calling, ci);
CC_SET_FASTPATH(calling->cc, vm_call_cfunc_with_frame, !rb_splat_or_kwargs_p(ci) && !calling->kw_splat);
return vm_call_cfunc_with_frame(ec, reg_cfp, calling);
@@ -3992,14 +4059,12 @@ vm_call_optimized(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb
return vm_call_opt_block_call(ec, cfp, calling);
case OPTIMIZED_METHOD_TYPE_STRUCT_AREF:
CALLER_SETUP_ARG(cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
rb_check_arity(calling->argc, 0, 0);
CC_SET_FASTPATH(cc, vm_call_opt_struct_aref, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE));
return vm_call_opt_struct_aref(ec, cfp, calling);
case OPTIMIZED_METHOD_TYPE_STRUCT_ASET:
CALLER_SETUP_ARG(cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
rb_check_arity(calling->argc, 1, 1);
CC_SET_FASTPATH(cc, vm_call_opt_struct_aset, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE));
return vm_call_opt_struct_aset(ec, cfp, calling);
@@ -4041,7 +4106,6 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
case VM_METHOD_TYPE_ATTRSET:
CALLER_SETUP_ARG(cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
rb_check_arity(calling->argc, 1, 1);
@@ -4077,7 +4141,6 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
case VM_METHOD_TYPE_IVAR:
CALLER_SETUP_ARG(cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
rb_check_arity(calling->argc, 0, 0);
vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID);
const unsigned int ivar_mask = (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT);
@@ -4458,7 +4521,6 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
VALUE arg0;
CALLER_SETUP_ARG(cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci);
if (arg_setup_type == arg_setup_block &&
calling->argc == 1 &&
@@ -4558,7 +4620,6 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
int argc;
const struct rb_captured_block *captured = VM_BH_TO_IFUNC_BLOCK(block_handler);
CALLER_SETUP_ARG(ec->cfp, calling, ci);
- CALLER_REMOVE_EMPTY_KW_SPLAT(ec->cfp, calling, ci);
argc = calling->argc;
val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL);
POPN(argc); /* TODO: should put before C/yield? */