Skip to content

Add top-level await support #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ test262_*.txt
test_fib.c
tests/bjson.so
*.a
*.orig

134 changes: 115 additions & 19 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,7 @@ struct JSModuleDef {
int import_entries_count;
int import_entries_size;

JSValue promise;
JSValue module_ns;
JSValue func_obj; /* only used for JS modules */
JSModuleInitFunc *init_func; /* only used for C modules */
Expand Down Expand Up @@ -863,6 +864,14 @@ struct JSShape {
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 {
union {
JSGCObjectHeader header;
Expand Down Expand Up @@ -1101,6 +1110,12 @@ static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor,
static void gc_decref(JSRuntime *rt);
static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
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 {
JS_EQ_STRICT,
Expand Down Expand Up @@ -27039,6 +27054,7 @@ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
}
m->header.ref_count = 1;
m->module_name = name;
m->promise = JS_UNDEFINED;
m->module_ns = JS_UNDEFINED;
m->func_obj = JS_UNDEFINED;
m->eval_exception = JS_UNDEFINED;
Expand All @@ -27060,6 +27076,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->func_obj, mark_func);
JS_MarkValue(rt, m->eval_exception, mark_func);
Expand Down Expand Up @@ -27095,6 +27112,7 @@ static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
}
js_free(ctx, m->import_entries);

JS_FreeValue(ctx, m->promise);
JS_FreeValue(ctx, m->module_ns);
JS_FreeValue(ctx, m->func_obj);
JS_FreeValue(ctx, m->eval_exception);
Expand Down Expand Up @@ -28206,6 +28224,18 @@ JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename,
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,
int argc, JSValueConst *argv)
{
Expand Down Expand Up @@ -28238,6 +28268,21 @@ static JSValue js_dynamic_import_job(JSContext *ctx,
if (JS_IsException(ns))
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,
1, (JSValueConst *)&ns);
JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
Expand Down Expand Up @@ -28289,6 +28334,12 @@ static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier)
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
modules. */
static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
Expand All @@ -28306,42 +28357,81 @@ static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
if (m->eval_has_exception) {
return JS_Throw(ctx, JS_DupValue(ctx, m->eval_exception));
} else {
return JS_UNDEFINED;
return JS_DupValue(ctx, m->promise);
}
}

m->eval_mark = TRUE;

JSValueConst promises = JS_NewArray(ctx);
if (JS_IsException(promises))
return JS_EXCEPTION;

BOOL async = FALSE;
JSValue promise = JS_UNDEFINED;
for(i = 0; i < m->req_module_entries_count; i++) {
JSReqModuleEntry *rme = &m->req_module_entries[i];
m1 = rme->module;
if (!m1->eval_mark) {
ret_val = js_evaluate_module(ctx, m1);
if (JS_IsException(ret_val)) {
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);
}
}

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) {
/* C module init */
if (m->init_func(ctx, m) < 0)
ret_val = JS_EXCEPTION;
else
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 {
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;
}
if (JS_IsException(ret_val)) {
/* save the thrown exception value */
m->eval_has_exception = TRUE;
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->evaluated = TRUE;
clean:
JS_FreeValue(ctx, (JSValue)promises);
JS_FreeValue(ctx, promise);
return ret_val;
}

Expand Down Expand Up @@ -33493,7 +33583,7 @@ static __exception int js_parse_program(JSParseState *s)

emit_op(s, OP_return);
} else {
emit_op(s, OP_return_undef);
emit_return(s, FALSE);
}

return 0;
Expand Down Expand Up @@ -33627,6 +33717,10 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1);
if (!fd)
goto fail1;
if (m != NULL) {
fd->in_function_body = TRUE;
fd->func_kind = JS_FUNC_ASYNC;
}
s->cur_func = fd;
fd->eval_type = eval_type;
fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT);
Expand Down Expand Up @@ -46274,20 +46368,6 @@ static const JSCFunctionListEntry js_generator_proto_funcs[] = {

/* 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 {
int ref_count;
BOOL already_resolved;
Expand All @@ -46304,6 +46384,22 @@ typedef struct JSPromiseReactionData {
JSValue handler;
} 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,
JSValueConst promise);

Expand Down
11 changes: 11 additions & 0 deletions quickjs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,17 @@ int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name,
int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m,
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_force_inline

Expand Down
23 changes: 23 additions & 0 deletions run-test262.c
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,29 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
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)) {
Expand Down
2 changes: 1 addition & 1 deletion test262.conf
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ Symbol.unscopables
tail-call-optimization=skip
template
Temporal=skip
top-level-await=skip
top-level-await
TypedArray
TypedArray.prototype.at=skip
u180e
Expand Down