Browse Source

Handle BREAK and CONTINUE opcodes without longjmp

Also RETURN and other control flow cleanups.  DUK_SETJMP() return value
consistency.
pull/348/head
Sami Vaarala 9 years ago
parent
commit
e5e172726a
  1. 12
      src/duk_heap.h
  2. 527
      src/duk_js_executor.c

12
src/duk_heap.h

@ -52,12 +52,12 @@
#define DUK_LJ_TYPE_UNKNOWN 0 /* unused */
#define DUK_LJ_TYPE_THROW 1 /* value1 -> error object */
#define DUK_LJ_TYPE_BREAK 2 /* value1 -> label number */
#define DUK_LJ_TYPE_CONTINUE 3 /* value1 -> label number */
#define DUK_LJ_TYPE_YIELD 4 /* value1 -> yield value, iserror -> error / normal */
#define DUK_LJ_TYPE_RESUME 5 /* value1 -> resume value, value2 -> resumee thread, iserror -> error/normal */
#define DUK_LJ_TYPE_NORMAL 6 /* pseudo-type to indicate a normal continuation (for 'finally') */
#define DUK_LJ_TYPE_RETURN 7 /* pseudo-type to indicate a return continuation (for 'finally') */
#define DUK_LJ_TYPE_YIELD 2 /* value1 -> yield value, iserror -> error / normal */
#define DUK_LJ_TYPE_RESUME 3 /* value1 -> resume value, value2 -> resumee thread, iserror -> error/normal */
#define DUK_LJ_TYPE_BREAK 4 /* value1 -> label number, pseudo-type to indicate a break continuation (for ENDFIN) */
#define DUK_LJ_TYPE_CONTINUE 5 /* value1 -> label number, pseudo-type to indicate a continue continuation (for ENDFIN) */
#define DUK_LJ_TYPE_RETURN 6 /* value1 -> return value, pseudo-type to indicate a return continuation (for ENDFIN) */
#define DUK_LJ_TYPE_NORMAL 7 /* no value, pseudo-type to indicate a normal continuation (for ENDFIN) */
/*
* Mark-and-sweep flags

527
src/duk_js_executor.c

@ -4,12 +4,6 @@
#include "duk_internal.h"
/*
* Local declarations
*/
DUK_LOCAL_DECL void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_small_uint_t retval_count);
/*
* Arithmetic, binary, and logical helpers.
*
@ -575,19 +569,13 @@ DUK_LOCAL void duk__vm_logical_not(duk_hthread *thr, duk_tval *tv_x, duk_tval *t
}
/*
* Longjmp handler for the bytecode executor (and a bunch of static
* helpers for it).
* Longjmp and other control flow transfer for the bytecode executor.
*
* Any type of longjmp() can be caught here, including intra-function
* longjmp()s like 'break', 'continue', (slow) 'return', 'yield', etc.
*
* Error policy: should not ordinarily throw errors. Errors thrown
* will bubble outwards.
*
* Returns:
* 0 restart execution
* 1 bytecode executor finished
* 2 rethrow longjmp
* The longjmp handler can handle all longjmp types: error, yield, and
* resume (pseudotypes are never actually thrown). Error policy for
* longjmp: should not ordinarily throw errors; if errors occur (e.g.
* due to out-of-memory) they bubble outwards rather than being handled
* recursively.
*/
/* XXX: duk_api operations for cross-thread reg manipulation? */
@ -597,9 +585,18 @@ DUK_LOCAL void duk__vm_logical_not(duk_hthread *thr, duk_tval *tv_x, duk_tval *t
#define DUK__LONGJMP_FINISHED 1 /* exit bytecode executor with return value */
#define DUK__LONGJMP_RETHROW 2 /* exit bytecode executor by rethrowing an error to caller */
/* only called when act_idx points to an Ecmascript function */
DUK_LOCAL void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_small_uint_t retval_count) {
#define DUK__RETHAND_RESTART 0 /* state updated, restart bytecode execution */
#define DUK__RETHAND_FINISHED 1 /* exit bytecode execution with return value */
/* XXX: optimize reconfig valstack operations so that resize, clamp, and setting
* top are combined into one pass.
*/
/* Reconfigure value stack for return to an Ecmascript function at 'act_idx'. */
DUK_LOCAL void duk__reconfig_valstack_ecma_return(duk_hthread *thr, duk_size_t act_idx) {
duk_activation *act;
duk_hcompiledfunction *h_func;
duk_idx_t clamp_top;
DUK_ASSERT(thr != NULL);
DUK_ASSERT_DISABLE(act_idx >= 0); /* unsigned */
@ -607,22 +604,19 @@ DUK_LOCAL void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_
DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + act_idx)));
DUK_ASSERT_DISABLE(thr->callstack[act_idx].idx_retval >= 0); /* unsigned */
thr->valstack_bottom = thr->valstack + thr->callstack[act_idx].idx_bottom;
/* clamp so that retval is at the top (retval_count == 1) or register just before
* intended retval is at the top (retval_count == 0, happens e.g. with 'finally').
/* Clamp so that values at 'clamp_top' and above are wiped and won't
* retain reachable garbage. Then extend to 'nregs' because we're
* returning to an Ecmascript function.
*/
duk_set_top((duk_context *) thr,
(duk_idx_t) (thr->callstack[act_idx].idx_retval -
thr->callstack[act_idx].idx_bottom +
retval_count));
/*
* When returning to an Ecmascript function, extend the valstack
* top to 'nregs' always.
*/
act = thr->callstack + act_idx;
h_func = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act);
h_func = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(thr->callstack + act_idx);
thr->valstack_bottom = thr->valstack + act->idx_bottom;
DUK_ASSERT(act->idx_retval >= act->idx_bottom);
clamp_top = (duk_idx_t) (act->idx_retval - act->idx_bottom + 1); /* +1 = one retval */
duk_set_top((duk_context *) thr, clamp_top);
act = NULL;
(void) duk_valstack_resize_raw((duk_context *) thr,
(thr->valstack_bottom - thr->valstack) + /* bottom of current func */
@ -635,63 +629,90 @@ DUK_LOCAL void duk__reconfig_valstack(duk_hthread *thr, duk_size_t act_idx, duk_
duk_set_top((duk_context *) thr, h_func->nregs);
}
DUK_LOCAL void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx, duk_bool_t is_finally) {
duk_context *ctx = (duk_context *) thr;
duk_tval tv_tmp;
duk_tval *tv1;
DUK_LOCAL void duk__reconfig_valstack_ecma_catcher(duk_hthread *thr, duk_size_t act_idx, duk_size_t cat_idx) {
duk_activation *act;
duk_catcher *cat;
duk_hcompiledfunction *h_func;
duk_idx_t clamp_top;
DUK_DDD(DUK_DDDPRINT("handling catch/finally, cat_idx=%ld, is_finally=%ld",
(long) cat_idx, (long) is_finally));
DUK_ASSERT(thr != NULL);
DUK_ASSERT_DISABLE(act_idx >= 0); /* unsigned */
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + act_idx) != NULL);
DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + act_idx)));
DUK_ASSERT_DISABLE(thr->callstack[act_idx].idx_retval >= 0); /* unsigned */
/*
* Set caught value and longjmp type to catcher regs.
*/
act = thr->callstack + act_idx;
cat = thr->catchstack + cat_idx;
h_func = (duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act);
DUK_DDD(DUK_DDDPRINT("writing catch registers: idx_base=%ld -> %!T, idx_base+1=%ld -> %!T",
(long) thr->catchstack[cat_idx].idx_base,
(duk_tval *) &thr->heap->lj.value1,
(long) (thr->catchstack[cat_idx].idx_base + 1),
(duk_tval *) &thr->heap->lj.value2));
thr->valstack_bottom = thr->valstack + act->idx_bottom;
DUK_ASSERT(cat->idx_base >= act->idx_bottom);
clamp_top = (duk_idx_t) (cat->idx_base - act->idx_bottom + 2); /* +2 = catcher value, catcher lj_type */
duk_set_top((duk_context *) thr, clamp_top);
act = NULL;
cat = NULL;
(void) duk_valstack_resize_raw((duk_context *) thr,
(thr->valstack_bottom - thr->valstack) + /* bottom of current func */
h_func->nregs + /* reg count */
DUK_VALSTACK_INTERNAL_EXTRA, /* + spare */
DUK_VSRESIZE_FLAG_SHRINK | /* flags */
0 /* no compact */ |
DUK_VSRESIZE_FLAG_THROW);
duk_set_top((duk_context *) thr, h_func->nregs);
}
/* Set catcher regs: idx_base+0 = value, idx_base+1 = lj_type. */
DUK_LOCAL void duk__set_catcher_regs(duk_hthread *thr, duk_size_t cat_idx, duk_tval *tv_val_unstable, duk_small_uint_t lj_type) {
duk_tval tv_tmp;
duk_tval *tv1;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(tv_val_unstable != NULL);
tv1 = thr->valstack + thr->catchstack[cat_idx].idx_base;
DUK_ASSERT(tv1 < thr->valstack_top);
DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
DUK_TVAL_SET_TVAL(tv1, tv_val_unstable);
DUK_TVAL_INCREF(thr, tv1);
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
tv1 = thr->valstack + thr->catchstack[cat_idx].idx_base + 1;
DUK_ASSERT(tv1 < thr->valstack_top);
DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
DUK_TVAL_SET_NUMBER(tv1, (duk_double_t) thr->heap->lj.type); /* XXX: set int */
#if defined(DUK_USE_FASTINT)
DUK_TVAL_SET_FASTINT_U32(tv1, (duk_uint32_t) lj_type);
#else
DUK_TVAL_SET_NUMBER(tv1, (duk_double_t) lj_type);
#endif
DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv1)); /* no need to incref */
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
}
/*
* Unwind catchstack and callstack.
*
* The 'cat_idx' catcher is always kept, even when executing finally.
*/
DUK_LOCAL void duk__handle_catch(duk_hthread *thr, duk_size_t cat_idx, duk_tval *tv_val_unstable, duk_small_uint_t lj_type) {
duk_context *ctx;
duk_activation *act;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(tv_val_unstable != NULL);
ctx = (duk_context *) thr;
duk__set_catcher_regs(thr, cat_idx, tv_val_unstable, lj_type);
duk_hthread_catchstack_unwind(thr, cat_idx + 1);
duk_hthread_callstack_unwind(thr, thr->catchstack[cat_idx].callstack_index + 1);
/*
* Reconfigure valstack to 'nregs' (this is always the case for
* Ecmascript functions).
*/
DUK_ASSERT(thr->callstack_top >= 1);
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);
DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
thr->valstack_bottom = thr->valstack + (thr->callstack + thr->callstack_top - 1)->idx_bottom;
duk_set_top((duk_context *) thr, ((duk_hcompiledfunction *) DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1))->nregs);
duk__reconfig_valstack_ecma_catcher(thr, thr->callstack_top - 1, cat_idx);
/*
* Reset PC: resume execution from catch or finally jump slot.
*/
(thr->callstack + thr->callstack_top - 1)->curr_pc =
thr->catchstack[cat_idx].pc_base + (is_finally ? 1 : 0);
DUK_ASSERT(thr->callstack_top >= 1);
act = thr->callstack + thr->callstack_top - 1;
act->curr_pc = thr->catchstack[cat_idx].pc_base + 0; /* +0 = catch */
act = NULL;
/*
* If entering a 'catch' block which requires an automatic
@ -703,8 +724,7 @@ DUK_LOCAL void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx
* which implies the binding is not deletable.
*/
if (!is_finally && DUK_CAT_HAS_CATCH_BINDING_ENABLED(&thr->catchstack[cat_idx])) {
duk_activation *act;
if (DUK_CAT_HAS_CATCH_BINDING_ENABLED(&thr->catchstack[cat_idx])) {
duk_hobject *new_env;
duk_hobject *act_lex_env;
@ -737,7 +757,7 @@ DUK_LOCAL void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx
DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
act_lex_env);
new_env = duk_require_hobject(ctx, -1);
new_env = duk_get_hobject(ctx, -1);
DUK_ASSERT(new_env != NULL);
DUK_DDD(DUK_DDDPRINT("new_env allocated: %!iO", (duk_heaphdr *) new_env));
@ -749,7 +769,7 @@ DUK_LOCAL void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx
DUK_ASSERT(thr->catchstack[cat_idx].h_varname != NULL);
duk_push_hstring(ctx, thr->catchstack[cat_idx].h_varname);
duk_push_tval(ctx, &thr->heap->lj.value1);
duk_push_tval(ctx, thr->valstack + thr->catchstack[cat_idx].idx_base);
duk_xdef_prop(ctx, -3, DUK_PROPDESC_FLAGS_W); /* writable, not configurable */
act = thr->callstack + thr->callstack_top - 1;
@ -763,28 +783,47 @@ DUK_LOCAL void duk__handle_catch_or_finally(duk_hthread *thr, duk_size_t cat_idx
DUK_DDD(DUK_DDDPRINT("new_env finished: %!iO", (duk_heaphdr *) new_env));
}
if (is_finally) {
DUK_CAT_CLEAR_FINALLY_ENABLED(&thr->catchstack[cat_idx]);
} else {
DUK_CAT_CLEAR_CATCH_ENABLED(&thr->catchstack[cat_idx]);
}
DUK_CAT_CLEAR_CATCH_ENABLED(&thr->catchstack[cat_idx]);
}
DUK_LOCAL void duk__handle_label(duk_hthread *thr, duk_size_t cat_idx) {
DUK_LOCAL void duk__handle_finally(duk_hthread *thr, duk_size_t cat_idx, duk_tval *tv_val_unstable, duk_small_uint_t lj_type) {
duk_activation *act;
/* no callstack changes, no value stack changes */
DUK_ASSERT(thr != NULL);
DUK_ASSERT(tv_val_unstable != NULL);
duk__set_catcher_regs(thr, cat_idx, tv_val_unstable, lj_type);
duk_hthread_catchstack_unwind(thr, cat_idx + 1); /* cat_idx catcher is kept, even for finally */
duk_hthread_callstack_unwind(thr, thr->catchstack[cat_idx].callstack_index + 1);
DUK_ASSERT(thr->callstack_top >= 1);
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL);
DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
duk__reconfig_valstack_ecma_catcher(thr, thr->callstack_top - 1, cat_idx);
DUK_ASSERT(thr->callstack_top >= 1);
act = thr->callstack + thr->callstack_top - 1;
act->curr_pc = thr->catchstack[cat_idx].pc_base + 1; /* +1 = finally */
act = NULL;
DUK_CAT_CLEAR_FINALLY_ENABLED(&thr->catchstack[cat_idx]);
}
DUK_LOCAL void duk__handle_label(duk_hthread *thr, duk_size_t cat_idx, duk_small_uint_t lj_type) {
duk_activation *act;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(thr->callstack_top >= 1);
act = thr->callstack + thr->callstack_top - 1;
DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL);
DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(act)));
/* +0 = break, +1 = continue */
act->curr_pc = thr->catchstack[cat_idx].pc_base + (thr->heap->lj.type == DUK_LJ_TYPE_CONTINUE ? 1 : 0);
act->curr_pc = thr->catchstack[cat_idx].pc_base + (lj_type == DUK_LJ_TYPE_CONTINUE ? 1 : 0);
act = NULL; /* invalidated */
duk_hthread_catchstack_unwind(thr, cat_idx + 1); /* keep label catcher */
@ -792,38 +831,36 @@ DUK_LOCAL void duk__handle_label(duk_hthread *thr, duk_size_t cat_idx) {
/* valstack should not need changes */
#if defined(DUK_USE_ASSERTIONS)
DUK_ASSERT(thr->callstack_top >= 1);
act = thr->callstack + thr->callstack_top - 1;
DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack_bottom) ==
(duk_size_t) ((duk_hcompiledfunction *) DUK_ACT_GET_FUNC(act))->nregs);
#endif
}
/* Note: called for DUK_LJ_TYPE_YIELD and in RETURN handling when a return
* terminates a thread and yields to the resumer.
/* Called for handling both a longjmp() with type DUK_LJ_TYPE_YIELD and
* when a RETURN opcode terminates a thread and yields to the resumer.
*/
DUK_LOCAL void duk__handle_yield(duk_hthread *thr, duk_hthread *resumer, duk_size_t act_idx) {
DUK_LOCAL void duk__handle_yield(duk_hthread *thr, duk_hthread *resumer, duk_size_t act_idx, duk_tval *tv_val_unstable) {
duk_tval tv_tmp;
duk_tval *tv1;
/* This may also be called in RETURN opcode handling; this is OK as long as
* lj.value1 is correct.
*/
DUK_ASSERT(thr != NULL);
DUK_ASSERT(resumer != NULL);
DUK_ASSERT(tv_val_unstable != NULL);
DUK_ASSERT(DUK_ACT_GET_FUNC(resumer->callstack + act_idx) != NULL);
DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(resumer->callstack + act_idx))); /* resume caller must be an ecmascript func */
DUK_DDD(DUK_DDDPRINT("resume idx_retval is %ld", (long) resumer->callstack[act_idx].idx_retval));
tv1 = resumer->valstack + resumer->callstack[act_idx].idx_retval; /* return value from Duktape.Thread.resume() */
DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
DUK_TVAL_SET_TVAL(tv1, tv_val_unstable);
DUK_TVAL_INCREF(thr, tv1);
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
duk_hthread_callstack_unwind(resumer, act_idx + 1); /* unwind to 'resume' caller */
/* no need to unwind catchstack */
duk__reconfig_valstack(resumer, act_idx, 1); /* 1 = have retval */
duk__reconfig_valstack_ecma_return(resumer, act_idx);
/* caller must change active thread, and set thr->resumer to NULL */
}
@ -845,6 +882,7 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
/* 'thr' is the current thread, as no-one resumes except us and we
* switch 'thr' in that case.
*/
DUK_ASSERT(thr == thr->heap->curr_thread);
/*
* (Re)try handling the longjmp.
@ -958,7 +996,7 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
/* no need to unwind catchstack */
duk__reconfig_valstack(resumee, act_idx, 1); /* 1 = have retval */
duk__reconfig_valstack_ecma_return(resumee, act_idx);
resumee->resumer = thr;
resumee->state = DUK_HTHREAD_STATE_RUNNING;
@ -1058,7 +1096,7 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_DD(DUK_DDPRINT("-> yield an error, converted to a throw in the resumer, propagate"));
goto check_longjmp;
} else {
duk__handle_yield(thr, resumer, resumer->callstack_top - 2);
duk__handle_yield(thr, resumer, resumer->callstack_top - 2, &thr->heap->lj.value1);
thr->state = DUK_HTHREAD_STATE_YIELDED;
thr->resumer = NULL;
@ -1076,69 +1114,6 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
break; /* never here */
}
case DUK_LJ_TYPE_BREAK:
case DUK_LJ_TYPE_CONTINUE: {
/*
* Find a matching label catcher or 'finally' catcher in
* the same function.
*
* A label catcher must always exist and will match unless
* a 'finally' captures the break/continue first. It is the
* compiler's responsibility to ensure that labels are used
* correctly.
*/
duk_catcher *cat;
duk_size_t orig_callstack_index;
duk_uint_t lj_label;
cat = thr->catchstack + thr->catchstack_top - 1;
orig_callstack_index = cat->callstack_index;
DUK_ASSERT(DUK_TVAL_IS_NUMBER(&thr->heap->lj.value1));
lj_label = (duk_uint_t) DUK_TVAL_GET_NUMBER(&thr->heap->lj.value1);
DUK_DDD(DUK_DDDPRINT("handling break/continue with label=%ld, callstack index=%ld",
(long) lj_label, (long) cat->callstack_index));
while (cat >= thr->catchstack) {
if (cat->callstack_index != orig_callstack_index) {
break;
}
DUK_DDD(DUK_DDDPRINT("considering catcher %ld: type=%ld label=%ld",
(long) (cat - thr->catchstack),
(long) DUK_CAT_GET_TYPE(cat),
(long) DUK_CAT_GET_LABEL(cat)));
if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF &&
DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
/* finally catches */
duk__handle_catch_or_finally(thr,
cat - thr->catchstack,
1); /* is_finally */
DUK_DD(DUK_DDPRINT("-> break/continue caught by a finally (in the same function), restart execution"));
retval = DUK__LONGJMP_RESTART;
goto wipe_and_return;
}
if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_LABEL &&
(duk_uint_t) DUK_CAT_GET_LABEL(cat) == lj_label) {
/* found label */
duk__handle_label(thr,
cat - thr->catchstack);
DUK_DD(DUK_DDPRINT("-> break/continue caught by a label catcher (in the same function), restart execution"));
retval = DUK__LONGJMP_RESTART;
goto wipe_and_return;
}
cat--;
}
/* should never happen, but be robust */
DUK_D(DUK_DPRINT("break/continue not caught by anything in the current function (should never happen)"));
goto convert_to_internal_error;
}
case DUK_LJ_TYPE_THROW: {
/*
* Three possible outcomes:
@ -1170,12 +1145,12 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
}
if (DUK_CAT_HAS_CATCH_ENABLED(cat)) {
/* try catches */
DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF);
duk__handle_catch_or_finally(thr,
cat - thr->catchstack,
0); /* is_finally */
duk__handle_catch(thr,
cat - thr->catchstack,
&thr->heap->lj.value1,
DUK_LJ_TYPE_THROW);
DUK_DD(DUK_DDPRINT("-> throw caught by a 'catch' clause, restart execution"));
retval = DUK__LONGJMP_RESTART;
@ -1186,9 +1161,10 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF);
DUK_ASSERT(!DUK_CAT_HAS_CATCH_ENABLED(cat));
duk__handle_catch_or_finally(thr,
cat - thr->catchstack,
1); /* is_finally */
duk__handle_finally(thr,
cat - thr->catchstack,
&thr->heap->lj.value1,
DUK_LJ_TYPE_THROW);
DUK_DD(DUK_DDPRINT("-> throw caught by a 'finally' clause, restart execution"));
retval = DUK__LONGJMP_RESTART;
@ -1213,7 +1189,7 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
/* Note: MUST NOT wipe_and_return here, as heap->lj must remain intact */
}
DUK_DD(DUK_DDPRINT("not caught by current thread, yield error to resumer"));
DUK_DD(DUK_DDPRINT("-> throw not caught by current thread, yield error to resumer and recheck longjmp"));
/* not caught by current thread, thread terminates (yield error to resumer);
* note that this may cause a cascade if the resumer terminates with an uncaught
@ -1245,8 +1221,10 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
goto check_longjmp;
}
case DUK_LJ_TYPE_NORMAL: /* pseudotypes */
case DUK_LJ_TYPE_BREAK: /* pseudotypes, not used in actual longjmps */
case DUK_LJ_TYPE_CONTINUE:
case DUK_LJ_TYPE_RETURN:
case DUK_LJ_TYPE_NORMAL:
default: {
/* should never happen, but be robust */
DUK_D(DUK_DPRINT("caught unknown longjmp type %ld, treat as internal error", (long) thr->heap->lj.type));
@ -1275,27 +1253,98 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
convert_to_internal_error:
/* This could also be thrown internally (set the error, goto check_longjmp),
* but it's better for internal errors to bubble outwards.
* but it's better for internal errors to bubble outwards so that we won't
* infinite loop in this catchpoint.
*/
DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR_EXEC_LONGJMP);
DUK_UNREACHABLE();
return retval;
}
/*
* RETURN handling
/* Handle a BREAK/CONTINUE opcode. Avoid using longjmp() for BREAK/CONTINUE
* handling because it has a measurable performance impact in ordinary
* environments and an extreme impact in Emscripten (GH-342).
*/
DUK_LOCAL void duk__handle_break_or_continue(duk_hthread *thr,
duk_uint_t label_id,
duk_small_uint_t lj_type) {
duk_catcher *cat;
duk_size_t orig_callstack_index;
DUK_ASSERT(thr != NULL);
/*
* Find a matching label catcher or 'finally' catcher in
* the same function.
*
* A label catcher must always exist and will match unless
* a 'finally' captures the break/continue first. It is the
* compiler's responsibility to ensure that labels are used
* correctly.
*/
/* Note: thr->catchstack_top may be 0, so that cat < thr->catchstack
* initially. This is OK and intended.
*/
cat = thr->catchstack + thr->catchstack_top - 1;
DUK_ASSERT(thr->callstack_top > 0);
orig_callstack_index = thr->callstack_top - 1;
DUK_DDD(DUK_DDDPRINT("handling break/continue with label=%ld, callstack index=%ld",
(long) label_id, (long) cat->callstack_index));
#define DUK__RETHAND_RESTART 0
#define DUK__RETHAND_FINISHED 1
while (cat >= thr->catchstack) {
if (cat->callstack_index != orig_callstack_index) {
break;
}
DUK_DDD(DUK_DDDPRINT("considering catcher %ld: type=%ld label=%ld",
(long) (cat - thr->catchstack),
(long) DUK_CAT_GET_TYPE(cat),
(long) DUK_CAT_GET_LABEL(cat)));
if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF &&
DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
duk_size_t cat_idx;
duk_tval tv_tmp;
cat_idx = (duk_size_t) (cat - thr->catchstack); /* get before side effects */
#if defined(DUK_USE_FASTINT)
DUK_TVAL_SET_FASTINT_U32(&tv_tmp, (duk_uint32_t) label_id);
#else
DUK_TVAL_SET_NUMBER(&tv_tmp, (duk_double_t) label_id);
#endif
duk__handle_finally(thr, cat_idx, &tv_tmp, lj_type);
DUK_DD(DUK_DDPRINT("-> break/continue caught by 'finally', restart execution"));
return;
}
if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_LABEL &&
(duk_uint_t) DUK_CAT_GET_LABEL(cat) == label_id) {
duk_size_t cat_idx;
cat_idx = (duk_size_t) (cat - thr->catchstack);
duk__handle_label(thr, cat_idx, lj_type);
DUK_DD(DUK_DDPRINT("-> break/continue caught by a label catcher (in the same function), restart execution"));
return;
}
cat--;
}
/* should never happen, but be robust */
DUK_D(DUK_DPRINT("-> break/continue not caught by anything in the current function (should never happen), throw internal error"));
DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_INTERNAL_ERROR);
return;
}
/* Handle a RETURN opcode. Avoid using longjmp() for return handling because
* it has a measurable performance impact in ordinary environments and an extreme
* impact in Emscripten (GH-342). Return value is on value stack top.
*/
DUK_LOCAL DUK_NOINLINE duk_small_uint_t duk__handle_return(duk_hthread *thr,
duk_hthread *entry_thread,
duk_size_t entry_callstack_top) {
DUK_LOCAL duk_small_uint_t duk__handle_return(duk_hthread *thr,
duk_hthread *entry_thread,
duk_size_t entry_callstack_top) {
duk_tval tv_tmp;
duk_tval *tv1;
duk_tval *tv2;
@ -1303,10 +1352,11 @@ DUK_LOCAL DUK_NOINLINE duk_small_uint_t duk__handle_return(duk_hthread *thr,
duk_catcher *cat;
duk_size_t new_cat_top;
duk_size_t orig_callstack_index;
duk_small_uint_t retval = DUK__RETHAND_RESTART;
/* We can directly access value stack here. */
DUK_ASSERT(thr != NULL);
DUK_ASSERT(entry_thread != NULL);
DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);
tv1 = thr->valstack_top - 1;
DUK_TVAL_CHKFAST_INPLACE(tv1); /* fastint downgrade check for return values */
@ -1351,22 +1401,13 @@ DUK_LOCAL DUK_NOINLINE duk_small_uint_t duk__handle_return(duk_hthread *thr,
DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
duk_size_t cat_idx;
/* Handle 'finally' by tweaking longjmp state for now. */
DUK_DDD(DUK_DDDPRINT("finally catches"));
cat_idx = (duk_size_t) (cat - thr->catchstack); /* get before side effects */
thr->heap->lj.type = DUK_LJ_TYPE_RETURN;
DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);
tv2 = thr->valstack_top - 1;
DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, tv2);
DUK_TVAL_INCREF(thr, tv2); /* == thr->heap->lj.value1 */
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
duk__handle_finally(thr, cat_idx, thr->valstack_top - 1, DUK_LJ_TYPE_RETURN);
duk__handle_catch_or_finally(thr, cat_idx, 1 /*is_finally*/);
DUK_ASSERT(retval == DUK__RETHAND_RESTART);
goto wipe_and_return;
DUK_DD(DUK_DDPRINT("-> return caught by 'finally', restart execution"));
return DUK__RETHAND_RESTART;
}
cat--;
}
@ -1378,21 +1419,18 @@ DUK_LOCAL DUK_NOINLINE duk_small_uint_t duk__handle_return(duk_hthread *thr,
DUK_DDD(DUK_DDDPRINT("no catcher in catch stack, return to calling activation / yield"));
/* return to calling activation (if any) */
if (thr == entry_thread &&
thr->callstack_top == entry_callstack_top) {
/* Return to the bytecode executor caller; caller will unwind stacks.
/* Return to the bytecode executor caller which will unwind stacks.
* Return value is already on the stack top: [ ... retval ].
*/
DUK_DDD(DUK_DDDPRINT("-> return propagated up to entry level, exit bytecode executor"));
retval = DUK__RETHAND_FINISHED;
goto wipe_and_return;
return DUK__RETHAND_FINISHED;
}
if (thr->callstack_top >= 2) {
/* there is a caller; it MUST be an Ecmascript caller (otherwise it would
/* There is a caller; it MUST be an Ecmascript caller (otherwise it would
* match entry level check)
*/
@ -1416,25 +1454,16 @@ DUK_LOCAL DUK_NOINLINE duk_small_uint_t duk__handle_return(duk_hthread *thr,
duk_hthread_catchstack_unwind(thr, new_cat_top); /* leave 'cat' as top catcher (also works if catchstack exhausted) */
duk_hthread_callstack_unwind(thr, thr->callstack_top - 1);
duk__reconfig_valstack(thr, thr->callstack_top - 1, 1); /* new top, i.e. callee */
duk__reconfig_valstack_ecma_return(thr, thr->callstack_top - 1);
DUK_DD(DUK_DDPRINT("-> return not intercepted, restart execution in caller"));
DUK_ASSERT(retval == DUK__RETHAND_RESTART);
goto wipe_and_return;
return DUK__RETHAND_RESTART;
}
DUK_DD(DUK_DDPRINT("no calling activation, thread finishes (similar to yield)"));
/* Share yield longjmp handler. */
thr->heap->lj.type = DUK_LJ_TYPE_NORMAL;
DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);
tv2 = thr->valstack_top - 1;
DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, tv2);
DUK_TVAL_INCREF(thr, tv2); /* == &thr->heap->lj.value1 */
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
DUK_ASSERT(thr->resumer != NULL);
DUK_ASSERT(thr->resumer->callstack_top >= 2); /* Ecmascript activation + Duktape.Thread.resume() activation */
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack + thr->resumer->callstack_top - 1) != NULL &&
@ -1448,7 +1477,8 @@ DUK_LOCAL DUK_NOINLINE duk_small_uint_t duk__handle_return(duk_hthread *thr,
resumer = thr->resumer;
duk__handle_yield(thr, resumer, resumer->callstack_top - 2);
DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);
duk__handle_yield(thr, resumer, resumer->callstack_top - 2, thr->valstack_top - 1);
duk_hthread_terminate(thr); /* updates thread state, minimizes its allocations */
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);
@ -1461,23 +1491,7 @@ DUK_LOCAL DUK_NOINLINE duk_small_uint_t duk__handle_return(duk_hthread *thr,
#endif
DUK_DD(DUK_DDPRINT("-> return not caught, thread terminated; handle like yield, restart execution in resumer"));
DUK_ASSERT(retval == DUK__RETHAND_RESTART);
goto wipe_and_return;
wipe_and_return:
/* this is not strictly necessary, but helps debugging */
thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
thr->heap->lj.iserror = 0;
DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value1);
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value2);
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
return retval;
return DUK__RETHAND_RESTART;
}
/*
@ -2053,7 +2067,7 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *exec_thr) {
thr->heap->lj.jmpbuf_ptr = &jmpbuf;
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);
if (DUK_SETJMP(thr->heap->lj.jmpbuf_ptr->jb)) {
if (DUK_SETJMP(thr->heap->lj.jmpbuf_ptr->jb) != 0) {
/*
* Note: any local variables accessed here must have their value
* assigned *before* the setjmp() call, OR they must be declared
@ -4237,12 +4251,15 @@ skip_interrupt:
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv1));
cont_type = (duk_small_uint_t) DUK_TVAL_GET_NUMBER(tv1);
if (cont_type == DUK_LJ_TYPE_NORMAL) {
switch (cont_type) {
case DUK_LJ_TYPE_NORMAL: {
DUK_DDD(DUK_DDDPRINT("ENDFIN: finally part finishing with 'normal' (non-abrupt) completion -> "
"dismantle catcher, resume execution after ENDFIN"));
duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
/* no need to unwind callstack */
} else if (cont_type == DUK_LJ_TYPE_RETURN) {
goto restart_execution;
}
case DUK_LJ_TYPE_RETURN: {
DUK_DDD(DUK_DDDPRINT("ENDFIN: finally part finishing with 'return' complation -> dismantle "
"catcher, handle return, lj.value1=%!T", thr->valstack + cat->idx_base));
@ -4270,22 +4287,50 @@ skip_interrupt:
thr->heap->lj.jmpbuf_ptr = (duk_jmpbuf *) entry_jmpbuf_ptr;
DUK_DDD(DUK_DDDPRINT("exiting executor after ENDFIN and RETURN (pseudo) longjmp type"));
return;
} else {
}
case DUK_LJ_TYPE_BREAK:
case DUK_LJ_TYPE_CONTINUE: {
duk_uint_t label_id;
duk_small_uint_t lj_type;
/* Not necessary to unwind catchstack: break/continue
* handling will do it. The finally flag of 'cat' is
* no longer set. The catch flag may be set, but it's
* not checked by break/continue handling.
*/
#if 0
duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
#endif
tv1 = thr->valstack + cat->idx_base;
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv1));
#if defined(DUK_USE_FASTINT)
DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv1));
label_id = (duk_small_uint_t) DUK_TVAL_GET_FASTINT_U32(tv1);
#else
label_id = (duk_small_uint_t) DUK_TVAL_GET_NUMBER(tv1);
#endif
lj_type = cont_type;
duk__handle_break_or_continue(thr, label_id, lj_type);
goto restart_execution;
}
default: {
DUK_DDD(DUK_DDDPRINT("ENDFIN: finally part finishing with abrupt completion, lj_type=%ld -> "
"dismantle catcher, re-throw error",
(long) cont_type));
duk_push_tval(ctx, thr->valstack + cat->idx_base);
/* XXX: assert lj type valid */
duk_err_setup_heap_ljstate(thr, (duk_small_int_t) cont_type);
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* always in executor */
duk_err_longjmp(thr);
DUK_UNREACHABLE();
}
}
/* continue execution after ENDFIN */
/* Must restart in all cases because we NULLed thr->ptr_curr_pc. */
DUK_UNREACHABLE();
break;
}
@ -4358,43 +4403,23 @@ skip_interrupt:
}
case DUK_EXTRAOP_BREAK: {
duk_context *ctx = (duk_context *) thr;
duk_uint_fast_t bc = DUK_DEC_BC(ins);
/* always the "slow break" variant (longjmp'ing); a "fast break" is
* simply an DUK_OP_JUMP.
*/
DUK_DDD(DUK_DDDPRINT("BREAK: %ld", (long) bc));
duk_push_uint(ctx, (duk_uint_t) bc);
duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_BREAK);
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* always in executor */
DUK__SYNC_AND_NULL_CURR_PC();
duk_err_longjmp(thr);
DUK_UNREACHABLE();
break;
duk__handle_break_or_continue(thr, (duk_uint_t) bc, DUK_LJ_TYPE_BREAK);
goto restart_execution;
}
case DUK_EXTRAOP_CONTINUE: {
duk_context *ctx = (duk_context *) thr;
duk_uint_fast_t bc = DUK_DEC_BC(ins);
/* always the "slow continue" variant (longjmp'ing); a "fast continue" is
* simply an DUK_OP_JUMP.
*/
DUK_DDD(DUK_DDDPRINT("CONTINUE: %ld", (long) bc));
duk_push_uint(ctx, (duk_uint_t) bc);
duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_CONTINUE);
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* always in executor */
DUK__SYNC_AND_NULL_CURR_PC();
duk_err_longjmp(thr);
DUK_UNREACHABLE();
break;
duk__handle_break_or_continue(thr, (duk_uint_t) bc, DUK_LJ_TYPE_CONTINUE);
goto restart_execution;
}
case DUK_EXTRAOP_BNOT: {

Loading…
Cancel
Save