fixed the garbage collection of async functions with closures (github issue #156)

This commit is contained in:
Fabrice Bellard 2023-12-27 17:21:46 +01:00
parent 399d916e66
commit 7414e5f67f
2 changed files with 302 additions and 247 deletions

525
quickjs.c
View file

@ -318,17 +318,18 @@ struct JSClass {
#define JS_MODE_STRICT (1 << 0)
#define JS_MODE_STRIP (1 << 1)
#define JS_MODE_MATH (1 << 2)
#define JS_MODE_ASYNC (1 << 3) /* async function */
typedef struct JSStackFrame {
struct JSStackFrame *prev_frame; /* NULL if first stack frame */
JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */
JSValue *arg_buf; /* arguments */
JSValue *var_buf; /* variables */
struct list_head var_ref_list; /* list of JSVarRef.link */
struct list_head var_ref_list; /* list of JSVarRef.var_ref_link */
const uint8_t *cur_pc; /* only used in bytecode functions : PC of the
instruction after the call */
int arg_count;
int js_mode; /* 0 or JS_MODE_MATH for C functions */
int js_mode; /* for C functions, only JS_MODE_MATH may be set */
/* only used in generators. Current stack pointer value. NULL if
the function is running. */
JSValue *cur_sp;
@ -361,11 +362,6 @@ typedef struct JSVarRef {
struct {
int __gc_ref_count; /* corresponds to header.ref_count */
uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */
/* 0 : the JSVarRef is on the stack. header.link is an element
of JSStackFrame.var_ref_list.
1 : the JSVarRef is detached. header.link has the normal meanning
*/
uint8_t is_detached : 1;
uint8_t is_arg : 1;
uint16_t var_idx; /* index of the corresponding function variable on
@ -374,7 +370,13 @@ typedef struct JSVarRef {
};
JSValue *pvalue; /* pointer to the value, either on the stack or
to 'value' */
JSValue value; /* used when the variable is no longer on the stack */
union {
JSValue value; /* used when is_detached = TRUE */
struct {
struct list_head var_ref_link; /* JSStackFrame.var_ref_list list */
struct JSAsyncFunctionState *async_func; /* != NULL if async stack frame */
}; /* used when is_detached = FALSE */
};
} JSVarRef;
/* the same structure is used for big integers and big floats. Big
@ -671,21 +673,16 @@ typedef struct JSTypedArray {
} JSTypedArray;
typedef struct JSAsyncFunctionState {
JSValue this_val; /* 'this' generator argument */
JSGCObjectHeader header;
JSValue this_val; /* 'this' argument */
int argc; /* number of function arguments */
BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */
BOOL is_completed; /* TRUE if the function has returned. The stack
frame is no longer valid */
JSValue resolving_funcs[2]; /* only used in JS async functions */
JSStackFrame frame;
} JSAsyncFunctionState;
/* XXX: could use an object instead to avoid the
JS_TAG_ASYNC_FUNCTION tag for the GC */
typedef struct JSAsyncFunctionData {
JSGCObjectHeader header; /* must come first */
JSValue resolving_funcs[2];
BOOL is_active; /* true if the async function state is valid */
JSAsyncFunctionState func_state;
} JSAsyncFunctionData;
typedef enum {
/* binary operators */
JS_OVOP_ADD,
@ -925,7 +922,7 @@ struct JSObject {
struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */
struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */
struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */
struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */
struct JSAsyncFunctionState *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */
struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */
struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */
@ -1201,6 +1198,8 @@ static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p);
static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx);
static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx,
BOOL is_arg);
static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s);
static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s);
static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
JSValueConst this_obj,
int argc, JSValueConst *argv,
@ -1239,8 +1238,6 @@ static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val);
static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc,
JSObject *p, JSAtom prop);
static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc);
static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s,
JS_MarkFunc *mark_func);
static void JS_AddIntrinsicBasicObjects(JSContext *ctx);
static void js_free_shape(JSRuntime *rt, JSShape *sh);
static void js_free_shape_null(JSRuntime *rt, JSShape *sh);
@ -1268,7 +1265,6 @@ static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val);
static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h,
JSGCObjectTypeEnum type);
static void remove_gc_object(JSGCObjectHeader *h);
static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s);
static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque);
static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom,
void *opaque);
@ -5254,10 +5250,12 @@ static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref)
if (--var_ref->header.ref_count == 0) {
if (var_ref->is_detached) {
JS_FreeValueRT(rt, var_ref->value);
remove_gc_object(&var_ref->header);
} else {
list_del(&var_ref->header.link); /* still on the stack */
list_del(&var_ref->var_ref_link); /* still on the stack */
if (var_ref->async_func)
async_func_free(rt, var_ref->async_func);
}
remove_gc_object(&var_ref->header);
js_free_rt(rt, var_ref);
}
}
@ -5355,7 +5353,7 @@ static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val,
if (var_refs) {
for(i = 0; i < b->closure_var_count; i++) {
JSVarRef *var_ref = var_refs[i];
if (var_ref && var_ref->is_detached) {
if (var_ref) {
mark_func(rt, &var_ref->header);
}
}
@ -5465,6 +5463,9 @@ static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp)
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE:
free_function_bytecode(rt, (JSFunctionBytecode *)gp);
break;
case JS_GC_OBJ_TYPE_ASYNC_FUNCTION:
__async_func_free(rt, (JSAsyncFunctionState *)gp);
break;
default:
abort();
}
@ -5623,11 +5624,9 @@ static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp,
if (pr->u.getset.setter)
mark_func(rt, &pr->u.getset.setter->header);
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) {
if (pr->u.var_ref->is_detached) {
/* Note: the tag does not matter
provided it is a GC object */
mark_func(rt, &pr->u.var_ref->header);
}
/* Note: the tag does not matter
provided it is a GC object */
mark_func(rt, &pr->u.var_ref->header);
} else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) {
js_autoinit_mark(rt, pr, mark_func);
}
@ -5661,16 +5660,32 @@ static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp,
case JS_GC_OBJ_TYPE_VAR_REF:
{
JSVarRef *var_ref = (JSVarRef *)gp;
/* only detached variable referenced are taken into account */
assert(var_ref->is_detached);
JS_MarkValue(rt, *var_ref->pvalue, mark_func);
if (var_ref->is_detached) {
JS_MarkValue(rt, *var_ref->pvalue, mark_func);
} else if (var_ref->async_func) {
mark_func(rt, &var_ref->async_func->header);
}
}
break;
case JS_GC_OBJ_TYPE_ASYNC_FUNCTION:
{
JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp;
if (s->is_active)
async_func_mark(rt, &s->func_state, mark_func);
JSAsyncFunctionState *s = (JSAsyncFunctionState *)gp;
JSStackFrame *sf = &s->frame;
JSValue *sp;
if (!s->is_completed) {
JS_MarkValue(rt, sf->cur_func, mark_func);
JS_MarkValue(rt, s->this_val, mark_func);
/* sf->cur_sp = NULL if the function is running */
if (sf->cur_sp) {
/* if the function is running, cur_sp is not known so we
cannot mark the stack. Marking the variables is not needed
because a running function cannot be part of a removable
cycle */
for(sp = sf->arg_buf; sp < sf->cur_sp; sp++)
JS_MarkValue(rt, *sp, mark_func);
}
}
JS_MarkValue(rt, s->resolving_funcs[0], mark_func);
JS_MarkValue(rt, s->resolving_funcs[1], mark_func);
}
@ -5778,12 +5793,13 @@ static void gc_free_cycles(JSRuntime *rt)
if (el == &rt->tmp_obj_list)
break;
p = list_entry(el, JSGCObjectHeader, link);
/* Only need to free the GC object associated with JS
values. The rest will be automatically removed because they
must be referenced by them. */
/* Only need to free the GC object associated with JS values
or async functions. The rest will be automatically removed
because they must be referenced by them. */
switch(p->gc_obj_type) {
case JS_GC_OBJ_TYPE_JS_OBJECT:
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE:
case JS_GC_OBJ_TYPE_ASYNC_FUNCTION:
#ifdef DUMP_GC_FREE
if (!header_done) {
printf("Freeing cycles:\n");
@ -5805,7 +5821,8 @@ static void gc_free_cycles(JSRuntime *rt)
list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) {
p = list_entry(el, JSGCObjectHeader, link);
assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT ||
p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE);
p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE ||
p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION);
js_free_rt(rt, p);
}
@ -15429,7 +15446,7 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf,
struct list_head *el;
list_for_each(el, &sf->var_ref_list) {
var_ref = list_entry(el, JSVarRef, header.link);
var_ref = list_entry(el, JSVarRef, var_ref_link);
if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) {
var_ref->header.ref_count++;
return var_ref;
@ -15440,15 +15457,29 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf,
if (!var_ref)
return NULL;
var_ref->header.ref_count = 1;
add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF);
var_ref->is_detached = FALSE;
var_ref->is_arg = is_arg;
var_ref->var_idx = var_idx;
list_add_tail(&var_ref->header.link, &sf->var_ref_list);
list_add_tail(&var_ref->var_ref_link, &sf->var_ref_list);
if (sf->js_mode & JS_MODE_ASYNC) {
/* The stack frame is detached and may be destroyed at any
time so its reference count must be increased. Calling
close_var_refs() when destroying the stack frame is not
possible because it would change the graph between the GC
objects. Another solution could be to temporarily detach
the JSVarRef of async functions during the GC. It would
have the advantage of allowing the release of unused stack
frames in a cycle. */
var_ref->async_func = container_of(sf, JSAsyncFunctionState, frame);
var_ref->async_func->header.ref_count++;
} else {
var_ref->async_func = NULL;
}
if (is_arg)
var_ref->pvalue = &sf->arg_buf[var_idx];
else
var_ref->pvalue = &sf->var_buf[var_idx];
var_ref->value = JS_UNDEFINED;
return var_ref;
}
@ -15679,7 +15710,10 @@ static void close_var_refs(JSRuntime *rt, JSStackFrame *sf)
int var_idx;
list_for_each_safe(el, el1, &sf->var_ref_list) {
var_ref = list_entry(el, JSVarRef, header.link);
var_ref = list_entry(el, JSVarRef, var_ref_link);
/* no need to unlink var_ref->var_ref_link as the list is never used afterwards */
if (var_ref->async_func)
async_func_free(rt, var_ref->async_func);
var_idx = var_ref->var_idx;
if (var_ref->is_arg)
var_ref->value = JS_DupValueRT(rt, sf->arg_buf[var_idx]);
@ -15688,7 +15722,6 @@ static void close_var_refs(JSRuntime *rt, JSStackFrame *sf)
var_ref->pvalue = &var_ref->value;
/* the reference is no longer to a local variable */
var_ref->is_detached = TRUE;
add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF);
}
}
@ -15699,14 +15732,15 @@ static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int idx, int is_
int var_idx = idx;
list_for_each_safe(el, el1, &sf->var_ref_list) {
var_ref = list_entry(el, JSVarRef, header.link);
var_ref = list_entry(el, JSVarRef, var_ref_link);
if (var_idx == var_ref->var_idx && var_ref->is_arg == is_arg) {
list_del(&var_ref->var_ref_link);
if (var_ref->async_func)
async_func_free(ctx->rt, var_ref->async_func);
var_ref->value = JS_DupValue(ctx, sf->var_buf[var_idx]);
var_ref->pvalue = &var_ref->value;
list_del(&var_ref->header.link);
/* the reference is no longer to a local variable */
var_ref->is_detached = TRUE;
add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF);
}
}
}
@ -15897,9 +15931,10 @@ typedef enum {
OP_SPECIAL_OBJECT_IMPORT_META,
} OPSpecialObjectEnum;
#define FUNC_RET_AWAIT 0
#define FUNC_RET_YIELD 1
#define FUNC_RET_YIELD_STAR 2
#define FUNC_RET_AWAIT 0
#define FUNC_RET_YIELD 1
#define FUNC_RET_YIELD_STAR 2
#define FUNC_RET_INITIAL_YIELD 3
/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */
static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
@ -18303,9 +18338,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD_STAR);
goto done_generator;
CASE(OP_return_async):
CASE(OP_initial_yield):
ret_val = JS_UNDEFINED;
goto done_generator;
CASE(OP_initial_yield):
ret_val = JS_NewInt32(ctx, FUNC_RET_INITIAL_YIELD);
goto done_generator;
CASE(OP_nop):
BREAK;
@ -18587,26 +18624,35 @@ static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom,
}
/* JSAsyncFunctionState (used by generator and async functions) */
static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s,
JSValueConst func_obj, JSValueConst this_obj,
int argc, JSValueConst *argv)
static JSAsyncFunctionState *async_func_init(JSContext *ctx,
JSValueConst func_obj, JSValueConst this_obj,
int argc, JSValueConst *argv)
{
JSAsyncFunctionState *s;
JSObject *p;
JSFunctionBytecode *b;
JSStackFrame *sf;
int local_count, i, arg_buf_len, n;
s = js_mallocz(ctx, sizeof(*s));
if (!s)
return NULL;
s->header.ref_count = 1;
add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION);
sf = &s->frame;
init_list_head(&sf->var_ref_list);
p = JS_VALUE_GET_OBJ(func_obj);
b = p->u.func.function_bytecode;
sf->js_mode = b->js_mode;
sf->js_mode = b->js_mode | JS_MODE_ASYNC;
sf->cur_pc = b->byte_code_buf;
arg_buf_len = max_int(b->arg_count, argc);
local_count = arg_buf_len + b->var_count + b->stack_size;
sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1));
if (!sf->arg_buf)
return -1;
if (!sf->arg_buf) {
js_free(ctx, s);
return NULL;
}
sf->cur_func = JS_DupValue(ctx, func_obj);
s->this_val = JS_DupValue(ctx, this_obj);
s->argc = argc;
@ -18618,38 +18664,17 @@ static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s,
n = arg_buf_len + b->var_count;
for(i = argc; i < n; i++)
sf->arg_buf[i] = JS_UNDEFINED;
return 0;
s->resolving_funcs[0] = JS_UNDEFINED;
s->resolving_funcs[1] = JS_UNDEFINED;
s->is_completed = FALSE;
return s;
}
static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s,
JS_MarkFunc *mark_func)
static void async_func_free_frame(JSRuntime *rt, JSAsyncFunctionState *s)
{
JSStackFrame *sf;
JSStackFrame *sf = &s->frame;
JSValue *sp;
sf = &s->frame;
JS_MarkValue(rt, sf->cur_func, mark_func);
JS_MarkValue(rt, s->this_val, mark_func);
if (sf->cur_sp) {
/* if the function is running, cur_sp is not known so we
cannot mark the stack. Marking the variables is not needed
because a running function cannot be part of a removable
cycle */
for(sp = sf->arg_buf; sp < sf->cur_sp; sp++)
JS_MarkValue(rt, *sp, mark_func);
}
}
static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s)
{
JSStackFrame *sf;
JSValue *sp;
sf = &s->frame;
/* close the closure variables. */
close_var_refs(rt, sf);
if (sf->arg_buf) {
/* cannot free the function if it is running */
assert(sf->cur_sp != NULL);
@ -18657,6 +18682,7 @@ static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s)
JS_FreeValueRT(rt, *sp);
}
js_free_rt(rt, sf->arg_buf);
sf->arg_buf = NULL;
}
JS_FreeValueRT(rt, sf->cur_func);
JS_FreeValueRT(rt, s->this_val);
@ -18664,17 +18690,66 @@ static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s)
static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s)
{
JSValue func_obj;
JSRuntime *rt = ctx->rt;
JSStackFrame *sf = &s->frame;
JSValue func_obj, ret;
if (js_check_stack_overflow(ctx->rt, 0))
return JS_ThrowStackOverflow(ctx);
assert(!s->is_completed);
if (js_check_stack_overflow(ctx->rt, 0)) {
ret = JS_ThrowStackOverflow(ctx);
} else {
/* the tag does not matter provided it is not an object */
func_obj = JS_MKPTR(JS_TAG_INT, s);
ret = JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED,
s->argc, sf->arg_buf, JS_CALL_FLAG_GENERATOR);
}
if (JS_IsException(ret) || JS_IsUndefined(ret)) {
if (JS_IsUndefined(ret)) {
ret = sf->cur_sp[-1];
sf->cur_sp[-1] = JS_UNDEFINED;
}
/* end of execution */
s->is_completed = TRUE;
/* the tag does not matter provided it is not an object */
func_obj = JS_MKPTR(JS_TAG_INT, s);
return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED,
s->argc, s->frame.arg_buf, JS_CALL_FLAG_GENERATOR);
/* close the closure variables. */
close_var_refs(rt, sf);
async_func_free_frame(rt, s);
}
return ret;
}
static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s)
{
/* cannot close the closure variables here because it would
potentially modify the object graph */
if (!s->is_completed) {
async_func_free_frame(rt, s);
}
JS_FreeValueRT(rt, s->resolving_funcs[0]);
JS_FreeValueRT(rt, s->resolving_funcs[1]);
remove_gc_object(&s->header);
if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && s->header.ref_count != 0) {
list_add_tail(&s->header.link, &rt->gc_zero_ref_count_list);
} else {
js_free_rt(rt, s);
}
}
static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s)
{
if (--s->header.ref_count == 0) {
if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) {
list_del(&s->header.link);
list_add(&s->header.link, &rt->gc_zero_ref_count_list);
if (rt->gc_phase == JS_GC_PHASE_NONE) {
free_zero_refcount(rt);
}
}
}
}
/* Generators */
@ -18688,14 +18763,17 @@ typedef enum JSGeneratorStateEnum {
typedef struct JSGeneratorData {
JSGeneratorStateEnum state;
JSAsyncFunctionState func_state;
JSAsyncFunctionState *func_state;
} JSGeneratorData;
static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s)
{
if (s->state == JS_GENERATOR_STATE_COMPLETED)
return;
async_func_free(rt, &s->func_state);
if (s->func_state) {
async_func_free(rt, s->func_state);
s->func_state = NULL;
}
s->state = JS_GENERATOR_STATE_COMPLETED;
}
@ -18720,9 +18798,9 @@ static void js_generator_mark(JSRuntime *rt, JSValueConst val,
JSObject *p = JS_VALUE_GET_OBJ(val);
JSGeneratorData *s = p->u.generator_data;
if (!s || s->state == JS_GENERATOR_STATE_COMPLETED)
if (!s || !s->func_state)
return;
async_func_mark(rt, &s->func_state, mark_func);
mark_func(rt, &s->func_state->header);
}
/* XXX: use enum */
@ -18741,7 +18819,7 @@ static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val,
*pdone = TRUE;
if (!s)
return JS_ThrowTypeError(ctx, "not a generator");
sf = &s->func_state.frame;
sf = &s->func_state->frame;
switch(s->state) {
default:
case JS_GENERATOR_STATE_SUSPENDED_START:
@ -18759,23 +18837,23 @@ static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val,
if (magic == GEN_MAGIC_THROW &&
s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) {
JS_Throw(ctx, ret);
s->func_state.throw_flag = TRUE;
s->func_state->throw_flag = TRUE;
} else {
sf->cur_sp[-1] = ret;
sf->cur_sp[0] = JS_NewInt32(ctx, magic);
sf->cur_sp++;
exec_no_arg:
s->func_state.throw_flag = FALSE;
s->func_state->throw_flag = FALSE;
}
s->state = JS_GENERATOR_STATE_EXECUTING;
func_ret = async_func_resume(ctx, &s->func_state);
func_ret = async_func_resume(ctx, s->func_state);
s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD;
if (JS_IsException(func_ret)) {
/* finalize the execution in case of exception */
if (s->func_state->is_completed) {
/* finalize the execution in case of exception or normal return */
free_generator_stack(ctx, s);
return func_ret;
}
if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
} else {
assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT);
/* get the returned yield value at the top of the stack */
ret = sf->cur_sp[-1];
sf->cur_sp[-1] = JS_UNDEFINED;
@ -18786,12 +18864,6 @@ static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val,
} else {
*pdone = FALSE;
}
} else {
/* end of iterator */
ret = sf->cur_sp[-1];
sf->cur_sp[-1] = JS_UNDEFINED;
JS_FreeValue(ctx, func_ret);
free_generator_stack(ctx, s);
}
break;
case JS_GENERATOR_STATE_COMPLETED:
@ -18829,13 +18901,14 @@ static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
if (!s)
return JS_EXCEPTION;
s->state = JS_GENERATOR_STATE_SUSPENDED_START;
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv);
if (!s->func_state) {
s->state = JS_GENERATOR_STATE_COMPLETED;
goto fail;
}
/* execute the function up to 'OP_initial_yield' */
func_ret = async_func_resume(ctx, &s->func_state);
func_ret = async_func_resume(ctx, s->func_state);
if (JS_IsException(func_ret))
goto fail;
JS_FreeValue(ctx, func_ret);
@ -18853,36 +18926,12 @@ static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
/* AsyncFunction */
static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s)
{
if (s->is_active) {
async_func_free(rt, &s->func_state);
s->is_active = FALSE;
}
}
static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s)
{
js_async_function_terminate(rt, s);
JS_FreeValueRT(rt, s->resolving_funcs[0]);
JS_FreeValueRT(rt, s->resolving_funcs[1]);
remove_gc_object(&s->header);
js_free_rt(rt, s);
}
static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s)
{
if (--s->header.ref_count == 0) {
js_async_function_free0(rt, s);
}
}
static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSAsyncFunctionData *s = p->u.async_function_data;
JSAsyncFunctionState *s = p->u.async_function_data;
if (s) {
js_async_function_free(rt, s);
async_func_free(rt, s);
}
}
@ -18890,14 +18939,14 @@ static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val,
JS_MarkFunc *mark_func)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSAsyncFunctionData *s = p->u.async_function_data;
JSAsyncFunctionState *s = p->u.async_function_data;
if (s) {
mark_func(rt, &s->header);
}
}
static int js_async_function_resolve_create(JSContext *ctx,
JSAsyncFunctionData *s,
JSAsyncFunctionState *s,
JSValue *resolving_funcs)
{
int i;
@ -18919,60 +18968,58 @@ static int js_async_function_resolve_create(JSContext *ctx,
return 0;
}
static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionState *s)
{
JSValue func_ret, ret2;
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret)) {
JSValue error;
fail:
error = JS_GetException(ctx);
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
1, (JSValueConst *)&error);
JS_FreeValue(ctx, error);
js_async_function_terminate(ctx->rt, s);
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
} else {
JSValue value;
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
if (JS_IsUndefined(func_ret)) {
/* function returned */
ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED,
1, (JSValueConst *)&value);
func_ret = async_func_resume(ctx, s);
if (s->is_completed) {
if (JS_IsException(func_ret)) {
JSValue error;
fail:
error = JS_GetException(ctx);
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
1, (JSValueConst *)&error);
JS_FreeValue(ctx, error);
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
JS_FreeValue(ctx, value);
js_async_function_terminate(ctx->rt, s);
} else {
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int i, res;
/* await */
JS_FreeValue(ctx, func_ret); /* not used */
promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, (JSValueConst *)&value, 0);
JS_FreeValue(ctx, value);
if (JS_IsException(promise))
goto fail;
if (js_async_function_resolve_create(ctx, s, resolving_funcs)) {
JS_FreeValue(ctx, promise);
goto fail;
}
/* Note: no need to create 'thrownawayCapability' as in
the spec */
for(i = 0; i < 2; i++)
resolving_funcs1[i] = JS_UNDEFINED;
res = perform_promise_then(ctx, promise,
(JSValueConst *)resolving_funcs,
(JSValueConst *)resolving_funcs1);
JS_FreeValue(ctx, promise);
for(i = 0; i < 2; i++)
JS_FreeValue(ctx, resolving_funcs[i]);
if (res)
goto fail;
/* normal return */
ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED,
1, (JSValueConst *)&func_ret);
JS_FreeValue(ctx, func_ret);
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
}
} else {
JSValue value, promise, resolving_funcs[2], resolving_funcs1[2];
int i, res;
value = s->frame.cur_sp[-1];
s->frame.cur_sp[-1] = JS_UNDEFINED;
/* await */
JS_FreeValue(ctx, func_ret); /* not used */
promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, (JSValueConst *)&value, 0);
JS_FreeValue(ctx, value);
if (JS_IsException(promise))
goto fail;
if (js_async_function_resolve_create(ctx, s, resolving_funcs)) {
JS_FreeValue(ctx, promise);
goto fail;
}
/* Note: no need to create 'thrownawayCapability' as in
the spec */
for(i = 0; i < 2; i++)
resolving_funcs1[i] = JS_UNDEFINED;
res = perform_promise_then(ctx, promise,
(JSValueConst *)resolving_funcs,
(JSValueConst *)resolving_funcs1);
JS_FreeValue(ctx, promise);
for(i = 0; i < 2; i++)
JS_FreeValue(ctx, resolving_funcs[i]);
if (res)
goto fail;
}
}
@ -18983,7 +19030,7 @@ static JSValue js_async_function_resolve_call(JSContext *ctx,
int flags)
{
JSObject *p = JS_VALUE_GET_OBJ(func_obj);
JSAsyncFunctionData *s = p->u.async_function_data;
JSAsyncFunctionState *s = p->u.async_function_data;
BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE;
JSValueConst arg;
@ -18991,12 +19038,12 @@ static JSValue js_async_function_resolve_call(JSContext *ctx,
arg = argv[0];
else
arg = JS_UNDEFINED;
s->func_state.throw_flag = is_reject;
s->throw_flag = is_reject;
if (is_reject) {
JS_Throw(ctx, JS_DupValue(ctx, arg));
} else {
/* return value of await */
s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg);
s->frame.cur_sp[-1] = JS_DupValue(ctx, arg);
}
js_async_function_resume(ctx, s);
return JS_UNDEFINED;
@ -19007,32 +19054,21 @@ static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj,
int argc, JSValueConst *argv, int flags)
{
JSValue promise;
JSAsyncFunctionData *s;
JSAsyncFunctionState *s;
s = js_mallocz(ctx, sizeof(*s));
s = async_func_init(ctx, func_obj, this_obj, argc, argv);
if (!s)
return JS_EXCEPTION;
s->header.ref_count = 1;
add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION);
s->is_active = FALSE;
s->resolving_funcs[0] = JS_UNDEFINED;
s->resolving_funcs[1] = JS_UNDEFINED;
promise = JS_NewPromiseCapability(ctx, s->resolving_funcs);
if (JS_IsException(promise))
goto fail;
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
fail:
JS_FreeValue(ctx, promise);
js_async_function_free(ctx->rt, s);
if (JS_IsException(promise)) {
async_func_free(ctx->rt, s);
return JS_EXCEPTION;
}
s->is_active = TRUE;
js_async_function_resume(ctx, s);
js_async_function_free(ctx->rt, s);
async_func_free(ctx->rt, s);
return promise;
}
@ -19061,7 +19097,8 @@ typedef struct JSAsyncGeneratorRequest {
typedef struct JSAsyncGeneratorData {
JSObject *generator; /* back pointer to the object (const) */
JSAsyncGeneratorStateEnum state;
JSAsyncFunctionState func_state;
/* func_state is NULL is state AWAITING_RETURN and COMPLETED */
JSAsyncFunctionState *func_state;
struct list_head queue; /* list of JSAsyncGeneratorRequest.link */
} JSAsyncGeneratorData;
@ -19079,10 +19116,8 @@ static void js_async_generator_free(JSRuntime *rt,
JS_FreeValueRT(rt, req->resolving_funcs[1]);
js_free_rt(rt, req);
}
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
async_func_free(rt, &s->func_state);
}
if (s->func_state)
async_func_free(rt, s->func_state);
js_free_rt(rt, s);
}
@ -19109,9 +19144,8 @@ static void js_async_generator_mark(JSRuntime *rt, JSValueConst val,
JS_MarkValue(rt, req->resolving_funcs[0], mark_func);
JS_MarkValue(rt, req->resolving_funcs[1], mark_func);
}
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
async_func_mark(rt, &s->func_state, mark_func);
if (s->func_state) {
mark_func(rt, &s->func_state->header);
}
}
}
@ -19221,7 +19255,8 @@ static void js_async_generator_complete(JSContext *ctx,
{
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) {
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
async_func_free(ctx->rt, &s->func_state);
async_func_free(ctx->rt, s->func_state);
s->func_state = NULL;
}
}
@ -19302,30 +19337,38 @@ static void js_async_generator_resume_next(JSContext *ctx,
if (next->completion_type == GEN_MAGIC_THROW &&
s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) {
JS_Throw(ctx, value);
s->func_state.throw_flag = TRUE;
s->func_state->throw_flag = TRUE;
} else {
/* 'yield' returns a value. 'yield *' also returns a value
in case the 'throw' method is called */
s->func_state.frame.cur_sp[-1] = value;
s->func_state.frame.cur_sp[0] =
s->func_state->frame.cur_sp[-1] = value;
s->func_state->frame.cur_sp[0] =
JS_NewInt32(ctx, next->completion_type);
s->func_state.frame.cur_sp++;
s->func_state->frame.cur_sp++;
exec_no_arg:
s->func_state.throw_flag = FALSE;
s->func_state->throw_flag = FALSE;
}
s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING;
resume_exec:
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret)) {
value = JS_GetException(ctx);
js_async_generator_complete(ctx, s);
js_async_generator_reject(ctx, s, value);
JS_FreeValue(ctx, value);
} else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) {
func_ret = async_func_resume(ctx, s->func_state);
if (s->func_state->is_completed) {
if (JS_IsException(func_ret)) {
value = JS_GetException(ctx);
js_async_generator_complete(ctx, s);
js_async_generator_reject(ctx, s, value);
JS_FreeValue(ctx, value);
} else {
/* end of function */
js_async_generator_complete(ctx, s);
js_async_generator_resolve(ctx, s, func_ret, TRUE);
JS_FreeValue(ctx, func_ret);
}
} else {
int func_ret_code, ret;
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT);
func_ret_code = JS_VALUE_GET_INT(func_ret);
value = s->func_state->frame.cur_sp[-1];
s->func_state->frame.cur_sp[-1] = JS_UNDEFINED;
switch(func_ret_code) {
case FUNC_RET_YIELD:
case FUNC_RET_YIELD_STAR:
@ -19341,21 +19384,13 @@ static void js_async_generator_resume_next(JSContext *ctx,
JS_FreeValue(ctx, value);
if (ret < 0) {
/* exception: throw it */
s->func_state.throw_flag = TRUE;
s->func_state->throw_flag = TRUE;
goto resume_exec;
}
goto done;
default:
abort();
}
} else {
assert(JS_IsUndefined(func_ret));
/* end of function */
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
js_async_generator_complete(ctx, s);
js_async_generator_resolve(ctx, s, value, TRUE);
JS_FreeValue(ctx, value);
}
break;
default:
@ -19389,12 +19424,12 @@ static JSValue js_async_generator_resolve_function(JSContext *ctx,
} else {
/* restart function execution after await() */
assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING);
s->func_state.throw_flag = is_reject;
s->func_state->throw_flag = is_reject;
if (is_reject) {
JS_Throw(ctx, JS_DupValue(ctx, arg));
} else {
/* return value of await */
s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg);
s->func_state->frame.cur_sp[-1] = JS_DupValue(ctx, arg);
}
js_async_generator_resume_next(ctx, s);
}
@ -19458,14 +19493,12 @@ static JSValue js_async_generator_function_call(JSContext *ctx, JSValueConst fun
return JS_EXCEPTION;
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START;
init_list_head(&s->queue);
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv);
if (!s->func_state)
goto fail;
}
/* execute the function up to 'OP_initial_yield' (no yield nor
await are possible) */
func_ret = async_func_resume(ctx, &s->func_state);
func_ret = async_func_resume(ctx, s->func_state);
if (JS_IsException(func_ret))
goto fail;
JS_FreeValue(ctx, func_ret);

View file

@ -270,6 +270,26 @@ function test_timer()
os.clearTimeout(th[i]);
}
/* test closure variable handling when freeing asynchronous
function */
function test_async_gc()
{
(async function run () {
let obj = {}
let done = () => {
obj
std.gc();
}
Promise.resolve().then(done)
const p = new Promise(() => {})
await p
})();
}
test_printf();
test_file1();
test_file2();
@ -279,3 +299,5 @@ test_os();
test_os_exec();
test_timer();
test_ext_json();
test_async_gc();