]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | /* |
2 | * Thread builtins | |
3 | */ | |
4 | ||
5 | #include "duk_internal.h" | |
6 | ||
7 | /* | |
8 | * Constructor | |
9 | */ | |
10 | ||
11 | DUK_INTERNAL duk_ret_t duk_bi_thread_constructor(duk_context *ctx) { | |
12 | duk_hthread *new_thr; | |
13 | duk_hobject *func; | |
14 | ||
15 | /* XXX: need a duk_require_func_or_lfunc_coerce() */ | |
16 | if (!duk_is_callable(ctx, 0)) { | |
17 | return DUK_RET_TYPE_ERROR; | |
18 | } | |
19 | func = duk_require_hobject_or_lfunc_coerce(ctx, 0); | |
20 | DUK_ASSERT(func != NULL); | |
21 | ||
22 | duk_push_thread(ctx); | |
23 | new_thr = (duk_hthread *) duk_get_hobject(ctx, -1); | |
24 | DUK_ASSERT(new_thr != NULL); | |
25 | new_thr->state = DUK_HTHREAD_STATE_INACTIVE; | |
26 | ||
27 | /* push initial function call to new thread stack; this is | |
28 | * picked up by resume(). | |
29 | */ | |
30 | duk_push_hobject((duk_context *) new_thr, func); | |
31 | ||
32 | return 1; /* return thread */ | |
33 | } | |
34 | ||
35 | /* | |
36 | * Resume a thread. | |
37 | * | |
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. | |
41 | * | |
42 | * Args: | |
43 | * - thread | |
44 | * - value | |
45 | * - isError (defaults to false) | |
46 | * | |
47 | * Note: yield and resume handling is currently asymmetric. | |
48 | */ | |
49 | ||
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; | |
7c673cae FG |
53 | duk_tval *tv; |
54 | duk_hobject *func; | |
55 | duk_hobject *caller_func; | |
56 | duk_small_int_t is_error; | |
57 | ||
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))); | |
62 | ||
63 | DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); | |
64 | DUK_ASSERT(thr->heap->curr_thread == thr); | |
65 | ||
66 | thr_resume = duk_require_hthread(ctx, 0); | |
67 | is_error = (duk_small_int_t) duk_to_boolean(ctx, 2); | |
68 | duk_set_top(ctx, 2); | |
69 | ||
70 | /* [ thread value ] */ | |
71 | ||
72 | /* | |
73 | * Thread state and calling context checks | |
74 | */ | |
75 | ||
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)")); | |
78 | goto state_error; | |
79 | } | |
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 */ | |
83 | ||
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")); | |
87 | goto state_error; | |
88 | } | |
89 | ||
90 | /* Note: there is no requirement that: 'thr->callstack_preventcount == 1' | |
91 | * like for yield. | |
92 | */ | |
93 | ||
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")); | |
97 | goto state_error; | |
98 | } | |
99 | ||
100 | DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE || | |
101 | thr_resume->state == DUK_HTHREAD_STATE_YIELDED); | |
102 | ||
103 | /* Further state-dependent pre-checks */ | |
104 | ||
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). | |
108 | */ | |
109 | } else { | |
110 | DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE); | |
111 | ||
112 | if ((thr_resume->callstack_top != 0) || | |
113 | (thr_resume->valstack_top - thr_resume->valstack != 1)) { | |
114 | goto state_invalid_initial; | |
115 | } | |
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; | |
120 | } | |
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. | |
126 | */ | |
127 | goto state_invalid_initial; | |
128 | } | |
129 | ||
130 | } | |
131 | ||
132 | /* | |
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. | |
138 | */ | |
139 | ||
140 | #if defined(DUK_USE_AUGMENT_ERROR_THROW) | |
141 | if (is_error) { | |
142 | DUK_ASSERT_TOP(ctx, 2); /* value (error) is at stack top */ | |
143 | duk_err_augment_error_throw(thr); /* in resumer's context */ | |
144 | } | |
145 | #endif | |
146 | ||
147 | #ifdef DUK_USE_DEBUG | |
148 | if (is_error) { | |
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))); | |
156 | } else { | |
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))); | |
160 | } | |
161 | #endif | |
162 | ||
163 | thr->heap->lj.type = DUK_LJ_TYPE_RESUME; | |
164 | ||
165 | /* lj value2: thread */ | |
166 | DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); | |
11fdf7f2 | 167 | DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value2, &thr->valstack_bottom[0]); /* side effects */ |
7c673cae FG |
168 | |
169 | /* lj value1: value */ | |
170 | DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top); | |
11fdf7f2 TL |
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); | |
7c673cae FG |
173 | |
174 | thr->heap->lj.iserror = is_error; | |
175 | ||
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 */ | |
179 | ||
180 | state_invalid_initial: | |
11fdf7f2 | 181 | DUK_ERROR_TYPE(thr, "invalid initial thread state/stack"); |
7c673cae FG |
182 | return 0; /* never here */ |
183 | ||
184 | state_error: | |
11fdf7f2 | 185 | DUK_ERROR_TYPE(thr, "invalid state"); |
7c673cae FG |
186 | return 0; /* never here */ |
187 | } | |
188 | ||
189 | /* | |
190 | * Yield the current thread. | |
191 | * | |
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. | |
196 | * | |
197 | * Args: | |
198 | * - value | |
199 | * - isError (defaults to false) | |
200 | * | |
201 | * Note: yield and resume handling is currently asymmetric. | |
202 | */ | |
203 | ||
204 | DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_context *ctx) { | |
205 | duk_hthread *thr = (duk_hthread *) ctx; | |
7c673cae FG |
206 | duk_hobject *caller_func; |
207 | duk_small_int_t is_error; | |
208 | ||
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))); | |
212 | ||
213 | DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); | |
214 | DUK_ASSERT(thr->heap->curr_thread == thr); | |
215 | ||
216 | is_error = (duk_small_int_t) duk_to_boolean(ctx, 1); | |
217 | duk_set_top(ctx, 1); | |
218 | ||
219 | /* [ value ] */ | |
220 | ||
221 | /* | |
222 | * Thread state and calling context checks | |
223 | */ | |
224 | ||
225 | if (!thr->resumer) { | |
226 | DUK_DD(DUK_DDPRINT("yield state invalid: current thread must have a resumer")); | |
227 | goto state_error; | |
228 | } | |
229 | DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED); | |
230 | ||
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)")); | |
233 | goto state_error; | |
234 | } | |
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 */ | |
238 | ||
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")); | |
242 | goto state_error; | |
243 | } | |
244 | ||
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)); | |
250 | goto state_error; | |
251 | } | |
252 | ||
253 | /* | |
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. | |
258 | */ | |
259 | ||
260 | #if defined(DUK_USE_AUGMENT_ERROR_THROW) | |
261 | if (is_error) { | |
262 | DUK_ASSERT_TOP(ctx, 1); /* value (error) is at stack top */ | |
263 | duk_err_augment_error_throw(thr); /* in yielder's context */ | |
264 | } | |
265 | #endif | |
266 | ||
267 | #ifdef DUK_USE_DEBUG | |
268 | if (is_error) { | |
269 | DUK_DDD(DUK_DDDPRINT("YIELD ERROR: value=%!T", | |
270 | (duk_tval *) duk_get_tval(ctx, 0))); | |
271 | } else { | |
272 | DUK_DDD(DUK_DDDPRINT("YIELD NORMAL: value=%!T", | |
273 | (duk_tval *) duk_get_tval(ctx, 0))); | |
274 | } | |
275 | #endif | |
276 | ||
277 | /* | |
278 | * Process yield | |
279 | * | |
280 | * After longjmp(), processing continues in bytecode executor longjmp | |
281 | * handler, which will e.g. update thr->resumer to NULL. | |
282 | */ | |
283 | ||
284 | thr->heap->lj.type = DUK_LJ_TYPE_YIELD; | |
285 | ||
286 | /* lj value1: value */ | |
287 | DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); | |
11fdf7f2 TL |
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); | |
7c673cae FG |
290 | |
291 | thr->heap->lj.iserror = is_error; | |
292 | ||
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 */ | |
296 | ||
297 | state_error: | |
11fdf7f2 | 298 | DUK_ERROR_TYPE(thr, "invalid state"); |
7c673cae FG |
299 | return 0; /* never here */ |
300 | } | |
301 | ||
302 | DUK_INTERNAL duk_ret_t duk_bi_thread_current(duk_context *ctx) { | |
303 | duk_push_current_thread(ctx); | |
304 | return 1; | |
305 | } |