diff --git a/include/silver/engine.h b/include/silver/engine.h index eb61468..5ac7217 100644 --- a/include/silver/engine.h +++ b/include/silver/engine.h @@ -343,7 +343,6 @@ struct sv_vm { // TODO: move to nested struct bool suspended; - bool async_handoff_pending; bool suspended_resume_pending; bool suspended_resume_is_error; sv_resume_kind_t suspended_resume_kind; diff --git a/include/sugar.h b/include/sugar.h index 251475e..52e66ff 100644 --- a/include/sugar.h +++ b/include/sugar.h @@ -37,6 +37,13 @@ typedef enum { CORO_ASYNC_GENERATOR } coroutine_type_t; +typedef enum { + CORO_HOLD_ACTIVE = 1u << 0, + CORO_HOLD_PENDING = 1u << 1, + CORO_HOLD_GENERATOR = 1u << 2, + CORO_HOLD_AWAIT = 1u << 3, +} coroutine_hold_t; + typedef struct coroutine { ant_t *js; @@ -66,6 +73,9 @@ typedef struct coroutine { int owner_saved_fp; int nargs; + uint32_t refcount; + uint8_t hold_bits; + bool is_settled; bool is_error; bool is_done; @@ -73,7 +83,8 @@ typedef struct coroutine { bool mco_started; bool is_ready; bool did_suspend; - bool free_pending; + bool await_registered; + bool destroy_requested; } coroutine_t; typedef struct { @@ -96,14 +107,23 @@ extern uint32_t coros_this_tick; void enqueue_coroutine(coroutine_t *coro); void remove_coroutine(coroutine_t *coro); -void free_coroutine(coroutine_t *coro); + +void coroutine_retain(coroutine_t *coro); +void coroutine_release(coroutine_t *coro); + +void coroutine_hold(coroutine_t *coro, uint8_t hold); +void coroutine_unhold(coroutine_t *coro, uint8_t hold); + void reap_retired_coroutines(void); +void free_coroutine(coroutine_t *coro); +void coroutine_clear_await_registration(coroutine_t *coro); ant_value_t start_async_in_coroutine(ant_t *js, const char *code, size_t code_len, ant_value_t closure_scope, ant_value_t *args, int nargs); ant_value_t resume_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs); ant_value_t reject_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs); js_await_result_t js_promise_await_coroutine(ant_t *js, ant_value_t promise, coroutine_t *coro); +void js_promise_clear_await_coroutine(ant_t *js, ant_value_t promise, coroutine_t *coro); void settle_and_resume_coroutine(ant_t *js, coroutine_t *coro, ant_value_t value, bool is_error); bool has_ready_coroutines(void); diff --git a/src/ant.c b/src/ant.c index 89c79bf..d631ea8 100644 --- a/src/ant.c +++ b/src/ant.c @@ -11321,6 +11321,25 @@ ant_value_t js_promise_assimilate_awaitable(ant_t *js, ant_value_t value) { return value; } +void js_promise_clear_await_coroutine(ant_t *js, ant_value_t promise, coroutine_t *coro) { + (void)js; + if (vtype(promise) != T_PROMISE || !coro) return; + + ant_promise_state_t *pd = get_promise_data(js, promise, false); + if (!pd || pd->handler_count == 0) return; + + if (pd->handler_count == 1) { + if (pd->inline_handler.await_coro == coro) pd->inline_handler.await_coro = NULL; + return; + } + + if (!pd->handlers) return; + promise_handler_t *h = NULL; + while ((h = (promise_handler_t *)utarray_next(pd->handlers, h))) { + if (h->await_coro == coro) h->await_coro = NULL; + } +} + js_await_result_t js_promise_await_coroutine(ant_t *js, ant_value_t promise, coroutine_t *coro) { js_await_result_t result = { .state = JS_AWAIT_PENDING, @@ -11343,6 +11362,10 @@ js_await_result_t js_promise_await_coroutine(ant_t *js, ant_value_t promise, cor return result; } + coro->awaited_promise = promise; + coro->await_registered = true; + coroutine_hold(coro, CORO_HOLD_AWAIT); + js_mark_promise_rejection_handled_chain(js, promise); if (pd->state == 0) gc_root_pending_promise(js_obj_ptr(js_as_obj(promise))); else queue_promise_trigger(js, promise); diff --git a/src/modules/generator.c b/src/modules/generator.c index 7805038..ba3a467 100644 --- a/src/modules/generator.c +++ b/src/modules/generator.c @@ -182,7 +182,7 @@ static coroutine_t *generator_coro(ant_value_t gen) { 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) free_coroutine(coro); + if (coro) coroutine_unhold(coro, CORO_HOLD_GENERATOR); } coroutine_t *generator_get_coro_for_gc(ant_value_t gen) { @@ -285,25 +285,9 @@ bool generator_resume_pending_request(ant_t *js, coroutine_t *coro, ant_value_t 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) { - coroutine_t *coro = data->coro; - bool linked_active = false; - bool linked_pending = - coro->prev || coro->next || - pending_coroutines.head == coro || - pending_coroutines.tail == coro; - - for (coroutine_t *it = js->active_async_coro; it; it = it->active_parent) if (it == coro) { - linked_active = true; - break; - } - - bool awaiting = vtype(coro->awaited_promise) != T_UNDEF; - if (!linked_pending && !linked_active && !awaiting) free_coroutine(coro); - data->coro = NULL; - } + if (data->coro) generator_clear_coro(gen, data->coro); js_set_native_ptr(gen, NULL); js_set_native_tag(gen, 0); @@ -378,6 +362,7 @@ static ant_value_t generator_resume_kind( coro->active_parent = saved_active; js->active_async_coro = coro; + coroutine_hold(coro, CORO_HOLD_ACTIVE); ant_value_t result; if (state == GEN_SUSPENDED_START) { @@ -398,6 +383,7 @@ static ant_value_t generator_resume_kind( GC_ROOT_PIN(js, result); js->active_async_coro = saved_active; coro->active_parent = NULL; + coroutine_unhold(coro, CORO_HOLD_ACTIVE); if (is_err(result)) { generator_set_state(gen, GEN_COMPLETED); @@ -559,10 +545,15 @@ ant_value_t sv_call_generator_closure_dispatch( .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, - .sv_vm = gen_vm, + .refcount = 1, + .hold_bits = 0, + .await_registered = false, + .destroy_requested = false, }; *data = (generator_data_t){ @@ -570,6 +561,8 @@ ant_value_t sv_call_generator_closure_dispatch( .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); diff --git a/src/reactor.c b/src/reactor.c index feac718..7eb6e4b 100644 --- a/src/reactor.c +++ b/src/reactor.c @@ -49,20 +49,19 @@ void js_poll_events(ant_t *js) { for (;;) { coroutine_t *temp = NULL; - for (coroutine_t *c = pending_coroutines.head; c; c = c->next) { + for (coroutine_t *c = pending_coroutines.head; c; c = c->next) if (c->is_ready && c->mco && mco_status(c->mco) == MCO_SUSPENDED) { temp = c; break; } - } if (!temp) break; temp->is_ready = false; - + coroutine_retain(temp); + mco_result res; MCO_RESUME_SAVE(js, temp->mco, res); - - if (res != MCO_SUCCESS || mco_status(temp->mco) == MCO_DEAD) { + + if (res != MCO_SUCCESS || mco_status(temp->mco) == MCO_DEAD) remove_coroutine(temp); - free_coroutine(temp); - } + coroutine_release(temp); } if (g_poll_hook) g_poll_hook(g_poll_hook_data); diff --git a/src/silver/engine.c b/src/silver/engine.c index 94ed071..22faf0b 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -1702,7 +1702,20 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant L_ITER_GET_VALUE: { sv_op_iter_get_value(vm, js); NEXT(1); } L_ITER_CLOSE: { sv_op_iter_close(vm, js); NEXT(1); } L_ITER_CALL: { VM_CHECK(sv_op_iter_call(vm, js, ip)); NEXT(2); } - L_AWAIT_ITER_NEXT: { VM_CHECK(sv_op_await_iter_next(vm, js)); NEXT(1); } + + L_AWAIT_ITER_NEXT: { + sv_await_result_t await_result = sv_op_await_iter_next(vm, js); + if (await_result.state == SV_AWAIT_ERROR) { + sv_err = await_result.value; + goto sv_throw; + } + if (await_result.state == SV_AWAIT_SUSPENDED) { + if (await_result.handoff) vm_result = js_mkundef(); + goto sv_leave; + } + NEXT(1); + } + L_DESTRUCTURE_INIT: { VM_CHECK(sv_op_destructure_init(vm, js)); NEXT(1); } L_DESTRUCTURE_NEXT: { VM_CHECK(sv_op_destructure_next(vm, js)); NEXT(1); } L_DESTRUCTURE_REST: { VM_CHECK(sv_op_destructure_rest(vm, js)); NEXT(1); } @@ -1714,23 +1727,24 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vm->suspended_entry_fp = entry_fp; vm->suspended_saved_fp = entry_fp - 1; - ant_value_t result = sv_await_value(vm, js, await_val); - if (vm->async_handoff_pending) { - vm->async_handoff_pending = false; + sv_await_result_t await_result = sv_await_value(vm, js, await_val); + if (await_result.state == SV_AWAIT_SUSPENDED && await_result.handoff) { + vm->suspended_entry_fp = -1; + vm->suspended_saved_fp = -1; vm_result = js_mkundef(); goto sv_leave; } - if (vm->suspended) goto sv_leave; + if (await_result.state == SV_AWAIT_SUSPENDED) goto sv_leave; vm->suspended_entry_fp = -1; vm->suspended_saved_fp = -1; - if (is_err(result)) { - sv_err = result; + if (await_result.state == SV_AWAIT_ERROR) { + sv_err = await_result.value; goto sv_throw; } - vm->stack[vm->sp++] = result; + vm->stack[vm->sp++] = await_result.value; NEXT(1); } diff --git a/src/silver/ops/async.h b/src/silver/ops/async.h index e54a59b..4e6f396 100644 --- a/src/silver/ops/async.h +++ b/src/silver/ops/async.h @@ -88,29 +88,48 @@ typedef struct { 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; 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 (js->active_async_coro == coro) js->active_async_coro = coro->active_parent; coro->active_parent = 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; - coroutine_t *fallback = js->active_async_coro; - if (!vm) return fallback; + if (!vm) return js->active_async_coro; - for (coroutine_t *it = fallback; it; it = it->active_parent) { - if (it->owner_vm == vm) return it; + for (coroutine_t *it = js->active_async_coro; it; it = it->active_parent) { + if (sv_async_coro_matches_vm(it, vm)) return it; } - return fallback; + return NULL; } static inline void sv_async_init_activation( @@ -141,6 +160,8 @@ static inline void sv_async_init_activation( .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, @@ -148,7 +169,8 @@ static inline void sv_async_init_activation( .mco_started = false, .is_ready = false, .did_suspend = false, - .free_pending = false, + .await_registered = false, + .destroy_requested = false, }; } @@ -183,10 +205,15 @@ static inline sv_vm_t *sv_async_prepare_materialization( if (!source_vm || !js || !coro || coro->sv_vm) return coro ? coro->sv_vm : NULL; if (source_vm->fp < 0) return NULL; - sv_frame_t *source_frame = &source_vm->frames[source_vm->fp]; - int stack_base = source_frame->prev_sp; + 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_count = source_vm->handler_depth - source_frame->handler_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; @@ -194,6 +221,10 @@ static inline sv_vm_t *sv_async_prepare_materialization( 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; @@ -207,22 +238,26 @@ static inline sv_vm_t *sv_async_prepare_materialization( ); } async_vm->sp = stack_count; - - async_vm->fp = 0; - async_vm->frames[0] = *source_frame; - async_vm->frames[0].prev_sp = 0; - async_vm->frames[0].handler_base = 0; - async_vm->frames[0].handler_top = handler_count; - - if (source_frame->bp) - async_vm->frames[0].bp = async_vm->stack + (source_frame->bp - &source_vm->stack[stack_base]); - if (source_frame->lp) - async_vm->frames[0].lp = async_vm->stack + (source_frame->lp - &source_vm->stack[stack_base]); + 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[source_frame->handler_base], + &source_vm->handler_stack[handler_base], sizeof(sv_handler_t) * (size_t)handler_count ); } @@ -239,47 +274,25 @@ static inline sv_vm_t *sv_async_prepare_materialization( return async_vm; } -static inline coroutine_t *sv_async_create_materialized_coro( - sv_vm_t *async_vm, coroutine_t *coro -) { - if (!async_vm || !coro) return NULL; - - coroutine_t *heap_coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t)); - if (!heap_coro) { - sv_vm_destroy(async_vm); - return NULL; - } - - *heap_coro = *coro; - heap_coro->prev = NULL; - heap_coro->next = NULL; - heap_coro->sv_vm = async_vm; - heap_coro->materialized = true; - heap_coro->free_pending = false; - return heap_coro; -} - -static inline void sv_async_finalize_materialization( - sv_vm_t *source_vm, sv_vm_t *async_vm, - coroutine_t *pending_coro, coroutine_t *heap_coro +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 || !pending_coro || !heap_coro || source_vm->fp < 0) return; - - sv_frame_t *source_frame = &source_vm->frames[source_vm->fp]; - ant_value_t *source_base = &source_vm->stack[source_frame->prev_sp]; - size_t stack_count = (size_t)(source_vm->sp - source_frame->prev_sp); + 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 ); - pending_coro->owner_entry_fp = source_vm->fp; - pending_coro->owner_saved_fp = source_vm->fp - 1; - pending_coro->sv_vm = async_vm; - pending_coro->materialized = true; - heap_coro->owner_entry_fp = source_vm->fp; - heap_coro->owner_saved_fp = source_vm->fp - 1; - source_vm->async_handoff_pending = true; + 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) { @@ -317,35 +330,26 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t GC_ROOT_PIN(js, this_val); GC_ROOT_PIN(js, promise); - sv_vm_t *async_vm = sv_vm_create(js, SV_VM_ASYNC); - if (!async_vm) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr(js, "out of memory for TLA VM"); - } - coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t)); if (!coro) { - sv_vm_destroy(async_vm); GC_ROOT_RESTORE(js, root_mark); return js_mkerr(js, "out of memory for TLA coroutine"); } sv_async_init_activation( - coro, js, async_vm, promise, this_val, + coro, js, js->vm, promise, this_val, js_mkundef(), js_mkundef(), js_mkundef(), 0 ); - coro->sv_vm = async_vm; - coro->materialized = true; sv_async_link_activation(js, coro); ant_value_t result = sv_execute_entry( - async_vm, func, + js->vm, func, this_val, NULL, 0 ); + sv_async_unlink_activation(js, coro); - if (async_vm->suspended) { - sv_async_unlink_activation(js, coro); - enqueue_coroutine(coro); + if (coro->sv_vm && coro->sv_vm->suspended) { + coroutine_release(coro); GC_ROOT_RESTORE(js, root_mark); return promise; } @@ -358,8 +362,7 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t } else { js_resolve_promise(js, promise, result); } - sv_async_unlink_activation(js, coro); - free_coroutine(coro); + coroutine_release(coro); GC_ROOT_RESTORE(js, root_mark); return promise; @@ -421,10 +424,15 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t .async_promise = promise, .next = NULL, .mco = mco, + .owner_vm = async_vm, + .sv_vm = async_vm, .mco_started = false, .is_ready = true, .did_suspend = false, - .sv_vm = async_vm, + .refcount = 1, + .hold_bits = 0, + .await_registered = false, + .destroy_requested = false, }; ctx->coro = coro; @@ -433,16 +441,17 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t if (res != MCO_SUCCESS && mco_status(mco) != MCO_DEAD) { remove_coroutine(coro); - free_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); - free_coroutine(coro); } + coroutine_release(coro); + return promise; } @@ -497,21 +506,27 @@ static inline ant_value_t sv_start_async_closure( ant_value_t promise = js_mkpromise(js); GC_ROOT_PIN(js, promise); - coroutine_t pending_coro; + 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( - &pending_coro, js, caller_vm, promise, this_val, + coro, js, caller_vm, promise, this_val, super_val, js->new_target, callee_func, argc ); - sv_async_link_activation(js, &pending_coro); + 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, &pending_coro); + sv_async_unlink_activation(js, coro); - if (pending_coro.materialized && pending_coro.sv_vm) { + if (coro->sv_vm && coro->sv_vm->suspended) { + coroutine_release(coro); GC_ROOT_RESTORE(js, root_mark); return promise; } @@ -524,6 +539,7 @@ static inline ant_value_t sv_start_async_closure( } else { js_resolve_promise(js, promise, result); } + coroutine_release(coro); GC_ROOT_RESTORE(js, root_mark); return promise; @@ -601,10 +617,15 @@ static inline ant_value_t sv_start_async_closure( .async_promise = promise, .next = NULL, .mco = mco, + .owner_vm = async_vm, + .sv_vm = async_vm, .mco_started = false, .is_ready = true, .did_suspend = false, - .sv_vm = async_vm, + .refcount = 1, + .hold_bits = 0, + .await_registered = false, + .destroy_requested = false, }; ctx->coro = coro; @@ -614,25 +635,39 @@ static inline ant_value_t sv_start_async_closure( if (res != MCO_SUCCESS && start_status != MCO_DEAD) { remove_coroutine(coro); - free_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); - free_coroutine(coro); } + coroutine_release(coro); + return promise; } -static inline ant_value_t sv_await_value(sv_vm_t *vm, ant_t *js, ant_value_t value) { +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)) return value; - if (vtype(value) != T_PROMISE) return 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; @@ -643,52 +678,79 @@ static inline ant_value_t sv_await_value(sv_vm_t *vm, ant_t *js, ant_value_t val if (hdr) coro = hdr->coro; } else coro = sv_async_get_active_coro_for_vm(js, vm); - if (!coro) - return js_mkerr(js, "await can only be used inside async functions"); + 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; - coroutine_t *pending_coro = coro; + 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) return js_mkerr(js, "out of memory for async VM"); - coroutine_t *materialized_coro = sv_async_create_materialized_coro(prepared_vm, coro); - if (!materialized_coro) return js_mkerr(js, "out of memory for coroutine"); - coro = materialized_coro; - enqueue_coroutine(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->awaited_promise = value; 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) free_coroutine(coro); + if (prepared_vm) { + sv_vm_destroy(prepared_vm); + coro->sv_vm = NULL; + coro->materialized = false; + } coro->is_settled = false; - coro->awaited_promise = js_mkundef(); - return js_throw(js, await_result.value); + 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) sv_async_finalize_materialization(vm, prepared_vm, pending_coro, coro); - if (coro->sv_vm) coro->sv_vm->suspended = true; - return js_mkundef(); + 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) - return js_mkerr(js, "failed to yield coroutine"); + 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); - ant_value_t result = coro->result; + out.value = coro->result; bool is_error = coro->is_error; coro->is_settled = false; coro->awaited_promise = js_mkundef(); - if (is_error) return js_throw(js, result); + if (is_error) { + out.state = SV_AWAIT_ERROR; + out.value = js_throw(js, out.value); + return out; + } - return result; + out.state = SV_AWAIT_READY; + return out; } diff --git a/src/silver/ops/iteration.h b/src/silver/ops/iteration.h index 7364ef5..a5307e6 100644 --- a/src/silver/ops/iteration.h +++ b/src/silver/ops/iteration.h @@ -358,32 +358,52 @@ static inline ant_value_t sv_op_iter_call(sv_vm_t *vm, ant_t *js, uint8_t *ip) { return tov(0); } -static inline ant_value_t sv_op_await_iter_next(sv_vm_t *vm, ant_t *js) { +static inline sv_await_result_t sv_op_await_iter_next(sv_vm_t *vm, ant_t *js) { + sv_await_result_t out = { + .state = SV_AWAIT_READY, + .value = js_mkundef(), + .handoff = false, + }; ant_value_t next_method = vm->stack[vm->sp - 2]; ant_value_t iterator = vm->stack[vm->sp - 3]; uint8_t ft = vtype(next_method); if (ft != T_FUNC && ft != T_CFUNC) - return js_mkerr(js, "iterator.next is not a function"); + return (sv_await_result_t){ .state = SV_AWAIT_ERROR, .value = js_mkerr(js, "iterator.next is not a function"), .handoff = false }; ant_value_t result = sv_vm_call(vm, js, next_method, iterator, NULL, 0, NULL, false); - if (is_err(result)) return result; + if (is_err(result)) + return (sv_await_result_t){ .state = SV_AWAIT_ERROR, .value = result, .handoff = false }; if (vtype(result) == T_PROMISE) { - ant_value_t awaited = sv_await_value(vm, js, result); - if (is_err(awaited)) return awaited; - result = awaited; + vm->suspended_entry_fp = vm->fp; + vm->suspended_saved_fp = vm->fp - 1; + sv_await_result_t awaited = sv_await_value(vm, js, result); + if (awaited.state != SV_AWAIT_SUSPENDED || awaited.handoff) { + vm->suspended_entry_fp = -1; + vm->suspended_saved_fp = -1; + } + if (awaited.state != SV_AWAIT_READY) return awaited; + result = awaited.value; } ant_value_t done = js_mkundef(); ant_value_t value = js_mkundef(); sv_iter_result_unpack(js, result, &done, &value); - if (is_err(done)) return done; - if (is_err(value)) return value; + if (is_err(done)) + return (sv_await_result_t){ .state = SV_AWAIT_ERROR, .value = done, .handoff = false }; + if (is_err(value)) + return (sv_await_result_t){ .state = SV_AWAIT_ERROR, .value = value, .handoff = false }; if (vtype(value) == T_PROMISE) { - ant_value_t awaited_val = sv_await_value(vm, js, value); - if (is_err(awaited_val)) return awaited_val; - value = awaited_val; + vm->suspended_entry_fp = vm->fp; + vm->suspended_saved_fp = vm->fp - 1; + sv_await_result_t awaited_val = sv_await_value(vm, js, value); + if (awaited_val.state != SV_AWAIT_SUSPENDED || awaited_val.handoff) { + vm->suspended_entry_fp = -1; + vm->suspended_saved_fp = -1; + } + if (awaited_val.state != SV_AWAIT_READY) return awaited_val; + value = awaited_val.value; } vm->stack[vm->sp++] = value; vm->stack[vm->sp++] = mkval(T_BOOL, js_truthy(js, done)); - return tov(0); + return out; } #endif diff --git a/src/sugar.c b/src/sugar.c index 7a133f7..1164b18 100644 --- a/src/sugar.c +++ b/src/sugar.c @@ -32,6 +32,7 @@ bool has_ready_coroutines(void) { void enqueue_coroutine(coroutine_t *coro) { if (!coro) return; + if (coro->hold_bits & CORO_HOLD_PENDING) return; coro->next = NULL; coro->prev = pending_coroutines.tail; @@ -39,10 +40,11 @@ void enqueue_coroutine(coroutine_t *coro) { pending_coroutines.tail->next = coro; } else pending_coroutines.head = coro; pending_coroutines.tail = coro; + coroutine_hold(coro, CORO_HOLD_PENDING); } void remove_coroutine(coroutine_t *coro) { - if (!coro) return; + if (!coro || !(coro->hold_bits & CORO_HOLD_PENDING)) return; if (coro->prev) { coro->prev->next = coro->next; @@ -54,34 +56,7 @@ void remove_coroutine(coroutine_t *coro) { coro->prev = NULL; coro->next = NULL; -} - -static void clear_await_coro_from_promise_state(ant_promise_state_t *pd, coroutine_t *coro) { - if (!pd || !coro || pd->handler_count == 0) return; - - if (pd->handler_count == 1) { - if (pd->inline_handler.await_coro == coro) pd->inline_handler.await_coro = NULL; - return; - } - - if (!pd->handlers) return; - promise_handler_t *h = NULL; - - while ((h = (promise_handler_t *)utarray_next(pd->handlers, h))) - if (h->await_coro == coro) h->await_coro = NULL; -} - -static void clear_await_coro_from_object_list(ant_object_t *head, coroutine_t *coro) { - for (ant_object_t *obj = head; obj; obj = obj->next) - if (obj->promise_state) clear_await_coro_from_promise_state(obj->promise_state, coro); -} - -static inline bool coroutine_is_queued(coroutine_t *coro) { - return coro && ( - coro->prev || coro->next || - pending_coroutines.head == coro || - pending_coroutines.tail == coro - ); + coroutine_unhold(coro, CORO_HOLD_PENDING); } static void retire_coroutine_storage(coroutine_t *coro) { @@ -117,6 +92,41 @@ static void destroy_coroutine_resources(coroutine_t *coro) { coro->materialized = false; } +void coroutine_retain(coroutine_t *coro) { + if (!coro) return; + coro->refcount++; +} + +static void coroutine_release_storage(coroutine_t *coro) { + if (!coro) return; + + ant_t *js = coro->js; + if (js && js->vm_exec_depth > 0) retire_coroutine_storage(coro); + else { + destroy_coroutine_resources(coro); + CORO_FREE(coro); + } +} + +void coroutine_release(coroutine_t *coro) { + if (!coro || coro->refcount == 0) return; + coro->refcount--; + if (coro->refcount != 0) return; + coroutine_release_storage(coro); +} + +void coroutine_hold(coroutine_t *coro, uint8_t hold) { + if (!coro || (coro->hold_bits & hold)) return; + coro->hold_bits |= hold; + coroutine_retain(coro); +} + +void coroutine_unhold(coroutine_t *coro, uint8_t hold) { + if (!coro || !(coro->hold_bits & hold)) return; + coro->hold_bits &= (uint8_t)~hold; + coroutine_release(coro); +} + void reap_retired_coroutines(void) { coroutine_t *coro = retired_coroutines; retired_coroutines = NULL; @@ -129,30 +139,24 @@ void reap_retired_coroutines(void) { } } -void free_coroutine(coroutine_t *coro) { - if (!coro || coro->free_pending) return; - coro->free_pending = true; +void coroutine_clear_await_registration(coroutine_t *coro) { + if (!coro || !coro->await_registered) return; ant_t *js = coro->js; - if (js) { - clear_await_coro_from_object_list(js->objects, coro); - clear_await_coro_from_object_list(js->objects_old, coro); - clear_await_coro_from_object_list(js->permanent_objects, coro); - - if ( - coro->prev || coro->next || - pending_coroutines.head == coro || - pending_coroutines.tail == coro - ) remove_coroutine(coro); - - if (js->active_async_coro == coro) js->active_async_coro = coro->active_parent; - coro->active_parent = NULL; - } + ant_value_t promise = coro->awaited_promise; + coro->await_registered = false; + coro->awaited_promise = js_mkundef(); - if (!js || js->vm_exec_depth == 0) - destroy_coroutine_resources(coro); + if (js && vtype(promise) == T_PROMISE) + js_promise_clear_await_coroutine(js, promise, coro); - retire_coroutine_storage(coro); + coroutine_unhold(coro, CORO_HOLD_AWAIT); +} + +void free_coroutine(coroutine_t *coro) { + if (!coro) return; + coroutine_clear_await_registration(coro); + coroutine_release(coro); } static size_t calculate_coro_stack_size(void) { @@ -180,9 +184,13 @@ static inline void settle_coroutine(coroutine_t *coro, ant_value_t *args, int na static void resume_coroutine_if_suspended(ant_t *js, coroutine_t *coro) { if (!coro) return; + coroutine_retain(coro); if (!coro->mco) { - if (!coro->sv_vm || !coro->sv_vm->suspended) return; + if (!coro->sv_vm || !coro->sv_vm->suspended) { + coroutine_release(coro); + return; + } coro->is_ready = false; coro->sv_vm->suspended_resume_value = coro->result; @@ -192,21 +200,27 @@ static void resume_coroutine_if_suspended(ant_t *js, coroutine_t *coro) { coro->active_parent = js->active_async_coro; js->active_async_coro = coro; + coroutine_hold(coro, CORO_HOLD_ACTIVE); ant_value_t result = sv_resume_suspended(coro->sv_vm); coro->is_settled = false; if (coro->sv_vm->suspended) { js->active_async_coro = coro->active_parent; coro->active_parent = NULL; + coroutine_unhold(coro, CORO_HOLD_ACTIVE); if (generator_resume_pending_request(js, coro, result)) return; + coroutine_release(coro); return; } js->active_async_coro = coro->active_parent; coro->active_parent = NULL; + coroutine_unhold(coro, CORO_HOLD_ACTIVE); - if (generator_resume_pending_request(js, coro, result)) return; - if (coroutine_is_queued(coro)) remove_coroutine(coro); + if (generator_resume_pending_request(js, coro, result)) { + coroutine_release(coro); + return; + } if (is_err(result)) { ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; @@ -216,22 +230,25 @@ static void resume_coroutine_if_suspended(ant_t *js, coroutine_t *coro) { } else js_resolve_promise(js, coro->async_promise, result); js_maybe_drain_microtasks_after_async_settle(js); - free_coroutine(coro); + coroutine_release(coro); return; } - if (mco_status(coro->mco) != MCO_SUSPENDED) return; + if (mco_status(coro->mco) != MCO_SUSPENDED) { + coroutine_release(coro); + return; + } coro->is_ready = false; mco_result res; MCO_RESUME_SAVE(js, coro->mco, res); mco_state status = mco_status(coro->mco); - if (res != MCO_SUCCESS || status == MCO_DEAD) { + if (res != MCO_SUCCESS || status == MCO_DEAD) remove_coroutine(coro); - free_coroutine(coro); - } + + coroutine_release(coro); } ant_value_t resume_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs) { @@ -265,8 +282,11 @@ ant_value_t reject_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs) { void settle_and_resume_coroutine(ant_t *js, coroutine_t *coro, ant_value_t value, bool is_error) { if (!coro) return; + coroutine_retain(coro); + coroutine_clear_await_registration(coro); + ant_value_t args[1] = { value }; settle_coroutine(coro, args, 1, is_error); - coro->awaited_promise = js_mkundef(); resume_coroutine_if_suspended(js, coro); + coroutine_release(coro); }