MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3
4#include "ant.h"
5#include "ptr.h"
6#include "errors.h"
7#include "internal.h"
8#include "runtime.h"
9#include "sugar.h"
10
11#include "gc/roots.h"
12#include "silver/engine.h"
13#include "modules/generator.h"
14#include "modules/iterator.h"
15#include "modules/symbol.h"
16
17enum { GENERATOR_NATIVE_TAG = 0x47454e52u }; // GENR
18
19typedef enum {
20 GEN_SUSPENDED_START = 0,
21 GEN_SUSPENDED_YIELD = 1,
22 GEN_EXECUTING = 2,
23 GEN_COMPLETED = 3,
24} generator_state_t;
25
26typedef struct generator_request {
27 sv_resume_kind_t kind;
28 ant_value_t value;
29 ant_value_t promise;
30 struct generator_request *next;
31} generator_request_t;
32
33typedef struct generator_data {
34 coroutine_t *coro;
35 generator_state_t state;
36 generator_request_t *queue_head;
37 generator_request_t *queue_tail;
38 bool is_async;
39} generator_data_t;
40
41static ant_value_t generator_resume_kind(
42 ant_t *js, ant_value_t gen,
43 ant_value_t resume_value, sv_resume_kind_t resume_kind
44);
45
46static ant_value_t generator_result(ant_t *js, bool done, ant_value_t value) {
47 ant_value_t result = js_mkobj(js);
48 js_set(js, result, "done", js_bool(done));
49 js_set(js, result, "value", value);
50 return result;
51}
52
53static generator_data_t *generator_data(ant_value_t gen) {
54 if (!js_check_native_tag(gen, GENERATOR_NATIVE_TAG)) return NULL;
55 return (generator_data_t *)js_get_native_ptr(gen);
56}
57
58static generator_state_t generator_state(ant_value_t gen) {
59 generator_data_t *data = generator_data(gen);
60 return data ? data->state : GEN_COMPLETED;
61}
62
63static void generator_set_state(ant_value_t gen, generator_state_t state) {
64 generator_data_t *data = generator_data(gen);
65 if (data) data->state = state;
66}
67
68static bool generator_is_async(ant_value_t gen) {
69 generator_data_t *data = generator_data(gen);
70 return data && data->is_async;
71}
72
73static ant_value_t generator_async_wrap_result(ant_t *js, ant_value_t result) {
74 ant_value_t promise = js_mkpromise(js);
75 if (is_err(promise)) return promise;
76
77 if (is_err(result)) {
78 ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result;
79 js->thrown_exists = false;
80 js->thrown_value = js_mkundef();
81 js_reject_promise(js, promise, reject_value);
82 } else js_resolve_promise(js, promise, result);
83
84 return promise;
85}
86
87static generator_request_t *generator_dequeue_request(generator_data_t *data) {
88 if (!data || !data->queue_head) return NULL;
89 generator_request_t *req = data->queue_head;
90 data->queue_head = req->next;
91 if (!data->queue_head) data->queue_tail = NULL;
92 req->next = NULL;
93 return req;
94}
95
96static void generator_free_queue(generator_data_t *data) {
97 if (!data) return;
98 generator_request_t *req = data->queue_head;
99
100 while (req) {
101 generator_request_t *next = req->next;
102 free(req);
103 req = next;
104 }
105
106 data->queue_head = NULL;
107 data->queue_tail = NULL;
108}
109
110static ant_value_t generator_enqueue_request(
111 ant_t *js, ant_value_t gen, sv_resume_kind_t kind, ant_value_t value
112) {
113 generator_data_t *data = generator_data(gen);
114 if (!data || !data->is_async) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing");
115
116 ant_value_t promise = js_mkpromise(js);
117 if (is_err(promise)) return promise;
118
119 generator_request_t *req = (generator_request_t *)calloc(1, sizeof(*req));
120 if (!req) return js_mkerr(js, "out of memory for generator request");
121
122 *req = (generator_request_t){
123 .kind = kind,
124 .value = value,
125 .promise = promise,
126 .next = NULL,
127 };
128
129 if (data->queue_tail) data->queue_tail->next = req;
130 else data->queue_head = req;
131 data->queue_tail = req;
132
133 return promise;
134}
135
136static void generator_settle_request_promise(ant_t *js, ant_value_t promise, ant_value_t result) {
137 if (is_err(result)) {
138 ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result;
139 js->thrown_exists = false;
140 js->thrown_value = js_mkundef();
141 js_reject_promise(js, promise, reject_value);
142 } else js_resolve_promise(js, promise, result);
143}
144
145static void generator_process_queue(ant_t *js, ant_value_t gen) {
146 generator_data_t *data = generator_data(gen);
147 if (!data || !data->is_async) return;
148
149 GC_ROOT_SAVE(root_mark, js);
150 GC_ROOT_PIN(js, gen);
151
152 while (data->state != GEN_EXECUTING && data->queue_head) {
153 generator_request_t *req = generator_dequeue_request(data);
154 if (!req) break;
155
156 GC_ROOT_PIN(js, req->value);
157 GC_ROOT_PIN(js, req->promise);
158
159 coroutine_t *coro = data->coro;
160 if (coro) coro->async_promise = req->promise;
161
162 ant_value_t result = generator_resume_kind(js, gen, req->value, req->kind);
163 GC_ROOT_PIN(js, result);
164
165 if (vtype(result) != T_PROMISE || result != req->promise) {
166 generator_settle_request_promise(js, req->promise, result);
167 js_maybe_drain_microtasks_after_async_settle(js);
168 }
169
170 free(req);
171 data = generator_data(gen);
172 if (!data) break;
173 }
174
175 GC_ROOT_RESTORE(js, root_mark);
176}
177
178static coroutine_t *generator_coro(ant_value_t gen) {
179 generator_data_t *data = generator_data(gen);
180 return data ? data->coro : NULL;
181}
182
183static void generator_clear_coro(ant_value_t gen, coroutine_t *coro) {
184 generator_data_t *data = generator_data(gen);
185 if (data && data->coro == coro) data->coro = NULL;
186 if (coro) coroutine_unhold(coro, CORO_HOLD_GENERATOR);
187}
188
189coroutine_t *generator_get_coro_for_gc(ant_value_t gen) {
190 return generator_coro(gen);
191}
192
193void generator_mark_for_gc(ant_t *js, ant_value_t gen) {
194 generator_data_t *data = generator_data(gen);
195 if (!data) return;
196 for (generator_request_t *req = data->queue_head; req; req = req->next) {
197 gc_mark_value(js, req->value);
198 gc_mark_value(js, req->promise);
199 }
200}
201
202static ant_value_t generator_find_owner_in_list(ant_object_t *head, coroutine_t *coro) {
203 for (ant_object_t *obj = head; obj; obj = obj->next) {
204 ant_value_t candidate = js_obj_from_ptr(obj);
205 generator_data_t *data = generator_data(candidate);
206 if (data && data->coro == coro) return candidate;
207 }
208 return js_mkundef();
209}
210
211static ant_value_t generator_find_owner(ant_t *js, coroutine_t *coro) {
212 ant_value_t gen = generator_find_owner_in_list(js->objects, coro);
213 if (vtype(gen) != T_UNDEF) return gen;
214 gen = generator_find_owner_in_list(js->objects_old, coro);
215 if (vtype(gen) != T_UNDEF) return gen;
216 return generator_find_owner_in_list(js->permanent_objects, coro);
217}
218
219bool generator_resume_pending_request(ant_t *js, coroutine_t *coro, ant_value_t result) {
220 if (!coro || coro->type != CORO_GENERATOR || vtype(coro->async_promise) != T_PROMISE) return false;
221
222 ant_value_t gen = generator_find_owner(js, coro);
223 generator_data_t *data = generator_data(gen);
224 if (!data || !data->is_async) return false;
225
226 GC_ROOT_SAVE(root_mark, js);
227 GC_ROOT_PIN(js, gen);
228 GC_ROOT_PIN(js, result);
229
230 ant_value_t pending = coro->async_promise;
231 GC_ROOT_PIN(js, pending);
232
233 if (is_err(result)) {
234 ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result;
235 js->thrown_exists = false;
236 js->thrown_value = js_mkundef();
237
238 GC_ROOT_PIN(js, reject_value);
239 coro->async_promise = js_mkundef();
240 generator_set_state(gen, GEN_COMPLETED);
241 generator_clear_coro(gen, coro);
242
243 js_reject_promise(js, pending, reject_value);
244 js_maybe_drain_microtasks_after_async_settle(js);
245 generator_process_queue(js, gen);
246 GC_ROOT_RESTORE(js, root_mark);
247
248 return true;
249 }
250
251 if (coro->sv_vm && coro->sv_vm->suspended) {
252 if (vtype(coro->awaited_promise) != T_UNDEF) {
253 generator_set_state(gen, GEN_EXECUTING);
254 GC_ROOT_RESTORE(js, root_mark);
255 return true;
256 }
257
258 ant_value_t out = generator_result(js, false, result);
259 GC_ROOT_PIN(js, out);
260 coro->async_promise = js_mkundef();
261 generator_set_state(gen, GEN_SUSPENDED_YIELD);
262
263 js_resolve_promise(js, pending, out);
264 js_maybe_drain_microtasks_after_async_settle(js);
265 generator_process_queue(js, gen);
266 GC_ROOT_RESTORE(js, root_mark);
267
268 return true;
269 }
270
271 ant_value_t out = generator_result(js, true, result);
272 GC_ROOT_PIN(js, out);
273 coro->async_promise = js_mkundef();
274
275 generator_set_state(gen, GEN_COMPLETED);
276 generator_clear_coro(gen, coro);
277
278 js_resolve_promise(js, pending, out);
279 js_maybe_drain_microtasks_after_async_settle(js);
280 generator_process_queue(js, gen);
281 GC_ROOT_RESTORE(js, root_mark);
282
283 return true;
284}
285
286static void generator_finalize(ant_t *js, ant_object_t *obj) {
287 ant_value_t gen = js_obj_from_ptr(obj);
288 generator_data_t *data = (generator_data_t *)js_get_native_ptr(gen);
289
290 if (!data) return;
291 if (data->coro) generator_clear_coro(gen, data->coro);
292
293 js_set_native_ptr(gen, NULL);
294 js_set_native_tag(gen, 0);
295
296 generator_free_queue(data);
297 free(data);
298}
299
300static ant_value_t generator_resume_kind(
301 ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind
302) {
303 GC_ROOT_SAVE(root_mark, js);
304 GC_ROOT_PIN(js, gen);
305 GC_ROOT_PIN(js, resume_value);
306 coroutine_t *coro = generator_coro(gen);
307
308 if (!coro || !coro->sv_vm) {
309 generator_set_state(gen, GEN_COMPLETED);
310 if (resume_kind == SV_RESUME_THROW) {
311 GC_ROOT_RESTORE(js, root_mark);
312 return js_throw(js, resume_value);
313 }
314
315 ant_value_t out = generator_result(
316 js, true, resume_kind == SV_RESUME_RETURN
317 ? resume_value : js_mkundef()
318 );
319
320 GC_ROOT_RESTORE(js, root_mark);
321 return out;
322 }
323
324 generator_state_t state = generator_state(gen);
325 if (state == GEN_EXECUTING) {
326 ant_value_t queued = generator_enqueue_request(js, gen, resume_kind, resume_value);
327 GC_ROOT_RESTORE(js, root_mark);
328 return queued;
329 }
330
331 if (state == GEN_COMPLETED) {
332 if (resume_kind == SV_RESUME_THROW) {
333 GC_ROOT_RESTORE(js, root_mark);
334 return js_throw(js, resume_value);
335 }
336
337 ant_value_t out = generator_result(
338 js, true, resume_kind == SV_RESUME_RETURN
339 ? resume_value : js_mkundef()
340 );
341
342 GC_ROOT_RESTORE(js, root_mark);
343 return out;
344 }
345
346 if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_THROW) {
347 generator_set_state(gen, GEN_COMPLETED);
348 generator_clear_coro(gen, coro);
349 GC_ROOT_RESTORE(js, root_mark);
350 return js_throw(js, resume_value);
351 }
352
353 if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_RETURN) {
354 generator_set_state(gen, GEN_COMPLETED);
355 generator_clear_coro(gen, coro);
356 ant_value_t out = generator_result(js, true, resume_value);
357 GC_ROOT_RESTORE(js, root_mark);
358 return out;
359 }
360
361 generator_set_state(gen, GEN_EXECUTING);
362 coroutine_t *saved_active = js->active_async_coro;
363
364 coro->active_parent = saved_active;
365 coro->active_prev = NULL;
366
367 if (saved_active) saved_active->active_prev = coro;
368 js->active_async_coro = coro;
369 coroutine_hold(coro, CORO_HOLD_ACTIVE);
370
371 ant_value_t result;
372 if (state == GEN_SUSPENDED_START) {
373 sv_closure_t *closure = (vtype(coro->async_func) == T_FUNC) ? js_func_closure(coro->async_func) : NULL;
374 if (!closure || !closure->func) result = js_mkerr(js, "invalid generator function");
375 else result = sv_execute_closure_entry(
376 coro->sv_vm, closure, coro->async_func,
377 coro->super_val, coro->this_val, coro->args, coro->nargs, NULL
378 );
379 } else {
380 coro->sv_vm->suspended_resume_value = resume_value;
381 coro->sv_vm->suspended_resume_is_error = (resume_kind == SV_RESUME_THROW);
382 coro->sv_vm->suspended_resume_kind = resume_kind;
383 coro->sv_vm->suspended_resume_pending = true;
384 result = sv_resume_suspended(coro->sv_vm);
385 }
386
387 GC_ROOT_PIN(js, result);
388 js->active_async_coro = saved_active;
389 if (saved_active) saved_active->active_prev = NULL;
390
391 coro->active_parent = NULL;
392 coro->active_prev = NULL;
393 coroutine_unhold(coro, CORO_HOLD_ACTIVE);
394
395 if (is_err(result)) {
396 generator_set_state(gen, GEN_COMPLETED);
397 generator_clear_coro(gen, coro);
398 GC_ROOT_RESTORE(js, root_mark);
399 return result;
400 }
401
402 if (coro->sv_vm && coro->sv_vm->suspended) {
403 if (generator_is_async(gen) && vtype(coro->awaited_promise) != T_UNDEF) {
404 generator_set_state(gen, GEN_EXECUTING);
405 if (vtype(coro->async_promise) != T_PROMISE) {
406 coro->async_promise = js_mkpromise(js);
407 GC_ROOT_PIN(js, coro->async_promise);
408 }
409
410 ant_value_t out = coro->async_promise;
411 GC_ROOT_RESTORE(js, root_mark);
412 return out;
413 }
414
415 generator_set_state(gen, GEN_SUSPENDED_YIELD);
416 ant_value_t out = generator_result(js, false, result);
417 GC_ROOT_RESTORE(js, root_mark);
418
419 return out;
420 }
421
422 generator_set_state(gen, GEN_COMPLETED);
423 generator_clear_coro(gen, coro);
424
425 ant_value_t out = generator_result(js, true, result);
426 GC_ROOT_RESTORE(js, root_mark);
427
428 return out;
429}
430
431static ant_value_t generator_resume(ant_t *js, ant_value_t gen, ant_value_t resume_value) {
432 return generator_resume_kind(js, gen, resume_value, SV_RESUME_NEXT);
433}
434
435static ant_value_t generator_next(ant_t *js, ant_value_t *args, int nargs) {
436 ant_value_t gen = js->this_val;
437 if (vtype(gen) != T_GENERATOR)
438 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.next called on incompatible receiver");
439
440 ant_value_t resume_value = nargs > 0 ? args[0] : js_mkundef();
441 ant_value_t result = generator_resume(js, gen, resume_value);
442
443 if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result;
444 return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result;
445}
446
447static ant_value_t generator_return(ant_t *js, ant_value_t *args, int nargs) {
448 ant_value_t gen = js->this_val;
449 if (vtype(gen) != T_GENERATOR)
450 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.return called on incompatible receiver");
451
452 generator_state_t state = generator_state(gen);
453 if (state == GEN_EXECUTING && !generator_is_async(gen))
454 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing");
455
456 ant_value_t value = nargs > 0 ? args[0] : js_mkundef();
457 ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_RETURN);
458
459 if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result;
460 return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result;
461}
462
463static ant_value_t generator_throw(ant_t *js, ant_value_t *args, int nargs) {
464 ant_value_t gen = js->this_val;
465 if (vtype(gen) != T_GENERATOR)
466 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.throw called on incompatible receiver");
467
468 generator_state_t state = generator_state(gen);
469 if (state == GEN_EXECUTING && !generator_is_async(gen))
470 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing");
471
472 ant_value_t value = nargs > 0 ? args[0] : js_mkundef();
473 ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_THROW);
474
475 if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result;
476 return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result;
477}
478
479static ant_value_t generator_async_dispose(ant_t *js, ant_value_t *args, int nargs) {
480 ant_value_t gen = js->this_val;
481 if (vtype(gen) != T_GENERATOR || !generator_is_async(gen)) return js_mkerr_typed(
482 js, JS_ERR_TYPE,
483 "AsyncGenerator.prototype[Symbol.asyncDispose] called on incompatible receiver"
484 );
485
486 return generator_return(js, NULL, 0);
487}
488
489void init_generator_module(void) {
490 ant_t *js = rt->js;
491 ant_value_t proto = js_mkobj(js);
492
493 js->sym.generator_proto = proto;
494 js_set_proto_init(proto, js->sym.iterator_proto);
495
496 js_set(js, proto, "next", js_mkfun(generator_next));
497 js_set(js, proto, "return", js_mkfun(generator_return));
498 js_set(js, proto, "throw", js_mkfun(generator_throw));
499 js_set_sym(js, proto, get_toStringTag_sym(), js_mkstr(js, "Generator", 9));
500
501 ant_value_t async_proto = js_mkobj(js);
502 js->sym.async_generator_proto = async_proto;
503 js_set_proto_init(async_proto, js->sym.async_iterator_proto);
504 js_set(js, async_proto, "next", js_mkfun(generator_next));
505 js_set(js, async_proto, "return", js_mkfun(generator_return));
506 js_set(js, async_proto, "throw", js_mkfun(generator_throw));
507 js_set_sym(js, async_proto, get_toStringTag_sym(), js_mkstr(js, "AsyncGenerator", 14));
508 js_set_sym(js, async_proto, get_asyncDispose_sym(), js_mkfun(generator_async_dispose));
509
510 ant_value_t async_generator_func_proto = js_get_slot(js_glob(js), SLOT_ASYNC_GENERATOR_PROTO);
511 if (is_object_type(async_generator_func_proto)) {
512 js_set(js, async_generator_func_proto, "prototype", async_proto);
513 js_set_descriptor(js, js_as_obj(async_generator_func_proto), "prototype", 9, JS_DESC_C);
514 js_set(js, async_proto, "constructor", async_generator_func_proto);
515 js_set_descriptor(js, async_proto, "constructor", 11, JS_DESC_C);
516 }
517
518 init_async_iterator_helpers();
519}
520
521ant_value_t sv_call_generator_closure_dispatch(
522 sv_vm_t *caller_vm, ant_t *js, sv_closure_t *closure,
523 ant_value_t callee_func, ant_value_t super_val,
524 ant_value_t this_val, ant_value_t *args, int argc
525) {
526 if (!closure || !closure->func) return js_mkerr(js, "invalid generator function");
527
528 sv_vm_t *gen_vm = sv_vm_create(js, SV_VM_ASYNC);
529 if (!gen_vm) return js_mkerr(js, "out of memory for generator VM");
530
531 coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t));
532 if (!coro) {
533 sv_vm_destroy(gen_vm);
534 return js_mkerr(js, "out of memory for generator");
535 }
536
537 ant_value_t *copied_args = NULL;
538 if (argc > 0 && args) {
539 copied_args = (ant_value_t *)CORO_MALLOC(sizeof(ant_value_t) * (size_t)argc);
540 if (!copied_args) {
541 sv_vm_destroy(gen_vm);
542 CORO_FREE(coro);
543 return js_mkerr(js, "out of memory for generator args");
544 }
545 memcpy(copied_args, args, sizeof(ant_value_t) * (size_t)argc);
546 }
547
548 ant_value_t gen = js_mkgenerator(js);
549 if (is_err(gen)) {
550 if (copied_args) CORO_FREE(copied_args);
551 sv_vm_destroy(gen_vm);
552 CORO_FREE(coro);
553 return gen;
554 }
555
556 generator_data_t *data = (generator_data_t *)calloc(1, sizeof(*data));
557 if (!data) {
558 if (copied_args) CORO_FREE(copied_args);
559 sv_vm_destroy(gen_vm);
560 CORO_FREE(coro);
561 return js_mkerr(js, "out of memory for generator data");
562 }
563
564 *coro = (coroutine_t){
565 .js = js,
566 .type = CORO_GENERATOR,
567 .this_val = this_val,
568 .super_val = super_val,
569 .new_target = js->new_target,
570 .awaited_promise = js_mkundef(),
571 .result = js_mkundef(),
572 .async_func = callee_func,
573 .args = copied_args,
574 .nargs = argc,
575 .active_parent = NULL,
576 .is_settled = false,
577 .is_error = false,
578 .is_done = false,
579 .resume_point = 0,
580 .yield_value = js_mkundef(),
581 .async_promise = js_mkundef(),
582 .next = NULL,
583 .mco = NULL,
584 .owner_vm = gen_vm,
585 .sv_vm = gen_vm,
586 .mco_started = false,
587 .is_ready = false,
588 .did_suspend = false,
589 .refcount = 1,
590 .hold_bits = 0,
591 .await_registered = false,
592 .destroy_requested = false,
593 };
594
595 *data = (generator_data_t){
596 .coro = coro,
597 .state = GEN_SUSPENDED_START,
598 .is_async = closure->func->is_async,
599 };
600 coroutine_hold(coro, CORO_HOLD_GENERATOR);
601 coroutine_release(coro);
602
603 js_set_native_ptr(gen, data);
604 js_set_native_tag(gen, GENERATOR_NATIVE_TAG);
605 js_set_finalizer(gen, generator_finalize);
606
607 ant_value_t instance_proto = js_get(js, callee_func, "prototype");
608 if (is_object_type(instance_proto)) js_set_proto_wb(js, gen, instance_proto);
609 else if (data->is_async && is_object_type(js->sym.async_generator_proto))
610 js_set_proto_wb(js, gen, js->sym.async_generator_proto);
611
612 return gen;
613}