Add top-level await support

Original author: zamfofex <zamfofex@twdb.moe>
This commit is contained in:
Saúl Ibarra Corretgé 2023-11-01 21:22:49 +01:00
parent f51616eac8
commit a9ac7a07ff
5 changed files with 150 additions and 20 deletions

1
.gitignore vendored
View file

@ -16,4 +16,5 @@ test262_*.txt
test_fib.c test_fib.c
tests/bjson.so tests/bjson.so
*.a *.a
*.orig

133
quickjs.c
View file

@ -785,6 +785,7 @@ struct JSModuleDef {
int import_entries_count; int import_entries_count;
int import_entries_size; int import_entries_size;
JSValue promise;
JSValue module_ns; JSValue module_ns;
JSValue func_obj; /* only used for JS modules */ JSValue func_obj; /* only used for JS modules */
JSModuleInitFunc *init_func; /* only used for C modules */ JSModuleInitFunc *init_func; /* only used for C modules */
@ -857,6 +858,14 @@ struct JSShape {
JSShapeProperty prop[0]; /* prop_size elements */ JSShapeProperty prop[0]; /* prop_size elements */
}; };
typedef struct JSPromiseData {
JSPromiseStateEnum promise_state;
/* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */
struct list_head promise_reactions[2];
BOOL is_handled; /* Note: only useful to debug */
JSValue promise_result;
} JSPromiseData;
struct JSObject { struct JSObject {
union { union {
JSGCObjectHeader header; JSGCObjectHeader header;
@ -1095,6 +1104,12 @@ static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor,
static void gc_decref(JSRuntime *rt); static void gc_decref(JSRuntime *rt);
static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
const JSClassDef *class_def, JSAtom name); const JSClassDef *class_def, JSAtom name);
static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic);
static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv);
static JSValue js_array_push(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int unshift);
typedef enum JSStrictEqModeEnum { typedef enum JSStrictEqModeEnum {
JS_EQ_STRICT, JS_EQ_STRICT,
@ -27033,6 +27048,7 @@ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
} }
m->header.ref_count = 1; m->header.ref_count = 1;
m->module_name = name; m->module_name = name;
m->promise = JS_UNDEFINED;
m->module_ns = JS_UNDEFINED; m->module_ns = JS_UNDEFINED;
m->func_obj = JS_UNDEFINED; m->func_obj = JS_UNDEFINED;
m->eval_exception = JS_UNDEFINED; m->eval_exception = JS_UNDEFINED;
@ -27054,6 +27070,7 @@ static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
} }
} }
JS_MarkValue(rt, m->promise, mark_func);
JS_MarkValue(rt, m->module_ns, mark_func); JS_MarkValue(rt, m->module_ns, mark_func);
JS_MarkValue(rt, m->func_obj, mark_func); JS_MarkValue(rt, m->func_obj, mark_func);
JS_MarkValue(rt, m->eval_exception, mark_func); JS_MarkValue(rt, m->eval_exception, mark_func);
@ -27089,6 +27106,7 @@ static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
} }
js_free(ctx, m->import_entries); js_free(ctx, m->import_entries);
JS_FreeValue(ctx, m->promise);
JS_FreeValue(ctx, m->module_ns); JS_FreeValue(ctx, m->module_ns);
JS_FreeValue(ctx, m->func_obj); JS_FreeValue(ctx, m->func_obj);
JS_FreeValue(ctx, m->eval_exception); JS_FreeValue(ctx, m->eval_exception);
@ -28200,6 +28218,18 @@ JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename,
return m; return m;
} }
static JSValue js_dynamic_import_resolve(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic, JSValue *func_data)
{
return JS_Call(ctx, func_data[0], JS_UNDEFINED, 1, (JSValueConst *)&func_data[2]);
}
static JSValue js_dynamic_import_reject(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic, JSValue *func_data)
{
return JS_Call(ctx, func_data[1], JS_UNDEFINED, 1, (JSValueConst *)&argv[0]);
}
static JSValue js_dynamic_import_job(JSContext *ctx, static JSValue js_dynamic_import_job(JSContext *ctx,
int argc, JSValueConst *argv) int argc, JSValueConst *argv)
{ {
@ -28232,6 +28262,21 @@ static JSValue js_dynamic_import_job(JSContext *ctx,
if (JS_IsException(ns)) if (JS_IsException(ns))
goto exception; goto exception;
if (!JS_IsUndefined(m->promise)) {
JSValueConst args[] = {argv[0], argv[1], ns};
JSValueConst funcs[2];
funcs[0] = JS_NewCFunctionData(ctx, js_dynamic_import_resolve, 0, 0, 3, args);
funcs[1] = JS_NewCFunctionData(ctx, js_dynamic_import_reject, 0, 0, 3, args);
JS_FreeValue(ctx, js_promise_then(ctx, m->promise, 2, funcs));
JS_FreeValue(ctx, (JSValue)funcs[0]);
JS_FreeValue(ctx, (JSValue)funcs[1]);
JS_FreeValue(ctx, ns);
JS_FreeCString(ctx, basename);
return JS_UNDEFINED;
}
ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED,
1, (JSValueConst *)&ns); 1, (JSValueConst *)&ns);
JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */ JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
@ -28283,6 +28328,12 @@ static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier)
return promise; return promise;
} }
static JSValue js_async_function_call2(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic, JSValue *func_data)
{
return js_async_function_call(ctx, func_data[0], this_val, argc, argv, magic);
}
/* Run the <eval> function of the module and of all its requested /* Run the <eval> function of the module and of all its requested
modules. */ modules. */
static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m) static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
@ -28300,12 +28351,17 @@ static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
if (m->eval_has_exception) { if (m->eval_has_exception) {
return JS_Throw(ctx, JS_DupValue(ctx, m->eval_exception)); return JS_Throw(ctx, JS_DupValue(ctx, m->eval_exception));
} else { } else {
return JS_UNDEFINED; return JS_DupValue(ctx, m->promise);
} }
} }
m->eval_mark = TRUE; m->eval_mark = TRUE;
JSValueConst promises = JS_NewArray(ctx);
if (JS_IsException(promises))
return JS_EXCEPTION;
BOOL async = FALSE;
for(i = 0; i < m->req_module_entries_count; i++) { for(i = 0; i < m->req_module_entries_count; i++) {
JSReqModuleEntry *rme = &m->req_module_entries[i]; JSReqModuleEntry *rme = &m->req_module_entries[i];
m1 = rme->module; m1 = rme->module;
@ -28313,29 +28369,62 @@ static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
ret_val = js_evaluate_module(ctx, m1); ret_val = js_evaluate_module(ctx, m1);
if (JS_IsException(ret_val)) { if (JS_IsException(ret_val)) {
m->eval_mark = FALSE; m->eval_mark = FALSE;
return ret_val; goto clean;
}
if (!JS_IsUndefined(ret_val)) {
js_array_push(ctx, promises, 1, (JSValueConst *)&ret_val, 0);
JS_FreeValue(ctx, ret_val);
async = TRUE;
} }
JS_FreeValue(ctx, ret_val);
} }
} }
JSValue promise = js_promise_all(ctx, ctx->promise_ctor, 1, &promises, 0);
if (JS_IsException(promise)) {
JS_FreeValue(ctx, (JSValue)promises);
return JS_EXCEPTION;
}
if (m->init_func) { if (m->init_func) {
/* C module init */ /* C module init */
if (m->init_func(ctx, m) < 0) if (m->init_func(ctx, m) < 0)
ret_val = JS_EXCEPTION; ret_val = JS_EXCEPTION;
else else
ret_val = JS_UNDEFINED; ret_val = JS_UNDEFINED;
} else if (!async) {
ret_val = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0);
JS_FreeValue(ctx, m->func_obj);
m->func_obj = JS_UNDEFINED;
JSPromiseData *s = JS_GetOpaque(ret_val, JS_CLASS_PROMISE);
if (s->promise_state != JS_PROMISE_PENDING) {
JSValue ret_val2 = ret_val;
if (s->promise_state == JS_PROMISE_REJECTED)
ret_val = JS_Throw(ctx, JS_DupValue(ctx, s->promise_result));
else
ret_val = JS_DupValue(ctx, s->promise_result);
JS_FreeValue(ctx, ret_val2);
}
} else { } else {
ret_val = JS_CallFree(ctx, m->func_obj, JS_UNDEFINED, 0, NULL); JSValueConst funcs[2];
funcs[0] = JS_NewCFunctionData(ctx, js_async_function_call2, 0, 0, 1, (JSValueConst *)&m->func_obj);
funcs[1] = JS_UNDEFINED;
ret_val = js_promise_then(ctx, promise, 2, funcs);
JS_FreeValue(ctx, (JSValue)funcs[0]);
JS_FreeValue(ctx, m->func_obj);
m->func_obj = JS_UNDEFINED; m->func_obj = JS_UNDEFINED;
} }
if (JS_IsException(ret_val)) { if (JS_IsException(ret_val)) {
/* save the thrown exception value */ /* save the thrown exception value */
m->eval_has_exception = TRUE; m->eval_has_exception = TRUE;
m->eval_exception = JS_DupValue(ctx, ctx->rt->current_exception); m->eval_exception = JS_DupValue(ctx, ctx->rt->current_exception);
} else if (!JS_IsUndefined(ret_val)) {
m->promise = JS_DupValue(ctx, ret_val);
} }
m->eval_mark = FALSE; m->eval_mark = FALSE;
m->evaluated = TRUE; m->evaluated = TRUE;
clean:
JS_FreeValue(ctx, (JSValue)promises);
JS_FreeValue(ctx, promise);
return ret_val; return ret_val;
} }
@ -33487,7 +33576,7 @@ static __exception int js_parse_program(JSParseState *s)
emit_op(s, OP_return); emit_op(s, OP_return);
} else { } else {
emit_op(s, OP_return_undef); emit_return(s, FALSE);
} }
return 0; return 0;
@ -33621,6 +33710,10 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1); fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1);
if (!fd) if (!fd)
goto fail1; goto fail1;
if (m != NULL) {
fd->in_function_body = TRUE;
fd->func_kind = JS_FUNC_ASYNC;
}
s->cur_func = fd; s->cur_func = fd;
fd->eval_type = eval_type; fd->eval_type = eval_type;
fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT); fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT);
@ -46268,20 +46361,6 @@ static const JSCFunctionListEntry js_generator_proto_funcs[] = {
/* Promise */ /* Promise */
typedef enum JSPromiseStateEnum {
JS_PROMISE_PENDING,
JS_PROMISE_FULFILLED,
JS_PROMISE_REJECTED,
} JSPromiseStateEnum;
typedef struct JSPromiseData {
JSPromiseStateEnum promise_state;
/* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */
struct list_head promise_reactions[2];
BOOL is_handled; /* Note: only useful to debug */
JSValue promise_result;
} JSPromiseData;
typedef struct JSPromiseFunctionDataResolved { typedef struct JSPromiseFunctionDataResolved {
int ref_count; int ref_count;
BOOL already_resolved; BOOL already_resolved;
@ -46298,6 +46377,22 @@ typedef struct JSPromiseReactionData {
JSValue handler; JSValue handler;
} JSPromiseReactionData; } JSPromiseReactionData;
JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise)
{
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
if (!s)
return -1;
return s->promise_state;
}
JSValue JS_PromiseResult(JSContext *ctx, JSValue promise)
{
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
if (!s)
return JS_UNDEFINED;
return JS_DupValue(ctx, s->promise_result);
}
static int js_create_resolving_functions(JSContext *ctx, JSValue *args, static int js_create_resolving_functions(JSContext *ctx, JSValue *args,
JSValueConst promise); JSValueConst promise);

View file

@ -1039,6 +1039,17 @@ int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name,
int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m,
const JSCFunctionListEntry *tab, int len); const JSCFunctionListEntry *tab, int len);
/* Promise */
typedef enum JSPromiseStateEnum {
JS_PROMISE_PENDING,
JS_PROMISE_FULFILLED,
JS_PROMISE_REJECTED,
} JSPromiseStateEnum;
JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise);
JSValue JS_PromiseResult(JSContext *ctx, JSValue promise);
#undef js_unlikely #undef js_unlikely
#undef js_force_inline #undef js_force_inline

View file

@ -1205,6 +1205,29 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
break; break;
} }
} }
} else if ((eval_flags & JS_EVAL_TYPE_MODULE) &&
!JS_IsUndefined(res_val) &&
!JS_IsException(res_val)) {
JSValue promise = res_val;
for(;;) {
JSContext *ctx1;
ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
if (ret < 0) {
res_val = JS_EXCEPTION;
break;
}
if (ret == 0) {
JSPromiseStateEnum s = JS_PromiseState(ctx, promise);
if (s == JS_PROMISE_FULFILLED)
res_val = JS_UNDEFINED;
else if (s == JS_PROMISE_REJECTED)
res_val = JS_Throw(ctx, JS_PromiseResult(ctx, promise));
else
res_val = JS_EXCEPTION;
break;
}
}
JS_FreeValue(ctx, promise);
} }
if (JS_IsException(res_val)) { if (JS_IsException(res_val)) {

View file

@ -174,7 +174,7 @@ Symbol.unscopables
tail-call-optimization=skip tail-call-optimization=skip
template template
Temporal=skip Temporal=skip
top-level-await=skip top-level-await
TypedArray TypedArray
TypedArray.prototype.at=skip TypedArray.prototype.at=skip
u180e u180e