| Index: pkg/compiler/lib/src/tree_ir/optimization/pull_into_initializers.dart
|
| diff --git a/pkg/compiler/lib/src/tree_ir/optimization/pull_into_initializers.dart b/pkg/compiler/lib/src/tree_ir/optimization/pull_into_initializers.dart
|
| index 53348d32c9b6f94d1b7c6a714938194256aceec4..f0285307df6734d2db8711a687335bc2d85c3e85 100644
|
| --- a/pkg/compiler/lib/src/tree_ir/optimization/pull_into_initializers.dart
|
| +++ b/pkg/compiler/lib/src/tree_ir/optimization/pull_into_initializers.dart
|
| @@ -7,6 +7,15 @@ library tree_ir.optimization.pull_into_initializers;
|
| import 'optimization.dart' show Pass;
|
| import '../tree_ir_nodes.dart';
|
|
|
| +/// Where a variable has been assigned.
|
| +enum AssignArea {
|
| + /// The variable is only assigned in the initializer block.
|
| + Initializer,
|
| +
|
| + // The variable has at least one assignment outside the initializer block.
|
| + Anywhere,
|
| +}
|
| +
|
| /// Pulls assignment expressions to the top of the function body so they can be
|
| /// translated into declaration-site variable initializaters.
|
| ///
|
| @@ -48,30 +57,50 @@ import '../tree_ir_nodes.dart';
|
| /// [PullIntoInitializers] cannot pull `y` into an initializer because
|
| /// the impure expressions `foo()` and `bar()` would then be swapped.
|
| ///
|
| -class PullIntoInitializers extends ExpressionVisitor<Expression>
|
| +class PullIntoInitializers extends RecursiveTransformer
|
| implements Pass {
|
| String get passName => 'Pull into initializers';
|
|
|
| - Set<Variable> assignedVariables = new Set<Variable>();
|
| + /// Denotes where each variable is currently assigned.
|
| + ///
|
| + /// Variables without assignments are absent from the map.
|
| + Map<Variable, AssignArea> assignArea = <Variable, AssignArea>{};
|
|
|
| /// The fragment between [first] and [last] holds the statements
|
| /// we pulled into the initializer block.
|
| ///
|
| - /// The *initializer block* is a sequence of [ExpressionStatement]s with
|
| + /// The "initializer block" is a sequence of [ExpressionStatement]s with
|
| /// [Assign]s that we create in the beginning of the body, with the intent
|
| /// that code generation will convert them to variable initializers.
|
| ///
|
| /// The block is empty when both are `null`.
|
| Statement first, last;
|
|
|
| - /// True if an impure expression has been returned by visitExpression.
|
| + /// The number of impure expressions separating the current program point
|
| + /// from the initializer block.
|
| + ///
|
| + /// A pure expression is an expression that cannot throw, diverge, have side
|
| + /// effects, or depend on mutable state.
|
| + ///
|
| + /// As a special case, variable uses are also considered pure when their only
|
| + /// reaching definition is an assignment in the initializer block.
|
| + int impureCounter = 0;
|
| +
|
| + /// The number of assignments separating the current program point from the
|
| + /// initializer block. Note that these are also counted as impure expressions.
|
| ///
|
| - /// Expressions cannot be pulled into an initializer if this might reorder
|
| - /// impure expressions.
|
| + /// Assignments are given special treatment because hoisting an assignment
|
| + /// may change the reaching definitions of a variable use. The analysis may
|
| + /// already have considered such a use to be pure, and we must then ensure
|
| + /// that it remains pure.
|
| + int assignCounter = 0;
|
| +
|
| + /// The number of branch points separating the current program point from
|
| + /// the initializer block.
|
| ///
|
| - /// A visit method may not be called while this flag is set, meaning all
|
| - /// visitor methods must check the flag between visiting subexpressions.
|
| - bool seenImpure;
|
| + /// We do not pull expressions out of branches, not even pure ones, but
|
| + /// we sometimes want to traverse branches to check if they are pure.
|
| + int branchCounter = 0;
|
|
|
| /// Appends a statement to the initializer block.
|
| void append(Statement node) {
|
| @@ -83,54 +112,13 @@ class PullIntoInitializers extends ExpressionVisitor<Expression>
|
| }
|
| }
|
|
|
| - /// Pulls assignment expressions from [node] into the initializer block
|
| - /// by calling [append].
|
| - ///
|
| - /// Returns a transformed expression where the pulled assignments are
|
| - /// replaced by variable uses.
|
| - Expression rewriteExpression(Expression node) {
|
| - seenImpure = false;
|
| - return visitExpression(node);
|
| - }
|
| -
|
| void rewrite(FunctionDefinition node) {
|
| - Statement body = node.body;
|
| - assignedVariables.addAll(node.parameters);
|
| -
|
| - // [body] represents the first statement after the initializer block.
|
| - // Repeatedly pull assignment statements into the initializer block.
|
| - while (body is ExpressionStatement) {
|
| - ExpressionStatement stmt = body;
|
| - stmt.expression = rewriteExpression(stmt.expression);
|
| - if (stmt.expression is VariableUse) {
|
| - // The entire expression was pulled into an initializer.
|
| - // This can happen when the expression was an assignment that was
|
| - // pulled into the initializer block and replaced by a variable use.
|
| - // Discard the statement and try to pull in more initializers from
|
| - // the next statement.
|
| - destroyVariableUse(stmt.expression);
|
| - body = stmt.next;
|
| - } else {
|
| - // The whole expression could not be pulled into an initializer, so we
|
| - // have reached the end of the initializer block.
|
| - break;
|
| - }
|
| + for (Variable param in node.parameters) {
|
| + assignArea[param] = AssignArea.Initializer;
|
| }
|
| -
|
| - // [If] and [Return] statements terminate the initializer block, but the
|
| - // initial expression they contain may be pulled up into an initializer.
|
| - // It's ok to pull an assignment across a label so look for the first
|
| - // non-labeled statement and try to pull its initial subexpression.
|
| - Statement entryNode = unfoldLabeledStatements(body);
|
| - if (entryNode is If) {
|
| - entryNode.condition = rewriteExpression(entryNode.condition);
|
| - } else if (entryNode is Return) {
|
| - entryNode.value = rewriteExpression(entryNode.value);
|
| - }
|
| -
|
| + Statement body = visitStatement(node.body);
|
| append(body);
|
| - assert(first != null); // Because we just appended the body.
|
| -
|
| + assert(first != null);
|
| node.body = first;
|
| }
|
|
|
| @@ -138,191 +126,201 @@ class PullIntoInitializers extends ExpressionVisitor<Expression>
|
| --node.variable.readCount;
|
| }
|
|
|
| - Statement unfoldLabeledStatements(Statement node) {
|
| - while (node is LabeledStatement) {
|
| - node = (node as LabeledStatement).body;
|
| + Statement visitExpressionStatement(ExpressionStatement node) {
|
| + node.expression = visitExpression(node.expression);
|
| + if (node.expression is VariableUse) {
|
| + // The entire expression was pulled into an initializer.
|
| + // This can happen when the expression was an assignment that was
|
| + // pulled into the initializer block and replaced by a variable use.
|
| + // Discard the statement and try to pull in more initializers from
|
| + // the next statement.
|
| + destroyVariableUse(node.expression);
|
| + return visitStatement(node.next);
|
| }
|
| + node.next = visitStatement(node.next);
|
| + return node;
|
| + }
|
| +
|
| + Statement visitIf(If node) {
|
| + node.condition = visitExpression(node.condition);
|
| + // We could traverse the branches and pull out pure expressions, but
|
| + // some pure expressions might be too slow for this to pay off.
|
| + // A CPS transform should decide when things get hoisted out of branches.
|
| + return node;
|
| + }
|
| +
|
| + Statement visitLabeledStatement(LabeledStatement node) {
|
| + node.body = visitStatement(node.body);
|
| + // The 'next' statement might not always get reached, so do not try to
|
| + // pull expressions up from there.
|
| + return node;
|
| + }
|
| +
|
| + Statement visitWhileTrue(WhileTrue node) {
|
| + return node;
|
| + }
|
| +
|
| + Statement visitWhileCondition(WhileCondition node) {
|
| + return node;
|
| + }
|
| +
|
| + Statement visitTry(Try node) {
|
| return node;
|
| }
|
|
|
| Expression visitAssign(Assign node) {
|
| - assert(!seenImpure);
|
| + bool inImpureContext = impureCounter > 0;
|
| + bool inBranch = branchCounter > 0;
|
| +
|
| + // Remember the number of impure expression seen yet, so we can tell if
|
| + // there are any impure expressions on the right-hand side.
|
| + int impureBefore = impureCounter;
|
| + int assignmentsBefore = assignCounter;
|
| node.value = visitExpression(node.value);
|
| - if (!assignedVariables.add(node.variable)) {
|
| - // This is not the first assignment to the variable, so it cannot be
|
| - // pulled into an initializer.
|
| - // We have to leave the assignment here, and assignments are impure.
|
| - seenImpure = true;
|
| + bool rightHandSideIsImpure = (impureCounter > impureBefore);
|
| + bool rightHandSideHasAssign = (assignCounter > assignmentsBefore);
|
| +
|
| + bool alreadyAssigned = assignArea.containsKey(node.variable);
|
| +
|
| + // An impure right-hand side cannot be pulled out of impure context.
|
| + // Expressions should not be pulled out of branches.
|
| + // If this is not the first assignment, it cannot be hoisted.
|
| + // If the right-hand side contains an unhoistable assignment, this
|
| + // assignment cannot be hoisted either.
|
| + if (inImpureContext && rightHandSideIsImpure ||
|
| + inBranch ||
|
| + alreadyAssigned ||
|
| + rightHandSideHasAssign) {
|
| + assignArea[node.variable] = AssignArea.Anywhere;
|
| + ++impureCounter;
|
| + ++assignCounter;
|
| return node;
|
| - } else {
|
| - // Pull the assignment into an initializer.
|
| - // We will leave behind a variable use, which is pure, so we can
|
| - // disregard any impure expressions seen in the right-hand side.
|
| - seenImpure = false;
|
| - append(new ExpressionStatement(node, null));
|
| - return new VariableUse(node.variable);
|
| }
|
| - }
|
|
|
| - void rewriteList(List<Expression> list) {
|
| - for (int i = 0; i < list.length; i++) {
|
| - list[i] = visitExpression(list[i]);
|
| - if (seenImpure) return;
|
| - }
|
| + // Pull the assignment into the initializer. Any side-effects in the
|
| + // right-hand side will move into the initializer block, so reset the
|
| + // impure counter.
|
| + assignArea[node.variable] = AssignArea.Initializer;
|
| + impureCounter = impureBefore;
|
| + append(new ExpressionStatement(node, null));
|
| + return new VariableUse(node.variable);
|
| }
|
|
|
| - Expression visitInvokeStatic(InvokeStatic node) {
|
| - rewriteList(node.arguments);
|
| - seenImpure = true;
|
| + Expression visitVariableUse(VariableUse node) {
|
| + if (assignArea[node.variable] == AssignArea.Anywhere) {
|
| + // There is a reaching definition outside the initializer block.
|
| + ++impureCounter;
|
| + }
|
| return node;
|
| }
|
|
|
| - Expression visitInvokeMethod(InvokeMethod node) {
|
| - node.receiver = visitExpression(node.receiver);
|
| - if (seenImpure) return node;
|
| - rewriteList(node.arguments);
|
| - seenImpure = true;
|
| - return node;
|
| + void rewriteList(List<Expression> nodes) {
|
| + for (int i = 0; i < nodes.length; ++i) {
|
| + nodes[i] = visitExpression(nodes[i]);
|
| + }
|
| }
|
|
|
| - Expression visitInvokeMethodDirectly(InvokeMethodDirectly node) {
|
| + Expression visitInvokeMethod(InvokeMethod node) {
|
| node.receiver = visitExpression(node.receiver);
|
| - if (seenImpure) return node;
|
| + if (!node.receiverIsNotNull) {
|
| + // If the receiver is null, the method lookup throws.
|
| + ++impureCounter;
|
| + }
|
| rewriteList(node.arguments);
|
| - seenImpure = true;
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| - Expression visitInvokeConstructor(InvokeConstructor node) {
|
| - rewriteList(node.arguments);
|
| - seenImpure = true;
|
| + Expression visitInvokeStatic(InvokeStatic node) {
|
| + super.visitInvokeStatic(node);
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| - Expression visitConcatenateStrings(ConcatenateStrings node) {
|
| - rewriteList(node.arguments);
|
| - seenImpure = true;
|
| + Expression visitInvokeMethodDirectly(InvokeMethodDirectly node) {
|
| + super.visitInvokeMethodDirectly(node);
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| - Expression visitTypeExpression(TypeExpression node) {
|
| - rewriteList(node.arguments);
|
| + Expression visitInvokeConstructor(InvokeConstructor node) {
|
| + super.visitInvokeConstructor(node);
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| Expression visitConditional(Conditional node) {
|
| node.condition = visitExpression(node.condition);
|
| - if (seenImpure) return node;
|
| + // Visit the branches to detect impure subexpressions, but do not pull
|
| + // expressions out of the branch.
|
| + ++branchCounter;
|
| node.thenExpression = visitExpression(node.thenExpression);
|
| - if (seenImpure) return node;
|
| node.elseExpression = visitExpression(node.elseExpression);
|
| + --branchCounter;
|
| return node;
|
| }
|
|
|
| Expression visitLogicalOperator(LogicalOperator node) {
|
| node.left = visitExpression(node.left);
|
| - if (seenImpure) return node;
|
| + ++branchCounter;
|
| node.right = visitExpression(node.right);
|
| + --branchCounter;
|
| return node;
|
| }
|
|
|
| Expression visitLiteralList(LiteralList node) {
|
| - rewriteList(node.values);
|
| - if (node.type != null) seenImpure = true; // Type casts can throw.
|
| + super.visitLiteralList(node);
|
| + if (node.type != null) {
|
| + ++impureCounter; // Type casts can throw.
|
| + }
|
| return node;
|
| }
|
|
|
| Expression visitLiteralMap(LiteralMap node) {
|
| - for (LiteralMapEntry entry in node.entries) {
|
| - entry.key = visitExpression(entry.key);
|
| - if (seenImpure) return node;
|
| - entry.value = visitExpression(entry.value);
|
| - if (seenImpure) return node;
|
| + super.visitLiteralMap(node);
|
| + if (node.type != null) {
|
| + ++impureCounter; // Type casts can throw.
|
| }
|
| - if (node.type != null) seenImpure = true; // Type casts can throw.
|
| return node;
|
| }
|
|
|
| Expression visitTypeOperator(TypeOperator node) {
|
| - node.value = visitExpression(node.value);
|
| - if (seenImpure) return node;
|
| - rewriteList(node.typeArguments);
|
| - if (!node.isTypeTest) seenImpure = true; // Type cast can throw.
|
| - return node;
|
| - }
|
| -
|
| - void visitInnerFunction(FunctionDefinition node) {
|
| - new PullIntoInitializers().rewrite(node);
|
| - }
|
| -
|
| - Expression visitFunctionExpression(FunctionExpression node) {
|
| - visitInnerFunction(node.definition);
|
| + super.visitTypeOperator(node);
|
| + if (!node.isTypeTest) {
|
| + ++impureCounter; // Type casts can throw.
|
| + }
|
| return node;
|
| }
|
|
|
| Expression visitGetField(GetField node) {
|
| - node.object = visitExpression(node.object);
|
| - seenImpure = true;
|
| + super.visitGetField(node);
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| Expression visitSetField(SetField node) {
|
| - node.object = visitExpression(node.object);
|
| - if (seenImpure) return node;
|
| - node.value = visitExpression(node.value);
|
| - seenImpure = true;
|
| + super.visitSetField(node);
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| Expression visitGetStatic(GetStatic node) {
|
| - seenImpure = true;
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| Expression visitSetStatic(SetStatic node) {
|
| - node.value = visitExpression(node.value);
|
| - seenImpure = true;
|
| - return node;
|
| - }
|
| -
|
| - Expression visitCreateBox(CreateBox node) {
|
| - return node;
|
| - }
|
| -
|
| - Expression visitCreateInstance(CreateInstance node) {
|
| - rewriteList(node.arguments);
|
| - return node;
|
| - }
|
| -
|
| - Expression visitReifyRuntimeType(ReifyRuntimeType node) {
|
| - node.value = visitExpression(node.value);
|
| - return node;
|
| - }
|
| -
|
| - Expression visitReadTypeVariable(ReadTypeVariable node) {
|
| - node.target = visitExpression(node.target);
|
| - return node;
|
| - }
|
| -
|
| - Expression visitConstant(Constant node) {
|
| - return node;
|
| - }
|
| -
|
| - Expression visitThis(This node) {
|
| - return node;
|
| - }
|
| -
|
| - Expression visitNot(Not node) {
|
| - node.operand = visitExpression(node.operand);
|
| + super.visitSetStatic(node);
|
| + ++impureCounter;
|
| return node;
|
| }
|
|
|
| - Expression visitVariableUse(VariableUse node) {
|
| - return node;
|
| + void visitInnerFunction(FunctionDefinition node) {
|
| + new PullIntoInitializers().rewrite(node);
|
| }
|
|
|
| - Expression visitCreateInvocationMirror(CreateInvocationMirror node) {
|
| - rewriteList(node.arguments);
|
| + Expression visitFunctionExpression(FunctionExpression node) {
|
| + visitInnerFunction(node.definition);
|
| return node;
|
| }
|
|
|
|
|