aboutsummaryrefslogtreecommitdiffstats
path: root/vm_insnhelper.c
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2023-03-06 15:58:58 -0800
committerJeremy Evans <code@jeremyevans.net>2023-04-25 08:06:16 -0700
commit99c6d19e502b5fdadbd367ae4b6bb3fab850fddc (patch)
tree241e8c41259147f7eb310fff63c2b84e918baebf /vm_insnhelper.c
parente7cdce83e8c8797c481ccb54c260c0db1e1afa7c (diff)
downloadruby-99c6d19e502b5fdadbd367ae4b6bb3fab850fddc.tar.gz
Generalize cfunc large array splat fix to fix many additional cases raising SystemStackError
Originally, when 2e7bceb34ea858649e1f975a934ce1894d1f06a6 fixed cfuncs to no longer use the VM stack for large array splats, it was thought to have fully fixed Bug #4040, since the issue was fixed for methods defined in Ruby (iseqs) back in Ruby 2.2. After additional research, I determined that same issue affects almost all types of method calls, not just iseq and cfunc calls. There were two main types of remaining issues, important cases (where large array splat should work) and pedantic cases (where large array splat raised SystemStackError instead of ArgumentError). Important cases: ```ruby define_method(:a){|*a|} a(*1380888.times) def b(*a); end send(:b, *1380888.times) :b.to_proc.call(self, *1380888.times) def d; yield(*1380888.times) end d(&method(:b)) def self.method_missing(*a); end not_a_method(*1380888.times) ``` Pedantic cases: ```ruby def a; end a(*1380888.times) def b(_); end b(*1380888.times) def c(_=nil); end c(*1380888.times) c = Class.new do attr_accessor :a alias b a= end.new c.a(*1380888.times) c.b(*1380888.times) c = Struct.new(:a) do alias b a= end.new c.a(*1380888.times) c.b(*1380888.times) ``` This patch fixes all usage of CALLER_SETUP_ARG with splatting a large number of arguments, and required similar fixes to use a temporary hidden array in three other cases where the VM would use the VM stack for handling a large number of arguments. However, it is possible there may be additional cases where splatting a large number of arguments still causes a SystemStackError. This has a measurable performance impact, as it requires additional checks for a large number of arguments in many additional cases. This change is fairly invasive, as there were many different VM functions that needed to be modified to support this. To avoid too much API change, I modified struct rb_calling_info to add a heap_argv member for storing the array, so I would not have to thread it through many functions. This struct is always stack allocated, which helps ensure sure GC doesn't collect it early. Because of how invasive the changes are, and how rarely large arrays are actually splatted in Ruby code, the existing test/spec suites are not great at testing for correct behavior. To try to find and fix all issues, I tested this in CI with VM_ARGC_STACK_MAX to -1, ensuring that a temporary array is used for all array splat method calls. This was very helpful in finding breaking cases, especially ones involving flagged keyword hashes. Fixes [Bug #4040] Co-authored-by: Jimmy Miller <jimmy.miller@shopify.com>
Diffstat (limited to 'vm_insnhelper.c')
-rw-r--r--vm_insnhelper.c458
1 files changed, 261 insertions, 197 deletions
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index aafc49bfd5..0bbae89498 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -2525,22 +2525,67 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
}
-static inline void
-vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary)
+#define ALLOW_HEAP_ARGV (-2)
+#define ALLOW_HEAP_ARGV_KEEP_KWSPLAT (-3)
+
+static inline bool
+vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary, int max_args)
{
vm_check_canary(GET_EC(), cfp->sp);
+ bool ret = false;
if (!NIL_P(ary)) {
const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary);
- long len = RARRAY_LEN(ary), i;
+ long len = RARRAY_LEN(ary);
+ int argc = calling->argc;
- CHECK_VM_STACK_OVERFLOW(cfp, len);
+ if (UNLIKELY(max_args <= ALLOW_HEAP_ARGV && len + argc > VM_ARGC_STACK_MAX)) {
+ /* Avoid SystemStackError when splatting large arrays by storing arguments in
+ * a temporary array, instead of trying to keeping arguments on the VM stack.
+ */
+ VALUE *argv = cfp->sp - argc;
+ VALUE argv_ary = rb_ary_hidden_new(len + argc + 1);
+ rb_ary_cat(argv_ary, argv, argc);
+ rb_ary_cat(argv_ary, ptr, len);
+ cfp->sp -= argc - 1;
+ cfp->sp[-1] = argv_ary;
+ calling->argc = 1;
+ calling->heap_argv = argv_ary;
+ RB_GC_GUARD(ary);
+ }
+ else {
+ long i;
+
+ if (max_args >= 0 && len + argc > max_args) {
+ /* If only a given max_args is allowed, copy up to max args.
+ * Used by vm_callee_setup_block_arg for non-lambda blocks,
+ * where additional arguments are ignored.
+ *
+ * Also, copy up to one more argument than the maximum,
+ * in case it is an empty keyword hash that will be removed.
+ */
+ calling->argc += len - (max_args - argc + 1);
+ len = max_args - argc + 1;
+ ret = true;
+ }
+ else {
+ /* Unset heap_argv if set originally. Can happen when
+ * forwarding modified arguments, where heap_argv was used
+ * originally, but heap_argv not supported by the forwarded
+ * method in all cases.
+ */
+ calling->heap_argv = 0;
+ }
+ CHECK_VM_STACK_OVERFLOW(cfp, len);
- for (i = 0; i < len; i++) {
- *cfp->sp++ = ptr[i];
+ for (i = 0; i < len; i++) {
+ *cfp->sp++ = ptr[i];
+ }
+ calling->argc += i;
}
- calling->argc += i;
}
+
+ return ret;
}
static inline void
@@ -2581,56 +2626,85 @@ vm_caller_setup_keyword_hash(const struct rb_callinfo *ci, VALUE 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)
+ const struct rb_callinfo *restrict ci, int max_args)
{
- 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);
+ if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
+ if (IS_ARGS_KW_SPLAT(ci)) {
+ // f(*a, **kw)
+ VM_ASSERT(calling->kw_splat == 1);
- // put kw
- if (!RHASH_EMPTY_P(kwh)) {
- cfp->sp[0] = kwh;
- cfp->sp++;
- calling->argc++;
+ cfp->sp -= 2;
+ calling->argc -= 2;
+ VALUE ary = cfp->sp[0];
+ VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]);
+
+ // splat a
+ if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) return;
+
+ // put kw
+ if (!RHASH_EMPTY_P(kwh)) {
+ if (UNLIKELY(calling->heap_argv)) {
+ rb_ary_push(calling->heap_argv, kwh);
+ ((struct RHash *)kwh)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
+ if (max_args != ALLOW_HEAP_ARGV_KEEP_KWSPLAT) {
+ calling->kw_splat = 0;
+ }
+ }
+ else {
+ cfp->sp[0] = kwh;
+ cfp->sp++;
+ calling->argc++;
- VM_ASSERT(calling->kw_splat == 1);
+ VM_ASSERT(calling->kw_splat == 1);
+ }
+ }
+ else {
+ calling->kw_splat = 0;
+ }
}
else {
- calling->kw_splat = 0;
- }
- }
- else if (UNLIKELY(IS_ARGS_SPLAT(ci))) {
- // f(*a)
- VM_ASSERT(calling->kw_splat == 0);
+ // f(*a)
+ VM_ASSERT(calling->kw_splat == 0);
- cfp->sp -= 1;
- calling->argc -= 1;
- VALUE ary = cfp->sp[0];
+ cfp->sp -= 1;
+ calling->argc -= 1;
+ VALUE ary = cfp->sp[0];
- vm_caller_setup_arg_splat(cfp, calling, ary);
+ if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) {
+ goto check_keyword;
+ }
- // 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)) {
+ // check the last argument
+ VALUE last_hash, argv_ary;
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
+ if (!IS_ARGS_KEYWORD(ci) &&
+ RARRAY_LEN(argv_ary) > 0 &&
+ RB_TYPE_P((last_hash = rb_ary_last(0, NULL, argv_ary)), T_HASH) &&
+ (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) {
- if (RHASH_EMPTY_P(last_hash)) {
- calling->argc--;
- cfp->sp -= 1;
+ rb_ary_pop(argv_ary);
+ if (!RHASH_EMPTY_P(last_hash)) {
+ rb_ary_push(argv_ary, rb_hash_dup(last_hash));
+ calling->kw_splat = 1;
+ }
+ }
}
else {
- cfp->sp[-1] = rb_hash_dup(last_hash);
- calling->kw_splat = 1;
+check_keyword:
+ 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 {
+ cfp->sp[-1] = rb_hash_dup(last_hash);
+ calling->kw_splat = 1;
+ }
+ }
}
}
}
@@ -2811,10 +2885,11 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) {
if (LIKELY(rb_simple_iseq_p(iseq))) {
rb_control_frame_t *cfp = ec->cfp;
- CALLER_SETUP_ARG(cfp, calling, ci);
+ int lead_num = ISEQ_BODY(iseq)->param.lead_num;
+ CALLER_SETUP_ARG(cfp, calling, ci, lead_num);
- 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);
+ if (calling->argc != lead_num) {
+ argument_arity_error(ec, iseq, calling->argc, lead_num, lead_num);
}
VM_ASSERT(ci == calling->ci);
@@ -2835,10 +2910,11 @@ 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);
const int lead_num = ISEQ_BODY(iseq)->param.lead_num;
const int opt_num = ISEQ_BODY(iseq)->param.opt_num;
+
+ CALLER_SETUP_ARG(cfp, calling, ci, lead_num + opt_num);
const int argc = calling->argc;
const int opt = argc - lead_num;
@@ -3385,86 +3461,16 @@ vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp
return vm_call_cfunc_with_frame_(ec, reg_cfp, calling, argc, argv, stack_bottom);
}
-#ifndef VM_ARGC_STACK_MAX
-#define VM_ARGC_STACK_MAX 128
-#endif
-
-static VALUE
-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 - (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);
- rb_obj_hide(argv_ary);
- rb_ary_cat(argv_ary, argv, argc-1);
- rb_ary_cat(argv_ary, ptr, len);
- cfp->sp -= argc - 1;
- 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 {
- return Qfalse;
- }
-}
-
static VALUE
vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling)
{
const struct rb_callinfo *ci = calling->ci;
RB_DEBUG_COUNTER_INC(ccf_cfunc);
+ CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV_KEEP_KWSPLAT);
VALUE argv_ary;
-
- if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling, ci))) {
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
VM_ASSERT(!IS_ARGS_KEYWORD(ci));
-
int argc = RARRAY_LENINT(argv_ary);
VALUE *argv = (VALUE *)RARRAY_CONST_PTR(argv_ary);
VALUE *stack_bottom = reg_cfp->sp - 2;
@@ -3476,7 +3482,6 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb
return vm_call_cfunc_with_frame_(ec, reg_cfp, calling, argc, argv, stack_bottom);
}
else {
- CALLER_SETUP_ARG(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);
@@ -3545,7 +3550,7 @@ vm_call_bmethod_body(rb_execution_context_t *ec, struct rb_calling_info *calling
/* control block frame */
GetProcPtr(procv, proc);
- val = rb_vm_invoke_bmethod(ec, proc, calling->recv, calling->argc, argv, calling->kw_splat, calling->block_handler, vm_cc_cme(cc));
+ val = rb_vm_invoke_bmethod(ec, proc, calling->recv, CALLING_ARGC(calling), argv, calling->kw_splat, calling->block_handler, vm_cc_cme(cc));
return val;
}
@@ -3559,11 +3564,17 @@ vm_call_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_c
int argc;
const struct rb_callinfo *ci = calling->ci;
- CALLER_SETUP_ARG(cfp, calling, ci);
- argc = calling->argc;
- argv = ALLOCA_N(VALUE, argc);
- MEMCPY(argv, cfp->sp - argc, VALUE, argc);
- cfp->sp += - argc - 1;
+ CALLER_SETUP_ARG(cfp, calling, ci, ALLOW_HEAP_ARGV);
+ if (UNLIKELY(calling->heap_argv)) {
+ argv = RARRAY_PTR(calling->heap_argv);
+ cfp->sp -= 2;
+ }
+ else {
+ argc = calling->argc;
+ argv = ALLOCA_N(VALUE, argc);
+ MEMCPY(argv, cfp->sp - argc, VALUE, argc);
+ cfp->sp += - argc - 1;
+ }
return vm_call_bmethod_body(ec, calling, argv);
}
@@ -3666,37 +3677,53 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
missing_reason = ci_missing_reason(ci);
ec->method_missing_reason = missing_reason;
- /* E.g. when argc == 2
- *
- * | | | | TOPN
- * | | +------+
- * | | +---> | arg1 | 0
- * +------+ | +------+
- * | arg1 | -+ +-> | arg0 | 1
- * +------+ | +------+
- * | arg0 | ---+ | sym | 2
- * +------+ +------+
- * | recv | | recv | 3
- * --+------+--------+------+------
- */
- int i = argc;
- CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
- INC_SP(1);
- MEMMOVE(&TOPN(i - 1), &TOPN(i), VALUE, i);
- argc = ++calling->argc;
+ VALUE argv_ary;
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
+ if (rb_method_basic_definition_p(klass, idMethodMissing)) {
+ rb_ary_unshift(argv_ary, symbol);
- if (rb_method_basic_definition_p(klass, idMethodMissing)) {
- /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */
- TOPN(i) = symbol;
- int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL);
- const VALUE *argv = STACK_ADDR_FROM_TOP(argc);
- VALUE exc = rb_make_no_method_exception(
- rb_eNoMethodError, 0, recv, argc, argv, priv);
+ /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */
+ int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL);
+ VALUE exc = rb_make_no_method_exception(
+ rb_eNoMethodError, 0, recv, RARRAY_LENINT(argv_ary), RARRAY_CONST_PTR(argv_ary), priv);
- rb_exc_raise(exc);
+ rb_exc_raise(exc);
+ }
+ rb_ary_unshift(argv_ary, rb_str_intern(symbol));
}
else {
- TOPN(i) = rb_str_intern(symbol);
+ /* E.g. when argc == 2
+ *
+ * | | | | TOPN
+ * | | +------+
+ * | | +---> | arg1 | 0
+ * +------+ | +------+
+ * | arg1 | -+ +-> | arg0 | 1
+ * +------+ | +------+
+ * | arg0 | ---+ | sym | 2
+ * +------+ +------+
+ * | recv | | recv | 3
+ * --+------+--------+------+------
+ */
+ int i = argc;
+ CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
+ INC_SP(1);
+ MEMMOVE(&TOPN(i - 1), &TOPN(i), VALUE, i);
+ argc = ++calling->argc;
+
+ if (rb_method_basic_definition_p(klass, idMethodMissing)) {
+ /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */
+ TOPN(i) = symbol;
+ int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL);
+ const VALUE *argv = STACK_ADDR_FROM_TOP(argc);
+ VALUE exc = rb_make_no_method_exception(
+ rb_eNoMethodError, 0, recv, argc, argv, priv);
+
+ rb_exc_raise(exc);
+ }
+ else {
+ TOPN(i) = rb_str_intern(symbol);
+ }
}
}
@@ -3737,17 +3764,26 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct
{
RB_DEBUG_COUNTER_INC(ccf_opt_send);
- int i;
- VALUE sym;
+ int i, flags = VM_CALL_FCALL;
+ VALUE sym, argv_ary;
- CALLER_SETUP_ARG(reg_cfp, calling, calling->ci);
-
- i = calling->argc - 1;
-
- if (calling->argc == 0) {
- rb_raise(rb_eArgError, "no method name given");
+ CALLER_SETUP_ARG(reg_cfp, calling, calling->ci, ALLOW_HEAP_ARGV);
+ if (UNLIKELY(argv_ary = calling->heap_argv)) {
+ sym = rb_ary_shift(argv_ary);
+ flags |= VM_CALL_ARGS_SPLAT;
+ if (calling->kw_splat) {
+ VALUE last_hash = rb_ary_last(0, NULL, argv_ary);
+ ((struct RHash *)last_hash)->basic.flags |= RHASH_PASS_AS_KEYWORDS;
+ calling->kw_splat = 0;
+ }
}
else {
+ i = calling->argc - 1;
+
+ if (calling->argc == 0) {
+ rb_raise(rb_eArgError, "no method name given");
+ }
+
sym = TOPN(i);
/* E.g. when i == 2
*
@@ -3768,9 +3804,9 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct
}
calling->argc -= 1;
DEC_SP(1);
-
- return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, VM_CALL_FCALL);
}
+
+ return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, flags);
}
static VALUE
@@ -3780,22 +3816,29 @@ vm_call_method_missing_body(rb_execution_context_t *ec, rb_control_frame_t *reg_
RB_DEBUG_COUNTER_INC(ccf_method_missing);
VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
- unsigned int argc;
+ unsigned int argc, flag;
- CALLER_SETUP_ARG(reg_cfp, calling, orig_ci);
- argc = calling->argc + 1;
+ CALLER_SETUP_ARG(reg_cfp, calling, orig_ci, ALLOW_HEAP_ARGV);
+ if (UNLIKELY(calling->heap_argv)) {
+ flag = VM_CALL_ARGS_SPLAT | VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
+ argc = 1;
+ rb_ary_unshift(calling->heap_argv, ID2SYM(vm_ci_mid(orig_ci)));
+ }
+ else {
+ argc = calling->argc + 1;
- unsigned int flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
- calling->argc = argc;
+ flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0);
+ calling->argc = argc;
- /* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */
- CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
- vm_check_canary(ec, reg_cfp->sp);
- if (argc > 1) {
- MEMMOVE(argv+1, argv, VALUE, argc-1);
+ /* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */
+ CHECK_VM_STACK_OVERFLOW(reg_cfp, 1);
+ vm_check_canary(ec, reg_cfp->sp);
+ if (argc > 1) {
+ MEMMOVE(argv+1, argv, VALUE, argc-1);
+ }
+ argv[0] = ID2SYM(vm_ci_mid(orig_ci));
+ INC_SP(1);
}
- argv[0] = ID2SYM(vm_ci_mid(orig_ci));
- INC_SP(1);
ec->method_missing_reason = reason;
calling->ci = &VM_CI_ON_STACK(idMethodMissing, flag, argc, vm_ci_kwarg(orig_ci));
@@ -4059,13 +4102,13 @@ vm_call_optimized(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb
CC_SET_FASTPATH(cc, vm_call_opt_block_call, TRUE);
return vm_call_opt_block_call(ec, cfp, calling);
case OPTIMIZED_METHOD_TYPE_STRUCT_AREF:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, 0);
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_SETUP_ARG(cfp, calling, ci, 1);
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);
@@ -4106,7 +4149,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
return vm_call_cfunc(ec, cfp, calling);
case VM_METHOD_TYPE_ATTRSET:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, 1);
rb_check_arity(calling->argc, 1, 1);
@@ -4141,7 +4184,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st
return v;
case VM_METHOD_TYPE_IVAR:
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, 0);
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);
@@ -4191,9 +4234,14 @@ vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct
const int stat = ci_missing_reason(ci);
if (vm_ci_mid(ci) == idMethodMissing) {
- rb_control_frame_t *reg_cfp = cfp;
- VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
- vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat);
+ if (UNLIKELY(calling->heap_argv)) {
+ vm_raise_method_missing(ec, RARRAY_LENINT(calling->heap_argv), RARRAY_CONST_PTR(calling->heap_argv), calling->recv, stat);
+ }
+ else {
+ rb_control_frame_t *reg_cfp = cfp;
+ VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc);
+ vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat);
+ }
}
else {
return vm_call_method_missing_body(ec, cfp, calling, ci, stat);
@@ -4517,7 +4565,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
rb_control_frame_t *cfp = ec->cfp;
VALUE arg0;
- CALLER_SETUP_ARG(cfp, calling, ci);
+ CALLER_SETUP_ARG(cfp, calling, ci, ISEQ_BODY(iseq)->param.lead_num);
if (arg_setup_type == arg_setup_block &&
calling->argc == 1 &&
@@ -4552,16 +4600,17 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
}
static int
-vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int argc, VALUE *argv, int kw_splat, VALUE block_handler, enum arg_setup_type arg_setup_type)
+vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int argc, VALUE *argv, int flags, VALUE block_handler, enum arg_setup_type arg_setup_type)
{
struct rb_calling_info calling_entry, *calling;
calling = &calling_entry;
calling->argc = argc;
calling->block_handler = block_handler;
- calling->kw_splat = kw_splat;
+ calling->kw_splat = (flags & VM_CALL_KW_SPLAT) ? 1 : 0;
calling->recv = Qundef;
- struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, (kw_splat ? VM_CALL_KW_SPLAT : 0), 0, 0);
+ calling->heap_argv = 0;
+ struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, flags, 0, 0);
return vm_callee_setup_block_arg(ec, calling, &dummy_ci, iseq, argv, arg_setup_type);
}
@@ -4577,7 +4626,8 @@ vm_invoke_iseq_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
const rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq);
const int arg_size = ISEQ_BODY(iseq)->param.size;
VALUE * const rsp = GET_SP() - calling->argc;
- int opt_pc = vm_callee_setup_block_arg(ec, calling, ci, iseq, rsp, is_lambda ? arg_setup_method : arg_setup_block);
+ VALUE * const argv = rsp;
+ int opt_pc = vm_callee_setup_block_arg(ec, calling, ci, iseq, argv, is_lambda ? arg_setup_method : arg_setup_block);
SET_SP(rsp);
@@ -4597,15 +4647,29 @@ vm_invoke_symbol_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
struct rb_calling_info *calling, const struct rb_callinfo *ci,
MAYBE_UNUSED(bool is_lambda), VALUE block_handler)
{
- if (calling->argc < 1) {
- rb_raise(rb_eArgError, "no receiver given");
+ VALUE symbol = VM_BH_TO_SYMBOL(block_handler);
+ CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV);
+ int flags = 0;
+ if (UNLIKELY(calling->heap_argv)) {
+#if VM_ARGC_STACK_MAX < 0
+ if (RARRAY_LEN(calling->heap_argv) < 1) {
+ rb_raise(rb_eArgError, "no receiver given");
+ }
+#endif
+ calling->recv = rb_ary_shift(calling->heap_argv);
+ // Modify stack to avoid cfp consistency error
+ reg_cfp->sp++;
+ reg_cfp->sp[-1] = reg_cfp->sp[-2];
+ reg_cfp->sp[-2] = calling->recv;
+ flags |= VM_CALL_ARGS_SPLAT;
}
else {
- VALUE symbol = VM_BH_TO_SYMBOL(block_handler);
- CALLER_SETUP_ARG(reg_cfp, calling, ci);
+ if (calling->argc < 1) {
+ rb_raise(rb_eArgError, "no receiver given");
+ }
calling->recv = TOPN(--calling->argc);
- return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, 0);
}
+ return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, flags);
}
static VALUE
@@ -4616,9 +4680,9 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp,
VALUE val;
int argc;
const struct rb_captured_block *captured = VM_BH_TO_IFUNC_BLOCK(block_handler);
- CALLER_SETUP_ARG(ec->cfp, calling, ci);
+ CALLER_SETUP_ARG(ec->cfp, calling, ci, ALLOW_HEAP_ARGV_KEEP_KWSPLAT);
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);
+ val = vm_yield_with_cfunc(ec, captured, captured->self, CALLING_ARGC(calling), calling->heap_argv ? RARRAY_CONST_PTR(calling->heap_argv) : STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL);
POPN(argc); /* TODO: should put before C/yield? */
return val;
}