blob: 813c8a1e3daf202f80e7b6b988d8d6c260d71dad [file] [log] [blame]
/*
* Thread builtins
*/
#include "duk_internal.h"
/*
* Constructor
*/
DUK_INTERNAL duk_ret_t duk_bi_thread_constructor(duk_context *ctx) {
duk_hthread *new_thr;
duk_hobject *func;
/* XXX: need a duk_require_func_or_lfunc_coerce() */
if (!duk_is_callable(ctx, 0)) {
return DUK_RET_TYPE_ERROR;
}
func = duk_require_hobject_or_lfunc_coerce(ctx, 0);
DUK_ASSERT(func != NULL);
duk_push_thread(ctx);
new_thr = (duk_hthread *) duk_get_hobject(ctx, -1);
DUK_ASSERT(new_thr != NULL);
new_thr->state = DUK_HTHREAD_STATE_INACTIVE;
/* push initial function call to new thread stack; this is
* picked up by resume().
*/
duk_push_hobject((duk_context *) new_thr, func);
return 1; /* return thread */
}
/*
* Resume a thread.
*
* The thread must be in resumable state, either (a) new thread which hasn't
* yet started, or (b) a thread which has previously yielded. This method
* must be called from an Ecmascript function.
*
* Args:
* - thread
* - value
* - isError (defaults to false)
*
* Note: yield and resume handling is currently asymmetric.
*/
DUK_INTERNAL duk_ret_t duk_bi_thread_resume(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_hthread *thr_resume;
duk_tval *tv;
duk_hobject *func;
duk_hobject *caller_func;
duk_small_int_t is_error;
DUK_DDD(DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T",
(duk_tval *) duk_get_tval(ctx, 0),
(duk_tval *) duk_get_tval(ctx, 1),
(duk_tval *) duk_get_tval(ctx, 2)));
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
DUK_ASSERT(thr->heap->curr_thread == thr);
thr_resume = duk_require_hthread(ctx, 0);
is_error = (duk_small_int_t) duk_to_boolean(ctx, 2);
duk_set_top(ctx, 2);
/* [ thread value ] */
/*
* Thread state and calling context checks
*/
if (thr->callstack_top < 2) {
DUK_DD(DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"));
goto state_error;
}
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL); /* us */
DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL); /* caller */
caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2);
if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func)) {
DUK_DD(DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"));
goto state_error;
}
/* Note: there is no requirement that: 'thr->callstack_preventcount == 1'
* like for yield.
*/
if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE &&
thr_resume->state != DUK_HTHREAD_STATE_YIELDED) {
DUK_DD(DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED"));
goto state_error;
}
DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE ||
thr_resume->state == DUK_HTHREAD_STATE_YIELDED);
/* Further state-dependent pre-checks */
if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) {
/* no pre-checks now, assume a previous yield() has left things in
* tip-top shape (longjmp handler will assert for these).
*/
} else {
DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE);
if ((thr_resume->callstack_top != 0) ||
(thr_resume->valstack_top - thr_resume->valstack != 1)) {
goto state_invalid_initial;
}
tv = &thr_resume->valstack_top[-1];
DUK_ASSERT(tv >= thr_resume->valstack && tv < thr_resume->valstack_top);
if (!DUK_TVAL_IS_OBJECT(tv)) {
goto state_invalid_initial;
}
func = DUK_TVAL_GET_OBJECT(tv);
DUK_ASSERT(func != NULL);
if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
/* Note: cannot be a bound function either right now,
* this would be easy to relax though.
*/
goto state_invalid_initial;
}
}
/*
* The error object has been augmented with a traceback and other
* info from its creation point -- usually another thread. The
* error handler is called here right before throwing, but it also
* runs in the resumer's thread. It might be nice to get a traceback
* from the resumee but this is not the case now.
*/
#if defined(DUK_USE_AUGMENT_ERROR_THROW)
if (is_error) {
DUK_ASSERT_TOP(ctx, 2); /* value (error) is at stack top */
duk_err_augment_error_throw(thr); /* in resumer's context */
}
#endif
#ifdef DUK_USE_DEBUG
if (is_error) {
DUK_DDD(DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T",
(duk_tval *) duk_get_tval(ctx, 0),
(duk_tval *) duk_get_tval(ctx, 1)));
} else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) {
DUK_DDD(DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T",
(duk_tval *) duk_get_tval(ctx, 0),
(duk_tval *) duk_get_tval(ctx, 1)));
} else {
DUK_DDD(DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T",
(duk_tval *) duk_get_tval(ctx, 0),
(duk_tval *) duk_get_tval(ctx, 1)));
}
#endif
thr->heap->lj.type = DUK_LJ_TYPE_RESUME;
/* lj value2: thread */
DUK_ASSERT(thr->valstack_bottom < thr->valstack_top);
DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value2, &thr->valstack_bottom[0]); /* side effects */
/* lj value1: value */
DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top);
DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value1, &thr->valstack_bottom[1]); /* side effects */
DUK_TVAL_CHKFAST_INPLACE(&thr->heap->lj.value1);
thr->heap->lj.iserror = is_error;
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */
duk_err_longjmp(thr); /* execution resumes in bytecode executor */
return 0; /* never here */
state_invalid_initial:
DUK_ERROR_TYPE(thr, "invalid initial thread state/stack");
return 0; /* never here */
state_error:
DUK_ERROR_TYPE(thr, "invalid state");
return 0; /* never here */
}
/*
* Yield the current thread.
*
* The thread must be in yieldable state: it must have a resumer, and there
* must not be any yield-preventing calls (native calls and constructor calls,
* currently) in the thread's call stack (otherwise a resume would not be
* possible later). This method must be called from an Ecmascript function.
*
* Args:
* - value
* - isError (defaults to false)
*
* Note: yield and resume handling is currently asymmetric.
*/
DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *caller_func;
duk_small_int_t is_error;
DUK_DDD(DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T",
(duk_tval *) duk_get_tval(ctx, 0),
(duk_tval *) duk_get_tval(ctx, 1)));
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
DUK_ASSERT(thr->heap->curr_thread == thr);
is_error = (duk_small_int_t) duk_to_boolean(ctx, 1);
duk_set_top(ctx, 1);
/* [ value ] */
/*
* Thread state and calling context checks
*/
if (!thr->resumer) {
DUK_DD(DUK_DDPRINT("yield state invalid: current thread must have a resumer"));
goto state_error;
}
DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);
if (thr->callstack_top < 2) {
DUK_DD(DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)"));
goto state_error;
}
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL); /* us */
DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1)));
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL); /* caller */
caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2);
if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func)) {
DUK_DD(DUK_DDPRINT("yield state invalid: caller must be Ecmascript code"));
goto state_error;
}
DUK_ASSERT(thr->callstack_preventcount >= 1); /* should never be zero, because we (Duktape.Thread.yield) are on the stack */
if (thr->callstack_preventcount != 1) {
/* Note: the only yield-preventing call is Duktape.Thread.yield(), hence check for 1, not 0 */
DUK_DD(DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack (preventcount is %ld)",
(long) thr->callstack_preventcount));
goto state_error;
}
/*
* The error object has been augmented with a traceback and other
* info from its creation point -- usually the current thread.
* The error handler, however, is called right before throwing
* and runs in the yielder's thread.
*/
#if defined(DUK_USE_AUGMENT_ERROR_THROW)
if (is_error) {
DUK_ASSERT_TOP(ctx, 1); /* value (error) is at stack top */
duk_err_augment_error_throw(thr); /* in yielder's context */
}
#endif
#ifdef DUK_USE_DEBUG
if (is_error) {
DUK_DDD(DUK_DDDPRINT("YIELD ERROR: value=%!T",
(duk_tval *) duk_get_tval(ctx, 0)));
} else {
DUK_DDD(DUK_DDDPRINT("YIELD NORMAL: value=%!T",
(duk_tval *) duk_get_tval(ctx, 0)));
}
#endif
/*
* Process yield
*
* After longjmp(), processing continues in bytecode executor longjmp
* handler, which will e.g. update thr->resumer to NULL.
*/
thr->heap->lj.type = DUK_LJ_TYPE_YIELD;
/* lj value1: value */
DUK_ASSERT(thr->valstack_bottom < thr->valstack_top);
DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value1, &thr->valstack_bottom[0]); /* side effects */
DUK_TVAL_CHKFAST_INPLACE(&thr->heap->lj.value1);
thr->heap->lj.iserror = is_error;
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */
duk_err_longjmp(thr); /* execution resumes in bytecode executor */
return 0; /* never here */
state_error:
DUK_ERROR_TYPE(thr, "invalid state");
return 0; /* never here */
}
DUK_INTERNAL duk_ret_t duk_bi_thread_current(duk_context *ctx) {
duk_push_current_thread(ctx);
return 1;
}