[top-level-await] Add support for top level await to d8
This cl adds support for top level await to d8, but still does not allow top level await through parsing. Unfortunately, due to that restriction this cl has no automated tests, but I added a 'top-level-await' variant and manually confirmed it passes locally. Bug: v8:9344 Change-Id: I3528442768107f5ad1ed1e9e947cfceae91c0cc6 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1808483 Commit-Queue: Joshua Litt <joshualitt@chromium.org> Reviewed-by: Georg Neis <neis@chromium.org> Reviewed-by: Adam Klein <adamk@chromium.org> Cr-Commit-Position: refs/heads/master@{#63909}
This commit is contained in:
parent
1d3c4975be
commit
39cc400dea
111
src/d8/d8.cc
111
src/d8/d8.cc
@ -752,8 +752,60 @@ struct DynamicImportData {
|
||||
Global<Promise::Resolver> resolver;
|
||||
};
|
||||
|
||||
struct ModuleResolutionData {
|
||||
ModuleResolutionData(Isolate* isolate_, Local<Value> module_namespace_,
|
||||
Local<Promise::Resolver> resolver_)
|
||||
: isolate(isolate_) {
|
||||
module_namespace.Reset(isolate, module_namespace_);
|
||||
resolver.Reset(isolate, resolver_);
|
||||
}
|
||||
|
||||
Isolate* isolate;
|
||||
Global<Value> module_namespace;
|
||||
Global<Promise::Resolver> resolver;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void Shell::ModuleResolutionSuccessCallback(
|
||||
const FunctionCallbackInfo<Value>& info) {
|
||||
std::unique_ptr<ModuleResolutionData> module_resolution_data(
|
||||
static_cast<ModuleResolutionData*>(
|
||||
info.Data().As<v8::External>()->Value()));
|
||||
Isolate* isolate(module_resolution_data->isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
Local<Promise::Resolver> resolver(
|
||||
module_resolution_data->resolver.Get(isolate));
|
||||
Local<Value> module_namespace(
|
||||
module_resolution_data->module_namespace.Get(isolate));
|
||||
|
||||
PerIsolateData* data = PerIsolateData::Get(isolate);
|
||||
Local<Context> realm = data->realms_[data->realm_current_].Get(isolate);
|
||||
Context::Scope context_scope(realm);
|
||||
|
||||
resolver->Resolve(realm, module_namespace).ToChecked();
|
||||
}
|
||||
|
||||
void Shell::ModuleResolutionFailureCallback(
|
||||
const FunctionCallbackInfo<Value>& info) {
|
||||
std::unique_ptr<ModuleResolutionData> module_resolution_data(
|
||||
static_cast<ModuleResolutionData*>(
|
||||
info.Data().As<v8::External>()->Value()));
|
||||
Isolate* isolate(module_resolution_data->isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
Local<Promise::Resolver> resolver(
|
||||
module_resolution_data->resolver.Get(isolate));
|
||||
|
||||
PerIsolateData* data = PerIsolateData::Get(isolate);
|
||||
Local<Context> realm = data->realms_[data->realm_current_].Get(isolate);
|
||||
Context::Scope context_scope(realm);
|
||||
|
||||
DCHECK_EQ(info.Length(), 1);
|
||||
resolver->Reject(realm, info[0]).ToChecked();
|
||||
}
|
||||
|
||||
MaybeLocal<Promise> Shell::HostImportModuleDynamically(
|
||||
Local<Context> context, Local<ScriptOrModule> referrer,
|
||||
Local<String> specifier) {
|
||||
@ -841,19 +893,44 @@ void Shell::DoHostImportModuleDynamically(void* import_data) {
|
||||
if (root_module->InstantiateModule(realm, ResolveModuleCallback)
|
||||
.FromMaybe(false)) {
|
||||
maybe_result = root_module->Evaluate(realm);
|
||||
CHECK_IMPLIES(i::FLAG_harmony_top_level_await, !maybe_result.IsEmpty());
|
||||
EmptyMessageQueues(isolate);
|
||||
}
|
||||
|
||||
Local<Value> module;
|
||||
if (!maybe_result.ToLocal(&module)) {
|
||||
Local<Value> result;
|
||||
if (!maybe_result.ToLocal(&result)) {
|
||||
DCHECK(try_catch.HasCaught());
|
||||
resolver->Reject(realm, try_catch.Exception()).ToChecked();
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK(!try_catch.HasCaught());
|
||||
Local<Value> module_namespace = root_module->GetModuleNamespace();
|
||||
resolver->Resolve(realm, module_namespace).ToChecked();
|
||||
if (i::FLAG_harmony_top_level_await) {
|
||||
Local<Promise> result_promise(Local<Promise>::Cast(result));
|
||||
if (result_promise->State() == Promise::kRejected) {
|
||||
resolver->Reject(realm, result_promise->Result()).ToChecked();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup callbacks, and then chain them to the result promise.
|
||||
// ModuleResolutionData will be deleted by the callbacks.
|
||||
auto module_resolution_data =
|
||||
new ModuleResolutionData(isolate, module_namespace, resolver);
|
||||
Local<v8::External> edata = External::New(isolate, module_resolution_data);
|
||||
Local<Function> callback_success;
|
||||
CHECK(Function::New(realm, ModuleResolutionSuccessCallback, edata)
|
||||
.ToLocal(&callback_success));
|
||||
Local<Function> callback_failure;
|
||||
CHECK(Function::New(realm, ModuleResolutionFailureCallback, edata)
|
||||
.ToLocal(&callback_failure));
|
||||
result_promise->Then(realm, callback_success, callback_failure)
|
||||
.ToLocalChecked();
|
||||
} else {
|
||||
// TODO(joshualitt): Clean up exception handling after introucing new
|
||||
// API for evaluating async modules.
|
||||
DCHECK(!try_catch.HasCaught());
|
||||
resolver->Resolve(realm, module_namespace).ToChecked();
|
||||
}
|
||||
}
|
||||
|
||||
bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
|
||||
@ -869,7 +946,6 @@ bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
|
||||
try_catch.SetVerbose(true);
|
||||
|
||||
Local<Module> root_module;
|
||||
MaybeLocal<Value> maybe_exception;
|
||||
|
||||
if (!FetchModuleTree(realm, absolute_path).ToLocal(&root_module)) {
|
||||
CHECK(try_catch.HasCaught());
|
||||
@ -881,6 +957,7 @@ bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
|
||||
if (root_module->InstantiateModule(realm, ResolveModuleCallback)
|
||||
.FromMaybe(false)) {
|
||||
maybe_result = root_module->Evaluate(realm);
|
||||
CHECK_IMPLIES(i::FLAG_harmony_top_level_await, !maybe_result.IsEmpty());
|
||||
EmptyMessageQueues(isolate);
|
||||
}
|
||||
Local<Value> result;
|
||||
@ -890,6 +967,30 @@ bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
|
||||
ReportException(isolate, &try_catch);
|
||||
return false;
|
||||
}
|
||||
if (i::FLAG_harmony_top_level_await) {
|
||||
// Loop until module execution finishes
|
||||
// TODO(joshualitt): This is a bit wonky. "Real" engines would not be
|
||||
// able to just busy loop waiting for execution to finish.
|
||||
Local<Promise> result_promise(Local<Promise>::Cast(result));
|
||||
while (result_promise->State() == Promise::kPending) {
|
||||
isolate->RunMicrotasks();
|
||||
}
|
||||
|
||||
if (result_promise->State() == Promise::kRejected) {
|
||||
// If the exception has been caught by the promise pipeline, we rethrow
|
||||
// here in order to ReportException.
|
||||
// TODO(joshualitt): Clean this up after we create a new API for the case
|
||||
// where TLA is enabled.
|
||||
if (!try_catch.HasCaught()) {
|
||||
isolate->ThrowException(result_promise->Result());
|
||||
} else {
|
||||
DCHECK_EQ(try_catch.Exception(), result_promise->Result());
|
||||
}
|
||||
ReportException(isolate, &try_catch);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DCHECK(!try_catch.HasCaught());
|
||||
return true;
|
||||
}
|
||||
|
@ -426,6 +426,10 @@ class Shell : public i::AllStatic {
|
||||
static MaybeLocal<Promise> HostImportModuleDynamically(
|
||||
Local<Context> context, Local<ScriptOrModule> referrer,
|
||||
Local<String> specifier);
|
||||
static void ModuleResolutionSuccessCallback(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void ModuleResolutionFailureCallback(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
static void HostInitializeImportMetaObject(Local<Context> context,
|
||||
Local<Module> module,
|
||||
Local<Object> meta);
|
||||
|
@ -3,6 +3,9 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --allow-natives-syntax --harmony-dynamic-import
|
||||
//
|
||||
// Note: This test fails with top level await due to test1, which tries to
|
||||
// import a module using top level await and expects it to fail.
|
||||
|
||||
var ran = false;
|
||||
|
||||
|
@ -1123,4 +1123,10 @@
|
||||
'compiler/load-elimination-const-field': [SKIP],
|
||||
}], # variant == turboprop
|
||||
|
||||
##############################################################################
|
||||
['variant == top_level_await', {
|
||||
# specifically expects to fail on top level await.
|
||||
'harmony/modules-import-15': [SKIP],
|
||||
}], # variant == top_level_await
|
||||
|
||||
]
|
||||
|
@ -300,6 +300,8 @@ JS_TEST_PATHS = {
|
||||
'webkit': [[]],
|
||||
}
|
||||
|
||||
FILE_EXTENSIONS = [".js", ".mjs"]
|
||||
|
||||
def PresubmitCheck(path):
|
||||
with open(path) as f:
|
||||
contents = ReadContent(f.read())
|
||||
@ -326,8 +328,11 @@ def PresubmitCheck(path):
|
||||
_assert('*' not in rule or (rule.count('*') == 1 and rule[-1] == '*'),
|
||||
"Only the last character of a rule key can be a wildcard")
|
||||
if basename in JS_TEST_PATHS and '*' not in rule:
|
||||
_assert(any(os.path.exists(os.path.join(os.path.dirname(path),
|
||||
*(paths + [rule + ".js"])))
|
||||
def _any_exist(paths):
|
||||
return any(os.path.exists(os.path.join(os.path.dirname(path),
|
||||
*(paths + [rule + ext])))
|
||||
for ext in FILE_EXTENSIONS)
|
||||
_assert(any(_any_exist(paths)
|
||||
for paths in JS_TEST_PATHS[basename]),
|
||||
"missing file for %s test %s" % (basename, rule))
|
||||
return status["success"]
|
||||
|
@ -33,6 +33,7 @@ ALL_VARIANT_FLAGS = {
|
||||
"trusted": [["--no-untrusted-code-mitigations"]],
|
||||
"no_wasm_traps": [["--no-wasm-trap-handler"]],
|
||||
"turboprop": [["--turboprop"]],
|
||||
"top_level_await": [["--harmony-top-level-await"]],
|
||||
}
|
||||
|
||||
SLOW_VARIANTS = set([
|
||||
|
Loading…
Reference in New Issue
Block a user