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:
dearblue
2020-07-18 19:05:12 +09:00
committed by Yukihiro "Matz" Matsumoto
parent 0d7b4deccf
commit c1f112c49a
10 changed files with 311 additions and 177 deletions
+1
View File
@@ -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) |
-8
View File
@@ -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;
/**
+33
View File
@@ -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
*
+1
View File
@@ -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) */
+35 -56
View File
@@ -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 {
-2
View File
@@ -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;
+3
View File
@@ -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);
-8
View File
@@ -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++)
-2
View File
@@ -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);
}
+238 -101
View File
@@ -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;
}