From e5e172726a799e1e486820c2cbe6fb93f688788d Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Mon, 14 Sep 2015 21:44:13 +0300 Subject: [PATCH] Handle BREAK and CONTINUE opcodes without longjmp Also RETURN and other control flow cleanups. DUK_SETJMP() return value consistency. --- src/duk_heap.h | 12 +- src/duk_js_executor.c | 527 ++++++++++++++++++++++-------------------- 2 files changed, 282 insertions(+), 257 deletions(-) diff --git a/src/duk_heap.h b/src/duk_heap.h index c530093b..c0d766a9 100644 --- a/src/duk_heap.h +++ b/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 diff --git a/src/duk_js_executor.c b/src/duk_js_executor.c index 57517bbc..110c5b12 100644 --- a/src/duk_js_executor.c +++ b/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: {