mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
Replace global jump with catch handler implementation
When a global jump occurs, look at the catch handler table to determine where to jump. In that case, `pc` already shows the following instruction, but since the table shows `begin_offset ... end_offset`, the comparison is done with `begin_offset < pc && pc <= end_offset`. If there is a corresponding handler, move `pc` to `handler.target_offset` and continue running the VM. When a global jump across `ensure` is made by `return`, `break`, `next`, `redo` and `retry`, the extended `RBreak` object saves and restores the C-level execution position. This extended `RBreak` can have tag information, which makes it a pseudo coroutine (the "tag" mimics CRuby). The implementation of pseudo coroutines by `RBreak` is summarized by `CHECKPOINT_RESTORE ... CHECKPOINT_MAIN ... CHECKPOINT_END` and `throw_tagged_break` / `unwind_ensure` macros. The restart of processing is branched by `RBREAK_TAG_FOREACH(DISPATCH_CHECKPOINTS)`. - Not only `rescue` blocks but also `ensure` blocks are now sandwiched between `OP_EXCEPT` and `OP_RAISEIF`. - Remove the function `ecall()`. It is no longer necessary to re-enter the VM to perform an "ensure block". This will resolves #1888. - Added instruction `OP_JUW` (Jump while UnWind). It jumps unconditionally like `OP_JMP`, but searches the catch handler table and executes the ensure block. Since it searches the catch handler table, it is much heavier than `OP_JMP`.
This commit is contained in:
committed by
Yukihiro "Matz" Matsumoto
parent
0d7b4deccf
commit
c1f112c49a
@@ -62,6 +62,7 @@ sign) of operands.
|
||||
| OP_JMPIF | BS | if R(a) pc=b |
|
||||
| OP_JMPNOT | BS | if !R(a) pc=b |
|
||||
| OP_JMPNIL | BS | if R(a)==nil pc=b |
|
||||
| OP_JUW | S | unwind_and_jump_to(a) |
|
||||
| OP_EXCEPT | B | R(a) = exc |
|
||||
| OP_RESCUE | BB | R(b) = R(a).isa?(R(b)) |
|
||||
| OP_RAISEIF | B | raise(R(a)) if R(a) |
|
||||
|
||||
@@ -149,8 +149,6 @@ typedef struct {
|
||||
mrb_sym mid;
|
||||
const struct RProc *proc;
|
||||
mrb_value *stackent;
|
||||
uint16_t ridx;
|
||||
uint16_t epos;
|
||||
struct REnv *env;
|
||||
const mrb_code *pc; /* return address */
|
||||
const mrb_code *err; /* error position */
|
||||
@@ -177,11 +175,6 @@ struct mrb_context {
|
||||
mrb_callinfo *ci;
|
||||
mrb_callinfo *cibase, *ciend;
|
||||
|
||||
uint16_t *rescue; /* exception handler stack */
|
||||
uint16_t rsize;
|
||||
struct RProc **ensure; /* ensure handler stack */
|
||||
uint16_t esize, eidx;
|
||||
|
||||
enum mrb_fiber_state status : 4;
|
||||
mrb_bool vmexec : 1;
|
||||
struct RFiber *fib;
|
||||
@@ -301,7 +294,6 @@ typedef struct mrb_state {
|
||||
mrb_atexit_func *atexit_stack;
|
||||
#endif
|
||||
uint16_t atexit_stack_len;
|
||||
uint16_t ecall_nest; /* prevent infinite recursive ecall() */
|
||||
} mrb_state;
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,6 +66,39 @@ mrb_break_value_set(struct RBreak *brk, mrb_value val)
|
||||
#define mrb_break_proc_get(brk) ((brk)->proc)
|
||||
#define mrb_break_proc_set(brk, p) ((brk)->proc = p)
|
||||
|
||||
#define RBREAK_TAG_FOREACH(f) \
|
||||
f(RBREAK_TAG_BREAK, 0) \
|
||||
f(RBREAK_TAG_BREAK_UPPER, 1) \
|
||||
f(RBREAK_TAG_BREAK_INTARGET, 2) \
|
||||
f(RBREAK_TAG_RETURN_BLOCK, 3) \
|
||||
f(RBREAK_TAG_RETURN, 4) \
|
||||
f(RBREAK_TAG_RETURN_TOPLEVEL, 5) \
|
||||
f(RBREAK_TAG_JUMP, 6) \
|
||||
f(RBREAK_TAG_STOP, 7)
|
||||
|
||||
#define RBREAK_TAG_DEFINE(tag, i) tag = i,
|
||||
enum {
|
||||
RBREAK_TAG_FOREACH(RBREAK_TAG_DEFINE)
|
||||
};
|
||||
#undef RBREAK_TAG_DEFINE
|
||||
|
||||
#define RBREAK_TAG_BIT 3
|
||||
#define RBREAK_TAG_BIT_OFF 8
|
||||
#define RBREAK_TAG_MASK (~(~UINT32_C(0) << RBREAK_TAG_BIT))
|
||||
|
||||
static inline uint32_t
|
||||
mrb_break_tag_get(struct RBreak *brk)
|
||||
{
|
||||
return (brk->flags >> RBREAK_TAG_BIT_OFF) & RBREAK_TAG_MASK;
|
||||
}
|
||||
|
||||
static inline void
|
||||
mrb_break_tag_set(struct RBreak *brk, uint32_t tag)
|
||||
{
|
||||
brk->flags &= ~(RBREAK_TAG_MASK << RBREAK_TAG_BIT_OFF);
|
||||
brk->flags |= (tag & RBREAK_TAG_MASK) << RBREAK_TAG_BIT_OFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protect
|
||||
*
|
||||
|
||||
@@ -49,6 +49,7 @@ OPCODE(JMP, S) /* pc=a */
|
||||
OPCODE(JMPIF, BS) /* if R(a) pc=b */
|
||||
OPCODE(JMPNOT, BS) /* if !R(a) pc=b */
|
||||
OPCODE(JMPNIL, BS) /* if R(a)==nil pc=b */
|
||||
OPCODE(JUW, S) /* unwind_and_jump_to(a) */
|
||||
OPCODE(EXCEPT, B) /* R(a) = exc */
|
||||
OPCODE(RESCUE, BB) /* R(b) = R(a).isa?(R(b)) */
|
||||
OPCODE(RAISEIF, B) /* raise(R(a)) if R(a) */
|
||||
|
||||
@@ -40,7 +40,6 @@ enum looptype {
|
||||
struct loopinfo {
|
||||
enum looptype type;
|
||||
int pc0, pc1, pc2, pc3, acc;
|
||||
int ensure_level;
|
||||
struct loopinfo *prev;
|
||||
};
|
||||
|
||||
@@ -61,7 +60,6 @@ typedef struct scope {
|
||||
mrb_bool mscope:1;
|
||||
|
||||
struct loopinfo *loop;
|
||||
int ensure_level;
|
||||
mrb_sym filename_sym;
|
||||
uint16_t lineno;
|
||||
|
||||
@@ -1465,16 +1463,19 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
{
|
||||
int noexc, exend, pos1, pos2, tmp;
|
||||
struct loopinfo *lp;
|
||||
int catch_entry, begin, end;
|
||||
|
||||
if (tree->car == NULL) goto exit;
|
||||
lp = loop_push(s, LOOP_BEGIN);
|
||||
lp->pc0 = new_label(s);
|
||||
lp->pc1 = genjmp(s, OP_ONERR, 0);
|
||||
catch_entry = catch_hander_new(s);
|
||||
begin = s->pc;
|
||||
codegen(s, tree->car, VAL);
|
||||
pop();
|
||||
lp->type = LOOP_RESCUE;
|
||||
end = s->pc;
|
||||
noexc = genjmp(s, OP_JMP, 0);
|
||||
dispatch(s, lp->pc1);
|
||||
catch_hander_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc);
|
||||
tree = tree->cdr;
|
||||
exend = 0;
|
||||
pos1 = 0;
|
||||
@@ -1539,7 +1540,6 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
pop();
|
||||
tree = tree->cdr;
|
||||
dispatch(s, noexc);
|
||||
genop_1(s, OP_POPERR, 1);
|
||||
if (tree->car) {
|
||||
codegen(s, tree->car, val);
|
||||
}
|
||||
@@ -1555,14 +1555,22 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
if (!tree->cdr || !tree->cdr->cdr ||
|
||||
(nint(tree->cdr->cdr->car) == NODE_BEGIN &&
|
||||
tree->cdr->cdr->cdr)) {
|
||||
int catch_entry, begin, end, target;
|
||||
int idx;
|
||||
|
||||
s->ensure_level++;
|
||||
idx = scope_body(s, tree->cdr, NOVAL);
|
||||
genop_1(s, OP_EPUSH, idx);
|
||||
catch_entry = catch_hander_new(s);
|
||||
begin = s->pc;
|
||||
codegen(s, tree->car, val);
|
||||
s->ensure_level--;
|
||||
genop_1(s, OP_EPOP, 1);
|
||||
end = target = s->pc;
|
||||
push();
|
||||
idx = cursp();
|
||||
genop_1(s, OP_EXCEPT, idx);
|
||||
push();
|
||||
codegen(s, tree->cdr->cdr, NOVAL);
|
||||
pop();
|
||||
genop_1(s, OP_RAISEIF, idx);
|
||||
pop();
|
||||
catch_hander_set(s, catch_entry, MRB_CATCH_ENSURE, begin, end, target);
|
||||
}
|
||||
else { /* empty ensure ignored */
|
||||
codegen(s, tree->car, val);
|
||||
@@ -2024,18 +2032,20 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
if ((len == 2 && name[0] == '|' && name[1] == '|') &&
|
||||
(nint(tree->car->car) == NODE_CONST ||
|
||||
nint(tree->car->car) == NODE_CVAR)) {
|
||||
int onerr, noexc, exc;
|
||||
int catch_entry, begin, end;
|
||||
int noexc, exc;
|
||||
struct loopinfo *lp;
|
||||
|
||||
onerr = genjmp(s, OP_ONERR, 0);
|
||||
lp = loop_push(s, LOOP_BEGIN);
|
||||
lp->pc1 = onerr;
|
||||
lp->pc0 = new_label(s);
|
||||
catch_entry = catch_hander_new(s);
|
||||
begin = s->pc;
|
||||
exc = cursp();
|
||||
codegen(s, tree->car, VAL);
|
||||
lp->type = LOOP_RESCUE;
|
||||
genop_1(s, OP_POPERR, 1);
|
||||
end = s->pc;
|
||||
noexc = genjmp(s, OP_JMP, 0);
|
||||
dispatch(s, onerr);
|
||||
lp->type = LOOP_RESCUE;
|
||||
catch_hander_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc);
|
||||
genop_1(s, OP_EXCEPT, exc);
|
||||
genop_1(s, OP_LOADF, exc);
|
||||
dispatch(s, noexc);
|
||||
@@ -2288,11 +2298,8 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
raise_error(s, "unexpected next");
|
||||
}
|
||||
else if (s->loop->type == LOOP_NORMAL) {
|
||||
if (s->ensure_level > s->loop->ensure_level) {
|
||||
genop_1(s, OP_EPOP, s->ensure_level - s->loop->ensure_level);
|
||||
}
|
||||
codegen(s, tree, NOVAL);
|
||||
genjmp(s, OP_JMP, s->loop->pc0);
|
||||
genjmp(s, OP_JUW, s->loop->pc0);
|
||||
}
|
||||
else {
|
||||
if (tree) {
|
||||
@@ -2312,10 +2319,7 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
raise_error(s, "unexpected redo");
|
||||
}
|
||||
else {
|
||||
if (s->ensure_level > s->loop->ensure_level) {
|
||||
genop_1(s, OP_EPOP, s->ensure_level - s->loop->ensure_level);
|
||||
}
|
||||
genjmp(s, OP_JMP, s->loop->pc2);
|
||||
genjmp(s, OP_JUW, s->loop->pc2);
|
||||
}
|
||||
if (val) push();
|
||||
break;
|
||||
@@ -2323,32 +2327,16 @@ codegen(codegen_scope *s, node *tree, int val)
|
||||
case NODE_RETRY:
|
||||
{
|
||||
const char *msg = "unexpected retry";
|
||||
const struct loopinfo *lp = s->loop;
|
||||
|
||||
if (!s->loop) {
|
||||
while (lp && lp->type != LOOP_RESCUE) {
|
||||
lp = lp->prev;
|
||||
}
|
||||
if (!lp) {
|
||||
raise_error(s, msg);
|
||||
}
|
||||
else {
|
||||
struct loopinfo *lp = s->loop;
|
||||
int n = 0;
|
||||
|
||||
while (lp && lp->type != LOOP_RESCUE) {
|
||||
if (lp->type == LOOP_BEGIN) {
|
||||
n++;
|
||||
}
|
||||
lp = lp->prev;
|
||||
}
|
||||
if (!lp) {
|
||||
raise_error(s, msg);
|
||||
}
|
||||
else {
|
||||
if (n > 0) {
|
||||
genop_1(s, OP_POPERR, n);
|
||||
}
|
||||
if (s->ensure_level > lp->ensure_level) {
|
||||
genop_1(s, OP_EPOP, s->ensure_level - lp->ensure_level);
|
||||
}
|
||||
genjmp(s, OP_JMP, lp->pc0);
|
||||
}
|
||||
genjmp(s, OP_JUW, lp->pc0);
|
||||
}
|
||||
if (val) push();
|
||||
}
|
||||
@@ -3132,7 +3120,6 @@ loop_push(codegen_scope *s, enum looptype t)
|
||||
p->type = t;
|
||||
p->pc0 = p->pc1 = p->pc2 = p->pc3 = 0;
|
||||
p->prev = s->loop;
|
||||
p->ensure_level = s->ensure_level;
|
||||
p->acc = cursp();
|
||||
s->loop = p;
|
||||
|
||||
@@ -3148,7 +3135,6 @@ loop_break(codegen_scope *s, node *tree)
|
||||
}
|
||||
else {
|
||||
struct loopinfo *loop;
|
||||
int n = 0;
|
||||
|
||||
if (tree) {
|
||||
gen_retval(s, tree);
|
||||
@@ -3157,7 +3143,6 @@ loop_break(codegen_scope *s, node *tree)
|
||||
loop = s->loop;
|
||||
while (loop) {
|
||||
if (loop->type == LOOP_BEGIN) {
|
||||
n++;
|
||||
loop = loop->prev;
|
||||
}
|
||||
else if (loop->type == LOOP_RESCUE) {
|
||||
@@ -3171,20 +3156,14 @@ loop_break(codegen_scope *s, node *tree)
|
||||
raise_error(s, "unexpected break");
|
||||
return;
|
||||
}
|
||||
if (n > 0) {
|
||||
genop_1(s, OP_POPERR, n);
|
||||
}
|
||||
|
||||
if (loop->type == LOOP_NORMAL) {
|
||||
int tmp;
|
||||
|
||||
if (s->ensure_level > s->loop->ensure_level) {
|
||||
genop_1(s, OP_EPOP, s->ensure_level - s->loop->ensure_level);
|
||||
}
|
||||
if (tree) {
|
||||
gen_move(s, loop->acc, cursp(), 0);
|
||||
}
|
||||
tmp = genjmp(s, OP_JMP, loop->pc3);
|
||||
tmp = genjmp(s, OP_JUW, loop->pc3);
|
||||
loop->pc3 = tmp;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -133,8 +133,6 @@ os_memsize_of_object(mrb_state* mrb, mrb_value obj)
|
||||
size += mrb_objspace_page_slot_size() +
|
||||
sizeof(struct RFiber) +
|
||||
sizeof(struct mrb_context) +
|
||||
sizeof(struct RProc *) * f->cxt->esize +
|
||||
sizeof(uint16_t *) * f->cxt->rsize +
|
||||
sizeof(mrb_value) * stack_size +
|
||||
sizeof(mrb_callinfo) * ci_size;
|
||||
break;
|
||||
|
||||
@@ -261,6 +261,9 @@ codedump(mrb_state *mrb, const mrb_irep *irep)
|
||||
CASE(OP_JMP, S);
|
||||
printf("OP_JMP\t\t%03d\n", a);
|
||||
break;
|
||||
CASE(OP_JUW, S);
|
||||
printf("OP_JUW\t\t%03d\n", a);
|
||||
break;
|
||||
CASE(OP_JMPIF, BS);
|
||||
printf("OP_JMPIF\tR%d\t%03d\t", a, b);
|
||||
print_lv_a(mrb, irep, a);
|
||||
|
||||
@@ -640,7 +640,6 @@ mark_context_stack(mrb_state *mrb, struct mrb_context *c)
|
||||
static void
|
||||
mark_context(mrb_state *mrb, struct mrb_context *c)
|
||||
{
|
||||
int i;
|
||||
mrb_callinfo *ci;
|
||||
|
||||
start:
|
||||
@@ -657,10 +656,6 @@ mark_context(mrb_state *mrb, struct mrb_context *c)
|
||||
mrb_gc_mark(mrb, (struct RBasic*)ci->target_class);
|
||||
}
|
||||
}
|
||||
/* mark ensure stack */
|
||||
for (i=0; i<c->eidx; i++) {
|
||||
mrb_gc_mark(mrb, (struct RBasic*)c->ensure[i]);
|
||||
}
|
||||
/* mark fibers */
|
||||
mrb_gc_mark(mrb, (struct RBasic*)c->fib);
|
||||
if (c->prev) {
|
||||
@@ -1014,9 +1009,6 @@ gc_gray_counts(mrb_state *mrb, mrb_gc *gc, struct RBasic *obj)
|
||||
if (c->stbase + i > c->stend) i = c->stend - c->stbase;
|
||||
children += i;
|
||||
|
||||
/* mark ensure stack */
|
||||
children += c->eidx;
|
||||
|
||||
/* mark closure */
|
||||
if (c->cibase) {
|
||||
for (i=0, ci = c->cibase; ci <= c->ci; i++, ci++)
|
||||
|
||||
@@ -171,8 +171,6 @@ mrb_free_context(mrb_state *mrb, struct mrb_context *c)
|
||||
if (!c) return;
|
||||
mrb_free(mrb, c->stbase);
|
||||
mrb_free(mrb, c->cibase);
|
||||
mrb_free(mrb, c->rescue);
|
||||
mrb_free(mrb, c->ensure);
|
||||
mrb_free(mrb, c);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <mruby/opcode.h>
|
||||
#include "value_array.h"
|
||||
#include <mruby/throw.h>
|
||||
#include <mruby/dump.h>
|
||||
|
||||
#ifdef MRB_DISABLE_STDIO
|
||||
#if defined(__cplusplus)
|
||||
@@ -280,8 +281,6 @@ cipush(mrb_state *mrb, const mrb_code *pc, int push_stacks, int acc,
|
||||
ci->mid = mid;
|
||||
ci->proc = proc;
|
||||
ci->stackent = c->stack;
|
||||
ci->epos = c->eidx;
|
||||
ci->ridx = ci[-1].ridx;
|
||||
ci->pc = pc;
|
||||
ci->argc = argc;
|
||||
ci->acc = acc;
|
||||
@@ -329,54 +328,6 @@ cipop(mrb_state *mrb)
|
||||
void mrb_exc_set(mrb_state *mrb, mrb_value exc);
|
||||
static mrb_value mrb_run(mrb_state *mrb, const struct RProc* proc, mrb_value self);
|
||||
|
||||
static void
|
||||
ecall(mrb_state *mrb)
|
||||
{
|
||||
struct RProc *p;
|
||||
struct mrb_context *c = mrb->c;
|
||||
mrb_callinfo *ci = c->ci;
|
||||
struct RObject *exc;
|
||||
struct REnv *env;
|
||||
ptrdiff_t cioff;
|
||||
int ai = mrb_gc_arena_save(mrb);
|
||||
uint16_t i;
|
||||
int nregs;
|
||||
|
||||
if (c->eidx == 0) return;
|
||||
i = --c->eidx;
|
||||
|
||||
/* restrict total call depth of ecall() */
|
||||
if (++mrb->ecall_nest > MRB_ECALL_DEPTH_MAX) {
|
||||
mrb_exc_raise(mrb, mrb_obj_value(mrb->stack_err));
|
||||
}
|
||||
p = c->ensure[i];
|
||||
if (!p) return;
|
||||
mrb_assert(!MRB_PROC_CFUNC_P(p));
|
||||
c->ensure[i] = NULL;
|
||||
nregs = p->upper->body.irep->nregs;
|
||||
if (ci->proc && !MRB_PROC_CFUNC_P(ci->proc) &&
|
||||
ci->proc->body.irep->nregs > nregs) {
|
||||
nregs = ci->proc->body.irep->nregs;
|
||||
}
|
||||
cioff = ci - c->cibase;
|
||||
ci = cipush(mrb, NULL, nregs, CI_ACC_SKIP, MRB_PROC_TARGET_CLASS(p), p, ci->mid, 0);
|
||||
env = MRB_PROC_ENV(p);
|
||||
mrb_assert(env);
|
||||
exc = mrb->exc; mrb->exc = 0;
|
||||
if (exc) {
|
||||
mrb_gc_protect(mrb, mrb_obj_value(exc));
|
||||
}
|
||||
if (mrb->c->fib) {
|
||||
mrb_gc_protect(mrb, mrb_obj_value(mrb->c->fib));
|
||||
}
|
||||
mrb_run(mrb, p, env->stack[0]);
|
||||
mrb->c = c;
|
||||
c->ci = c->cibase + cioff;
|
||||
if (!mrb->exc) mrb->exc = exc;
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb->ecall_nest--;
|
||||
}
|
||||
|
||||
#ifndef MRB_FUNCALL_ARGC_MAX
|
||||
#define MRB_FUNCALL_ARGC_MAX 16
|
||||
#endif
|
||||
@@ -821,17 +772,56 @@ mrb_yield_cont(mrb_state *mrb, mrb_value b, mrb_value self, mrb_int argc, const
|
||||
}
|
||||
|
||||
static struct RBreak*
|
||||
break_new(mrb_state *mrb, const struct RProc *p, mrb_value val)
|
||||
break_new(mrb_state *mrb, uint32_t tag, const struct RProc *p, mrb_value val)
|
||||
{
|
||||
struct RBreak *brk;
|
||||
|
||||
brk = (struct RBreak*)mrb_obj_alloc(mrb, MRB_TT_BREAK, NULL);
|
||||
mrb_break_proc_set(brk, p);
|
||||
mrb_break_value_set(brk, val);
|
||||
mrb_break_tag_set(brk, tag);
|
||||
|
||||
return brk;
|
||||
}
|
||||
|
||||
#define MRB_CATCH_FILTER_RESCUE (UINT32_C(1) << MRB_CATCH_RESCUE)
|
||||
#define MRB_CATCH_FILTER_ENSURE (UINT32_C(1) << MRB_CATCH_ENSURE)
|
||||
#define MRB_CATCH_FILTER_ALL (MRB_CATCH_FILTER_RESCUE | MRB_CATCH_FILTER_ENSURE)
|
||||
|
||||
static const struct mrb_irep_catch_hander *
|
||||
catch_handler_find(mrb_state *mrb, mrb_callinfo *ci, const mrb_code *pc, uint32_t filter)
|
||||
{
|
||||
mrb_irep *irep;
|
||||
ptrdiff_t xpc;
|
||||
size_t cnt;
|
||||
const struct mrb_irep_catch_hander *e;
|
||||
|
||||
/* The comparison operators use `>` and `<=` because pc already points to the next instruction */
|
||||
#define catch_cover_p(pc, beg, end) ((pc) > (beg) && (pc) <= (end))
|
||||
|
||||
if (ci->proc == NULL || MRB_PROC_CFUNC_P(ci->proc)) return NULL;
|
||||
irep = ci->proc->body.irep;
|
||||
if (irep->clen < 1) return NULL;
|
||||
xpc = pc - irep->iseq;
|
||||
/* If it retry at the top level, pc will be 0, so check with -1 as the start position */
|
||||
mrb_assert(catch_cover_p(xpc, -1, irep->ilen));
|
||||
if (!catch_cover_p(xpc, -1, irep->ilen)) return NULL;
|
||||
|
||||
/* Currently uses a simple linear search to avoid processing complexity. */
|
||||
cnt = irep->clen;
|
||||
e = mrb_irep_catch_handler_table(irep) + cnt - 1;
|
||||
for (; cnt > 0; cnt --, e --) {
|
||||
if (((UINT32_C(1) << e->type) & filter) &&
|
||||
catch_cover_p(xpc, bin_to_uint16(e->begin), bin_to_uint16(e->end))) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
#undef catch_cover_p
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
LOCALJUMP_ERROR_RETURN = 0,
|
||||
LOCALJUMP_ERROR_BREAK = 1,
|
||||
@@ -878,6 +868,81 @@ argnum_error(mrb_state *mrb, mrb_int num)
|
||||
mrb_exc_set(mrb, exc);
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
break_tag_p(struct RBreak *brk, uint32_t tag)
|
||||
{
|
||||
return (brk != NULL && brk->tt == MRB_TT_BREAK) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
prepare_tagged_break(mrb_state *mrb, uint32_t tag, struct RProc *proc, mrb_value val)
|
||||
{
|
||||
if (break_tag_p((struct RBreak*)mrb->exc, tag)) {
|
||||
mrb_break_tag_set((struct RBreak*)mrb->exc, tag);
|
||||
}
|
||||
else {
|
||||
mrb->exc = (struct RObject*)break_new(mrb, tag, proc, val);
|
||||
}
|
||||
}
|
||||
|
||||
#define THROW_TAGGED_BREAK(mrb, tag, proc, val) \
|
||||
do { \
|
||||
prepare_tagged_break(mrb, tag, proc, val); \
|
||||
goto L_CATCH_TAGGED_BREAK; \
|
||||
} while (0)
|
||||
|
||||
#define UNWIND_ENSURE(mrb, ci, pc, tag, proc, val) \
|
||||
do { \
|
||||
ch = catch_handler_find(mrb, ci, pc, MRB_CATCH_FILTER_ENSURE); \
|
||||
if (ch) { \
|
||||
THROW_TAGGED_BREAK(mrb, tag, proc, val); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* CHECKPOINT_RESTORE(tag) {
|
||||
* This part is executed when jumping by the same "tag" of RBreak (it is not executed the first time).
|
||||
* Write the code required (initialization of variables, etc.) for the subsequent processing.
|
||||
* }
|
||||
* CHECKPOINT_MAIN(tag) {
|
||||
* This part is always executed.
|
||||
* }
|
||||
* CHECKPOINT_END(tag);
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // Jump to CHECKPOINT_RESTORE with the same "tag".
|
||||
* goto CHECKPOINT_LABEL_MAKE(tag);
|
||||
*/
|
||||
|
||||
#define CHECKPOINT_LABEL_MAKE(tag) L_CHECKPOINT_ ## tag
|
||||
|
||||
#define CHECKPOINT_RESTORE(tag) \
|
||||
do { \
|
||||
DEBUG_ONLY_EXPR(int current_checkpoint_tag); \
|
||||
DEBUG_ONLY_EXPR(current_checkpoint_tag = (tag)); \
|
||||
if (FALSE) { \
|
||||
CHECKPOINT_LABEL_MAKE(tag): \
|
||||
DEBUG_ONLY_EXPR(current_checkpoint_tag = (tag)); \
|
||||
do {
|
||||
|
||||
#define CHECKPOINT_MAIN(tag) \
|
||||
} while (0); \
|
||||
} \
|
||||
mrb_assert((tag) == current_checkpoint_tag); \
|
||||
do {
|
||||
|
||||
#define CHECKPOINT_END(tag) \
|
||||
} while (0); \
|
||||
mrb_assert((tag) == current_checkpoint_tag); \
|
||||
} while (0)
|
||||
|
||||
#ifdef MRB_DEBUG
|
||||
#define DEBUG_ONLY_EXPR(e) e
|
||||
#else
|
||||
#define DEBUG_ONLY_EXPR(e) ((void)0)
|
||||
#endif
|
||||
|
||||
#define ERR_PC_SET(mrb) mrb->c->ci->err = pc0;
|
||||
#define ERR_PC_CLR(mrb) mrb->c->ci->err = 0;
|
||||
#ifdef MRB_ENABLE_DEBUG_HOOK
|
||||
@@ -976,6 +1041,7 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)
|
||||
uint16_t b;
|
||||
uint8_t c;
|
||||
mrb_sym mid;
|
||||
const struct mrb_irep_catch_hander *ch;
|
||||
|
||||
#ifdef DIRECT_THREADED
|
||||
static void *optable[] = {
|
||||
@@ -1231,6 +1297,30 @@ RETRY_TRY_BLOCK:
|
||||
NEXT;
|
||||
}
|
||||
|
||||
CASE(OP_JUW, S) {
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_JUMP) {
|
||||
struct RBreak *brk = (struct RBreak*)mrb->exc;
|
||||
mrb_value target = mrb_break_value_get(brk);
|
||||
mrb_assert(mrb_fixnum_p(target));
|
||||
a = mrb_fixnum(target);
|
||||
mrb_assert(a >= 0 && a < irep->ilen);
|
||||
}
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_JUMP) {
|
||||
ch = catch_handler_find(mrb, mrb->c->ci, pc, MRB_CATCH_FILTER_ENSURE);
|
||||
if (ch) {
|
||||
/* avoiding a jump from a catch handler into the same handler */
|
||||
if (a < bin_to_uint16(ch->begin) || a >= bin_to_uint16(ch->end)) {
|
||||
THROW_TAGGED_BREAK(mrb, RBREAK_TAG_JUMP, proc, mrb_fixnum_value(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_JUMP);
|
||||
|
||||
mrb->exc = NULL; /* clear break object */
|
||||
pc = irep->iseq + a;
|
||||
JUMP;
|
||||
}
|
||||
|
||||
CASE(OP_EXCEPT, B) {
|
||||
mrb_value exc;
|
||||
|
||||
@@ -1563,6 +1653,7 @@ RETRY_TRY_BLOCK:
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
if (mrb->exc) goto L_RAISE;
|
||||
ci = mrb->c->ci;
|
||||
mrb_assert(!mrb_break_p(v));
|
||||
if (!ci->target_class) { /* return from context modifying method (resume/yield) */
|
||||
if (ci->acc == CI_ACC_RESUMED) {
|
||||
mrb->jmp = prev_jmp;
|
||||
@@ -1839,13 +1930,7 @@ RETRY_TRY_BLOCK:
|
||||
c = OP_R_NORMAL;
|
||||
L_RETURN:
|
||||
{
|
||||
mrb_callinfo *ci;
|
||||
|
||||
#define ecall_adjust() do {\
|
||||
ptrdiff_t cioff = ci - mrb->c->cibase;\
|
||||
ecall(mrb);\
|
||||
ci = mrb->c->cibase + cioff;\
|
||||
} while (0)
|
||||
mrb_callinfo *ci;
|
||||
|
||||
ci = mrb->c->ci;
|
||||
if (ci->mid) {
|
||||
@@ -1873,17 +1958,20 @@ RETRY_TRY_BLOCK:
|
||||
L_RAISE:
|
||||
ci0 = ci = mrb->c->ci;
|
||||
if (ci == mrb->c->cibase) {
|
||||
if (ci->ridx == 0) goto L_FTOP;
|
||||
goto L_RESCUE;
|
||||
ch = catch_handler_find(mrb, ci, pc, MRB_CATCH_FILTER_ALL);
|
||||
if (ch == NULL) goto L_FTOP;
|
||||
goto L_CATCH;
|
||||
}
|
||||
while (ci[0].ridx == ci[-1].ridx) {
|
||||
while ((ch = catch_handler_find(mrb, ci, pc, MRB_CATCH_FILTER_ALL)) == NULL) {
|
||||
ci = cipop(mrb);
|
||||
if (ci[1].acc == CI_ACC_SKIP && prev_jmp) {
|
||||
mrb->jmp = prev_jmp;
|
||||
MRB_THROW(prev_jmp);
|
||||
}
|
||||
pc = ci[1].pc;
|
||||
if (ci == mrb->c->cibase) {
|
||||
if (ci->ridx == 0) {
|
||||
ch = catch_handler_find(mrb, ci, pc, MRB_CATCH_FILTER_ALL);
|
||||
if (ch == NULL) {
|
||||
L_FTOP: /* fiber top */
|
||||
if (mrb->c == mrb->root_c) {
|
||||
mrb->c->stack = mrb->c->stbase;
|
||||
@@ -1892,9 +1980,6 @@ RETRY_TRY_BLOCK:
|
||||
else {
|
||||
struct mrb_context *c = mrb->c;
|
||||
|
||||
while (c->eidx > ci->epos) {
|
||||
ecall_adjust();
|
||||
}
|
||||
c->status = MRB_FIBER_TERMINATED;
|
||||
mrb->c = c->prev;
|
||||
c->prev = NULL;
|
||||
@@ -1903,15 +1988,13 @@ RETRY_TRY_BLOCK:
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* call ensure only when we skip this callinfo */
|
||||
if (ci[0].ridx == ci[-1].ridx) {
|
||||
while (mrb->c->eidx > ci->epos) {
|
||||
ecall_adjust();
|
||||
}
|
||||
}
|
||||
}
|
||||
L_RESCUE:
|
||||
if (ci->ridx == 0) goto L_STOP;
|
||||
L_CATCH:
|
||||
if (ch == NULL) goto L_STOP;
|
||||
if (FALSE) {
|
||||
L_CATCH_TAGGED_BREAK: /* from THROW_TAGGED_BREAK() or UNWIND_ENSURE() */
|
||||
ci = ci0 = mrb->c->ci;
|
||||
}
|
||||
proc = ci->proc;
|
||||
irep = proc->body.irep;
|
||||
pool = irep->pool;
|
||||
@@ -1920,7 +2003,7 @@ RETRY_TRY_BLOCK:
|
||||
mrb->c->stack = ci[1].stackent;
|
||||
}
|
||||
mrb_stack_extend(mrb, irep->nregs);
|
||||
pc = irep->iseq+mrb->c->rescue[--ci->ridx];
|
||||
pc = irep->iseq + bin_to_uint16(ch->target);
|
||||
}
|
||||
else {
|
||||
int acc;
|
||||
@@ -1950,8 +2033,19 @@ RETRY_TRY_BLOCK:
|
||||
localjump_error(mrb, LOCALJUMP_ERROR_RETURN);
|
||||
goto L_RAISE;
|
||||
}
|
||||
ci--;
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_RETURN_BLOCK) {
|
||||
cibase = mrb->c->cibase;
|
||||
dst = top_proc(mrb, proc);
|
||||
}
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_RETURN_BLOCK) {
|
||||
UNWIND_ENSURE(mrb, ci, pc, RBREAK_TAG_RETURN_BLOCK, proc, v);
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_RETURN_BLOCK);
|
||||
pc = ci->pc;
|
||||
ci = cipop(mrb);
|
||||
}
|
||||
mrb->exc = NULL; /* clear break object */
|
||||
proc = ci->proc;
|
||||
if (ci <= cibase) {
|
||||
localjump_error(mrb, LOCALJUMP_ERROR_RETURN);
|
||||
goto L_RAISE;
|
||||
@@ -1966,16 +2060,20 @@ RETRY_TRY_BLOCK:
|
||||
|
||||
if (!c->prev) { /* toplevel return */
|
||||
regs[irep->nlocals] = v;
|
||||
goto L_STOP;
|
||||
goto CHECKPOINT_LABEL_MAKE(RBREAK_TAG_STOP);
|
||||
}
|
||||
if (c->prev->ci == c->prev->cibase) {
|
||||
mrb_value exc = mrb_exc_new_str_lit(mrb, E_FIBER_ERROR, "double resume");
|
||||
mrb_exc_set(mrb, exc);
|
||||
goto L_RAISE;
|
||||
}
|
||||
while (c->eidx > 0) {
|
||||
ecall(mrb);
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_RETURN_TOPLEVEL) {
|
||||
c = mrb->c;
|
||||
}
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_RETURN_TOPLEVEL) {
|
||||
UNWIND_ENSURE(mrb, ci, pc, RBREAK_TAG_RETURN_TOPLEVEL, proc, v);
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_RETURN_TOPLEVEL);
|
||||
/* automatic yield at the end */
|
||||
c->status = MRB_FIBER_TERMINATED;
|
||||
mrb->c = c->prev;
|
||||
@@ -1983,6 +2081,14 @@ RETRY_TRY_BLOCK:
|
||||
mrb->c->status = MRB_FIBER_RUNNING;
|
||||
ci = mrb->c->ci;
|
||||
}
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_RETURN) {
|
||||
/* do nothing */
|
||||
}
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_RETURN) {
|
||||
UNWIND_ENSURE(mrb, ci, pc, RBREAK_TAG_RETURN, proc, v);
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_RETURN);
|
||||
mrb->exc = NULL; /* clear break object */
|
||||
break;
|
||||
case OP_R_BREAK:
|
||||
if (MRB_PROC_STRICT_P(proc)) goto NORMAL_RETURN;
|
||||
@@ -2005,9 +2111,13 @@ RETRY_TRY_BLOCK:
|
||||
goto L_BREAK_ERROR;
|
||||
}
|
||||
}
|
||||
while (mrb->c->eidx > mrb->c->ci->epos) {
|
||||
ecall_adjust();
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_BREAK) {
|
||||
/* do nothing */
|
||||
}
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_BREAK) {
|
||||
UNWIND_ENSURE(mrb, ci, pc, RBREAK_TAG_BREAK, proc, v);
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_BREAK);
|
||||
/* break from fiber block */
|
||||
if (ci == mrb->c->cibase && ci->pc) {
|
||||
struct mrb_context *c = mrb->c;
|
||||
@@ -2017,45 +2127,64 @@ RETRY_TRY_BLOCK:
|
||||
ci = mrb->c->ci;
|
||||
}
|
||||
if (ci->acc < 0) {
|
||||
ci = cipop(mrb);
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb->c->vmexec = FALSE;
|
||||
mrb->exc = (struct RObject*)break_new(mrb, proc, v);
|
||||
mrb->exc = (struct RObject*)break_new(mrb, RBREAK_TAG_BREAK, proc, v);
|
||||
mrb->jmp = prev_jmp;
|
||||
MRB_THROW(prev_jmp);
|
||||
}
|
||||
if (FALSE) {
|
||||
struct RBreak *brk;
|
||||
|
||||
L_BREAK:
|
||||
v = mrb_break_value_get((struct RBreak*)mrb->exc);
|
||||
proc = mrb_break_proc_get((struct RBreak*)mrb->exc);
|
||||
mrb->exc = NULL;
|
||||
brk = (struct RBreak*)mrb->exc;
|
||||
proc = mrb_break_proc_get(brk);
|
||||
v = mrb_break_value_get(brk);
|
||||
ci = mrb->c->ci;
|
||||
|
||||
switch (mrb_break_tag_get(brk)) {
|
||||
#define DISPATCH_CHECKPOINTS(n, i) case n: goto CHECKPOINT_LABEL_MAKE(n);
|
||||
RBREAK_TAG_FOREACH(DISPATCH_CHECKPOINTS)
|
||||
#undef DISPATCH_CHECKPOINTS
|
||||
default:
|
||||
mrb_assert(!"wrong break tag");
|
||||
}
|
||||
}
|
||||
mrb->c->stack = ci->stackent;
|
||||
proc = proc->upper;
|
||||
while (mrb->c->cibase < ci && ci[-1].proc != proc) {
|
||||
while (mrb->c->cibase < ci && ci[-1].proc != proc->upper) {
|
||||
if (ci[-1].acc == CI_ACC_SKIP) {
|
||||
while (ci < mrb->c->ci) {
|
||||
cipop(mrb);
|
||||
}
|
||||
goto L_BREAK_ERROR;
|
||||
}
|
||||
ci--;
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_BREAK_UPPER) {
|
||||
/* do nothing */
|
||||
}
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_BREAK_UPPER) {
|
||||
UNWIND_ENSURE(mrb, ci, pc, RBREAK_TAG_BREAK_UPPER, proc, v);
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_BREAK_UPPER);
|
||||
pc = ci->pc;
|
||||
ci = cipop(mrb);
|
||||
}
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_BREAK_INTARGET) {
|
||||
/* do nothing */
|
||||
}
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_BREAK_INTARGET) {
|
||||
UNWIND_ENSURE(mrb, ci, pc, RBREAK_TAG_BREAK_INTARGET, proc, v);
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_BREAK_INTARGET);
|
||||
if (ci == mrb->c->cibase) {
|
||||
goto L_BREAK_ERROR;
|
||||
}
|
||||
mrb->exc = NULL; /* clear break object */
|
||||
break;
|
||||
default:
|
||||
/* cannot happen */
|
||||
break;
|
||||
}
|
||||
while (ci < mrb->c->ci) {
|
||||
cipop(mrb);
|
||||
}
|
||||
ci[0].ridx = ci[-1].ridx;
|
||||
while (mrb->c->eidx > ci->epos) {
|
||||
ecall_adjust();
|
||||
}
|
||||
mrb_assert(ci == mrb->c->ci);
|
||||
mrb_assert(mrb->exc == NULL);
|
||||
|
||||
if (mrb->c->vmexec && !ci->target_class) {
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
mrb->c->vmexec = FALSE;
|
||||
@@ -2679,14 +2808,18 @@ RETRY_TRY_BLOCK:
|
||||
|
||||
CASE(OP_STOP, Z) {
|
||||
/* stop VM */
|
||||
L_STOP:
|
||||
while (mrb->c->eidx > 0) {
|
||||
ecall(mrb);
|
||||
CHECKPOINT_RESTORE(RBREAK_TAG_STOP) {
|
||||
/* do nothing */
|
||||
}
|
||||
mrb->c->cibase->ridx = 0;
|
||||
CHECKPOINT_MAIN(RBREAK_TAG_STOP) {
|
||||
UNWIND_ENSURE(mrb, mrb->c->ci, pc, RBREAK_TAG_STOP, proc, mrb_nil_value());
|
||||
}
|
||||
CHECKPOINT_END(RBREAK_TAG_STOP);
|
||||
L_STOP:
|
||||
ERR_PC_CLR(mrb);
|
||||
mrb->jmp = prev_jmp;
|
||||
if (mrb->exc) {
|
||||
mrb_assert(mrb->exc->tt == MRB_TT_EXCEPTION);
|
||||
return mrb_obj_value(mrb->exc);
|
||||
}
|
||||
return regs[irep->nlocals];
|
||||
@@ -2696,6 +2829,10 @@ RETRY_TRY_BLOCK:
|
||||
#undef regs
|
||||
}
|
||||
MRB_CATCH(&c_jmp) {
|
||||
mrb_callinfo *ci = mrb->c->ci;
|
||||
while (ci > mrb->c->cibase && ci->acc == CI_ACC_DIRECT) {
|
||||
ci = cipop(mrb);
|
||||
}
|
||||
exc_catched = TRUE;
|
||||
goto RETRY_TRY_BLOCK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user