summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabrice Bellard <fabrice@bellard.org>2023-12-27 17:10:47 +0100
committerFabrice Bellard <fabrice@bellard.org>2023-12-27 17:10:47 +0100
commit6e4931c4ad6c1f06cd1e46ecbf98e04cbb8b4ba7 (patch)
tree8d69d6513520c68d94795084e01859baae527ba0
parent9b587c461b06af06e78070b86d9b99da7d863065 (diff)
downloadquickjs-6e4931c4ad6c1f06cd1e46ecbf98e04cbb8b4ba7.tar.gz
quickjs-6e4931c4ad6c1f06cd1e46ecbf98e04cbb8b4ba7.zip
top-level-await support - follow the spec in the implementation of the module linking and evaluation to avoid errors with cycling module dependencies
-rw-r--r--quickjs-libc.c6
-rw-r--r--quickjs.c712
-rw-r--r--quickjs.h12
-rw-r--r--run-test262.c35
-rw-r--r--test262.conf2
5 files changed, 668 insertions, 99 deletions
diff --git a/quickjs-libc.c b/quickjs-libc.c
index e180dd0..f916314 100644
--- a/quickjs-libc.c
+++ b/quickjs-libc.c
@@ -3274,6 +3274,7 @@ static void *worker_func(void *opaque)
JSRuntime *rt;
JSThreadState *ts;
JSContext *ctx;
+ JSValue promise;
rt = JS_NewRuntime();
if (rt == NULL) {
@@ -3300,8 +3301,11 @@ static void *worker_func(void *opaque)
js_std_add_helpers(ctx, -1, NULL);
- if (!JS_RunModule(ctx, args->basename, args->filename))
+ promise = JS_LoadModule(ctx, args->basename, args->filename);
+ if (JS_IsException(promise))
js_std_dump_error(ctx);
+ /* XXX: check */
+ JS_FreeValue(ctx, promise);
free(args->filename);
free(args->basename);
free(args);
diff --git a/quickjs.c b/quickjs.c
index 93d3949..7cbfe70 100644
--- a/quickjs.c
+++ b/quickjs.c
@@ -283,7 +283,9 @@ struct JSRuntime {
JSModuleNormalizeFunc *module_normalize_func;
JSModuleLoaderFunc *module_loader_func;
void *module_loader_opaque;
-
+ /* timestamp for internal use in module evaluation */
+ int64_t module_async_evaluation_next_timestamp;
+
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
/* used to allocate, free and clone SharedArrayBuffers */
JSSharedArrayBufferFunctions sab_funcs;
@@ -765,6 +767,15 @@ typedef struct JSImportEntry {
int req_module_idx; /* in req_module_entries */
} JSImportEntry;
+typedef enum {
+ JS_MODULE_STATUS_UNLINKED,
+ JS_MODULE_STATUS_LINKING,
+ JS_MODULE_STATUS_LINKED,
+ JS_MODULE_STATUS_EVALUATING,
+ JS_MODULE_STATUS_EVALUATING_ASYNC,
+ JS_MODULE_STATUS_EVALUATED,
+} JSModuleStatus;
+
struct JSModuleDef {
JSRefCountHeader header; /* must come first, 32-bit */
JSAtom module_name;
@@ -789,11 +800,24 @@ struct JSModuleDef {
JSValue module_ns;
JSValue func_obj; /* only used for JS modules */
JSModuleInitFunc *init_func; /* only used for C modules */
+ BOOL has_tla : 8; /* true if func_obj contains await */
BOOL resolved : 8;
BOOL func_created : 8;
- BOOL instantiated : 8;
- BOOL evaluated : 8;
- BOOL eval_mark : 8; /* temporary use during js_evaluate_module() */
+ JSModuleStatus status : 8;
+ /* temp use during js_module_link() & js_module_evaluate() */
+ int dfs_index, dfs_ancestor_index;
+ JSModuleDef *stack_prev;
+ /* temp use during js_module_evaluate() */
+ JSModuleDef **async_parent_modules;
+ int async_parent_modules_count;
+ int async_parent_modules_size;
+ int pending_async_dependencies;
+ BOOL async_evaluation;
+ int64_t async_evaluation_timestamp;
+ JSModuleDef *cycle_root;
+ JSValue promise; /* corresponds to spec field: capability */
+ JSValue resolving_funcs[2]; /* corresponds to spec field: capability */
+
/* true if evaluation yielded an exception. It is saved in
eval_exception */
BOOL eval_has_exception : 8;
@@ -1198,6 +1222,8 @@ static __exception int perform_promise_then(JSContext *ctx,
JSValueConst *cap_resolving_funcs);
static JSValue js_promise_resolve(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 int js_string_compare(JSContext *ctx,
const JSString *p1, const JSString *p2);
static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val);
@@ -2192,7 +2218,6 @@ JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id)
typedef enum JSFreeModuleEnum {
JS_FREE_MODULE_ALL,
JS_FREE_MODULE_NOT_RESOLVED,
- JS_FREE_MODULE_NOT_EVALUATED,
} JSFreeModuleEnum;
/* XXX: would be more efficient with separate module lists */
@@ -2202,8 +2227,7 @@ static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag)
list_for_each_safe(el, el1, &ctx->loaded_modules) {
JSModuleDef *m = list_entry(el, JSModuleDef, link);
if (flag == JS_FREE_MODULE_ALL ||
- (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved) ||
- (flag == JS_FREE_MODULE_NOT_EVALUATED && !m->evaluated)) {
+ (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) {
js_free_module_def(ctx, m);
}
}
@@ -19716,6 +19740,7 @@ typedef struct JSFunctionDef {
int source_len;
JSModuleDef *module; /* != NULL when parsing a module */
+ BOOL has_await; /* TRUE if await is used (used in module eval) */
} JSFunctionDef;
typedef struct JSToken {
@@ -24774,6 +24799,7 @@ static __exception int js_parse_unary(JSParseState *s, int parse_flags)
return -1;
if (js_parse_unary(s, PF_POW_FORBIDDEN))
return -1;
+ s->cur_func->has_await = TRUE;
emit_op(s, OP_await);
parse_flags = 0;
break;
@@ -26173,6 +26199,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
is_async = TRUE;
if (next_token(s))
goto fail;
+ s->cur_func->has_await = TRUE;
}
if (js_parse_expect(s, '('))
goto fail;
@@ -26703,6 +26730,9 @@ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
m->func_obj = JS_UNDEFINED;
m->eval_exception = JS_UNDEFINED;
m->meta_obj = JS_UNDEFINED;
+ m->promise = JS_UNDEFINED;
+ m->resolving_funcs[0] = JS_UNDEFINED;
+ m->resolving_funcs[1] = JS_UNDEFINED;
list_add_tail(&m->link, &ctx->loaded_modules);
return m;
}
@@ -26724,6 +26754,9 @@ static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
JS_MarkValue(rt, m->func_obj, mark_func);
JS_MarkValue(rt, m->eval_exception, mark_func);
JS_MarkValue(rt, m->meta_obj, mark_func);
+ JS_MarkValue(rt, m->promise, mark_func);
+ JS_MarkValue(rt, m->resolving_funcs[0], mark_func);
+ JS_MarkValue(rt, m->resolving_funcs[1], mark_func);
}
static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
@@ -26754,11 +26787,15 @@ static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
JS_FreeAtom(ctx, mi->import_name);
}
js_free(ctx, m->import_entries);
+ js_free(ctx, m->async_parent_modules);
JS_FreeValue(ctx, m->module_ns);
JS_FreeValue(ctx, m->func_obj);
JS_FreeValue(ctx, m->eval_exception);
JS_FreeValue(ctx, m->meta_obj);
+ JS_FreeValue(ctx, m->promise);
+ JS_FreeValue(ctx, m->resolving_funcs[0]);
+ JS_FreeValue(ctx, m->resolving_funcs[1]);
list_del(&m->link);
js_free(ctx, m);
}
@@ -27614,7 +27651,8 @@ static int js_create_module_function(JSContext *ctx, JSModuleDef *m)
/* Prepare a module to be executed by resolving all the imported
variables. */
-static int js_link_module(JSContext *ctx, JSModuleDef *m)
+static int js_inner_module_linking(JSContext *ctx, JSModuleDef *m,
+ JSModuleDef **pstack_top, int index)
{
int i;
JSImportEntry *mi;
@@ -27624,21 +27662,47 @@ static int js_link_module(JSContext *ctx, JSModuleDef *m)
BOOL is_c_module;
JSValue ret_val;
- if (m->instantiated)
- return 0;
- m->instantiated = TRUE;
-
+ if (js_check_stack_overflow(ctx->rt, 0)) {
+ JS_ThrowStackOverflow(ctx);
+ return -1;
+ }
+
#ifdef DUMP_MODULE_RESOLVE
{
char buf1[ATOM_GET_STR_BUF_SIZE];
- printf("start instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
+ printf("js_inner_module_linking '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
}
#endif
+ if (m->status == JS_MODULE_STATUS_LINKING ||
+ m->status == JS_MODULE_STATUS_LINKED ||
+ m->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m->status == JS_MODULE_STATUS_EVALUATED)
+ return index;
+
+ assert(m->status == JS_MODULE_STATUS_UNLINKED);
+ m->status = JS_MODULE_STATUS_LINKING;
+ m->dfs_index = index;
+ m->dfs_ancestor_index = index;
+ index++;
+ /* push 'm' on stack */
+ m->stack_prev = *pstack_top;
+ *pstack_top = m;
+
for(i = 0; i < m->req_module_entries_count; i++) {
JSReqModuleEntry *rme = &m->req_module_entries[i];
- if (js_link_module(ctx, rme->module) < 0)
+ m1 = rme->module;
+ index = js_inner_module_linking(ctx, m1, pstack_top, index);
+ if (index < 0)
goto fail;
+ assert(m1->status == JS_MODULE_STATUS_LINKING ||
+ m1->status == JS_MODULE_STATUS_LINKED ||
+ m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m1->status == JS_MODULE_STATUS_EVALUATED);
+ if (m1->status == JS_MODULE_STATUS_LINKING) {
+ m->dfs_ancestor_index = min_int(m->dfs_ancestor_index,
+ m1->dfs_ancestor_index);
+ }
}
#ifdef DUMP_MODULE_RESOLVE
@@ -27764,14 +27828,59 @@ static int js_link_module(JSContext *ctx, JSModuleDef *m)
JS_FreeValue(ctx, ret_val);
}
+ assert(m->dfs_ancestor_index <= m->dfs_index);
+ if (m->dfs_index == m->dfs_ancestor_index) {
+ for(;;) {
+ /* pop m1 from stack */
+ m1 = *pstack_top;
+ *pstack_top = m1->stack_prev;
+ m1->status = JS_MODULE_STATUS_LINKED;
+ if (m1 == m)
+ break;
+ }
+ }
+
#ifdef DUMP_MODULE_RESOLVE
- printf("done instantiate\n");
+ printf("js_inner_module_linking done\n");
#endif
- return 0;
+ return index;
fail:
return -1;
}
+/* Prepare a module to be executed by resolving all the imported
+ variables. */
+static int js_link_module(JSContext *ctx, JSModuleDef *m)
+{
+ JSModuleDef *stack_top, *m1;
+
+#ifdef DUMP_MODULE_RESOLVE
+ {
+ char buf1[ATOM_GET_STR_BUF_SIZE];
+ printf("js_link_module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
+ }
+#endif
+ assert(m->status == JS_MODULE_STATUS_UNLINKED ||
+ m->status == JS_MODULE_STATUS_LINKED ||
+ m->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m->status == JS_MODULE_STATUS_EVALUATED);
+ stack_top = NULL;
+ if (js_inner_module_linking(ctx, m, &stack_top, 0) < 0) {
+ while (stack_top != NULL) {
+ m1 = stack_top;
+ assert(m1->status == JS_MODULE_STATUS_LINKING);
+ m1->status = JS_MODULE_STATUS_UNLINKED;
+ stack_top = m1->stack_prev;
+ }
+ return -1;
+ }
+ assert(stack_top == NULL);
+ assert(m->status == JS_MODULE_STATUS_LINKED ||
+ m->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m->status == JS_MODULE_STATUS_EVALUATED);
+ return 0;
+}
+
/* return JS_ATOM_NULL if the name cannot be found. Only works with
not striped bytecode functions. */
JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels)
@@ -27841,29 +27950,110 @@ static JSValue js_import_meta(JSContext *ctx)
return JS_GetImportMeta(ctx, m);
}
-/* used by os.Worker() and import() */
-JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename,
- const char *filename)
+static JSValue JS_NewModuleValue(JSContext *ctx, JSModuleDef *m)
+{
+ return JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
+}
+
+static JSValue js_load_module_rejected(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int magic, JSValue *func_data)
{
+ JSValueConst *resolving_funcs = (JSValueConst *)func_data;
+ JSValueConst error;
+ JSValue ret;
+
+ /* XXX: check if the test is necessary */
+ if (argc >= 1)
+ error = argv[0];
+ else
+ error = JS_UNDEFINED;
+ ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
+ 1, &error);
+ JS_FreeValue(ctx, ret);
+ return JS_UNDEFINED;
+}
+
+static JSValue js_load_module_fulfilled(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int magic, JSValue *func_data)
+{
+ JSValueConst *resolving_funcs = (JSValueConst *)func_data;
+ JSModuleDef *m = JS_VALUE_GET_PTR(func_data[2]);
+ JSValue ret, ns;
+
+ /* return the module namespace */
+ ns = js_get_module_ns(ctx, m);
+ if (JS_IsException(ns)) {
+ JSValue err = JS_GetException(ctx);
+ js_load_module_rejected(ctx, JS_UNDEFINED, 1, (JSValueConst *)&err, 0, func_data);
+ return JS_UNDEFINED;
+ }
+ ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED,
+ 1, (JSValueConst *)&ns);
+ JS_FreeValue(ctx, ret);
+ JS_FreeValue(ctx, ns);
+ return JS_UNDEFINED;
+}
+
+static void JS_LoadModuleInternal(JSContext *ctx, const char *basename,
+ const char *filename,
+ JSValueConst *resolving_funcs)
+{
+ JSValue evaluate_promise;
JSModuleDef *m;
- JSValue ret, func_obj;
+ JSValue ret, err, func_obj, evaluate_resolving_funcs[2];
+ JSValueConst func_data[3];
m = js_host_resolve_imported_module(ctx, basename, filename);
if (!m)
- return NULL;
+ goto fail;
if (js_resolve_module(ctx, m) < 0) {
js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED);
- return NULL;
+ goto fail;
}
/* Evaluate the module code */
- func_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
- ret = JS_EvalFunction(ctx, func_obj);
- if (JS_IsException(ret))
- return NULL;
+ func_obj = JS_NewModuleValue(ctx, m);
+ evaluate_promise = JS_EvalFunction(ctx, func_obj);
+ if (JS_IsException(evaluate_promise)) {
+ fail:
+ err = JS_GetException(ctx);
+ ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
+ 1, (JSValueConst *)&err);
+ JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
+ JS_FreeValue(ctx, err);
+ return;
+ }
+
+ func_obj = JS_NewModuleValue(ctx, m);
+ func_data[0] = resolving_funcs[0];
+ func_data[1] = resolving_funcs[1];
+ func_data[2] = func_obj;
+ evaluate_resolving_funcs[0] = JS_NewCFunctionData(ctx, js_load_module_fulfilled, 0, 0, 3, func_data);
+ evaluate_resolving_funcs[1] = JS_NewCFunctionData(ctx, js_load_module_rejected, 0, 0, 3, func_data);
+ JS_FreeValue(ctx, func_obj);
+ ret = js_promise_then(ctx, evaluate_promise, 2, (JSValueConst *)evaluate_resolving_funcs);
JS_FreeValue(ctx, ret);
- return m;
+ JS_FreeValue(ctx, evaluate_resolving_funcs[0]);
+ JS_FreeValue(ctx, evaluate_resolving_funcs[1]);
+ JS_FreeValue(ctx, evaluate_promise);
+}
+
+/* Return a promise or an exception in case of memory error. Used by
+ os.Worker() */
+JSValue JS_LoadModule(JSContext *ctx, const char *basename,
+ const char *filename)
+{
+ JSValue promise, resolving_funcs[2];
+
+ promise = JS_NewPromiseCapability(ctx, resolving_funcs);
+ if (JS_IsException(promise))
+ return JS_EXCEPTION;
+ JS_LoadModuleInternal(ctx, basename, filename,
+ (JSValueConst *)resolving_funcs);
+ JS_FreeValue(ctx, resolving_funcs[0]);
+ JS_FreeValue(ctx, resolving_funcs[1]);
+ return promise;
}
static JSValue js_dynamic_import_job(JSContext *ctx,
@@ -27872,9 +28062,8 @@ static JSValue js_dynamic_import_job(JSContext *ctx,
JSValueConst *resolving_funcs = argv;
JSValueConst basename_val = argv[2];
JSValueConst specifier = argv[3];
- JSModuleDef *m;
const char *basename = NULL, *filename;
- JSValue ret, err, ns;
+ JSValue ret, err;
if (!JS_IsString(basename_val)) {
JS_ThrowTypeError(ctx, "no function filename for import()");
@@ -27888,24 +28077,12 @@ static JSValue js_dynamic_import_job(JSContext *ctx,
if (!filename)
goto exception;
- m = JS_RunModule(ctx, basename, filename);
+ JS_LoadModuleInternal(ctx, basename, filename,
+ resolving_funcs);
JS_FreeCString(ctx, filename);
- if (!m)
- goto exception;
-
- /* return the module namespace */
- ns = js_get_module_ns(ctx, m);
- if (JS_IsException(ns))
- goto exception;
-
- ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED,
- 1, (JSValueConst *)&ns);
- JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */
- JS_FreeValue(ctx, ns);
JS_FreeCString(ctx, basename);
return JS_UNDEFINED;
exception:
-
err = JS_GetException(ctx);
ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
1, (JSValueConst *)&err);
@@ -27941,6 +28118,8 @@ static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier)
args[2] = basename_val;
args[3] = specifier;
+ /* cannot run JS_LoadModuleInternal synchronously because it would
+ cause an unexpected recursion in js_evaluate_module() */
JS_EnqueueJob(ctx, js_dynamic_import_job, 4, args);
JS_FreeValue(ctx, basename_val);
@@ -27949,60 +28128,397 @@ static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier)
return promise;
}
-/* Run the <eval> function of the module and of all its requested
- modules. */
-static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
+static void js_set_module_evaluated(JSContext *ctx, JSModuleDef *m)
+{
+ m->status = JS_MODULE_STATUS_EVALUATED;
+ if (!JS_IsUndefined(m->promise)) {
+ JSValue value, ret_val;
+ assert(m->cycle_root == m);
+ value = JS_UNDEFINED;
+ ret_val = JS_Call(ctx, m->resolving_funcs[0], JS_UNDEFINED,
+ 1, (JSValueConst *)&value);
+ JS_FreeValue(ctx, ret_val);
+ }
+}
+
+typedef struct {
+ JSModuleDef **tab;
+ int count;
+ int size;
+} ExecModuleList;
+
+/* XXX: slow. Could use a linked list instead of ExecModuleList */
+static BOOL find_in_exec_module_list(ExecModuleList *exec_list, JSModuleDef *m)
+{
+ int i;
+ for(i = 0; i < exec_list->count; i++) {
+ if (exec_list->tab[i] == m)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int gather_available_ancestors(JSContext *ctx, JSModuleDef *module,
+ ExecModuleList *exec_list)
+{
+ int i;
+
+ if (js_check_stack_overflow(ctx->rt, 0)) {
+ JS_ThrowStackOverflow(ctx);
+ return -1;
+ }
+ for(i = 0; i < module->async_parent_modules_count; i++) {
+ JSModuleDef *m = module->async_parent_modules[i];
+ if (!find_in_exec_module_list(exec_list, m) &&
+ !m->cycle_root->eval_has_exception) {
+ assert(m->status == JS_MODULE_STATUS_EVALUATING_ASYNC);
+ assert(!m->eval_has_exception);
+ assert(m->async_evaluation);
+ assert(m->pending_async_dependencies > 0);
+ m->pending_async_dependencies--;
+ if (m->pending_async_dependencies == 0) {
+ if (js_resize_array(ctx, (void **)&exec_list->tab, sizeof(exec_list->tab[0]), &exec_list->size, exec_list->count + 1)) {
+ return -1;
+ }
+ exec_list->tab[exec_list->count++] = m;
+ if (!m->has_tla) {
+ if (gather_available_ancestors(ctx, m, exec_list))
+ return -1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int exec_module_list_cmp(const void *p1, const void *p2, void *opaque)
+{
+ JSModuleDef *m1 = *(JSModuleDef **)p1;
+ JSModuleDef *m2 = *(JSModuleDef **)p2;
+ return (m1->async_evaluation_timestamp > m2->async_evaluation_timestamp) -
+ (m1->async_evaluation_timestamp < m2->async_evaluation_timestamp);
+}
+
+static int js_execute_async_module(JSContext *ctx, JSModuleDef *m);
+static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m,
+ JSValue *pvalue);
+
+static JSValue js_async_module_execution_rejected(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int magic, JSValue *func_data)
+{
+ JSModuleDef *module = JS_VALUE_GET_PTR(func_data[0]);
+ JSValueConst error = argv[0];
+ int i;
+
+ if (js_check_stack_overflow(ctx->rt, 0))
+ return JS_ThrowStackOverflow(ctx);
+
+ if (module->status == JS_MODULE_STATUS_EVALUATED) {
+ assert(module->eval_has_exception);
+ return JS_UNDEFINED;
+ }
+
+ assert(module->status == JS_MODULE_STATUS_EVALUATING_ASYNC);
+ assert(!module->eval_has_exception);
+ assert(module->async_evaluation);
+
+ module->eval_has_exception = TRUE;
+ module->eval_exception = JS_DupValue(ctx, error);
+ module->status = JS_MODULE_STATUS_EVALUATED;
+
+ for(i = 0; i < module->async_parent_modules_count; i++) {
+ JSModuleDef *m = module->async_parent_modules[i];
+ JSValue m_obj = JS_NewModuleValue(ctx, m);
+ js_async_module_execution_rejected(ctx, JS_UNDEFINED, 1, &error, 0,
+ &m_obj);
+ JS_FreeValue(ctx, m_obj);
+ }
+
+ if (!JS_IsUndefined(module->promise)) {
+ JSValue ret_val;
+ assert(module->cycle_root == module);
+ ret_val = JS_Call(ctx, module->resolving_funcs[1], JS_UNDEFINED,
+ 1, &error);
+ JS_FreeValue(ctx, ret_val);
+ }
+ return JS_UNDEFINED;
+}
+
+static JSValue js_async_module_execution_fulfilled(JSContext *ctx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int magic, JSValue *func_data)
+{
+ JSModuleDef *module = JS_VALUE_GET_PTR(func_data[0]);
+ ExecModuleList exec_list_s, *exec_list = &exec_list_s;
+ int i;
+
+ if (module->status == JS_MODULE_STATUS_EVALUATED) {
+ assert(module->eval_has_exception);
+ return JS_UNDEFINED;
+ }
+ assert(module->status == JS_MODULE_STATUS_EVALUATING_ASYNC);
+ assert(!module->eval_has_exception);
+ assert(module->async_evaluation);
+ module->async_evaluation = FALSE;
+ js_set_module_evaluated(ctx, module);
+
+ exec_list->tab = NULL;
+ exec_list->count = 0;
+ exec_list->size = 0;
+
+ if (gather_available_ancestors(ctx, module, exec_list) < 0) {
+ js_free(ctx, exec_list->tab);
+ return JS_EXCEPTION;
+ }
+
+ /* sort by increasing async_evaluation timestamp */
+ rqsort(exec_list->tab, exec_list->count, sizeof(exec_list->tab[0]),
+ exec_module_list_cmp, NULL);
+
+ for(i = 0; i < exec_list->count; i++) {
+ JSModuleDef *m = exec_list->tab[i];
+ if (m->status == JS_MODULE_STATUS_EVALUATED) {
+ assert(m->eval_has_exception);
+ } else if (m->has_tla) {
+ js_execute_async_module(ctx, m);
+ } else {
+ JSValue error;
+ if (js_execute_sync_module(ctx, m, &error) < 0) {
+ JSValue m_obj = JS_NewModuleValue(ctx, m);
+ js_async_module_execution_rejected(ctx, JS_UNDEFINED,
+ 1, (JSValueConst *)&error, 0,
+ &m_obj);
+ JS_FreeValue(ctx, m_obj);
+ JS_FreeValue(ctx, error);
+ } else {
+ js_set_module_evaluated(ctx, m);
+ }
+ }
+ }
+ js_free(ctx, exec_list->tab);
+ return JS_UNDEFINED;
+}
+
+static int js_execute_async_module(JSContext *ctx, JSModuleDef *m)
+{
+ JSValue promise, m_obj;
+ JSValue resolve_funcs[2], ret_val;
+ promise = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0);
+ if (JS_IsException(promise))
+ return -1;
+ m_obj = JS_NewModuleValue(ctx, m);
+ resolve_funcs[0] = JS_NewCFunctionData(ctx, js_async_module_execution_fulfilled, 0, 0, 1, (JSValueConst *)&m_obj);
+ resolve_funcs[1] = JS_NewCFunctionData(ctx, js_async_module_execution_rejected, 0, 0, 1, (JSValueConst *)&m_obj);
+ ret_val = js_promise_then(ctx, promise, 2, (JSValueConst *)resolve_funcs);
+ JS_FreeValue(ctx, ret_val);
+ JS_FreeValue(ctx, m_obj);
+ JS_FreeValue(ctx, resolve_funcs[0]);
+ JS_FreeValue(ctx, resolve_funcs[1]);
+ JS_FreeValue(ctx, promise);
+ return 0;
+}
+
+/* return < 0 in case of exception. *pvalue contains the exception. */
+static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m,
+ JSValue *pvalue)
+{
+ if (m->init_func) {
+ /* C module init : no asynchronous execution */
+ if (m->init_func(ctx, m) < 0)
+ goto fail;
+ } else {
+ JSValue promise;
+ JSPromiseStateEnum state;
+
+ promise = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0);
+ if (JS_IsException(promise))
+ goto fail;
+ state = JS_PromiseState(ctx, promise);
+ if (state == JS_PROMISE_FULFILLED) {
+ JS_FreeValue(ctx, promise);
+ } else if (state == JS_PROMISE_REJECTED) {
+ *pvalue = JS_PromiseResult(ctx, promise);
+ JS_FreeValue(ctx, promise);
+ return -1;
+ } else {
+ JS_FreeValue(ctx, promise);
+ JS_ThrowTypeError(ctx, "promise is pending");
+ fail:
+ *pvalue = JS_GetException(ctx);
+ return -1;
+ }
+ }
+ *pvalue = JS_UNDEFINED;
+ return 0;
+}
+
+/* spec: InnerModuleEvaluation. Return (index, JS_UNDEFINED) or (-1,
+ exception) */
+static int js_inner_module_evaluation(JSContext *ctx, JSModuleDef *m,
+ int index, JSModuleDef **pstack_top,
+ JSValue *pvalue)
{
JSModuleDef *m1;
int i;
- JSValue ret_val;
- if (m->eval_mark)
- return JS_UNDEFINED; /* avoid cycles */
+ if (js_check_stack_overflow(ctx->rt, 0)) {
+ JS_ThrowStackOverflow(ctx);
+ *pvalue = JS_GetException(ctx);
+ return -1;
+ }
+
+#ifdef DUMP_MODULE_RESOLVE
+ {
+ char buf1[ATOM_GET_STR_BUF_SIZE];
+ printf("js_inner_module_evaluation '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name));
+ }
+#endif
- if (m->evaluated) {
- /* if the module was already evaluated, rethrow the exception
- it raised */
+ if (m->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m->status == JS_MODULE_STATUS_EVALUATED) {
if (m->eval_has_exception) {
- return JS_Throw(ctx, JS_DupValue(ctx, m->eval_exception));
+ *pvalue = JS_DupValue(ctx, m->eval_exception);
+ return -1;
} else {
- return JS_UNDEFINED;
+ *pvalue = JS_UNDEFINED;
+ return index;
}
}
+ if (m->status == JS_MODULE_STATUS_EVALUATING) {
+ *pvalue = JS_UNDEFINED;
+ return index;
+ }
+ assert(m->status == JS_MODULE_STATUS_LINKED);
- m->eval_mark = TRUE;
-
+ m->status = JS_MODULE_STATUS_EVALUATING;
+ m->dfs_index = index;
+ m->dfs_ancestor_index = index;
+ m->pending_async_dependencies = 0;
+ index++;
+ /* push 'm' on stack */
+ m->stack_prev = *pstack_top;
+ *pstack_top = m;
+
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;
+ index = js_inner_module_evaluation(ctx, m1, index, pstack_top, pvalue);
+ if (index < 0)
+ return -1;
+ assert(m1->status == JS_MODULE_STATUS_EVALUATING ||
+ m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m1->status == JS_MODULE_STATUS_EVALUATED);
+ if (m1->status == JS_MODULE_STATUS_EVALUATING) {
+ m->dfs_ancestor_index = min_int(m->dfs_ancestor_index,
+ m1->dfs_ancestor_index);
+ } else {
+ m1 = m1->cycle_root;
+ assert(m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m1->status == JS_MODULE_STATUS_EVALUATED);
+ if (m1->eval_has_exception) {
+ *pvalue = JS_DupValue(ctx, m1->eval_exception);
+ return -1;
}
- JS_FreeValue(ctx, ret_val);
+ }
+ if (m1->async_evaluation) {
+ m->pending_async_dependencies++;
+ if (js_resize_array(ctx, (void **)&m1->async_parent_modules, sizeof(m1->async_parent_modules[0]), &m1->async_parent_modules_size, m1->async_parent_modules_count + 1)) {
+ *pvalue = JS_GetException(ctx);
+ return -1;
+ }
+ m1->async_parent_modules[m1->async_parent_modules_count++] = m;
}
}
- if (m->init_func) {
- /* C module init */
- if (m->init_func(ctx, m) < 0)
- ret_val = JS_EXCEPTION;
- else
- ret_val = JS_UNDEFINED;
+ if (m->pending_async_dependencies > 0) {
+ assert(!m->async_evaluation);
+ m->async_evaluation = TRUE;
+ m->async_evaluation_timestamp =
+ ctx->rt->module_async_evaluation_next_timestamp++;
+ } else if (m->has_tla) {
+ assert(!m->async_evaluation);
+ m->async_evaluation = TRUE;
+ m->async_evaluation_timestamp =
+ ctx->rt->module_async_evaluation_next_timestamp++;
+ js_execute_async_module(ctx, m);
} else {
- ret_val = JS_CallFree(ctx, m->func_obj, JS_UNDEFINED, 0, NULL);
- m->func_obj = JS_UNDEFINED;
+ if (js_execute_sync_module(ctx, m, pvalue) < 0)
+ return -1;
}
- 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);
+
+ assert(m->dfs_ancestor_index <= m->dfs_index);
+ if (m->dfs_index == m->dfs_ancestor_index) {
+ for(;;) {
+ /* pop m1 from stack */
+ m1 = *pstack_top;
+ *pstack_top = m1->stack_prev;
+ if (!m1->async_evaluation) {
+ m1->status = JS_MODULE_STATUS_EVALUATED;
+ } else {
+ m1->status = JS_MODULE_STATUS_EVALUATING_ASYNC;
+ }
+ /* spec bug: cycle_root must be assigned before the test */
+ m1->cycle_root = m;
+ if (m1 == m)
+ break;
+ }
}
- m->eval_mark = FALSE;
- m->evaluated = TRUE;
- return ret_val;
+ *pvalue = JS_UNDEFINED;
+ return index;
+}
+
+/* Run the <eval> function of the module and of all its requested
+ modules. Return a promise or an exception. */
+static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
+{
+ JSModuleDef *m1, *stack_top;
+ JSValue ret_val, result;
+
+ assert(m->status == JS_MODULE_STATUS_LINKED ||
+ m->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m->status == JS_MODULE_STATUS_EVALUATED);
+ if (m->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m->status == JS_MODULE_STATUS_EVALUATED) {
+ m = m->cycle_root;
+ }
+ /* a promise may be created only on the cycle_root of a cycle */
+ if (!JS_IsUndefined(m->promise))
+ return JS_DupValue(ctx, m->promise);
+ m->promise = JS_NewPromiseCapability(ctx, m->resolving_funcs);
+ if (JS_IsException(m->promise))
+ return JS_EXCEPTION;
+
+ stack_top = NULL;
+ if (js_inner_module_evaluation(ctx, m, 0, &stack_top, &result) < 0) {
+ while (stack_top != NULL) {
+ m1 = stack_top;
+ assert(m1->status == JS_MODULE_STATUS_EVALUATING);
+ m1->status = JS_MODULE_STATUS_EVALUATED;
+ m1->eval_has_exception = TRUE;
+ m1->eval_exception = JS_DupValue(ctx, result);
+ m1->cycle_root = m; /* spec bug: should be present */
+ stack_top = m1->stack_prev;
+ }
+ JS_FreeValue(ctx, result);
+ assert(m->status == JS_MODULE_STATUS_EVALUATED);
+ assert(m->eval_has_exception);
+ ret_val = JS_Call(ctx, m->resolving_funcs[1], JS_UNDEFINED,
+ 1, (JSValueConst *)&m->eval_exception);
+ JS_FreeValue(ctx, ret_val);
+ } else {
+ assert(m->status == JS_MODULE_STATUS_EVALUATING_ASYNC ||
+ m->status == JS_MODULE_STATUS_EVALUATED);
+ assert(!m->eval_has_exception);
+ if (!m->async_evaluation) {
+ JSValue value;
+ assert(m->status == JS_MODULE_STATUS_EVALUATED);
+ value = JS_UNDEFINED;
+ ret_val = JS_Call(ctx, m->resolving_funcs[0], JS_UNDEFINED,
+ 1, (JSValueConst *)&value);
+ JS_FreeValue(ctx, ret_val);
+ }
+ assert(stack_top == NULL);
+ }
+ return JS_DupValue(ctx, m->promise);
}
static __exception JSAtom js_parse_from_clause(JSParseState *s)
@@ -33217,7 +33733,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;
@@ -33260,7 +33776,6 @@ static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj,
ret_val = js_evaluate_module(ctx, m);
if (JS_IsException(ret_val)) {
fail:
- js_free_modules(ctx, JS_FREE_MODULE_NOT_EVALUATED);
return JS_EXCEPTION;
}
} else {
@@ -33373,6 +33888,10 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
goto fail;
}
fd->module = m;
+ if (m != NULL) {
+ fd->in_function_body = TRUE;
+ fd->func_kind = JS_FUNC_ASYNC;
+ }
s->is_module = (m != NULL);
s->allow_html_comments = !s->is_module;
@@ -33387,6 +33906,9 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
goto fail1;
}
+ if (m != NULL)
+ m->has_tla = fd->has_await;
+
/* create the function object and all the enclosed functions */
fun_obj = js_create_function(ctx, fd);
if (JS_IsException(fun_obj))
@@ -33396,7 +33918,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
m->func_obj = fun_obj;
if (js_resolve_module(ctx, m) < 0)
goto fail1;
- fun_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
+ fun_obj = JS_NewModuleValue(ctx, m);
}
if (flags & JS_EVAL_FLAG_COMPILE_ONLY) {
ret_val = fun_obj;
@@ -34142,6 +34664,8 @@ static int JS_WriteModule(BCWriterState *s, JSValueConst obj)
bc_put_atom(s, mi->import_name);
bc_put_leb128(s, mi->req_module_idx);
}
+
+ bc_put_u8(s, m->has_tla);
if (JS_WriteObjectRec(s, m->func_obj))
goto fail;
@@ -35153,7 +35677,7 @@ static JSValue JS_ReadModule(BCReaderState *s)
m = js_new_module_def(ctx, module_name);
if (!m)
goto fail;
- obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
+ obj = JS_NewModuleValue(ctx, m);
if (bc_get_leb128_int(s, &m->req_module_entries_count))
goto fail;
if (m->req_module_entries_count != 0) {
@@ -35226,6 +35750,10 @@ static JSValue JS_ReadModule(BCReaderState *s)
}
}
+ if (bc_get_u8(s, &v8))
+ goto fail;
+ m->has_tla = (v8 != 0);
+
m->func_obj = JS_ReadObjectRec(s);
if (JS_IsException(m->func_obj))
goto fail;
@@ -46148,12 +46676,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 */
@@ -46178,6 +46700,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);
diff --git a/quickjs.h b/quickjs.h
index ce3dc90..41c3882 100644
--- a/quickjs.h
+++ b/quickjs.h
@@ -831,7 +831,15 @@ typedef struct {
void JS_SetSharedArrayBufferFunctions(JSRuntime *rt,
const JSSharedArrayBufferFunctions *sf);
+typedef enum JSPromiseStateEnum {
+ JS_PROMISE_PENDING,
+ JS_PROMISE_FULFILLED,
+ JS_PROMISE_REJECTED,
+} JSPromiseStateEnum;
+
JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs);
+JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise);
+JSValue JS_PromiseResult(JSContext *ctx, JSValue promise);
/* is_handled = TRUE means that the rejection is handled */
typedef void JSHostPromiseRejectionTracker(JSContext *ctx, JSValueConst promise,
@@ -902,8 +910,8 @@ int JS_ResolveModule(JSContext *ctx, JSValueConst obj);
/* only exported for os.Worker() */
JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels);
/* only exported for os.Worker() */
-JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename,
- const char *filename);
+JSValue JS_LoadModule(JSContext *ctx, const char *basename,
+ const char *filename);
/* C function definition */
typedef enum JSCFunctionEnum { /* XXX: should rename for namespace isolation */
diff --git a/run-test262.c b/run-test262.c
index 2092cac..e0cf771 100644
--- a/run-test262.c
+++ b/run-test262.c
@@ -1174,7 +1174,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
{
JSValue res_val, exception_val;
int ret, error_line, pos, pos_line;
- BOOL is_error, has_error_line;
+ BOOL is_error, has_error_line, ret_promise;
const char *error_name;
pos = skip_comments(buf, 1, &pos_line);
@@ -1183,12 +1183,19 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
exception_val = JS_UNDEFINED;
error_name = NULL;
+ /* a module evaluation returns a promise */
+ ret_promise = ((eval_flags & JS_EVAL_TYPE_MODULE) != 0);
async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */
-
+
res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
- if (is_async && !JS_IsException(res_val)) {
- JS_FreeValue(ctx, res_val);
+ if ((is_async || ret_promise) && !JS_IsException(res_val)) {
+ JSValue promise = JS_UNDEFINED;
+ if (ret_promise) {
+ promise = res_val;
+ } else {
+ JS_FreeValue(ctx, res_val);
+ }
for(;;) {
JSContext *ctx1;
ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
@@ -1196,15 +1203,27 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len,
res_val = JS_EXCEPTION;
break;
} else if (ret == 0) {
- /* test if the test called $DONE() once */
- if (async_done != 1) {
- res_val = JS_ThrowTypeError(ctx, "$DONE() not called");
+ if (is_async) {
+ /* test if the test called $DONE() once */
+ if (async_done != 1) {
+ res_val = JS_ThrowTypeError(ctx, "$DONE() not called");
+ } else {
+ res_val = JS_UNDEFINED;
+ }
} else {
- res_val = JS_UNDEFINED;
+ /* check that the returned promise is fulfilled */
+ JSPromiseStateEnum state = JS_PromiseState(ctx, promise);
+ if (state == JS_PROMISE_FULFILLED)
+ res_val = JS_UNDEFINED;
+ else if (state == JS_PROMISE_REJECTED)
+ res_val = JS_Throw(ctx, JS_PromiseResult(ctx, promise));
+ else
+ res_val = JS_ThrowTypeError(ctx, "promise is pending");
}
break;
}
}
+ JS_FreeValue(ctx, promise);
}
if (JS_IsException(res_val)) {
diff --git a/test262.conf b/test262.conf
index c4349f1..1fec965 100644
--- a/test262.conf
+++ b/test262.conf
@@ -195,7 +195,7 @@ symbols-as-weakmap-keys=skip
tail-call-optimization=skip
template
Temporal=skip
-top-level-await=skip
+top-level-await
TypedArray
TypedArray.prototype.at
u180e