MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <time.h>
7#include <uv.h>
8
9#include "errors.h"
10#include "runtime.h"
11#include "internal.h"
12
13#include "silver/engine.h"
14#include "gc/roots.h"
15#include "gc/modules.h"
16#include "modules/abort.h"
17#include "modules/timer.h"
18#include "modules/symbol.h"
19
20typedef struct timer_entry {
21 uv_timer_t handle;
22 ant_value_t callback;
23 ant_value_t *args;
24 int nargs;
25 int timer_id;
26 int active;
27 int closed;
28 int is_interval;
29 struct timer_entry *next;
30 struct timer_entry *prev;
31} timer_entry_t;
32
33typedef struct microtask_entry {
34 ant_value_t callback;
35 ant_value_t promise;
36 struct microtask_entry *next;
37 uint8_t argc;
38 ant_value_t argv[];
39} microtask_entry_t;
40
41typedef struct immediate_entry {
42 ant_value_t callback;
43 int immediate_id;
44 int active;
45 struct immediate_entry *next;
46} immediate_entry_t;
47
48static struct {
49 ant_t *js;
50 timer_entry_t *timers;
51
52 microtask_entry_t *next_ticks;
53 microtask_entry_t *next_ticks_tail;
54 microtask_entry_t *next_ticks_processing;
55 microtask_entry_t *microtasks;
56 microtask_entry_t *microtasks_tail;
57 microtask_entry_t *microtasks_processing;
58 immediate_entry_t *immediates;
59 immediate_entry_t *immediates_tail;
60
61 int next_timer_id;
62 int next_immediate_id;
63 int active_timer_count;
64} timer_state = {
65 .js = NULL,
66 .timers = NULL,
67 .next_ticks = NULL,
68 .next_ticks_tail = NULL,
69 .next_ticks_processing = NULL,
70 .microtasks = NULL,
71 .microtasks_tail = NULL,
72 .microtasks_processing = NULL,
73 .immediates = NULL,
74 .immediates_tail = NULL,
75 .next_timer_id = 1,
76 .next_immediate_id = 1,
77 .active_timer_count = 0,
78};
79
80static ant_value_t g_timeout_proto = 0;
81static ant_value_t g_interval_proto = 0;
82
83static void add_timer_entry(timer_entry_t *entry) {
84 entry->next = timer_state.timers;
85 entry->prev = NULL;
86 if (timer_state.timers) timer_state.timers->prev = entry;
87 timer_state.timers = entry;
88}
89
90static void remove_timer_entry(timer_entry_t *entry) {
91 if (entry->prev) entry->prev->next = entry->next;
92 else timer_state.timers = entry->next;
93 if (entry->next) entry->next->prev = entry->prev;
94}
95
96static int timer_entry_is_registered(timer_entry_t *entry) {
97 for (timer_entry_t *it = timer_state.timers; it != NULL; it = it->next)
98 if (it == entry) return 1;
99 return 0;
100}
101
102static timer_entry_t *find_timer_entry_by_id(int timer_id) {
103 for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next)
104 if (entry->timer_id == timer_id) return entry;
105 return NULL;
106}
107
108static int timer_copy_args(timer_entry_t *entry, ant_value_t *args, int nargs) {
109 entry->nargs = nargs > 2 ? nargs - 2 : 0;
110 if (entry->nargs > 0) {
111 entry->args = ant_calloc(sizeof(ant_value_t) * entry->nargs);
112 if (!entry->args) return -1;
113 memcpy(entry->args, args + 2, sizeof(ant_value_t) * entry->nargs);
114 } else entry->args = NULL;
115 return 0;
116}
117
118static ant_value_t timer_to_primitive(ant_t *js, ant_value_t *args, int nargs) {
119 return js_get_slot(js_getthis(js), SLOT_DATA);
120}
121
122static ant_value_t timer_inspect(ant_t *js, ant_value_t *args, int nargs) {
123 ant_value_t this_obj = js_getthis(js);
124 ant_value_t id_val = js_get_slot(this_obj, SLOT_DATA);
125 int timer_id = vtype(id_val) == T_NUM ? (int)js_getnum(id_val) : 0;
126
127 ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym());
128 const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Timeout";
129
130 js_inspect_builder_t builder;
131 if (!js_inspect_builder_init_dynamic(&builder, js, 128)) {
132 return js_mkerr(js, "out of memory");
133 }
134
135 bool ok = js_inspect_header(&builder, "%s (%d)", tag, timer_id);
136 if (ok) ok = js_inspect_object_body(&builder, this_obj);
137 if (ok) ok = js_inspect_close(&builder);
138
139 if (!ok) {
140 js_inspect_builder_dispose(&builder);
141 return js_mkerr(js, "out of memory");
142 }
143
144 return js_inspect_builder_result(&builder);
145}
146
147static ant_value_t js_timer_ref(ant_t *js, ant_value_t *args, int nargs) {
148 ant_value_t this_obj = js_getthis(js);
149 timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(this_obj, SLOT_DATA)));
150 if (entry && !entry->closed && !uv_is_closing((uv_handle_t *)&entry->handle))
151 uv_ref((uv_handle_t *)&entry->handle);
152 return this_obj;
153}
154
155static ant_value_t js_timer_unref(ant_t *js, ant_value_t *args, int nargs) {
156 ant_value_t this_obj = js_getthis(js);
157 timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(this_obj, SLOT_DATA)));
158 if (entry && !entry->closed && !uv_is_closing((uv_handle_t *)&entry->handle))
159 uv_unref((uv_handle_t *)&entry->handle);
160 return this_obj;
161}
162
163static ant_value_t js_timer_has_ref(ant_t *js, ant_value_t *args, int nargs) {
164 timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(js_getthis(js), SLOT_DATA)));
165 if (!entry || entry->closed || uv_is_closing((uv_handle_t *)&entry->handle)) return js_false;
166 return js_bool(uv_has_ref((const uv_handle_t *)&entry->handle) != 0);
167}
168
169static ant_value_t timer_make_object(ant_t *js, int id, double delay_ms, int is_interval, ant_value_t callback) {
170 ant_value_t obj = js_mkobj(js);
171 ant_value_t proto = is_interval ? g_interval_proto : g_timeout_proto;
172
173 if (is_object_type(proto)) js_set_proto_init(obj, proto);
174
175 js_set(js, obj, "delay", js_mknum(delay_ms));
176 js_set(js, obj, "repeat", is_interval ? js_mknum(delay_ms) : js_mknull());
177
178 js_set(js, obj, "callback", callback);
179 js_set_descriptor(js, obj, "callback", 8, JS_DESC_W | JS_DESC_C);
180
181 js_set_slot(obj, SLOT_DATA, js_mknum((double)id));
182 js_set_sym(js, obj, get_toPrimitive_sym(), js_mkfun(timer_to_primitive));
183
184 return obj;
185}
186
187static int timer_id_from_arg(ant_t *js, ant_value_t arg) {
188 if (vtype(arg) == T_NUM) return (int)js_getnum(arg);
189 return (int)js_getnum(js_get_slot(arg, SLOT_DATA));
190}
191
192static void timer_close_cb(uv_handle_t *h) {
193 timer_entry_t *entry = (timer_entry_t *)h->data;
194 if (!entry) return;
195 if (entry->closed) return;
196 if (timer_entry_is_registered(entry)) remove_timer_entry(entry);
197 entry->closed = 1;
198 entry->active = 0;
199 entry->callback = 0;
200 entry->next = NULL;
201 entry->prev = NULL;
202
203 if (entry->args) {
204 free(entry->args);
205 entry->args = NULL;
206 }
207 entry->nargs = 0;
208}
209
210static void timer_callback(uv_timer_t *handle) {
211 timer_entry_t *entry = (timer_entry_t *)handle->data;
212 if (!entry || entry->closed || !timer_entry_is_registered(entry) || !entry->active) return;
213
214 ant_t *js = timer_state.js;
215 if (!entry->is_interval) {
216 entry->active = 0;
217 timer_state.active_timer_count--;
218 }
219
220 sv_vm_call(js->vm, js, entry->callback, js_mkundef(), entry->args, entry->nargs, NULL, false);
221 process_microtasks(js);
222
223 if (!entry->is_interval) if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close(
224 (uv_handle_t *)&entry->handle, timer_close_cb
225 );
226}
227
228// setTimeout(callback, delay, ...args)
229static ant_value_t js_set_timeout(ant_t *js, ant_value_t *args, int nargs) {
230 if (nargs < 1) {
231 return js_mkerr(js, "setTimeout requires at least 1 argument (callback)");
232 }
233
234 ant_value_t callback = args[0];
235 double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0;
236 uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 0;
237
238 timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t));
239 if (entry == NULL) return js_mkerr(js, "failed to allocate timer");
240
241 if (timer_copy_args(entry, args, nargs) < 0) {
242 free(entry);
243 return js_mkerr(js, "failed to allocate timer args");
244 }
245
246 uv_timer_init(uv_default_loop(), &entry->handle);
247 entry->handle.data = entry;
248 entry->callback = callback;
249 entry->timer_id = timer_state.next_timer_id++;
250 entry->active = 1;
251 entry->closed = 0;
252 entry->is_interval = 0;
253
254 add_timer_entry(entry);
255 timer_state.active_timer_count++;
256 uv_timer_start(&entry->handle, timer_callback, ms, 0);
257
258 return timer_make_object(js, entry->timer_id, delay_ms, 0, callback);
259}
260
261// setInterval(callback, delay, ...args)
262static ant_value_t js_set_interval(ant_t *js, ant_value_t *args, int nargs) {
263 if (nargs < 1) {
264 return js_mkerr(js, "setInterval requires at least 1 argument (callback)");
265 }
266
267 ant_value_t callback = args[0];
268 double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0;
269 uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 1;
270
271 timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t));
272 if (entry == NULL) return js_mkerr(js, "failed to allocate timer");
273
274 if (timer_copy_args(entry, args, nargs) < 0) {
275 free(entry);
276 return js_mkerr(js, "failed to allocate timer args");
277 }
278
279 uv_timer_init(uv_default_loop(), &entry->handle);
280 entry->handle.data = entry;
281 entry->callback = callback;
282 entry->timer_id = timer_state.next_timer_id++;
283 entry->active = 1;
284 entry->closed = 0;
285 entry->is_interval = 1;
286
287 add_timer_entry(entry);
288 timer_state.active_timer_count++;
289 uv_timer_start(&entry->handle, timer_callback, ms, ms);
290
291 return timer_make_object(js, entry->timer_id, delay_ms, 1, callback);
292}
293
294// clearTimeout(timerId | timerObject)
295static ant_value_t js_clear_timeout(ant_t *js, ant_value_t *args, int nargs) {
296 if (nargs < 1) return js_mkundef();
297 int timer_id = timer_id_from_arg(js, args[0]);
298
299 for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next) {
300 if (entry->timer_id == timer_id && entry->active) {
301 entry->active = 0; timer_state.active_timer_count--;
302 if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close((uv_handle_t *)&entry->handle, timer_close_cb);
303 break;
304 }}
305
306 return js_mkundef();
307}
308
309// setImmediate(callback)
310static ant_value_t js_set_immediate(ant_t *js, ant_value_t *args, int nargs) {
311 if (nargs < 1) {
312 return js_mkerr(js, "setImmediate requires 1 argument (callback)");
313 }
314
315 ant_value_t callback = args[0];
316
317 immediate_entry_t *entry = ant_calloc(sizeof(immediate_entry_t));
318 if (entry == NULL) {
319 return js_mkerr(js, "failed to allocate immediate");
320 }
321
322 entry->callback = callback;
323 entry->immediate_id = timer_state.next_immediate_id++;
324 entry->active = 1;
325 entry->next = NULL;
326
327 if (timer_state.immediates_tail == NULL) {
328 timer_state.immediates = entry;
329 timer_state.immediates_tail = entry;
330 } else {
331 timer_state.immediates_tail->next = entry;
332 timer_state.immediates_tail = entry;
333 }
334
335 ant_value_t obj = js_mkobj(js);
336 js_set(js, obj, "id", js_mknum((double)entry->immediate_id));
337 js_set(js, obj, "callback", callback);
338
339 return obj;
340}
341
342// clearImmediate(immediateId | immediateObject)
343static ant_value_t js_clear_immediate(ant_t *js, ant_value_t *args, int nargs) {
344 if (nargs < 1) return js_mkundef();
345 int immediate_id = timer_id_from_arg(js, args[0]);
346
347 for (immediate_entry_t *entry = timer_state.immediates; entry != NULL; entry = entry->next) {
348 if (entry->immediate_id == immediate_id) { entry->active = 0; break; }
349 }
350
351 return js_mkundef();
352}
353
354// queueMicrotask(callback)
355static ant_value_t js_queue_microtask(ant_t *js, ant_value_t *args, int nargs) {
356 if (nargs < 1) {
357 return js_mkerr(js, "queueMicrotask requires 1 argument (callback)");
358 }
359
360 queue_microtask(js, args[0]);
361 return js_mkundef();
362}
363
364static ant_value_t timers_promises_get_state(ant_t *js) {
365 return js_get_slot(js->current_func, SLOT_DATA);
366}
367
368static ant_value_t timers_promises_abort_reason(ant_t *js, ant_value_t signal) {
369 ant_value_t reason = abort_signal_get_reason(signal);
370 if (vtype(reason) != T_UNDEF && vtype(reason) != T_NULL) return reason;
371 return js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted");
372}
373
374static void timers_promises_remove_abort_listener(ant_t *js, ant_value_t state) {
375 ant_value_t signal = 0;
376 ant_value_t listener = 0;
377
378 if (!is_object_type(state)) return;
379
380 signal = js_get(js, state, "signal");
381 listener = js_get(js, state, "abortListener");
382
383 if (abort_signal_is_signal(signal) && is_callable(listener))
384 abort_signal_remove_listener(js, signal, listener);
385
386 js_set(js, state, "abortListener", js_mkundef());
387}
388
389static void timers_promises_settle(ant_t *js, ant_value_t state, bool reject, ant_value_t value) {
390 ant_value_t settled = 0;
391 ant_value_t promise = 0;
392
393 if (!is_object_type(state)) return;
394
395 settled = js_get(js, state, "settled");
396 if (js_truthy(js, settled)) return;
397
398 js_set(js, state, "settled", js_true);
399 timers_promises_remove_abort_listener(js, state);
400 js_set(js, state, "handle", js_mkundef());
401
402 promise = js_get(js, state, "promise");
403 if (reject) js_reject_promise(js, promise, value);
404 else js_resolve_promise(js, promise, value);
405}
406
407static ant_value_t timers_promises_resolve(ant_t *js, ant_value_t *args, int nargs) {
408 ant_value_t state = timers_promises_get_state(js);
409 ant_value_t value = js_mkundef();
410 if (!is_object_type(state)) return js_mkundef();
411 value = js_get(js, state, "value");
412 timers_promises_settle(js, state, false, value);
413 return js_mkundef();
414}
415
416static ant_value_t timers_promises_on_abort(ant_t *js, ant_value_t *args, int nargs) {
417 ant_value_t state = timers_promises_get_state(js);
418 ant_value_t signal = 0;
419 ant_value_t handle = 0;
420 ant_value_t is_immediate = 0;
421 ant_value_t reason = js_mkundef();
422 ant_value_t clear_args[1];
423
424 if (!is_object_type(state)) return js_mkundef();
425
426 signal = js_get(js, state, "signal");
427 handle = js_get(js, state, "handle");
428 is_immediate = js_get(js, state, "isImmediate");
429
430 if (vtype(handle) != T_UNDEF && vtype(handle) != T_NULL) {
431 clear_args[0] = handle;
432 if (js_truthy(js, is_immediate)) js_clear_immediate(js, clear_args, 1);
433 else js_clear_timeout(js, clear_args, 1);
434 }
435
436 if (abort_signal_is_signal(signal)) reason = timers_promises_abort_reason(js, signal);
437 else reason = js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted");
438
439 timers_promises_settle(js, state, true, reason);
440 return js_mkundef();
441}
442
443static bool timers_promises_parse_options(
444 ant_t *js,
445 ant_value_t value,
446 ant_value_t *signal_out,
447 ant_value_t *error_out
448) {
449 ant_value_t signal = js_mkundef();
450
451 if (signal_out) *signal_out = js_mkundef();
452 if (error_out) *error_out = js_mkundef();
453
454 if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true;
455 if (vtype(value) != T_OBJ) {
456 if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Timer options must be an object");
457 return false;
458 }
459
460 signal = js_get(js, value, "signal");
461 if (vtype(signal) != T_UNDEF && vtype(signal) != T_NULL && !abort_signal_is_signal(signal)) {
462 if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "options.signal must be an AbortSignal");
463 return false;
464 }
465
466 if (signal_out) *signal_out = signal;
467 return true;
468}
469
470static ant_value_t timers_promises_schedule(
471 ant_t *js,
472 double delay_ms,
473 ant_value_t value,
474 ant_value_t signal,
475 bool is_immediate
476) {
477 ant_value_t promise = js_mkpromise(js);
478 ant_value_t state = js_mkobj(js);
479 ant_value_t callback = 0;
480 ant_value_t handle = 0;
481 ant_value_t args[2];
482
483 if (abort_signal_is_signal(signal) && abort_signal_is_aborted(signal)) {
484 js_reject_promise(js, promise, timers_promises_abort_reason(js, signal));
485 return promise;
486 }
487
488 js_set(js, state, "promise", promise);
489 js_set(js, state, "value", value);
490 js_set(js, state, "signal", signal);
491 js_set(js, state, "abortListener", js_mkundef());
492 js_set(js, state, "handle", js_mkundef());
493 js_set(js, state, "settled", js_false);
494 js_set(js, state, "isImmediate", js_bool(is_immediate));
495
496 callback = js_heavy_mkfun(js, timers_promises_resolve, state);
497 if (is_immediate) handle = js_set_immediate(js, &callback, 1);
498 else {
499 args[0] = callback;
500 args[1] = js_mknum(delay_ms);
501 handle = js_set_timeout(js, args, 2);
502 }
503
504 if (is_err(handle)) {
505 js_reject_promise(js, promise, handle);
506 return promise;
507 }
508
509 js_set(js, state, "handle", handle);
510
511 if (abort_signal_is_signal(signal)) {
512 ant_value_t listener = js_heavy_mkfun(js, timers_promises_on_abort, state);
513 js_set(js, state, "abortListener", listener);
514 abort_signal_add_listener(js, signal, listener);
515 }
516
517 return promise;
518}
519
520static ant_value_t js_timers_promises_setTimeout(ant_t *js, ant_value_t *args, int nargs) {
521 double delay_ms = nargs > 0 ? js_getnum(args[0]) : 0;
522 ant_value_t value = nargs > 1 ? args[1] : js_mkundef();
523 ant_value_t options = nargs > 2 ? args[2] : js_mkundef();
524 ant_value_t signal = js_mkundef();
525 ant_value_t error = js_mkundef();
526
527 if (!timers_promises_parse_options(js, options, &signal, &error)) return error;
528 return timers_promises_schedule(js, delay_ms, value, signal, false);
529}
530
531static ant_value_t js_timers_promises_setImmediate(ant_t *js, ant_value_t *args, int nargs) {
532 ant_value_t value = nargs > 0 ? args[0] : js_mkundef();
533 ant_value_t options = nargs > 1 ? args[1] : js_mkundef();
534 ant_value_t signal = js_mkundef();
535 ant_value_t error = js_mkundef();
536
537 if (!timers_promises_parse_options(js, options, &signal, &error)) return error;
538 return timers_promises_schedule(js, 0, value, signal, true);
539}
540
541static ant_value_t js_timers_promises_setInterval(ant_t *js, ant_value_t *args, int nargs) {
542 return js_mkerr_typed(js, JS_ERR_TYPE, "node:timers/promises setInterval() is not implemented yet");
543}
544
545static ant_value_t js_timers_promises_scheduler_wait(ant_t *js, ant_value_t *args, int nargs) {
546 return js_timers_promises_setTimeout(js, args, nargs);
547}
548
549static ant_value_t js_timers_promises_scheduler_yield(ant_t *js, ant_value_t *args, int nargs) {
550 return js_timers_promises_setImmediate(js, args, nargs);
551}
552
553static void queue_microtask_entry(
554 microtask_entry_t **head,
555 microtask_entry_t **tail,
556 microtask_entry_t *entry
557) {
558 if (*tail == NULL) goto empty;
559
560 (*tail)->next = entry;
561 *tail = entry;
562 return;
563
564empty:
565 *head = entry;
566 *tail = entry;
567}
568
569void queue_microtask(ant_t *js, ant_value_t callback) {
570 microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t));
571 if (entry == NULL) return;
572
573 entry->callback = callback;
574 entry->promise = js_mkundef();
575 entry->next = NULL;
576 entry->argc = 0;
577
578 queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);
579}
580
581void queue_microtask_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) {
582 if (nargs <= 0) { queue_microtask(js, callback); return; }
583
584 microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t));
585 if (entry == NULL) return;
586
587 entry->callback = callback;
588 entry->promise = js_mkundef();
589 entry->next = NULL;
590 entry->argc = (uint8_t)nargs;
591
592 for (int i = 0; i < nargs; i++) entry->argv[i] = args[i];
593 queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);
594}
595
596void queue_next_tick(ant_t *js, ant_value_t callback) {
597 microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t));
598 if (entry == NULL) return;
599
600 entry->callback = callback;
601 entry->promise = js_mkundef();
602 entry->next = NULL;
603 entry->argc = 0;
604
605 queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry);
606}
607
608void queue_next_tick_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) {
609 if (nargs <= 0) { queue_next_tick(js, callback); return; }
610
611 microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t));
612 if (entry == NULL) return;
613
614 entry->callback = callback;
615 entry->promise = js_mkundef();
616 entry->next = NULL;
617 entry->argc = (uint8_t)nargs;
618
619 for (int i = 0; i < nargs; i++) entry->argv[i] = args[i];
620 queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry);
621}
622
623void queue_promise_trigger(ant_t *js, ant_value_t promise) {
624 if (!js_mark_promise_trigger_queued(js, promise)) return;
625
626 microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t));
627 if (entry == NULL) {
628 js_mark_promise_trigger_dequeued(js, promise);
629 return;
630 }
631
632 entry->callback = js_mkundef();
633 entry->promise = promise;
634 entry->next = NULL;
635
636 queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);
637}
638
639static inline void process_microtask_entry(ant_t *js, microtask_entry_t *entry) {
640 if (!entry) return;
641
642 if (vtype(entry->promise) == T_PROMISE) {
643 GC_ROOT_SAVE(root_mark, js);
644 ant_value_t promise = entry->promise;
645
646 GC_ROOT_PIN(js, promise);
647 js_mark_promise_trigger_dequeued(js, promise);
648 js_process_promise_handlers(js, promise);
649 GC_ROOT_RESTORE(js, root_mark);
650
651 return;
652 }
653
654 GC_ROOT_SAVE(root_mark, js);
655 ant_value_t callback = entry->callback;
656 GC_ROOT_PIN(js, callback);
657
658 for (uint8_t i = 0; i < entry->argc; i++) GC_ROOT_PIN(js, entry->argv[i]);
659 sv_vm_call(js->vm, js, callback, js_mkundef(), entry->argv, entry->argc, NULL, false);
660 GC_ROOT_RESTORE(js, root_mark);
661}
662
663static inline microtask_entry_t *take_microtask_batch(void) {
664 microtask_entry_t *batch = timer_state.microtasks;
665
666 timer_state.microtasks = NULL;
667 timer_state.microtasks_tail = NULL;
668 timer_state.microtasks_processing = batch;
669
670 return batch;
671}
672
673static inline microtask_entry_t *take_next_tick_batch(void) {
674 microtask_entry_t *batch = timer_state.next_ticks;
675
676 timer_state.next_ticks = NULL;
677 timer_state.next_ticks_tail = NULL;
678 timer_state.next_ticks_processing = batch;
679
680 return batch;
681}
682
683static inline void process_microtask_batch(ant_t *js, microtask_entry_t *batch) {
684while (batch != NULL) {
685 microtask_entry_t *entry = batch;
686 batch = entry->next;
687 timer_state.microtasks_processing = batch;
688 process_microtask_entry(js, entry);
689 free(entry);
690}}
691
692static inline void process_next_tick_batch(ant_t *js, microtask_entry_t *batch) {
693while (batch != NULL) {
694 microtask_entry_t *entry = batch;
695 batch = entry->next;
696 timer_state.next_ticks_processing = batch;
697 process_microtask_entry(js, entry);
698 free(entry);
699}}
700
701static void process_microtasks_internal(ant_t *js, bool check_unhandled_rejections) {
702 microtask_entry_t *batch = NULL;
703
704 if (!js || js->microtasks_draining) return;
705 js->microtasks_draining = true;
706
707 while (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) {
708 while ((batch = timer_state.next_ticks) != NULL) {
709 batch = take_next_tick_batch();
710 process_next_tick_batch(js, batch);
711 }
712 while ((batch = timer_state.microtasks) != NULL) {
713 batch = take_microtask_batch();
714 process_microtask_batch(js, batch);
715 }}
716
717 timer_state.next_ticks_processing = NULL;
718 timer_state.microtasks_processing = NULL;
719 if (check_unhandled_rejections) js_check_unhandled_rejections(js);
720 js->microtasks_draining = false;
721 reap_retired_coroutines();
722}
723
724void process_microtasks(ant_t *js) {
725 process_microtasks_internal(js, true);
726}
727
728bool js_maybe_drain_microtasks(ant_t *js) {
729 if (!js) return false;
730 if (js->microtasks_draining) return false;
731 if (js->vm_exec_depth != 0) return false;
732 if (!has_pending_microtasks()) return false;
733 process_microtasks_internal(js, true);
734 return true;
735}
736
737bool js_maybe_drain_microtasks_after_async_settle(ant_t *js) {
738 if (!js) return false;
739
740 if (js->microtasks_draining) return false;
741 if (!has_pending_microtasks()) return false;
742
743 process_microtasks_internal(js, false);
744 return true;
745}
746
747void process_immediates(ant_t *js) {
748while (timer_state.immediates != NULL) {
749 immediate_entry_t *entry = timer_state.immediates;
750 timer_state.immediates = entry->next;
751
752 if (timer_state.immediates == NULL) {
753 timer_state.immediates_tail = NULL;
754 }
755
756 if (entry->active) {
757 ant_value_t args[0];
758 sv_vm_call(js->vm, js, entry->callback, js_mkundef(), args, 0, NULL, false);
759 process_microtasks(js);
760 }
761
762 free(entry);
763}}
764
765int has_pending_immediates(void) {
766 for (
767 immediate_entry_t *entry = timer_state.immediates;
768 entry != NULL; entry = entry->next
769 ) if (entry->active) return 1;
770 return 0;
771}
772
773int has_pending_timers(void) {
774 return timer_state.active_timer_count > 0;
775}
776
777int has_pending_microtasks(void) {
778 return (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) ? 1 : 0;
779}
780
781static void timers_define_common(ant_t *js, ant_value_t obj) {
782 js_set(js, obj, "setTimeout", js_mkfun_flags(js_set_timeout, CFUNC_HAS_PROTOTYPE));
783 js_set(js, obj, "clearTimeout", js_mkfun_flags(js_clear_timeout, CFUNC_HAS_PROTOTYPE));
784 js_set(js, obj, "setInterval", js_mkfun_flags(js_set_interval, CFUNC_HAS_PROTOTYPE));
785 js_set(js, obj, "clearInterval", js_mkfun_flags(js_clear_timeout, CFUNC_HAS_PROTOTYPE));
786 js_set(js, obj, "setImmediate", js_mkfun_flags(js_set_immediate, CFUNC_HAS_PROTOTYPE));
787 js_set(js, obj, "clearImmediate", js_mkfun_flags(js_clear_immediate, CFUNC_HAS_PROTOTYPE));
788 js_set(js, obj, "queueMicrotask", js_mkfun(js_queue_microtask));
789}
790
791void init_timer_module() {
792 ant_t *js = rt->js;
793 timer_state.js = js;
794
795 g_timeout_proto = js_mkobj(js);
796 g_interval_proto = js_mkobj(js);
797 gc_register_root(&g_timeout_proto);
798 gc_register_root(&g_interval_proto);
799
800 js_set_proto_init(g_timeout_proto, js->sym.object_proto);
801 js_set(js, g_timeout_proto, "ref", js_mkfun(js_timer_ref));
802 js_set(js, g_timeout_proto, "unref", js_mkfun(js_timer_unref));
803 js_set(js, g_timeout_proto, "hasRef", js_mkfun(js_timer_has_ref));
804 js_set_sym(js, g_timeout_proto, get_toStringTag_sym(), js_mkstr(js, "Timeout", 7));
805 js_set_sym(js, g_timeout_proto, get_inspect_sym(), js_mkfun(timer_inspect));
806
807 js_set_proto_init(g_interval_proto, js->sym.object_proto);
808 js_set(js, g_interval_proto, "ref", js_mkfun(js_timer_ref));
809 js_set(js, g_interval_proto, "unref", js_mkfun(js_timer_unref));
810 js_set(js, g_interval_proto, "hasRef", js_mkfun(js_timer_has_ref));
811 js_set_sym(js, g_interval_proto, get_toStringTag_sym(), js_mkstr(js, "Interval", 8));
812 js_set_sym(js, g_interval_proto, get_inspect_sym(), js_mkfun(timer_inspect));
813
814 timers_define_common(js, js_glob(js));
815}
816
817ant_value_t timers_library(ant_t *js) {
818 ant_value_t lib = js_mkobj(js);
819
820 timers_define_common(js, lib);
821 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "timers", 6));
822
823 return lib;
824}
825
826// TODO: mostly stubbed
827ant_value_t timers_promises_library(ant_t *js) {
828 ant_value_t lib = js_mkobj(js);
829 ant_value_t scheduler = js_mkobj(js);
830
831 js_set(js, lib, "scheduler", scheduler);
832 js_set(js, lib, "setTimeout", js_mkfun(js_timers_promises_setTimeout));
833 js_set(js, lib, "setImmediate", js_mkfun(js_timers_promises_setImmediate));
834 js_set(js, lib, "setInterval", js_mkfun(js_timers_promises_setInterval));
835 js_set(js, scheduler, "wait", js_mkfun(js_timers_promises_scheduler_wait));
836 js_set(js, scheduler, "yield", js_mkfun(js_timers_promises_scheduler_yield));
837 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "timers/promises", 16));
838
839 return lib;
840}
841
842void gc_mark_timers(ant_t *js, gc_mark_fn mark) {
843 if (is_object_type(g_timeout_proto)) mark(js, g_timeout_proto);
844 if (is_object_type(g_interval_proto)) mark(js, g_interval_proto);
845 for (timer_entry_t *t = timer_state.timers; t; t = t->next) {
846 mark(js, t->callback);
847 for (int i = 0; i < t->nargs; i++) mark(js, t->args[i]);
848 }
849 for (microtask_entry_t *m = timer_state.microtasks; m; m = m->next) {
850 mark(js, m->callback);
851 mark(js, m->promise);
852 for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
853 }
854 for (microtask_entry_t *m = timer_state.microtasks_processing; m; m = m->next) {
855 mark(js, m->callback);
856 mark(js, m->promise);
857 for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
858 }
859 for (microtask_entry_t *m = timer_state.next_ticks; m; m = m->next) {
860 mark(js, m->callback);
861 mark(js, m->promise);
862 for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
863 }
864 for (microtask_entry_t *m = timer_state.next_ticks_processing; m; m = m->next) {
865 mark(js, m->callback);
866 mark(js, m->promise);
867 for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
868 }
869 for (immediate_entry_t *i = timer_state.immediates; i; i = i->next) {
870 mark(js, i->callback);
871 }
872}