Skip to content

Commit

Permalink
[SDK] Adds --lazy-async-stack support for async*.
Browse files Browse the repository at this point in the history
Bug: #39525
Change-Id: I53cd334243649901ea8e0f9799d9f41c126e3627
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/126729
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
  • Loading branch information
Clement Skau authored and commit-bot@chromium.org committed Dec 4, 2019
1 parent 42a4442 commit f9327d3
Show file tree
Hide file tree
Showing 14 changed files with 359 additions and 24 deletions.
4 changes: 3 additions & 1 deletion pkg/kernel/lib/transformations/continuation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ContinuationVariables {
static const asyncCompleter = ':async_completer';
static const awaitContextVar = ':await_ctx_var';
static const asyncStackTraceVar = ':async_stack_trace';
static const controller = ':controller';
static const controllerStreamVar = ':controller_stream';
static const exceptionParam = ':exception';
static const stackTraceParam = ':stack_trace';
Expand Down Expand Up @@ -965,7 +966,8 @@ class AsyncStarFunctionRewriter extends AsyncRewriterBase {
final elementType = elementTypeFromReturnType(helper.streamClass);

// _AsyncStarStreamController<T> :controller;
controllerVariable = new VariableDeclaration(":controller",
controllerVariable = new VariableDeclaration(
ContinuationVariables.controller,
type: new InterfaceType(helper.asyncStarStreamControllerClass,
staticTypeContext.nullable, [elementType]));
statements.add(controllerVariable);
Expand Down
26 changes: 20 additions & 6 deletions pkg/vm/lib/bytecode/local_vars.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import '../metadata/direct_call.dart' show DirectCallMetadata;
// Keep in sync with runtime/vm/object.h:Context::kAwaitJumpVarIndex.
const int awaitJumpVarContextIndex = 0;
const int asyncCompleterContextIndex = 1;
const int controllerContextIndex = 1;

class LocalVariables {
final _scopes = new Map<TreeNode, Scope>();
Expand Down Expand Up @@ -443,12 +444,17 @@ class _ScopeBuilder extends RecursiveVisitor<Null> {
._getVarDesc(_currentFrame
.getSyntheticVar(ContinuationVariables.awaitJumpVar))
.moveToScope(_currentScope);
}
if (_currentFrame.dartAsyncMarker == AsyncMarker.Async) {
locals
._getVarDesc(_currentFrame
.getSyntheticVar(ContinuationVariables.asyncCompleter))
.moveToScope(_currentScope);
if (_currentFrame.dartAsyncMarker == AsyncMarker.Async) {
locals
._getVarDesc(_currentFrame
.getSyntheticVar(ContinuationVariables.asyncCompleter))
.moveToScope(_currentScope);
} else if (_currentFrame.dartAsyncMarker == AsyncMarker.AsyncStar) {
locals
._getVarDesc(_currentFrame
.getSyntheticVar(ContinuationVariables.controller))
.moveToScope(_currentScope);
}
}
}

Expand Down Expand Up @@ -1110,6 +1116,12 @@ class _Allocator extends RecursiveVisitor<Null> {
assert(locals._getVarDesc(asyncCompleter).index ==
asyncCompleterContextIndex);
}
if (_currentFrame.dartAsyncMarker == AsyncMarker.AsyncStar) {
final controller =
_currentFrame.getSyntheticVar(ContinuationVariables.controller);
_allocateVariable(controller);
assert(locals._getVarDesc(controller).index == controllerContextIndex);
}
_allocateParameters(node, function);
_allocateSpecialVariables();

Expand Down Expand Up @@ -1172,6 +1184,8 @@ class _Allocator extends RecursiveVisitor<Null> {
assert(locals._getVarDesc(node).index == awaitJumpVarContextIndex);
} else if (node.name == ContinuationVariables.asyncCompleter) {
assert(locals._getVarDesc(node).index == asyncCompleterContextIndex);
} else if (node.name == ContinuationVariables.controller) {
assert(locals._getVarDesc(node).index == controllerContextIndex);
} else {
_allocateVariable(node);
}
Expand Down
187 changes: 187 additions & 0 deletions runtime/tests/vm/dart/causal_stacks/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,37 @@ Future nonAsyncNoStack1() async => await nonAsyncNoStack2();

Future nonAsyncNoStack2() async => Future.value(0).then((_) => throwAsync());

// ----
// Scenario: async*:
// ----

Future awaitEveryAsyncStarThrowSync() async {
await for (Future v in asyncStarThrowSync()) {
await v;
}
}

Stream<Future> asyncStarThrowSync() async* {
for (int i = 0; i < 2; i++) {
await i;
yield throwSync();
}
}

Future awaitEveryAsyncStarThrowAsync() async {
await for (Future v in asyncStarThrowAsync()) {
await v;
}
}

Stream<Future> asyncStarThrowAsync() async* {
for (int i = 0; i < 2; i++) {
await i;
yield Future.value(i);
await throwAsync();
}
}

// Helpers:

void assertStack(List<String> expects, StackTrace stackTrace) {
Expand Down Expand Up @@ -354,6 +385,93 @@ Future<void> doTestsCausal() async {
await doTestAwait(nonAsyncNoStack, nonAsyncNoStackExpected);
await doTestAwaitThen(nonAsyncNoStack, nonAsyncNoStackExpected);
await doTestAwaitCatchError(nonAsyncNoStack, nonAsyncNoStackExpected);

final asyncStarThrowSyncExpected = const <String>[
r'^#0 throwSync \(.*/utils.dart:(16|16:3)\)$',
r'^#1 asyncStarThrowSync \(.*/utils.dart:(112|112:11)\)$',
r'^<asynchronous suspension>$',
r'^#2 awaitEveryAsyncStarThrowSync \(.+\)$',
];
await doTestAwait(
awaitEveryAsyncStarThrowSync,
asyncStarThrowSyncExpected +
const <String>[
r'^#3 doTestAwait \(.+\)$',
r'^#4 doTestsCausal \(.+\)$',
r'^<asynchronous suspension>$',
r'^#5 main \(.+\)$',
r'^#6 _startIsolate.<anonymous closure> \(.+\)$',
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
r'^$',
]);
await doTestAwaitThen(
awaitEveryAsyncStarThrowSync,
asyncStarThrowSyncExpected +
const <String>[
r'^#3 doTestAwaitThen \(.+\)$',
r'^#4 doTestsCausal \(.+\)$',
r'^<asynchronous suspension>$',
r'^#5 main \(.+\)$',
r'^#6 _startIsolate.<anonymous closure> \(.+\)$',
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
r'^$',
]);
await doTestAwaitCatchError(
awaitEveryAsyncStarThrowSync,
asyncStarThrowSyncExpected +
const <String>[
r'^#3 doTestAwaitCatchError \(.+\)$',
r'^#4 doTestsCausal \(.+\)$',
r'^<asynchronous suspension>$',
r'^#5 main \(.+\)$',
r'^#6 _startIsolate.<anonymous closure> \(.+\)$',
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
r'^$',
]);

final asyncStarThrowAsyncExpected = const <String>[
r'^#0 throwAsync \(.*/utils.dart:(21|21:3)\)$',
r'^<asynchronous suspension>$',
r'^#1 asyncStarThrowAsync \(.*/utils.dart:(126|126:11)\)$',
r'^<asynchronous suspension>$',
r'^#2 awaitEveryAsyncStarThrowAsync \(.+\)$',
];
await doTestAwait(
awaitEveryAsyncStarThrowAsync,
asyncStarThrowAsyncExpected +
const <String>[
r'^#3 doTestAwait \(.+\)$',
r'^#4 doTestsCausal \(.+\)$',
r'^<asynchronous suspension>$',
r'^#5 main \(.+\)$',
r'^#6 _startIsolate.<anonymous closure> \(.+\)$',
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
r'^$',
]);
await doTestAwaitThen(
awaitEveryAsyncStarThrowAsync,
asyncStarThrowAsyncExpected +
const <String>[
r'^#3 doTestAwaitThen \(.+\)$',
r'^#4 doTestsCausal \(.+\)$',
r'^<asynchronous suspension>$',
r'^#5 main \(.+\)$',
r'^#6 _startIsolate.<anonymous closure> \(.+\)$',
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
r'^$',
]);
await doTestAwaitCatchError(
awaitEveryAsyncStarThrowAsync,
asyncStarThrowAsyncExpected +
const <String>[
r'^#3 doTestAwaitCatchError \(.+\)$',
r'^#4 doTestsCausal \(.+\)$',
r'^<asynchronous suspension>$',
r'^#5 main \(.+\)$',
r'^#6 _startIsolate.<anonymous closure> \(.+\)$',
r'^#7 _RawReceivePortImpl._handleMessage \(.+\)$',
r'^$',
]);
}

// For: --no-causal-async-stacks
Expand Down Expand Up @@ -586,6 +704,47 @@ Future<void> doTestsNoCausal() async {
await doTestAwait(nonAsyncNoStack, nonAsyncNoStackExpected);
await doTestAwaitThen(nonAsyncNoStack, nonAsyncNoStackExpected);
await doTestAwaitCatchError(nonAsyncNoStack, nonAsyncNoStackExpected);

final asyncStarThrowSyncExpected = const <String>[
r'^#0 throwSync \(.+/utils.dart:(16|16:3)\)$',
r'^#1 asyncStarThrowSync \(.+/utils.dart:(112|112:11)\)$',
r'^#2 _RootZone.runUnary \(.+\)$',
r'^#3 _FutureListener.handleValue \(.+\)$',
r'^#4 Future._propagateToListeners.handleValueCallback \(.+\)$',
r'^#5 Future._propagateToListeners \(.+\)$',
// TODO(dart-vm): Figure out why this is inconsistent:
r'^#6 Future.(_addListener|_prependListeners).<anonymous closure> \(.+\)$',
r'^#7 _microtaskLoop \(.+\)$',
r'^#8 _startMicrotaskLoop \(.+\)$',
r'^#9 _runPendingImmediateCallback \(.+\)$',
r'^#10 _RawReceivePortImpl._handleMessage \(.+\)$',
r'^$',
];
await doTestAwait(awaitEveryAsyncStarThrowSync, asyncStarThrowSyncExpected);
await doTestAwaitThen(
awaitEveryAsyncStarThrowSync, asyncStarThrowSyncExpected);
await doTestAwaitCatchError(
awaitEveryAsyncStarThrowSync, asyncStarThrowSyncExpected);

final asyncStarThrowAsyncExpected = const <String>[
r'^#0 throwAsync \(.*/utils.dart:(21|21:3)\)$',
r'^#1 _RootZone.runUnary ',
r'^#2 _FutureListener.handleValue ',
r'^#3 Future._propagateToListeners.handleValueCallback ',
r'^#4 Future._propagateToListeners ',
// TODO(dart-vm): Figure out why this is inconsistent:
r'^#5 Future.(_addListener|_prependListeners).<anonymous closure> ',
r'^#6 _microtaskLoop ',
r'^#7 _startMicrotaskLoop ',
r'^#8 _runPendingImmediateCallback ',
r'^#9 _RawReceivePortImpl._handleMessage ',
r'^$',
];
await doTestAwait(awaitEveryAsyncStarThrowAsync, asyncStarThrowAsyncExpected);
await doTestAwaitThen(
awaitEveryAsyncStarThrowAsync, asyncStarThrowAsyncExpected);
await doTestAwaitCatchError(
awaitEveryAsyncStarThrowAsync, asyncStarThrowAsyncExpected);
}

// For: --lazy-async-stacks
Expand Down Expand Up @@ -744,4 +903,32 @@ Future<void> doTestsLazy() async {
const <String>[
r'^$',
]);

final asyncStarThrowSyncExpected = const <String>[
r'^#0 throwSync \(.+/utils.dart:(16|16:3)\)$',
r'^#1 asyncStarThrowSync \(.+/utils.dart:(112|112:11)\)$',
r'^<asynchronous suspension>$',
// Non-visible _onData frame.
r'^<asynchronous suspension>$',
r'^$',
];
await doTestAwait(awaitEveryAsyncStarThrowSync, asyncStarThrowSyncExpected);
await doTestAwaitThen(
awaitEveryAsyncStarThrowSync, asyncStarThrowSyncExpected);
await doTestAwaitCatchError(
awaitEveryAsyncStarThrowSync, asyncStarThrowSyncExpected);

final asyncStarThrowAsyncExpected = const <String>[
r'^#0 throwAsync \(.*/utils.dart:(21|21:3)\)$',
r'^<asynchronous suspension>$',
r'^#1 asyncStarThrowAsync \(.*/utils.dart:(0|126|126:5)\)$',
r'^<asynchronous suspension>$',
// Non-visible _onData frame.
r'^<asynchronous suspension>$',
];
await doTestAwait(awaitEveryAsyncStarThrowAsync, asyncStarThrowAsyncExpected);
await doTestAwaitThen(
awaitEveryAsyncStarThrowAsync, asyncStarThrowAsyncExpected);
await doTestAwaitCatchError(
awaitEveryAsyncStarThrowAsync, asyncStarThrowAsyncExpected);
}
3 changes: 2 additions & 1 deletion runtime/vm/compiler/frontend/scope_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1280,7 +1280,8 @@ void ScopeBuilder::VisitVariableDeclaration() {
// This way we can allocate them in the outermost context at fixed indices,
// allowing support for --lazy-async-stacks implementation to find awaiters.
if (name.Equals(Symbols::AwaitJumpVar()) ||
name.Equals(Symbols::AsyncCompleter())) {
name.Equals(Symbols::AsyncCompleter()) ||
name.Equals(Symbols::Controller())) {
scope_->parent()->AddVariable(variable);
} else {
scope_->AddVariable(variable);
Expand Down
1 change: 1 addition & 0 deletions runtime/vm/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -6036,6 +6036,7 @@ class Context : public Object {

static const intptr_t kAwaitJumpVarIndex = 0;
static const intptr_t kAsyncCompleterIndex = 1;
static const intptr_t kControllerIndex = 1;

static intptr_t variable_offset(intptr_t context_index) {
return OFFSET_OF_RETURNED_VALUE(RawContext, data) +
Expand Down
11 changes: 10 additions & 1 deletion runtime/vm/scopes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,

LocalVariable* await_jump_var = nullptr;
LocalVariable* async_completer = nullptr;
LocalVariable* controller = nullptr;
for (intptr_t i = 0; i < num_variables(); i++) {
LocalVariable* variable = VariableAt(i);
if (variable->owner() == this) {
Expand All @@ -218,6 +219,8 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
await_jump_var = variable;
} else if (variable->name().Equals(Symbols::AsyncCompleter())) {
async_completer = variable;
} else if (variable->name().Equals(Symbols::Controller())) {
controller = variable;
}
}
}
Expand All @@ -234,6 +237,11 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
*found_captured_variables = true;
ASSERT(async_completer->index().value() == Context::kAsyncCompleterIndex);
}
if (controller != nullptr) {
AllocateContextVariable(controller, &context_owner);
*found_captured_variables = true;
ASSERT(controller->index().value() == Context::kControllerIndex);
}

while (pos < num_parameters) {
LocalVariable* parameter = VariableAt(pos);
Expand Down Expand Up @@ -263,7 +271,8 @@ VariableIndex LocalScope::AllocateVariables(VariableIndex first_parameter_index,
if (variable->owner() == this) {
if (variable->is_captured()) {
// Skip the two variables already pre-allocated above.
if (variable != await_jump_var && variable != async_completer) {
if (variable != await_jump_var && variable != async_completer &&
variable != controller) {
AllocateContextVariable(variable, &context_owner);
*found_captured_variables = true;
}
Expand Down
Loading

0 comments on commit f9327d3

Please sign in to comment.