Skip to content

Commit

Permalink
Add top-level await support
Browse files Browse the repository at this point in the history
Original author: zamfofex <zamfofex@twdb.moe>
  • Loading branch information
saghul committed Nov 1, 2023
1 parent f51616e commit a9ac7a0
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 20 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -16,4 +16,5 @@ test262_*.txt
test_fib.c
tests/bjson.so
*.a
*.orig

133 changes: 114 additions & 19 deletions quickjs.c
Expand Up @@ -785,6 +785,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 @@ -857,6 +858,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 @@ -1095,6 +1104,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 @@ -27033,6 +27048,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 @@ -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->func_obj, mark_func);
JS_MarkValue(rt, m->eval_exception, mark_func);
Expand Down Expand Up @@ -27089,6 +27106,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 @@ -28200,6 +28218,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 @@ -28232,6 +28262,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 @@ -28283,6 +28328,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 @@ -28300,42 +28351,80 @@ 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;
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);
}
}

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) {
/* 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 @@ -33487,7 +33576,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 @@ -33621,6 +33710,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 @@ -46268,20 +46361,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 @@ -46298,6 +46377,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
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
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
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

0 comments on commit a9ac7a0

Please sign in to comment.