Page MenuHomePhorge

async.h
No OneTemporary

Size
21 KB
Referenced Files
None
Subscribers
None
#ifndef SV_ASYNC_H
#define SV_ASYNC_H
#include "ant.h"
#include "gc/roots.h"
#include "sugar.h"
#include "silver/engine.h"
#include <minicoro.h>
typedef struct {
ant_t *js;
coroutine_t *coro;
} sv_coro_header_t;
typedef struct {
ant_t *js;
coroutine_t *coro;
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;
sv_vm_t *vm;
} sv_async_ctx_t;
static inline bool sv_async_func_supports_lazy_start(sv_func_t *func) {
if (!func || !func->has_await || func->is_generator) return false;
for (int off = 0; off < func->code_len;) {
sv_op_t op = (sv_op_t)func->code[off];
if (
op == OP_AWAIT_ITER_NEXT ||
op == OP_YIELD ||
op == OP_YIELD_STAR_NEXT ||
op == OP_YIELD_STAR_THROW ||
op == OP_YIELD_STAR_RETURN
) return false;
int size = sv_op_size[op];
if (size <= 0) return false;
off += size;
}
return true;
}
static void sv_mco_async_entry(mco_coro *mco) {
sv_async_ctx_t *ctx = (sv_async_ctx_t *)mco_get_user_data(mco);
ant_t *js = ctx->js;
MCO_CORO_STACK_ENTER(js, mco);
ant_value_t super_val = ctx->super_val;
if (ctx->coro) {
super_val = ctx->coro->super_val;
js->new_target = ctx->coro->new_target;
}
sv_vm_t *vm = ctx->vm;
ant_value_t result = sv_execute_closure_entry(
vm, ctx->closure, ctx->callee_func,
super_val, ctx->this_val, ctx->args, ctx->argc, NULL
);
ant_value_t promise = ctx->coro->async_promise;
if (vm && vm->suspended) return;
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);
js_maybe_drain_microtasks_after_async_settle(js);
} else {
js_resolve_promise(js, promise, result);
js_maybe_drain_microtasks_after_async_settle(js);
}
}
typedef struct {
ant_t *js;
coroutine_t *coro;
sv_func_t *func;
ant_value_t this_val;
sv_vm_t *vm;
} sv_tla_ctx_t;
typedef enum {
SV_AWAIT_READY = 0,
SV_AWAIT_ERROR,
SV_AWAIT_SUSPENDED,
} sv_await_state_t;
typedef struct {
sv_await_state_t state;
ant_value_t value;
bool handoff;
} sv_await_result_t;
static inline void sv_async_link_activation(ant_t *js, coroutine_t *coro) {
if (!js || !coro) return;
coro->active_parent = js->active_async_coro;
coro->active_prev = NULL;
if (js->active_async_coro) js->active_async_coro->active_prev = coro;
js->active_async_coro = coro;
coroutine_hold(coro, CORO_HOLD_ACTIVE);
}
static inline void sv_async_unlink_activation(ant_t *js, coroutine_t *coro) {
if (!js || !coro) return;
if (coro->active_prev) coro->active_prev->active_parent = coro->active_parent;
else if (js->active_async_coro == coro) js->active_async_coro = coro->active_parent;
if (coro->active_parent) coro->active_parent->active_prev = coro->active_prev;
coro->active_parent = NULL;
coro->active_prev = NULL;
coroutine_unhold(coro, CORO_HOLD_ACTIVE);
}
static inline bool sv_async_coro_matches_vm(const coroutine_t *coro, const sv_vm_t *vm) {
if (!coro || !vm) return false;
if (coro->sv_vm == vm) return true;
return coro->owner_vm == vm;
}
static inline coroutine_t *sv_async_get_active_coro_for_vm(ant_t *js, sv_vm_t *vm) {
if (!js || !js->active_async_coro) return NULL;
if (!vm) return js->active_async_coro;
for (coroutine_t *it = js->active_async_coro; it; it = it->active_parent) {
if (sv_async_coro_matches_vm(it, vm)) return it;
}
return NULL;
}
static inline void sv_async_init_activation(
coroutine_t *coro, ant_t *js, sv_vm_t *owner_vm, ant_value_t promise,
ant_value_t this_val, ant_value_t super_val, ant_value_t new_target,
ant_value_t async_func, int nargs
) {
if (!coro) return;
*coro = (coroutine_t){
.js = js,
.this_val = this_val,
.super_val = super_val,
.new_target = new_target,
.result = js_mkundef(),
.async_func = async_func,
.yield_value = js_mkundef(),
.args = NULL,
.awaited_promise = js_mkundef(),
.async_promise = promise,
.active_parent = NULL,
.prev = NULL,
.next = NULL,
.mco = NULL,
.owner_vm = owner_vm,
.sv_vm = NULL,
.resume_point = 0,
.type = CORO_ASYNC_AWAIT,
.owner_entry_fp = owner_vm ? owner_vm->fp : -1,
.owner_saved_fp = owner_vm ? owner_vm->fp - 1 : -1,
.nargs = nargs,
.refcount = 1,
.hold_bits = 0,
.is_settled = false,
.is_error = false,
.is_done = false,
.materialized = false,
.mco_started = false,
.is_ready = false,
.did_suspend = false,
.await_registered = false,
.destroy_requested = false,
};
}
static inline void sv_async_move_open_upvalues(
sv_vm_t *source_vm, sv_vm_t *async_vm,
ant_value_t *source_base, ant_value_t *dest_base, size_t stack_count
) {
if (!source_vm || !async_vm || !source_base || !dest_base || stack_count == 0) return;
sv_upvalue_t **src_pp = &source_vm->open_upvalues;
sv_upvalue_t **dst_pp = &async_vm->open_upvalues;
while (*src_pp) {
sv_upvalue_t *uv = *src_pp;
if (!sv_slot_in_range(source_base, stack_count, uv->location)) {
src_pp = &uv->next;
continue;
}
ptrdiff_t slot = uv->location - source_base;
*src_pp = uv->next;
uv->location = &dest_base[slot];
uv->next = NULL;
*dst_pp = uv;
dst_pp = &uv->next;
}
}
static inline sv_vm_t *sv_async_prepare_materialization(
sv_vm_t *source_vm, ant_t *js, coroutine_t *coro
) {
if (!source_vm || !js || !coro || coro->sv_vm) return coro ? coro->sv_vm : NULL;
if (source_vm->fp < 0) return NULL;
int entry_fp = source_vm->suspended_entry_fp;
if (entry_fp < 0 || entry_fp > source_vm->fp) entry_fp = source_vm->fp;
sv_frame_t *entry_frame = &source_vm->frames[entry_fp];
int frame_count = source_vm->fp - entry_fp + 1;
int stack_base = entry_frame->prev_sp;
int stack_count = source_vm->sp - stack_base;
int handler_base = entry_frame->handler_base;
int handler_count = source_vm->handler_depth - handler_base;
sv_vm_t *async_vm = sv_vm_create(js, SV_VM_ASYNC);
if (!async_vm) return NULL;
if (stack_count < 0 || stack_count > async_vm->stack_size) {
sv_vm_destroy(async_vm);
return NULL;
}
if (frame_count < 1 || frame_count > async_vm->max_frames) {
sv_vm_destroy(async_vm);
return NULL;
}
if (handler_count < 0 || handler_count > SV_HANDLER_MAX) {
sv_vm_destroy(async_vm);
return NULL;
}
if (stack_count > 0) {
memcpy(
async_vm->stack,
&source_vm->stack[stack_base],
sizeof(ant_value_t) * (size_t)stack_count
);
}
async_vm->sp = stack_count;
async_vm->fp = frame_count - 1;
for (int i = 0; i < frame_count; i++) {
sv_frame_t *src = &source_vm->frames[entry_fp + i];
sv_frame_t *dst = &async_vm->frames[i];
*dst = *src;
dst->prev_sp = src->prev_sp - stack_base;
dst->handler_base = src->handler_base - handler_base;
dst->handler_top = src->handler_top - handler_base;
if (src->bp)
dst->bp = async_vm->stack + (src->bp - &source_vm->stack[stack_base]);
if (src->lp)
dst->lp = async_vm->stack + (src->lp - &source_vm->stack[stack_base]);
}
if (handler_count > 0) {
memcpy(
async_vm->handler_stack,
&source_vm->handler_stack[handler_base],
sizeof(sv_handler_t) * (size_t)handler_count
);
}
async_vm->handler_depth = handler_count;
async_vm->suspended = true;
async_vm->suspended_entry_fp = 0;
async_vm->suspended_saved_fp = -1;
async_vm->suspended_resume_pending = false;
async_vm->suspended_resume_is_error = false;
async_vm->suspended_resume_kind = SV_RESUME_NEXT;
async_vm->suspended_resume_value = js_mkundef();
return async_vm;
}
static inline bool sv_async_materialize_activation(
sv_vm_t *source_vm, sv_vm_t *async_vm, coroutine_t *coro
) {
if (!source_vm || !async_vm || !coro || source_vm->fp < 0) return false;
int entry_fp = source_vm->suspended_entry_fp;
if (entry_fp < 0 || entry_fp > source_vm->fp) entry_fp = source_vm->fp;
sv_frame_t *entry_frame = &source_vm->frames[entry_fp];
ant_value_t *source_base = &source_vm->stack[entry_frame->prev_sp];
size_t stack_count = (size_t)(source_vm->sp - entry_frame->prev_sp);
sv_async_move_open_upvalues(
source_vm, async_vm, source_base, async_vm->stack, stack_count
);
coro->owner_entry_fp = source_vm->suspended_entry_fp;
coro->owner_saved_fp = source_vm->suspended_saved_fp;
coro->sv_vm = async_vm;
coro->materialized = true;
return true;
}
static void sv_mco_tla_entry(mco_coro *mco) {
sv_tla_ctx_t *ctx = (sv_tla_ctx_t *)mco_get_user_data(mco);
ant_t *js = ctx->js;
sv_vm_t *vm = ctx->vm;
MCO_CORO_STACK_ENTER(js, mco);
ant_value_t result = sv_execute_entry(vm, ctx->func, ctx->this_val, NULL, 0);
ant_value_t promise = ctx->coro->async_promise;
if (vm && vm->suspended) return;
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);
js_maybe_drain_microtasks_after_async_settle(js);
} else {
js_resolve_promise(js, promise, result);
js_maybe_drain_microtasks_after_async_settle(js);
}
}
static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t this_val) {
if (++coros_this_tick > CORO_PER_TICK_LIMIT) {
js->fatal_error = true;
return js_mkerr_typed(js, JS_ERR_RANGE | JS_ERR_NO_STACK,
"Maximum async operations per tick exceeded");
}
ant_value_t promise = js_mkpromise(js);
if (func && (!func->has_await || sv_async_func_supports_lazy_start(func))) {
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, this_val);
GC_ROOT_PIN(js, promise);
coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t));
if (!coro) {
GC_ROOT_RESTORE(js, root_mark);
return js_mkerr(js, "out of memory for TLA coroutine");
}
sv_async_init_activation(
coro, js, js->vm, promise, this_val,
js_mkundef(), js_mkundef(), js_mkundef(), 0
);
sv_async_link_activation(js, coro);
ant_value_t result = sv_execute_entry(
js->vm, func,
this_val, NULL, 0
);
sv_async_unlink_activation(js, coro);
if (coro->sv_vm && coro->sv_vm->suspended) {
coroutine_release(coro);
GC_ROOT_RESTORE(js, root_mark);
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);
}
coroutine_release(coro);
GC_ROOT_RESTORE(js, root_mark);
return promise;
}
sv_tla_ctx_t *ctx = (sv_tla_ctx_t *)CORO_MALLOC(sizeof(sv_tla_ctx_t));
if (!ctx) return js_mkerr(js, "out of memory for TLA context");
sv_vm_t *async_vm = sv_vm_create(js, SV_VM_ASYNC);
if (!async_vm) { CORO_FREE(ctx); return js_mkerr(js, "out of memory for TLA VM"); }
ctx->js = js;
ctx->func = func;
ctx->this_val = this_val;
ctx->coro = NULL;
ctx->vm = async_vm;
size_t stack_size = 0;
const char *env_stack = getenv("ANT_CORO_STACK_SIZE");
if (env_stack) {
size_t sz = (size_t)atoi(env_stack) * 1024;
if (sz >= 32 * 1024 && sz <= 8 * 1024 * 1024) stack_size = sz;
}
mco_desc desc = mco_desc_init(sv_mco_tla_entry, stack_size);
desc.user_data = ctx;
mco_coro *mco = NULL;
mco_result res = mco_create(&mco, &desc);
if (res != MCO_SUCCESS) {
sv_vm_destroy(async_vm); CORO_FREE(ctx);
return js_mkerr(js, "failed to create TLA coroutine");
}
coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t));
if (!coro) {
mco_destroy(mco);
sv_vm_destroy(async_vm); CORO_FREE(ctx);
return js_mkerr(js, "out of memory for TLA coroutine");
}
*coro = (coroutine_t){
.js = js,
.type = CORO_ASYNC_AWAIT,
.this_val = this_val,
.super_val = js_mkundef(),
.new_target = js_mkundef(),
.awaited_promise = js_mkundef(),
.result = js_mkundef(),
.async_func = js_mkundef(),
.args = NULL,
.nargs = 0,
.active_parent = NULL,
.is_settled = false,
.is_error = false,
.is_done = false,
.resume_point = 0,
.yield_value = js_mkundef(),
.async_promise = promise,
.next = NULL,
.mco = mco,
.owner_vm = async_vm,
.sv_vm = async_vm,
.mco_started = false,
.is_ready = true,
.did_suspend = false,
.refcount = 1,
.hold_bits = 0,
.await_registered = false,
.destroy_requested = false,
};
ctx->coro = coro;
enqueue_coroutine(coro);
MCO_RESUME_SAVE(js, mco, res);
if (res != MCO_SUCCESS && mco_status(mco) != MCO_DEAD) {
remove_coroutine(coro);
coroutine_release(coro);
return js_mkerr(js, "failed to start TLA coroutine");
}
coro->mco_started = true;
if (mco_status(mco) == MCO_DEAD) {
remove_coroutine(coro);
}
coroutine_release(coro);
return promise;
}
static inline ant_value_t sv_start_async_closure(
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 (++coros_this_tick > CORO_PER_TICK_LIMIT) {
js->fatal_error = true;
return js_mkerr_typed(js, JS_ERR_RANGE | JS_ERR_NO_STACK,
"Maximum async operations per tick exceeded");
}
if (caller_vm && closure && closure->func && !closure->func->has_await) {
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, callee_func);
GC_ROOT_PIN(js, super_val);
GC_ROOT_PIN(js, this_val);
if (args) {
for (int i = 0; i < argc; i++) GC_ROOT_PIN(js, args[i]);
}
ant_value_t promise = js_mkpromise(js);
GC_ROOT_PIN(js, promise);
ant_value_t result = sv_execute_closure_entry(
caller_vm, closure, callee_func, super_val, this_val, args, argc, NULL
);
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);
GC_ROOT_RESTORE(js, root_mark);
return promise;
}
if (closure && closure->func && sv_async_func_supports_lazy_start(closure->func)) {
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, callee_func);
GC_ROOT_PIN(js, super_val);
GC_ROOT_PIN(js, this_val);
if (args) {
for (int i = 0; i < argc; i++) GC_ROOT_PIN(js, args[i]);
}
ant_value_t promise = js_mkpromise(js);
GC_ROOT_PIN(js, promise);
coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t));
if (!coro) {
GC_ROOT_RESTORE(js, root_mark);
return js_mkerr(js, "out of memory for async coroutine");
}
sv_async_init_activation(
coro, js, caller_vm, promise, this_val,
super_val, js->new_target, callee_func, argc
);
sv_async_link_activation(js, coro);
ant_value_t result = sv_execute_closure_entry(
caller_vm, closure, callee_func,
super_val, this_val, args, argc, NULL
);
sv_async_unlink_activation(js, coro);
if (coro->sv_vm && coro->sv_vm->suspended) {
coroutine_release(coro);
GC_ROOT_RESTORE(js, root_mark);
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);
}
coroutine_release(coro);
GC_ROOT_RESTORE(js, root_mark);
return promise;
}
ant_value_t promise = js_mkpromise(js);
sv_async_ctx_t *ctx = (sv_async_ctx_t *)CORO_MALLOC(sizeof(sv_async_ctx_t));
if (!ctx) return js_mkerr(js, "out of memory for async context");
sv_vm_t *async_vm = sv_vm_create(js, SV_VM_ASYNC);
if (!async_vm) { CORO_FREE(ctx); return js_mkerr(js, "out of memory for async VM"); }
ctx->js = js;
ctx->closure = closure;
ctx->callee_func = callee_func;
ctx->super_val = super_val;
ctx->this_val = this_val;
ctx->args = NULL;
ctx->argc = argc;
ctx->coro = NULL;
ctx->vm = async_vm;
if (argc > 0 && args) {
ctx->args = (ant_value_t *)CORO_MALLOC(sizeof(ant_value_t) * (size_t)argc);
if (!ctx->args) {
sv_vm_destroy(async_vm); CORO_FREE(ctx);
return js_mkerr(js, "out of memory for async args");
}
memcpy(ctx->args, args, sizeof(ant_value_t) * (size_t)argc);
}
size_t stack_size = 0;
const char *env_stack = getenv("ANT_CORO_STACK_SIZE");
if (env_stack) {
size_t sz = (size_t)atoi(env_stack) * 1024;
if (sz >= 32 * 1024 && sz <= 8 * 1024 * 1024) stack_size = sz;
}
mco_desc desc = mco_desc_init(sv_mco_async_entry, stack_size);
desc.user_data = ctx;
mco_coro *mco = NULL;
mco_result res = mco_create(&mco, &desc);
if (res != MCO_SUCCESS) {
if (ctx->args) CORO_FREE(ctx->args);
sv_vm_destroy(async_vm); CORO_FREE(ctx);
return js_mkerr(js, "failed to create async coroutine");
}
coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t));
if (!coro) {
mco_destroy(mco);
if (ctx->args) CORO_FREE(ctx->args);
sv_vm_destroy(async_vm); CORO_FREE(ctx);
return js_mkerr(js, "out of memory for coroutine");
}
*coro = (coroutine_t){
.js = js,
.type = CORO_ASYNC_AWAIT,
.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 = ctx->args,
.nargs = argc,
.active_parent = NULL,
.is_settled = false,
.is_error = false,
.is_done = false,
.resume_point = 0,
.yield_value = js_mkundef(),
.async_promise = promise,
.next = NULL,
.mco = mco,
.owner_vm = async_vm,
.sv_vm = async_vm,
.mco_started = false,
.is_ready = true,
.did_suspend = false,
.refcount = 1,
.hold_bits = 0,
.await_registered = false,
.destroy_requested = false,
};
ctx->coro = coro;
enqueue_coroutine(coro);
MCO_RESUME_SAVE(js, mco, res);
mco_state start_status = mco_status(mco);
if (res != MCO_SUCCESS && start_status != MCO_DEAD) {
remove_coroutine(coro);
coroutine_release(coro);
return js_mkerr(js, "failed to start async coroutine");
}
coro->mco_started = true;
if (start_status == MCO_DEAD) {
remove_coroutine(coro);
}
coroutine_release(coro);
return promise;
}
static inline sv_await_result_t sv_await_value(sv_vm_t *vm, ant_t *js, ant_value_t value) {
sv_await_result_t out = {
.state = SV_AWAIT_READY,
.value = js_mkundef(),
.handoff = false,
};
value = js_promise_assimilate_awaitable(js, value);
if (is_err(value)) {
out.state = SV_AWAIT_ERROR;
out.value = value;
return out;
}
if (vtype(value) != T_PROMISE) {
out.value = value;
return out;
}
mco_coro *current_mco = mco_running();
if (!current_mco) current_mco = NULL;
coroutine_t *coro = NULL;
if (current_mco) {
sv_coro_header_t *hdr = (sv_coro_header_t *)mco_get_user_data(current_mco);
if (hdr) coro = hdr->coro;
} else coro = sv_async_get_active_coro_for_vm(js, vm);
if (!coro) {
out.state = SV_AWAIT_ERROR;
out.value = js_mkerr(js, "await can only be used inside async functions");
return out;
}
sv_vm_t *prepared_vm = NULL;
bool handoff = false;
if (!current_mco && vm && coro->owner_vm == vm && !coro->sv_vm) {
prepared_vm = sv_async_prepare_materialization(vm, js, coro);
if (!prepared_vm) {
out.state = SV_AWAIT_ERROR;
out.value = js_mkerr(js, "out of memory for async VM");
return out;
}
handoff = true;
}
coro->is_settled = false;
coro->is_ready = false;
js_await_result_t await_result = js_promise_await_coroutine(js, value, coro);
if (await_result.state == JS_AWAIT_ERROR) {
if (prepared_vm) {
sv_vm_destroy(prepared_vm);
coro->sv_vm = NULL;
coro->materialized = false;
}
coro->is_settled = false;
out.state = SV_AWAIT_ERROR;
out.value = js_throw(js, await_result.value);
return out;
}
coro->did_suspend = true;
if (!current_mco) {
if (prepared_vm) {
if (!sv_async_materialize_activation(vm, prepared_vm, coro)) {
coroutine_clear_await_registration(coro);
sv_vm_destroy(prepared_vm);
out.state = SV_AWAIT_ERROR;
out.value = js_mkerr(js, "failed to materialize async activation");
return out;
}
}
out.state = SV_AWAIT_SUSPENDED;
out.handoff = handoff;
if (handoff) coro->sv_vm->suspended = true;
else if (coro->sv_vm) coro->sv_vm->suspended = true;
return out;
}
mco_result mco_res = mco_yield(current_mco);
if (mco_res != MCO_SUCCESS) {
out.state = SV_AWAIT_ERROR;
out.value = js_mkerr(js, "failed to yield coroutine");
return out;
}
MCO_CORO_STACK_ENTER(js, current_mco);
out.value = coro->result;
bool is_error = coro->is_error;
coro->is_settled = false;
coro->awaited_promise = js_mkundef();
if (is_error) {
out.state = SV_AWAIT_ERROR;
out.value = js_throw(js, out.value);
return out;
}
out.state = SV_AWAIT_READY;
return out;
}
#endif

File Metadata

Mime Type
text/x-c
Expires
Sat, May 2, 5:47 AM (2 d)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
541376
Default Alt Text
async.h (21 KB)

Event Timeline