#include // IWYU pragma: keep #include #include #include #include #include #include "errors.h" #include "cfunc.h" #include "builder.h" #include "ptr.h" #include "internal.h" #include "silver/call.h" #include "gc/uv.h" #include "gc/weak.h" #include "gc/roots.h" #include "gc/modules.h" #include "modules/abort.h" #include "loop.h" #include "modules/timer.h" #include "modules/process.h" #include "modules/generator.h" typedef struct timer_entry { struct timer_entry *next; ant_value_t obj; ant_value_t callback; ant_value_t *args; int nargs; int active; bool young; struct timer_entry *prev; int timer_id; int closed; int is_interval; bool handle_closed; bool detached; uint64_t timeout_ms; uv_timer_t handle; } timer_entry_t; enum { MT_CALLBACK = 0, MT_PROMISE_TRIGGER, MT_THENABLE_JOB, MT_AWAIT_RESUME, MT_GENERATOR_YIELD, MT_PROMISE_RESOLVE, MT_AWAIT_RESUME_THROW, MT_AWAIT_DEFER, }; static inline bool microtask_holds_coro(uint8_t kind) { return kind == MT_AWAIT_RESUME || kind == MT_AWAIT_RESUME_THROW || kind == MT_AWAIT_DEFER; } typedef struct microtask_entry { ant_value_t callback; union { ant_value_t promise; ant_value_t this_val; coroutine_t *coro; } u; struct microtask_entry *next; uint8_t argc; uint8_t kind; ant_value_t argv[]; } microtask_entry_t; typedef struct immediate_entry { ant_value_t callback; int immediate_id; int active; bool refed; ant_value_t handle; struct immediate_entry *next; int argc; ant_value_t argv[]; } immediate_entry_t; static void add_timer_entry(ant_t *js, timer_entry_t *entry) { entry->young = true; entry->next = js->timers.timers; entry->prev = NULL; if (js->timers.timers) js->timers.timers->prev = entry; js->timers.timers = entry; } static void remove_timer_entry(ant_t *js, timer_entry_t *entry) { if (entry->prev) entry->prev->next = entry->next; else js->timers.timers = entry->next; if (entry->next) entry->next->prev = entry->prev; entry->next = NULL; entry->prev = NULL; } static void timer_entry_hold_young(ant_t *js, timer_entry_t *entry) { if (entry->young) return; remove_timer_entry(js, entry); add_timer_entry(js, entry); } static timer_entry_t *find_timer_entry_by_id(ant_t *js, int timer_id) { for (timer_entry_t *entry = js->timers.timers; entry != NULL; entry = entry->next) if (entry->timer_id == timer_id) return entry; return NULL; } static int timer_copy_args(timer_entry_t *entry, ant_value_t *args, int nargs) { entry->nargs = nargs > 2 ? nargs - 2 : 0; if (entry->nargs > 0) { entry->args = calloc(1, sizeof(ant_value_t) * entry->nargs); if (!entry->args) return -1; memcpy(entry->args, args + 2, sizeof(ant_value_t) * entry->nargs); } else entry->args = NULL; return 0; } static void timer_release_args(timer_entry_t *entry) { if (!entry) return; if (entry->args) { free(entry->args); entry->args = NULL; } entry->nargs = 0; } static void timer_release_callback_args(timer_entry_t *entry) { if (!entry) return; entry->callback = js_mkundef(); timer_release_args(entry); } static int timer_copy_args_from_object(ant_t *js, timer_entry_t *entry, ant_value_t obj) { ant_value_t args_arr = js_get_slot(obj, SLOT_AUX); ant_offset_t len = vtype(args_arr) == kTypeArray ? js_arr_len(js, args_arr) : 0; timer_release_args(entry); if (len == 0) return 0; entry->args = calloc(1, sizeof(ant_value_t) * (size_t)len); if (!entry->args) return -1; entry->nargs = (int)len; for (ant_offset_t i = 0; i < len; i++) entry->args[i] = js_arr_get(js, args_arr, i); return 0; } static ant_value_t timer_make_args_array(ant_native_params_t) { ant_value_t arr = js_mkundef(); int arg_count = nargs > 2 ? nargs - 2 : 0; if (arg_count <= 0) return arr; arr = js_mkarr(js); for (int i = 0; i < arg_count; i++) js_arr_push(js, arr, args[i + 2]); return arr; } static constexpr uint32_t TIMER_NATIVE_TAG = Ant_NativeTag("TIMR"); static constexpr uint32_t IMMEDIATE_NATIVE_TAG = Ant_NativeTag("IMMD"); static timer_entry_t *timer_entry_of(ant_value_t handle) { return is_object_type(handle) ? js_get_native(handle, TIMER_NATIVE_TAG) : NULL; } static timer_entry_t *timer_open_entry_of(ant_value_t handle) { timer_entry_t *entry = timer_entry_of(handle); return entry && !entry->closed ? entry : NULL; } static ant_value_t timer_to_primitive(ant_params_t) { timer_entry_t *entry = timer_entry_of(js_getthis(js)); return entry ? js_mknum((double)entry->timer_id) : js_mkundef(); } static immediate_entry_t *immediate_entry_of(ant_value_t handle) { return is_object_type(handle) ? js_get_native(handle, IMMEDIATE_NATIVE_TAG) : NULL; } static ant_value_t timer_inspect(ant_params_t) { ant_value_t this_obj = js_getthis(js); timer_entry_t *entry = timer_entry_of(this_obj); immediate_entry_t *immediate = immediate_entry_of(this_obj); ant_value_t refed = js_mknull(); bool destroyed = true; if (entry) { refed = js_bool(uv_has_ref((const uv_handle_t *)&entry->handle)); destroyed = entry->closed || !entry->active; } else if (immediate && immediate->active) { refed = js_bool(immediate->refed); destroyed = false; } ant_value_t tag_val = js_get_sym(js, this_obj, js->sym.toStringTag_sym); const char *tag = vtype(tag_val) == kTypeString ? js_getstr(js, tag_val, NULL) : "Timeout"; js_inspect_builder_t builder; if (!js_inspect_builder_init_dynamic(&builder, js, 128)) return js_mkerr(js, "out of memory"); bool ok = entry ? js_inspect_header(&builder, "%s (%d)", tag, entry->timer_id) : js_inspect_header(&builder, "%s", tag); if (ok) ok = js_inspect_object_body(&builder, this_obj); if (ok) ok = js_inspect_field(&builder, "Symbol(refed)", refed); if (ok) ok = js_inspect_field(&builder, "Symbol(destroyed)", js_bool(destroyed)); if (ok) ok = js_inspect_close(&builder); if (!ok) { js_inspect_builder_dispose(&builder); return js_mkerr(js, "out of memory"); } return js_inspect_builder_result(&builder); } static ant_value_t js_timer_ref(ant_params_t) { ant_value_t this_obj = js_getthis(js); timer_entry_t *entry = timer_open_entry_of(this_obj); if (entry) { int was_refed = uv_has_ref((const uv_handle_t *)&entry->handle); uv_ref((uv_handle_t *)&entry->handle); if (entry->active && !was_refed) js->timers.active_refed_timer_count++; } return this_obj; } static ant_value_t js_timer_unref(ant_params_t) { ant_value_t this_obj = js_getthis(js); timer_entry_t *entry = timer_open_entry_of(this_obj); if (entry) { int was_refed = uv_has_ref((const uv_handle_t *)&entry->handle); uv_unref((uv_handle_t *)&entry->handle); if (entry->active && was_refed) js->timers.active_refed_timer_count--; } return this_obj; } static ant_value_t js_timer_has_ref(ant_params_t) { timer_entry_t *entry = timer_open_entry_of(js_getthis(js)); if (!entry) return js_false; return js_bool(uv_has_ref((const uv_handle_t *)&entry->handle) != 0); } static void immediate_detach_handle(immediate_entry_t *entry) { if (is_object_type(entry->handle)) js_clear_native(entry->handle, IMMEDIATE_NATIVE_TAG); entry->handle = js_mkundef(); } static ant_value_t js_immediate_ref(ant_params_t) { immediate_entry_t *entry = immediate_entry_of(js_getthis(js)); if (entry) entry->refed = true; return js_getthis(js); } static ant_value_t js_immediate_unref(ant_params_t) { immediate_entry_t *entry = immediate_entry_of(js_getthis(js)); if (entry) entry->refed = false; return js_getthis(js); } static ant_value_t js_immediate_has_ref(ant_params_t) { immediate_entry_t *entry = immediate_entry_of(js_getthis(js)); return js_bool(entry && entry->active && entry->refed); } static ant_value_t js_immediate_dispose(ant_params_t) { immediate_entry_t *entry = immediate_entry_of(js_getthis(js)); if (entry) entry->active = 0; return js_mkundef(); } static ant_value_t immediate_make_object(ant_t *js, immediate_entry_t *entry) { ant_value_t obj = js_mkobj_from_template(js, js->builtins.immediate_template); if (is_err(obj)) return obj; ant_object_t *ptr = js_obj_ptr(obj); ant_object_prop_set_unchecked(ptr, 0, entry->callback); gc_write_barrier(js, ptr, entry->callback); js_set_native(obj, entry, IMMEDIATE_NATIVE_TAG); entry->handle = obj; return obj; } // uv_close completes on a later loop turn, possibly after the isolate is gone, // so the entry leaves its isolate's list when it is closed, not here static void timer_close_cb(uv_handle_t *h) { timer_entry_t *entry = (timer_entry_t *)h->data; if (!entry) return; timer_release_callback_args(entry); entry->handle_closed = true; if (entry->detached) free(entry); } static void timer_close_entry(ant_t *js, timer_entry_t *entry) { if (!entry || entry->closed) return; if (entry->active) { entry->active = 0; js->timers.active_timer_count--; if (uv_has_ref((const uv_handle_t *)&entry->handle)) js->timers.active_refed_timer_count--; } remove_timer_entry(js, entry); entry->closed = 1; entry->obj = js_mkundef(); if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close((uv_handle_t *)&entry->handle, timer_close_cb); } // the entry outlives its uv handle while the timer object is alive, and is // freed by whichever of uv_close and the object's finalizer finishes last static void timer_object_finalize(ant_t *js, ant_object_t *obj) { timer_entry_t *entry = timer_entry_of(js_obj_from_ptr(obj)); if (!entry) return; entry->detached = true; if (!entry->closed) timer_close_entry(js, entry); else if (entry->handle_closed) free(entry); } static ant_value_t timer_make_object(ant_t *js, timer_entry_t *entry, double delay_ms, ant_value_t timer_args) { ant_value_t obj = js_mkobj_from_template(js, entry->is_interval ? js->builtins.interval_template : js->builtins.timeout_template ); if (is_err(obj)) { entry->detached = true; timer_close_entry(js, entry); return obj; } ant_object_t *ptr = js_obj_ptr(obj); ant_value_t delay = js_mknum(delay_ms); ant_object_prop_set_unchecked(ptr, 0, delay); ant_object_prop_set_unchecked(ptr, 1, entry->is_interval ? delay : js_mknull()); ant_object_prop_set_unchecked(ptr, 2, entry->callback); gc_write_barrier(js, ptr, entry->callback); if (vtype(timer_args) != kTypeUndefined) js_set_slot_wb(js, obj, SLOT_AUX, timer_args); js_set_native(obj, entry, TIMER_NATIVE_TAG); js_set_finalizer(obj, timer_object_finalize); entry->obj = obj; timer_entry_hold_young(js, entry); return obj; } static void timer_callback(uv_timer_t *handle) { GC_UV_CALLBACK(); timer_entry_t *entry = (timer_entry_t *)handle->data; if (!entry || entry->closed || !entry->active) return; ant_t *js = Ant_Loop_Isolate(handle->loop); ant_value_t callback = entry->callback; if (!entry->is_interval) { entry->active = 0; js->timers.active_timer_count--; if (uv_has_ref((const uv_handle_t *)&entry->handle)) js->timers.active_refed_timer_count--; } ant_value_t self = entry->obj; GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, callback); GC_ROOT_PIN(js, self); for (int i = 0; i < entry->nargs; i++) GC_ROOT_PIN(js, entry->args[i]); sv_vm_call(js->vm, js, callback, self, entry->args, entry->nargs, NULL, js_mkundef()); GC_ROOT_RESTORE(js, root_mark); if (!entry->is_interval && !entry->active) timer_release_callback_args(entry); process_report_uncaught_exception_if_pending(js); process_microtasks(js); } static void timer_start(ant_t *js, timer_entry_t *entry, uint64_t timeout, uint64_t repeat) { uv_update_time(js->loop); uv_timer_start(&entry->handle, timer_callback, timeout, repeat); } static ant_value_t js_timer_refresh(ant_params_t) { ant_value_t this_obj = js_getthis(js); timer_entry_t *entry = timer_open_entry_of(this_obj); if (!entry) return this_obj; if (!entry->active) { if (vtype(entry->callback) == kTypeUndefined) { entry->callback = js_get(js, this_obj, "callback"); if (!is_callable(entry->callback)) return this_obj; if (timer_copy_args_from_object(js, entry, this_obj) != 0) return js_mkerr(js, "failed to allocate timer args"); timer_entry_hold_young(js, entry); } entry->active = 1; js->timers.active_timer_count++; if (uv_has_ref((const uv_handle_t *)&entry->handle)) js->timers.active_refed_timer_count++; } timer_start(js, entry, entry->timeout_ms, entry->is_interval ? entry->timeout_ms : 0); return this_obj; } // setTimeout(callback, delay, ...args) static ant_value_t js_set_timeout(ant_params_t) { if (nargs < 1) return js_mkerr(js, "setTimeout requires at least 1 argument (callback)"); ant_value_t callback = args[0]; double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0; uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 0; ant_value_t timer_args = timer_make_args_array(js, args, nargs); timer_entry_t *entry = calloc(1, sizeof(timer_entry_t)); if (entry == NULL) return js_mkerr(js, "failed to allocate timer"); if (timer_copy_args(entry, args, nargs) < 0) { free(entry); return js_mkerr(js, "failed to allocate timer args"); } uv_timer_init(js->loop, &entry->handle); entry->handle.data = entry; entry->callback = callback; entry->timer_id = js->timers.next_timer_id++; entry->active = 1; entry->closed = 0; entry->is_interval = 0; entry->timeout_ms = ms; add_timer_entry(js, entry); js->timers.active_timer_count++; js->timers.active_refed_timer_count++; timer_start(js, entry, ms, 0); return timer_make_object(js, entry, delay_ms, timer_args); } // setInterval(callback, delay, ...args) static ant_value_t js_set_interval(ant_params_t) { if (nargs < 1) return js_mkerr(js, "setInterval requires at least 1 argument (callback)"); ant_value_t callback = args[0]; double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0; uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 1; ant_value_t timer_args = timer_make_args_array(js, args, nargs); timer_entry_t *entry = calloc(1, sizeof(timer_entry_t)); if (entry == NULL) return js_mkerr(js, "failed to allocate timer"); if (timer_copy_args(entry, args, nargs) < 0) { free(entry); return js_mkerr(js, "failed to allocate timer args"); } uv_timer_init(js->loop, &entry->handle); entry->handle.data = entry; entry->callback = callback; entry->timer_id = js->timers.next_timer_id++; entry->active = 1; entry->closed = 0; entry->is_interval = 1; entry->timeout_ms = ms; add_timer_entry(js, entry); js->timers.active_timer_count++; js->timers.active_refed_timer_count++; timer_start(js, entry, ms, ms); return timer_make_object(js, entry, delay_ms, timer_args); } // clearTimeout(timerId | timerObject) static ant_value_t js_clear_timeout(ant_params_t) { if (nargs < 1) return js_mkundef(); timer_entry_t *entry = timer_open_entry_of(args[0]); if (!entry && vtype(args[0]) == kTypeNumber) entry = find_timer_entry_by_id(js, (int)js_getnum(args[0])); if (entry) timer_close_entry(js, entry); return js_mkundef(); } // setImmediate(callback, ...args) static ant_value_t js_set_immediate(ant_params_t) { if (nargs < 1) { return js_mkerr(js, "setImmediate requires 1 argument (callback)"); } ant_value_t callback = args[0]; int argc = nargs - 1; immediate_entry_t *entry = calloc(1, sizeof(immediate_entry_t) + (size_t)argc * sizeof(ant_value_t)); if (entry == NULL) { return js_mkerr(js, "failed to allocate immediate"); } entry->callback = callback; entry->argc = argc; for (int i = 0; i < argc; i++) entry->argv[i] = args[i + 1]; entry->immediate_id = js->timers.next_immediate_id++; entry->active = 1; entry->refed = true; entry->handle = js_mkundef(); entry->next = NULL; if (js->timers.immediates_tail == NULL) { js->timers.immediates = entry; js->timers.immediates_tail = entry; } else { js->timers.immediates_tail->next = entry; js->timers.immediates_tail = entry; } return immediate_make_object(js, entry); } // clearImmediate(immediateId | immediateObject) static ant_value_t js_clear_immediate(ant_params_t) { if (nargs < 1) return js_mkundef(); immediate_entry_t *entry = immediate_entry_of(args[0]); if (entry) entry->active = 0; else if (vtype(args[0]) == kTypeNumber) { int immediate_id = (int)js_getnum(args[0]); immediate_entry_t *lists[2] = { js->timers.immediates_processing, js->timers.immediates }; for (int l = 0; l < 2 && !entry; l++) for (immediate_entry_t *it = lists[l]; it; it = it->next) if (it->immediate_id == immediate_id) { entry = it; break; } if (entry) entry->active = 0; } return js_mkundef(); } // queueMicrotask(callback) static ant_value_t js_queue_microtask(ant_params_t) { if (nargs < 1) { return js_mkerr(js, "queueMicrotask requires 1 argument (callback)"); } queue_microtask(js, args[0]); return js_mkundef(); } static ant_value_t timers_promises_get_state(ant_t *js) { return js_get_slot(js->current_func, SLOT_DATA); } static void timers_promises_remove_abort_listener(ant_t *js, ant_value_t state) { ant_value_t signal = 0; ant_value_t listener = 0; if (!is_object_type(state)) return; signal = js_get(js, state, "signal"); listener = js_get(js, state, "abortListener"); if (abort_signal_is_signal(signal) && is_callable(listener)) abort_signal_remove_listener(js, signal, listener); js_set(js, state, "abortListener", js_mkundef()); } static void timers_promises_settle(ant_t *js, ant_value_t state, bool reject, ant_value_t value) { ant_value_t settled = 0; ant_value_t promise = 0; if (!is_object_type(state)) return; settled = js_get(js, state, "settled"); if (js_truthy(js, settled)) return; js_set(js, state, "settled", js_true); timers_promises_remove_abort_listener(js, state); js_set(js, state, "handle", js_mkundef()); promise = js_get(js, state, "promise"); if (reject) js_reject_promise(js, promise, value); else js_resolve_promise(js, promise, value); } static ant_value_t timers_promises_resolve(ant_params_t) { ant_value_t state = timers_promises_get_state(js); ant_value_t value = js_mkundef(); if (!is_object_type(state)) return js_mkundef(); value = js_get(js, state, "value"); timers_promises_settle(js, state, false, value); return js_mkundef(); } static ant_value_t timers_promises_on_abort(ant_params_t) { ant_value_t state = timers_promises_get_state(js); ant_value_t signal = 0; ant_value_t handle = 0; ant_value_t is_immediate = 0; ant_value_t reason = js_mkundef(); ant_value_t clear_args[1]; if (!is_object_type(state)) return js_mkundef(); signal = js_get(js, state, "signal"); handle = js_get(js, state, "handle"); is_immediate = js_get(js, state, "isImmediate"); if (vtype(handle) != kTypeUndefined && vtype(handle) != kTypeNull) { clear_args[0] = handle; if (js_truthy(js, is_immediate)) js_clear_immediate(js, clear_args, 1, js_mkundef()); else js_clear_timeout(js, clear_args, 1, js_mkundef()); } if (abort_signal_is_signal(signal)) reason = abort_signal_make_error(js, signal); else reason = Ant_Error_Create(js, JS_ERR_TYPE, "The operation was aborted"); GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, reason); timers_promises_settle(js, state, true, reason); GC_ROOT_RESTORE(js, root_mark); return js_mkundef(); } static bool timers_promises_parse_options( ant_t *js, ant_value_t value, ant_value_t *signal_out, ant_value_t *error_out ) { ant_value_t signal = js_mkundef(); if (signal_out) *signal_out = js_mkundef(); if (error_out) *error_out = js_mkundef(); if (vtype(value) == kTypeUndefined || vtype(value) == kTypeNull) return true; if (vtype(value) != kTypeObject) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Timer options must be an object"); return false; } signal = js_get(js, value, "signal"); if (is_err(signal)) { if (error_out) *error_out = signal; return false; } if (vtype(signal) != kTypeUndefined && vtype(signal) != kTypeNull && !abort_signal_is_signal(signal)) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "options.signal must be an AbortSignal"); return false; } if (signal_out) *signal_out = signal; return true; } static ant_value_t timers_promises_schedule( ant_t *js, double delay_ms, ant_value_t value, ant_value_t signal, bool is_immediate ) { ant_value_t promise = js_mkpromise(js); ant_value_t state = Ant_Object_CreateBare(js); ant_value_t callback = 0; ant_value_t handle = 0; ant_value_t args[2]; if (abort_signal_is_signal(signal) && abort_signal_is_aborted(signal)) { js_reject_promise(js, promise, abort_signal_make_error(js, signal)); return promise; } js_set(js, state, "promise", promise); js_set(js, state, "value", value); js_set(js, state, "signal", signal); js_set(js, state, "abortListener", js_mkundef()); js_set(js, state, "handle", js_mkundef()); js_set(js, state, "settled", js_false); js_set(js, state, "isImmediate", js_bool(is_immediate)); callback = js_heavy_mkfun(js, timers_promises_resolve, state); if (is_immediate) handle = js_set_immediate(js, &callback, 1, js_mkundef()); else { args[0] = callback; args[1] = js_mknum(delay_ms); handle = js_set_timeout(js, args, 2, js_mkundef()); } if (is_err(handle)) { js_reject_promise(js, promise, handle); return promise; } js_set(js, state, "handle", handle); if (abort_signal_is_signal(signal)) { ant_value_t listener = js_heavy_mkfun(js, timers_promises_on_abort, state); js_set(js, state, "abortListener", listener); abort_signal_add_listener(js, signal, listener); } return promise; } static ant_value_t js_timers_promises_setTimeout(ant_params_t) { double delay_ms = nargs > 0 ? js_getnum(args[0]) : 0; ant_value_t value = nargs > 1 ? args[1] : js_mkundef(); ant_value_t options = nargs > 2 ? args[2] : js_mkundef(); ant_value_t signal = js_mkundef(); ant_value_t error = js_mkundef(); if (!timers_promises_parse_options(js, options, &signal, &error)) return error; return timers_promises_schedule(js, delay_ms, value, signal, false); } static ant_value_t js_timers_promises_setImmediate(ant_params_t) { ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t options = nargs > 1 ? args[1] : js_mkundef(); ant_value_t signal = js_mkundef(); ant_value_t error = js_mkundef(); if (!timers_promises_parse_options(js, options, &signal, &error)) return error; return timers_promises_schedule(js, 0, value, signal, true); } static ant_value_t js_timers_promises_setInterval(ant_params_t) { return js_mkerr_typed(js, JS_ERR_TYPE, "node:timers/promises setInterval() is not implemented yet"); } static ant_value_t js_timers_promises_scheduler_wait(ant_params_t) { return js_timers_promises_setTimeout(js, args, nargs, js_mkundef()); } static ant_value_t js_timers_promises_scheduler_yield(ant_params_t) { return js_timers_promises_setImmediate(js, args, nargs, js_mkundef()); } static constexpr int MICROTASK_POOLED_ARGS = 1; static constexpr uint32_t MICROTASK_FREE_JOBS_MAX = 256; static constexpr size_t MICROTASK_POOLED_BYTES = sizeof(microtask_entry_t) + MICROTASK_POOLED_ARGS * sizeof(ant_value_t); static microtask_entry_t *microtask_entry_new(ant_t *js, int nargs) { if (nargs > MICROTASK_POOLED_ARGS) return calloc(1, sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t)); microtask_entry_t *entry = js->timers.free_jobs; if (!entry) return calloc(1, MICROTASK_POOLED_BYTES); js->timers.free_jobs = entry->next; js->timers.free_job_count--; memset(entry, 0, MICROTASK_POOLED_BYTES); return entry; } static void microtask_entry_free(ant_t *js, microtask_entry_t *entry) { if (entry->argc > MICROTASK_POOLED_ARGS || js->timers.free_job_count >= MICROTASK_FREE_JOBS_MAX) { free(entry); return; } entry->next = js->timers.free_jobs; js->timers.free_jobs = entry; js->timers.free_job_count++; } static void queue_microtask_entry( microtask_entry_t **head, microtask_entry_t **tail, microtask_entry_t *entry ) { if (*tail == NULL) goto empty; (*tail)->next = entry; *tail = entry; return; empty: *head = entry; *tail = entry; } static inline void queue_job(ant_t *js, microtask_entry_t *entry) { ant_realm_t *own = js->microtask_realm; if (__builtin_expect(own != NULL, 0)) queue_microtask_entry(&own->microtasks, &own->microtasks_tail, entry); else queue_microtask_entry(&js->timers.microtasks, &js->timers.microtasks_tail, entry); } void queue_microtask(ant_t *js, ant_value_t callback) { microtask_entry_t *entry = microtask_entry_new(js, 0); if (entry == NULL) return; entry->callback = callback; entry->u.promise = js_mkundef(); entry->next = NULL; entry->argc = 0; queue_job(js, entry); } void queue_microtask_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) { if (nargs <= 0) { queue_microtask(js, callback); return; } microtask_entry_t *entry = microtask_entry_new(js, nargs); if (entry == NULL) return; entry->callback = callback; entry->u.promise = js_mkundef(); entry->next = NULL; entry->argc = (uint8_t)nargs; for (int i = 0; i < nargs; i++) entry->argv[i] = args[i]; queue_job(js, entry); } bool queue_promise_thenable_job(ant_t *js, ant_value_t promise, ant_value_t thenable, ant_value_t then_fn) { microtask_entry_t *entry = microtask_entry_new(js, 1); if (entry == NULL) return false; entry->callback = then_fn; entry->u.this_val = thenable; entry->next = NULL; entry->argc = 1; entry->kind = MT_THENABLE_JOB; entry->argv[0] = promise; queue_job(js, entry); return true; } bool queue_generator_yield_job(ant_t *js, ant_value_t gen, ant_value_t value) { microtask_entry_t *entry = microtask_entry_new(js, 0); if (entry == NULL) return false; entry->callback = value; entry->u.this_val = gen; entry->kind = MT_GENERATOR_YIELD; queue_microtask_entry(&js->timers.microtasks, &js->timers.microtasks_tail, entry); return true; } bool queue_promise_resolve_job(ant_t *js, ant_value_t promise, ant_value_t value) { microtask_entry_t *entry = microtask_entry_new(js, 0); if (entry == NULL) return false; entry->callback = value; entry->u.promise = promise; entry->kind = MT_PROMISE_RESOLVE; queue_job(js, entry); return true; } static bool queue_coro_job(coroutine_t *coro, ant_value_t value, uint8_t kind) { ant_t *js = coro->js; microtask_entry_t *entry = microtask_entry_new(js, 0); if (entry == NULL) return false; entry->callback = value; entry->u.coro = coro; entry->next = NULL; entry->kind = kind; coroutine_retain(coro); coro->await_resume_job = entry; queue_job(js, entry); return true; } bool queue_await_resume_job(coroutine_t *coro, ant_value_t value) { return queue_coro_job(coro, value, MT_AWAIT_RESUME); } bool queue_await_settle_job(coroutine_t *coro, ant_value_t value, bool is_error) { return queue_coro_job(coro, value, is_error ? MT_AWAIT_RESUME_THROW : MT_AWAIT_RESUME); } bool queue_await_defer_job(coroutine_t *coro, ant_value_t value) { return queue_coro_job(coro, value, MT_AWAIT_DEFER); } void queue_next_tick(ant_t *js, ant_value_t callback) { microtask_entry_t *entry = microtask_entry_new(js, 0); if (entry == NULL) return; entry->callback = callback; entry->u.promise = js_mkundef(); entry->next = NULL; entry->argc = 0; queue_microtask_entry(&js->timers.next_ticks, &js->timers.next_ticks_tail, entry); } void queue_next_tick_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) { if (nargs <= 0) { queue_next_tick(js, callback); return; } microtask_entry_t *entry = microtask_entry_new(js, nargs); if (entry == NULL) return; entry->callback = callback; entry->u.promise = js_mkundef(); entry->next = NULL; entry->argc = (uint8_t)nargs; for (int i = 0; i < nargs; i++) entry->argv[i] = args[i]; queue_microtask_entry(&js->timers.next_ticks, &js->timers.next_ticks_tail, entry); } void queue_promise_trigger(ant_t *js, ant_value_t promise) { if (!js_mark_promise_trigger_queued(js, promise)) return; microtask_entry_t *entry = microtask_entry_new(js, 0); if (entry == NULL) { js_mark_promise_trigger_dequeued(js, promise); return; } entry->callback = js_mkundef(); entry->u.promise = promise; entry->next = NULL; entry->kind = MT_PROMISE_TRIGGER; queue_job(js, entry); } static inline void process_microtask_entry(ant_t *js, microtask_entry_t *entry) { if (!entry) return; if (entry->kind == MT_PROMISE_TRIGGER) { GC_ROOT_SAVE(root_mark, js); ant_value_t promise = entry->u.promise; GC_ROOT_PIN(js, promise); js_mark_promise_trigger_dequeued(js, promise); js_process_promise_handlers(js, promise); GC_ROOT_RESTORE(js, root_mark); return; } if (entry->kind == MT_GENERATOR_YIELD) { Ant_Generator_SettleYield(js, entry->u.this_val, entry->callback); return; } if (entry->kind == MT_THENABLE_JOB) { js_process_promise_thenable_job( js, entry->argv[0], entry->u.this_val, entry->callback ); return; } if (entry->kind == MT_PROMISE_RESOLVE) { GC_ROOT_SAVE(root_mark, js); ant_value_t promise = entry->u.promise; ant_value_t value = entry->callback; GC_ROOT_PIN(js, promise); GC_ROOT_PIN(js, value); js_resolve_promise(js, promise, value); GC_ROOT_RESTORE(js, root_mark); return; } if (entry->kind == MT_AWAIT_DEFER) { GC_ROOT_SAVE(root_mark, js); ant_value_t value = entry->callback; coroutine_t *coro = entry->u.coro; GC_ROOT_PIN(js, value); if ( coro->await_registered && coro->await_resume_job == entry && !queue_await_resume_job(coro, value) ) Ant_Coroutine_ResumeAwaitJob(js, coro, value, false); coroutine_release(coro); GC_ROOT_RESTORE(js, root_mark); return; } if (entry->kind == MT_AWAIT_RESUME || entry->kind == MT_AWAIT_RESUME_THROW) { GC_ROOT_SAVE(root_mark, js); ant_value_t value = entry->callback; coroutine_t *coro = entry->u.coro; GC_ROOT_PIN(js, value); if (coro->await_registered && coro->await_resume_job == entry) Ant_Coroutine_ResumeAwaitJob(js, coro, value, entry->kind == MT_AWAIT_RESUME_THROW); coroutine_release(coro); GC_ROOT_RESTORE(js, root_mark); return; } ant_value_t this_val = js_mkundef(); GC_ROOT_SAVE(root_mark, js); ant_value_t callback = entry->callback; GC_ROOT_PIN(js, callback); GC_ROOT_PIN(js, this_val); for (uint8_t i = 0; i < entry->argc; i++) GC_ROOT_PIN(js, entry->argv[i]); sv_vm_call(js->vm, js, callback, this_val, entry->argv, entry->argc, NULL, js_mkundef()); GC_ROOT_RESTORE(js, root_mark); process_report_uncaught_exception_if_pending(js); } static inline microtask_entry_t *take_microtask_batch(ant_t *js) { microtask_entry_t *batch = js->timers.microtasks; js->timers.microtasks = NULL; js->timers.microtasks_tail = NULL; js->timers.microtasks_processing = batch; return batch; } static inline microtask_entry_t *take_next_tick_batch(ant_t *js) { microtask_entry_t *batch = js->timers.next_ticks; js->timers.next_ticks = NULL; js->timers.next_ticks_tail = NULL; js->timers.next_ticks_processing = batch; return batch; } static inline void process_microtask_batch(ant_t *js, microtask_entry_t *batch) { while (batch != NULL) { microtask_entry_t *entry = batch; batch = entry->next; js->timers.microtasks_processing = batch; process_microtask_entry(js, entry); microtask_entry_free(js, entry); } } static inline void process_next_tick_batch(ant_t *js, microtask_entry_t *batch) { while (batch != NULL) { microtask_entry_t *entry = batch; batch = entry->next; js->timers.next_ticks_processing = batch; process_microtask_entry(js, entry); microtask_entry_free(js, entry); } } static void process_microtasks_internal(ant_t *js, bool check_unhandled_rejections) { microtask_entry_t *batch = NULL; if (!js || js->microtasks_draining || js->destroying) return; bool at_job_boundary = js->vm_exec_depth == 0; js->microtasks_draining = true; while (js->timers.next_ticks != NULL || js->timers.microtasks != NULL) { while ((batch = js->timers.next_ticks) != NULL) { batch = take_next_tick_batch(js); process_next_tick_batch(js, batch); } while ((batch = js->timers.microtasks) != NULL) { batch = take_microtask_batch(js); process_microtask_batch(js, batch); } } js->timers.next_ticks_processing = NULL; js->timers.microtasks_processing = NULL; if (check_unhandled_rejections) js_check_unhandled_rejections(js); js->microtasks_draining = false; if (at_job_boundary) gc_weak_clear_kept_alive(js); } void ant_realm_run_microtasks(ant_t *js, ant_realm_t *realm) { while (realm->microtasks) { microtask_entry_t *batch = realm->microtasks; realm->microtasks = realm->microtasks_tail = NULL; while (batch) { microtask_entry_t *entry = batch; batch = entry->next; realm->microtasks_processing = batch; process_microtask_entry(js, entry); microtask_entry_free(js, entry); } }} void process_microtasks(ant_t *js) { process_microtasks_internal(js, true); } bool js_maybe_drain_microtasks(ant_t *js) { if (!js) return false; if (js->microtasks_draining) return false; if (js->vm_exec_depth != 0) return false; if (!has_pending_microtasks(js)) return false; process_microtasks_internal(js, true); return true; } bool js_maybe_drain_microtasks_after_async_settle(ant_t *js) { if (!js) return false; if (js->microtasks_draining) return false; if (!has_pending_microtasks(js)) return false; process_microtasks_internal(js, false); return true; } void process_immediates(ant_t *js) { ant_timer_state_t *t = &js->timers; if (t->immediates_processing) return; t->immediates_processing = t->immediates; t->immediates = t->immediates_tail = NULL; while (t->immediates_processing) { immediate_entry_t *entry = t->immediates_processing; t->immediates_processing = entry->next; ant_value_t self = entry->handle; immediate_detach_handle(entry); if (entry->active) { GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, entry->callback); GC_ROOT_PIN(js, self); for (int i = 0; i < entry->argc; i++) GC_ROOT_PIN(js, entry->argv[i]); sv_vm_call(js->vm, js, entry->callback, self, entry->argv, entry->argc, NULL, js_mkundef()); GC_ROOT_RESTORE(js, root_mark); process_report_uncaught_exception_if_pending(js); process_microtasks(js); } free(entry); } } int has_pending_immediates(ant_t *js) { for ( immediate_entry_t *entry = js->timers.immediates; entry != NULL; entry = entry->next ) if (entry->active && entry->refed) return 1; return 0; } int has_active_immediates(ant_t *js) { for ( immediate_entry_t *entry = js->timers.immediates; entry != NULL; entry = entry->next ) if (entry->active) return 1; return 0; } int has_pending_timers(ant_t *js) { return js->timers.active_refed_timer_count > 0; } int has_pending_microtasks(ant_t *js) { return (js->timers.next_ticks != NULL || js->timers.microtasks != NULL) ? 1 : 0; } static void timers_define_common(ant_t *js, ant_value_t obj) { js_set(js, obj, "setTimeout", Ant_Function_CreateEx(js_set_timeout, 2, CFUNC_HAS_PROTOTYPE)); js_set(js, obj, "clearTimeout", Ant_Function_CreateEx(js_clear_timeout, 1, CFUNC_HAS_PROTOTYPE)); js_set(js, obj, "setInterval", Ant_Function_CreateEx(js_set_interval, 2, CFUNC_HAS_PROTOTYPE)); js_set(js, obj, "clearInterval", Ant_Function_CreateEx(js_clear_timeout, 1, CFUNC_HAS_PROTOTYPE)); js_set(js, obj, "setImmediate", Ant_Function_CreateEx(js_set_immediate, 1, CFUNC_HAS_PROTOTYPE)); js_set(js, obj, "clearImmediate", Ant_Function_CreateEx(js_clear_immediate, 1, CFUNC_HAS_PROTOTYPE)); js_set(js, obj, "queueMicrotask", Ant_Function_CreateWithArity(js_queue_microtask, 1)); } static ant_value_t timer_make_template(ant_t *js, ant_value_t proto, ant_value_t repeat) { ant_value_t template = Ant_Object_CreateBare(js); js_set_proto_init(template, proto); js_set(js, template, "delay", js_mknum(0)); js_set(js, template, "repeat", repeat); js_set(js, template, "callback", js_mkundef()); js_set_descriptor(js, template, "callback", 8, JS_DESC_W | JS_DESC_C); return template; } void init_timer_module(ant_t *js) { js->timers.next_timer_id = 1; js->timers.next_immediate_id = 1; js->builtins.timeout_proto = Ant_Object_CreateBare(js); js->builtins.interval_proto = Ant_Object_CreateBare(js); js_set_proto_init(js->builtins.timeout_proto, js->sym.object_proto); js_set(js, js->builtins.timeout_proto, "ref", Ant_Function_Create(js_timer_ref)); js_set(js, js->builtins.timeout_proto, "unref", Ant_Function_Create(js_timer_unref)); js_set(js, js->builtins.timeout_proto, "hasRef", Ant_Function_Create(js_timer_has_ref)); js_set(js, js->builtins.timeout_proto, "refresh", Ant_Function_Create(js_timer_refresh)); js_set_tostringtag(js, js->builtins.timeout_proto, js_mkstr(js, "Timeout", 7)); js_set_sym(js, js->builtins.timeout_proto, js->sym.inspect_sym, Ant_Function_Create(timer_inspect)); js_set_sym(js, js->builtins.timeout_proto, js->sym.toPrimitive_sym, Ant_Function_Create(timer_to_primitive)); js->builtins.immediate_proto = Ant_Object_CreateBare(js); js_set_proto_init(js->builtins.immediate_proto, js->sym.object_proto); js_set(js, js->builtins.immediate_proto, "ref", Ant_Function_Create(js_immediate_ref)); js_set(js, js->builtins.immediate_proto, "unref", Ant_Function_Create(js_immediate_unref)); js_set(js, js->builtins.immediate_proto, "hasRef", Ant_Function_Create(js_immediate_has_ref)); js_set_sym(js, js->builtins.immediate_proto, js->sym.dispose_sym, Ant_Function_Create(js_immediate_dispose)); js_set_tostringtag(js, js->builtins.immediate_proto, js_mkstr(js, "Immediate", 9)); js_set_sym(js, js->builtins.immediate_proto, js->sym.inspect_sym, Ant_Function_Create(timer_inspect)); js->builtins.immediate_template = Ant_Object_CreateBare(js); js_set_proto_init(js->builtins.immediate_template, js->builtins.immediate_proto); js_set(js, js->builtins.immediate_template, "callback", js_mkundef()); js_set_descriptor(js, js->builtins.immediate_template, "callback", 8, JS_DESC_W | JS_DESC_C); js_set_proto_init(js->builtins.interval_proto, js->sym.object_proto); js_set(js, js->builtins.interval_proto, "ref", Ant_Function_Create(js_timer_ref)); js_set(js, js->builtins.interval_proto, "unref", Ant_Function_Create(js_timer_unref)); js_set(js, js->builtins.interval_proto, "hasRef", Ant_Function_Create(js_timer_has_ref)); js_set(js, js->builtins.interval_proto, "refresh", Ant_Function_Create(js_timer_refresh)); js_set_tostringtag(js, js->builtins.interval_proto, js_mkstr(js, "Interval", 8)); js_set_sym(js, js->builtins.interval_proto, js->sym.inspect_sym, Ant_Function_Create(timer_inspect)); js_set_sym(js, js->builtins.interval_proto, js->sym.toPrimitive_sym, Ant_Function_Create(timer_to_primitive)); js->builtins.timeout_template = timer_make_template(js, js->builtins.timeout_proto, js_mknull()); js->builtins.interval_template = timer_make_template(js, js->builtins.interval_proto, js_mknum(0)); timers_define_common(js, js_glob(js)); } ant_value_t timers_library(ant_t *js) { ant_value_t lib = Ant_Object_CreateBare(js); timers_define_common(js, lib); js_set_tostringtag(js, lib, js_mkstr(js, "timers", 6)); return lib; } // TODO: mostly stubbed ant_value_t timers_promises_library(ant_t *js) { ant_value_t lib = Ant_Object_CreateBare(js); ant_value_t scheduler = Ant_Object_CreateBare(js); js_set(js, lib, "scheduler", scheduler); js_set(js, lib, "setTimeout", Ant_Function_CreateWithArity(js_timers_promises_setTimeout, 2)); js_set(js, lib, "setImmediate", Ant_Function_CreateWithArity(js_timers_promises_setImmediate, 1)); js_set(js, lib, "setInterval", Ant_Function_CreateWithArity(js_timers_promises_setInterval, 2)); js_set(js, scheduler, "wait", Ant_Function_Create(js_timers_promises_scheduler_wait)); js_set(js, scheduler, "yield", Ant_Function_Create(js_timers_promises_scheduler_yield)); js_set_tostringtag(js, lib, js_mkstr(js, "timers/promises", 16)); return lib; } static void discard_jobs(ant_t *js, microtask_entry_t *entry) { while (entry) { microtask_entry_t *next = entry->next; if (microtask_holds_coro(entry->kind)) { coroutine_clear_await_registration(entry->u.coro); coroutine_release(entry->u.coro); } else if (entry->kind == MT_PROMISE_TRIGGER) js_mark_promise_trigger_dequeued(js, entry->u.promise); free(entry); entry = next; } } void cleanup_timer_module(ant_t *js) { ant_timer_state_t *t = &js->timers; ANT_ASSERT( !t->microtasks_processing && !t->next_ticks_processing && !t->immediates_processing, "cannot destroy an isolate while it runs a job batch" ); while (t->timers) timer_close_entry(js, t->timers); discard_jobs(js, t->microtasks); discard_jobs(js, t->next_ticks); for (ant_realm_t *realm = js->realms; realm; realm = realm->next) { discard_jobs(js, realm->microtasks); realm->microtasks = realm->microtasks_tail = NULL; } t->microtasks = t->microtasks_tail = NULL; t->next_ticks = t->next_ticks_tail = NULL; for (immediate_entry_t *entry = t->immediates, *next; entry; entry = next) { next = entry->next; free(entry); } t->immediates = t->immediates_tail = NULL; while (t->free_jobs) { microtask_entry_t *next = t->free_jobs->next; free(t->free_jobs); t->free_jobs = next; } t->free_job_count = 0; } static void gc_mark_jobs(ant_t *js, microtask_entry_t *m, gc_mark_fn mark) { for (; m; m = m->next) { mark(js, m->callback); if (microtask_holds_coro(m->kind)) gc_mark_coroutine(js, m->u.coro); else mark(js, m->u.promise); for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); } } void gc_mark_timers(ant_t *js, gc_mark_fn mark) { bool minor = js->gc.minor; for (timer_entry_t *t = js->timers.timers; t; t = t->next) { if (minor && !t->young) break; if (minor) t->young = false; if (!t->active) continue; if (is_object_type(t->obj)) mark(js, t->obj); mark(js, t->callback); for (int i = 0; i < t->nargs; i++) mark(js, t->args[i]); } gc_mark_jobs(js, js->timers.microtasks, mark); gc_mark_jobs(js, js->timers.microtasks_processing, mark); for (ant_realm_t *realm = js->realms; realm; realm = realm->next) { gc_mark_jobs(js, realm->microtasks, mark); gc_mark_jobs(js, realm->microtasks_processing, mark); } for (microtask_entry_t *m = js->timers.next_ticks; m; m = m->next) { mark(js, m->callback); mark(js, m->u.promise); for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); } for (microtask_entry_t *m = js->timers.next_ticks_processing; m; m = m->next) { mark(js, m->callback); mark(js, m->u.promise); for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); } immediate_entry_t *immediate_lists[2] = { js->timers.immediates, js->timers.immediates_processing }; for (int l = 0; l < 2; l++) for (immediate_entry_t *i = immediate_lists[l]; i; i = i->next) { mark(js, i->callback); mark(js, i->handle); for (int a = 0; a < i->argc; a++) mark(js, i->argv[a]); } }