diff options
| author | Yukihiro "Matz" Matsumoto <[email protected]> | 2021-02-25 21:15:24 +0900 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-02-25 21:15:24 +0900 |
| commit | 192f3df9a1fca043801e42febcd4b105fa1d5733 (patch) | |
| tree | 2e83f49a75fb581e522a7d388c5cc86f978cd1a7 /src | |
| parent | f1c2096f8ed966cf055d1a32271748b5fad4ffe0 (diff) | |
| parent | 927615e1f072d8fff3d9b84660cdce15a239e36c (diff) | |
| download | mruby-192f3df9a1fca043801e42febcd4b105fa1d5733.tar.gz mruby-192f3df9a1fca043801e42febcd4b105fa1d5733.zip | |
Merge pull request #5362 from dearblue/binding
Binding
Diffstat (limited to 'src')
| -rw-r--r-- | src/proc.c | 105 | ||||
| -rw-r--r-- | src/vm.c | 79 |
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) { @@ -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* |
