summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorYukihiro "Matz" Matsumoto <[email protected]>2021-02-25 21:15:24 +0900
committerGitHub <[email protected]>2021-02-25 21:15:24 +0900
commit192f3df9a1fca043801e42febcd4b105fa1d5733 (patch)
tree2e83f49a75fb581e522a7d388c5cc86f978cd1a7 /src
parentf1c2096f8ed966cf055d1a32271748b5fad4ffe0 (diff)
parent927615e1f072d8fff3d9b84660cdce15a239e36c (diff)
downloadmruby-192f3df9a1fca043801e42febcd4b105fa1d5733.tar.gz
mruby-192f3df9a1fca043801e42febcd4b105fa1d5733.zip
Merge pull request #5362 from dearblue/binding
Binding
Diffstat (limited to 'src')
-rw-r--r--src/proc.c105
-rw-r--r--src/vm.c79
2 files changed, 177 insertions, 7 deletions
diff --git a/src/proc.c b/src/proc.c
index 870d5ea16..cfaf37af6 100644
--- a/src/proc.c
+++ b/src/proc.c
@@ -10,6 +10,8 @@
#include <mruby/opcode.h>
#include <mruby/data.h>
#include <mruby/presym.h>
+#include <mruby/array.h>
+#include <mruby/hash.h>
static const mrb_code call_iseq[] = {
OP_CALL,
@@ -305,6 +307,109 @@ mrb_proc_arity(const struct RProc *p)
return arity;
}
+mrb_value
+mrb_proc_local_variables(mrb_state *mrb, const struct RProc *proc)
+{
+ const mrb_irep *irep;
+ mrb_value vars;
+ size_t i;
+
+ if (proc == NULL || MRB_PROC_CFUNC_P(proc)) {
+ return mrb_ary_new(mrb);
+ }
+ vars = mrb_hash_new(mrb);
+ while (proc) {
+ if (MRB_PROC_CFUNC_P(proc)) break;
+ irep = proc->body.irep;
+ if (irep->lv) {
+ for (i = 0; i + 1 < irep->nlocals; ++i) {
+ if (irep->lv[i]) {
+ mrb_sym sym = irep->lv[i];
+ const char *name = mrb_sym_name(mrb, sym);
+ switch (name[0]) {
+ case '*': case '&':
+ break;
+ default:
+ mrb_hash_set(mrb, vars, mrb_symbol_value(sym), mrb_true_value());
+ break;
+ }
+ }
+ }
+ }
+ if (MRB_PROC_SCOPE_P(proc)) break;
+ proc = proc->upper;
+ }
+
+ return mrb_hash_keys(mrb, vars);
+}
+
+const struct RProc *
+mrb_proc_get_caller(mrb_state *mrb, struct REnv **envp)
+{
+ struct mrb_context *c = mrb->c;
+ mrb_callinfo *ci = (c->ci > c->cibase) ? c->ci - 1 : c->cibase;
+ const struct RProc *proc = ci->proc;
+
+ if (!proc || MRB_PROC_CFUNC_P(proc)) {
+ if (envp) *envp = NULL;
+ }
+ else {
+ struct RClass *tc = MRB_PROC_TARGET_CLASS(proc);
+ struct REnv *e = mrb_vm_ci_env(ci);
+
+ if (e == NULL) {
+ int nstacks = proc->body.irep->nlocals;
+ e = mrb_env_new(mrb, c, ci, nstacks, ci->stack, tc);
+ ci->u.env = e;
+ }
+ else if (tc) {
+ e->c = tc;
+ mrb_field_write_barrier(mrb, (struct RBasic*)e, (struct RBasic*)tc);
+ }
+ if (envp) *envp = e;
+ }
+
+ return proc;
+}
+
+#define IREP_LVAR_MERGE_DEFAULT 50
+#define IREP_LVAR_MERGE_MINIMUM 8
+#define IREP_LVAR_MERGE_MAXIMUM 240
+
+#ifdef MRB_IREP_LVAR_MERGE_LIMIT
+# define IREP_LVAR_MERGE_LIMIT \
+ ((MRB_IREP_LVAR_MERGE_LIMIT) < IREP_LVAR_MERGE_MINIMUM ? IREP_LVAR_MERGE_MINIMUM : \
+ (MRB_IREP_LVAR_MERGE_LIMIT) > IREP_LVAR_MERGE_MAXIMUM ? IREP_LVAR_MERGE_MAXIMUM : \
+ (MRB_IREP_LVAR_MERGE_LIMIT))
+#else
+# define IREP_LVAR_MERGE_LIMIT IREP_LVAR_MERGE_DEFAULT
+#endif
+
+void
+mrb_proc_merge_lvar(mrb_state *mrb, mrb_irep *irep, struct REnv *env, int num, const mrb_sym *lv, const mrb_value *stack)
+{
+ mrb_assert(!(irep->flags & MRB_IREP_NO_FREE));
+
+ if ((irep->nlocals + num) > IREP_LVAR_MERGE_LIMIT) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "too many local variables for binding (mruby limitation)");
+ }
+
+ if (!lv) {
+ mrb_raise(mrb, E_RUNTIME_ERROR, "unavailable local variable names");
+ }
+
+ irep->lv = (mrb_sym*)mrb_realloc(mrb, (mrb_sym*)irep->lv, sizeof(mrb_sym) * (irep->nlocals + num));
+ env->stack = (mrb_value*)mrb_realloc(mrb, env->stack, sizeof(mrb_value) * (irep->nlocals + 1 /* self */ + num));
+
+ mrb_sym *destlv = (mrb_sym*)irep->lv + irep->nlocals - 1 /* self */;
+ mrb_value *destst = env->stack + irep->nlocals;
+ memmove(destlv, lv, sizeof(mrb_sym) * num);
+ memmove(destst, stack, sizeof(mrb_value) * num);
+ irep->nlocals += num;
+ irep->nregs = irep->nlocals;
+ MRB_ENV_SET_LEN(env, irep->nlocals);
+}
+
void
mrb_init_proc(mrb_state *mrb)
{
diff --git a/src/vm.c b/src/vm.c
index 0e7eb65e5..2607ba308 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -483,13 +483,73 @@ mrb_funcall_argv(mrb_state *mrb, mrb_value self, mrb_sym mid, mrb_int argc, cons
return mrb_funcall_with_block(mrb, self, mid, argc, argv, mrb_nil_value());
}
+#define DECOMPOSE32(n) (((n) >> 24) & 0xff), (((n) >> 16) & 0xff), (((n) >> 8) & 0xff), (((n) >> 0) & 0xff)
+#define CATCH_HANDLER_MAKE_BYTECODE(t, b, e, j) t, DECOMPOSE32(b), DECOMPOSE32(e), DECOMPOSE32(j)
+#define CATCH_HANDLER_NUM_TO_BYTE(n) ((n) * sizeof(struct mrb_irep_catch_handler))
+
+static void
+mrb_exec_irep_prepare_posthook(mrb_state *mrb, mrb_callinfo *ci, int nregs, mrb_func_t posthook)
+{
+ /*
+ * stack: [proc, errinfo, return value by called proc]
+ *
+ * begin
+ * OP_NOP # A dummy instruction built in to make the catch handler react.
+ * ensure
+ * OP_EXCEPT R1 # Save the exception object.
+ * OP_CALL # Call a C function for the hook.
+ * # The stack is kept as it is in the called proc.
+ * # The exception will be rethrown within the hook function.
+ * end
+ */
+ static const mrb_code hook_iseq[] = {
+ OP_NOP,
+ OP_EXCEPT, 1,
+ OP_CALL,
+ CATCH_HANDLER_MAKE_BYTECODE(MRB_CATCH_ENSURE, 0, 1, 1),
+ };
+ static const mrb_irep hook_irep = {
+ 1, 3, 1, MRB_IREP_STATIC, hook_iseq,
+ NULL, NULL, NULL, NULL, NULL,
+ sizeof(hook_iseq) / sizeof(hook_iseq[0]) - CATCH_HANDLER_NUM_TO_BYTE(1),
+ 0, 0, 0, 0
+ };
+ static const struct RProc hook_caller = {
+ NULL, NULL, MRB_TT_PROC, 7 /* GC_RED */, MRB_FL_OBJ_IS_FROZEN, { &hook_irep }, NULL, { NULL }
+ };
+
+ struct RProc *hook = mrb_proc_new_cfunc(mrb, posthook);
+ int acc = 2;
+ memmove(ci->stack + acc, ci->stack, sizeof(mrb_value) * nregs);
+ ci->stack[0] = mrb_obj_value(hook);
+ ci->stack[1] = mrb_nil_value();
+ mrb_callinfo hook_ci = { 0, 0, ci->acc, &hook_caller, ci->stack, &hook_iseq[1], { NULL } };
+ ci = cipush(mrb, acc, acc, NULL, ci[0].proc, ci[0].mid, ci[0].argc);
+ ci->u.env = ci[-1].u.env;
+ ci[-1] = hook_ci;
+}
+
+/*
+ * If `posthook` is given, `posthook` will be called even if an
+ * exception or global jump occurs in `p`. Exception or global jump objects
+ * are stored in `mrb->c->stack[1]` and should be rethrown in `posthook`.
+ *
+ * if (!mrb_nil_p(mrb->c->stack[1])) {
+ * mrb_exc_raise(mrb, mrb->c->stack[1]);
+ * }
+ *
+ * If you want to return the return value by `proc` as it is, please do
+ * `return mrb->c->stack[2]`.
+ *
+ * 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_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook)
{
mrb_callinfo *ci = mrb->c->ci;
mrb_int keep, nregs;
- mrb->c->ci->stack[0] = self;
+ ci->stack[0] = self;
mrb_vm_ci_proc_set(ci, p);
if (MRB_PROC_CFUNC_P(p)) {
return MRB_PROC_CFUNC(p)(mrb, self);
@@ -497,12 +557,17 @@ mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p)
nregs = p->body.irep->nregs;
if (ci->argc < 0) keep = 3;
else keep = ci->argc + 2;
+ int extra = posthook ? (2 /* hook proc + errinfo */) : 0;
if (nregs < keep) {
- mrb_stack_extend(mrb, keep);
+ mrb_stack_extend(mrb, keep + extra);
}
else {
- mrb_stack_extend(mrb, nregs);
- stack_clear(mrb->c->ci->stack+keep, nregs-keep);
+ mrb_stack_extend(mrb, nregs + extra);
+ stack_clear(ci->stack+keep, nregs-keep + extra);
+ }
+
+ if (posthook) {
+ mrb_exec_irep_prepare_posthook(mrb, ci, (nregs < keep ? keep : nregs), posthook);
}
cipush(mrb, 0, 0, NULL, NULL, 0, 0);
@@ -573,7 +638,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));
+ return mrb_exec_irep(mrb, self, MRB_METHOD_PROC(m), NULL);
}
static mrb_value
@@ -760,7 +825,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);
+ return mrb_exec_irep(mrb, self, p, NULL);
}
static struct RBreak*