Skip to content

Commit 219935a

Browse files
Clement Skaucommit-bot@chromium.org
Clement Skau
authored andcommitted
[VM] Adds Future.timeout stack unwinding support for --lazy-async-stacks.
Bug: #40815 Change-Id: I1603e1effe67b727d5dc47c0830b4758c764cf4a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/152328 Commit-Queue: Clement Skau <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent 05003ab commit 219935a

File tree

10 files changed

+259
-13
lines changed

10 files changed

+259
-13
lines changed

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

+101
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ Future<void> customErrorZone() async {
149149
return completer.future;
150150
}
151151

152+
// ----
153+
// Scenario: Future.timeout:
154+
// ----
155+
156+
Future awaitTimeout() async {
157+
await (throwAsync().timeout(Duration(seconds: 1)));
158+
}
159+
152160
// Helpers:
153161

154162
// We want lines that either start with a frame index or an async gap marker.
@@ -587,6 +595,7 @@ Future<void> doTestsCausal([String? debugInfoFilename]) async {
587595
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
588596
],
589597
debugInfoFilename);
598+
590599
final customErrorZoneExpected = const <String>[
591600
r'#0 throwSync \(.*/utils.dart:16(:3)?\)$',
592601
r'#1 allYield3 \(.*/utils.dart:39(:3)?\)$',
@@ -638,6 +647,48 @@ Future<void> doTestsCausal([String? debugInfoFilename]) async {
638647
r'#14 _RawReceivePortImpl._handleMessage ',
639648
],
640649
debugInfoFilename);
650+
651+
final awaitTimeoutExpected = const <String>[
652+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
653+
r'^^<asynchronous suspension>$',
654+
r'^#1 awaitTimeout ',
655+
];
656+
await doTestAwait(
657+
awaitTimeout,
658+
awaitTimeoutExpected +
659+
const <String>[
660+
r'^#2 doTestAwait ',
661+
r'^#3 doTestsCausal ',
662+
r'^<asynchronous suspension>$',
663+
r'^#4 main \(.+\)$',
664+
r'^#5 _startIsolate.<anonymous closure> ',
665+
r'^#6 _RawReceivePortImpl._handleMessage ',
666+
],
667+
debugInfoFilename);
668+
await doTestAwaitThen(
669+
awaitTimeout,
670+
awaitTimeoutExpected +
671+
const <String>[
672+
r'^#2 doTestAwaitThen ',
673+
r'^#3 doTestsCausal ',
674+
r'^<asynchronous suspension>$',
675+
r'^#4 main \(.+\)$',
676+
r'^#5 _startIsolate.<anonymous closure> ',
677+
r'^#6 _RawReceivePortImpl._handleMessage ',
678+
],
679+
debugInfoFilename);
680+
await doTestAwaitCatchError(
681+
awaitTimeout,
682+
awaitTimeoutExpected +
683+
const <String>[
684+
r'^#2 doTestAwaitCatchError ',
685+
r'^#3 doTestsCausal ',
686+
r'^<asynchronous suspension>$',
687+
r'^#4 main \(.+\)$',
688+
r'^#5 _startIsolate.<anonymous closure> ',
689+
r'^#6 _RawReceivePortImpl._handleMessage ',
690+
],
691+
debugInfoFilename);
641692
}
642693

643694
// For: --no-causal-async-stacks --no-lazy-async-stacks
@@ -957,6 +1008,25 @@ Future<void> doTestsNoCausalNoLazy([String? debugInfoFilename]) async {
9571008
customErrorZone, customErrorZoneExpected, debugInfoFilename);
9581009
await doTestAwaitCatchError(
9591010
customErrorZone, customErrorZoneExpected, debugInfoFilename);
1011+
1012+
final awaitTimeoutExpected = const <String>[
1013+
r'#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1014+
r'^#1 _RootZone.runUnary ',
1015+
r'^#2 _FutureListener.handleValue ',
1016+
r'^#3 Future._propagateToListeners.handleValueCallback ',
1017+
r'^#4 Future._propagateToListeners ',
1018+
r'^#5 Future.(_addListener|_prependListeners).<anonymous closure> ',
1019+
r'^#6 _microtaskLoop ',
1020+
r'^#7 _startMicrotaskLoop ',
1021+
r'^#8 _runPendingImmediateCallback ',
1022+
r'^#9 _RawReceivePortImpl._handleMessage ',
1023+
];
1024+
await doTestAwait(
1025+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1026+
await doTestAwaitThen(
1027+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1028+
await doTestAwaitCatchError(
1029+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
9601030
}
9611031

9621032
// For: --lazy-async-stacks
@@ -1218,4 +1288,35 @@ Future<void> doTestsLazy([String? debugInfoFilename]) async {
12181288
customErrorZone, customErrorZoneExpected, debugInfoFilename);
12191289
await doTestAwaitCatchError(
12201290
customErrorZone, customErrorZoneExpected, debugInfoFilename);
1291+
1292+
final awaitTimeoutExpected = const <String>[
1293+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1294+
r'^<asynchronous suspension>$',
1295+
r'^#1 Future.timeout.<anonymous closure> \(dart:async/future_impl.dart\)$',
1296+
r'^<asynchronous suspension>$',
1297+
r'^#2 awaitTimeout ',
1298+
r'^<asynchronous suspension>$',
1299+
];
1300+
await doTestAwait(
1301+
awaitTimeout,
1302+
awaitTimeoutExpected +
1303+
const <String>[
1304+
r'^#3 doTestAwait ',
1305+
r'^<asynchronous suspension>$',
1306+
r'^#4 doTestsLazy ',
1307+
r'^<asynchronous suspension>$',
1308+
r'^#5 main ',
1309+
r'^<asynchronous suspension>$',
1310+
],
1311+
debugInfoFilename);
1312+
await doTestAwaitThen(
1313+
awaitTimeout,
1314+
awaitTimeoutExpected +
1315+
const <String>[
1316+
r'^#3 doTestAwaitThen.<anonymous closure> ',
1317+
r'^<asynchronous suspension>$',
1318+
],
1319+
debugInfoFilename);
1320+
await doTestAwaitCatchError(
1321+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
12211322
}

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

+101
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ Future<void> customErrorZone() async {
149149
return completer.future;
150150
}
151151

152+
// ----
153+
// Scenario: Future.timeout:
154+
// ----
155+
156+
Future awaitTimeout() async {
157+
await (throwAsync().timeout(Duration(seconds: 1)));
158+
}
159+
152160
// Helpers:
153161

154162
// We want lines that either start with a frame index or an async gap marker.
@@ -587,6 +595,7 @@ Future<void> doTestsCausal([String debugInfoFilename]) async {
587595
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
588596
],
589597
debugInfoFilename);
598+
590599
final customErrorZoneExpected = const <String>[
591600
r'#0 throwSync \(.*/utils.dart:16(:3)?\)$',
592601
r'#1 allYield3 \(.*/utils.dart:39(:3)?\)$',
@@ -638,6 +647,48 @@ Future<void> doTestsCausal([String debugInfoFilename]) async {
638647
r'#14 _RawReceivePortImpl._handleMessage ',
639648
],
640649
debugInfoFilename);
650+
651+
final awaitTimeoutExpected = const <String>[
652+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
653+
r'^^<asynchronous suspension>$',
654+
r'^#1 awaitTimeout ',
655+
];
656+
await doTestAwait(
657+
awaitTimeout,
658+
awaitTimeoutExpected +
659+
const <String>[
660+
r'^#2 doTestAwait ',
661+
r'^#3 doTestsCausal ',
662+
r'^<asynchronous suspension>$',
663+
r'^#4 main \(.+\)$',
664+
r'^#5 _startIsolate.<anonymous closure> ',
665+
r'^#6 _RawReceivePortImpl._handleMessage ',
666+
],
667+
debugInfoFilename);
668+
await doTestAwaitThen(
669+
awaitTimeout,
670+
awaitTimeoutExpected +
671+
const <String>[
672+
r'^#2 doTestAwaitThen ',
673+
r'^#3 doTestsCausal ',
674+
r'^<asynchronous suspension>$',
675+
r'^#4 main \(.+\)$',
676+
r'^#5 _startIsolate.<anonymous closure> ',
677+
r'^#6 _RawReceivePortImpl._handleMessage ',
678+
],
679+
debugInfoFilename);
680+
await doTestAwaitCatchError(
681+
awaitTimeout,
682+
awaitTimeoutExpected +
683+
const <String>[
684+
r'^#2 doTestAwaitCatchError ',
685+
r'^#3 doTestsCausal ',
686+
r'^<asynchronous suspension>$',
687+
r'^#4 main \(.+\)$',
688+
r'^#5 _startIsolate.<anonymous closure> ',
689+
r'^#6 _RawReceivePortImpl._handleMessage ',
690+
],
691+
debugInfoFilename);
641692
}
642693

643694
// For: --no-causal-async-stacks --no-lazy-async-stacks
@@ -957,6 +1008,25 @@ Future<void> doTestsNoCausalNoLazy([String debugInfoFilename]) async {
9571008
customErrorZone, customErrorZoneExpected, debugInfoFilename);
9581009
await doTestAwaitCatchError(
9591010
customErrorZone, customErrorZoneExpected, debugInfoFilename);
1011+
1012+
final awaitTimeoutExpected = const <String>[
1013+
r'#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1014+
r'^#1 _RootZone.runUnary ',
1015+
r'^#2 _FutureListener.handleValue ',
1016+
r'^#3 Future._propagateToListeners.handleValueCallback ',
1017+
r'^#4 Future._propagateToListeners ',
1018+
r'^#5 Future.(_addListener|_prependListeners).<anonymous closure> ',
1019+
r'^#6 _microtaskLoop ',
1020+
r'^#7 _startMicrotaskLoop ',
1021+
r'^#8 _runPendingImmediateCallback ',
1022+
r'^#9 _RawReceivePortImpl._handleMessage ',
1023+
];
1024+
await doTestAwait(
1025+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1026+
await doTestAwaitThen(
1027+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
1028+
await doTestAwaitCatchError(
1029+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
9601030
}
9611031

9621032
// For: --lazy-async-stacks
@@ -1218,4 +1288,35 @@ Future<void> doTestsLazy([String debugInfoFilename]) async {
12181288
customErrorZone, customErrorZoneExpected, debugInfoFilename);
12191289
await doTestAwaitCatchError(
12201290
customErrorZone, customErrorZoneExpected, debugInfoFilename);
1291+
1292+
final awaitTimeoutExpected = const <String>[
1293+
r'^#0 throwAsync \(.*/utils.dart:21(:3)?\)$',
1294+
r'^<asynchronous suspension>$',
1295+
r'^#1 Future.timeout.<anonymous closure> \(dart:async/future_impl.dart\)$',
1296+
r'^<asynchronous suspension>$',
1297+
r'^#2 awaitTimeout ',
1298+
r'^<asynchronous suspension>$',
1299+
];
1300+
await doTestAwait(
1301+
awaitTimeout,
1302+
awaitTimeoutExpected +
1303+
const <String>[
1304+
r'^#3 doTestAwait ',
1305+
r'^<asynchronous suspension>$',
1306+
r'^#4 doTestsLazy ',
1307+
r'^<asynchronous suspension>$',
1308+
r'^#5 main ',
1309+
r'^<asynchronous suspension>$',
1310+
],
1311+
debugInfoFilename);
1312+
await doTestAwaitThen(
1313+
awaitTimeout,
1314+
awaitTimeoutExpected +
1315+
const <String>[
1316+
r'^#3 doTestAwaitThen.<anonymous closure> ',
1317+
r'^<asynchronous suspension>$',
1318+
],
1319+
debugInfoFilename);
1320+
await doTestAwaitCatchError(
1321+
awaitTimeout, awaitTimeoutExpected + const <String>[], debugInfoFilename);
12211322
}

runtime/vm/compiler/frontend/scope_builder.cc

+12-4
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,8 @@ void ScopeBuilder::VisitFunctionNode() {
540540
FunctionNodeHelper function_node_helper(&helper_);
541541
function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters);
542542

543+
const auto& function = parsed_function_->function();
544+
543545
intptr_t list_length =
544546
helper_.ReadListLength(); // read type_parameters list length.
545547
for (intptr_t i = 0; i < list_length; ++i) {
@@ -565,10 +567,8 @@ void ScopeBuilder::VisitFunctionNode() {
565567
}
566568

567569
if (function_node_helper.async_marker_ == FunctionNodeHelper::kSyncYielding) {
568-
intptr_t offset = parsed_function_->function().num_fixed_parameters();
569-
for (intptr_t i = 0;
570-
i < parsed_function_->function().NumOptionalPositionalParameters();
571-
i++) {
570+
intptr_t offset = function.num_fixed_parameters();
571+
for (intptr_t i = 0; i < function.NumOptionalPositionalParameters(); i++) {
572572
parsed_function_->ParameterVariable(offset + i)->set_is_forced_stack();
573573
}
574574
}
@@ -627,6 +627,14 @@ void ScopeBuilder::VisitFunctionNode() {
627627
}
628628
}
629629
}
630+
631+
// Mark known chained futures such as _Future::timeout()'s _future.
632+
if (function.recognized_kind() == MethodRecognizer::kFutureTimeout &&
633+
depth_.function_ == 1) {
634+
LocalVariable* future = scope_->LookupVariable(Symbols::_future(), true);
635+
ASSERT(future != nullptr);
636+
future->set_is_chained_future();
637+
}
630638
}
631639

632640
void ScopeBuilder::VisitInitializer() {

runtime/vm/compiler/recognized_methods_list.h

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ namespace dart {
177177
V(Pointer, get:address, FfiGetAddress, 0x29a505a1) \
178178
V(::, reachabilityFence, ReachabilityFence, 0x0) \
179179
V(_Utf8Decoder, _scan, Utf8DecoderScan, 0x78f44c3c) \
180+
V(_Future, timeout, FutureTimeout, 0) \
180181

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

runtime/vm/object.h

+1
Original file line numberDiff line numberDiff line change
@@ -6708,6 +6708,7 @@ class Context : public Object {
67086708
static const intptr_t kAwaitJumpVarIndex = 0;
67096709
static const intptr_t kAsyncCompleterIndex = 1;
67106710
static const intptr_t kControllerIndex = 1;
6711+
static const intptr_t kChainedFutureIndex = 2;
67116712

67126713
static intptr_t variable_offset(intptr_t context_index) {
67136714
return OFFSET_OF_RETURNED_VALUE(ContextLayout, data) +

runtime/vm/scopes.cc

+9-1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
212212
LocalVariable* await_jump_var = nullptr;
213213
LocalVariable* async_completer = nullptr;
214214
LocalVariable* controller = nullptr;
215+
LocalVariable* chained_future = nullptr;
215216
for (intptr_t i = 0; i < num_variables(); i++) {
216217
LocalVariable* variable = VariableAt(i);
217218
if (variable->owner() == this) {
@@ -222,6 +223,8 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
222223
async_completer = variable;
223224
} else if (variable->name().Equals(Symbols::Controller())) {
224225
controller = variable;
226+
} else if (variable->is_chained_future()) {
227+
chained_future = variable;
225228
}
226229
}
227230
}
@@ -243,6 +246,11 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
243246
*found_captured_variables = true;
244247
ASSERT(controller->index().value() == Context::kControllerIndex);
245248
}
249+
if (chained_future != nullptr) {
250+
AllocateContextVariable(chained_future, &context_owner);
251+
*found_captured_variables = true;
252+
ASSERT(chained_future->index().value() == Context::kChainedFutureIndex);
253+
}
246254

247255
while (pos < num_parameters) {
248256
LocalVariable* parameter = VariableAt(pos);
@@ -273,7 +281,7 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
273281
if (variable->is_captured()) {
274282
// Skip the two variables already pre-allocated above.
275283
if (variable != await_jump_var && variable != async_completer &&
276-
variable != controller) {
284+
variable != controller && variable != chained_future) {
277285
AllocateContextVariable(variable, &context_owner);
278286
*found_captured_variables = true;
279287
}

runtime/vm/scopes.h

+5
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class LocalVariable : public ZoneAllocated {
9393
is_forced_stack_(false),
9494
is_explicit_covariant_parameter_(false),
9595
is_late_(false),
96+
is_chained_future_(false),
9697
late_init_offset_(0),
9798
type_check_mode_(kDoTypeCheck),
9899
index_() {
@@ -131,6 +132,9 @@ class LocalVariable : public ZoneAllocated {
131132
bool is_late() const { return is_late_; }
132133
void set_is_late() { is_late_ = true; }
133134

135+
bool is_chained_future() const { return is_chained_future_; }
136+
void set_is_chained_future() { is_chained_future_ = true; }
137+
134138
intptr_t late_init_offset() const { return late_init_offset_; }
135139
void set_late_init_offset(intptr_t late_init_offset) {
136140
late_init_offset_ = late_init_offset;
@@ -220,6 +224,7 @@ class LocalVariable : public ZoneAllocated {
220224
bool is_forced_stack_;
221225
bool is_explicit_covariant_parameter_;
222226
bool is_late_;
227+
bool is_chained_future_;
223228
intptr_t late_init_offset_;
224229
TypeCheckMode type_check_mode_;
225230
VariableIndex index_;

0 commit comments

Comments
 (0)