327 lines
11 KiB
C
327 lines
11 KiB
C
/*
|
|
* Thread builtins
|
|
*/
|
|
|
|
#include "third_party/duktape/duk_internal.h"
|
|
|
|
/*
|
|
* Constructor
|
|
*/
|
|
|
|
#if defined(DUK_USE_COROUTINE_SUPPORT)
|
|
DUK_INTERNAL duk_ret_t duk_bi_thread_constructor(duk_hthread *thr) {
|
|
duk_hthread *new_thr;
|
|
duk_hobject *func;
|
|
|
|
/* Check that the argument is callable; this is not 100% because we
|
|
* don't allow native functions to be a thread's initial function.
|
|
* Resume will reject such functions in any case.
|
|
*/
|
|
/* XXX: need a duk_require_func_promote_lfunc() */
|
|
func = duk_require_hobject_promote_lfunc(thr, 0);
|
|
DUK_ASSERT(func != NULL);
|
|
duk_require_callable(thr, 0);
|
|
|
|
duk_push_thread(thr);
|
|
new_thr = (duk_hthread *) duk_known_hobject(thr, -1);
|
|
new_thr->state = DUK_HTHREAD_STATE_INACTIVE;
|
|
|
|
/* push initial function call to new thread stack; this is
|
|
* picked up by resume().
|
|
*/
|
|
duk_push_hobject(new_thr, func);
|
|
|
|
return 1; /* return thread */
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#if defined(DUK_USE_COROUTINE_SUPPORT)
|
|
DUK_INTERNAL duk_ret_t duk_bi_thread_resume(duk_hthread *ctx) {
|
|
duk_hthread *thr = (duk_hthread *) ctx;
|
|
duk_hthread *thr_resume;
|
|
duk_hobject *caller_func;
|
|
duk_small_uint_t is_error;
|
|
|
|
DUK_DDD(DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T",
|
|
(duk_tval *) duk_get_tval(thr, 0),
|
|
(duk_tval *) duk_get_tval(thr, 1),
|
|
(duk_tval *) duk_get_tval(thr, 2)));
|
|
|
|
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
|
|
DUK_ASSERT(thr->heap->curr_thread == thr);
|
|
|
|
thr_resume = duk_require_hthread(thr, 0);
|
|
DUK_ASSERT(duk_get_top(thr) == 3);
|
|
is_error = (duk_small_uint_t) duk_to_boolean_top_pop(thr);
|
|
DUK_ASSERT(duk_get_top(thr) == 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(thr->callstack_curr != NULL);
|
|
DUK_ASSERT(thr->callstack_curr->parent != NULL);
|
|
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL); /* us */
|
|
DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
|
|
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr->parent) != NULL); /* caller */
|
|
|
|
caller_func = DUK_ACT_GET_FUNC(thr->callstack_curr->parent);
|
|
if (!DUK_HOBJECT_IS_COMPFUNC(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_hobject *h_fun;
|
|
|
|
DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE);
|
|
|
|
/* The initial function must be an ECMAScript function (but
|
|
* can be bound). We must make sure of that before we longjmp
|
|
* because an error in the RESUME handler call processing will
|
|
* not be handled very cleanly.
|
|
*/
|
|
if ((thr_resume->callstack_top != 0) ||
|
|
(thr_resume->valstack_top - thr_resume->valstack != 1)) {
|
|
goto state_error;
|
|
}
|
|
|
|
duk_push_tval(thr, DUK_GET_TVAL_NEGIDX(thr_resume, -1));
|
|
duk_resolve_nonbound_function(thr);
|
|
h_fun = duk_require_hobject(thr, -1); /* reject lightfuncs on purpose */
|
|
if (!DUK_HOBJECT_IS_CALLABLE(h_fun) || !DUK_HOBJECT_IS_COMPFUNC(h_fun)) {
|
|
goto state_error;
|
|
}
|
|
duk_pop(thr);
|
|
}
|
|
|
|
#if 0
|
|
/* This check would prevent a heap destruction time finalizer from
|
|
* launching a coroutine, which would ensure that during finalization
|
|
* 'thr' would always equal heap_thread. Normal runtime finalizers
|
|
* run with ms_running == 0, i.e. outside mark-and-sweep. See GH-2030.
|
|
*/
|
|
if (thr->heap->ms_running) {
|
|
DUK_D(DUK_DPRINT("refuse Duktape.Thread.resume() when ms_running != 0"));
|
|
goto state_error;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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(thr, 2); /* value (error) is at stack top */
|
|
duk_err_augment_error_throw(thr); /* in resumer's context */
|
|
}
|
|
#endif
|
|
|
|
#if defined(DUK_USE_DEBUG)
|
|
if (is_error) {
|
|
DUK_DDD(DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T",
|
|
(duk_tval *) duk_get_tval(thr, 0),
|
|
(duk_tval *) duk_get_tval(thr, 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(thr, 0),
|
|
(duk_tval *) duk_get_tval(thr, 1)));
|
|
} else {
|
|
DUK_DDD(DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T",
|
|
(duk_tval *) duk_get_tval(thr, 0),
|
|
(duk_tval *) duk_get_tval(thr, 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_SLOW(&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 */
|
|
DUK_UNREACHABLE();
|
|
/* Never here, fall through to error (from compiler point of view). */
|
|
|
|
state_error:
|
|
DUK_DCERROR_TYPE_INVALID_STATE(thr);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#if defined(DUK_USE_COROUTINE_SUPPORT)
|
|
DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_hthread *thr) {
|
|
duk_hobject *caller_func;
|
|
duk_small_uint_t is_error;
|
|
|
|
DUK_DDD(DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T",
|
|
(duk_tval *) duk_get_tval(thr, 0),
|
|
(duk_tval *) duk_get_tval(thr, 1)));
|
|
|
|
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
|
|
DUK_ASSERT(thr->heap->curr_thread == thr);
|
|
|
|
DUK_ASSERT(duk_get_top(thr) == 2);
|
|
is_error = (duk_small_uint_t) duk_to_boolean_top_pop(thr);
|
|
DUK_ASSERT(duk_get_top(thr) == 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(thr->callstack_curr != NULL);
|
|
DUK_ASSERT(thr->callstack_curr->parent != NULL);
|
|
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL); /* us */
|
|
DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
|
|
DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr->parent) != NULL); /* caller */
|
|
|
|
caller_func = DUK_ACT_GET_FUNC(thr->callstack_curr->parent);
|
|
if (!DUK_HOBJECT_IS_COMPFUNC(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(thr, 1); /* value (error) is at stack top */
|
|
duk_err_augment_error_throw(thr); /* in yielder's context */
|
|
}
|
|
#endif
|
|
|
|
#if defined(DUK_USE_DEBUG)
|
|
if (is_error) {
|
|
DUK_DDD(DUK_DDDPRINT("YIELD ERROR: value=%!T",
|
|
(duk_tval *) duk_get_tval(thr, 0)));
|
|
} else {
|
|
DUK_DDD(DUK_DDDPRINT("YIELD NORMAL: value=%!T",
|
|
(duk_tval *) duk_get_tval(thr, 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_SLOW(&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 */
|
|
DUK_UNREACHABLE();
|
|
/* Never here, fall through to error (from compiler point of view). */
|
|
|
|
state_error:
|
|
DUK_DCERROR_TYPE_INVALID_STATE(thr);
|
|
}
|
|
#endif
|
|
|
|
#if defined(DUK_USE_COROUTINE_SUPPORT)
|
|
DUK_INTERNAL duk_ret_t duk_bi_thread_current(duk_hthread *thr) {
|
|
duk_push_current_thread(thr);
|
|
return 1;
|
|
}
|
|
#endif
|