#include #include #include "ant.h" #include "ptr.h" #include "errors.h" #include "internal.h" #include "runtime.h" #include "sugar.h" #include "gc/roots.h" #include "silver/engine.h" #include "modules/generator.h" #include "modules/iterator.h" #include "modules/symbol.h" enum { GENERATOR_NATIVE_TAG = 0x47454e52u }; // GENR typedef enum { GEN_SUSPENDED_START = 0, GEN_SUSPENDED_YIELD = 1, GEN_EXECUTING = 2, GEN_COMPLETED = 3, } generator_state_t; typedef struct generator_request { sv_resume_kind_t kind; ant_value_t value; ant_value_t promise; struct generator_request *next; } generator_request_t; typedef struct generator_data { coroutine_t *coro; generator_state_t state; generator_request_t *queue_head; generator_request_t *queue_tail; bool is_async; } generator_data_t; static ant_value_t generator_resume_kind( ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind ); static ant_value_t generator_result(ant_t *js, bool done, ant_value_t value) { ant_value_t result = js_mkobj(js); js_set(js, result, "done", js_bool(done)); js_set(js, result, "value", value); return result; } static generator_data_t *generator_data(ant_value_t gen) { if (!js_check_native_tag(gen, GENERATOR_NATIVE_TAG)) return NULL; return (generator_data_t *)js_get_native_ptr(gen); } static generator_state_t generator_state(ant_value_t gen) { generator_data_t *data = generator_data(gen); return data ? data->state : GEN_COMPLETED; } static void generator_set_state(ant_value_t gen, generator_state_t state) { generator_data_t *data = generator_data(gen); if (data) data->state = state; } static bool generator_is_async(ant_value_t gen) { generator_data_t *data = generator_data(gen); return data && data->is_async; } static ant_value_t generator_async_wrap_result(ant_t *js, ant_value_t result) { ant_value_t promise = js_mkpromise(js); if (is_err(promise)) return promise; if (is_err(result)) { ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; js->thrown_exists = false; js->thrown_value = js_mkundef(); js_reject_promise(js, promise, reject_value); } else js_resolve_promise(js, promise, result); return promise; } static generator_request_t *generator_dequeue_request(generator_data_t *data) { if (!data || !data->queue_head) return NULL; generator_request_t *req = data->queue_head; data->queue_head = req->next; if (!data->queue_head) data->queue_tail = NULL; req->next = NULL; return req; } static void generator_free_queue(generator_data_t *data) { if (!data) return; generator_request_t *req = data->queue_head; while (req) { generator_request_t *next = req->next; free(req); req = next; } data->queue_head = NULL; data->queue_tail = NULL; } static ant_value_t generator_enqueue_request( ant_t *js, ant_value_t gen, sv_resume_kind_t kind, ant_value_t value ) { generator_data_t *data = generator_data(gen); if (!data || !data->is_async) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); ant_value_t promise = js_mkpromise(js); if (is_err(promise)) return promise; generator_request_t *req = (generator_request_t *)calloc(1, sizeof(*req)); if (!req) return js_mkerr(js, "out of memory for generator request"); *req = (generator_request_t){ .kind = kind, .value = value, .promise = promise, .next = NULL, }; if (data->queue_tail) data->queue_tail->next = req; else data->queue_head = req; data->queue_tail = req; return promise; } static void generator_settle_request_promise(ant_t *js, ant_value_t promise, ant_value_t result) { if (is_err(result)) { ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; js->thrown_exists = false; js->thrown_value = js_mkundef(); js_reject_promise(js, promise, reject_value); } else js_resolve_promise(js, promise, result); } static void generator_process_queue(ant_t *js, ant_value_t gen) { generator_data_t *data = generator_data(gen); if (!data || !data->is_async) return; GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, gen); while (data->state != GEN_EXECUTING && data->queue_head) { generator_request_t *req = generator_dequeue_request(data); if (!req) break; GC_ROOT_PIN(js, req->value); GC_ROOT_PIN(js, req->promise); coroutine_t *coro = data->coro; if (coro) coro->async_promise = req->promise; ant_value_t result = generator_resume_kind(js, gen, req->value, req->kind); GC_ROOT_PIN(js, result); if (vtype(result) != T_PROMISE || result != req->promise) { generator_settle_request_promise(js, req->promise, result); js_maybe_drain_microtasks_after_async_settle(js); } free(req); data = generator_data(gen); if (!data) break; } GC_ROOT_RESTORE(js, root_mark); } static coroutine_t *generator_coro(ant_value_t gen) { generator_data_t *data = generator_data(gen); return data ? data->coro : NULL; } static void generator_clear_coro(ant_value_t gen, coroutine_t *coro) { generator_data_t *data = generator_data(gen); if (data && data->coro == coro) data->coro = NULL; if (coro) coroutine_unhold(coro, CORO_HOLD_GENERATOR); } coroutine_t *generator_get_coro_for_gc(ant_value_t gen) { return generator_coro(gen); } void generator_mark_for_gc(ant_t *js, ant_value_t gen) { generator_data_t *data = generator_data(gen); if (!data) return; for (generator_request_t *req = data->queue_head; req; req = req->next) { gc_mark_value(js, req->value); gc_mark_value(js, req->promise); } } static ant_value_t generator_find_owner_in_list(ant_object_t *head, coroutine_t *coro) { for (ant_object_t *obj = head; obj; obj = obj->next) { ant_value_t candidate = js_obj_from_ptr(obj); generator_data_t *data = generator_data(candidate); if (data && data->coro == coro) return candidate; } return js_mkundef(); } static ant_value_t generator_find_owner(ant_t *js, coroutine_t *coro) { ant_value_t gen = generator_find_owner_in_list(js->objects, coro); if (vtype(gen) != T_UNDEF) return gen; gen = generator_find_owner_in_list(js->objects_old, coro); if (vtype(gen) != T_UNDEF) return gen; return generator_find_owner_in_list(js->permanent_objects, coro); } bool generator_resume_pending_request(ant_t *js, coroutine_t *coro, ant_value_t result) { if (!coro || coro->type != CORO_GENERATOR || vtype(coro->async_promise) != T_PROMISE) return false; ant_value_t gen = generator_find_owner(js, coro); generator_data_t *data = generator_data(gen); if (!data || !data->is_async) return false; GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, gen); GC_ROOT_PIN(js, result); ant_value_t pending = coro->async_promise; GC_ROOT_PIN(js, pending); if (is_err(result)) { ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; js->thrown_exists = false; js->thrown_value = js_mkundef(); GC_ROOT_PIN(js, reject_value); coro->async_promise = js_mkundef(); generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); js_reject_promise(js, pending, reject_value); js_maybe_drain_microtasks_after_async_settle(js); generator_process_queue(js, gen); GC_ROOT_RESTORE(js, root_mark); return true; } if (coro->sv_vm && coro->sv_vm->suspended) { if (vtype(coro->awaited_promise) != T_UNDEF) { generator_set_state(gen, GEN_EXECUTING); GC_ROOT_RESTORE(js, root_mark); return true; } ant_value_t out = generator_result(js, false, result); GC_ROOT_PIN(js, out); coro->async_promise = js_mkundef(); generator_set_state(gen, GEN_SUSPENDED_YIELD); js_resolve_promise(js, pending, out); js_maybe_drain_microtasks_after_async_settle(js); generator_process_queue(js, gen); GC_ROOT_RESTORE(js, root_mark); return true; } ant_value_t out = generator_result(js, true, result); GC_ROOT_PIN(js, out); coro->async_promise = js_mkundef(); generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); js_resolve_promise(js, pending, out); js_maybe_drain_microtasks_after_async_settle(js); generator_process_queue(js, gen); GC_ROOT_RESTORE(js, root_mark); return true; } static void generator_finalize(ant_t *js, ant_object_t *obj) { ant_value_t gen = js_obj_from_ptr(obj); generator_data_t *data = (generator_data_t *)js_get_native_ptr(gen); if (!data) return; if (data->coro) generator_clear_coro(gen, data->coro); js_set_native_ptr(gen, NULL); js_set_native_tag(gen, 0); generator_free_queue(data); free(data); } static ant_value_t generator_resume_kind( ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind ) { GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, gen); GC_ROOT_PIN(js, resume_value); coroutine_t *coro = generator_coro(gen); if (!coro || !coro->sv_vm) { generator_set_state(gen, GEN_COMPLETED); if (resume_kind == SV_RESUME_THROW) { GC_ROOT_RESTORE(js, root_mark); return js_throw(js, resume_value); } ant_value_t out = generator_result( js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef() ); GC_ROOT_RESTORE(js, root_mark); return out; } generator_state_t state = generator_state(gen); if (state == GEN_EXECUTING) { ant_value_t queued = generator_enqueue_request(js, gen, resume_kind, resume_value); GC_ROOT_RESTORE(js, root_mark); return queued; } if (state == GEN_COMPLETED) { if (resume_kind == SV_RESUME_THROW) { GC_ROOT_RESTORE(js, root_mark); return js_throw(js, resume_value); } ant_value_t out = generator_result( js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef() ); GC_ROOT_RESTORE(js, root_mark); return out; } if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_THROW) { generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); GC_ROOT_RESTORE(js, root_mark); return js_throw(js, resume_value); } if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_RETURN) { generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); ant_value_t out = generator_result(js, true, resume_value); GC_ROOT_RESTORE(js, root_mark); return out; } generator_set_state(gen, GEN_EXECUTING); coroutine_t *saved_active = js->active_async_coro; coro->active_parent = saved_active; coro->active_prev = NULL; if (saved_active) saved_active->active_prev = coro; js->active_async_coro = coro; coroutine_hold(coro, CORO_HOLD_ACTIVE); ant_value_t result; if (state == GEN_SUSPENDED_START) { sv_closure_t *closure = (vtype(coro->async_func) == T_FUNC) ? js_func_closure(coro->async_func) : NULL; if (!closure || !closure->func) result = js_mkerr(js, "invalid generator function"); else result = sv_execute_closure_entry( coro->sv_vm, closure, coro->async_func, coro->super_val, coro->this_val, coro->args, coro->nargs, NULL ); } else { coro->sv_vm->suspended_resume_value = resume_value; coro->sv_vm->suspended_resume_is_error = (resume_kind == SV_RESUME_THROW); coro->sv_vm->suspended_resume_kind = resume_kind; coro->sv_vm->suspended_resume_pending = true; result = sv_resume_suspended(coro->sv_vm); } GC_ROOT_PIN(js, result); js->active_async_coro = saved_active; if (saved_active) saved_active->active_prev = NULL; coro->active_parent = NULL; coro->active_prev = NULL; coroutine_unhold(coro, CORO_HOLD_ACTIVE); if (is_err(result)) { generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); GC_ROOT_RESTORE(js, root_mark); return result; } if (coro->sv_vm && coro->sv_vm->suspended) { if (generator_is_async(gen) && vtype(coro->awaited_promise) != T_UNDEF) { generator_set_state(gen, GEN_EXECUTING); if (vtype(coro->async_promise) != T_PROMISE) { coro->async_promise = js_mkpromise(js); GC_ROOT_PIN(js, coro->async_promise); } ant_value_t out = coro->async_promise; GC_ROOT_RESTORE(js, root_mark); return out; } generator_set_state(gen, GEN_SUSPENDED_YIELD); ant_value_t out = generator_result(js, false, result); GC_ROOT_RESTORE(js, root_mark); return out; } generator_set_state(gen, GEN_COMPLETED); generator_clear_coro(gen, coro); ant_value_t out = generator_result(js, true, result); GC_ROOT_RESTORE(js, root_mark); return out; } static ant_value_t generator_resume(ant_t *js, ant_value_t gen, ant_value_t resume_value) { return generator_resume_kind(js, gen, resume_value, SV_RESUME_NEXT); } static ant_value_t generator_next(ant_t *js, ant_value_t *args, int nargs) { ant_value_t gen = js->this_val; if (vtype(gen) != T_GENERATOR) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.next called on incompatible receiver"); ant_value_t resume_value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t result = generator_resume(js, gen, resume_value); if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result; return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; } static ant_value_t generator_return(ant_t *js, ant_value_t *args, int nargs) { ant_value_t gen = js->this_val; if (vtype(gen) != T_GENERATOR) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.return called on incompatible receiver"); generator_state_t state = generator_state(gen); if (state == GEN_EXECUTING && !generator_is_async(gen)) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_RETURN); if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result; return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; } static ant_value_t generator_throw(ant_t *js, ant_value_t *args, int nargs) { ant_value_t gen = js->this_val; if (vtype(gen) != T_GENERATOR) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.throw called on incompatible receiver"); generator_state_t state = generator_state(gen); if (state == GEN_EXECUTING && !generator_is_async(gen)) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_THROW); if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result; return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; } static ant_value_t generator_async_dispose(ant_t *js, ant_value_t *args, int nargs) { ant_value_t gen = js->this_val; if (vtype(gen) != T_GENERATOR || !generator_is_async(gen)) return js_mkerr_typed( js, JS_ERR_TYPE, "AsyncGenerator.prototype[Symbol.asyncDispose] called on incompatible receiver" ); return generator_return(js, NULL, 0); } void init_generator_module(void) { ant_t *js = rt->js; ant_value_t proto = js_mkobj(js); js->sym.generator_proto = proto; js_set_proto_init(proto, js->sym.iterator_proto); js_set(js, proto, "next", js_mkfun(generator_next)); js_set(js, proto, "return", js_mkfun(generator_return)); js_set(js, proto, "throw", js_mkfun(generator_throw)); js_set_sym(js, proto, get_toStringTag_sym(), js_mkstr(js, "Generator", 9)); ant_value_t async_proto = js_mkobj(js); js->sym.async_generator_proto = async_proto; js_set_proto_init(async_proto, js->sym.async_iterator_proto); js_set(js, async_proto, "next", js_mkfun(generator_next)); js_set(js, async_proto, "return", js_mkfun(generator_return)); js_set(js, async_proto, "throw", js_mkfun(generator_throw)); js_set_sym(js, async_proto, get_toStringTag_sym(), js_mkstr(js, "AsyncGenerator", 14)); js_set_sym(js, async_proto, get_asyncDispose_sym(), js_mkfun(generator_async_dispose)); ant_value_t async_generator_func_proto = js_get_slot(js_glob(js), SLOT_ASYNC_GENERATOR_PROTO); if (is_object_type(async_generator_func_proto)) { js_set(js, async_generator_func_proto, "prototype", async_proto); js_set_descriptor(js, js_as_obj(async_generator_func_proto), "prototype", 9, JS_DESC_C); js_set(js, async_proto, "constructor", async_generator_func_proto); js_set_descriptor(js, async_proto, "constructor", 11, JS_DESC_C); } init_async_iterator_helpers(); } ant_value_t sv_call_generator_closure_dispatch( sv_vm_t *caller_vm, ant_t *js, sv_closure_t *closure, ant_value_t callee_func, ant_value_t super_val, ant_value_t this_val, ant_value_t *args, int argc ) { if (!closure || !closure->func) return js_mkerr(js, "invalid generator function"); sv_vm_t *gen_vm = sv_vm_create(js, SV_VM_ASYNC); if (!gen_vm) return js_mkerr(js, "out of memory for generator VM"); coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t)); if (!coro) { sv_vm_destroy(gen_vm); return js_mkerr(js, "out of memory for generator"); } ant_value_t *copied_args = NULL; if (argc > 0 && args) { copied_args = (ant_value_t *)CORO_MALLOC(sizeof(ant_value_t) * (size_t)argc); if (!copied_args) { sv_vm_destroy(gen_vm); CORO_FREE(coro); return js_mkerr(js, "out of memory for generator args"); } memcpy(copied_args, args, sizeof(ant_value_t) * (size_t)argc); } ant_value_t gen = js_mkgenerator(js); if (is_err(gen)) { if (copied_args) CORO_FREE(copied_args); sv_vm_destroy(gen_vm); CORO_FREE(coro); return gen; } generator_data_t *data = (generator_data_t *)calloc(1, sizeof(*data)); if (!data) { if (copied_args) CORO_FREE(copied_args); sv_vm_destroy(gen_vm); CORO_FREE(coro); return js_mkerr(js, "out of memory for generator data"); } *coro = (coroutine_t){ .js = js, .type = CORO_GENERATOR, .this_val = this_val, .super_val = super_val, .new_target = js->new_target, .awaited_promise = js_mkundef(), .result = js_mkundef(), .async_func = callee_func, .args = copied_args, .nargs = argc, .active_parent = NULL, .is_settled = false, .is_error = false, .is_done = false, .resume_point = 0, .yield_value = js_mkundef(), .async_promise = js_mkundef(), .next = NULL, .mco = NULL, .owner_vm = gen_vm, .sv_vm = gen_vm, .mco_started = false, .is_ready = false, .did_suspend = false, .refcount = 1, .hold_bits = 0, .await_registered = false, .destroy_requested = false, }; *data = (generator_data_t){ .coro = coro, .state = GEN_SUSPENDED_START, .is_async = closure->func->is_async, }; coroutine_hold(coro, CORO_HOLD_GENERATOR); coroutine_release(coro); js_set_native_ptr(gen, data); js_set_native_tag(gen, GENERATOR_NATIVE_TAG); js_set_finalizer(gen, generator_finalize); ant_value_t instance_proto = js_get(js, callee_func, "prototype"); if (is_object_type(instance_proto)) js_set_proto_wb(js, gen, instance_proto); else if (data->is_async && is_object_type(js->sym.async_generator_proto)) js_set_proto_wb(js, gen, js->sym.async_generator_proto); return gen; }