[dart2wasm] New async implementation
This CL re-implements the async function compilation without using JSPI
or any other platform features.
This implementation is faster than the JSPI-based one in all benchmarks,
in some cases up to 200x (benchmark results at the end). So we remove
the JSPI-based implementation as there's no point in maintaining a much
slower implementation and supporting two implementations at the same
time (which is tricky because these implementations need different
libraries, all scripts need to support two modes etc.) that also
requires experimental platform features.
# Main changes
- A new pass `AwaitTransformer` transforms `await` expressions to
top-level statements in form `var <fresh variable> = await <simple
expr>`, where `<simple expr>` is an expression without `await`.
After this pass all `await` expressions have the simple continuation
of "assign the value of the awaited future to this variable and
continue with the next statement". This simplifies `await`
compilation.
- A new code generator `AsyncCodeGenerator` (inherits from
`CodeGenerator`) compiles `async` functions. The `_YieldFinder` class
is copied from `sync*` code generator but modified to handle `async`
expressions.
- Mentions to the V8 flag `--experimental-wasm-stack-switching` is
removed from all scripts and documents.
# Future work
- Control flow handling in `AsyncCodeGenerator` needs to be implemented
in a similar way in `SyncStarCodeGenerator`. Doing this without
duplicating a lot of code will require some refactoring.
# New passing tests
- co19/Language/Statements/Yield_and_Yield_Each/Yield/execution_async_A05_t01
- co19/Language/Statements/For/Asynchronous_For_in/execution_A02_t02
- language/regress/regress23996_test
- language/sync_star/dcall_type_test
# Benchmarks
Current implementation:
```
AsyncLiveVars.LiveObj1(RunTime): 1586000.0 us.
AsyncLiveVars.LiveObj2(RunTime): 2114000.0 us.
AsyncLiveVars.LiveObj4(RunTime): 1972500.0 us.
AsyncLiveVars.LiveObj8(RunTime): 2212000.0 us.
AsyncLiveVars.LiveObj16(RunTime): 2238000.0 us.
AsyncLiveVars.LiveInt1(RunTime): 2362000.0 us.
AsyncLiveVars.LiveInt4(RunTime): 2470000.0 us.
AsyncLiveVars.LiveObj2Int2(RunTime): 2575000.0 us.
AsyncLiveVars.LiveObj4Int4(RunTime): 2820000.0 us.
Calls.AwaitAsyncCall(RunTimeRaw): 35676.15658362989 ns.
Calls.AwaitAsyncCallClosureTargetPolymorphic(RunTimeRaw): 38934.108527131786 ns.
Calls.AwaitAsyncCallInstanceTargetPolymorphic(RunTimeRaw): 42617.02127659575 ns.
Calls.AwaitFutureCall(RunTimeRaw): 2832.058906825262 ns.
Calls.AwaitFutureCallClosureTargetPolymorphic(RunTimeRaw): 3665.8125915080527 ns.
Calls.AwaitFutureCallInstanceTargetPolymorphic(RunTimeRaw): 4420.449537241076 ns.
Calls.AwaitFutureOrCall(RunTimeRaw): 3692.7621861152143 ns.
Calls.AwaitFutureOrCallClosureTargetPolymorphic(RunTimeRaw): 4625.346901017576 ns.
Calls.AwaitFutureOrCallInstanceTargetPolymorphic(RunTimeRaw): 4514.6726862302485 ns.
Calls.AwaitFutureOrCallInstanceTargetPolymorphicManyAwaits(RunTimeRaw): 345172.4137931034 ns.
Calls.AwaitForAsyncStarStreamPolymorphic(RunTimeRaw): 697000.0 ns.
Calls.AwaitForAsyncStarStreamPolymorphicManyYields(RunTimeRaw): 704666.6666666666 ns.
Calls.AwaitForManualStreamPolymorphic(RunTimeRaw): 11010.989010989011 ns.
Calls.SyncCall(RunTimeRaw): 0.40275240996973316 ns.
Calls.SyncCallClosureTarget(RunTimeRaw): 0.3989591156672242 ns.
Calls.SyncCallInstanceTargetPolymorphic(RunTimeRaw): 3.2632549336335526 ns.
Calls.IterableSyncStarIterablePolymorphic(RunTimeRaw): 353.3980582524272 ns.
Calls.IterableManualIterablePolymorphic(RunTimeRaw): 332.1161825726141 ns.
Calls.IterableManualIterablePolymorphicManyYields(RunTimeRaw): 354.28067078552516 ns.
```
New implementation:
```
AsyncLiveVars.LiveObj1(RunTime): 11327.683615819209 us.
AsyncLiveVars.LiveObj2(RunTime): 10923.91304347826 us.
AsyncLiveVars.LiveObj4(RunTime): 10956.284153005465 us.
AsyncLiveVars.LiveObj8(RunTime): 11286.516853932584 us.
AsyncLiveVars.LiveObj16(RunTime): 11445.714285714286 us.
AsyncLiveVars.LiveInt1(RunTime): 11016.483516483517 us.
AsyncLiveVars.LiveInt4(RunTime): 11327.683615819209 us.
AsyncLiveVars.LiveObj2Int2(RunTime): 10918.478260869566 us.
AsyncLiveVars.LiveObj4Int4(RunTime): 10737.967914438503 us.
Calls.AwaitAsyncCall(RunTimeRaw): 1082.2510822510822 ns.
Calls.AwaitAsyncCallClosureTargetPolymorphic(RunTimeRaw): 1056.4124234100993 ns.
Calls.AwaitAsyncCallInstanceTargetPolymorphic(RunTimeRaw): 1134.1726210729273 ns.
Calls.AwaitFutureCall(RunTimeRaw): 865.6509695290858 ns.
Calls.AwaitFutureCallClosureTargetPolymorphic(RunTimeRaw): 841.3967185527977 ns.
Calls.AwaitFutureCallInstanceTargetPolymorphic(RunTimeRaw): 839.066957543212 ns.
Calls.AwaitFutureOrCall(RunTimeRaw): 397.9941096871766 ns.
Calls.AwaitFutureOrCallClosureTargetPolymorphic(RunTimeRaw): 406.17384240454913 ns.
Calls.AwaitFutureOrCallInstanceTargetPolymorphic(RunTimeRaw): 393.7472929873607 ns.
Calls.AwaitFutureOrCallInstanceTargetPolymorphicManyAwaits(RunTimeRaw): 1095.0503723171266 ns.
Calls.AwaitForAsyncStarStreamPolymorphic(RunTimeRaw): 6643.426294820717 ns.
Calls.AwaitForAsyncStarStreamPolymorphicManyYields(RunTimeRaw): 7178.750897343863 ns.
Calls.AwaitForManualStreamPolymorphic(RunTimeRaw): 1456.23998835008 ns.
Calls.SyncCall(RunTimeRaw): 0.3919935321067202 ns.
Calls.SyncCallClosureTarget(RunTimeRaw): 0.3906669661780074 ns.
Calls.SyncCallInstanceTargetPolymorphic(RunTimeRaw): 3.1676143112814583 ns.
Calls.IterableSyncStarIterablePolymorphic(RunTimeRaw): 104.4932079414838 ns.
Calls.IterableManualIterablePolymorphic(RunTimeRaw): 104.57516339869281 ns.
Calls.IterableManualIterablePolymorphicManyYields(RunTimeRaw): 116.92487576731949 ns.
```
TEST=ci
CoreLibraryReviewExempt: Added entry-point pragmas.
Change-Id: I02fbd08141f51c00fb37b6fa0304dc25d6afdb71
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301020
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: William Hesse <whesse@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
diff --git a/pkg/dart2wasm/README.md b/pkg/dart2wasm/README.md
index cb1cce1..3e0c04c 100644
--- a/pkg/dart2wasm/README.md
+++ b/pkg/dart2wasm/README.md
@@ -44,7 +44,7 @@
Dart2Wasm will output a `wasm` file, containing Dart compiled to Wasm, as well as an `mjs` file containing the runtime. The result can be run with:
-`d8 --experimental-wasm-gc --experimental-wasm-stack-switching --experimental-wasm-type-reflection pkg/dart2wasm/bin/run_wasm.js -- `*outfile*`.wasm` /abs/path/to/`*outfile*`.mjs
+`d8 --experimental-wasm-gc --experimental-wasm-type-reflection pkg/dart2wasm/bin/run_wasm.js -- `*outfile*`.wasm` /abs/path/to/`*outfile*`.mjs
Where `d8` is the [V8 developer shell](https://v8.dev/docs/d8).
diff --git a/pkg/dart2wasm/bin/run_wasm.js b/pkg/dart2wasm/bin/run_wasm.js
index 19b61cd..bd8c692 100644
--- a/pkg/dart2wasm/bin/run_wasm.js
+++ b/pkg/dart2wasm/bin/run_wasm.js
@@ -6,7 +6,7 @@
//
// Run as follows:
//
-// $> d8 --experimental-wasm-gc --experimental-wasm-stack-switching \
+// $> d8 --experimental-wasm-gc \
// --experimental-wasm-type-reflection run_wasm.js \
// -- /abs/path/to/<dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart
new file mode 100644
index 0000000..5ae734b
--- /dev/null
+++ b/pkg/dart2wasm/lib/async.dart
@@ -0,0 +1,1459 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/closures.dart';
+import 'package:dart2wasm/code_generator.dart';
+import 'package:dart2wasm/sync_star.dart'
+ show StateTarget, StateTargetPlacement;
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Identify which statements contain `await` statements, and assign target
+/// indices to all control flow targets of these.
+///
+/// Target indices are assigned in program order.
+class _YieldFinder extends RecursiveVisitor {
+ final List<StateTarget> targets = [];
+ final bool enableAsserts;
+
+ // The number of `await` statements seen so far.
+ int yieldCount = 0;
+
+ _YieldFinder(this.enableAsserts);
+
+ List<StateTarget> find(FunctionNode function) {
+ // Initial state
+ addTarget(function.body!, StateTargetPlacement.Inner);
+ assert(function.body is Block || function.body is ReturnStatement);
+ recurse(function.body!);
+ // Final state
+ addTarget(function.body!, StateTargetPlacement.After);
+ return this.targets;
+ }
+
+ /// Recurse into a statement and then remove any targets added by the
+ /// statement if it doesn't contain any `await` statements.
+ void recurse(Statement statement) {
+ final yieldCountIn = yieldCount;
+ final targetsIn = targets.length;
+ statement.accept(this);
+ if (yieldCount == yieldCountIn) {
+ targets.length = targetsIn;
+ }
+ }
+
+ void addTarget(TreeNode node, StateTargetPlacement placement) {
+ targets.add(StateTarget(targets.length, node, placement));
+ }
+
+ @override
+ void visitBlock(Block node) {
+ for (Statement statement in node.statements) {
+ recurse(statement);
+ }
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.body);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ recurse(node.then);
+ if (node.otherwise != null) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.otherwise!);
+ }
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitLabeledStatement(LabeledStatement node) {
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ for (SwitchCase c in node.cases) {
+ addTarget(c, StateTargetPlacement.Inner);
+ recurse(c.body);
+ }
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitTryFinally(TryFinally node) {
+ // [TryFinally] blocks are always compiled to as CFG, even when they don't
+ // have awaits. This is to keep the code size small: with normal
+ // compilation finalizer blocks need to be duplicated based on
+ // continuations, which we don't need in the CFG implementation.
+ yieldCount += 1;
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.finalizer);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitTryCatch(TryCatch node) {
+ // Also always compile [TryCatch] blocks to the CFG to be able to set
+ // finalizer continuations.
+ yieldCount += 1;
+ recurse(node.body);
+ for (Catch c in node.catches) {
+ addTarget(c, StateTargetPlacement.Inner);
+ recurse(c.body);
+ }
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ addTarget(node, StateTargetPlacement.Inner);
+ recurse(node.body);
+ addTarget(node, StateTargetPlacement.After);
+ }
+
+ @override
+ void visitYieldStatement(YieldStatement node) {
+ throw 'Yield statement in async function: $node (${node.location})';
+ }
+
+ // Handle awaits. After the await transformation await can only appear in a
+ // RHS of a top-level assignment, or as a top-level statement.
+ @override
+ void visitVariableSet(VariableSet node) {
+ if (node.value is AwaitExpression) {
+ yieldCount++;
+ addTarget(node, StateTargetPlacement.After);
+ } else {
+ super.visitVariableSet(node);
+ }
+ }
+
+ @override
+ void visitExpressionStatement(ExpressionStatement node) {
+ if (node.expression is AwaitExpression) {
+ yieldCount++;
+ addTarget(node, StateTargetPlacement.After);
+ } else {
+ super.visitExpressionStatement(node);
+ }
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {}
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {}
+
+ // Any other await expression means the await transformer is buggy and didn't
+ // transform the expression as expected.
+ @override
+ void visitAwaitExpression(AwaitExpression node) {
+ throw 'Unexpected await expression: $node (${node.location})';
+ }
+
+ @override
+ void visitAssertStatement(AssertStatement node) {
+ if (enableAsserts) {
+ super.visitAssertStatement(node);
+ }
+ }
+
+ @override
+ void visitAssertBlock(AssertBlock node) {
+ if (enableAsserts) {
+ super.visitAssertBlock(node);
+ }
+ }
+}
+
+class _ExceptionHandlerStack {
+ /// Current exception handler stack. A CFG block generated when this is not
+ /// empty should have a Wasm `try` instruction wrapping the block.
+ ///
+ /// A `catch` block will jump to the last handler, which then jumps to the
+ /// next if the exception type test fails.
+ ///
+ /// Because the CFG blocks for [Catch] blocks and finalizers will have Wasm
+ /// `try` blocks for the parent handlers, we can use a Wasm `throw`
+ /// instruction (instead of jumping to the parent handler) in [Catch] blocks
+ /// and finalizers for rethrowing.
+ final List<_ExceptionHandler> _handlers = [];
+
+ /// Maps Wasm `try` blocks to number of handlers in [_handlers] that they
+ /// cover for.
+ final List<int> _tryBlockNumHandlers = [];
+
+ final AsyncCodeGenerator codeGen;
+
+ _ExceptionHandlerStack(this.codeGen);
+
+ void pushTryCatch(TryCatch node) {
+ final catcher = Catcher.fromTryCatch(
+ codeGen, node, codeGen.innerTargets[node.catches.first]!);
+ _handlers.add(catcher);
+ }
+
+ Finalizer pushTryFinally(TryFinally node) {
+ final finalizer =
+ Finalizer(codeGen, node, nextFinalizer, codeGen.innerTargets[node]!);
+ _handlers.add(finalizer);
+ return finalizer;
+ }
+
+ void pop() {
+ _handlers.removeLast();
+ }
+
+ int get numHandlers => _handlers.length;
+
+ int get coveredHandlers => _tryBlockNumHandlers.fold(0, (i1, i2) => i1 + i2);
+
+ int get numFinalizers {
+ int i = 0;
+ for (final handler in _handlers) {
+ if (handler is Finalizer) {
+ i += 1;
+ }
+ }
+ return i;
+ }
+
+ Finalizer? get nextFinalizer {
+ for (final handler in _handlers.reversed) {
+ if (handler is Finalizer) {
+ return handler;
+ }
+ }
+ return null;
+ }
+
+ void forEachFinalizer(void f(Finalizer finalizer, bool lastFinalizer)) {
+ Finalizer? finalizer = nextFinalizer;
+ while (finalizer != null) {
+ Finalizer? next = finalizer.parentFinalizer;
+ f(finalizer, next == null);
+ finalizer = next;
+ }
+ }
+
+ /// Generates Wasm `try` blocks for Dart `try` blocks wrapping the current
+ /// CFG block.
+ ///
+ /// Call this when generating a new CFG block.
+ void generateTryBlocks(w.Instructions b) {
+ final handlersToCover = _handlers.length - coveredHandlers;
+
+ if (handlersToCover == 0) {
+ return;
+ }
+
+ b.try_();
+ _tryBlockNumHandlers.add(handlersToCover);
+ }
+
+ /// Terminates Wasm `try` blocks generated by [generateTryBlocks].
+ ///
+ /// Call this right before terminating a CFG block.
+ void terminateTryBlocks() {
+ int handlerIdx = _handlers.length - 1;
+ while (_tryBlockNumHandlers.isNotEmpty) {
+ int nCoveredHandlers = _tryBlockNumHandlers.removeLast();
+
+ codeGen.b.catch_(codeGen.translator.exceptionTag);
+
+ final stackTraceLocal =
+ codeGen.addLocal(codeGen.translator.stackTraceInfo.nonNullableType);
+ codeGen.b.local_set(stackTraceLocal);
+
+ final exceptionLocal =
+ codeGen.addLocal(codeGen.translator.topInfo.nonNullableType);
+ codeGen.b.local_set(exceptionLocal);
+
+ final nextHandler = _handlers[handlerIdx];
+
+ while (nCoveredHandlers != 0) {
+ final handler = _handlers[handlerIdx];
+ handlerIdx -= 1;
+ if (handler is Finalizer) {
+ handler.setContinuationRethrow(
+ () => codeGen.b.local_get(exceptionLocal),
+ () => codeGen.b.local_get(stackTraceLocal));
+ }
+ nCoveredHandlers -= 1;
+ }
+
+ // Set the untyped "current exception" variable. Catch blocks will do the
+ // type tests as necessary using this variable and set their exception
+ // and stack trace locals.
+ codeGen._setCurrentException(() => codeGen.b.local_get(exceptionLocal));
+ codeGen._setCurrentExceptionStackTrace(
+ () => codeGen.b.local_get(stackTraceLocal));
+
+ codeGen.jumpToTarget(nextHandler.target);
+
+ codeGen.b.end(); // end catch
+ }
+ }
+}
+
+/// Represents an exception handler (`catch` or `finally`).
+///
+/// Note: for a [TryCatch] with multiple [Catch] blocks we jump to the first
+/// [Catch] block on exception, which checks the exception type and jumps to
+/// the next one if necessary.
+abstract class _ExceptionHandler {
+ /// CFG block for the `catch` or `finally` block.
+ final StateTarget target;
+
+ _ExceptionHandler(this.target);
+}
+
+class Catcher extends _ExceptionHandler {
+ final List<VariableDeclaration> _exceptionVars = [];
+ final List<VariableDeclaration> _stackTraceVars = [];
+ final AsyncCodeGenerator codeGen;
+
+ Catcher.fromTryCatch(this.codeGen, TryCatch node, super.target) {
+ for (Catch catch_ in node.catches) {
+ _exceptionVars.add(catch_.exception!);
+ _stackTraceVars.add(catch_.stackTrace!);
+ }
+ }
+
+ void setException(void pushException()) {
+ for (final exceptionVar in _exceptionVars) {
+ codeGen._setVariable(exceptionVar, pushException);
+ }
+ }
+
+ void setStackTrace(void pushStackTrace()) {
+ for (final stackTraceVar in _stackTraceVars) {
+ codeGen._setVariable(stackTraceVar, pushStackTrace);
+ }
+ }
+}
+
+const int continuationFallthrough = 0;
+const int continuationReturn = 1;
+const int continuationRethrow = 2;
+
+// For larger continuation values, `value - continuationJump` gives us the
+// target block index to jump.
+const int continuationJump = 3;
+
+class Finalizer extends _ExceptionHandler {
+ final VariableDeclaration _continuationVar;
+ final VariableDeclaration _exceptionVar;
+ final VariableDeclaration _stackTraceVar;
+ final Finalizer? parentFinalizer;
+ final AsyncCodeGenerator codeGen;
+
+ Finalizer(this.codeGen, TryFinally node, this.parentFinalizer, super.target)
+ : _continuationVar =
+ (node.parent as Block).statements[0] as VariableDeclaration,
+ _exceptionVar =
+ (node.parent as Block).statements[1] as VariableDeclaration,
+ _stackTraceVar =
+ (node.parent as Block).statements[2] as VariableDeclaration;
+
+ void setContinuationFallthrough() {
+ codeGen._setVariable(_continuationVar, () {
+ codeGen.b.i64_const(continuationFallthrough);
+ });
+ }
+
+ void setContinuationReturn() {
+ codeGen._setVariable(_continuationVar, () {
+ codeGen.b.i64_const(continuationReturn);
+ });
+ }
+
+ void setContinuationRethrow(void pushException(), void pushStackTrace()) {
+ codeGen._setVariable(_continuationVar, () {
+ codeGen.b.i64_const(continuationRethrow);
+ });
+ codeGen._setVariable(_exceptionVar, pushException);
+ codeGen._setVariable(_stackTraceVar, pushStackTrace);
+ }
+
+ void setContinuationJump(int index) {
+ codeGen._setVariable(_continuationVar, () {
+ codeGen.b.i64_const(index + continuationJump);
+ });
+ }
+
+ /// Push continuation of the finalizer block onto the stack as `i32`.
+ void pushContinuation() {
+ codeGen.visitVariableGet(VariableGet(_continuationVar), w.NumType.i64);
+ codeGen.b.i32_wrap_i64();
+ }
+
+ void pushException() {
+ codeGen._getVariable(_exceptionVar);
+ }
+
+ void pushStackTrace() {
+ codeGen._getVariable(_stackTraceVar);
+ }
+}
+
+/// Represents target of a `break` statement.
+abstract class LabelTarget {
+ void jump(AsyncCodeGenerator codeGen, BreakStatement node);
+}
+
+/// Target of a [BreakStatement] that can be implemented with a Wasm `br`
+/// instruction.
+///
+/// This [LabelTarget] is used when the [LabeledStatement] is compiled using
+/// the normal code generator (instead of async code generator).
+class DirectLabelTarget implements LabelTarget {
+ final w.Label label;
+
+ DirectLabelTarget(this.label);
+
+ @override
+ void jump(AsyncCodeGenerator codeGen, BreakStatement node) {
+ codeGen.b.br(label);
+ }
+}
+
+/// Target of a [BreakStatement] when the [LabeledStatement] is compiled to
+/// CFG.
+class IndirectLabelTarget implements LabelTarget {
+ /// Number of finalizers wrapping the [LabeledStatement].
+ final int finalizerDepth;
+
+ /// CFG state for the [LabeledStatement] continuation.
+ final StateTarget stateTarget;
+
+ IndirectLabelTarget(this.finalizerDepth, this.stateTarget);
+
+ @override
+ void jump(AsyncCodeGenerator codeGen, BreakStatement node) {
+ final currentFinalizerDepth = codeGen.exceptionHandlers.numFinalizers;
+ final finalizersToRun = currentFinalizerDepth - finalizerDepth;
+
+ // Control flow overridden by a `break`, reset finalizer continuations.
+ var i = finalizersToRun;
+ codeGen.exceptionHandlers.forEachFinalizer((finalizer, last) {
+ if (i <= 0) {
+ // Finalizer won't be run by the `break`, reset continuation.
+ finalizer.setContinuationFallthrough();
+ } else {
+ // Finalizer will be run by the `break`. Each finalizer jumps to the
+ // next, last finalizer jumps to the `break` target.
+ finalizer.setContinuationJump(i == 1
+ ? stateTarget.index
+ : finalizer.parentFinalizer!.target.index);
+ }
+ i -= 1;
+ });
+
+ if (finalizersToRun == 0) {
+ codeGen.jumpToTarget(stateTarget);
+ } else {
+ codeGen.jumpToTarget(codeGen.exceptionHandlers.nextFinalizer!.target);
+ }
+ }
+}
+
+/// Exception and stack trace variables of a [Catch] block. These variables are
+/// used to get the exception and stack trace to throw when compiling
+/// [Rethrow].
+class CatchVariables {
+ final VariableDeclaration exception;
+ final VariableDeclaration stackTrace;
+
+ CatchVariables(this.exception, this.stackTrace);
+}
+
+class AsyncCodeGenerator extends CodeGenerator {
+ AsyncCodeGenerator(super.translator, super.function, super.reference);
+
+ /// Targets of the CFG, indexed by target index.
+ late final List<StateTarget> targets;
+
+ // Targets categorized by placement and indexed by node.
+ final Map<TreeNode, StateTarget> innerTargets = {};
+ final Map<TreeNode, StateTarget> afterTargets = {};
+
+ /// The loop around the switch.
+ late final w.Label masterLoop;
+
+ /// The target labels of the switch, indexed by target index.
+ late final List<w.Label> labels;
+
+ /// The target index of the entry label for the current CFG node.
+ int currentTargetIndex = -1;
+
+ /// The local in the inner function for the async state, with type
+ /// `ref _AsyncSuspendState`.
+ late final w.Local suspendStateLocal;
+
+ /// The local in the inner function for the value of the last awaited future,
+ /// with type `ref null #Top`.
+ late final w.Local awaitValueLocal;
+
+ /// The local for the CFG target block index.
+ late final w.Local targetIndexLocal;
+
+ /// Exception handlers wrapping the current CFG block. Used to generate Wasm
+ /// `try` and `catch` blocks around the CFG blocks.
+ late final _ExceptionHandlerStack exceptionHandlers;
+
+ /// Maps labelled statements to their CFG targets. Used when jumping to a CFG
+ /// block on `break`.
+ final Map<LabeledStatement, LabelTarget> labelTargets = {};
+
+ late final ClassInfo asyncSuspendStateInfo =
+ translator.classInfo[translator.asyncSuspendStateClass]!;
+
+ /// Current [Catch] block stack, used to compile [Rethrow].
+ ///
+ /// Because there can be an `await` in a [Catch] block before a [Rethrow], we
+ /// can't compile [Rethrow] to Wasm `rethrow`. Instead we `throw` using the
+ /// [Rethrow]'s parent [Catch] block's exception and stack variables.
+ List<CatchVariables> catchVariableStack = [];
+
+ @override
+ void generate() {
+ closures = Closures(this);
+ setupParametersAndContexts(member);
+ generateTypeChecks(member.function!.typeParameters, member.function!,
+ translator.paramInfoFor(reference));
+ _generateBodies(member.function!);
+ }
+
+ @override
+ w.DefinedFunction generateLambda(Lambda lambda, Closures closures) {
+ this.closures = closures;
+ setupLambdaParametersAndContexts(lambda);
+ _generateBodies(lambda.functionNode);
+ return function;
+ }
+
+ void _generateBodies(FunctionNode functionNode) {
+ // Number and categorize CFG targets.
+ targets = _YieldFinder(translator.options.enableAsserts).find(functionNode);
+ for (final target in targets) {
+ switch (target.placement) {
+ case StateTargetPlacement.Inner:
+ innerTargets[target.node] = target;
+ break;
+ case StateTargetPlacement.After:
+ afterTargets[target.node] = target;
+ break;
+ }
+ }
+
+ exceptionHandlers = _ExceptionHandlerStack(this);
+
+ // Wasm function containing the body of the `async` function
+ // (`_AyncResumeFun`).
+ final w.DefinedFunction resumeFun = m.addFunction(
+ m.addFunctionType([
+ asyncSuspendStateInfo.nonNullableType, // _AsyncSuspendState
+ translator.topInfo.nullableType, // Object?, await value
+ translator.topInfo.nullableType, // Object?, error value
+ translator
+ .stackTraceInfo.nullableType // StackTrace?, error stack trace
+ ], [
+ // Inner function does not return a value, but it's Dart type is
+ // `void Function(...)` and all Dart functions return a value, so we
+ // add a return type.
+ translator.topInfo.nullableType
+ ]),
+ "${function.functionName} inner");
+
+ Context? context = closures.contexts[functionNode];
+ if (context != null && context.isEmpty) context = context.parent;
+
+ _generateOuter(functionNode, context, resumeFun);
+
+ // Forget about the outer function locals containing the type arguments,
+ // so accesses to the type arguments in the inner function will fetch them
+ // from the context.
+ typeLocals.clear();
+
+ _generateInner(functionNode, context, resumeFun);
+ }
+
+ void _generateOuter(FunctionNode functionNode, Context? context,
+ w.DefinedFunction resumeFun) {
+ // Outer (wrapper) function creates async state, calls the inner function
+ // (which runs until first suspension point, i.e. `await`), and returns the
+ // completer's future.
+
+ // (1) Create async state.
+
+ final asyncStateLocal = function
+ .addLocal(w.RefType(asyncSuspendStateInfo.struct, nullable: false));
+
+ // AsyncResumeFun _resume
+ b.global_get(translator.makeFunctionRef(resumeFun));
+
+ // WasmStructRef? _context
+ if (context != null) {
+ assert(!context.isEmpty);
+ b.local_get(context.currentLocal);
+ } else {
+ b.ref_null(w.HeapType.struct);
+ }
+
+ // _AsyncCompleter _completer
+ final DartType returnType = functionNode.returnType;
+ final DartType innerType = returnType is InterfaceType &&
+ returnType.classNode == translator.coreTypes.futureClass
+ ? returnType.typeArguments.single
+ : const DynamicType();
+ types.makeType(this, innerType);
+ b.call(translator.functions
+ .getFunction(translator.makeAsyncCompleter.reference));
+
+ // Allocate `_AsyncSuspendState`
+ b.call(translator.functions
+ .getFunction(translator.newAsyncSuspendState.reference));
+ b.local_set(asyncStateLocal);
+
+ // (2) Call inner function.
+ //
+ // Note: the inner function does not throw, so we don't need a `try` block
+ // here.
+
+ b.local_get(asyncStateLocal);
+ b.ref_null(translator.topInfo.struct); // await value
+ b.ref_null(translator.topInfo.struct); // error value
+ b.ref_null(translator.stackTraceInfo.struct); // stack trace
+ b.call(resumeFun);
+ b.drop(); // drop null
+
+ // (3) Return the completer's future.
+
+ b.local_get(asyncStateLocal);
+ final completerFutureGetter = translator.functions
+ .getFunction(translator.completerFuture.getterReference);
+ b.struct_get(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
+ translator.convertType(
+ function,
+ asyncSuspendStateInfo.struct.fields[5].type.unpacked,
+ completerFutureGetter.type.inputs[0]);
+ b.call(completerFutureGetter);
+ b.end();
+ }
+
+ /// Clones the context pointed to by the [srcContext] local. Returns a local
+ /// pointing to the cloned context.
+ ///
+ /// It is assumed that the context is the function-level context for the
+ /// `async` function.
+ w.Local _cloneContext(
+ FunctionNode functionNode, Context context, w.Local srcContext) {
+ assert(context.owner == functionNode);
+
+ final w.Local destContext = addLocal(context.currentLocal.type);
+ b.struct_new_default(context.struct);
+ b.local_set(destContext);
+
+ void copyCapture(TreeNode node) {
+ Capture? capture = closures.captures[node];
+ if (capture != null) {
+ assert(capture.context == context);
+ b.local_get(destContext);
+ b.local_get(srcContext);
+ b.struct_get(context.struct, capture.fieldIndex);
+ b.struct_set(context.struct, capture.fieldIndex);
+ }
+ }
+
+ if (context.containsThis) {
+ b.local_get(destContext);
+ b.local_get(srcContext);
+ b.struct_get(context.struct, context.thisFieldIndex);
+ b.struct_set(context.struct, context.thisFieldIndex);
+ }
+ if (context.parent != null) {
+ b.local_get(destContext);
+ b.local_get(srcContext);
+ b.struct_get(context.struct, context.parentFieldIndex);
+ b.struct_set(context.struct, context.parentFieldIndex);
+ }
+ functionNode.positionalParameters.forEach(copyCapture);
+ functionNode.namedParameters.forEach(copyCapture);
+ functionNode.typeParameters.forEach(copyCapture);
+
+ return destContext;
+ }
+
+ void _generateInner(FunctionNode functionNode, Context? context,
+ w.DefinedFunction resumeFun) {
+ // void Function(_AsyncSuspendState, Object?)
+
+ // Set the current Wasm function for the code generator to the inner
+ // function of the `async`, which is to contain the body.
+ function = resumeFun;
+
+ suspendStateLocal = function.locals[0]; // ref _AsyncSuspendState
+ awaitValueLocal = function.locals[1]; // ref null #Top
+
+ // Set up locals for contexts and `this`.
+ thisLocal = null;
+ Context? localContext = context;
+ while (localContext != null) {
+ if (!localContext.isEmpty) {
+ localContext.currentLocal = function
+ .addLocal(w.RefType.def(localContext.struct, nullable: true));
+ if (localContext.containsThis) {
+ assert(thisLocal == null);
+ thisLocal = function.addLocal(localContext
+ .struct.fields[localContext.thisFieldIndex].type.unpacked
+ .withNullability(false));
+ translator.globals.instantiateDummyValue(b, thisLocal!.type);
+ b.local_set(thisLocal!);
+
+ preciseThisLocal = thisLocal;
+ }
+ }
+ localContext = localContext.parent;
+ }
+
+ // Read target index from the suspend state.
+ targetIndexLocal = addLocal(w.NumType.i32);
+ b.local_get(suspendStateLocal);
+ b.struct_get(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateTargetIndex);
+ b.local_set(targetIndexLocal);
+
+ // The outer `try` block calls `completeOnError` on exceptions.
+ b.try_();
+
+ // Switch on the target index.
+ masterLoop = b.loop(const [], const []);
+ labels = List.generate(targets.length, (_) => b.block()).reversed.toList();
+ w.Label defaultLabel = b.block();
+ b.local_get(targetIndexLocal);
+ b.br_table(labels, defaultLabel);
+ b.end(); // defaultLabel
+ b.unreachable();
+
+ // Initial state
+ final StateTarget initialTarget = targets.first;
+ _emitTargetLabel(initialTarget);
+
+ // Clone context on first execution.
+ _restoreContextsAndThis(context, cloneContextFor: functionNode);
+
+ visitStatement(functionNode.body!);
+
+ // Final state: return.
+ _emitTargetLabel(targets.last);
+ b.local_get(suspendStateLocal);
+ b.struct_get(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
+ // Non-null Dart field represented as nullable Wasm field.
+ b.ref_as_non_null();
+ b.ref_null(translator.topInfo.struct);
+ b.call(translator.functions
+ .getFunction(translator.completerComplete.reference));
+ b.return_();
+ b.end(); // masterLoop
+
+ b.catch_(translator.exceptionTag);
+
+ final stackTraceLocal = addLocal(translator.stackTraceInfo.nonNullableType);
+ b.local_set(stackTraceLocal);
+
+ final exceptionLocal = addLocal(translator.topInfo.nonNullableType);
+ b.local_set(exceptionLocal);
+
+ b.local_get(suspendStateLocal);
+ b.struct_get(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
+ b.ref_as_non_null();
+ b.local_get(exceptionLocal);
+ b.local_get(stackTraceLocal);
+ b.call(translator.functions
+ .getFunction(translator.completerCompleteError.reference));
+ b.return_();
+
+ b.end(); // end try
+
+ b.unreachable();
+ b.end();
+ }
+
+ // Note: These two locals are only available in "inner" functions.
+ w.Local get pendingExceptionLocal => function.locals[2];
+ w.Local get pendingStackTraceLocal => function.locals[3];
+
+ void _emitTargetLabel(StateTarget target) {
+ currentTargetIndex++;
+ assert(
+ target.index == currentTargetIndex,
+ 'target.index = ${target.index}, '
+ 'currentTargetIndex = $currentTargetIndex, '
+ 'target.node.location = ${target.node.location}');
+ exceptionHandlers.terminateTryBlocks();
+ b.end();
+ exceptionHandlers.generateTryBlocks(b);
+ }
+
+ void jumpToTarget(StateTarget target,
+ {Expression? condition, bool negated = false}) {
+ if (condition == null && negated) return;
+ if (target.index > currentTargetIndex) {
+ // Forward jump directly to the label.
+ branchIf(condition, labels[target.index], negated: negated);
+ } else {
+ // Backward jump via the switch.
+ w.Label block = b.block();
+ branchIf(condition, block, negated: !negated);
+ b.i32_const(target.index);
+ b.local_set(targetIndexLocal);
+ b.br(masterLoop);
+ b.end(); // block
+ }
+ }
+
+ void _restoreContextsAndThis(Context? context,
+ {FunctionNode? cloneContextFor}) {
+ if (context != null) {
+ assert(!context.isEmpty);
+ b.local_get(suspendStateLocal);
+ b.struct_get(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateContext);
+ b.ref_cast(context.currentLocal.type as w.RefType);
+ b.local_set(context.currentLocal);
+
+ if (context.owner == cloneContextFor) {
+ context.currentLocal =
+ _cloneContext(cloneContextFor!, context, context.currentLocal);
+ }
+
+ while (context!.parent != null) {
+ assert(!context.parent!.isEmpty);
+ b.local_get(context.currentLocal);
+ b.struct_get(context.struct, context.parentFieldIndex);
+ b.ref_as_non_null();
+ context = context.parent!;
+ b.local_set(context.currentLocal);
+ }
+ if (context.containsThis) {
+ b.local_get(context.currentLocal);
+ b.struct_get(context.struct, context.thisFieldIndex);
+ b.ref_as_non_null();
+ b.local_set(thisLocal!);
+ }
+ }
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ StateTarget? inner = innerTargets[node];
+ if (inner == null) return super.visitDoStatement(node);
+
+ _emitTargetLabel(inner);
+ allocateContext(node);
+ visitStatement(node.body);
+ jumpToTarget(inner, condition: node.condition);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ StateTarget? inner = innerTargets[node];
+ if (inner == null) return super.visitForStatement(node);
+ StateTarget after = afterTargets[node]!;
+
+ allocateContext(node);
+ for (VariableDeclaration variable in node.variables) {
+ visitStatement(variable);
+ }
+ _emitTargetLabel(inner);
+ jumpToTarget(after, condition: node.condition, negated: true);
+ visitStatement(node.body);
+
+ emitForStatementUpdate(node);
+
+ jumpToTarget(inner);
+ _emitTargetLabel(after);
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ StateTarget? after = afterTargets[node];
+ if (after == null) return super.visitIfStatement(node);
+ StateTarget? inner = innerTargets[node];
+
+ jumpToTarget(inner ?? after, condition: node.condition, negated: true);
+ visitStatement(node.then);
+ if (node.otherwise != null) {
+ jumpToTarget(after);
+ _emitTargetLabel(inner!);
+ visitStatement(node.otherwise!);
+ }
+ _emitTargetLabel(after);
+ }
+
+ @override
+ void visitLabeledStatement(LabeledStatement node) {
+ StateTarget? after = afterTargets[node];
+ if (after == null) {
+ final w.Label label = b.block();
+ labelTargets[node] = DirectLabelTarget(label);
+ visitStatement(node.body);
+ labelTargets.remove(node);
+ b.end();
+ } else {
+ labelTargets[node] =
+ IndirectLabelTarget(exceptionHandlers.numFinalizers, after);
+ visitStatement(node.body);
+ labelTargets.remove(node);
+ _emitTargetLabel(after);
+ }
+ }
+
+ @override
+ void visitBreakStatement(BreakStatement node) {
+ labelTargets[node.target]!.jump(this, node);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ StateTarget? after = afterTargets[node];
+ if (after == null) return super.visitSwitchStatement(node);
+
+ final switchInfo = SwitchInfo(this, node);
+
+ bool isNullable = dartTypeOf(node.expression).isPotentiallyNullable;
+
+ // Special cases
+ final SwitchCase? defaultCase = switchInfo.defaultCase;
+ final SwitchCase? nullCase = switchInfo.nullCase;
+
+ // When the type is nullable we use two variables: one for the nullable
+ // value, one after the null check, with non-nullable type.
+ w.Local switchValueNonNullableLocal = addLocal(switchInfo.nonNullableType);
+ w.Local? switchValueNullableLocal =
+ isNullable ? addLocal(switchInfo.nullableType) : null;
+
+ // Initialize switch value local
+ wrap(node.expression,
+ isNullable ? switchInfo.nullableType : switchInfo.nonNullableType);
+ b.local_set(
+ isNullable ? switchValueNullableLocal! : switchValueNonNullableLocal);
+
+ // Compute value and handle null
+ if (isNullable) {
+ final StateTarget nullTarget = nullCase != null
+ ? innerTargets[nullCase]!
+ : defaultCase != null
+ ? innerTargets[defaultCase]!
+ : after;
+
+ b.local_get(switchValueNullableLocal!);
+ b.ref_is_null();
+ b.if_();
+ jumpToTarget(nullTarget);
+ b.end();
+ b.local_get(switchValueNullableLocal);
+ b.ref_as_non_null();
+ // Unbox if necessary
+ translator.convertType(function, switchValueNullableLocal.type,
+ switchValueNonNullableLocal.type);
+ b.local_set(switchValueNonNullableLocal);
+ }
+
+ // Compare against all case values
+ for (SwitchCase c in node.cases) {
+ for (Expression exp in c.expressions) {
+ if (exp is NullLiteral ||
+ exp is ConstantExpression && exp.constant is NullConstant) {
+ // Null already checked, skip
+ } else {
+ wrap(exp, switchInfo.nonNullableType);
+ b.local_get(switchValueNonNullableLocal);
+ switchInfo.compare();
+ b.if_();
+ jumpToTarget(innerTargets[c]!);
+ b.end();
+ }
+ }
+ }
+
+ // No explicit cases matched
+ if (node.isExplicitlyExhaustive) {
+ b.unreachable();
+ } else {
+ final StateTarget defaultLabel =
+ defaultCase != null ? innerTargets[defaultCase]! : after;
+ jumpToTarget(defaultLabel);
+ }
+
+ // Emit case bodies
+ for (SwitchCase c in node.cases) {
+ _emitTargetLabel(innerTargets[c]!);
+ visitStatement(c.body);
+ jumpToTarget(after);
+ }
+
+ _emitTargetLabel(after);
+ }
+
+ @override
+ void visitContinueSwitchStatement(ContinueSwitchStatement node) {
+ jumpToTarget(innerTargets[node.target]!);
+ }
+
+ @override
+ void visitTryCatch(TryCatch node) {
+ StateTarget? after = afterTargets[node];
+ if (after == null) return super.visitTryCatch(node);
+
+ allocateContext(node);
+
+ for (Catch c in node.catches) {
+ if (c.exception != null) {
+ visitVariableDeclaration(c.exception!);
+ }
+ if (c.stackTrace != null) {
+ visitVariableDeclaration(c.stackTrace!);
+ }
+ }
+
+ exceptionHandlers.pushTryCatch(node);
+ exceptionHandlers.generateTryBlocks(b);
+ visitStatement(node.body);
+ jumpToTarget(after);
+ exceptionHandlers.terminateTryBlocks();
+ exceptionHandlers.pop();
+
+ void emitCatchBlock(Catch catch_, Catch? nextCatch, bool emitGuard) {
+ if (emitGuard) {
+ _getCurrentException();
+ b.ref_as_non_null();
+ types.emitTypeTest(
+ this, catch_.guard, translator.coreTypes.objectNonNullableRawType);
+ b.i32_eqz();
+ // When generating guards we can't generate the catch body inside the
+ // `if` block for the guard as the catch body can have suspension
+ // points and generate target labels.
+ b.if_();
+ if (nextCatch != null) {
+ jumpToTarget(innerTargets[nextCatch]!);
+ } else {
+ // Rethrow.
+ _getCurrentException();
+ b.ref_as_non_null();
+ _getCurrentExceptionStackTrace();
+ b.ref_as_non_null();
+ // TODO (omersa): When there is a finalizer we can jump to it
+ // directly, instead of via throw/catch. Would that be faster?
+ exceptionHandlers.forEachFinalizer(
+ (finalizer, _last) => finalizer.setContinuationRethrow(
+ () => _getVariable(catch_.exception!),
+ () => _getVariable(catch_.stackTrace!),
+ ));
+ b.throw_(translator.exceptionTag);
+ }
+ b.end();
+ }
+
+ // Set exception and stack trace variables.
+ _setVariable(catch_.exception!, () {
+ _getCurrentException();
+ // Type test already passed, convert the exception.
+ translator.convertType(
+ function,
+ asyncSuspendStateInfo
+ .struct
+ .fields[FieldIndex.asyncSuspendStateCurrentException]
+ .type
+ .unpacked,
+ translator.translateType(catch_.exception!.type));
+ });
+ _setVariable(catch_.stackTrace!, () => _getCurrentExceptionStackTrace());
+
+ catchVariableStack
+ .add(CatchVariables(catch_.exception!, catch_.stackTrace!));
+
+ visitStatement(catch_.body);
+
+ catchVariableStack.removeLast();
+
+ jumpToTarget(after);
+ }
+
+ for (int catchIdx = 0; catchIdx < node.catches.length; catchIdx += 1) {
+ final Catch catch_ = node.catches[catchIdx];
+ final Catch? nextCatch =
+ node.catches.length < catchIdx ? node.catches[catchIdx + 1] : null;
+
+ _emitTargetLabel(innerTargets[catch_]!);
+
+ final bool shouldEmitGuard =
+ catch_.guard != translator.coreTypes.objectNonNullableRawType;
+
+ emitCatchBlock(catch_, nextCatch, shouldEmitGuard);
+
+ if (!shouldEmitGuard) {
+ break;
+ }
+ }
+
+ // Rethrow. Note that we don't override finalizer continuations here, they
+ // should be set by the original `throw` site.
+ _getCurrentException();
+ b.ref_as_non_null();
+ _getCurrentExceptionStackTrace();
+ b.ref_as_non_null();
+ b.throw_(translator.exceptionTag);
+
+ _emitTargetLabel(after);
+ }
+
+ @override
+ void visitTryFinally(TryFinally node) {
+ allocateContext(node);
+
+ final StateTarget finalizerTarget = innerTargets[node]!;
+ final StateTarget fallthroughContinuationTarget = afterTargets[node]!;
+
+ // Body
+ final finalizer = exceptionHandlers.pushTryFinally(node);
+ exceptionHandlers.generateTryBlocks(b);
+ visitStatement(node.body);
+
+ // Set continuation of the finalizer.
+ finalizer.setContinuationFallthrough();
+
+ jumpToTarget(finalizerTarget);
+ exceptionHandlers.terminateTryBlocks();
+ exceptionHandlers.pop();
+
+ // Finalizer
+ {
+ _emitTargetLabel(finalizerTarget);
+ visitStatement(node.finalizer);
+
+ // Check continuation.
+
+ // Fallthrough
+ assert(continuationFallthrough == 0); // update eqz below if changed
+ finalizer.pushContinuation();
+ b.i32_eqz();
+ b.if_();
+ jumpToTarget(fallthroughContinuationTarget);
+ b.end();
+
+ // Return
+ finalizer.pushContinuation();
+ b.i32_const(continuationReturn);
+ b.i32_eq();
+ b.if_();
+ b.local_get(suspendStateLocal);
+ b.struct_get(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
+ // Non-null Dart field represented as nullable Wasm field.
+ b.ref_as_non_null();
+ b.local_get(suspendStateLocal);
+ b.struct_get(asyncSuspendStateInfo.struct,
+ FieldIndex.asyncSuspendStateCurrentReturnValue);
+ b.call(translator.functions
+ .getFunction(translator.completerComplete.reference));
+ b.return_();
+ b.end();
+
+ // Rethrow
+ finalizer.pushContinuation();
+ b.i32_const(continuationRethrow);
+ b.i32_eq();
+ b.if_();
+ finalizer.pushException();
+ b.ref_as_non_null();
+ finalizer.pushStackTrace();
+ b.ref_as_non_null();
+ b.throw_(translator.exceptionTag);
+ b.end();
+
+ // Any other value: jump to the target.
+ finalizer.pushContinuation();
+ b.i32_const(continuationJump);
+ b.i32_sub();
+ b.local_set(targetIndexLocal);
+ b.br(masterLoop);
+ }
+
+ _emitTargetLabel(fallthroughContinuationTarget);
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ StateTarget? inner = innerTargets[node];
+ if (inner == null) return super.visitWhileStatement(node);
+ StateTarget after = afterTargets[node]!;
+
+ _emitTargetLabel(inner);
+ jumpToTarget(after, condition: node.condition, negated: true);
+ allocateContext(node);
+ visitStatement(node.body);
+ jumpToTarget(inner);
+ _emitTargetLabel(after);
+ }
+
+ @override
+ void visitYieldStatement(YieldStatement node) {
+ throw 'Yield statement in async function: $node (${node.location})';
+ }
+
+ @override
+ void visitReturnStatement(ReturnStatement node) {
+ final Finalizer? firstFinalizer = exceptionHandlers.nextFinalizer;
+
+ if (firstFinalizer == null) {
+ b.local_get(suspendStateLocal);
+ b.struct_get(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateCompleter);
+ // Non-null Dart field represented as nullable Wasm field.
+ b.ref_as_non_null();
+ }
+
+ final value = node.expression;
+ if (value == null) {
+ b.ref_null(translator.topInfo.struct);
+ } else {
+ wrap(value, translator.topInfo.nullableType);
+ }
+
+ if (firstFinalizer == null) {
+ b.call(translator.functions
+ .getFunction(translator.completerComplete.reference));
+ b.return_();
+ } else {
+ final returnValueLocal = addLocal(translator.topInfo.nullableType);
+ b.local_set(returnValueLocal);
+
+ // Set return value
+ b.local_get(suspendStateLocal);
+ b.local_get(returnValueLocal);
+ b.struct_set(asyncSuspendStateInfo.struct,
+ FieldIndex.asyncSuspendStateCurrentReturnValue);
+
+ // Update continuation variables of finalizers. Last finalizer returns
+ // the value.
+ exceptionHandlers.forEachFinalizer((finalizer, last) {
+ if (last) {
+ finalizer.setContinuationReturn();
+ } else {
+ finalizer
+ .setContinuationJump(finalizer.parentFinalizer!.target.index);
+ }
+ });
+
+ // Jump to the first finalizer
+ jumpToTarget(firstFinalizer.target);
+ }
+ }
+
+ @override
+ w.ValueType visitThrow(Throw node, w.ValueType expectedType) {
+ final exceptionLocal = addLocal(translator.topInfo.nonNullableType);
+ wrap(node.expression, translator.topInfo.nonNullableType);
+ b.local_set(exceptionLocal);
+
+ final stackTraceLocal = addLocal(translator.stackTraceInfo.nonNullableType);
+ call(translator.stackTraceCurrent.reference);
+ b.local_set(stackTraceLocal);
+
+ exceptionHandlers.forEachFinalizer((finalizer, _last) {
+ finalizer.setContinuationRethrow(() => b.local_get(exceptionLocal),
+ () => b.local_get(stackTraceLocal));
+ });
+
+ // TODO (omersa): An alternative would be to directly jump to the parent
+ // handler, or call `completeOnError` if we're not in a try-catch or
+ // try-finally. Would that be more efficient?
+ b.local_get(exceptionLocal);
+ b.local_get(stackTraceLocal);
+ b.throw_(translator.exceptionTag);
+
+ b.unreachable();
+ return expectedType;
+ }
+
+ @override
+ w.ValueType visitRethrow(Rethrow node, w.ValueType expectedType) {
+ final catchVars = catchVariableStack.last;
+
+ exceptionHandlers.forEachFinalizer((finalizer, _last) {
+ finalizer.setContinuationRethrow(
+ () => _getVariable(catchVars.exception),
+ () => _getVariable(catchVars.stackTrace),
+ );
+ });
+
+ // TODO (omersa): Similar to `throw` compilation above, we could directly
+ // jump to the target block or call `completeOnError`.
+ _getCurrentException();
+ b.ref_as_non_null();
+ _getCurrentExceptionStackTrace();
+ b.ref_as_non_null();
+ b.throw_(translator.exceptionTag);
+ b.unreachable();
+ return expectedType;
+ }
+
+ // Handle awaits
+ @override
+ void visitExpressionStatement(ExpressionStatement node) {
+ final expression = node.expression;
+ if (expression is VariableSet) {
+ final value = expression.value;
+ if (value is AwaitExpression) {
+ _generateAwait(value, expression.variable);
+ return;
+ }
+ }
+
+ super.visitExpressionStatement(node);
+ }
+
+ void _generateAwait(AwaitExpression node, VariableDeclaration awaitValueVar) {
+ // Find the current context.
+ Context? context;
+ TreeNode contextOwner = node;
+ do {
+ contextOwner = contextOwner.parent!;
+ context = closures.contexts[contextOwner];
+ } while (
+ contextOwner.parent != null && (context == null || context.isEmpty));
+
+ // Store context.
+ if (context != null) {
+ assert(!context.isEmpty);
+ b.local_get(suspendStateLocal);
+ b.local_get(context.currentLocal);
+ b.struct_set(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateContext);
+ }
+
+ // Set state target to label after await.
+ final StateTarget after = afterTargets[node.parent]!;
+ b.local_get(suspendStateLocal);
+ b.i32_const(after.index);
+ b.struct_set(
+ asyncSuspendStateInfo.struct, FieldIndex.asyncSuspendStateTargetIndex);
+
+ b.local_get(suspendStateLocal);
+ wrap(node.operand, translator.topInfo.nullableType);
+ b.call(translator.functions.getFunction(translator.awaitHelper.reference));
+ b.return_();
+
+ // Generate resume label
+ _emitTargetLabel(after);
+
+ _restoreContextsAndThis(context);
+
+ // Handle exceptions
+ final exceptionBlock = b.block();
+ b.local_get(pendingExceptionLocal);
+ b.br_on_null(exceptionBlock);
+
+ exceptionHandlers.forEachFinalizer((finalizer, _last) {
+ finalizer.setContinuationRethrow(() {
+ b.local_get(pendingExceptionLocal);
+ b.ref_as_non_null();
+ }, () => b.local_get(pendingStackTraceLocal));
+ });
+
+ b.local_get(pendingStackTraceLocal);
+ b.ref_as_non_null();
+
+ b.throw_(translator.exceptionTag);
+ b.end(); // exceptionBlock
+
+ _setVariable(awaitValueVar, () {
+ b.local_get(awaitValueLocal);
+ translator.convertType(
+ function, awaitValueLocal.type, translateType(awaitValueVar.type));
+ });
+ }
+
+ void _setVariable(VariableDeclaration variable, void pushValue()) {
+ final w.Local? local = locals[variable];
+ final Capture? capture = closures.captures[variable];
+ if (capture != null) {
+ assert(capture.written);
+ b.local_get(capture.context.currentLocal);
+ pushValue();
+ b.struct_set(capture.context.struct, capture.fieldIndex);
+ } else {
+ if (local == null) {
+ throw "Write of undefined variable ${variable}";
+ }
+ pushValue();
+ b.local_set(local);
+ }
+ }
+
+ void _getVariable(VariableDeclaration variable) {
+ final w.Local? local = locals[variable];
+ final Capture? capture = closures.captures[variable];
+ if (capture != null) {
+ if (!capture.written && local != null) {
+ b.local_get(local);
+ } else {
+ b.local_get(capture.context.currentLocal);
+ b.struct_get(capture.context.struct, capture.fieldIndex);
+ }
+ } else {
+ if (local == null) {
+ throw "Write of undefined variable ${variable}";
+ }
+ b.local_get(local);
+ }
+ }
+
+ void _getCurrentException() {
+ b.local_get(suspendStateLocal);
+ b.struct_get(asyncSuspendStateInfo.struct,
+ FieldIndex.asyncSuspendStateCurrentException);
+ }
+
+ void _setCurrentException(void Function() emitValue) {
+ b.local_get(suspendStateLocal);
+ emitValue();
+ b.struct_set(asyncSuspendStateInfo.struct,
+ FieldIndex.asyncSuspendStateCurrentException);
+ }
+
+ void _getCurrentExceptionStackTrace() {
+ b.local_get(suspendStateLocal);
+ b.struct_get(asyncSuspendStateInfo.struct,
+ FieldIndex.asyncSuspendStateCurrentExceptionStackTrace);
+ }
+
+ void _setCurrentExceptionStackTrace(void Function() emitValue) {
+ b.local_get(suspendStateLocal);
+ emitValue();
+ b.struct_set(asyncSuspendStateInfo.struct,
+ FieldIndex.asyncSuspendStateCurrentExceptionStackTrace);
+ }
+}
diff --git a/pkg/dart2wasm/lib/await_transformer.dart b/pkg/dart2wasm/lib/await_transformer.dart
new file mode 100644
index 0000000..f0ebf61
--- /dev/null
+++ b/pkg/dart2wasm/lib/await_transformer.dart
@@ -0,0 +1,1277 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dart2wasm/async.dart' as asyncCodeGen;
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/type_environment.dart';
+
+/// This pass lifts `await` expressions to the top-level. After the pass, all
+/// `await` expressions will have the form:
+///
+/// final $temp = await <simple expr>;
+///
+/// where `<simple expr>` is an expression without `await`.
+///
+/// `await`s in block expressions are also lifted to the statement level.
+///
+/// The idea is that after this pass every `await` will have a simple
+/// continuation of "assign the awaited value to the variable, continue with
+/// the next statement". This allows simple code generation for async inner
+/// functions.
+///
+/// The implementation is mostly copied from the old VM async/await transformer
+/// with some changes. The old pass was removed in commit 94c120a.
+void transformLibraries(
+ List<Library> libraries, ClassHierarchy hierarchy, CoreTypes coreTypes) {
+ final typeEnv = TypeEnvironment(coreTypes, hierarchy);
+
+ var rewriter =
+ _AwaitTransformer(StatefulStaticTypeContext.stacked(typeEnv), coreTypes);
+
+ for (var library in libraries) {
+ rewriter.transform(library);
+ }
+}
+
+class _AwaitTransformer extends Transformer {
+ final StatefulStaticTypeContext staticTypeContext;
+
+ final CoreTypes coreTypes;
+
+ List<Statement> statements = <Statement>[];
+
+ late final _ExpressionTransformer expressionTransformer;
+
+ _AwaitTransformer(this.staticTypeContext, this.coreTypes) {
+ expressionTransformer =
+ _ExpressionTransformer(this, staticTypeContext, coreTypes);
+ }
+
+ @override
+ TreeNode visitField(Field node) {
+ staticTypeContext.enterMember(node);
+ super.visitField(node);
+ staticTypeContext.leaveMember(node);
+ return node;
+ }
+
+ @override
+ TreeNode visitConstructor(Constructor node) {
+ staticTypeContext.enterMember(node);
+ final result = super.visitConstructor(node);
+ staticTypeContext.leaveMember(node);
+ return result;
+ }
+
+ @override
+ TreeNode visitProcedure(Procedure node) {
+ staticTypeContext.enterMember(node);
+ final result = node.isAbstract ? node : super.visitProcedure(node);
+ staticTypeContext.leaveMember(node);
+ return result;
+ }
+
+ @override
+ TreeNode visitFunctionNode(FunctionNode node) {
+ final Statement? body = node.body;
+ if (body != null) {
+ final transformer = _AwaitTransformer(staticTypeContext, coreTypes);
+ Statement newBody = transformer.transform(body);
+
+ final List<Statement> newStatements = [
+ ...transformer.expressionTransformer.variables,
+ ...transformer.statements,
+ ];
+
+ if (newStatements.isNotEmpty) {
+ newBody = Block([
+ ...newStatements,
+ ...newBody is Block ? newBody.statements : [newBody]
+ ]);
+ }
+
+ node.body = newBody..parent = node;
+ }
+ return node;
+ }
+
+ @override
+ TreeNode visitAssertBlock(AssertBlock stmt) {
+ final savedStatements = statements;
+ statements = [];
+ for (final stmt in stmt.statements) {
+ statements.add(transform(stmt));
+ }
+ final newBlock = AssertBlock(statements);
+ statements = savedStatements;
+ return newBlock;
+ }
+
+ @override
+ TreeNode visitAssertStatement(AssertStatement stmt) {
+ final List<Statement> condEffects = [];
+ final cond = expressionTransformer.rewrite(stmt.condition, condEffects);
+ final msg = stmt.message;
+ if (msg == null) {
+ stmt.condition = cond..parent = stmt;
+ // If the translation of the condition produced a non-empty list of
+ // statements, ensure they are guarded by whether asserts are enabled.
+ return condEffects.isEmpty ? stmt : AssertBlock(condEffects..add(stmt));
+ }
+
+ // The translation depends on the translation of the message.
+ final List<Statement> msgEffects = [];
+ stmt.message = expressionTransformer.rewrite(msg, msgEffects)
+ ..parent = stmt;
+ if (condEffects.isEmpty) {
+ if (msgEffects.isEmpty) {
+ // The condition rewrote to ([], C) and the message rewrote to ([], M).
+ // The result is
+ //
+ // assert(C, M)
+ stmt.condition = cond..parent = stmt;
+ return stmt;
+ } else {
+ // The condition rewrote to ([], C) and the message rewrote to (S*, M)
+ // where S* is non-empty. The result is
+ //
+ // assert { if (C) {} else { S*; assert(false, M); }}
+ stmt.condition = BoolLiteral(false)..parent = stmt;
+ return AssertBlock([
+ IfStatement(cond, EmptyStatement(), Block(msgEffects..add(stmt)))
+ ]);
+ }
+ } else {
+ if (msgEffects.isEmpty) {
+ // The condition rewrote to (S*, C) where S* is non-empty and the
+ // message rewrote to ([], M). The result is
+ //
+ // assert { S*; assert(C, M); }
+ stmt.condition = cond..parent = stmt;
+ condEffects.add(stmt);
+ } else {
+ // The condition rewrote to (S0*, C) and the message rewrote to (S1*, M)
+ // where both S0* and S1* are non-empty. The result is
+ //
+ // assert { S0*; if (C) {} else { S1*; assert(false, M); }}
+ stmt.condition = BoolLiteral(false)..parent = stmt;
+ condEffects.add(
+ IfStatement(cond, EmptyStatement(), Block(msgEffects..add(stmt))));
+ }
+ return AssertBlock(condEffects);
+ }
+ }
+
+ @override
+ TreeNode visitBlock(Block stmt) {
+ final savedStatements = statements;
+ statements = [];
+ for (final statement in stmt.statements) {
+ final newStatement = transform(statement);
+ statements.add(newStatement);
+ }
+ final newBlock = Block(statements);
+ statements = savedStatements;
+ return newBlock;
+ }
+
+ @override
+ TreeNode visitBreakStatement(BreakStatement stmt) => stmt;
+
+ @override
+ TreeNode visitContinueSwitchStatement(ContinueSwitchStatement stmt) => stmt;
+
+ Statement visitDelimited(Statement stmt) {
+ final saved = statements;
+ statements = [];
+ statements.add(transform(stmt));
+ final result =
+ statements.length == 1 ? statements.first : Block(statements);
+ statements = saved;
+ return result;
+ }
+
+ @override
+ TreeNode visitDoStatement(DoStatement stmt) {
+ Statement body = visitDelimited(stmt.body); // block or single statement
+ final List<Statement> effects = [];
+ stmt.condition = expressionTransformer.rewrite(stmt.condition, effects)
+ ..parent = stmt;
+ if (effects.isNotEmpty) {
+ // The condition rewrote to a non-empty sequence of statements S* and
+ // value V. Add the statements to the end of the loop body.
+ final Block block = body is Block ? body : body = Block([body]);
+ for (final effect in effects) {
+ block.statements.add(effect);
+ effect.parent = body;
+ }
+ }
+ stmt.body = body..parent = stmt;
+ return stmt;
+ }
+
+ @override
+ TreeNode visitEmptyStatement(EmptyStatement stmt) => stmt;
+
+ @override
+ TreeNode visitExpressionStatement(ExpressionStatement stmt) {
+ stmt.expression = expressionTransformer.rewrite(stmt.expression, statements)
+ ..parent = stmt;
+ return stmt;
+ }
+
+ @override
+ TreeNode visitForInStatement(ForInStatement stmt) {
+ throw 'For statement at ${stmt.location}';
+ }
+
+ @override
+ TreeNode visitForStatement(ForStatement stmt) {
+ // Because of for-loop scoping and variable capture, it is tricky to deal
+ // with await in the loop's variable initializers or update expressions.
+ bool isSimple = true;
+ int length = stmt.variables.length;
+ List<List<Statement>> initEffects =
+ List<List<Statement>>.generate(length, (int i) {
+ VariableDeclaration decl = stmt.variables[i];
+ List<Statement> statements = <Statement>[];
+ if (decl.initializer != null) {
+ decl.initializer = expressionTransformer.rewrite(
+ decl.initializer!, statements)
+ ..parent = decl;
+ }
+ isSimple = isSimple && statements.isEmpty;
+ return statements;
+ });
+
+ length = stmt.updates.length;
+ List<List<Statement>> updateEffects =
+ List<List<Statement>>.generate(length, (int i) {
+ List<Statement> statements = <Statement>[];
+ stmt.updates[i] = expressionTransformer.rewrite(
+ stmt.updates[i], statements)
+ ..parent = stmt;
+ isSimple = isSimple && statements.isEmpty;
+ return statements;
+ });
+
+ Statement body = visitDelimited(stmt.body);
+ Expression? cond = stmt.condition;
+ List<Statement>? condEffects;
+ if (cond != null) {
+ condEffects = <Statement>[];
+ cond = expressionTransformer.rewrite(stmt.condition!, condEffects);
+ }
+
+ if (isSimple) {
+ // If the condition contains await, we use a translation like the one for
+ // while loops, but leaving the variable declarations and the update
+ // expressions in place.
+ if (condEffects == null || condEffects.isEmpty) {
+ if (cond != null) stmt.condition = cond..parent = stmt;
+ stmt.body = body..parent = stmt;
+ return stmt;
+ } else {
+ LabeledStatement labeled = LabeledStatement(stmt);
+ // No condition in a for loop is the same as true.
+ stmt.condition = null;
+ condEffects.add(IfStatement(cond!, body, BreakStatement(labeled)));
+ stmt.body = Block(condEffects)..parent = stmt;
+ return labeled;
+ }
+ }
+
+ // If the rewrite of the initializer or update expressions produces a
+ // non-empty sequence of statements then the loop is desugared. If the loop
+ // has the form:
+ //
+ // label: for (Type x = init; cond; update) body
+ //
+ // it is translated as if it were:
+ //
+ // {
+ // bool first = true;
+ // Type temp;
+ // label: while (true) {
+ // Type x;
+ // if (first) {
+ // first = false;
+ // x = init;
+ // } else {
+ // x = temp;
+ // update;
+ // }
+ // if (cond) {
+ // body;
+ // temp = x;
+ // } else {
+ // break;
+ // }
+ // }
+ // }
+
+ // Place the loop variable declarations at the beginning of the body
+ // statements and move their initializers to a guarded list of statements.
+ // Add assignments to the loop variables from the previous iterations temp
+ // variables before the updates.
+ //
+ // temps.first is the flag 'first'.
+ List<VariableDeclaration> temps = <VariableDeclaration>[
+ VariableDeclaration.forValue(BoolLiteral(true), isFinal: false)
+ ];
+ List<Statement> loopBody = <Statement>[];
+ List<Statement> initializers = <Statement>[
+ ExpressionStatement(VariableSet(temps.first, BoolLiteral(false)))
+ ];
+ List<Statement> updates = <Statement>[];
+ List<Statement> newBody = <Statement>[body];
+ for (int i = 0; i < stmt.variables.length; ++i) {
+ VariableDeclaration decl = stmt.variables[i];
+ temps
+ .add(VariableDeclaration(null, type: decl.type, isSynthesized: true));
+ loopBody.add(decl);
+ if (decl.initializer != null) {
+ initializers.addAll(initEffects[i]);
+ initializers
+ .add(ExpressionStatement(VariableSet(decl, decl.initializer!)));
+ decl.initializer = null;
+ }
+ updates
+ .add(ExpressionStatement(VariableSet(decl, VariableGet(temps.last))));
+ newBody
+ .add(ExpressionStatement(VariableSet(temps.last, VariableGet(decl))));
+ }
+ // Add the updates to their guarded list of statements.
+ for (int i = 0; i < stmt.updates.length; ++i) {
+ updates.addAll(updateEffects[i]);
+ updates.add(ExpressionStatement(stmt.updates[i]));
+ }
+ // Initializers or updates could be empty.
+ loopBody.add(IfStatement(
+ VariableGet(temps.first), Block(initializers), Block(updates)));
+
+ LabeledStatement labeled = LabeledStatement(null);
+ if (cond != null) {
+ loopBody.addAll(condEffects!);
+ } else {
+ cond = BoolLiteral(true);
+ }
+ loopBody.add(IfStatement(cond, Block(newBody), BreakStatement(labeled)));
+ labeled.body = WhileStatement(BoolLiteral(true), Block(loopBody))
+ ..parent = labeled;
+ return Block(<Statement>[]
+ ..addAll(temps)
+ ..add(labeled));
+ }
+
+ @override
+ TreeNode visitFunctionDeclaration(FunctionDeclaration stmt) {
+ stmt.function = transform(stmt.function)..parent = stmt;
+ return stmt;
+ }
+
+ @override
+ TreeNode visitIfStatement(IfStatement stmt) {
+ stmt.condition = expressionTransformer.rewrite(stmt.condition, statements)
+ ..parent = stmt;
+ stmt.then = visitDelimited(stmt.then)..parent = stmt;
+ if (stmt.otherwise != null) {
+ stmt.otherwise = visitDelimited(stmt.otherwise!)..parent = stmt;
+ }
+ return stmt;
+ }
+
+ @override
+ TreeNode visitLabeledStatement(LabeledStatement stmt) {
+ stmt.body = visitDelimited(stmt.body)..parent = stmt;
+ return stmt;
+ }
+
+ @override
+ TreeNode visitReturnStatement(ReturnStatement stmt) {
+ if (stmt.expression != null) {
+ stmt.expression = expressionTransformer.rewrite(
+ stmt.expression!, statements)
+ ..parent = stmt;
+ }
+
+ return stmt;
+ }
+
+ @override
+ TreeNode visitSwitchStatement(SwitchStatement stmt) {
+ stmt.expression = expressionTransformer.rewrite(stmt.expression, statements)
+ ..parent = stmt;
+ for (final switchCase in stmt.cases) {
+ // Expressions in switch cases cannot contain await so they do not need to
+ // be translated.
+ switchCase.body = visitDelimited(switchCase.body)..parent = switchCase;
+ }
+ return stmt;
+ }
+
+ @override
+ TreeNode visitTryCatch(TryCatch stmt) {
+ stmt.body = visitDelimited(stmt.body)..parent = stmt;
+ for (final catch_ in stmt.catches) {
+ // Create a fresh variable for the exception and stack trace: when a
+ // catch block has an `await` we use the catch block variables to restore
+ // the current exception after the `await`.
+ //
+ // TODO (omersa): We could mark [TreeNode]s with `await`s and only do this
+ if (catch_.exception == null) {
+ catch_.exception = VariableDeclaration(null,
+ type: InterfaceType(coreTypes.objectClass, Nullability.nonNullable),
+ isSynthesized: true)
+ ..parent = catch_;
+ }
+ if (catch_.stackTrace == null) {
+ catch_.stackTrace = VariableDeclaration(null,
+ type: InterfaceType(
+ coreTypes.stackTraceClass, Nullability.nonNullable),
+ isSynthesized: true)
+ ..parent = catch_;
+ }
+
+ var body = visitDelimited(catch_.body);
+
+ // Add uses to exception and stack trace vars so that they will be added
+ // to the context if the catch block has an await.
+ if (body is Block) {
+ body.statements.add(
+ ExpressionStatement(VariableGet(catch_.exception!))..parent = body);
+ body.statements.add(ExpressionStatement(VariableGet(catch_.stackTrace!))
+ ..parent = body);
+ } else {
+ body = Block([
+ body,
+ ExpressionStatement(VariableGet(catch_.exception!)),
+ ExpressionStatement(VariableGet(catch_.stackTrace!)),
+ ]);
+ }
+
+ catch_.body = body..parent = catch_;
+ }
+ return stmt;
+ }
+
+ @override
+ TreeNode visitTryFinally(TryFinally stmt) {
+ // TODO (omersa): Wrapped in a block to be able to get the variable
+ // declarations using `parent.statements[0]` etc. when compiling the node.
+ // Ideally we may want to create these variables not in kernel but during
+ // code generation.
+
+ // Variable for the finalizer block continuation.
+ final continuationVar = VariableDeclaration(null,
+ initializer: IntLiteral(asyncCodeGen.continuationFallthrough),
+ type: InterfaceType(coreTypes.intClass, Nullability.nonNullable),
+ isSynthesized: true);
+
+ // When the finalizer continuation is "rethrow", this stores the exception
+ // to rethrow.
+ final exceptionVar = VariableDeclaration(null,
+ type: InterfaceType(coreTypes.objectClass, Nullability.nonNullable),
+ isSynthesized: true);
+
+ // When the finalizer continuation is "rethrow", this stores the stack
+ // trace of the exception in [exceptionVar].
+ final stackTraceVar = VariableDeclaration(null,
+ type: InterfaceType(coreTypes.stackTraceClass, Nullability.nonNullable),
+ isSynthesized: true);
+
+ final body = visitDelimited(stmt.body);
+ var finalizer = visitDelimited(stmt.finalizer);
+
+ // Add a use of `continuationVar` in finally so that it will be added to
+ // the context
+ if (finalizer is Block) {
+ finalizer.statements.add(ExpressionStatement(VariableGet(continuationVar))
+ ..parent = finalizer);
+ finalizer.statements.add(
+ ExpressionStatement(VariableGet(exceptionVar))..parent = finalizer);
+ finalizer.statements.add(
+ ExpressionStatement(VariableGet(stackTraceVar))..parent = finalizer);
+ } else {
+ finalizer = Block([
+ finalizer,
+ ExpressionStatement(VariableGet(continuationVar)),
+ ExpressionStatement(VariableGet(exceptionVar)),
+ ExpressionStatement(VariableGet(stackTraceVar)),
+ ]);
+ }
+
+ return Block([
+ continuationVar,
+ exceptionVar,
+ stackTraceVar,
+ TryFinally(body, finalizer)
+ ]);
+ }
+
+ @override
+ TreeNode visitVariableDeclaration(VariableDeclaration stmt) {
+ final initializer = stmt.initializer;
+ if (initializer != null) {
+ stmt.initializer = expressionTransformer.rewrite(initializer, statements)
+ ..parent = stmt;
+ }
+ return stmt;
+ }
+
+ @override
+ TreeNode visitWhileStatement(WhileStatement stmt) {
+ final Statement body = visitDelimited(stmt.body);
+ final List<Statement> effects = [];
+ final Expression cond =
+ expressionTransformer.rewrite(stmt.condition, effects);
+ if (effects.isEmpty) {
+ stmt.condition = cond..parent = stmt;
+ stmt.body = body..parent = stmt;
+ return stmt;
+ } else {
+ // The condition rewrote to a non-empty sequence of statements S* and
+ // value V. Rewrite the loop to:
+ //
+ // L: while (true) {
+ // S*
+ // if (V) {
+ // [body]
+ // else {
+ // break L;
+ // }
+ // }
+ final LabeledStatement labeled = LabeledStatement(stmt);
+ stmt.condition = BoolLiteral(true)..parent = stmt;
+ effects.add(IfStatement(cond, body, BreakStatement(labeled)));
+ stmt.body = Block(effects)..parent = stmt;
+ return labeled;
+ }
+ }
+
+ @override
+ TreeNode visitYieldStatement(YieldStatement stmt) {
+ stmt.expression = expressionTransformer.rewrite(stmt.expression, statements)
+ ..parent = stmt;
+ return stmt;
+ }
+
+ @override
+ TreeNode defaultStatement(Statement stmt) =>
+ throw 'Unhandled statement: $stmt (${stmt.location})';
+
+ @override
+ TreeNode defaultExpression(Expression expr) {
+ // This visits initializer expressions, annotations etc.
+ final List<Statement> effects = [];
+ final Expression transformedExpr =
+ expressionTransformer.rewrite(expr, effects);
+ if (effects.isEmpty) {
+ return transformedExpr;
+ } else {
+ return BlockExpression(Block(effects), expr);
+ }
+ }
+}
+
+class _ExpressionTransformer extends Transformer {
+ /// Whether we have seen an await to the right in the expression tree.
+ ///
+ /// Subexpressions are visited right-to-left in the reverse of evaluation
+ /// order.
+ ///
+ /// On entry to an expression's visit method, [seenAwait] indicates whether a
+ /// sibling to the right contains an await. If so the expression will be
+ /// named in a temporary variable because it is potentially live across an
+ /// await.
+ ///
+ /// On exit from an expression's visit method, [seenAwait] indicates whether
+ /// the expression itself or a sibling to the right contains an await.
+ bool seenAwait = false;
+
+ /// The (reverse order) sequence of statements that have been emitted.
+ ///
+ /// Transformation of an expression produces a transformed expression and a
+ /// sequence of statements which are assignments to local variables, calls to
+ /// helper functions, and yield points. Only the yield points need to be a
+ /// statements, and they are statements so an implementation does not have to
+ /// handle unnamed expression intermediate live across yield points.
+ ///
+ /// The visit methods return the transformed expression and build a sequence
+ /// of statements by emitting statements into this list. This list is built
+ /// in reverse because children are visited right-to-left.
+ ///
+ /// If an expression should be named it is named before visiting its children
+ /// so the naming assignment appears in the list before all statements
+ /// implementing the translation of the children.
+ ///
+ /// Children that are conditionally evaluated, such as some parts of logical
+ /// and conditional expressions, must be delimited so that they do not emit
+ /// unguarded statements into [statements]. This is implemented by setting
+ /// [statements] to a fresh empty list before transforming those children.
+ List<Statement> statements = <Statement>[];
+
+ /// The number of currently live named intermediate values.
+ ///
+ /// This index is used to allocate names to temporary values. Because
+ /// children are visited right-to-left, names are assigned in reverse order
+ /// of index.
+ ///
+ /// When an assignment is emitted into [statements] to name an expression
+ /// before visiting its children, the index is not immediately reserved
+ /// because a child can freely use the same name as its parent. In practice,
+ /// this will be the rightmost named child.
+ ///
+ /// After visiting the children of a named expression, [nameIndex] is set to
+ /// indicate one more live value (the value of the expression) than before
+ /// visiting the expression.
+ ///
+ /// After visiting the children of an expression that is not named,
+ /// [nameIndex] may still account for names of subexpressions.
+ int nameIndex = 0;
+
+ /// Variables created for temporaries.
+ final List<VariableDeclaration> variables = <VariableDeclaration>[];
+
+ final _AwaitTransformer _statementTransformer;
+
+ final StatefulStaticTypeContext staticTypeContext;
+
+ final CoreTypes coreTypes;
+
+ _ExpressionTransformer(
+ this._statementTransformer, this.staticTypeContext, this.coreTypes);
+
+ // Helpers
+
+ /// Name an expression by emitting an assignment to a temporary variable.
+ Expression name(Expression expr) {
+ final DartType type = expr.getStaticType(staticTypeContext);
+ final VariableDeclaration temp = allocateTemporary(nameIndex, type);
+ statements.add(ExpressionStatement(VariableSet(temp, expr)));
+ return castVariableGet(temp, type);
+ }
+
+ VariableDeclaration allocateTemporary(int index,
+ [DartType type = const DynamicType()]) {
+ if (variables.length > index) {
+ // Re-using a temporary. Re-type it to dynamic if we detect reuse with
+ // different type.
+ if (variables[index].type != const DynamicType() &&
+ variables[index].type != type) {
+ variables[index].type = const DynamicType();
+ }
+ return variables[index];
+ }
+ for (var i = variables.length; i <= index; i++) {
+ variables.add(VariableDeclaration(":async_temporary_${i}", type: type));
+ }
+ return variables[index];
+ }
+
+ /// Casts a [VariableGet] with `as dynamic` if its type is not `dynamic`.
+ Expression castVariableGet(VariableDeclaration variable, DartType type) {
+ Expression expr = VariableGet(variable);
+ if (type != const DynamicType()) {
+ expr = AsExpression(expr, DynamicType());
+ }
+ return expr;
+ }
+
+ // Expressions
+
+ /// Rewrite a top-level expression (top-level wrt. a statement). This is the
+ /// entry-point from [_AwaitTransformer].
+ ///
+ /// Rewriting an expression produces a sequence of statements and an
+ /// expression. The sequence of statements are added to the given list. Pass
+ /// an empty list if the rewritten expression should be delimited from the
+ /// surrounding context.
+ //
+ // TODO (omersa): We should be able to maintain the state for temporaries
+ // (`nameIndex`, `variables`) in a separate class and create a new expression
+ // transformer every time we transform a top-level expression. Would that
+ // make the code clearer?
+ Expression rewrite(Expression expression, List<Statement> outer) {
+ assert(statements.isEmpty);
+ final saved = seenAwait;
+ seenAwait = false;
+ final Expression result = transform(expression);
+ outer.addAll(statements.reversed);
+ statements.clear();
+ seenAwait = seenAwait || saved;
+ return result;
+ }
+
+ @override
+ TreeNode defaultExpression(Expression expr) =>
+ throw 'Unhandled expression: $expr (${expr.location})';
+
+ @override
+ TreeNode visitFunctionExpression(FunctionExpression expr) {
+ expr.transformChildren(this);
+ return expr;
+ }
+
+ // Simple literals. These are pure expressions so they can be evaluated after
+ // an await to their right.
+ @override
+ TreeNode visitSymbolLiteral(SymbolLiteral expr) => expr;
+
+ @override
+ TreeNode visitTypeLiteral(TypeLiteral expr) => expr;
+
+ @override
+ TreeNode visitThisExpression(ThisExpression expr) => expr;
+
+ @override
+ TreeNode visitStringLiteral(StringLiteral expr) => expr;
+
+ @override
+ TreeNode visitIntLiteral(IntLiteral expr) => expr;
+
+ @override
+ TreeNode visitDoubleLiteral(DoubleLiteral expr) => expr;
+
+ @override
+ TreeNode visitBoolLiteral(BoolLiteral expr) => expr;
+
+ @override
+ TreeNode visitNullLiteral(NullLiteral expr) => expr;
+
+ @override
+ TreeNode visitConstantExpression(ConstantExpression expr) => expr;
+
+ @override
+ TreeNode visitCheckLibraryIsLoaded(CheckLibraryIsLoaded expr) => expr;
+
+ @override
+ TreeNode visitLoadLibrary(LoadLibrary expr) => expr;
+
+ /// Transform expressions with no child expressions.
+ Expression nullary(Expression expr) {
+ if (seenAwait) {
+ expr = name(expr);
+ ++nameIndex;
+ }
+ return expr;
+ }
+
+ @override
+ TreeNode visitSuperPropertyGet(SuperPropertyGet expr) => nullary(expr);
+
+ @override
+ TreeNode visitStaticGet(StaticGet expr) => nullary(expr);
+
+ @override
+ TreeNode visitStaticTearOff(StaticTearOff expr) => nullary(expr);
+
+ @override
+ TreeNode visitRethrow(Rethrow expr) => nullary(expr);
+
+ @override
+ TreeNode visitVariableGet(VariableGet expr) {
+ Expression result = expr;
+ // Getting a final or const variable is not an effect so it can be
+ // evaluated after an await to its right.
+ if (seenAwait && !expr.variable.isFinal && !expr.variable.isConst) {
+ result = name(expr);
+ ++nameIndex;
+ }
+ return result;
+ }
+
+ /// Transform an expression given an action to transform the children. For
+ /// this purposes of the await transformer the children should generally be
+ /// translated from right to left, in the reverse of evaluation order.
+ Expression transformTreeNode(Expression expr, void action(),
+ {bool alwaysName = false}) {
+ final bool shouldName = alwaysName || seenAwait;
+
+ // 1. If there is an await in a sibling to the right, emit an assignment to
+ // a temporary variable before transforming the children.
+ final Expression result = shouldName ? name(expr) : expr;
+
+ // 2. Remember the number of live temporaries before transforming the
+ // children.
+ final int index = nameIndex;
+
+ // 3. Transform the children. Initially they do not have an await in a
+ // sibling to their right.
+ seenAwait = false;
+ action();
+
+ // 4. If the expression was named then the variables used for children are
+ // no longer live but the variable used for the expression is. On the other
+ // hand, a sibling to the left (yet to be processed) cannot reuse any of
+ // the variables used here, as the assignments in the children (here) would
+ // overwrite assignments in the siblings to the left, possibly before the
+ // use of the overwritten values.
+ if (shouldName) {
+ if (index + 1 > nameIndex) {
+ nameIndex = index + 1;
+ }
+ seenAwait = true;
+ }
+
+ return result;
+ }
+
+ /// Transform expressions with one child expression.
+ Expression unary(Expression expr) {
+ return transformTreeNode(expr, () {
+ expr.transformChildren(this);
+ });
+ }
+
+ @override
+ TreeNode visitInvalidExpression(InvalidExpression expr) => unary(expr);
+
+ @override
+ TreeNode visitVariableSet(VariableSet expr) => unary(expr);
+
+ @override
+ TreeNode visitInstanceGet(InstanceGet expr) => unary(expr);
+
+ @override
+ TreeNode visitDynamicGet(DynamicGet expr) => unary(expr);
+
+ @override
+ TreeNode visitInstanceTearOff(InstanceTearOff expr) => unary(expr);
+
+ @override
+ TreeNode visitFunctionTearOff(FunctionTearOff expr) => unary(expr);
+
+ @override
+ TreeNode visitSuperPropertySet(SuperPropertySet expr) => unary(expr);
+
+ @override
+ TreeNode visitStaticSet(StaticSet expr) => unary(expr);
+
+ @override
+ TreeNode visitNot(Not expr) => unary(expr);
+
+ @override
+ TreeNode visitIsExpression(IsExpression expr) => unary(expr);
+
+ @override
+ TreeNode visitAsExpression(AsExpression expr) => unary(expr);
+
+ @override
+ TreeNode visitThrow(Throw expr) => unary(expr);
+
+ @override
+ TreeNode visitEqualsNull(EqualsNull expr) => unary(expr);
+
+ @override
+ TreeNode visitRecordIndexGet(RecordIndexGet expr) => unary(expr);
+
+ @override
+ TreeNode visitRecordNameGet(RecordNameGet expr) => unary(expr);
+
+ @override
+ TreeNode visitNullCheck(NullCheck expr) => unary(expr);
+
+ @override
+ TreeNode visitInstantiation(Instantiation expr) => unary(expr);
+
+ @override
+ TreeNode visitInstanceSet(InstanceSet expr) {
+ return transformTreeNode(expr, () {
+ expr.value = transform(expr.value)..parent = expr;
+ expr.receiver = transform(expr.receiver)..parent = expr;
+ });
+ }
+
+ @override
+ TreeNode visitDynamicSet(DynamicSet expr) {
+ return transformTreeNode(expr, () {
+ expr.value = transform(expr.value)..parent = expr;
+ expr.receiver = transform(expr.receiver)..parent = expr;
+ });
+ }
+
+ @override
+ TreeNode visitArguments(Arguments args) {
+ for (final named in args.named.reversed) {
+ named.value = transform(named.value)..parent = named;
+ }
+ final positional = args.positional;
+ for (var i = positional.length - 1; i >= 0; --i) {
+ positional[i] = transform(positional[i])..parent = args;
+ }
+ return args;
+ }
+
+ @override
+ TreeNode visitInstanceInvocation(InstanceInvocation expr) {
+ return transformTreeNode(expr, () {
+ visitArguments(expr.arguments);
+ expr.receiver = transform(expr.receiver)..parent = expr;
+ });
+ }
+
+ @override
+ TreeNode visitLocalFunctionInvocation(LocalFunctionInvocation expr) {
+ return transformTreeNode(expr, () {
+ visitArguments(expr.arguments);
+ });
+ }
+
+ @override
+ TreeNode visitDynamicInvocation(DynamicInvocation expr) {
+ return transformTreeNode(expr, () {
+ visitArguments(expr.arguments);
+ expr.receiver = transform(expr.receiver)..parent = expr;
+ });
+ }
+
+ @override
+ TreeNode visitFunctionInvocation(FunctionInvocation expr) {
+ return transformTreeNode(expr, () {
+ visitArguments(expr.arguments);
+ expr.receiver = transform(expr.receiver)..parent = expr;
+ });
+ }
+
+ @override
+ TreeNode visitEqualsCall(EqualsCall expr) {
+ return transformTreeNode(expr, () {
+ expr.right = transform(expr.right)..parent = expr;
+ expr.left = transform(expr.left)..parent = expr;
+ });
+ }
+
+ @override
+ TreeNode visitSuperMethodInvocation(SuperMethodInvocation expr) {
+ return transformTreeNode(expr, () {
+ visitArguments(expr.arguments);
+ });
+ }
+
+ @override
+ TreeNode visitStaticInvocation(StaticInvocation expr) {
+ return transformTreeNode(expr, () {
+ visitArguments(expr.arguments);
+ });
+ }
+
+ @override
+ TreeNode visitConstructorInvocation(ConstructorInvocation expr) {
+ return transformTreeNode(expr, () {
+ visitArguments(expr.arguments);
+ });
+ }
+
+ @override
+ TreeNode visitStringConcatenation(StringConcatenation expr) {
+ return transformTreeNode(expr, () {
+ final expressions = expr.expressions;
+ for (var i = expressions.length - 1; i >= 0; --i) {
+ expressions[i] = transform(expressions[i])..parent = expr;
+ }
+ });
+ }
+
+ @override
+ TreeNode visitListLiteral(ListLiteral expr) {
+ return transformTreeNode(expr, () {
+ final expressions = expr.expressions;
+ for (var i = expressions.length - 1; i >= 0; --i) {
+ expressions[i] = transform(expr.expressions[i])..parent = expr;
+ }
+ });
+ }
+
+ @override
+ TreeNode visitMapLiteral(MapLiteral expr) {
+ return transformTreeNode(expr, () {
+ for (final entry in expr.entries.reversed) {
+ entry.value = transform(entry.value)..parent = entry;
+ entry.key = transform(entry.key)..parent = entry;
+ }
+ });
+ }
+
+ @override
+ TreeNode visitSetLiteral(SetLiteral expr) {
+ return transformTreeNode(expr, () {
+ final expressions = expr.expressions;
+ for (var i = expressions.length - 1; i >= 0; --i) {
+ expressions[i] = transform(expr.expressions[i])..parent = expr;
+ }
+ });
+ }
+
+ @override
+ TreeNode visitRecordLiteral(RecordLiteral expr) {
+ return transformTreeNode(expr, () {
+ final named = expr.named;
+ for (var i = named.length - 1; i >= 0; --i) {
+ named[i] = transform(expr.named[i])..parent = expr;
+ }
+
+ final positional = expr.positional;
+ for (var i = positional.length - 1; i >= 0; --i) {
+ positional[i] = transform(expr.positional[i])..parent = expr;
+ }
+ });
+ }
+
+ // Expressions with control flow
+
+ /// Perform an action with a given list of statements so that it cannot emit
+ /// statements into the 'outer' list.
+ Expression delimit(Expression action(), List<Statement> inner) {
+ final outer = statements;
+ statements = inner;
+ final result = action();
+ statements = outer;
+ return result;
+ }
+
+ /// Make a [Block] from a reversed list of [Statement]s by reverting the
+ /// statements.
+ Block blockOf(List<Statement> reversedStatements) {
+ return Block(reversedStatements.reversed.toList());
+ }
+
+ @override
+ TreeNode visitLogicalExpression(LogicalExpression expr) {
+ final bool shouldName = seenAwait;
+
+ // Right is delimited because it is conditionally evaluated.
+ final List<Statement> rightStatements = [];
+ seenAwait = false;
+ expr.right = delimit(() => transform(expr.right), rightStatements)
+ ..parent = expr;
+ final bool rightAwait = seenAwait;
+
+ if (rightStatements.isEmpty) {
+ // Easy case: right did not emit any statements.
+ seenAwait = shouldName;
+ return transformTreeNode(expr, () {
+ expr.left = transform(expr.left)..parent = expr;
+ seenAwait = seenAwait || rightAwait;
+ });
+ }
+
+ // If right has emitted statements we will produce a temporary t and emit
+ // for && (there is an analogous case for ||):
+ //
+ // t = [left] == true;
+ // if (t) {
+ // t = [right] == true;
+ // }
+
+ // Recall that statements are emitted in reverse order, so first emit the
+ // if statement, then the assignment of [left] == true, and then translate
+ // left so any statements it emits occur after in the accumulated list
+ // (that is, so they occur before in the corresponding block).
+ final Block rightBody = blockOf(rightStatements);
+ final InterfaceType type = staticTypeContext.typeEnvironment.coreTypes
+ .boolRawType(staticTypeContext.nonNullable);
+ final VariableDeclaration result = allocateTemporary(nameIndex, type);
+ final objectEquals = coreTypes.objectEquals;
+ rightBody.addStatement(ExpressionStatement(VariableSet(
+ result,
+ EqualsCall(expr.right, BoolLiteral(true),
+ interfaceTarget: objectEquals,
+ functionType: objectEquals.getterType as FunctionType))));
+ final Statement then;
+ final Statement? otherwise;
+ if (expr.operatorEnum == LogicalExpressionOperator.AND) {
+ then = rightBody;
+ otherwise = null;
+ } else {
+ then = EmptyStatement();
+ otherwise = rightBody;
+ }
+ statements.add(IfStatement(castVariableGet(result, type), then, otherwise));
+
+ final test = EqualsCall(expr.left, BoolLiteral(true),
+ interfaceTarget: objectEquals,
+ functionType: objectEquals.getterType as FunctionType);
+ statements.add(ExpressionStatement(VariableSet(result, test)));
+
+ seenAwait = false;
+ test.left = transform(test.left)..parent = test;
+
+ nameIndex += 1;
+ seenAwait = seenAwait || rightAwait;
+ return castVariableGet(result, type);
+ }
+
+ @override
+ TreeNode visitConditionalExpression(ConditionalExpression expr) {
+ // Then and otherwise are delimited because they are conditionally
+ // evaluated.
+ final bool shouldName = seenAwait;
+
+ final int savedNameIndex = nameIndex;
+
+ final thenStatements = <Statement>[];
+ seenAwait = false;
+ expr.then = delimit(() => transform(expr.then), thenStatements)
+ ..parent = expr;
+ final thenAwait = seenAwait;
+
+ final thenNameIndex = nameIndex;
+ nameIndex = savedNameIndex;
+
+ final List<Statement> otherwiseStatements = [];
+ seenAwait = false;
+ expr.otherwise =
+ delimit(() => transform(expr.otherwise), otherwiseStatements)
+ ..parent = expr;
+ final otherwiseAwait = seenAwait;
+
+ // Only one side of this branch will get executed at a time, so just make
+ // sure we have enough temps for either, not both at the same time.
+ if (thenNameIndex > nameIndex) {
+ nameIndex = thenNameIndex;
+ }
+
+ if (thenStatements.isEmpty && otherwiseStatements.isEmpty) {
+ // Easy case: neither then nor otherwise emitted any statements.
+ seenAwait = shouldName;
+ return transformTreeNode(expr, () {
+ expr.condition = transform(expr.condition)..parent = expr;
+ seenAwait = seenAwait || thenAwait || otherwiseAwait;
+ });
+ }
+
+ // If `then` or `otherwise` has emitted statements we will produce a
+ // temporary t and emit:
+ //
+ // if ([condition]) {
+ // t = [left];
+ // } else {
+ // t = [right];
+ // }
+ final result = allocateTemporary(nameIndex, expr.staticType);
+ final thenBody = blockOf(thenStatements);
+ final otherwiseBody = blockOf(otherwiseStatements);
+ thenBody.addStatement(ExpressionStatement(VariableSet(result, expr.then)));
+ otherwiseBody
+ .addStatement(ExpressionStatement(VariableSet(result, expr.otherwise)));
+ final branch = IfStatement(expr.condition, thenBody, otherwiseBody);
+ statements.add(branch);
+
+ seenAwait = false;
+ branch.condition = transform(branch.condition)..parent = branch;
+
+ nameIndex += 1;
+ seenAwait = seenAwait || thenAwait || otherwiseAwait;
+ return castVariableGet(result, expr.staticType);
+ }
+
+ // Await expression
+
+ @override
+ TreeNode visitAwaitExpression(AwaitExpression expr) {
+ // TODO (omersa): Only name if the await is not already in assignment RHS
+ return transformTreeNode(expr, () {
+ expr.transformChildren(this);
+ }, alwaysName: true);
+ }
+
+ // Block expressions
+
+ @override
+ TreeNode visitBlockExpression(BlockExpression expr) {
+ return transformTreeNode(expr, () {
+ expr.value = transform(expr.value)..parent = expr;
+ final List<Statement> body = <Statement>[];
+ for (final Statement stmt in expr.body.statements.reversed) {
+ final Statement? translation = _rewriteStatement(stmt);
+ if (translation != null) {
+ body.add(translation);
+ }
+ }
+ expr.body = Block(body.reversed.toList())..parent = expr;
+ });
+ }
+
+ @override
+ TreeNode visitLet(Let expr) {
+ final body = transform(expr.body);
+ final VariableDeclaration variable = expr.variable;
+ if (seenAwait) {
+ // There is an await in the body of `let var x = initializer in body` or
+ // to its right. We will produce the sequence of statements:
+ //
+ // <initializer's statements>
+ // var x = <initializer's value>
+ // <body's statements>
+ //
+ // and return the body's value.
+ statements.add(variable);
+ var index = nameIndex;
+ seenAwait = false;
+ variable.initializer = transform(variable.initializer!)
+ ..parent = variable;
+ // Temporaries used in the initializer or the body are not live but the
+ // temporary used for the body is.
+ if (index + 1 > nameIndex) {
+ nameIndex = index + 1;
+ }
+ seenAwait = true;
+ return body;
+ } else {
+ // The body in `let x = initializer in body` did not contain an await.
+ // We can leave a let expression.
+ return transformTreeNode(expr, () {
+ // The body has already been translated.
+ expr.body = body..parent = expr;
+ variable.initializer = transform(variable.initializer!)
+ ..parent = variable;
+ });
+ }
+ }
+
+ @override
+ TreeNode visitFunctionNode(FunctionNode node) {
+ var nestedRewriter = _AwaitTransformer(staticTypeContext, coreTypes);
+ return nestedRewriter.transform(node);
+ }
+
+ /// This method translates a statement nested in an expression (e.g., in a
+ /// block expression). It produces a translated statement, a list of
+ /// statements which are side effects necessary for any await, and a flag
+ /// indicating whether there was an await in the statement or to its right.
+ /// The translated statement can be null in the case where there was already
+ /// an await to the right.
+ Statement? _rewriteStatement(Statement stmt) {
+ // The translation is accumulating two lists of statements, an inner list
+ // which is a reversed list of effects needed for the current expression
+ // and an outer list which represents the block containing the current
+ // statement. We need to preserve both of those from side effects.
+ final List<Statement> savedInner = statements;
+ final List<Statement> savedOuter = _statementTransformer.statements;
+ statements = <Statement>[];
+ _statementTransformer.statements = <Statement>[];
+ stmt = _statementTransformer.transform(stmt);
+
+ final List<Statement> results = _statementTransformer.statements;
+ results.add(stmt);
+
+ statements = savedInner;
+ _statementTransformer.statements = savedOuter;
+ if (!seenAwait && results.length == 1) {
+ return results.first;
+ }
+ statements.addAll(results.reversed);
+ return null;
+ }
+
+ @override
+ TreeNode defaultStatement(Statement stmt) {
+ throw UnsupportedError(
+ "Use _rewriteStatement to transform statement: ${stmt}");
+ }
+}
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
index 01df811..d52dd8d 100644
--- a/pkg/dart2wasm/lib/class_info.dart
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -17,6 +17,14 @@
/// [ClassInfo._addField] (for manually added fields) or by a line in
/// [FieldIndex.validate] (for fields declared in Dart code).
class FieldIndex {
+ static const asyncSuspendStateResume = 2;
+ static const asyncSuspendStateContext = 3;
+ static const asyncSuspendStateTargetIndex = 4;
+ static const asyncSuspendStateCompleter = 5;
+ static const asyncSuspendStateCurrentException = 6;
+ static const asyncSuspendStateCurrentExceptionStackTrace = 7;
+ static const asyncSuspendStateCurrentReturnValue = 8;
+
static const classId = 0;
static const boxValue = 1;
static const identityHash = 1;
@@ -61,6 +69,21 @@
"Unexpected field index for ${cls.name}.$name");
}
+ check(translator.asyncSuspendStateClass, "_resume",
+ FieldIndex.asyncSuspendStateResume);
+ check(translator.asyncSuspendStateClass, "_context",
+ FieldIndex.asyncSuspendStateContext);
+ check(translator.asyncSuspendStateClass, "_targetIndex",
+ FieldIndex.asyncSuspendStateTargetIndex);
+ check(translator.asyncSuspendStateClass, "_completer",
+ FieldIndex.asyncSuspendStateCompleter);
+ check(translator.asyncSuspendStateClass, "_currentException",
+ FieldIndex.asyncSuspendStateCurrentException);
+ check(translator.asyncSuspendStateClass, "_currentExceptionStackTrace",
+ FieldIndex.asyncSuspendStateCurrentExceptionStackTrace);
+ check(translator.asyncSuspendStateClass, "_currentReturnValue",
+ FieldIndex.asyncSuspendStateCurrentReturnValue);
+
check(translator.boxedBoolClass, "value", FieldIndex.boxValue);
check(translator.boxedIntClass, "value", FieldIndex.boxValue);
check(translator.boxedDoubleClass, "value", FieldIndex.boxValue);
diff --git a/pkg/dart2wasm/lib/closures.dart b/pkg/dart2wasm/lib/closures.dart
index c174424..f9e3e77 100644
--- a/pkg/dart2wasm/lib/closures.dart
+++ b/pkg/dart2wasm/lib/closures.dart
@@ -1078,9 +1078,9 @@
final Closures closures;
final Member member;
final Map<TreeNode, int> variableDepth = {};
- final List<bool> functionIsSyncStar = [false];
+ final List<bool> functionIsSyncStarOrAsync = [false];
- int get depth => functionIsSyncStar.length - 1;
+ int get depth => functionIsSyncStarOrAsync.length - 1;
CaptureFinder(this.closures, this.member);
@@ -1091,15 +1091,23 @@
@override
void visitFunctionNode(FunctionNode node) {
assert(depth == 0); // Nested function nodes are skipped by [_visitLambda].
- functionIsSyncStar[0] = node.asyncMarker == AsyncMarker.SyncStar;
+ functionIsSyncStarOrAsync[0] = node.asyncMarker == AsyncMarker.SyncStar ||
+ node.asyncMarker == AsyncMarker.Async;
node.visitChildren(this);
- functionIsSyncStar[0] = false;
+ functionIsSyncStarOrAsync[0] = false;
}
@override
void visitAssertStatement(AssertStatement node) {
if (translator.options.enableAsserts) {
- node.visitChildren(this);
+ super.visitAssertStatement(node);
+ }
+ }
+
+ @override
+ void visitAssertBlock(AssertBlock node) {
+ if (translator.options.enableAsserts) {
+ super.visitAssertBlock(node);
}
}
@@ -1124,9 +1132,9 @@
void _visitVariableUse(TreeNode variable) {
int declDepth = variableDepth[variable] ?? 0;
assert(declDepth <= depth);
- if (declDepth < depth || functionIsSyncStar[declDepth]) {
+ if (declDepth < depth || functionIsSyncStarOrAsync[declDepth]) {
final capture = closures.captures[variable] ??= Capture(variable);
- if (functionIsSyncStar[declDepth]) capture.written = true;
+ if (functionIsSyncStarOrAsync[declDepth]) capture.written = true;
} else if (variable is VariableDeclaration &&
variable.parent is FunctionDeclaration) {
closures.closurizedFunctions.add(variable.parent as FunctionDeclaration);
@@ -1146,7 +1154,7 @@
}
void _visitThis() {
- if (depth > 0 || functionIsSyncStar[0]) {
+ if (depth > 0 || functionIsSyncStarOrAsync[0]) {
closures.isThisCaptured = true;
}
}
@@ -1200,9 +1208,10 @@
m.addFunction(type, "$member closure at ${node.location}");
closures.lambdas[node] = Lambda(node, function);
- functionIsSyncStar.add(node.asyncMarker == AsyncMarker.SyncStar);
+ functionIsSyncStarOrAsync.add(node.asyncMarker == AsyncMarker.SyncStar ||
+ node.asyncMarker == AsyncMarker.Async);
node.visitChildren(this);
- functionIsSyncStar.removeLast();
+ functionIsSyncStarOrAsync.removeLast();
}
@override
@@ -1232,7 +1241,14 @@
@override
void visitAssertStatement(AssertStatement node) {
if (enableAsserts) {
- node.visitChildren(this);
+ super.visitAssertStatement(node);
+ }
+ }
+
+ @override
+ void visitAssertBlock(AssertBlock node) {
+ if (enableAsserts) {
+ super.visitAssertBlock(node);
}
}
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index fc3cb78..6ec69c9 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -4,6 +4,7 @@
import 'dart:collection' show LinkedHashMap;
+import 'package:dart2wasm/async.dart';
import 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/closures.dart';
import 'package:dart2wasm/dispatch_table.dart';
@@ -98,9 +99,17 @@
Reference reference) {
bool isSyncStar = functionNode?.asyncMarker == AsyncMarker.SyncStar &&
!reference.isTearOffReference;
- return isSyncStar
- ? SyncStarCodeGenerator(translator, function, reference)
- : CodeGenerator(translator, function, reference);
+ bool isAsync = functionNode?.asyncMarker == AsyncMarker.Async &&
+ !reference.isTearOffReference;
+ bool isTypeChecker = reference.isTypeCheckerReference;
+
+ if (!isTypeChecker && isSyncStar) {
+ return SyncStarCodeGenerator(translator, function, reference);
+ } else if (!isTypeChecker && isAsync) {
+ return AsyncCodeGenerator(translator, function, reference);
+ } else {
+ return CodeGenerator(translator, function, reference);
+ }
}
w.Module get m => translator.m;
@@ -209,19 +218,7 @@
}
assert(member.function!.asyncMarker != AsyncMarker.SyncStar);
-
- if (member.function!.asyncMarker == AsyncMarker.Async &&
- !reference.isAsyncInnerReference) {
- // Generate the async wrapper function, i.e. the function that gets
- // called when an async function is called. The inner function, containing
- // the body of the async function, is marked as an async inner reference
- // and is generated separately.
- Procedure procedure = member as Procedure;
- w.BaseFunction inner =
- translator.functions.getFunction(procedure.asyncInnerReference);
- int parameterOffset = _initializeThis(member);
- return generateAsyncWrapper(procedure.function, inner, parameterOffset);
- }
+ assert(member.function!.asyncMarker != AsyncMarker.Async);
translator.membersBeingGenerated.add(member);
generateBody(member);
@@ -295,94 +292,6 @@
b.end();
}
- /// Generate the async wrapper for an async function and its associated
- /// stub function.
- ///
- /// The async wrapper is the outer function that gets called when the async
- /// function is called. It bundles up the arguments to the function into an
- /// arguments struct along with a reference to the stub function.
- ///
- /// This struct is passed to the async helper, which allocates a new stack and
- /// calls the stub function on that stack.
- ///
- /// The stub function unwraps the arguments from the struct and calls the
- /// inner function, containing the implementation of the async function.
- void generateAsyncWrapper(
- FunctionNode functionNode, w.BaseFunction inner, int parameterOffset) {
- w.DefinedFunction stub =
- m.addFunction(translator.functions.asyncStubFunctionType);
- w.BaseFunction asyncHelper =
- translator.functions.getFunction(translator.asyncHelper.reference);
-
- w.Instructions stubBody = stub.body;
- w.Local stubArguments = stub.locals[0];
- w.Local stubStack = stub.locals[1];
-
- // Set up the parameter to local mapping, for type checks and in case a
- // type parameter is used in the return type.
- int paramIndex = parameterOffset;
- for (TypeParameter typeParam in functionNode.typeParameters) {
- typeLocals[typeParam] = paramLocals[paramIndex++];
- }
- for (VariableDeclaration param in functionNode.positionalParameters) {
- locals[param] = paramLocals[paramIndex++];
- }
- for (VariableDeclaration param in functionNode.namedParameters) {
- locals[param] = paramLocals[paramIndex++];
- }
-
- generateTypeChecks(functionNode.typeParameters, functionNode,
- ParameterInfo.fromLocalFunction(functionNode));
-
- // Push the type argument to the async helper, specifying the type argument
- // of the returned `Future`.
- DartType returnType = functionNode.returnType;
- DartType innerType = returnType is InterfaceType &&
- returnType.classNode == translator.coreTypes.futureClass
- ? returnType.typeArguments.single
- : const DynamicType();
- types.makeType(this, innerType);
-
- // Create struct for stub reference and arguments
- w.StructType baseStruct = translator.functions.asyncStubBaseStruct;
- w.StructType argsStruct = m.addStructType("${function.functionName} (args)",
- fields: baseStruct.fields, superType: baseStruct);
-
- // Push stub reference
- w.Global stubGlobal = translator.makeFunctionRef(stub);
- b.global_get(stubGlobal);
-
- // Transfer function arguments to inner
- w.Local argsLocal =
- stub.addLocal(w.RefType.def(argsStruct, nullable: false));
- stubBody.local_get(stubArguments);
- translator.convertType(stub, stubArguments.type, argsLocal.type);
- stubBody.local_set(argsLocal);
- int arity = function.type.inputs.length;
- for (int i = 0; i < arity; i++) {
- int fieldIndex = argsStruct.fields.length;
- w.ValueType type = function.locals[i].type;
- argsStruct.fields.add(w.FieldType(type, mutable: false));
- b.local_get(function.locals[i]);
- stubBody.local_get(argsLocal);
- stubBody.struct_get(argsStruct, fieldIndex);
- }
- b.struct_new(argsStruct);
-
- // Call async helper
- b.call(asyncHelper);
- translator.convertType(
- function, asyncHelper.type.outputs.single, outputs.single);
- b.end();
-
- // Call inner function from stub
- stubBody.local_get(stubStack);
- stubBody.call(inner);
- translator.convertType(
- stub, inner.type.outputs.single, stub.type.outputs.single);
- stubBody.end();
- }
-
void setupParametersAndContexts(Member member) {
ParameterInfo paramInfo = translator.paramInfoFor(reference);
int parameterOffset = _initializeThis(member);
@@ -533,15 +442,11 @@
if (member is Constructor) {
generateInitializerList(member);
}
- // Async function type checks are generated in the wrapper functions, in
- // [generateAsyncWrapper].
- if (member.function!.asyncMarker != AsyncMarker.Async) {
- final List<TypeParameter> typeParameters = member is Constructor
- ? member.enclosingClass.typeParameters
- : member.function!.typeParameters;
- generateTypeChecks(
- typeParameters, member.function!, translator.paramInfoFor(reference));
- }
+ final List<TypeParameter> typeParameters = member is Constructor
+ ? member.enclosingClass.typeParameters
+ : member.function!.typeParameters;
+ generateTypeChecks(
+ typeParameters, member.function!, translator.paramInfoFor(reference));
Statement? body = member.function!.body;
if (body != null) {
visitStatement(body);
@@ -574,10 +479,7 @@
// Initialize closure information from enclosing member.
this.closures = closures;
- if (lambda.functionNode.asyncMarker == AsyncMarker.Async &&
- lambda.function == function) {
- return generateAsyncLambdaWrapper(lambda);
- }
+ assert(lambda.functionNode.asyncMarker != AsyncMarker.Async);
setupLambdaParametersAndContexts(lambda);
@@ -588,15 +490,6 @@
return function;
}
- w.DefinedFunction generateAsyncLambdaWrapper(Lambda lambda) {
- _initializeContextLocals(lambda.functionNode);
- w.DefinedFunction inner =
- translator.functions.addAsyncInnerFunctionFor(function);
- generateAsyncWrapper(lambda.functionNode, inner, 1);
- return CodeGenerator(translator, inner, reference)
- .generateLambda(lambda, closures);
- }
-
/// Initialize locals containing `this` in constructors and instance members.
/// Returns the number of parameter locals taken up by the receiver parameter,
/// i.e. the parameter offset for the first type parameter (or the first
@@ -937,7 +830,13 @@
}
@override
- void visitAssertBlock(AssertBlock node) {}
+ void visitAssertBlock(AssertBlock node) {
+ if (!options.enableAsserts) return;
+
+ for (Statement statement in node.statements) {
+ visitStatement(statement);
+ }
+ }
@override
void visitTryCatch(TryCatch node) {
@@ -1369,81 +1268,25 @@
return;
}
- final switchExprClass =
- translator.classForType(dartTypeOf(node.expression));
-
- bool check<L extends Expression, C extends Constant>() =>
- node.cases.expand((c) => c.expressions).every((e) =>
- e is L ||
- e is NullLiteral ||
- (e is ConstantExpression &&
- (e.constant is C || e.constant is NullConstant) &&
- (translator.hierarchy.isSubtypeOf(
- translator.classForType(dartTypeOf(e)), switchExprClass))));
-
- // Identify kind of switch. One of `nullableType` or `nonNullableType` will
- // be the type for Wasm local that holds the switch value.
- late final w.ValueType nullableType;
- late final w.ValueType nonNullableType;
- late final void Function() compare;
- if (node.cases.every((c) =>
- c.expressions.isEmpty && c.isDefault ||
- c.expressions.every((e) =>
- e is NullLiteral ||
- e is ConstantExpression && e.constant is NullConstant))) {
- // default-only switch
- nonNullableType = w.RefType.eq(nullable: false);
- nullableType = w.RefType.eq(nullable: true);
- compare = () => throw "Comparison in default-only switch";
- } else if (check<BoolLiteral, BoolConstant>()) {
- // bool switch
- nonNullableType = w.NumType.i32;
- nullableType =
- translator.classInfo[translator.boxedBoolClass]!.nullableType;
- compare = () => b.i32_eq();
- } else if (check<IntLiteral, IntConstant>()) {
- // int switch
- nonNullableType = w.NumType.i64;
- nullableType =
- translator.classInfo[translator.boxedIntClass]!.nullableType;
- compare = () => b.i64_eq();
- } else if (check<StringLiteral, StringConstant>()) {
- // String switch
- nonNullableType =
- translator.classInfo[translator.stringBaseClass]!.nonNullableType;
- nullableType = nonNullableType.withNullability(true);
- compare = () => call(translator.stringEquals.reference);
- } else {
- // Object switch
- nonNullableType = translator.topInfo.nonNullableType;
- nullableType = translator.topInfo.nullableType;
- compare = () => b.call(translator.functions
- .getFunction(translator.coreTypes.identicalProcedure.reference));
- }
+ final switchInfo = SwitchInfo(this, node);
bool isNullable = dartTypeOf(node.expression).isPotentiallyNullable;
// When the type is nullable we use two variables: one for the nullable
// value, one after the null check, with non-nullable type.
- w.Local switchValueNonNullableLocal = addLocal(nonNullableType);
+ w.Local switchValueNonNullableLocal = addLocal(switchInfo.nonNullableType);
w.Local? switchValueNullableLocal =
- isNullable ? addLocal(nullableType) : null;
+ isNullable ? addLocal(switchInfo.nullableType) : null;
// Initialize switch value local
- wrap(node.expression, isNullable ? nullableType : nonNullableType);
+ wrap(node.expression,
+ isNullable ? switchInfo.nullableType : switchInfo.nonNullableType);
b.local_set(
isNullable ? switchValueNullableLocal! : switchValueNonNullableLocal);
// Special cases
- SwitchCase? defaultCase = node.cases
- .cast<SwitchCase?>()
- .firstWhere((c) => c!.isDefault, orElse: () => null);
-
- SwitchCase? nullCase = node.cases.cast<SwitchCase?>().firstWhere(
- (c) => c!.expressions.any((e) =>
- e is NullLiteral ||
- e is ConstantExpression && e.constant is NullConstant),
- orElse: () => null);
+ SwitchCase? defaultCase = switchInfo.defaultCase;
+ SwitchCase? nullCase = switchInfo.nullCase;
// Create `loop` for backward jumps
w.Label loopLabel = b.loop();
@@ -1472,7 +1315,9 @@
b.local_get(switchValueNullableLocal!);
b.br_on_null(nullLabel);
translator.convertType(
- function, nullableType.withNullability(false), nonNullableType);
+ function,
+ switchInfo.nullableType.withNullability(false),
+ switchInfo.nonNullableType);
b.local_set(switchValueNonNullableLocal);
}
@@ -1483,9 +1328,9 @@
exp is ConstantExpression && exp.constant is NullConstant) {
// Null already checked, skip
} else {
- wrap(exp, nonNullableType);
+ wrap(exp, switchInfo.nonNullableType);
b.local_get(switchValueNonNullableLocal);
- compare();
+ switchInfo.compare();
b.br_if(switchLabels[c]!);
}
}
@@ -1560,18 +1405,7 @@
@override
w.ValueType visitAwaitExpression(
AwaitExpression node, w.ValueType expectedType) {
- w.BaseFunction awaitHelper =
- translator.functions.getFunction(translator.awaitHelper.reference);
-
- // The stack for the suspension is the last parameter to the function.
- w.Local stack = function.locals[function.type.inputs.length - 1];
- assert(stack.type == translator.functions.asyncStackType);
-
- wrap(node.operand, translator.topInfo.nullableType);
- b.local_get(stack);
- b.call(awaitHelper);
-
- return translator.topInfo.nullableType;
+ throw 'Await expression in code generator: $node (${node.location})';
}
@override
@@ -3322,6 +3156,14 @@
// evaluator.
throw new UnsupportedError("CodeGenerator.visitIfCaseStatement");
}
+
+ void debugRuntimePrint(String s) {
+ final printFunction =
+ translator.functions.getFunction(translator.printToConsole.reference);
+ translator.constants.instantiateConstant(
+ function, b, StringConstant(s), printFunction.type.inputs[0]);
+ b.call(printFunction);
+ }
}
class TryBlockFinalizer {
@@ -3364,6 +3206,90 @@
: defaultLoopLabel = null;
}
+class SwitchInfo {
+ /// Non-nullable Wasm type of the `switch` expression. Used when the
+ /// expression is not nullable, and after the null check.
+ late final w.ValueType nullableType;
+
+ /// Nullable Wasm type of the `switch` expression. Only used when the
+ /// expression is nullable.
+ late final w.ValueType nonNullableType;
+
+ /// Generates code that compares value of a `case` expression with the
+ /// `switch` expression's value. Expects `case` and `switch` values to be on
+ /// stack, in that order.
+ late final void Function() compare;
+
+ /// The `default: ...` case, if exists.
+ late final SwitchCase? defaultCase;
+
+ /// The `null: ...` case, if exists.
+ late final SwitchCase? nullCase;
+
+ SwitchInfo(CodeGenerator codeGen, SwitchStatement node) {
+ final translator = codeGen.translator;
+
+ final switchExprClass =
+ translator.classForType(codeGen.dartTypeOf(node.expression));
+
+ bool check<L extends Expression, C extends Constant>() =>
+ node.cases.expand((c) => c.expressions).every((e) =>
+ e is L ||
+ e is NullLiteral ||
+ (e is ConstantExpression &&
+ (e.constant is C || e.constant is NullConstant) &&
+ (translator.hierarchy.isSubtypeOf(
+ translator.classForType(codeGen.dartTypeOf(e)),
+ switchExprClass))));
+
+ if (node.cases.every((c) =>
+ c.expressions.isEmpty && c.isDefault ||
+ c.expressions.every((e) =>
+ e is NullLiteral ||
+ e is ConstantExpression && e.constant is NullConstant))) {
+ // default-only switch
+ nonNullableType = w.RefType.eq(nullable: false);
+ nullableType = w.RefType.eq(nullable: true);
+ compare = () => throw "Comparison in default-only switch";
+ } else if (check<BoolLiteral, BoolConstant>()) {
+ // bool switch
+ nonNullableType = w.NumType.i32;
+ nullableType =
+ translator.classInfo[translator.boxedBoolClass]!.nullableType;
+ compare = () => codeGen.b.i32_eq();
+ } else if (check<IntLiteral, IntConstant>()) {
+ // int switch
+ nonNullableType = w.NumType.i64;
+ nullableType =
+ translator.classInfo[translator.boxedIntClass]!.nullableType;
+ compare = () => codeGen.b.i64_eq();
+ } else if (check<StringLiteral, StringConstant>()) {
+ // String switch
+ nonNullableType =
+ translator.classInfo[translator.stringBaseClass]!.nonNullableType;
+ nullableType = nonNullableType.withNullability(true);
+ compare = () => codeGen.call(translator.stringEquals.reference);
+ } else {
+ // Object switch
+ nonNullableType = translator.topInfo.nonNullableType;
+ nullableType = translator.topInfo.nullableType;
+ compare = () => codeGen.b.call(translator.functions
+ .getFunction(translator.coreTypes.identicalProcedure.reference));
+ }
+
+ // Special cases
+ defaultCase = node.cases
+ .cast<SwitchCase?>()
+ .firstWhere((c) => c!.isDefault, orElse: () => null);
+
+ nullCase = node.cases.cast<SwitchCase?>().firstWhere(
+ (c) => c!.expressions.any((e) =>
+ e is NullLiteral ||
+ e is ConstantExpression && e.constant is NullConstant),
+ orElse: () => null);
+ }
+}
+
enum _VirtualCallKind {
Get,
Set,
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
index a3b1971..5573188 100644
--- a/pkg/dart2wasm/lib/functions.dart
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -28,17 +28,6 @@
// allocation of that class is encountered
final Map<int, List<Reference>> _pendingAllocation = {};
- final w.ValueType asyncStackType = const w.RefType.extern(nullable: true);
-
- late final w.FunctionType asyncStubFunctionType = m.addFunctionType(
- [const w.RefType.struct(nullable: false), asyncStackType],
- [translator.topInfo.nullableType]);
-
- late final w.StructType asyncStubBaseStruct = m.addStructType("#AsyncStub",
- fields: [
- w.FieldType(w.RefType.def(asyncStubFunctionType, nullable: false))
- ]);
-
FunctionCollector(this.translator);
w.Module get m => translator.m;
@@ -167,27 +156,11 @@
"${target.asMember}");
}
- if (target.isAsyncInnerReference) {
- w.BaseFunction outer = getFunction(target.asProcedure.reference);
- return action(
- _asyncInnerFunctionTypeFor(outer), "${outer.functionName} inner");
- }
-
final ftype =
target.asMember.accept1(_FunctionTypeGenerator(translator), target);
return action(ftype, "${target.asMember}");
}
- w.DefinedFunction addAsyncInnerFunctionFor(w.BaseFunction outer) {
- w.FunctionType ftype = _asyncInnerFunctionTypeFor(outer);
- return m.addFunction(ftype, "${outer.functionName} inner");
- }
-
- w.FunctionType _asyncInnerFunctionTypeFor(w.BaseFunction outer) {
- return m.addFunctionType([...outer.type.inputs, asyncStackType],
- [translator.topInfo.nullableType]);
- }
-
void activateSelector(SelectorInfo selector) {
selector.targets.forEach((classId, target) {
if (!target.asMember.isAbstract) {
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index bdb7c94..122d3ee 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -1517,23 +1517,6 @@
}
}
- // _asyncBridge2
- if (member.enclosingLibrary.name == "dart.async" &&
- name == "_asyncBridge2") {
- w.Local args = paramLocals[0];
- w.Local stack = paramLocals[1];
- const int stubFieldIndex = 0;
-
- b.local_get(args);
- b.local_get(stack);
- b.local_get(args);
- b.ref_cast(w.RefType.def(translator.functions.asyncStubBaseStruct,
- nullable: false));
- b.struct_get(translator.functions.asyncStubBaseStruct, stubFieldIndex);
- b.call_ref(translator.functions.asyncStubFunctionType);
- return true;
- }
-
// int members
if (member.enclosingClass == translator.boxedIntClass &&
member.function.body == null) {
diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart
index 8887177..b5585e8 100644
--- a/pkg/dart2wasm/lib/js/runtime_blob.dart
+++ b/pkg/dart2wasm/lib/js/runtime_blob.dart
@@ -13,7 +13,6 @@
// the module will be instantiated.
// This function returns a promise to the instantiated module.
export const instantiate = async (modulePromise, importObjectPromise) => {
- let asyncBridge;
let dartInstance;
function stringFromDartString(string) {
const totalLength = dartInstance.exports.$stringLength(string);
@@ -101,11 +100,6 @@
...(await importObjectPromise),
});
- // Initialize async bridge.
- asyncBridge = new WebAssembly.Function(
- {parameters: ['anyref', 'anyref'], results: ['externref']},
- dartInstance.exports.$asyncBridge,
- {promising: 'first'});
return dartInstance;
}
diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart
index d31bc35..58f59f2 100644
--- a/pkg/dart2wasm/lib/kernel_nodes.dart
+++ b/pkg/dart2wasm/lib/kernel_nodes.dart
@@ -87,6 +87,22 @@
late final Class syncStarIteratorClass =
index.getClass("dart:core", "_SyncStarIterator");
+ // async support classes
+ late final Class asyncSuspendStateClass =
+ index.getClass("dart:async", "_AsyncSuspendState");
+ late final Procedure makeAsyncCompleter =
+ index.getTopLevelProcedure("dart:async", "_makeAsyncCompleter");
+ late final Field completerFuture =
+ index.getField("dart:async", "_Completer", "future");
+ late final Procedure completerComplete =
+ index.getProcedure("dart:async", "_AsyncCompleter", "complete");
+ late final Procedure completerCompleteError =
+ index.getProcedure("dart:async", "_Completer", "completeError");
+ late final Procedure awaitHelper =
+ index.getTopLevelProcedure("dart:async", "_awaitHelper");
+ late final Procedure newAsyncSuspendState =
+ index.getTopLevelProcedure("dart:async", "_newAsyncSuspendState");
+
// dart:ffi classes
late final Class ffiCompoundClass = index.getClass("dart:ffi", "_Compound");
late final Class ffiPointerClass = index.getClass("dart:ffi", "Pointer");
@@ -133,12 +149,6 @@
late final Procedure checkLibraryIsLoaded =
index.getTopLevelProcedure("dart:_internal", "checkLibraryIsLoaded");
- // dart:async procedures
- late final Procedure asyncHelper =
- index.getTopLevelProcedure("dart:async", "_asyncHelper");
- late final Procedure awaitHelper =
- index.getTopLevelProcedure("dart:async", "_awaitHelper");
-
// dart:collection procedures
late final Procedure mapFactory =
index.getProcedure("dart:collection", "LinkedHashMap", "_default");
@@ -249,4 +259,8 @@
index.getProcedure("dart:_wasm", "WasmFunction", "get:call");
late final Procedure wasmTableCallIndirect =
index.getProcedure("dart:_wasm", "WasmTable", "callIndirect");
+
+ // Debugging
+ late final Procedure printToConsole =
+ index.getTopLevelProcedure("dart:_internal", "printToConsole");
}
diff --git a/pkg/dart2wasm/lib/reference_extensions.dart b/pkg/dart2wasm/lib/reference_extensions.dart
index ef0a015..d8d7a72 100644
--- a/pkg/dart2wasm/lib/reference_extensions.dart
+++ b/pkg/dart2wasm/lib/reference_extensions.dart
@@ -32,23 +32,15 @@
// implementation for that procedure. This enables a Reference to refer to any
// implementation relating to a member, including its tear-off, which it can't
// do in plain kernel.
-// Also add an asyncInnerReference that refers to the inner, suspendable
-// body of an async function, which returns the value to be put into a future.
-// This can be called directly from other async functions if the result is
-// directly awaited.
// Use Expandos to avoid keeping the procedure alive.
final Expando<Reference> _tearOffReference = Expando();
-final Expando<Reference> _asyncInnerReference = Expando();
final Expando<Reference> _typeCheckerReference = Expando();
extension CustomReference on Member {
Reference get tearOffReference =>
_tearOffReference[this] ??= Reference()..node = this;
- Reference get asyncInnerReference =>
- _asyncInnerReference[this] ??= Reference()..node = this;
-
Reference get typeCheckerReference =>
_typeCheckerReference[this] ??= Reference()..node = this;
}
@@ -56,8 +48,6 @@
extension IsCustomReference on Reference {
bool get isTearOffReference => _tearOffReference[asMember] == this;
- bool get isAsyncInnerReference => _asyncInnerReference[asMember] == this;
-
bool get isTypeCheckerReference => _typeCheckerReference[asMember] == this;
}
diff --git a/pkg/dart2wasm/lib/sync_star.dart b/pkg/dart2wasm/lib/sync_star.dart
index 075bd9c..99bb541 100644
--- a/pkg/dart2wasm/lib/sync_star.dart
+++ b/pkg/dart2wasm/lib/sync_star.dart
@@ -21,18 +21,18 @@
/// initial entry target for a function body.
/// - [After]: After a statement, the resumption point of a [YieldStatement],
/// or the final state (iterator done) of a function body.
-enum _StateTargetPlacement { Inner, After }
+enum StateTargetPlacement { Inner, After }
/// Representation of target in the `sync*` control flow graph.
-class _StateTarget {
+class StateTarget {
int index;
TreeNode node;
- _StateTargetPlacement placement;
+ StateTargetPlacement placement;
- _StateTarget(this.index, this.node, this.placement);
+ StateTarget(this.index, this.node, this.placement);
String toString() {
- String place = placement == _StateTargetPlacement.Inner ? "in" : "after";
+ String place = placement == StateTargetPlacement.Inner ? "in" : "after";
return "$index: $place $node";
}
}
@@ -49,15 +49,15 @@
_YieldFinder(this.codeGen);
- List<_StateTarget> get targets => codeGen.targets;
+ List<StateTarget> get targets => codeGen.targets;
void find(FunctionNode function) {
// Initial state
- addTarget(function.body!, _StateTargetPlacement.Inner);
+ addTarget(function.body!, StateTargetPlacement.Inner);
assert(function.body is Block || function.body is ReturnStatement);
recurse(function.body!);
// Final state
- addTarget(function.body!, _StateTargetPlacement.After);
+ addTarget(function.body!, StateTargetPlacement.After);
}
/// Recurse into a statement and then remove any targets added by the
@@ -69,8 +69,8 @@
if (yieldCount == yieldCountIn) targets.length = targetsIn;
}
- void addTarget(TreeNode node, _StateTargetPlacement placement) {
- targets.add(_StateTarget(targets.length, node, placement));
+ void addTarget(TreeNode node, StateTargetPlacement placement) {
+ targets.add(StateTarget(targets.length, node, placement));
}
@override
@@ -89,71 +89,71 @@
@override
void visitDoStatement(DoStatement node) {
- addTarget(node, _StateTargetPlacement.Inner);
+ addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
}
@override
void visitForStatement(ForStatement node) {
- addTarget(node, _StateTargetPlacement.Inner);
+ addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
@override
void visitIfStatement(IfStatement node) {
recurse(node.then);
if (node.otherwise != null) {
- addTarget(node, _StateTargetPlacement.Inner);
+ addTarget(node, StateTargetPlacement.Inner);
recurse(node.otherwise!);
}
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
@override
void visitLabeledStatement(LabeledStatement node) {
recurse(node.body);
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
@override
void visitSwitchStatement(SwitchStatement node) {
for (SwitchCase c in node.cases) {
- addTarget(c, _StateTargetPlacement.Inner);
+ addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryCatch(TryCatch node) {
recurse(node.body);
for (Catch c in node.catches) {
- addTarget(c, _StateTargetPlacement.Inner);
+ addTarget(c, StateTargetPlacement.Inner);
recurse(c.body);
}
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
@override
void visitTryFinally(TryFinally node) {
recurse(node.body);
- addTarget(node, _StateTargetPlacement.Inner);
+ addTarget(node, StateTargetPlacement.Inner);
recurse(node.finalizer);
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
@override
void visitWhileStatement(WhileStatement node) {
- addTarget(node, _StateTargetPlacement.Inner);
+ addTarget(node, StateTargetPlacement.Inner);
recurse(node.body);
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
@override
void visitYieldStatement(YieldStatement node) {
yieldCount++;
- addTarget(node, _StateTargetPlacement.After);
+ addTarget(node, StateTargetPlacement.After);
}
}
@@ -176,11 +176,11 @@
SyncStarCodeGenerator(super.translator, super.function, super.reference);
/// Targets of the CFG, indexed by target index.
- final List<_StateTarget> targets = [];
+ final List<StateTarget> targets = [];
// Targets categorized by placement and indexed by node.
- final Map<TreeNode, _StateTarget> innerTargets = {};
- final Map<TreeNode, _StateTarget> afterTargets = {};
+ final Map<TreeNode, StateTarget> innerTargets = {};
+ final Map<TreeNode, StateTarget> afterTargets = {};
/// The loop around the switch.
late final w.Label masterLoop;
@@ -226,10 +226,10 @@
_YieldFinder(this).find(functionNode);
for (final target in targets) {
switch (target.placement) {
- case _StateTargetPlacement.Inner:
+ case StateTargetPlacement.Inner:
innerTargets[target.node] = target;
break;
- case _StateTargetPlacement.After:
+ case StateTargetPlacement.After:
afterTargets[target.node] = target;
break;
}
@@ -374,7 +374,7 @@
b.unreachable();
// Initial state, executed on first [moveNext] on the iterator.
- _StateTarget initialTarget = targets.first;
+ StateTarget initialTarget = targets.first;
emitTargetLabel(initialTarget);
// Clone context on first execution.
@@ -390,7 +390,7 @@
b.end();
}
- void emitTargetLabel(_StateTarget target) {
+ void emitTargetLabel(StateTarget target) {
currentTargetIndex++;
assert(target.index == currentTargetIndex);
b.end();
@@ -407,7 +407,7 @@
b.return_();
}
- void jumpToTarget(_StateTarget target,
+ void jumpToTarget(StateTarget target,
{Expression? condition, bool negated = false}) {
if (condition == null && negated) return;
if (target.index > currentTargetIndex) {
@@ -457,7 +457,7 @@
@override
void visitDoStatement(DoStatement node) {
- _StateTarget? inner = innerTargets[node];
+ StateTarget? inner = innerTargets[node];
if (inner == null) return super.visitDoStatement(node);
emitTargetLabel(inner);
@@ -468,9 +468,9 @@
@override
void visitForStatement(ForStatement node) {
- _StateTarget? inner = innerTargets[node];
+ StateTarget? inner = innerTargets[node];
if (inner == null) return super.visitForStatement(node);
- _StateTarget after = afterTargets[node]!;
+ StateTarget after = afterTargets[node]!;
allocateContext(node);
for (VariableDeclaration variable in node.variables) {
@@ -488,9 +488,9 @@
@override
void visitIfStatement(IfStatement node) {
- _StateTarget? after = afterTargets[node];
+ StateTarget? after = afterTargets[node];
if (after == null) return super.visitIfStatement(node);
- _StateTarget? inner = innerTargets[node];
+ StateTarget? inner = innerTargets[node];
jumpToTarget(inner ?? after, condition: node.condition, negated: true);
visitStatement(node.then);
@@ -504,7 +504,7 @@
@override
void visitLabeledStatement(LabeledStatement node) {
- _StateTarget? after = afterTargets[node];
+ StateTarget? after = afterTargets[node];
if (after == null) return super.visitLabeledStatement(node);
visitStatement(node.body);
@@ -513,7 +513,7 @@
@override
void visitBreakStatement(BreakStatement node) {
- _StateTarget? target = afterTargets[node.target];
+ StateTarget? target = afterTargets[node.target];
if (target == null) return super.visitBreakStatement(node);
jumpToTarget(target);
@@ -521,7 +521,7 @@
@override
void visitSwitchStatement(SwitchStatement node) {
- _StateTarget? after = afterTargets[node];
+ StateTarget? after = afterTargets[node];
if (after == null) return super.visitSwitchStatement(node);
// TODO(51342): Implement this.
@@ -530,7 +530,7 @@
@override
void visitTryCatch(TryCatch node) {
- _StateTarget? after = afterTargets[node];
+ StateTarget? after = afterTargets[node];
if (after == null) return super.visitTryCatch(node);
// TODO(51343): implement this.
@@ -539,7 +539,7 @@
@override
void visitTryFinally(TryFinally node) {
- _StateTarget? after = afterTargets[node];
+ StateTarget? after = afterTargets[node];
if (after == null) return super.visitTryFinally(node);
// TODO(51343): implement this.
@@ -548,9 +548,9 @@
@override
void visitWhileStatement(WhileStatement node) {
- _StateTarget? inner = innerTargets[node];
+ StateTarget? inner = innerTargets[node];
if (inner == null) return super.visitWhileStatement(node);
- _StateTarget after = afterTargets[node]!;
+ StateTarget after = afterTargets[node]!;
emitTargetLabel(inner);
jumpToTarget(after, condition: node.condition, negated: true);
@@ -562,7 +562,7 @@
@override
void visitYieldStatement(YieldStatement node) {
- _StateTarget after = afterTargets[node]!;
+ StateTarget after = afterTargets[node]!;
// Evaluate operand and store it to `_current` for `yield` or
// `_yieldStarIterable` for `yield*`.
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
index 06fbb2b..0fff96f 100644
--- a/pkg/dart2wasm/lib/target.dart
+++ b/pkg/dart2wasm/lib/target.dart
@@ -29,9 +29,10 @@
import 'package:front_end/src/api_prototype/const_conditional_simplifier.dart'
show ConstConditionalSimplifier;
+import 'package:dart2wasm/await_transformer.dart' as awaitTrans;
import 'package:dart2wasm/ffi_native_transformer.dart' as wasmFfiNativeTrans;
-import 'package:dart2wasm/transformers.dart' as wasmTrans;
import 'package:dart2wasm/records.dart' show RecordShape;
+import 'package:dart2wasm/transformers.dart' as wasmTrans;
class WasmTarget extends Target {
WasmTarget({this.constantBranchPruning = true});
@@ -222,6 +223,8 @@
wasmTrans.transformLibraries(
libraries, coreTypes, hierarchy, diagnosticReporter);
+
+ awaitTrans.transformLibraries(libraries, hierarchy, coreTypes);
}
@override
diff --git a/pkg/dart2wasm/tool/run_benchmark b/pkg/dart2wasm/tool/run_benchmark
index 538b0f4..6b85189 100755
--- a/pkg/dart2wasm/tool/run_benchmark
+++ b/pkg/dart2wasm/tool/run_benchmark
@@ -26,7 +26,7 @@
# Hardcoded to x64 Linux for now.
D8="$SDK_DIR/third_party/d8/linux/x64/d8"
-D8_OPTIONS="--experimental-wasm-gc --experimental-wasm-stack-switching --experimental-wasm-type-reflection"
+D8_OPTIONS="--experimental-wasm-gc --experimental-wasm-type-reflection"
RUN_WASM="$SDK_DIR/pkg/dart2wasm/bin/run_wasm.js"
diff --git a/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect
index dcae11c..883420d 100644
--- a/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect
@@ -15,20 +15,38 @@
}
}
static method asyncMethod(asy::Stream<core::int> stream) → dynamic async /* futureValueType= dynamic */ {
+ core::bool :async_temporary_0;
+ dynamic :async_temporary_1;
{
synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
synthesized core::bool #jumpSentinel = #C1;
- try {
- for (; #jumpSentinel = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}; ) {
- core::int i = #forIterator.{asy::_StreamIterator::current}{core::int};
- {
- core::print(i);
+ {
+ core::int #t1 = 0;
+ core::Object #t2;
+ core::StackTrace #t3;
+ try {
+ #L1:
+ for (; ; ) {
+ :async_temporary_0 = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>};
+ if(#jumpSentinel = :async_temporary_0 as dynamic) {
+ core::int i = #forIterator.{asy::_StreamIterator::current}{core::int};
+ {
+ core::print(i);
+ }
+ }
+ else
+ break #L1;
}
}
- }
- finally {
- if(#jumpSentinel)
- await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
+ finally {
+ if(#jumpSentinel) {
+ :async_temporary_1 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
+ :async_temporary_1;
+ }
+ #t1;
+ #t2;
+ #t3;
+ }
}
}
}
diff --git a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect
index 70ebc7b..d386450 100644
--- a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect
@@ -11,39 +11,65 @@
static method asyncMethod(asy::Stream<core::int> stream) → asy::Stream<core::int> {
synthesized asy::StreamController<core::Object?> #controller = asy::StreamController::•<core::Object?>();
synthesized () → asy::Future<void> #body = () → asy::Future<void> async /* futureValueType= void */ {
+ core::bool :async_temporary_0;
+ core::bool :async_temporary_1;
+ core::bool :async_temporary_2;
+ core::bool :async_temporary_3;
+ core::bool :async_temporary_4;
+ dynamic :async_temporary_5;
synthesized asy::Completer<core::bool> #completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
- await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ :async_temporary_0 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ :async_temporary_0 as dynamic;
{
{
#controller.{asy::StreamController::add}(1){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
- await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ :async_temporary_1 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ :async_temporary_1 as dynamic;
}
{
#controller.{asy::StreamController::add}(2){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
- await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ :async_temporary_2 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ :async_temporary_2 as dynamic;
}
{
synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
synthesized core::bool #jumpSentinel = #C1;
- try {
- for (; #jumpSentinel = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}; ) {
- synthesized core::int #awaitForVar = #forIterator.{asy::_StreamIterator::current}{core::int};
- {
- #controller.{asy::StreamController::add}(#awaitForVar){(core::Object?) → void};
- #completer = asy::Completer::•<core::bool>();
- #controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
- await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ {
+ core::int #t1 = 0;
+ core::Object #t2;
+ core::StackTrace #t3;
+ try {
+ #L1:
+ for (; ; ) {
+ :async_temporary_4 = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>};
+ if(#jumpSentinel = :async_temporary_4 as dynamic) {
+ synthesized core::int #awaitForVar = #forIterator.{asy::_StreamIterator::current}{core::int};
+ {
+ #controller.{asy::StreamController::add}(#awaitForVar){(core::Object?) → void};
+ #completer = asy::Completer::•<core::bool>();
+ #controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
+ :async_temporary_3 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
+ :async_temporary_3 as dynamic;
+ }
+ }
+ else
+ break #L1;
}
}
- }
- finally {
- if(#jumpSentinel)
- await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
+ finally {
+ if(#jumpSentinel) {
+ :async_temporary_5 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
+ :async_temporary_5;
+ }
+ #t1;
+ #t2;
+ #t3;
+ }
}
}
}
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index b49ab84..ab3f34f 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -593,7 +593,6 @@
final args = testFile.dartOptions;
return [
'--experimental-wasm-gc',
- '--experimental-wasm-stack-switching',
'--experimental-wasm-type-reflection',
'pkg/dart2wasm/bin/run_wasm.js',
'--',
diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
index a9aa541..66c05e5 100644
--- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart
+++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
@@ -260,6 +260,10 @@
node.statements.isEmpty || node.statements.last.accept(this);
@override
+ bool visitAssertBlock(AssertBlock node) =>
+ node.statements.isEmpty || node.statements.last.accept(this);
+
+ @override
bool visitEmptyStatement(EmptyStatement node) => true;
@override
diff --git a/sdk/bin/run_dart2wasm_d8 b/sdk/bin/run_dart2wasm_d8
index 05811ab..32bd6d6 100755
--- a/sdk/bin/run_dart2wasm_d8
+++ b/sdk/bin/run_dart2wasm_d8
@@ -36,6 +36,6 @@
fi
# Find the JS runtime based on the input wasm file.
-exec "$D8_EXEC" --experimental-wasm-gc --experimental-wasm-stack-switching \
+exec "$D8_EXEC" --experimental-wasm-gc \
--experimental-wasm-type-reflection "${EXTRA_D8_OPTIONS[@]}" \
"$SDK_DIR/pkg/dart2wasm/bin/run_wasm.js" -- "$(realpath -- "${1%.*}.mjs")" "$@"
diff --git a/sdk/lib/_internal/wasm/lib/async_patch.dart b/sdk/lib/_internal/wasm/lib/async_patch.dart
index 4635435..5372cd1 100644
--- a/sdk/lib/_internal/wasm/lib/async_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/async_patch.dart
@@ -1,137 +1,86 @@
-// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// Machinery for async and await.
-//
-// The implementation is based on two mechanisms in the JS Promise integration:
-//
-// The export wrapper: Allocates a new stack and calls the wrapped export on the
-// new stack, passing a suspender object as an extra first argument that
-// represents the new stack.
-//
-// The import wrapper: Takes a suspender object as an extra first argument and
-// calls the wrapped import. If the wrapped import returns a `Promise`, the
-// current stack is suspended, and the `Promise` is forwarded to the
-// corresponding call of the export wrapper, where execution resumes on the
-// original stack. When the `Promise` is resolved, execution resumes on the
-// suspended stack, with the call to the import wrapper returning the value the
-// `Promise` was resolved with.
-//
-// The call sequence when calling an async function is:
-//
-// Caller
-// -> Outer (function specific, generated by `generateAsyncWrapper`)
-// -> `_asyncHelper`
-// -> `_callAsyncBridge` (imported JS function)
-// -> `_asyncBridge` (via the Promise integration export wrapper)
-// -> `_asyncBridge2` (intrinsic function)
-// -> Stub (function specific, generated by `generateAsyncWrapper`)
-// -> Inner (contains implementation, generated from async inner reference)
-//
-// The call sequence on await is:
-//
-// Function containing await
-// -> `_awaitHelper`
-// -> `_futurePromise` (via the Promise integration import wrapper)
-// -> `new Promise`
-// -> `Promise` constructor callback
-// -> `_awaitCallback`
-// -> `Future.then`
-// `futurePromise` returns the newly created `Promise`, suspending the
-// current execution.
-//
-// When the `Future` completes:
-//
-// `Future.then` callback
-// -> `_callResolve` (imported JS function)
-// -> `Promise` resolve function
-// Resolving the `Promise` causes the suspended execution to resume.
-
-import 'dart:_internal' show patch, scheduleCallback, unsafeCastOpaque;
-import 'dart:_js_helper' show JS;
+import 'dart:_internal' show scheduleCallback, patch, _AsyncCompleter;
import 'dart:_wasm';
part 'timer_patch.dart';
-@pragma("wasm:entry-point")
-Future<T> _asyncHelper<T>(WasmStructRef args) {
- Completer<T> completer = Completer();
- _callAsyncBridge(args, completer);
- return completer.future;
-}
-
-void _callAsyncBridge(WasmStructRef args, Completer<Object?> completer) =>
- // This trampoline is needed because [asyncBridge] is a function wrapped
- // by `returnPromiseOnSuspend`, and the stack-switching functionality of
- // that wrapper is implemented as part of the export adapter.
- JS<void>(
- "(args, completer) => asyncBridge(args, completer)", args, completer);
-
-@pragma("wasm:export", "\$asyncBridge")
-WasmExternRef? _asyncBridge(
- WasmExternRef? stack, WasmStructRef args, Completer<Object?> completer) {
- try {
- Object? result = _asyncBridge2(args, stack);
- completer.complete(result);
- } catch (e, s) {
- completer.completeError(e, s);
- }
-}
-
-external Object? _asyncBridge2(WasmStructRef args, WasmExternRef? stack);
-
-class _FutureError {
- final Object exception;
- final StackTrace stackTrace;
-
- _FutureError(this.exception, this.stackTrace);
-}
+typedef _AsyncResumeFun = WasmFunction<
+ void Function(
+ _AsyncSuspendState,
+ // Value of the last `await`
+ Object?,
+ // If the last `await` throwed an error, the error value
+ Object?,
+ // If the last `await` throwed an error, the stack trace
+ StackTrace?)>;
@pragma("wasm:entry-point")
-Object? _awaitHelper(Object? operand, WasmExternRef? stack) {
- // Save the existing zone in a local, and restore('_leave') upon returning. We
- // ensure that the zone will be restored in the event of an exception by
- // restoring the original zone before we throw the exception.
- _Zone current = Zone._current;
+class _AsyncSuspendState {
+ // The inner function.
+ //
+ // Note: this function never throws. Any uncaught exceptions are passed to
+ // `_completer.completeError`.
+ @pragma("wasm:entry-point")
+ final _AsyncResumeFun _resume;
+
+ // Context containing the local variables of the function.
+ @pragma("wasm:entry-point")
+ final WasmStructRef? _context;
+
+ // CFG target index for the next resumption.
+ @pragma("wasm:entry-point")
+ WasmI32 _targetIndex;
+
+ // The completer. The inner function calls `_completer.complete` or
+ // `_completer.onError` on completion.
+ @pragma("wasm:entry-point")
+ final _AsyncCompleter _completer;
+
+ // When a called function throws this stores the thrown exception. Used when
+ // performing type tests in catch blocks.
+ @pragma("wasm:entry-point")
+ Object? _currentException;
+
+ // When a called function throws this stores the stack trace.
+ @pragma("wasm:entry-point")
+ StackTrace? _currentExceptionStackTrace;
+
+ // When running finalizers and the continuation is "return", the value to
+ // return after the last finalizer.
+ //
+ // Used in finalizer blocks.
+ @pragma("wasm:entry-point")
+ Object? _currentReturnValue;
+
+ @pragma("wasm:entry-point")
+ _AsyncSuspendState(this._resume, this._context, this._completer)
+ : _targetIndex = WasmI32.fromInt(0),
+ _currentException = null,
+ _currentExceptionStackTrace = null,
+ _currentReturnValue = null;
+}
+
+// Note: [_AsyncCompleter] is taken as an argument to be able to pass the type
+// parameter to [_AsyncCompleter] without having to add a type parameter to
+// [_AsyncSuspendState].
+//
+// TODO (omersa): I'm not sure if the type parameter is necessary?
+@pragma("wasm:entry-point")
+_AsyncSuspendState _newAsyncSuspendState(_AsyncResumeFun resume,
+ WasmStructRef? context, _AsyncCompleter completer) =>
+ _AsyncSuspendState(resume, context, completer);
+
+@pragma("wasm:entry-point")
+_AsyncCompleter<T> _makeAsyncCompleter<T>() => _AsyncCompleter<T>();
+
+@pragma("wasm:entry-point")
+void _awaitHelper(_AsyncSuspendState suspendState, Object? operand) {
if (operand is! Future) {
operand = Future.value(operand);
}
- Object? result = _futurePromise(stack, operand);
- Zone._leave(current);
- if (result is _FutureError) {
- // TODO(joshualitt): `result.stackTrace` is not currently the complete stack
- // trace. We might be able to stitch together `result.stackTrace` with
- // `StackTrace.current`, but we would need to be able to handle the case
- // where `result.stackTrace` is supplied by the user and must then be exact.
- // Alternatively, we may be able to fix this when we actually generate stack
- // traces.
- Error.throwWithStackTrace(result.exception, result.stackTrace);
- }
- return result;
-}
-
-Object? _futurePromise(WasmExternRef? stack, Future<Object?> future) =>
- JS<Object?>("""new WebAssembly.Function(
- {parameters: ['externref', 'anyref'], results: ['anyref']},
- function(future) {
- return new Promise(function (resolve, reject) {
- dartInstance.exports.\$awaitCallback(future, resolve);
- });
- },
- {suspending: 'first'})""", stack, future);
-
-@pragma("wasm:export", "\$awaitCallback")
-void _awaitCallback(Future<Object?> future, WasmExternRef? resolve) {
- future.then((value) {
- _callResolve(resolve, value);
+ operand.then((value) {
+ suspendState._resume.call(suspendState, value, null, null);
}, onError: (exception, stackTrace) {
- _callResolve(resolve, _FutureError(exception, stackTrace));
+ suspendState._resume.call(suspendState, null, exception, stackTrace);
});
}
-
-void _callResolve(WasmExternRef? resolve, Object? result) =>
- // This trampoline is needed because [resolve] is a JS function that
- // can't be called directly from Wasm.
- JS<void>("(resolve, result) => resolve(result)", resolve, result);
diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart
index b92c5b0..7ccc1ff 100644
--- a/sdk/lib/async/future_impl.dart
+++ b/sdk/lib/async/future_impl.dart
@@ -5,11 +5,13 @@
part of dart.async;
abstract class _Completer<T> implements Completer<T> {
+ @pragma("wasm:entry-point")
final _Future<T> future = new _Future<T>();
// Overridden by either a synchronous or asynchronous implementation.
void complete([FutureOr<T>? value]);
+ @pragma("wasm:entry-point")
void completeError(Object error, [StackTrace? stackTrace]) {
// TODO(40614): Remove once non-nullability is sound.
checkNotNullable(error, "error");
@@ -34,6 +36,7 @@
/// Completer which completes future asynchronously.
class _AsyncCompleter<T> extends _Completer<T> {
+ @pragma("wasm:entry-point")
void complete([FutureOr<T>? value]) {
if (!future._mayComplete) throw new StateError("Future already completed");
future._asyncComplete(value == null ? value as dynamic : value);