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

[top-level-await] Implement the new post-order requirement for async subgraphs

This CL implements
https://github.com/tc39/proposal-top-level-await/pull/159, which reached
consensus at the March 2021 TC39.

The high-level intent is for parent modules that depend on async modules
to remember the DFS post-order such that when their async dependency
finishes, they execute in that original post-order. This aligns the
ordering between completely sync module graphs and async module graphs.

Bug: v8:11557
Change-Id: I5bd8f38f040115c255ca1ce8253b9686fdb4af03
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2757901
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73551}
parent 341ab4dc
......@@ -365,6 +365,9 @@ constexpr int kUC16Size = sizeof(uc16); // NOLINT
// 128 bit SIMD value size.
constexpr int kSimd128Size = 16;
// Maximum ordinal used for tracking asynchronous module evaluation order.
constexpr unsigned kMaxModuleAsyncEvaluatingOrdinal = (1 << 30) - 1;
// FUNCTION_ADDR(f) gets the address of a C function f.
#define FUNCTION_ADDR(f) (reinterpret_cast<v8::internal::Address>(f))
......
......@@ -1519,7 +1519,7 @@ void SourceTextModule::SourceTextModuleVerify(Isolate* isolate) {
}
CHECK(!AsyncParentModuleCount());
CHECK(!pending_async_dependencies());
CHECK(!async_evaluating());
CHECK(!IsAsyncEvaluating());
}
CHECK_EQ(requested_modules().length(), info().module_requests().length());
......
......@@ -1746,6 +1746,7 @@ void SourceTextModule::SourceTextModulePrint(std::ostream& os) { // NOLINT
os << "\n - requested_modules: " << Brief(requested_modules());
os << "\n - import_meta: " << Brief(import_meta());
os << "\n - cycle_root: " << Brief(cycle_root());
os << "\n - async_evaluating_ordinal: " << async_evaluating_ordinal();
os << "\n";
}
......
......@@ -14,6 +14,7 @@
#include "src/objects/property-cell.h"
#include "src/objects/regexp-match-info.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/source-text-module-inl.h"
namespace v8 {
namespace internal {
......@@ -120,6 +121,36 @@ bool Isolate::IsAnyInitialArrayPrototype(JSArray array) {
return IsInAnyContext(array, Context::INITIAL_ARRAY_PROTOTYPE_INDEX);
}
void Isolate::DidFinishModuleAsyncEvaluation(unsigned ordinal) {
// To address overflow, the ordinal is reset when the async module with the
// largest vended ordinal finishes evaluating. Modules are evaluated in
// ascending order of their async_evaluating_ordinal.
//
// While the specification imposes a global total ordering, the intention is
// that for each async module, all its parents are totally ordered by when
// they first had their [[AsyncEvaluating]] bit set.
//
// The module with largest vended ordinal finishes evaluating implies that the
// async dependency as well as all other modules in that module's graph
// depending on async dependencies are finished evaluating.
//
// If the async dependency participates in other module graphs (e.g. via
// dynamic import, or other <script type=module> tags), those module graphs
// must have been evaluated either before or after the async dependency is
// settled, as the concrete Evaluate() method on cyclic module records is
// neither reentrant nor performs microtask checkpoints during its
// evaluation. If before, then all modules that depend on the async
// dependencies were given an ordinal that ensure they are relatively ordered,
// before the global ordinal was reset. If after, then the async evaluating
// ordering does not apply, as the dependency is no longer asynchronous.
//
// https://tc39.es/ecma262/#sec-moduleevaluation
if (ordinal + 1 == next_module_async_evaluating_ordinal_) {
next_module_async_evaluating_ordinal_ =
SourceTextModule::kFirstAsyncEvaluatingOrdinal;
}
}
#define NATIVE_CONTEXT_FIELD_ACCESSOR(index, type, name) \
Handle<type> Isolate::name() { \
return Handle<type>(raw_native_context().name(), this); \
......
......@@ -74,6 +74,7 @@
#include "src/objects/prototype.h"
#include "src/objects/slots.h"
#include "src/objects/smi.h"
#include "src/objects/source-text-module-inl.h"
#include "src/objects/stack-frame-info-inl.h"
#include "src/objects/visitors.h"
#include "src/profiler/heap-profiler.h"
......@@ -2937,6 +2938,8 @@ Isolate::Isolate(std::unique_ptr<i::IsolateAllocator> isolate_allocator)
#if V8_SFI_HAS_UNIQUE_ID
next_unique_sfi_id_(0),
#endif
next_module_async_evaluating_ordinal_(
SourceTextModule::kFirstAsyncEvaluatingOrdinal),
cancelable_task_manager_(new CancelableTaskManager()) {
TRACE_ISOLATE(constructor);
CheckIsolateLayout();
......
......@@ -1374,6 +1374,22 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
return id;
}
// https://github.com/tc39/proposal-top-level-await/pull/159
// TODO(syg): Update to actual spec link once merged.
//
// According to the spec, modules that depend on async modules (i.e. modules
// with top-level await) must be evaluated in order in which their
// [[AsyncEvaluating]] flags were set to true. V8 tracks this global total
// order with next_module_async_evaluating_ordinal_. Each module that sets its
// [[AsyncEvaluating]] to true grabs the next ordinal.
unsigned NextModuleAsyncEvaluatingOrdinal() {
unsigned ordinal = next_module_async_evaluating_ordinal_++;
CHECK_LT(ordinal, kMaxModuleAsyncEvaluatingOrdinal);
return ordinal;
}
inline void DidFinishModuleAsyncEvaluation(unsigned ordinal);
void AddNearHeapLimitCallback(v8::NearHeapLimitCallback, void* data);
void RemoveNearHeapLimitCallback(v8::NearHeapLimitCallback callback,
size_t heap_limit);
......@@ -1979,6 +1995,8 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
std::atomic<int> next_unique_sfi_id_;
#endif
unsigned next_module_async_evaluating_ordinal_;
// Vector of callbacks before a Call starts execution.
std::vector<BeforeCallEnteredCallback> before_call_entered_callbacks_;
......
......@@ -2416,7 +2416,7 @@ Handle<SourceTextModule> Factory::NewSourceTextModule(
module.set_dfs_ancestor_index(-1);
module.set_flags(0);
module.set_async(IsAsyncModule(sfi->kind()));
module.set_async_evaluating(false);
module.set_async_evaluating_ordinal(SourceTextModule::kNotAsyncEvaluated);
module.set_cycle_root(roots.the_hole_value(), SKIP_WRITE_BARRIER);
module.set_async_parent_modules(*async_parent_modules);
module.set_pending_async_dependencies(0);
......
......@@ -38,8 +38,8 @@ SMI_ACCESSORS(Module, status, kStatusOffset)
SMI_ACCESSORS(Module, hash, kHashOffset)
BOOL_ACCESSORS(SourceTextModule, flags, async, AsyncBit::kShift)
BOOL_ACCESSORS(SourceTextModule, flags, async_evaluating,
AsyncEvaluatingBit::kShift)
BIT_FIELD_ACCESSORS(SourceTextModule, flags, async_evaluating_ordinal,
SourceTextModule::AsyncEvaluatingOrdinalBits)
ACCESSORS(SourceTextModule, async_parent_modules, ArrayList,
kAsyncParentModulesOffset)
......@@ -139,6 +139,10 @@ int SourceTextModule::AsyncParentModuleCount() {
return async_parent_modules().Length();
}
bool SourceTextModule::IsAsyncEvaluating() const {
return async_evaluating_ordinal() >= kFirstAsyncEvaluatingOrdinal;
}
bool SourceTextModule::HasPendingAsyncDependencies() {
DCHECK_GE(pending_async_dependencies(), 0);
return pending_async_dependencies() > 0;
......
This diff is collapsed.
......@@ -7,6 +7,7 @@
#include "src/objects/module.h"
#include "src/objects/promise.h"
#include "src/zone/zone-containers.h"
#include "torque-generated/bit-fields.h"
// Has to be the last include (doesn't have include guards):
......@@ -73,10 +74,16 @@ class SourceTextModule
SubclassBodyDescriptor<Module::BodyDescriptor,
FixedBodyDescriptor<kCodeOffset, kSize, kSize>>;
static constexpr unsigned kFirstAsyncEvaluatingOrdinal = 2;
private:
friend class Factory;
friend class Module;
struct AsyncEvaluatingOrdinalCompare;
using AsyncParentCompletionSet =
ZoneSet<Handle<SourceTextModule>, AsyncEvaluatingOrdinalCompare>;
// Appends a tuple of module and generator to the async parent modules
// ArrayList.
inline static void AddAsyncParentModule(Isolate* isolate,
......@@ -94,6 +101,8 @@ class SourceTextModule
// Returns the number of async parent modules for a given async child.
inline int AsyncParentModuleCount();
inline bool IsAsyncEvaluating() const;
inline bool HasPendingAsyncDependencies();
inline void IncrementPendingAsyncDependencies();
inline void DecrementPendingAsyncDependencies();
......@@ -101,13 +110,26 @@ class SourceTextModule
// Bits for flags.
DEFINE_TORQUE_GENERATED_SOURCE_TEXT_MODULE_FLAGS()
// async_evaluating, top_level_capability, pending_async_dependencies, and
// async_parent_modules are used exclusively during evaluation of async
// async_evaluating_ordinal, top_level_capability, pending_async_dependencies,
// and async_parent_modules are used exclusively during evaluation of async
// modules and the modules which depend on them.
//
// Whether or not this module is async and evaluating or currently evaluating
// an async child.
DECL_BOOLEAN_ACCESSORS(async_evaluating)
// If >1, this module is async and evaluating or currently evaluating an async
// child. The integer is an ordinal for when this module first started async
// evaluation and is used for sorting async parent modules when determining
// which parent module can start executing after an async evaluation
// completes.
//
// If 1, this module has finished async evaluating.
//
// If 0, this module is not async or has not been async evaluated.
static constexpr unsigned kNotAsyncEvaluated = 0;
static constexpr unsigned kAsyncEvaluateDidFinish = 1;
STATIC_ASSERT(kNotAsyncEvaluated < kAsyncEvaluateDidFinish);
STATIC_ASSERT(kAsyncEvaluateDidFinish < kFirstAsyncEvaluatingOrdinal);
STATIC_ASSERT(kMaxModuleAsyncEvaluatingOrdinal ==
AsyncEvaluatingOrdinalBits::kMax);
DECL_PRIMITIVE_ACCESSORS(async_evaluating_ordinal, unsigned)
// The parent modules of a given async dependency, use async_parent_modules()
// to retrieve the ArrayList representation.
......@@ -151,6 +173,10 @@ class SourceTextModule
Handle<SourceTextModule> module, Zone* zone,
UnorderedModuleSet* visited);
static void GatherAsyncParentCompletions(Isolate* isolate, Zone* zone,
Handle<SourceTextModule> start,
AsyncParentCompletionSet* exec_list);
// Implementation of spec concrete method Evaluate.
static V8_WARN_UNUSED_RESULT MaybeHandle<Object> EvaluateMaybeAsync(
Isolate* isolate, Handle<SourceTextModule> module);
......
......@@ -6,7 +6,7 @@ type SourceTextModuleInfo extends FixedArray;
bitfield struct SourceTextModuleFlags extends uint31 {
async: bool: 1 bit;
async_evaluating: bool: 1 bit;
async_evaluating_ordinal: uint32: 30 bit;
}
@generateCppClass
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-top-level-await
import "modules-skip-async-subgraph-start.mjs"
assertEquals(globalThis.test_order, [
'async before', 'async after', '1', '2', 'x', 'start'
]);
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-top-level-await
import "modules-skip-async-subgraph-async.mjs"
if (globalThis.test_order === undefined) {
globalThis.test_order = [];
}
globalThis.test_order.push('1');
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-top-level-await
import "modules-skip-async-subgraph-async.mjs"
if (globalThis.test_order === undefined) {
globalThis.test_order = [];
}
globalThis.test_order.push('2');
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-top-level-await
if (globalThis.test_order === undefined) {
globalThis.test_order = [];
}
globalThis.test_order.push('async before');
await 0;
globalThis.test_order.push('async after');
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-top-level-await
import "modules-skip-async-subgraph-1.mjs"
import "modules-skip-async-subgraph-2.mjs"
import "modules-skip-async-subgraph-x.mjs"
if (globalThis.test_order === undefined) {
globalThis.test_order = [];
}
globalThis.test_order.push('start');
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-top-level-await
import "modules-skip-async-subgraph-1.mjs"
if (globalThis.test_order === undefined) {
globalThis.test_order = [];
}
globalThis.test_order.push('x');
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