[VM] Adds Future.wait support for --lazy-async-stacks.

- Makes Future.wait a recognised function, and asserts its chained
future, _future is allocated at a known index in the context.
- Adds logic to locate, extract the chained future during lazy async
stack unwinding.
- Adds tests for the Future.wait async case.
- Minor consistency nits, comments.

This change is similar to a previous CL, adding Future.timeout support:
https://dart-review.googlesource.com/c/sdk/+/152328

Change-Id: I7439750968595d25d7bbac0068ad64fcc891e176
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155420
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Clement Skau 2020-07-28 05:41:01 +00:00 committed by commit-bot@chromium.org
parent d878cfbf20
commit 6ca00d12c0
9 changed files with 256 additions and 22 deletions

View file

@ -157,6 +157,19 @@ Future awaitTimeout() async {
await (throwAsync().timeout(Duration(seconds: 1)));
}
// ----
// Scenario: Future.wait:
// ----
Future awaitWait() async {
await Future.wait([
throwAsync(),
() async {
await Future.value();
}()
]);
}
// Helpers:
// We want lines that either start with a frame index or an async gap marker.
@ -689,6 +702,48 @@ Future<void> doTestsCausal([String? debugInfoFilename]) async {
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
final awaitWaitExpected = const <String>[
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
r'^^<asynchronous suspension>$',
r'^#1 awaitWait ',
];
await doTestAwait(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#2 doTestAwait ',
r'^#3 doTestsCausal ',
r'^<asynchronous suspension>$',
r'^#4 main \(.+\)$',
r'^#5 _startIsolate.<anonymous closure> ',
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
await doTestAwaitThen(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#2 doTestAwaitThen ',
r'^#3 doTestsCausal ',
r'^<asynchronous suspension>$',
r'^#4 main \(.+\)$',
r'^#5 _startIsolate.<anonymous closure> ',
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
await doTestAwaitCatchError(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#2 doTestAwaitCatchError ',
r'^#3 doTestsCausal ',
r'^<asynchronous suspension>$',
r'^#4 main \(.+\)$',
r'^#5 _startIsolate.<anonymous closure> ',
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
}
// For: --no-causal-async-stacks --no-lazy-async-stacks
@ -1027,6 +1082,25 @@ Future<void> doTestsNoCausalNoLazy([String? debugInfoFilename]) async {
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
await doTestAwaitCatchError(
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
final awaitWaitExpected = const <String>[
r'#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
r'^#1 _RootZone.runUnary ',
r'^#2 _FutureListener.handleValue ',
r'^#3 Future._propagateToListeners.handleValueCallback ',
r'^#4 Future._propagateToListeners ',
r'^#5 Future.(_addListener|_prependListeners).<anonymous closure> ',
r'^#6 _microtaskLoop ',
r'^#7 _startMicrotaskLoop ',
r'^#8 _runPendingImmediateCallback ',
r'^#9 _RawReceivePortImpl._handleMessage ',
];
await doTestAwait(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
await doTestAwaitThen(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
await doTestAwaitCatchError(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
}
// For: --lazy-async-stacks
@ -1319,4 +1393,35 @@ Future<void> doTestsLazy([String? debugInfoFilename]) async {
debugInfoFilename);
await doTestAwaitCatchError(
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
final awaitWaitExpected = const <String>[
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
r'^<asynchronous suspension>$',
r'^#1 Future.wait.<anonymous closure> \(dart:async/future.dart\)$',
r'^<asynchronous suspension>$',
r'^#2 awaitWait ',
r'^<asynchronous suspension>$',
];
await doTestAwait(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#3 doTestAwait ',
r'^<asynchronous suspension>$',
r'^#4 doTestsLazy ',
r'^<asynchronous suspension>$',
r'^#5 main ',
r'^<asynchronous suspension>$',
],
debugInfoFilename);
await doTestAwaitThen(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#3 doTestAwaitThen.<anonymous closure> ',
r'^<asynchronous suspension>$',
],
debugInfoFilename);
await doTestAwaitCatchError(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
}

View file

@ -157,6 +157,19 @@ Future awaitTimeout() async {
await (throwAsync().timeout(Duration(seconds: 1)));
}
// ----
// Scenario: Future.wait:
// ----
Future awaitWait() async {
await Future.wait([
throwAsync(),
() async {
await Future.value();
}()
]);
}
// Helpers:
// We want lines that either start with a frame index or an async gap marker.
@ -689,6 +702,48 @@ Future<void> doTestsCausal([String debugInfoFilename]) async {
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
final awaitWaitExpected = const <String>[
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
r'^^<asynchronous suspension>$',
r'^#1 awaitWait ',
];
await doTestAwait(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#2 doTestAwait ',
r'^#3 doTestsCausal ',
r'^<asynchronous suspension>$',
r'^#4 main \(.+\)$',
r'^#5 _startIsolate.<anonymous closure> ',
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
await doTestAwaitThen(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#2 doTestAwaitThen ',
r'^#3 doTestsCausal ',
r'^<asynchronous suspension>$',
r'^#4 main \(.+\)$',
r'^#5 _startIsolate.<anonymous closure> ',
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
await doTestAwaitCatchError(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#2 doTestAwaitCatchError ',
r'^#3 doTestsCausal ',
r'^<asynchronous suspension>$',
r'^#4 main \(.+\)$',
r'^#5 _startIsolate.<anonymous closure> ',
r'^#6 _RawReceivePortImpl._handleMessage ',
],
debugInfoFilename);
}
// For: --no-causal-async-stacks --no-lazy-async-stacks
@ -1027,6 +1082,25 @@ Future<void> doTestsNoCausalNoLazy([String debugInfoFilename]) async {
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
await doTestAwaitCatchError(
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
final awaitWaitExpected = const <String>[
r'#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
r'^#1 _RootZone.runUnary ',
r'^#2 _FutureListener.handleValue ',
r'^#3 Future._propagateToListeners.handleValueCallback ',
r'^#4 Future._propagateToListeners ',
r'^#5 Future.(_addListener|_prependListeners).<anonymous closure> ',
r'^#6 _microtaskLoop ',
r'^#7 _startMicrotaskLoop ',
r'^#8 _runPendingImmediateCallback ',
r'^#9 _RawReceivePortImpl._handleMessage ',
];
await doTestAwait(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
await doTestAwaitThen(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
await doTestAwaitCatchError(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
}
// For: --lazy-async-stacks
@ -1319,4 +1393,35 @@ Future<void> doTestsLazy([String debugInfoFilename]) async {
debugInfoFilename);
await doTestAwaitCatchError(
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
final awaitWaitExpected = const <String>[
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
r'^<asynchronous suspension>$',
r'^#1 Future.wait.<anonymous closure> \(dart:async/future.dart\)$',
r'^<asynchronous suspension>$',
r'^#2 awaitWait ',
r'^<asynchronous suspension>$',
];
await doTestAwait(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#3 doTestAwait ',
r'^<asynchronous suspension>$',
r'^#4 doTestsLazy ',
r'^<asynchronous suspension>$',
r'^#5 main ',
r'^<asynchronous suspension>$',
],
debugInfoFilename);
await doTestAwaitThen(
awaitWait,
awaitWaitExpected +
const <String>[
r'^#3 doTestAwaitThen.<anonymous closure> ',
r'^<asynchronous suspension>$',
],
debugInfoFilename);
await doTestAwaitCatchError(
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
}

View file

@ -409,7 +409,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
}
case FunctionLayout::kNoSuchMethodDispatcher:
case FunctionLayout::kInvokeFieldDispatcher:
case FunctionLayout::kFfiTrampoline:
case FunctionLayout::kFfiTrampoline: {
for (intptr_t i = 0; i < function.NumParameters(); ++i) {
LocalVariable* variable = MakeVariable(
TokenPosition::kNoSource, TokenPosition::kNoSource,
@ -433,6 +433,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
--depth_.catch_;
}
break;
}
case FunctionLayout::kSignatureFunction:
case FunctionLayout::kIrregexpFunction:
UNREACHABLE();
@ -443,6 +444,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
if (parsed_function_->function().MayHaveUncheckedEntryPoint()) {
scope_->AddVariable(parsed_function_->EnsureEntryPointsTemp());
}
parsed_function_->AllocateVariables();
return result_;
@ -633,6 +635,13 @@ void ScopeBuilder::VisitFunctionNode() {
LocalVariable* future = scope_->LookupVariable(Symbols::_future(), true);
ASSERT(future != nullptr);
future->set_is_chained_future();
future->set_expected_context_index(Context::kFutureTimeoutFutureIndex);
} else if (function.recognized_kind() == MethodRecognizer::kFutureWait &&
depth_.function_ == 1) {
LocalVariable* future = scope_->LookupVariable(Symbols::_future(), true);
ASSERT(future != nullptr);
future->set_is_chained_future();
future->set_expected_context_index(Context::kFutureWaitFutureIndex);
}
}
@ -1313,7 +1322,8 @@ void ScopeBuilder::VisitVariableDeclaration() {
variable->set_is_late();
variable->set_late_init_offset(initializer_offset);
}
// Lift the two special async vars out of the function body scope, into the
// Lift the special async vars out of the function body scope, into the
// outer function declaration scope.
// This way we can allocate them in the outermost context at fixed indices,
// allowing support for --lazy-async-stacks implementation to find awaiters.

View file

@ -178,6 +178,7 @@ namespace dart {
V(::, reachabilityFence, ReachabilityFence, 0xad39d0a6) \
V(_Utf8Decoder, _scan, Utf8DecoderScan, 0x78f44c3c) \
V(_Future, timeout, FutureTimeout, 0x010f8ad4) \
V(Future, wait, FutureWait, 0x486414a9) \
// List of intrinsics:
// (class-name, function-name, intrinsification method, fingerprint).

View file

@ -6805,7 +6805,10 @@ class Context : public Object {
static const intptr_t kAwaitJumpVarIndex = 0;
static const intptr_t kAsyncCompleterIndex = 1;
static const intptr_t kControllerIndex = 1;
static const intptr_t kChainedFutureIndex = 2;
// Expected context index of chained futures in recognized async functions.
// These are used to unwind async stacks.
static const intptr_t kFutureTimeoutFutureIndex = 2;
static const intptr_t kFutureWaitFutureIndex = 2;
static intptr_t variable_offset(intptr_t context_index) {
return OFFSET_OF_RETURNED_VALUE(ContextLayout, data) +

View file

@ -249,7 +249,8 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
if (chained_future != nullptr) {
AllocateContextVariable(chained_future, &context_owner);
*found_captured_variables = true;
ASSERT(chained_future->index().value() == Context::kChainedFutureIndex);
ASSERT(chained_future->index().value() ==
chained_future->expected_context_index());
}
while (pos < num_parameters) {
@ -279,7 +280,7 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
LocalVariable* variable = VariableAt(pos);
if (variable->owner() == this) {
if (variable->is_captured()) {
// Skip the two variables already pre-allocated above.
// Skip the variables already pre-allocated above.
if (variable != await_jump_var && variable != async_completer &&
variable != controller && variable != chained_future) {
AllocateContextVariable(variable, &context_owner);

View file

@ -94,6 +94,7 @@ class LocalVariable : public ZoneAllocated {
is_explicit_covariant_parameter_(false),
is_late_(false),
is_chained_future_(false),
expected_context_index_(-1),
late_init_offset_(0),
type_check_mode_(kDoTypeCheck),
index_() {
@ -135,6 +136,11 @@ class LocalVariable : public ZoneAllocated {
bool is_chained_future() const { return is_chained_future_; }
void set_is_chained_future() { is_chained_future_ = true; }
intptr_t expected_context_index() const { return expected_context_index_; }
void set_expected_context_index(int index) {
expected_context_index_ = index;
}
intptr_t late_init_offset() const { return late_init_offset_; }
void set_late_init_offset(intptr_t late_init_offset) {
late_init_offset_ = late_init_offset;
@ -225,6 +231,7 @@ class LocalVariable : public ZoneAllocated {
bool is_explicit_covariant_parameter_;
bool is_late_;
bool is_chained_future_;
intptr_t expected_context_index_;
intptr_t late_init_offset_;
TypeCheckMode type_check_mode_;
VariableIndex index_;

View file

@ -131,8 +131,7 @@ class CallerClosureFinder {
var_data_field(Field::Handle(zone)),
state_field(Field::Handle(zone)),
on_data_field(Field::Handle(zone)),
state_data_field(Field::Handle(zone)),
future_timeout_method_(Function::Handle(zone)) {
state_data_field(Field::Handle(zone)) {
const auto& async_lib = Library::Handle(zone, Library::AsyncLibrary());
// Look up classes:
// - async:
@ -197,11 +196,6 @@ class CallerClosureFinder {
state_data_field =
stream_iterator_class.LookupFieldAllowPrivate(Symbols::_stateData());
ASSERT(!state_data_field.IsNull());
// Functions:
future_timeout_method_ =
future_impl_class.LookupFunction(Symbols::timeout());
ASSERT(!future_timeout_method_.IsNull());
}
ClosurePtr GetCallerInFutureImpl(const Object& future_) {
@ -296,7 +290,14 @@ class CallerClosureFinder {
parent_function_ = receiver_function_.parent_function();
if (parent_function_.recognized_kind() ==
MethodRecognizer::kFutureTimeout) {
context_entry_ = receiver_context_.At(Context::kChainedFutureIndex);
context_entry_ =
receiver_context_.At(Context::kFutureTimeoutFutureIndex);
return GetCallerInFutureImpl(context_entry_);
} else if (parent_function_.recognized_kind() ==
MethodRecognizer::kFutureWait) {
receiver_context_ = receiver_context_.parent();
ASSERT(!receiver_context_.IsNull());
context_entry_ = receiver_context_.At(Context::kFutureWaitFutureIndex);
return GetCallerInFutureImpl(context_entry_);
}
}
@ -363,8 +364,6 @@ class CallerClosureFinder {
Field& state_field;
Field& on_data_field;
Field& state_data_field;
Function& future_timeout_method_;
};
void StackTraceUtils::CollectFramesLazy(

View file

@ -360,9 +360,12 @@ abstract class Future<T> {
* The call to [cleanUp] should not throw. If it does, the error will be an
* uncaught asynchronous error.
*/
@pragma("vm:entry-point")
static Future<List<T>> wait<T>(Iterable<Future<T>> futures,
{bool eagerError = false, void cleanUp(T successValue)?}) {
final _Future<List<T>> result = new _Future<List<T>>();
// This is a VM recognised method, and the _future variable is deliberately
// allocated in a specific slot in the closure context for stack unwinding.
final _Future<List<T>> _future = _Future<List<T>>();
List<T?>? values; // Collects the values. Set to null on error.
int remaining = 0; // How many futures are we waiting for.
late Object error; // The first error from a future.
@ -386,13 +389,13 @@ abstract class Future<T> {
}
values = null;
if (remaining == 0 || eagerError) {
result._completeError(theError, theStackTrace);
_future._completeError(theError, theStackTrace);
} else {
error = theError;
stackTrace = theStackTrace;
}
} else if (remaining == 0 && !eagerError) {
result._completeError(error, stackTrace);
_future._completeError(error, stackTrace);
}
}
@ -407,7 +410,7 @@ abstract class Future<T> {
if (valueList != null) {
valueList[pos] = value;
if (remaining == 0) {
result._completeWithValue(List<T>.from(valueList));
_future._completeWithValue(List<T>.from(valueList));
}
} else {
if (cleanUp != null && value != null) {
@ -419,7 +422,7 @@ abstract class Future<T> {
if (remaining == 0 && !eagerError) {
// If eagerError is false, and valueList is null, then
// error and stackTrace have been set in handleError above.
result._completeError(error, stackTrace);
_future._completeError(error, stackTrace);
}
}
}, onError: handleError);
@ -439,7 +442,7 @@ abstract class Future<T> {
// gracefully.
if (remaining == 0 || eagerError) {
// Throw a new Future.error.
// Don't just call `result._completeError` since that would propagate
// Don't just call `_future._completeError` since that would propagate
// the error too eagerly, not giving the callers time to install
// error handlers.
// Also, don't use `_asyncCompleteError` since that one doesn't give
@ -453,7 +456,7 @@ abstract class Future<T> {
stackTrace = st;
}
}
return result;
return _future;
}
/**