]>
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; | |
53 | duk_tval tv_tmp; | |
54 | duk_tval *tv; | |
55 | duk_hobject *func; | |
56 | duk_hobject *caller_func; | |
57 | duk_small_int_t is_error; | |
58 | ||
59 | DUK_DDD(DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T", | |
60 | (duk_tval *) duk_get_tval(ctx, 0), | |
61 | (duk_tval *) duk_get_tval(ctx, 1), | |
62 | (duk_tval *) duk_get_tval(ctx, 2))); | |
63 | ||
64 | DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); | |
65 | DUK_ASSERT(thr->heap->curr_thread == thr); | |
66 | ||
67 | thr_resume = duk_require_hthread(ctx, 0); | |
68 | is_error = (duk_small_int_t) duk_to_boolean(ctx, 2); | |
69 | duk_set_top(ctx, 2); | |
70 | ||
71 | /* [ thread value ] */ | |
72 | ||
73 | /* | |
74 | * Thread state and calling context checks | |
75 | */ | |
76 | ||
77 | if (thr->callstack_top < 2) { | |
78 | DUK_DD(DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)")); | |
79 | goto state_error; | |
80 | } | |
81 | DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL); /* us */ | |
82 | DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1))); | |
83 | DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL); /* caller */ | |
84 | ||
85 | caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2); | |
86 | if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func)) { | |
87 | DUK_DD(DUK_DDPRINT("resume state invalid: caller must be Ecmascript code")); | |
88 | goto state_error; | |
89 | } | |
90 | ||
91 | /* Note: there is no requirement that: 'thr->callstack_preventcount == 1' | |
92 | * like for yield. | |
93 | */ | |
94 | ||
95 | if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE && | |
96 | thr_resume->state != DUK_HTHREAD_STATE_YIELDED) { | |
97 | DUK_DD(DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED")); | |
98 | goto state_error; | |
99 | } | |
100 | ||
101 | DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE || | |
102 | thr_resume->state == DUK_HTHREAD_STATE_YIELDED); | |
103 | ||
104 | /* Further state-dependent pre-checks */ | |
105 | ||
106 | if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { | |
107 | /* no pre-checks now, assume a previous yield() has left things in | |
108 | * tip-top shape (longjmp handler will assert for these). | |
109 | */ | |
110 | } else { | |
111 | DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE); | |
112 | ||
113 | if ((thr_resume->callstack_top != 0) || | |
114 | (thr_resume->valstack_top - thr_resume->valstack != 1)) { | |
115 | goto state_invalid_initial; | |
116 | } | |
117 | tv = &thr_resume->valstack_top[-1]; | |
118 | DUK_ASSERT(tv >= thr_resume->valstack && tv < thr_resume->valstack_top); | |
119 | if (!DUK_TVAL_IS_OBJECT(tv)) { | |
120 | goto state_invalid_initial; | |
121 | } | |
122 | func = DUK_TVAL_GET_OBJECT(tv); | |
123 | DUK_ASSERT(func != NULL); | |
124 | if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { | |
125 | /* Note: cannot be a bound function either right now, | |
126 | * this would be easy to relax though. | |
127 | */ | |
128 | goto state_invalid_initial; | |
129 | } | |
130 | ||
131 | } | |
132 | ||
133 | /* | |
134 | * The error object has been augmented with a traceback and other | |
135 | * info from its creation point -- usually another thread. The | |
136 | * error handler is called here right before throwing, but it also | |
137 | * runs in the resumer's thread. It might be nice to get a traceback | |
138 | * from the resumee but this is not the case now. | |
139 | */ | |
140 | ||
141 | #if defined(DUK_USE_AUGMENT_ERROR_THROW) | |
142 | if (is_error) { | |
143 | DUK_ASSERT_TOP(ctx, 2); /* value (error) is at stack top */ | |
144 | duk_err_augment_error_throw(thr); /* in resumer's context */ | |
145 | } | |
146 | #endif | |
147 | ||
148 | #ifdef DUK_USE_DEBUG | |
149 | if (is_error) { | |
150 | DUK_DDD(DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T", | |
151 | (duk_tval *) duk_get_tval(ctx, 0), | |
152 | (duk_tval *) duk_get_tval(ctx, 1))); | |
153 | } else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { | |
154 | DUK_DDD(DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T", | |
155 | (duk_tval *) duk_get_tval(ctx, 0), | |
156 | (duk_tval *) duk_get_tval(ctx, 1))); | |
157 | } else { | |
158 | DUK_DDD(DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T", | |
159 | (duk_tval *) duk_get_tval(ctx, 0), | |
160 | (duk_tval *) duk_get_tval(ctx, 1))); | |
161 | } | |
162 | #endif | |
163 | ||
164 | thr->heap->lj.type = DUK_LJ_TYPE_RESUME; | |
165 | ||
166 | /* lj value2: thread */ | |
167 | DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); | |
168 | DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2); | |
169 | DUK_TVAL_SET_TVAL(&thr->heap->lj.value2, &thr->valstack_bottom[0]); | |
170 | DUK_TVAL_INCREF(thr, &thr->heap->lj.value2); | |
171 | DUK_TVAL_DECREF(thr, &tv_tmp); | |
172 | ||
173 | /* lj value1: value */ | |
174 | DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top); | |
175 | DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); | |
176 | DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[1]); | |
177 | DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); | |
178 | DUK_TVAL_DECREF(thr, &tv_tmp); | |
179 | ||
180 | thr->heap->lj.iserror = is_error; | |
181 | ||
182 | DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ | |
183 | duk_err_longjmp(thr); /* execution resumes in bytecode executor */ | |
184 | return 0; /* never here */ | |
185 | ||
186 | state_invalid_initial: | |
187 | DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid initial thread state/stack"); | |
188 | return 0; /* never here */ | |
189 | ||
190 | state_error: | |
191 | DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for resume"); | |
192 | return 0; /* never here */ | |
193 | } | |
194 | ||
195 | /* | |
196 | * Yield the current thread. | |
197 | * | |
198 | * The thread must be in yieldable state: it must have a resumer, and there | |
199 | * must not be any yield-preventing calls (native calls and constructor calls, | |
200 | * currently) in the thread's call stack (otherwise a resume would not be | |
201 | * possible later). This method must be called from an Ecmascript function. | |
202 | * | |
203 | * Args: | |
204 | * - value | |
205 | * - isError (defaults to false) | |
206 | * | |
207 | * Note: yield and resume handling is currently asymmetric. | |
208 | */ | |
209 | ||
210 | DUK_INTERNAL duk_ret_t duk_bi_thread_yield(duk_context *ctx) { | |
211 | duk_hthread *thr = (duk_hthread *) ctx; | |
212 | duk_tval tv_tmp; | |
213 | duk_hobject *caller_func; | |
214 | duk_small_int_t is_error; | |
215 | ||
216 | DUK_DDD(DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T", | |
217 | (duk_tval *) duk_get_tval(ctx, 0), | |
218 | (duk_tval *) duk_get_tval(ctx, 1))); | |
219 | ||
220 | DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); | |
221 | DUK_ASSERT(thr->heap->curr_thread == thr); | |
222 | ||
223 | is_error = (duk_small_int_t) duk_to_boolean(ctx, 1); | |
224 | duk_set_top(ctx, 1); | |
225 | ||
226 | /* [ value ] */ | |
227 | ||
228 | /* | |
229 | * Thread state and calling context checks | |
230 | */ | |
231 | ||
232 | if (!thr->resumer) { | |
233 | DUK_DD(DUK_DDPRINT("yield state invalid: current thread must have a resumer")); | |
234 | goto state_error; | |
235 | } | |
236 | DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED); | |
237 | ||
238 | if (thr->callstack_top < 2) { | |
239 | DUK_DD(DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)")); | |
240 | goto state_error; | |
241 | } | |
242 | DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1) != NULL); /* us */ | |
243 | DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 1))); | |
244 | DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2) != NULL); /* caller */ | |
245 | ||
246 | caller_func = DUK_ACT_GET_FUNC(thr->callstack + thr->callstack_top - 2); | |
247 | if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(caller_func)) { | |
248 | DUK_DD(DUK_DDPRINT("yield state invalid: caller must be Ecmascript code")); | |
249 | goto state_error; | |
250 | } | |
251 | ||
252 | DUK_ASSERT(thr->callstack_preventcount >= 1); /* should never be zero, because we (Duktape.Thread.yield) are on the stack */ | |
253 | if (thr->callstack_preventcount != 1) { | |
254 | /* Note: the only yield-preventing call is Duktape.Thread.yield(), hence check for 1, not 0 */ | |
255 | DUK_DD(DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack (preventcount is %ld)", | |
256 | (long) thr->callstack_preventcount)); | |
257 | goto state_error; | |
258 | } | |
259 | ||
260 | /* | |
261 | * The error object has been augmented with a traceback and other | |
262 | * info from its creation point -- usually the current thread. | |
263 | * The error handler, however, is called right before throwing | |
264 | * and runs in the yielder's thread. | |
265 | */ | |
266 | ||
267 | #if defined(DUK_USE_AUGMENT_ERROR_THROW) | |
268 | if (is_error) { | |
269 | DUK_ASSERT_TOP(ctx, 1); /* value (error) is at stack top */ | |
270 | duk_err_augment_error_throw(thr); /* in yielder's context */ | |
271 | } | |
272 | #endif | |
273 | ||
274 | #ifdef DUK_USE_DEBUG | |
275 | if (is_error) { | |
276 | DUK_DDD(DUK_DDDPRINT("YIELD ERROR: value=%!T", | |
277 | (duk_tval *) duk_get_tval(ctx, 0))); | |
278 | } else { | |
279 | DUK_DDD(DUK_DDDPRINT("YIELD NORMAL: value=%!T", | |
280 | (duk_tval *) duk_get_tval(ctx, 0))); | |
281 | } | |
282 | #endif | |
283 | ||
284 | /* | |
285 | * Process yield | |
286 | * | |
287 | * After longjmp(), processing continues in bytecode executor longjmp | |
288 | * handler, which will e.g. update thr->resumer to NULL. | |
289 | */ | |
290 | ||
291 | thr->heap->lj.type = DUK_LJ_TYPE_YIELD; | |
292 | ||
293 | /* lj value1: value */ | |
294 | DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); | |
295 | DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); | |
296 | DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[0]); | |
297 | DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); | |
298 | DUK_TVAL_DECREF(thr, &tv_tmp); | |
299 | ||
300 | thr->heap->lj.iserror = is_error; | |
301 | ||
302 | DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ | |
303 | duk_err_longjmp(thr); /* execution resumes in bytecode executor */ | |
304 | return 0; /* never here */ | |
305 | ||
306 | state_error: | |
307 | DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for yield"); | |
308 | return 0; /* never here */ | |
309 | } | |
310 | ||
311 | DUK_INTERNAL duk_ret_t duk_bi_thread_current(duk_context *ctx) { | |
312 | duk_push_current_thread(ctx); | |
313 | return 1; | |
314 | } |