| // Copyright (c) 2014, 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. |
| |
| part of tree_ir.optimization; |
| |
| /** |
| * Performs the following transformations on the tree: |
| * - Assignment propagation |
| * - If-to-conditional conversion |
| * - Flatten nested ifs |
| * - Break inlining |
| * - Redirect breaks |
| * |
| * The above transformations all eliminate statements from the tree, and may |
| * introduce redexes of each other. |
| * |
| * |
| * ASSIGNMENT PROPAGATION: |
| * Single-use definitions are propagated to their use site when possible. |
| * For example: |
| * |
| * { v0 = foo(); return v0; } |
| * ==> |
| * return foo() |
| * |
| * After translating out of CPS, all intermediate values are bound by [Assign]. |
| * This transformation propagates such definitions to their uses when it is |
| * safe and profitable. Bindings are processed "on demand" when their uses are |
| * seen, but are only processed once to keep this transformation linear in |
| * the size of the tree. |
| * |
| * The transformation builds an environment containing [Assign] bindings that |
| * are in scope. These bindings have yet-untranslated definitions. When a use |
| * is encountered the transformation determines if it is safe and profitable |
| * to propagate the definition to its use. If so, it is removed from the |
| * environment and the definition is recursively processed (in the |
| * new environment at the use site) before being propagated. |
| * |
| * See [visitVariableUse] for the implementation of the heuristic for |
| * propagating a definition. |
| * |
| * |
| * IF-TO-CONDITIONAL CONVERSION: |
| * If-statement are converted to conditional expressions when possible. |
| * For example: |
| * |
| * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } |
| * ==> |
| * { v1 = v0 ? foo() : bar(); break L } |
| * |
| * This can lead to inlining of L, which in turn can lead to further propagation |
| * of the variable v1. |
| * |
| * See [visitIf]. |
| * |
| * |
| * FLATTEN NESTED IFS: |
| * An if inside an if is converted to an if with a logical operator. |
| * For example: |
| * |
| * if (E1) { if (E2) {S} else break L } else break L |
| * ==> |
| * if (E1 && E2) {S} else break L |
| * |
| * This may lead to inlining of L. |
| * |
| * |
| * BREAK INLINING: |
| * Single-use labels are inlined at [Break] statements. |
| * For example: |
| * |
| * L0: { v0 = foo(); break L0 }; return v0; |
| * ==> |
| * v0 = foo(); return v0; |
| * |
| * This can lead to propagation of v0. |
| * |
| * See [visitBreak] and [visitLabeledStatement]. |
| * |
| * |
| * REDIRECT BREAKS: |
| * Labeled statements whose next is a break become flattened and all breaks |
| * to their label are redirected. |
| * For example, where 'jump' is either break or continue: |
| * |
| * L0: {... break L0 ...}; jump L1 |
| * ==> |
| * {... jump L1 ...} |
| * |
| * This may trigger a flattening of nested ifs in case the eliminated label |
| * separated two ifs. |
| */ |
| class StatementRewriter extends Visitor<Statement, Expression> with PassMixin { |
| String get passName => 'Statement rewriter'; |
| |
| // The binding environment. The rightmost element of the list is the nearest |
| // available enclosing binding. |
| List<Assign> environment; |
| |
| /// Binding environment for variables that are assigned to effectively |
| /// constant expressions (see [isEffectivelyConstant]). |
| final Map<Variable, Expression> constantEnvironment; |
| |
| /// Substitution map for labels. Any break to a label L should be substituted |
| /// for a break to L' if L maps to L'. |
| Map<Label, Jump> labelRedirects = <Label, Jump>{}; |
| |
| /// Rewriter for methods. |
| StatementRewriter() : constantEnvironment = <Variable, Expression>{}; |
| |
| /// Rewriter for nested functions. |
| StatementRewriter.nested(StatementRewriter parent) |
| : constantEnvironment = parent.constantEnvironment; |
| |
| /// A set of labels that can be safely inlined at their use. |
| /// |
| /// The successor statements for labeled statements that have only one break |
| /// from them are normally rewritten inline at the site of the break. This |
| /// is not safe if the code would be moved inside the scope of an exception |
| /// handler (i.e., if the code would be moved into a try from outside it). |
| Set<Label> safeForInlining = new Set<Label>(); |
| |
| /// Returns the redirect target of [jump] or [jump] itself if it should not |
| /// be redirected. |
| Jump redirect(Jump jump) { |
| Jump newJump = labelRedirects[jump.target]; |
| return newJump != null ? newJump : jump; |
| } |
| |
| rewriteExecutableDefinition(ExecutableDefinition definition) { |
| inEmptyEnvironment(() { |
| definition.body = visitStatement(definition.body); |
| }); |
| } |
| |
| void rewriteConstructorDefinition(ConstructorDefinition definition) { |
| if (definition.isAbstract) return; |
| definition.initializers.forEach(visitExpression); |
| rewriteExecutableDefinition(definition); |
| } |
| |
| void inEmptyEnvironment(void action()) { |
| List<Assign> oldEnvironment = environment; |
| environment = <Assign>[]; |
| action(); |
| assert(environment.isEmpty); |
| environment = oldEnvironment; |
| } |
| |
| Expression visitFieldInitializer(FieldInitializer node) { |
| inEmptyEnvironment(() { |
| node.body = visitStatement(node.body); |
| }); |
| return node; |
| } |
| |
| Expression visitSuperInitializer(SuperInitializer node) { |
| inEmptyEnvironment(() { |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = visitStatement(node.arguments[i]); |
| assert(environment.isEmpty); |
| } |
| }); |
| return node; |
| } |
| |
| Expression visitExpression(Expression e) => e.processed ? e : e.accept(this); |
| |
| @override |
| Expression visitVariableUse(VariableUse node) { |
| // Propagate constant to use site. |
| Expression constant = constantEnvironment[node.variable]; |
| if (constant != null) { |
| --node.variable.readCount; |
| return constant; |
| } |
| |
| // Propagate a variable's definition to its use site if: |
| // 1. It has a single use, to avoid code growth and potential duplication |
| // of side effects, AND |
| // 2. It was the most recent expression evaluated so that we do not |
| // reorder expressions with side effects. |
| if (!environment.isEmpty && |
| environment.last.variable == node.variable && |
| node.variable.readCount == 1) { |
| --node.variable.readCount; |
| return visitExpression(environment.removeLast().value); |
| } |
| |
| // If the definition could not be propagated, leave the variable use. |
| return node; |
| } |
| |
| /// Returns true if [exp] has no side effects and has a constant value within |
| /// any given activation of the enclosing method. |
| bool isEffectivelyConstant(Expression exp) { |
| // TODO(asgerf): Can be made more aggressive e.g. by checking conditional |
| // expressions recursively. Determine if that is a valuable optimization |
| // and/or if it is better handled at the CPS level. |
| return exp is Constant || |
| exp is This || |
| exp is ReifyTypeVar || |
| exp is VariableUse && constantEnvironment.containsKey(exp.variable); |
| } |
| |
| Statement visitAssign(Assign node) { |
| if (isEffectivelyConstant(node.value) && |
| node.variable.writeCount == 1) { |
| // Handle constant assignments specially. |
| // They are always safe to propagate (though we should avoid duplication). |
| // Moreover, they should not prevent other expressions from propagating. |
| if (node.variable.readCount <= 1) { |
| // A single-use constant should always be propagted to its use site. |
| constantEnvironment[node.variable] = visitExpression(node.value); |
| --node.variable.writeCount; |
| return visitStatement(node.next); |
| } else { |
| // With more than one use, we cannot propagate the constant. |
| // Visit the following statement without polluting [environment] so |
| // that any preceding non-constant assignments might still propagate. |
| node.next = visitStatement(node.next); |
| node.value = visitExpression(node.value); |
| return node; |
| } |
| } else { |
| // Try to propagate assignment, and block previous assignment until this |
| // has propagated. |
| environment.add(node); |
| Statement next = visitStatement(node.next); |
| if (!environment.isEmpty && environment.last == node) { |
| // The definition could not be propagated. Residualize the let binding. |
| node.next = next; |
| environment.removeLast(); |
| node.value = visitExpression(node.value); |
| return node; |
| } |
| assert(!environment.contains(node)); |
| --node.variable.writeCount; // This assignment was removed. |
| return next; |
| } |
| } |
| |
| Expression visitInvokeStatic(InvokeStatic node) { |
| // Process arguments right-to-left, the opposite of evaluation order. |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = visitExpression(node.arguments[i]); |
| } |
| return node; |
| } |
| |
| Expression visitInvokeMethod(InvokeMethod node) { |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = visitExpression(node.arguments[i]); |
| } |
| node.receiver = visitExpression(node.receiver); |
| return node; |
| } |
| |
| Expression visitInvokeMethodDirectly(InvokeMethodDirectly node) { |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = visitExpression(node.arguments[i]); |
| } |
| node.receiver = visitExpression(node.receiver); |
| return node; |
| } |
| |
| Expression visitInvokeConstructor(InvokeConstructor node) { |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = visitExpression(node.arguments[i]); |
| } |
| return node; |
| } |
| |
| Expression visitConcatenateStrings(ConcatenateStrings node) { |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = visitExpression(node.arguments[i]); |
| } |
| return node; |
| } |
| |
| Expression visitConditional(Conditional node) { |
| node.condition = visitExpression(node.condition); |
| |
| inEmptyEnvironment(() { |
| node.thenExpression = visitExpression(node.thenExpression); |
| node.elseExpression = visitExpression(node.elseExpression); |
| }); |
| |
| return node; |
| } |
| |
| Expression visitLogicalOperator(LogicalOperator node) { |
| node.left = visitExpression(node.left); |
| |
| // Impure expressions may not propagate across the branch. |
| inEmptyEnvironment(() { |
| node.right = visitExpression(node.right); |
| }); |
| |
| return node; |
| } |
| |
| Expression visitNot(Not node) { |
| node.operand = visitExpression(node.operand); |
| return node; |
| } |
| |
| Expression visitFunctionExpression(FunctionExpression node) { |
| new StatementRewriter.nested(this).rewrite(node.definition); |
| return node; |
| } |
| |
| Statement visitFunctionDeclaration(FunctionDeclaration node) { |
| new StatementRewriter.nested(this).rewrite(node.definition); |
| node.next = visitStatement(node.next); |
| return node; |
| } |
| |
| Statement visitReturn(Return node) { |
| node.value = visitExpression(node.value); |
| return node; |
| } |
| |
| |
| Statement visitBreak(Break node) { |
| // Redirect through chain of breaks. |
| // Note that useCount was accounted for at visitLabeledStatement. |
| // Note redirect may return either a Break or Continue statement. |
| Jump jump = redirect(node); |
| if (jump is Break && |
| jump.target.useCount == 1 && |
| safeForInlining.contains(jump.target)) { |
| --jump.target.useCount; |
| return visitStatement(jump.target.binding.next); |
| } |
| return jump; |
| } |
| |
| Statement visitContinue(Continue node) { |
| return node; |
| } |
| |
| Statement visitLabeledStatement(LabeledStatement node) { |
| if (node.next is Jump) { |
| // Eliminate label if next is a break or continue statement |
| // Breaks to this label are redirected to the outer label. |
| // Note that breakCount for the two labels is updated proactively here |
| // so breaks can reliably tell if they should inline their target. |
| Jump next = node.next; |
| Jump newJump = redirect(next); |
| labelRedirects[node.label] = newJump; |
| newJump.target.useCount += node.label.useCount - 1; |
| node.label.useCount = 0; |
| Statement result = visitStatement(node.body); |
| labelRedirects.remove(node.label); // Save some space. |
| return result; |
| } |
| |
| safeForInlining.add(node.label); |
| node.body = visitStatement(node.body); |
| safeForInlining.remove(node.label); |
| |
| if (node.label.useCount == 0) { |
| // Eliminate the label if next was inlined at a break |
| return node.body; |
| } |
| |
| // Do not propagate assignments into the successor statements, since they |
| // may be overwritten by assignments in the body. |
| inEmptyEnvironment(() { |
| node.next = visitStatement(node.next); |
| }); |
| |
| return node; |
| } |
| |
| Statement visitIf(If node) { |
| node.condition = visitExpression(node.condition); |
| |
| // Do not propagate assignments into branches. Doing so will lead to code |
| // duplication. |
| // TODO(kmillikin): Rethink this. Propagating some assignments |
| // (e.g. variables) is benign. If they can occur here, they should |
| // be handled well. |
| inEmptyEnvironment(() { |
| node.thenStatement = visitStatement(node.thenStatement); |
| node.elseStatement = visitStatement(node.elseStatement); |
| }); |
| |
| tryCollapseIf(node); |
| |
| Statement reduced = combineStatementsWithSubexpressions( |
| node.thenStatement, |
| node.elseStatement, |
| (t,f) => new Conditional(node.condition, t, f)..processed = true); |
| if (reduced != null) { |
| if (reduced.next is Break) { |
| // In case the break can now be inlined. |
| reduced = visitStatement(reduced); |
| } |
| return reduced; |
| } |
| |
| return node; |
| } |
| |
| Statement visitWhileTrue(WhileTrue node) { |
| // Do not propagate assignments into loops. Doing so is not safe for |
| // variables modified in the loop (the initial value will be propagated). |
| inEmptyEnvironment(() { |
| node.body = visitStatement(node.body); |
| }); |
| return node; |
| } |
| |
| Statement visitWhileCondition(WhileCondition node) { |
| // Not introduced yet |
| throw "Unexpected WhileCondition in StatementRewriter"; |
| } |
| |
| Statement visitTry(Try node) { |
| inEmptyEnvironment(() { |
| Set<Label> saved = safeForInlining; |
| safeForInlining = new Set<Label>(); |
| node.tryBody = visitStatement(node.tryBody); |
| safeForInlining = saved; |
| node.catchBody = visitStatement(node.catchBody); |
| }); |
| return node; |
| } |
| |
| Expression visitConstant(Constant node) { |
| return node; |
| } |
| |
| Expression visitThis(This node) { |
| return node; |
| } |
| |
| Expression visitReifyTypeVar(ReifyTypeVar node) { |
| return node; |
| } |
| |
| Expression visitLiteralList(LiteralList node) { |
| // Process values right-to-left, the opposite of evaluation order. |
| for (int i = node.values.length - 1; i >= 0; --i) { |
| node.values[i] = visitExpression(node.values[i]); |
| } |
| return node; |
| } |
| |
| Expression visitLiteralMap(LiteralMap node) { |
| // Process arguments right-to-left, the opposite of evaluation order. |
| for (LiteralMapEntry entry in node.entries.reversed) { |
| entry.value = visitExpression(entry.value); |
| entry.key = visitExpression(entry.key); |
| } |
| return node; |
| } |
| |
| Expression visitTypeOperator(TypeOperator node) { |
| node.receiver = visitExpression(node.receiver); |
| return node; |
| } |
| |
| Statement visitExpressionStatement(ExpressionStatement node) { |
| node.expression = visitExpression(node.expression); |
| // Do not allow propagation of assignments past an expression evaluated |
| // for its side effects because it risks reordering side effects. |
| // TODO(kmillikin): Rethink this. Some propagation is benign, |
| // e.g. variables, or other pure values that are not destroyed by |
| // the expression statement. If they can occur here they should be |
| // handled well. |
| inEmptyEnvironment(() { |
| node.next = visitStatement(node.next); |
| }); |
| return node; |
| } |
| |
| Statement visitSetField(SetField node) { |
| node.next = visitStatement(node.next); |
| node.value = visitExpression(node.value); |
| node.object = visitExpression(node.object); |
| return node; |
| } |
| |
| Expression visitGetField(GetField node) { |
| node.object = visitExpression(node.object); |
| return node; |
| } |
| |
| Expression visitCreateBox(CreateBox node) { |
| return node; |
| } |
| |
| Expression visitCreateInstance(CreateInstance node) { |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = visitExpression(node.arguments[i]); |
| } |
| return node; |
| } |
| |
| Expression visitReifyRuntimeType(ReifyRuntimeType node) { |
| node.value = visitExpression(node.value); |
| return node; |
| } |
| |
| Expression visitReadTypeVariable(ReadTypeVariable node) { |
| node.target = visitExpression(node.target); |
| return node; |
| } |
| |
| /// If [s] and [t] are similar statements we extract their subexpressions |
| /// and returns a new statement of the same type using expressions combined |
| /// with the [combine] callback. For example: |
| /// |
| /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) |
| /// |
| /// If [combine] returns E1 then the unified statement is equivalent to [s], |
| /// and if [combine] returns E2 the unified statement is equivalence to [t]. |
| /// |
| /// It is guaranteed that no side effects occur between the beginning of the |
| /// statement and the position of the combined expression. |
| /// |
| /// Returns null if the statements are too different. |
| /// |
| /// If non-null is returned, the caller MUST discard [s] and [t] and use |
| /// the returned statement instead. |
| static Statement combineStatementsWithSubexpressions( |
| Statement s, |
| Statement t, |
| Expression combine(Expression s, Expression t)) { |
| if (s is Return && t is Return) { |
| return new Return(combine(s.value, t.value)); |
| } |
| if (s is Assign && t is Assign && s.variable == t.variable) { |
| Statement next = combineStatements(s.next, t.next); |
| if (next != null) { |
| // Destroy both original assignments to the variable. |
| --s.variable.writeCount; |
| --t.variable.writeCount; |
| // The Assign constructor will increment the reference count again. |
| return new Assign(s.variable, |
| combine(s.value, t.value), |
| next); |
| } |
| } |
| if (s is ExpressionStatement && t is ExpressionStatement) { |
| Statement next = combineStatements(s.next, t.next); |
| if (next != null) { |
| return new ExpressionStatement(combine(s.expression, t.expression), |
| next); |
| } |
| } |
| return null; |
| } |
| |
| /// Returns a statement equivalent to both [s] and [t], or null if [s] and |
| /// [t] are incompatible. |
| /// If non-null is returned, the caller MUST discard [s] and [t] and use |
| /// the returned statement instead. |
| /// If two breaks are combined, the label's break counter will be decremented. |
| static Statement combineStatements(Statement s, Statement t) { |
| if (s is Break && t is Break && s.target == t.target) { |
| --t.target.useCount; // Two breaks become one. |
| return s; |
| } |
| if (s is Continue && t is Continue && s.target == t.target) { |
| --t.target.useCount; // Two continues become one. |
| return s; |
| } |
| if (s is Return && t is Return) { |
| Expression e = combineExpressions(s.value, t.value); |
| if (e != null) { |
| return new Return(e); |
| } |
| } |
| return null; |
| } |
| |
| /// Returns an expression equivalent to both [e1] and [e2]. |
| /// If non-null is returned, the caller must discard [e1] and [e2] and use |
| /// the resulting expression in the tree. |
| static Expression combineExpressions(Expression e1, Expression e2) { |
| if (e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable) { |
| --e1.variable.readCount; // Two references become one. |
| return e1; |
| } |
| if (e1 is Constant && e2 is Constant && e1.value == e2.value) { |
| return e1; |
| } |
| return null; |
| } |
| |
| /// Try to collapse nested ifs using && and || expressions. |
| /// For example: |
| /// |
| /// if (E1) { if (E2) S else break L } else break L |
| /// ==> |
| /// if (E1 && E2) S else break L |
| /// |
| /// [branch1] and [branch2] control the position of the S statement. |
| /// |
| /// Returns true if another collapse redex might have been introduced. |
| void tryCollapseIf(If node) { |
| // Repeatedly try to collapse nested ifs. |
| // The transformation is shrinking (destroys an if) so it remains linear. |
| // Here is an example where more than one iteration is required: |
| // |
| // if (E1) |
| // if (E2) break L2 else break L1 |
| // else |
| // break L1 |
| // |
| // L1.target ::= |
| // if (E3) S else break L2 |
| // |
| // After first collapse: |
| // |
| // if (E1 && E2) |
| // break L2 |
| // else |
| // {if (E3) S else break L2} (inlined from break L1) |
| // |
| // We can then do another collapse using the inlined nested if. |
| bool changed = true; |
| while (changed) { |
| changed = false; |
| if (tryCollapseIfAux(node, true, true)) { |
| changed = true; |
| } |
| if (tryCollapseIfAux(node, true, false)) { |
| changed = true; |
| } |
| if (tryCollapseIfAux(node, false, true)) { |
| changed = true; |
| } |
| if (tryCollapseIfAux(node, false, false)) { |
| changed = true; |
| } |
| } |
| } |
| |
| bool tryCollapseIfAux(If outerIf, bool branch1, bool branch2) { |
| // NOTE: We name variables here as if S is in the then-then position. |
| Statement outerThen = getBranch(outerIf, branch1); |
| Statement outerElse = getBranch(outerIf, !branch1); |
| if (outerThen is If && outerElse is Break) { |
| If innerIf = outerThen; |
| Statement innerThen = getBranch(innerIf, branch2); |
| Statement innerElse = getBranch(innerIf, !branch2); |
| if (innerElse is Break && innerElse.target == outerElse.target) { |
| // We always put S in the then branch of the result, and adjust the |
| // condition expression if S was actually found in the else branch(es). |
| outerIf.condition = new LogicalOperator.and( |
| makeCondition(outerIf.condition, branch1), |
| makeCondition(innerIf.condition, branch2)); |
| outerIf.thenStatement = innerThen; |
| --innerElse.target.useCount; |
| |
| // Try to inline the remaining break. Do not propagate assignments. |
| inEmptyEnvironment(() { |
| outerIf.elseStatement = visitStatement(outerElse); |
| }); |
| |
| return outerIf.elseStatement is If && innerThen is Break; |
| } |
| } |
| return false; |
| } |
| |
| Expression makeCondition(Expression e, bool polarity) { |
| return polarity ? e : new Not(e); |
| } |
| |
| Statement getBranch(If node, bool polarity) { |
| return polarity ? node.thenStatement : node.elseStatement; |
| } |
| } |