Browse Source

Merge pull request #342 from svaarala/perf-fast-return-support

Add minimal fast return support
pull/349/head
Sami Vaarala 9 years ago
parent
commit
9165478b97
  1. 10
      src/duk_js_compiler.c
  2. 59
      src/duk_js_executor.c
  3. 2
      tests/api/test-dump-load-basic.c
  4. 149
      tests/ecmascript/test-dev-fast-return-cases.js

10
src/duk_js_compiler.c

@ -5666,20 +5666,12 @@ DUK_LOCAL void duk__parse_return_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *re
ret_flags = DUK_BC_RETURN_FLAG_HAVE_RETVAL;
}
/* XXX: For now, "fast returns" are disabled. The compiler doesn't track
* label site depth so when it emits a fast return, it doesn't know whether
* label sites exist or not. Label sites are emitted for e.g. for loops,
* so it's probably quite relevant to handle them in the executor's fast
* return handler.
*/
#if 0
if (comp_ctx->curr_func.catch_depth == 0) {
DUK_DDD(DUK_DDDPRINT("fast return allowed -> use fast return"));
ret_flags |= DUK_BC_RETURN_FLAG_FAST;
} else {
DUK_DDD(DUK_DDDPRINT("fast return not allowed -> use slow return"));
}
#endif
duk__emit_a_b(comp_ctx,
DUK_OP_RETURN | DUK__EMIT_FLAG_NO_SHUFFLE_A,
@ -7151,6 +7143,8 @@ DUK_LOCAL void duk__parse_func_body(duk_compiler_ctx *comp_ctx, duk_bool_t expec
* detected; even if the previous instruction is an unconditional jump,
* there may be a previous jump which jumps to current PC (which is the
* case for iteration and conditional statements, for instance).
*
* A final return is always a fast return: there can be no active catchers.
*/
/* XXX: request a "last statement is terminal" from duk__parse_stmt() and duk__parse_stmts();

59
src/duk_js_executor.c

@ -1417,25 +1417,21 @@ duk_small_uint_t duk__handle_longjmp(duk_hthread *thr,
return retval;
}
/* XXX: Disabled for 1.0 release. This needs to handle unwinding for label
* sites (which are created for explicit labels but also for control statements
* like for-loops). At that point it's quite close to the "slow return" handler
* except for longjmp(). Perhaps all returns could initially be handled as fast
* returns and only converted to longjmp()s when basic handling won't do?
*/
#if 0
/* Try a fast return. Return false if fails, so that a slow return can be done
* instead.
*/
DUK_LOCAL
duk_bool_t duk__handle_fast_return(duk_hthread *thr,
duk_tval *tv_retval,
duk_hthread *entry_thread,
duk_size_t entry_callstack_top) {
DUK_LOCAL DUK_NOINLINE duk_bool_t duk__handle_fast_return(duk_hthread *thr,
duk_tval *tv_retval,
duk_hthread *entry_thread,
duk_size_t entry_callstack_top) {
duk_tval tv_tmp;
duk_tval *tv1;
duk_catcher *cat;
duk_size_t orig_callstack_index;
/* retval == NULL indicates 'undefined' return value */
/* tv_retval == NULL indicates 'undefined' return value */
/* XXX: assert: no finally/TCF catchers */
if (thr == entry_thread && thr->callstack_top == entry_callstack_top) {
DUK_DDD(DUK_DDDPRINT("reject fast return: return would exit bytecode executor to caller"));
@ -1446,6 +1442,22 @@ duk_bool_t duk__handle_fast_return(duk_hthread *thr,
return 0;
}
DUK_ASSERT(thr->ptr_curr_pc == NULL); /* caller ensures */
/* XXX: it'd be more efficient if the returning 'act' indicated the
* proper catchstack level it is using
*/
/* Catchstack */
cat = thr->catchstack + thr->catchstack_top - 1; /* may be < thr->catchstack initially */
DUK_ASSERT(thr->callstack_top > 0); /* ensures callstack_top - 1 >= 0 */
orig_callstack_index = thr->callstack_top - 1;
while (cat >= thr->catchstack) {
if (cat->callstack_index != orig_callstack_index) {
break;
}
cat--;
}
/* There is a caller, and it must be an Ecmascript caller (otherwise
* it would have matched the entry level check).
*/
@ -1456,6 +1468,7 @@ duk_bool_t duk__handle_fast_return(duk_hthread *thr,
DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
if (tv_retval) {
DUK_TVAL_SET_TVAL(tv1, tv_retval);
DUK_TVAL_CHKFAST_INPLACE(tv1);
DUK_TVAL_INCREF(thr, tv1);
} else {
DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
@ -1463,17 +1476,13 @@ duk_bool_t duk__handle_fast_return(duk_hthread *thr,
}
DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
/* No catchstack to unwind. */
#if 0
duk_hthread_catchstack_unwind(thr, (cat - thr->catchstack) + 1); /* leave 'cat' as top catcher (also works if catchstack exhausted) */
#endif
duk_hthread_callstack_unwind(thr, thr->callstack_top - 1);
duk__reconfig_valstack(thr, thr->callstack_top - 1, 1); /* new top, i.e. callee */
DUK_DDD(DUK_DDDPRINT("fast return accepted"));
return 1;
}
#endif
/*
* Executor interrupt handling
@ -3237,15 +3246,17 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *exec_thr) {
* C -> currently unused
*/
/* Sync and NULL early, so that both fast/slow return code paths
* share this. Syncing is not that important for RETURN, but
* NULLing is.
*/
DUK__SYNC_AND_NULL_CURR_PC();
/* A fast return avoids full longjmp handling for a set of
* scenarios which hopefully represents the common cases.
* The compiler is responsible for emitting fast returns
* only when they are safe. Currently this means that there
* is nothing on the catch stack (not even label catchers).
* The speed advantage of fast returns (avoiding longjmp) is
* not very high, around 10-15%.
* only when they are safe.
*/
#if 0 /* XXX: Disabled for 1.0 release */
if (a & DUK_BC_RETURN_FLAG_FAST) {
DUK_DDD(DUK_DDDPRINT("FASTRETURN attempt a=%ld b=%ld", (long) a, (long) b));
@ -3254,11 +3265,9 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *exec_thr) {
entry_thread,
entry_callstack_top)) {
DUK_DDD(DUK_DDDPRINT("FASTRETURN success a=%ld b=%ld", (long) a, (long) b));
DUK__SYNC_CURR_PC();
goto restart_execution;
}
}
#endif
/* No fast return, slow path. */
DUK_DDD(DUK_DDDPRINT("SLOWRETURN a=%ld b=%ld", (long) a, (long) b));
@ -3282,7 +3291,7 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *exec_thr) {
duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_RETURN);
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* in bytecode executor, should always be set */
DUK__SYNC_AND_NULL_CURR_PC();
DUK_ASSERT(thr->ptr_curr_pc == NULL); /* done above */
duk_err_longjmp(thr);
DUK_UNREACHABLE();
break;

2
tests/api/test-dump-load-basic.c

@ -40,7 +40,7 @@
/*===
*** test_basic (duk_safe_call)
dump result type: 7
ff000000000e0000000300000001000900000000000100000001300004c0000000f300400052000040c200000114000101400001414c800041c3800082030101403000014100000081420180403000004000000000ef00000000057072696e74000000000568656c6c6f0140091eb851eb851f000000030000000000000000000300020000000100000001300604400080009a000080af0000006f000000020000000561646465720000000f66616b6546696c656e616d652e6a730000000d03000000010000000c000000000000000178000000000000000179000000010000000000000001780000000179000000000000000000000006676c6f62616c0000000f66616b6546696c656e616d652e6a730000000e0e000000010000000c00000000000000000000000000
ff000000000e0000000300000001000900000000000100000001300004c0000000f300400052000040c200000114000101400001414c800041c3800082030101403000014100000081420180403000004000000000ef00000000057072696e74000000000568656c6c6f0140091eb851eb851f000000030000000000000000000300020000000100000001300604400080009a000080ef0000006f000000020000000561646465720000000f66616b6546696c656e616d652e6a730000000d03000000010000000c000000000000000178000000000000000179000000010000000000000001780000000179000000000000000000000006676c6f62616c0000000f66616b6546696c656e616d652e6a730000000e0e000000010000000c00000000000000000000000000
load result type: 6
hello 3 3.14
call result type: 1

149
tests/ecmascript/test-dev-fast-return-cases.js

@ -1,43 +1,176 @@
/*
* Exercise various code paths related to fast return handling.
*
* Duktape executor supports two kinds of return:
*
* - A slow return is implemented using longjmp() and handles all cases
* like label catchers, try-catch-finally blocks, etc.
*
* - A fast return avoids the longjmp() but is limited to common cases
* only.
*
* Longjmp has a moderate performance impact on normal platforms. However
* it has a much larger relative impact when Duktape is compiled with
* Emscripten.
*/
/* XXX: incomplete, finish when fast return implemented. */
/*===
testReturn1
returned 123
testReturn2
returned undefined
testReturn3
returned undefined
testForLoop1
returned 123
testTryCatch1
returned 123
testTryCatch2
returned 123
testTryFinally1
finally intercepted, return continues
returned 123
testTryFinally2
finally intercepted, replace return value
returned 321
testTryFinally3
returned 123
testTryFinally4
Error: canceled return
still here
returned 432
===*/
/* Basic case, call to test1() is from Ecmascript, so a fast return is
* allowed.
*/
/* Basic case: fast return allowed. */
function testReturn1() {
return 123;
}
/* Basic case, implicit return value (undefined). */
/* Basic case, implicit return value (undefined). Fast return allowed. */
function testReturn2() {
return;
}
/* Basic case, implicit return value with no explicit return statement. */
/* Basic case, implicit return value with no explicit return statement.
* Fast return allowed.
*/
function testReturn3() {
}
/* Return from inside a label site (created by for-loop) */
/* Return from inside a label site (created by for-loop). Fast return
* is allowed for label catchers.
*/
function testForLoop1() {
for (;;) {
return 123;
}
}
/* Return from inside the try block of a try-catch is currently not a
* fast return.
* XXX: could allow if there's no finally clause?
*/
function testTryCatch1() {
try {
return 123;
} catch (e) {
}
}
/* Return from inside the catch block of a try-catch is currently not a
* fast return.
* XXX: could allow if there's no finally clause?
*/
function testTryCatch2() {
try {
throw new Error('test');
} catch (e) {
return 123;
}
}
/* Return from inside the try block of a try-finally is not a fast
* return because the finally block intercepts the return (and can
* even cancel it).
*/
function testTryFinally1() {
try {
return 123;
} finally {
print('finally intercepted, return continues');
}
}
function testTryFinally2() {
try {
return 123;
} finally {
print('finally intercepted, replace return value');
return 321;
}
}
/* Return from inside the finally block of a try-finally is not a fast
* return.
* XXX: it could be, because there's no longer a chance of anyone
* catching the return.
*/
function testTryFinally3() {
try {
;
} finally {
return 123;
}
}
function testTryFinally4() {
// Here 'finally' cancels the return and replaces it with a thrown error.
// The outer try-catch catches and neutralizes that, so that execution
// resumes within the function normally.
try {
try {
return 123;
} finally {
throw new Error('canceled return');
}
} catch (e) {
print(e);
}
print('still here');
return 432;
}
try {
print('testReturn1');
print('returned', testReturn1());
print('testReturn2');
print('returned', testReturn2());
print('testReturn3');
print('returned', testReturn3());
print('testForLoop1');
print('returned', testForLoop1());
print('testTryCatch1');
print('returned', testTryCatch1());
print('testTryCatch2');
print('returned', testTryCatch2());
print('testTryFinally1');
print('returned', testTryFinally1());
print('testTryFinally2');
print('returned', testTryFinally2());
print('testTryFinally3');
print('returned', testTryFinally3());
print('testTryFinally4');
print('returned', testTryFinally4());
} catch (e) {
print(e);
}

Loading…
Cancel
Save