5 #include "duk_internal.h"
11 DUK_INTERNAL duk_ret_t
duk_bi_thread_constructor(duk_context
*ctx
) {
15 /* XXX: need a duk_require_func_or_lfunc_coerce() */
16 if (!duk_is_callable(ctx
, 0)) {
17 return DUK_RET_TYPE_ERROR
;
19 func
= duk_require_hobject_or_lfunc_coerce(ctx
, 0);
20 DUK_ASSERT(func
!= NULL
);
23 new_thr
= (duk_hthread
*) duk_get_hobject(ctx
, -1);
24 DUK_ASSERT(new_thr
!= NULL
);
25 new_thr
->state
= DUK_HTHREAD_STATE_INACTIVE
;
27 /* push initial function call to new thread stack; this is
28 * picked up by resume().
30 duk_push_hobject((duk_context
*) new_thr
, func
);
32 return 1; /* return thread */
38 * The thread must be in resumable state, either (a) new thread which hasn't
39 * yet started, or (b) a thread which has previously yielded. This method
40 * must be called from an Ecmascript function.
45 * - isError (defaults to false)
47 * Note: yield and resume handling is currently asymmetric.
50 DUK_INTERNAL duk_ret_t
duk_bi_thread_resume(duk_context
*ctx
) {
51 duk_hthread
*thr
= (duk_hthread
*) ctx
;
52 duk_hthread
*thr_resume
;
55 duk_hobject
*caller_func
;
56 duk_small_int_t is_error
;
58 DUK_DDD(DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T",
59 (duk_tval
*) duk_get_tval(ctx
, 0),
60 (duk_tval
*) duk_get_tval(ctx
, 1),
61 (duk_tval
*) duk_get_tval(ctx
, 2)));
63 DUK_ASSERT(thr
->state
== DUK_HTHREAD_STATE_RUNNING
);
64 DUK_ASSERT(thr
->heap
->curr_thread
== thr
);
66 thr_resume
= duk_require_hthread(ctx
, 0);
67 is_error
= (duk_small_int_t
) duk_to_boolean(ctx
, 2);
70 /* [ thread value ] */
73 * Thread state and calling context checks
76 if (thr
->callstack_top
< 2) {
77 DUK_DD(DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"));
80 DUK_ASSERT(DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 1) != NULL
); /* us */
81 DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 1)));
82 DUK_ASSERT(DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 2) != NULL
); /* caller */
84 caller_func
= DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 2);
85 if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func
)) {
86 DUK_DD(DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"));
90 /* Note: there is no requirement that: 'thr->callstack_preventcount == 1'
94 if (thr_resume
->state
!= DUK_HTHREAD_STATE_INACTIVE
&&
95 thr_resume
->state
!= DUK_HTHREAD_STATE_YIELDED
) {
96 DUK_DD(DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED"));
100 DUK_ASSERT(thr_resume
->state
== DUK_HTHREAD_STATE_INACTIVE
||
101 thr_resume
->state
== DUK_HTHREAD_STATE_YIELDED
);
103 /* Further state-dependent pre-checks */
105 if (thr_resume
->state
== DUK_HTHREAD_STATE_YIELDED
) {
106 /* no pre-checks now, assume a previous yield() has left things in
107 * tip-top shape (longjmp handler will assert for these).
110 DUK_ASSERT(thr_resume
->state
== DUK_HTHREAD_STATE_INACTIVE
);
112 if ((thr_resume
->callstack_top
!= 0) ||
113 (thr_resume
->valstack_top
- thr_resume
->valstack
!= 1)) {
114 goto state_invalid_initial
;
116 tv
= &thr_resume
->valstack_top
[-1];
117 DUK_ASSERT(tv
>= thr_resume
->valstack
&& tv
< thr_resume
->valstack_top
);
118 if (!DUK_TVAL_IS_OBJECT(tv
)) {
119 goto state_invalid_initial
;
121 func
= DUK_TVAL_GET_OBJECT(tv
);
122 DUK_ASSERT(func
!= NULL
);
123 if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func
)) {
124 /* Note: cannot be a bound function either right now,
125 * this would be easy to relax though.
127 goto state_invalid_initial
;
133 * The error object has been augmented with a traceback and other
134 * info from its creation point -- usually another thread. The
135 * error handler is called here right before throwing, but it also
136 * runs in the resumer's thread. It might be nice to get a traceback
137 * from the resumee but this is not the case now.
140 #if defined(DUK_USE_AUGMENT_ERROR_THROW)
142 DUK_ASSERT_TOP(ctx
, 2); /* value (error) is at stack top */
143 duk_err_augment_error_throw(thr
); /* in resumer's context */
149 DUK_DDD(DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T",
150 (duk_tval
*) duk_get_tval(ctx
, 0),
151 (duk_tval
*) duk_get_tval(ctx
, 1)));
152 } else if (thr_resume
->state
== DUK_HTHREAD_STATE_YIELDED
) {
153 DUK_DDD(DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T",
154 (duk_tval
*) duk_get_tval(ctx
, 0),
155 (duk_tval
*) duk_get_tval(ctx
, 1)));
157 DUK_DDD(DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T",
158 (duk_tval
*) duk_get_tval(ctx
, 0),
159 (duk_tval
*) duk_get_tval(ctx
, 1)));
163 thr
->heap
->lj
.type
= DUK_LJ_TYPE_RESUME
;
165 /* lj value2: thread */
166 DUK_ASSERT(thr
->valstack_bottom
< thr
->valstack_top
);
167 DUK_TVAL_SET_TVAL_UPDREF(thr
, &thr
->heap
->lj
.value2
, &thr
->valstack_bottom
[0]); /* side effects */
169 /* lj value1: value */
170 DUK_ASSERT(thr
->valstack_bottom
+ 1 < thr
->valstack_top
);
171 DUK_TVAL_SET_TVAL_UPDREF(thr
, &thr
->heap
->lj
.value1
, &thr
->valstack_bottom
[1]); /* side effects */
172 DUK_TVAL_CHKFAST_INPLACE(&thr
->heap
->lj
.value1
);
174 thr
->heap
->lj
.iserror
= is_error
;
176 DUK_ASSERT(thr
->heap
->lj
.jmpbuf_ptr
!= NULL
); /* call is from executor, so we know we have a jmpbuf */
177 duk_err_longjmp(thr
); /* execution resumes in bytecode executor */
178 return 0; /* never here */
180 state_invalid_initial
:
181 DUK_ERROR_TYPE(thr
, "invalid initial thread state/stack");
182 return 0; /* never here */
185 DUK_ERROR_TYPE(thr
, "invalid state");
186 return 0; /* never here */
190 * Yield the current thread.
192 * The thread must be in yieldable state: it must have a resumer, and there
193 * must not be any yield-preventing calls (native calls and constructor calls,
194 * currently) in the thread's call stack (otherwise a resume would not be
195 * possible later). This method must be called from an Ecmascript function.
199 * - isError (defaults to false)
201 * Note: yield and resume handling is currently asymmetric.
204 DUK_INTERNAL duk_ret_t
duk_bi_thread_yield(duk_context
*ctx
) {
205 duk_hthread
*thr
= (duk_hthread
*) ctx
;
206 duk_hobject
*caller_func
;
207 duk_small_int_t is_error
;
209 DUK_DDD(DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T",
210 (duk_tval
*) duk_get_tval(ctx
, 0),
211 (duk_tval
*) duk_get_tval(ctx
, 1)));
213 DUK_ASSERT(thr
->state
== DUK_HTHREAD_STATE_RUNNING
);
214 DUK_ASSERT(thr
->heap
->curr_thread
== thr
);
216 is_error
= (duk_small_int_t
) duk_to_boolean(ctx
, 1);
222 * Thread state and calling context checks
226 DUK_DD(DUK_DDPRINT("yield state invalid: current thread must have a resumer"));
229 DUK_ASSERT(thr
->resumer
->state
== DUK_HTHREAD_STATE_RESUMED
);
231 if (thr
->callstack_top
< 2) {
232 DUK_DD(DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)"));
235 DUK_ASSERT(DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 1) != NULL
); /* us */
236 DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 1)));
237 DUK_ASSERT(DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 2) != NULL
); /* caller */
239 caller_func
= DUK_ACT_GET_FUNC(thr
->callstack
+ thr
->callstack_top
- 2);
240 if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func
)) {
241 DUK_DD(DUK_DDPRINT("yield state invalid: caller must be Ecmascript code"));
245 DUK_ASSERT(thr
->callstack_preventcount
>= 1); /* should never be zero, because we (Duktape.Thread.yield) are on the stack */
246 if (thr
->callstack_preventcount
!= 1) {
247 /* Note: the only yield-preventing call is Duktape.Thread.yield(), hence check for 1, not 0 */
248 DUK_DD(DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack (preventcount is %ld)",
249 (long) thr
->callstack_preventcount
));
254 * The error object has been augmented with a traceback and other
255 * info from its creation point -- usually the current thread.
256 * The error handler, however, is called right before throwing
257 * and runs in the yielder's thread.
260 #if defined(DUK_USE_AUGMENT_ERROR_THROW)
262 DUK_ASSERT_TOP(ctx
, 1); /* value (error) is at stack top */
263 duk_err_augment_error_throw(thr
); /* in yielder's context */
269 DUK_DDD(DUK_DDDPRINT("YIELD ERROR: value=%!T",
270 (duk_tval
*) duk_get_tval(ctx
, 0)));
272 DUK_DDD(DUK_DDDPRINT("YIELD NORMAL: value=%!T",
273 (duk_tval
*) duk_get_tval(ctx
, 0)));
280 * After longjmp(), processing continues in bytecode executor longjmp
281 * handler, which will e.g. update thr->resumer to NULL.
284 thr
->heap
->lj
.type
= DUK_LJ_TYPE_YIELD
;
286 /* lj value1: value */
287 DUK_ASSERT(thr
->valstack_bottom
< thr
->valstack_top
);
288 DUK_TVAL_SET_TVAL_UPDREF(thr
, &thr
->heap
->lj
.value1
, &thr
->valstack_bottom
[0]); /* side effects */
289 DUK_TVAL_CHKFAST_INPLACE(&thr
->heap
->lj
.value1
);
291 thr
->heap
->lj
.iserror
= is_error
;
293 DUK_ASSERT(thr
->heap
->lj
.jmpbuf_ptr
!= NULL
); /* call is from executor, so we know we have a jmpbuf */
294 duk_err_longjmp(thr
); /* execution resumes in bytecode executor */
295 return 0; /* never here */
298 DUK_ERROR_TYPE(thr
, "invalid state");
299 return 0; /* never here */
302 DUK_INTERNAL duk_ret_t
duk_bi_thread_current(duk_context
*ctx
) {
303 duk_push_current_thread(ctx
);