Commit 3499b856 authored by Shu-yu Guo's avatar Shu-yu Guo Committed by Commit Bot

[top-level-await] Implement v8::Module::IsGraphAsync()

This is a predicate checking if any module in a module graph is [[Async]], i.e.
contains a top-level await. It is needed for ServiceWorker integration, as
ServiceWorkers disallows top-level await in its modules to prevent stalling
during registration.

https://github.com/w3c/ServiceWorker/pull/1444

Bug: v8:9344
Change-Id: Id84489bc73717b4c9950059c8ff6def9297499d0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2451212
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70390}
parent cb0d2f44
...@@ -1608,6 +1608,14 @@ class V8_EXPORT Module : public Data { ...@@ -1608,6 +1608,14 @@ class V8_EXPORT Module : public Data {
*/ */
int ScriptId(); int ScriptId();
/**
* Returns whether this module or any of its requested modules is async,
* i.e. contains top-level await.
*
* The module's status must be at least kInstantiated.
*/
bool IsGraphAsync() const;
/** /**
* Returns whether the module is a SourceTextModule. * Returns whether the module is a SourceTextModule.
*/ */
......
...@@ -2329,6 +2329,15 @@ int Module::ScriptId() { ...@@ -2329,6 +2329,15 @@ int Module::ScriptId() {
return ToApiHandle<UnboundScript>(sfi)->GetId(); return ToApiHandle<UnboundScript>(sfi)->GetId();
} }
bool Module::IsGraphAsync() const {
Utils::ApiCheck(
GetStatus() >= kInstantiated, "v8::Module::IsGraphAsync",
"v8::Module::IsGraphAsync must be used on an instantiated module");
i::Handle<i::Module> self = Utils::OpenHandle(this);
auto isolate = reinterpret_cast<i::Isolate*>(self->GetIsolate());
return self->IsGraphAsync(isolate);
}
bool Module::IsSourceTextModule() const { bool Module::IsSourceTextModule() const {
return Utils::OpenHandle(this)->IsSourceTextModule(); return Utils::OpenHandle(this)->IsSourceTextModule();
} }
......
...@@ -44,6 +44,12 @@ ACCESSORS(SourceTextModule, async_parent_modules, ArrayList, ...@@ -44,6 +44,12 @@ ACCESSORS(SourceTextModule, async_parent_modules, ArrayList,
ACCESSORS(SourceTextModule, top_level_capability, HeapObject, ACCESSORS(SourceTextModule, top_level_capability, HeapObject,
kTopLevelCapabilityOffset) kTopLevelCapabilityOffset)
struct Module::Hash {
V8_INLINE size_t operator()(Module const& module) const {
return module.hash();
}
};
SourceTextModuleInfo SourceTextModule::info() const { SourceTextModuleInfo SourceTextModule::info() const {
return status() == kErrored return status() == kErrored
? SourceTextModuleInfo::cast(code()) ? SourceTextModuleInfo::cast(code())
......
...@@ -371,5 +371,38 @@ Maybe<PropertyAttributes> JSModuleNamespace::GetPropertyAttributes( ...@@ -371,5 +371,38 @@ Maybe<PropertyAttributes> JSModuleNamespace::GetPropertyAttributes(
return Just(it->property_attributes()); return Just(it->property_attributes());
} }
bool Module::IsGraphAsync(Isolate* isolate) {
DisallowGarbageCollection no_gc;
// Only SourceTextModules may be async.
if (!IsSourceTextModule()) return false;
SourceTextModule root = SourceTextModule::cast(*this);
Zone zone(isolate->allocator(), ZONE_NAME);
const size_t bucket_count = 2;
ZoneUnorderedSet<Module, Module::Hash> visited(&zone, bucket_count);
ZoneVector<SourceTextModule> worklist(&zone);
visited.insert(root);
worklist.push_back(root);
do {
SourceTextModule current = worklist.back();
worklist.pop_back();
DCHECK_GE(current.status(), kInstantiated);
if (current.async()) return true;
FixedArray requested_modules = current.requested_modules();
for (int i = 0, length = requested_modules.length(); i < length; ++i) {
Module descendant = Module::cast(requested_modules.get(i));
if (descendant.IsSourceTextModule()) {
const bool cycle = !visited.insert(descendant).second;
if (!cycle) worklist.push_back(SourceTextModule::cast(descendant));
}
}
} while (!worklist.empty());
return false;
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -63,6 +63,10 @@ class Module : public HeapObject { ...@@ -63,6 +63,10 @@ class Module : public HeapObject {
Object GetException(); Object GetException();
DECL_ACCESSORS(exception, Object) DECL_ACCESSORS(exception, Object)
// Returns if this module or any transitively requested module is [[Async]],
// i.e. has a top-level await.
V8_WARN_UNUSED_RESULT bool IsGraphAsync(Isolate* isolate);
// Implementation of spec operation ModuleDeclarationInstantiation. // Implementation of spec operation ModuleDeclarationInstantiation.
// Returns false if an exception occurred during instantiation, true // Returns false if an exception occurred during instantiation, true
// otherwise. (In the case where the callback throws an exception, that // otherwise. (In the case where the callback throws an exception, that
...@@ -87,6 +91,8 @@ class Module : public HeapObject { ...@@ -87,6 +91,8 @@ class Module : public HeapObject {
using BodyDescriptor = using BodyDescriptor =
FixedBodyDescriptor<kExportsOffset, kHeaderSize, kHeaderSize>; FixedBodyDescriptor<kExportsOffset, kHeaderSize, kHeaderSize>;
struct Hash;
protected: protected:
friend class Factory; friend class Factory;
......
...@@ -993,4 +993,131 @@ TEST(TerminateExecutionTopLevelAwaitAsync) { ...@@ -993,4 +993,131 @@ TEST(TerminateExecutionTopLevelAwaitAsync) {
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value; i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
} }
static Local<Module> async_leaf_module;
static Local<Module> sync_leaf_module;
static Local<Module> cycle_self_module;
static Local<Module> cycle_one_module;
static Local<Module> cycle_two_module;
MaybeLocal<Module> ResolveCallbackForIsGraphAsyncTopLevelAwait(
Local<Context> context, Local<String> specifier, Local<Module> referrer) {
if (specifier->StrictEquals(v8_str("./async_leaf.js"))) {
return async_leaf_module;
} else if (specifier->StrictEquals(v8_str("./sync_leaf.js"))) {
return sync_leaf_module;
} else if (specifier->StrictEquals(v8_str("./cycle_self.js"))) {
return cycle_self_module;
} else if (specifier->StrictEquals(v8_str("./cycle_one.js"))) {
return cycle_one_module;
} else {
CHECK(specifier->StrictEquals(v8_str("./cycle_two.js")));
return cycle_two_module;
}
}
TEST(IsGraphAsyncTopLevelAwait) {
bool previous_top_level_await_flag_value = i::FLAG_harmony_top_level_await;
i::FLAG_harmony_top_level_await = true;
Isolate* isolate = CcTest::isolate();
HandleScope scope(isolate);
LocalContext env;
{
Local<String> source_text = v8_str("await notExecuted();");
ScriptOrigin origin =
ModuleOrigin(v8_str("async_leaf.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
async_leaf_module =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(async_leaf_module
->InstantiateModule(env.local(),
ResolveCallbackForIsGraphAsyncTopLevelAwait)
.FromJust());
CHECK(async_leaf_module->IsGraphAsync());
}
{
Local<String> source_text = v8_str("notExecuted();");
ScriptOrigin origin =
ModuleOrigin(v8_str("sync_leaf.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
sync_leaf_module =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(sync_leaf_module
->InstantiateModule(env.local(),
ResolveCallbackForIsGraphAsyncTopLevelAwait)
.FromJust());
CHECK(!sync_leaf_module->IsGraphAsync());
}
{
Local<String> source_text = v8_str("import './async_leaf.js'");
ScriptOrigin origin =
ModuleOrigin(v8_str("import_async.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(module
->InstantiateModule(env.local(),
ResolveCallbackForIsGraphAsyncTopLevelAwait)
.FromJust());
CHECK(module->IsGraphAsync());
}
{
Local<String> source_text = v8_str("import './sync_leaf.js'");
ScriptOrigin origin =
ModuleOrigin(v8_str("import_sync.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(module
->InstantiateModule(env.local(),
ResolveCallbackForIsGraphAsyncTopLevelAwait)
.FromJust());
CHECK(!module->IsGraphAsync());
}
{
Local<String> source_text = v8_str(
"import './cycle_self.js'\n"
"import './async_leaf.js'");
ScriptOrigin origin =
ModuleOrigin(v8_str("cycle_self.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
cycle_self_module =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(cycle_self_module
->InstantiateModule(env.local(),
ResolveCallbackForIsGraphAsyncTopLevelAwait)
.FromJust());
CHECK(cycle_self_module->IsGraphAsync());
}
{
Local<String> source_text1 = v8_str("import './cycle_two.js'");
ScriptOrigin origin1 =
ModuleOrigin(v8_str("cycle_one.js"), CcTest::isolate());
ScriptCompiler::Source source1(source_text1, origin1);
cycle_one_module =
ScriptCompiler::CompileModule(isolate, &source1).ToLocalChecked();
Local<String> source_text2 = v8_str(
"import './cycle_one.js'\n"
"import './async_leaf.js'");
ScriptOrigin origin2 =
ModuleOrigin(v8_str("cycle_two.js"), CcTest::isolate());
ScriptCompiler::Source source2(source_text2, origin2);
cycle_two_module =
ScriptCompiler::CompileModule(isolate, &source2).ToLocalChecked();
CHECK(cycle_one_module
->InstantiateModule(env.local(),
ResolveCallbackForIsGraphAsyncTopLevelAwait)
.FromJust());
CHECK(cycle_one_module->IsGraphAsync());
CHECK(cycle_two_module->IsGraphAsync());
}
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
}
} // anonymous namespace } // anonymous namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment