Skip to content

Commit 6ca00d1

Browse files
Clement Skaucommit-bot@chromium.org
authored andcommitted
[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 <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent d878cfb commit 6ca00d1

File tree

9 files changed

+256
-22
lines changed

9 files changed

+256
-22
lines changed

runtime/tests/vm/dart/causal_stacks/utils.dart

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,19 @@ Future awaitTimeout() async {
157157
await (throwAsync().timeout(Duration(seconds: 1)));
158158
}
159159

160+
// ----
161+
// Scenario: Future.wait:
162+
// ----
163+
164+
Future awaitWait() async {
165+
await Future.wait([
166+
throwAsync(),
167+
() async {
168+
await Future.value();
169+
}()
170+
]);
171+
}
172+
160173
// Helpers:
161174

162175
// 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 {
689702
r'^#6 _RawReceivePortImpl._handleMessage ',
690703
],
691704
debugInfoFilename);
705+
706+
final awaitWaitExpected = const <String>[
707+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
708+
r'^^<asynchronous suspension>$',
709+
r'^#1 awaitWait ',
710+
];
711+
await doTestAwait(
712+
awaitWait,
713+
awaitWaitExpected +
714+
const <String>[
715+
r'^#2 doTestAwait ',
716+
r'^#3 doTestsCausal ',
717+
r'^<asynchronous suspension>$',
718+
r'^#4 main \(.+\)$',
719+
r'^#5 _startIsolate.<anonymous closure> ',
720+
r'^#6 _RawReceivePortImpl._handleMessage ',
721+
],
722+
debugInfoFilename);
723+
await doTestAwaitThen(
724+
awaitWait,
725+
awaitWaitExpected +
726+
const <String>[
727+
r'^#2 doTestAwaitThen ',
728+
r'^#3 doTestsCausal ',
729+
r'^<asynchronous suspension>$',
730+
r'^#4 main \(.+\)$',
731+
r'^#5 _startIsolate.<anonymous closure> ',
732+
r'^#6 _RawReceivePortImpl._handleMessage ',
733+
],
734+
debugInfoFilename);
735+
await doTestAwaitCatchError(
736+
awaitWait,
737+
awaitWaitExpected +
738+
const <String>[
739+
r'^#2 doTestAwaitCatchError ',
740+
r'^#3 doTestsCausal ',
741+
r'^<asynchronous suspension>$',
742+
r'^#4 main \(.+\)$',
743+
r'^#5 _startIsolate.<anonymous closure> ',
744+
r'^#6 _RawReceivePortImpl._handleMessage ',
745+
],
746+
debugInfoFilename);
692747
}
693748

694749
// For: --no-causal-async-stacks --no-lazy-async-stacks
@@ -1027,6 +1082,25 @@ Future<void> doTestsNoCausalNoLazy([String? debugInfoFilename]) async {
10271082
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
10281083
await doTestAwaitCatchError(
10291084
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1085+
1086+
final awaitWaitExpected = const <String>[
1087+
r'#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1088+
r'^#1 _RootZone.runUnary ',
1089+
r'^#2 _FutureListener.handleValue ',
1090+
r'^#3 Future._propagateToListeners.handleValueCallback ',
1091+
r'^#4 Future._propagateToListeners ',
1092+
r'^#5 Future.(_addListener|_prependListeners).<anonymous closure> ',
1093+
r'^#6 _microtaskLoop ',
1094+
r'^#7 _startMicrotaskLoop ',
1095+
r'^#8 _runPendingImmediateCallback ',
1096+
r'^#9 _RawReceivePortImpl._handleMessage ',
1097+
];
1098+
await doTestAwait(
1099+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
1100+
await doTestAwaitThen(
1101+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
1102+
await doTestAwaitCatchError(
1103+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
10301104
}
10311105

10321106
// For: --lazy-async-stacks
@@ -1319,4 +1393,35 @@ Future<void> doTestsLazy([String? debugInfoFilename]) async {
13191393
debugInfoFilename);
13201394
await doTestAwaitCatchError(
13211395
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1396+
1397+
final awaitWaitExpected = const <String>[
1398+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1399+
r'^<asynchronous suspension>$',
1400+
r'^#1 Future.wait.<anonymous closure> \(dart:async/future.dart\)$',
1401+
r'^<asynchronous suspension>$',
1402+
r'^#2 awaitWait ',
1403+
r'^<asynchronous suspension>$',
1404+
];
1405+
await doTestAwait(
1406+
awaitWait,
1407+
awaitWaitExpected +
1408+
const <String>[
1409+
r'^#3 doTestAwait ',
1410+
r'^<asynchronous suspension>$',
1411+
r'^#4 doTestsLazy ',
1412+
r'^<asynchronous suspension>$',
1413+
r'^#5 main ',
1414+
r'^<asynchronous suspension>$',
1415+
],
1416+
debugInfoFilename);
1417+
await doTestAwaitThen(
1418+
awaitWait,
1419+
awaitWaitExpected +
1420+
const <String>[
1421+
r'^#3 doTestAwaitThen.<anonymous closure> ',
1422+
r'^<asynchronous suspension>$',
1423+
],
1424+
debugInfoFilename);
1425+
await doTestAwaitCatchError(
1426+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
13221427
}

runtime/tests/vm/dart_2/causal_stacks/utils.dart

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,19 @@ Future awaitTimeout() async {
157157
await (throwAsync().timeout(Duration(seconds: 1)));
158158
}
159159

160+
// ----
161+
// Scenario: Future.wait:
162+
// ----
163+
164+
Future awaitWait() async {
165+
await Future.wait([
166+
throwAsync(),
167+
() async {
168+
await Future.value();
169+
}()
170+
]);
171+
}
172+
160173
// Helpers:
161174

162175
// 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 {
689702
r'^#6 _RawReceivePortImpl._handleMessage ',
690703
],
691704
debugInfoFilename);
705+
706+
final awaitWaitExpected = const <String>[
707+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
708+
r'^^<asynchronous suspension>$',
709+
r'^#1 awaitWait ',
710+
];
711+
await doTestAwait(
712+
awaitWait,
713+
awaitWaitExpected +
714+
const <String>[
715+
r'^#2 doTestAwait ',
716+
r'^#3 doTestsCausal ',
717+
r'^<asynchronous suspension>$',
718+
r'^#4 main \(.+\)$',
719+
r'^#5 _startIsolate.<anonymous closure> ',
720+
r'^#6 _RawReceivePortImpl._handleMessage ',
721+
],
722+
debugInfoFilename);
723+
await doTestAwaitThen(
724+
awaitWait,
725+
awaitWaitExpected +
726+
const <String>[
727+
r'^#2 doTestAwaitThen ',
728+
r'^#3 doTestsCausal ',
729+
r'^<asynchronous suspension>$',
730+
r'^#4 main \(.+\)$',
731+
r'^#5 _startIsolate.<anonymous closure> ',
732+
r'^#6 _RawReceivePortImpl._handleMessage ',
733+
],
734+
debugInfoFilename);
735+
await doTestAwaitCatchError(
736+
awaitWait,
737+
awaitWaitExpected +
738+
const <String>[
739+
r'^#2 doTestAwaitCatchError ',
740+
r'^#3 doTestsCausal ',
741+
r'^<asynchronous suspension>$',
742+
r'^#4 main \(.+\)$',
743+
r'^#5 _startIsolate.<anonymous closure> ',
744+
r'^#6 _RawReceivePortImpl._handleMessage ',
745+
],
746+
debugInfoFilename);
692747
}
693748

694749
// For: --no-causal-async-stacks --no-lazy-async-stacks
@@ -1027,6 +1082,25 @@ Future<void> doTestsNoCausalNoLazy([String debugInfoFilename]) async {
10271082
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
10281083
await doTestAwaitCatchError(
10291084
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1085+
1086+
final awaitWaitExpected = const <String>[
1087+
r'#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1088+
r'^#1 _RootZone.runUnary ',
1089+
r'^#2 _FutureListener.handleValue ',
1090+
r'^#3 Future._propagateToListeners.handleValueCallback ',
1091+
r'^#4 Future._propagateToListeners ',
1092+
r'^#5 Future.(_addListener|_prependListeners).<anonymous closure> ',
1093+
r'^#6 _microtaskLoop ',
1094+
r'^#7 _startMicrotaskLoop ',
1095+
r'^#8 _runPendingImmediateCallback ',
1096+
r'^#9 _RawReceivePortImpl._handleMessage ',
1097+
];
1098+
await doTestAwait(
1099+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
1100+
await doTestAwaitThen(
1101+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
1102+
await doTestAwaitCatchError(
1103+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
10301104
}
10311105

10321106
// For: --lazy-async-stacks
@@ -1319,4 +1393,35 @@ Future<void> doTestsLazy([String debugInfoFilename]) async {
13191393
debugInfoFilename);
13201394
await doTestAwaitCatchError(
13211395
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1396+
1397+
final awaitWaitExpected = const <String>[
1398+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1399+
r'^<asynchronous suspension>$',
1400+
r'^#1 Future.wait.<anonymous closure> \(dart:async/future.dart\)$',
1401+
r'^<asynchronous suspension>$',
1402+
r'^#2 awaitWait ',
1403+
r'^<asynchronous suspension>$',
1404+
];
1405+
await doTestAwait(
1406+
awaitWait,
1407+
awaitWaitExpected +
1408+
const <String>[
1409+
r'^#3 doTestAwait ',
1410+
r'^<asynchronous suspension>$',
1411+
r'^#4 doTestsLazy ',
1412+
r'^<asynchronous suspension>$',
1413+
r'^#5 main ',
1414+
r'^<asynchronous suspension>$',
1415+
],
1416+
debugInfoFilename);
1417+
await doTestAwaitThen(
1418+
awaitWait,
1419+
awaitWaitExpected +
1420+
const <String>[
1421+
r'^#3 doTestAwaitThen.<anonymous closure> ',
1422+
r'^<asynchronous suspension>$',
1423+
],
1424+
debugInfoFilename);
1425+
await doTestAwaitCatchError(
1426+
awaitWait, awaitWaitExpected + const <String>[], debugInfoFilename);
13221427
}

runtime/vm/compiler/frontend/scope_builder.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
409409
}
410410
case FunctionLayout::kNoSuchMethodDispatcher:
411411
case FunctionLayout::kInvokeFieldDispatcher:
412-
case FunctionLayout::kFfiTrampoline:
412+
case FunctionLayout::kFfiTrampoline: {
413413
for (intptr_t i = 0; i < function.NumParameters(); ++i) {
414414
LocalVariable* variable = MakeVariable(
415415
TokenPosition::kNoSource, TokenPosition::kNoSource,
@@ -433,6 +433,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
433433
--depth_.catch_;
434434
}
435435
break;
436+
}
436437
case FunctionLayout::kSignatureFunction:
437438
case FunctionLayout::kIrregexpFunction:
438439
UNREACHABLE();
@@ -443,6 +444,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
443444
if (parsed_function_->function().MayHaveUncheckedEntryPoint()) {
444445
scope_->AddVariable(parsed_function_->EnsureEntryPointsTemp());
445446
}
447+
446448
parsed_function_->AllocateVariables();
447449

448450
return result_;
@@ -633,6 +635,13 @@ void ScopeBuilder::VisitFunctionNode() {
633635
LocalVariable* future = scope_->LookupVariable(Symbols::_future(), true);
634636
ASSERT(future != nullptr);
635637
future->set_is_chained_future();
638+
future->set_expected_context_index(Context::kFutureTimeoutFutureIndex);
639+
} else if (function.recognized_kind() == MethodRecognizer::kFutureWait &&
640+
depth_.function_ == 1) {
641+
LocalVariable* future = scope_->LookupVariable(Symbols::_future(), true);
642+
ASSERT(future != nullptr);
643+
future->set_is_chained_future();
644+
future->set_expected_context_index(Context::kFutureWaitFutureIndex);
636645
}
637646
}
638647

@@ -1313,7 +1322,8 @@ void ScopeBuilder::VisitVariableDeclaration() {
13131322
variable->set_is_late();
13141323
variable->set_late_init_offset(initializer_offset);
13151324
}
1316-
// Lift the two special async vars out of the function body scope, into the
1325+
1326+
// Lift the special async vars out of the function body scope, into the
13171327
// outer function declaration scope.
13181328
// This way we can allocate them in the outermost context at fixed indices,
13191329
// allowing support for --lazy-async-stacks implementation to find awaiters.

runtime/vm/compiler/recognized_methods_list.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ namespace dart {
178178
V(::, reachabilityFence, ReachabilityFence, 0xad39d0a6) \
179179
V(_Utf8Decoder, _scan, Utf8DecoderScan, 0x78f44c3c) \
180180
V(_Future, timeout, FutureTimeout, 0x010f8ad4) \
181+
V(Future, wait, FutureWait, 0x486414a9) \
181182

182183
// List of intrinsics:
183184
// (class-name, function-name, intrinsification method, fingerprint).

runtime/vm/object.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6805,7 +6805,10 @@ class Context : public Object {
68056805
static const intptr_t kAwaitJumpVarIndex = 0;
68066806
static const intptr_t kAsyncCompleterIndex = 1;
68076807
static const intptr_t kControllerIndex = 1;
6808-
static const intptr_t kChainedFutureIndex = 2;
6808+
// Expected context index of chained futures in recognized async functions.
6809+
// These are used to unwind async stacks.
6810+
static const intptr_t kFutureTimeoutFutureIndex = 2;
6811+
static const intptr_t kFutureWaitFutureIndex = 2;
68096812

68106813
static intptr_t variable_offset(intptr_t context_index) {
68116814
return OFFSET_OF_RETURNED_VALUE(ContextLayout, data) +

runtime/vm/scopes.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
249249
if (chained_future != nullptr) {
250250
AllocateContextVariable(chained_future, &context_owner);
251251
*found_captured_variables = true;
252-
ASSERT(chained_future->index().value() == Context::kChainedFutureIndex);
252+
ASSERT(chained_future->index().value() ==
253+
chained_future->expected_context_index());
253254
}
254255

255256
while (pos < num_parameters) {
@@ -279,7 +280,7 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
279280
LocalVariable* variable = VariableAt(pos);
280281
if (variable->owner() == this) {
281282
if (variable->is_captured()) {
282-
// Skip the two variables already pre-allocated above.
283+
// Skip the variables already pre-allocated above.
283284
if (variable != await_jump_var && variable != async_completer &&
284285
variable != controller && variable != chained_future) {
285286
AllocateContextVariable(variable, &context_owner);

runtime/vm/scopes.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class LocalVariable : public ZoneAllocated {
9494
is_explicit_covariant_parameter_(false),
9595
is_late_(false),
9696
is_chained_future_(false),
97+
expected_context_index_(-1),
9798
late_init_offset_(0),
9899
type_check_mode_(kDoTypeCheck),
99100
index_() {
@@ -135,6 +136,11 @@ class LocalVariable : public ZoneAllocated {
135136
bool is_chained_future() const { return is_chained_future_; }
136137
void set_is_chained_future() { is_chained_future_ = true; }
137138

139+
intptr_t expected_context_index() const { return expected_context_index_; }
140+
void set_expected_context_index(int index) {
141+
expected_context_index_ = index;
142+
}
143+
138144
intptr_t late_init_offset() const { return late_init_offset_; }
139145
void set_late_init_offset(intptr_t late_init_offset) {
140146
late_init_offset_ = late_init_offset;
@@ -225,6 +231,7 @@ class LocalVariable : public ZoneAllocated {
225231
bool is_explicit_covariant_parameter_;
226232
bool is_late_;
227233
bool is_chained_future_;
234+
intptr_t expected_context_index_;
228235
intptr_t late_init_offset_;
229236
TypeCheckMode type_check_mode_;
230237
VariableIndex index_;

0 commit comments

Comments
 (0)