Page MenuHomePhorge

generator.c
No OneTemporary

Size
19 KB
Referenced Files
None
Subscribers
None

generator.c

#include <stdlib.h>
#include <string.h>
#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;
}

File Metadata

Mime Type
text/x-c
Expires
Fri, May 1, 11:34 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
542971
Default Alt Text
generator.c (19 KB)

Event Timeline