[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:
Joshua Litt 2019-09-20 06:32:43 -07:00 committed by Commit Bot
parent 1d3c4975be
commit 39cc400dea
6 changed files with 127 additions and 7 deletions

View File

@ -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,20 +893,45 @@ 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();
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) {
HandleScope handle_scope(isolate);
@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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
]

View File

@ -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"]

View File

@ -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([