From dd34ac647fb5d6842941b3529d1452797862cc23 Mon Sep 17 00:00:00 2001 From: dearblue Date: Sat, 3 Apr 2021 21:17:48 +0900 Subject: Make `mrb_exec_irep()` allow non-VM to enter. Change the old `mrb_exec_irep()` as-is to static `mrb_exec_irep_vm()`. Extract the VM entry part from the old `exec_irep()` in `mruby-eval/src/eval.c` and make it the core of the new `mrb_exec_irep()`. --- mrbgems/mruby-eval/src/eval.c | 9 --------- src/vm.c | 31 +++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/mrbgems/mruby-eval/src/eval.c b/mrbgems/mruby-eval/src/eval.c index 978d4fc30..508f5ffcb 100644 --- a/mrbgems/mruby-eval/src/eval.c +++ b/mrbgems/mruby-eval/src/eval.c @@ -131,15 +131,6 @@ exec_irep(mrb_state *mrb, mrb_value self, struct RProc *proc, mrb_func_t posthoo { /* no argument passed from eval() */ mrb->c->ci->argc = 0; - if (mrb->c->ci->acc < 0) { - ptrdiff_t cioff = mrb->c->ci - mrb->c->cibase; - mrb_value ret = mrb_top_run(mrb, proc, self, 0); - if (mrb->exc) { - mrb_exc_raise(mrb, mrb_obj_value(mrb->exc)); - } - mrb->c->ci = mrb->c->cibase + cioff; - return ret; - } /* clear block */ mrb->c->ci->stack[1] = mrb_nil_value(); return mrb_exec_irep(mrb, self, proc, posthook); diff --git a/src/vm.c b/src/vm.c index edfb55586..d7f2f58d7 100644 --- a/src/vm.c +++ b/src/vm.c @@ -543,8 +543,8 @@ mrb_exec_irep_prepare_posthook(mrb_state *mrb, mrb_callinfo *ci, int nregs, mrb_ * * However, if `proc` is a C function, it will be ignored. */ -mrb_value -mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook) +static mrb_value +mrb_exec_irep_vm(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook) { mrb_callinfo *ci = mrb->c->ci; int keep, nregs; @@ -575,6 +575,29 @@ mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t postho return self; } +mrb_value +mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook) +{ + mrb_callinfo *ci = mrb->c->ci; + if (ci->acc >= 0) { + return mrb_exec_irep_vm(mrb, self, p, posthook); + } + else { + mrb_value ret; + if (MRB_PROC_CFUNC_P(p)) { + ret = MRB_PROC_CFUNC(p)(mrb, self); + } + else { + int keep = (ci->argc < 0 ? 1 : ci->argc) + 2 /* receiver + block */; + ret = mrb_top_run(mrb, p, self, keep); + } + if (mrb->exc && mrb->jmp) { + mrb_exc_raise(mrb, mrb_obj_value(mrb->exc)); + } + return ret; + } +} + /* 15.3.1.3.4 */ /* 15.3.1.3.44 */ /* @@ -638,7 +661,7 @@ mrb_f_send(mrb_state *mrb, mrb_value self) } return MRB_METHOD_CFUNC(m)(mrb, self); } - return mrb_exec_irep(mrb, self, MRB_METHOD_PROC(m), NULL); + return mrb_exec_irep_vm(mrb, self, MRB_METHOD_PROC(m), NULL); } static mrb_value @@ -826,7 +849,7 @@ mrb_yield_cont(mrb_state *mrb, mrb_value b, mrb_value self, mrb_int argc, const mrb->c->ci->stack[1] = mrb_ary_new_from_values(mrb, argc, argv); mrb->c->ci->stack[2] = mrb_nil_value(); ci->argc = -1; - return mrb_exec_irep(mrb, self, p, NULL); + return mrb_exec_irep_vm(mrb, self, p, NULL); } static struct RBreak* -- cgit v1.2.3 From 4c196dcdaaf4127e2ce988fb79f1912c78b9dca8 Mon Sep 17 00:00:00 2001 From: dearblue Date: Sat, 3 Apr 2021 21:20:59 +0900 Subject: Reorganize `mcall()` in `mruby-method`. Use `mrb_exec_irep()`. If possible, re-entry into the VM will be suppressed. Note that due to the effect of being a tail-call, the backtrace of `Method#call` will be lost, and it will look as if the target method was called directly. This change fixes the problem of infinite loops when redefining methods that make block calls using `mruby-method`. ```console % bin/mruby -e 'mm = method(:proc); define_method(:proc, ->(*a, &b) { mm.call(*a, &b) }); p proc { 1 }' trace (most recent call last): [257] -e:1 [256] -e:1:in proc [255] -e:1:in proc ...SNIP... [1] -e:1:in proc -e:1:in proc: stack level too deep (SystemStackError) ``` --- mrbgems/mruby-method/src/method.c | 173 +++++++++++++++++++++++++++++--------- src/vm.c | 2 + 2 files changed, 135 insertions(+), 40 deletions(-) diff --git a/mrbgems/mruby-method/src/method.c b/mrbgems/mruby-method/src/method.c index 8c8ebcd4f..02eddda9c 100644 --- a/mrbgems/mruby-method/src/method.c +++ b/mrbgems/mruby-method/src/method.c @@ -6,12 +6,126 @@ #include "mruby/string.h" #include "mruby/presym.h" +mrb_noreturn void mrb_method_missing(mrb_state *mrb, mrb_sym name, mrb_value self, mrb_value args); +mrb_value mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook); + +static mrb_value +args_shift(mrb_state *mrb) +{ + mrb_value *argv = mrb->c->ci->stack + 1; + + if (mrb->c->ci->argc > 0) { + mrb_value obj = argv[0]; + memmove(argv, argv + 1, (mrb->c->ci->argc + 1 /* block */ - 1 /* first value */) * sizeof(mrb_value)); + mrb->c->ci->argc--; + return obj; + } + else if (mrb->c->ci->argc < 0 && RARRAY_LEN(*argv) > 0) { + return mrb_ary_shift(mrb, *argv); + } + else { + mrb_argnum_error(mrb, 0, 1, -1); + return mrb_undef_value(); /* not reached */ + } +} + +static void +args_unshift(mrb_state *mrb, mrb_value obj) +{ + mrb_value *argv = mrb->c->ci->stack + 1; + + if (mrb->c->ci->argc >= 0) { + mrb_value block = argv[mrb->c->ci->argc]; + argv[0] = mrb_ary_new_from_values(mrb, mrb->c->ci->argc, argv); + argv[1] = block; + mrb->c->ci->argc = -1; + } + + mrb_ary_unshift(mrb, *argv, obj); +} + +static struct RProc* +method_missing_prepare(mrb_state *mrb, mrb_sym *mid, mrb_value recv, struct RClass **tc) +{ + const mrb_sym id_method_missing = MRB_SYM(method_missing); + + if (*mid == id_method_missing) { + method_missing: ; + int argc = mrb->c->ci->argc; + mrb_value *argv = mrb->c->ci->stack + 1; + mrb_value args = (argc < 0) ? argv[0] : mrb_ary_new_from_values(mrb, argc, argv); + mrb_method_missing(mrb, *mid, recv, args); + } + + *tc = mrb_class(mrb, recv); + mrb_method_t m = mrb_method_search_vm(mrb, tc, id_method_missing); + if (MRB_METHOD_UNDEF_P(m)) { + goto method_missing; + } + + struct RProc *proc; + if (MRB_METHOD_FUNC_P(m)) { + proc = mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); + MRB_PROC_SET_TARGET_CLASS(proc, *tc); + } + else { + proc = MRB_METHOD_PROC(m); + } + + args_unshift(mrb, mrb_symbol_value(*mid)); + *mid = id_method_missing; + + return proc; +} + static struct RObject * method_object_alloc(mrb_state *mrb, struct RClass *mclass) { return (struct RObject*)mrb_obj_alloc(mrb, MRB_TT_OBJECT, mclass); } +static struct RProc* +method_extract_proc(mrb_state *mrb, mrb_value self) +{ + mrb_value obj = mrb_iv_get(mrb, self, MRB_SYM(_proc)); + if (mrb_nil_p(obj)) { + return NULL; + } + else { + mrb_check_type(mrb, obj, MRB_TT_PROC); + return mrb_proc_ptr(obj); + } +} + +static mrb_value +method_extract_receiver(mrb_state *mrb, mrb_value self) +{ + return mrb_iv_get(mrb, self, MRB_SYM(_recv)); +} + +static mrb_sym +method_extract_mid(mrb_state *mrb, mrb_value self) +{ + mrb_value obj = mrb_iv_get(mrb, self, MRB_SYM(_name)); + mrb_check_type(mrb, obj, MRB_TT_SYMBOL); + return mrb_symbol(obj); +} + +static struct RClass* +method_extract_owner(mrb_state *mrb, mrb_value self) +{ + mrb_value obj = mrb_iv_get(mrb, self, MRB_SYM(_owner)); + switch (mrb_type(obj)) { + case MRB_TT_CLASS: + case MRB_TT_MODULE: + case MRB_TT_SCLASS: + break; + default: + mrb_raise(mrb, E_TYPE_ERROR, "not class/module as owner of method object"); + } + return mrb_class_ptr(obj); +} + static void bind_check(mrb_state *mrb, mrb_value recv, mrb_value owner) { @@ -109,61 +223,40 @@ method_eql(mrb_state *mrb, mrb_value self) #undef IV_GET static mrb_value -mcall(mrb_state *mrb, mrb_value recv, mrb_value proc, mrb_value name, struct RClass *owner, - mrb_int argc, const mrb_value *argv, mrb_value block) +mcall(mrb_state *mrb, mrb_value self, mrb_value recv) { - mrb_value ret; - mrb_sym orig_mid = mrb->c->ci->mid; + struct RProc *proc = method_extract_proc(mrb, self); + mrb_sym mid = method_extract_mid(mrb, self); + struct RClass *tc = method_extract_owner(mrb, self); - mrb->c->ci->mid = mrb_symbol(name); - if (mrb_nil_p(proc)) { - mrb_value missing_argv = mrb_ary_new_from_values(mrb, argc, argv); - mrb_ary_unshift(mrb, missing_argv, name); - ret = mrb_funcall_argv(mrb, recv, MRB_SYM(method_missing), argc + 1, RARRAY_PTR(missing_argv)); - } - else if (!mrb_nil_p(block)) { - /* - workaround since `mrb_yield_with_class` does not support passing block as parameter - need new API that initializes `mrb->c->stack[argc+1]` with block passed by argument - */ - ret = mrb_funcall_with_block(mrb, recv, mrb_symbol(name), argc, argv, block); + if (mrb_undef_p(recv)) { + recv = method_extract_receiver(mrb, self); } else { - ret = mrb_yield_with_class(mrb, proc, argc, argv, recv, owner); + bind_check(mrb, recv, mrb_obj_value(tc)); } - mrb->c->ci->mid = orig_mid; - return ret; + + if (!proc) { + proc = method_missing_prepare(mrb, &mid, recv, &tc); + } + mrb->c->ci->mid = mid; + mrb->c->ci->u.target_class = tc; + + return mrb_exec_irep(mrb, recv, proc, NULL); } static mrb_value method_call(mrb_state *mrb, mrb_value self) { - mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); - mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); - mrb_value recv = mrb_iv_get(mrb, self, MRB_SYM(_recv)); - struct RClass *owner = mrb_class_ptr(mrb_iv_get(mrb, self, MRB_SYM(_owner))); - mrb_int argc; - const mrb_value *argv; - mrb_value block; - - mrb_get_args(mrb, "*&", &argv, &argc, &block); - return mcall(mrb, recv, proc, name, owner, argc, argv, block); + return mcall(mrb, self, mrb_undef_value()); } static mrb_value method_bcall(mrb_state *mrb, mrb_value self) { - mrb_value proc = mrb_iv_get(mrb, self, MRB_SYM(_proc)); - mrb_value name = mrb_iv_get(mrb, self, MRB_SYM(_name)); - mrb_value recv = mrb_iv_get(mrb, self, MRB_SYM(_recv)); - mrb_value owner = mrb_iv_get(mrb, self, MRB_SYM(_owner)); - mrb_int argc; - const mrb_value *argv; - mrb_value block; - - mrb_get_args(mrb, "o*&", &recv, &argv, &argc, &block); - bind_check(mrb, recv, owner); - return mcall(mrb, recv, proc, name, mrb_class_ptr(owner), argc, argv, block); + mrb_value recv = args_shift(mrb); + mrb_gc_protect(mrb, recv); + return mcall(mrb, self, recv); } static mrb_value diff --git a/src/vm.c b/src/vm.c index d7f2f58d7..dbc8bd88a 100644 --- a/src/vm.c +++ b/src/vm.c @@ -585,7 +585,9 @@ mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t postho else { mrb_value ret; if (MRB_PROC_CFUNC_P(p)) { + cipush(mrb, 0, CI_ACC_DIRECT, mrb_vm_ci_target_class(ci), p, ci->mid, ci->argc); ret = MRB_PROC_CFUNC(p)(mrb, self); + cipop(mrb); } else { int keep = (ci->argc < 0 ? 1 : ci->argc) + 2 /* receiver + block */; -- cgit v1.2.3