| // 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:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/type_environment.dart'; |
| |
| import 'state_machine.dart' as stateMachineCodeGen; |
| |
| /// 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>[...temps, 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 |
| catch_.exception ??= VariableDeclaration(null, |
| type: InterfaceType(coreTypes.objectClass, Nullability.nonNullable), |
| isSynthesized: true) |
| ..parent = catch_; |
| 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(stateMachineCodeGen.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 visitFileUriExpression(FileUriExpression expr) => unary(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 Function() 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 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 Function() 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"); |
| } |
| } |