Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Unified Diff: pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart

Issue 1088493002: Assignment expressions in tree IR. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Comments Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart
diff --git a/pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart b/pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart
index 88ab5d8f9a6ba3d91dc5d19a2cafee8425ab7c84..543aa31202b5e826d1c765ca7b3408687584cbb3 100644
--- a/pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart
+++ b/pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart
@@ -6,7 +6,8 @@ part of tree_ir.optimization;
/**
* Performs the following transformations on the tree:
- * - Assignment propagation
+ * - Assignment inlining
+ * - Assignment expression propagation
* - If-to-conditional conversion
* - Flatten nested ifs
* - Break inlining
@@ -16,8 +17,8 @@ part of tree_ir.optimization;
* introduce redexes of each other.
*
*
- * ASSIGNMENT PROPAGATION:
- * Single-use definitions are propagated to their use site when possible.
+ * ASSIGNMENT INLINING:
+ * Single-use definitions are inlined at their use site when possible.
* For example:
*
* { v0 = foo(); return v0; }
@@ -41,6 +42,19 @@ part of tree_ir.optimization;
* propagating a definition.
*
*
+ * ASSIGNMENT EXPRESSION PROPAGATION:
+ * Definitions with multiple uses are propagated to their first use site
+ * when possible. For example:
+ *
+ * { v0 = foo(); bar(v0); return v0; }
+ * ==>
+ * { bar(v0 = foo()); return v0; }
+ *
+ * Note that the [RestoreInitializers] phase will later undo this rewrite
+ * in cases where it prevents an assignment from being pulled into an
+ * initializer.
+ *
+ *
* IF-TO-CONDITIONAL CONVERSION:
* If-statement are converted to conditional expressions when possible.
* For example:
@@ -99,9 +113,21 @@ class StatementRewriter extends Transformer implements Pass {
node.replaceEachBody(visitStatement);
}
- // The binding environment. The rightmost element of the list is the nearest
- // available enclosing binding.
- List<Assign> environment = <Assign>[];
+ /// True if targeting Dart.
+ final bool isDartMode;
+
+ /// The most recently evaluated impure expressions, with the most recent
+ /// expression being last.
+ ///
+ /// Most importantly, this contains [Assign] expressions that we attempt to
+ /// inline at their use site. It also contains other impure expressions that
+ /// we can propagate to a variable use if they are known to return the value
+ /// of that variable.
+ ///
+ /// Except for [Conditional]s, expressions in the environment have
+ /// not been processed, and all their subexpressions must therefore be
+ /// variables uses.
+ List<Expression> environment = <Expression>[];
/// Binding environment for variables that are assigned to effectively
/// constant expressions (see [isEffectivelyConstant]).
@@ -111,12 +137,21 @@ class StatementRewriter extends Transformer implements Pass {
/// for a break to L' if L maps to L'.
Map<Label, Jump> labelRedirects = <Label, Jump>{};
+ /// Number of uses seen so far. Used to detect the first use of a variable
+ /// (since we do backwards traversal, the first use is the last one seen).
+ Map<Variable, int> seenUses = <Variable, int>{};
+
/// Rewriter for methods.
- StatementRewriter() : constantEnvironment = <Variable, Expression>{};
+ StatementRewriter({this.isDartMode})
+ : constantEnvironment = <Variable, Expression>{} {
+ assert(isDartMode != null);
+ }
/// Rewriter for nested functions.
StatementRewriter.nested(StatementRewriter parent)
- : constantEnvironment = parent.constantEnvironment;
+ : constantEnvironment = parent.constantEnvironment,
+ seenUses = parent.seenUses,
+ isDartMode = parent.isDartMode;
/// A set of labels that can be safely inlined at their use.
///
@@ -134,34 +169,92 @@ class StatementRewriter extends Transformer implements Pass {
}
void inEmptyEnvironment(void action()) {
- List<Assign> oldEnvironment = environment;
- environment = <Assign>[];
+ List oldEnvironment = environment;
+ environment = <Expression>[];
action();
assert(environment.isEmpty);
environment = oldEnvironment;
}
- Expression visitExpression(Expression e) => e.processed ? e : e.accept(this);
+ /// Left-hand side of the given assignment, or `null` if not an assignment.
+ Variable getLeftHand(Expression e) {
+ return e is Assign ? e.variable : null;
+ }
+
+ /// If the given expression always returns the value of one of its
+ /// subexpressions, returns that subexpression, otherwise `null`.
+ Expression getValueSubexpression(Expression e) {
+ if (isDartMode &&
+ e is InvokeMethod &&
+ (e.selector.isSetter || e.selector.isIndexSet)) {
+ return e.arguments.last;
+ }
+ if (e is SetField) return e.value;
+ return null;
+ }
+
+ /// If the given expression always returns the value of one of its
+ /// subexpressions, and that subexpression is a variable use, returns that
+ /// variable. Otherwise `null`.
+ Variable getRightHand(Expression e) {
+ Expression value = getValueSubexpression(e);
+ return value is VariableUse ? value.variable : null;
+ }
@override
Expression visitVariableUse(VariableUse node) {
+ // Count of number of uses seen so far.
+ seenUses[node.variable] = 1 + seenUses.putIfAbsent(node.variable, () => 0);
+
+ // We traverse the tree right-to-left, so when we have seen all uses,
+ // it means we are looking at the first use.
+ assert(seenUses[node.variable] <= node.variable.readCount);
+ bool isFirstUse = seenUses[node.variable] == node.variable.readCount;
+
// Propagate constant to use site.
Expression constant = constantEnvironment[node.variable];
if (constant != null) {
--node.variable.readCount;
+ --seenUses[node.variable]; // Do not count the use we just destroyed.
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);
+ // Try to propagate another expression into this variable use.
+ if (!environment.isEmpty) {
+ Expression binding = environment.last;
+
+ // Is this variable assigned by the most recently evaluated impure
+ // expression?
+ //
+ // If so, propagate the assignment, e.g:
+ //
+ // { x = foo(); bar(x, x) } ==> bar(x = foo(), x)
+ //
+ // We must ensure that no other uses separate this use from the
+ // assignment. We therefore only propagate assignments into the first use.
+ //
+ // Note that if this is only use, `visitAssign` will then remove the
+ // redundant assignment.
+ if (getLeftHand(binding) == node.variable && isFirstUse) {
+ environment.removeLast();
+ --node.variable.readCount;
+ --seenUses[node.variable]; // Do not count the use we just destroyed.
+ return visitExpression(binding);
+ }
+
+ // Is the most recently evaluated impure expression known to have the
+ // value of this variable?
+ //
+ // If so, we can replace this use with the impure expression, e.g:
+ //
+ // { E.foo = x; bar(x) } ==> bar(E.foo = x)
+ //
+ if (getRightHand(binding) == node.variable) {
+ environment.removeLast();
+ --node.variable.readCount;
+ --seenUses[node.variable];
+ return visitExpression(binding);
+ }
}
// If the definition could not be propagated, leave the variable use.
@@ -180,41 +273,72 @@ class StatementRewriter extends Transformer implements Pass {
exp is VariableUse && constantEnvironment.containsKey(exp.variable);
}
- Statement visitAssign(Assign node) {
- if (isEffectivelyConstant(node.value) &&
- node.variable.writeCount == 1) {
+ /// True if [node] is an assignment that can be propagated as a constant.
+ bool isEffectivelyConstantAssignment(Expression node) {
+ return node is Assign &&
+ node.variable.writeCount == 1 &&
+ isEffectivelyConstant(node.value);
+ }
+
+ Statement visitExpressionStatement(ExpressionStatement stmt) {
+ if (isEffectivelyConstantAssignment(stmt.expression)) {
+ Assign assign = stmt.expression;
// 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) {
+ if (assign.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);
+ constantEnvironment[assign.variable] = visitExpression(assign.value);
+ --assign.variable.writeCount;
+ return visitStatement(stmt.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;
+ stmt.next = visitStatement(stmt.next);
+ assign.value = visitExpression(assign.value);
+ return stmt;
}
+ }
+ // Try to propagate the expression, and block previous impure expressions
+ // until this has propagated.
+ environment.add(stmt.expression);
+ stmt.next = visitStatement(stmt.next);
+ if (!environment.isEmpty && environment.last == stmt.expression) {
+ // Retain the expression statement.
+ environment.removeLast();
+ stmt.expression = visitExpression(stmt.expression);
+ return stmt;
} 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 was propagated into the successor.
+ return stmt.next;
+ }
+ }
+
+ Expression visitAssign(Assign node) {
+ node.value = visitExpression(node.value);
+ // Remove assignments to variables without any uses. This can happen
+ // because the assignment was propagated into its use, e.g:
+ //
+ // { x = foo(); bar(x) } ==> bar(x = foo()) ==> bar(foo())
+ //
+ if (node.variable.readCount == 0) {
+ --node.variable.writeCount;
+ return node.value;
+ }
+ return node;
+ }
+
+ Statement visitVariableDeclaration(VariableDeclaration node) {
+ if (isEffectivelyConstant(node.value)) {
+ node.next = visitStatement(node.next);
+ } else {
+ inEmptyEnvironment(() {
+ node.next = visitStatement(node.next);
+ });
}
+ node.value = visitExpression(node.value);
+ return node;
}
Expression visitInvokeStatic(InvokeStatic node) {
@@ -256,13 +380,25 @@ class StatementRewriter extends Transformer implements Pass {
}
Expression visitConditional(Conditional node) {
- node.condition = visitExpression(node.condition);
-
- inEmptyEnvironment(() {
- node.thenExpression = visitExpression(node.thenExpression);
- node.elseExpression = visitExpression(node.elseExpression);
- });
-
+ // Conditional expressions do not exist in the input, but they are
+ // introduced by if-to-conditional conversion.
+ // Their subexpressions have already been processed; do not reprocess them.
+ //
+ // Note that this can only happen for conditional expressions. It is an
+ // error for any other type of expression to be visited twice or to be
+ // created and then visited. We use this special treatment of conditionals
+ // to allow for assignment inlining after if-to-conditional conversion.
+ //
+ // There are several reasons we should not reprocess the subexpressions:
+ //
+ // - It will mess up the [seenUses] counter, since a single use will be
+ // counted twice.
+ //
+ // - Other visit methods assume that all subexpressions are variable uses
+ // because they come fresh out of the tree IR builder.
+ //
+ // - Reprocessing can be expensive.
+ //
return node;
}
@@ -298,7 +434,6 @@ class StatementRewriter extends Transformer implements Pass {
return node;
}
-
Statement visitBreak(Break node) {
// Redirect through chain of breaks.
// Note that useCount was accounted for at visitLabeledStatement.
@@ -362,20 +497,16 @@ class StatementRewriter extends Transformer implements Pass {
inEmptyEnvironment(() {
node.thenStatement = visitStatement(node.thenStatement);
node.elseStatement = visitStatement(node.elseStatement);
- });
- tryCollapseIf(node);
+ tryCollapseIf(node);
+ });
- Statement reduced = combineStatementsWithSubexpressions(
+ Statement reduced = combineStatementsInBranches(
node.thenStatement,
node.elseStatement,
- (t,f) => new Conditional(node.condition, t, f)..processed = true);
+ node.condition);
if (reduced != null) {
- // TODO(asgerf): Avoid revisiting nodes or visiting nodes that we created.
- // This breaks the assumption that all subexpressions are
- // variable uses, and it can be expensive.
- // Revisit in case the break can now be inlined.
- return visitStatement(reduced);
+ return reduced;
}
return node;
@@ -440,22 +571,7 @@ class StatementRewriter extends Transformer implements Pass {
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);
+ Expression visitSetField(SetField node) {
node.value = visitExpression(node.value);
node.object = visitExpression(node.object);
return node;
@@ -511,43 +627,93 @@ class StatementRewriter extends Transformer implements Pass {
///
/// If non-null is returned, the caller MUST discard [s] and [t] and use
/// the returned statement instead.
- static Statement combineStatementsWithSubexpressions(
+ Statement combineStatementsInBranches(
Statement s,
Statement t,
- Expression combine(Expression s, Expression t)) {
+ Expression condition) {
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);
- }
+ return new Return(new Conditional(condition, s.value, t.value));
}
if (s is ExpressionStatement && t is ExpressionStatement) {
+ // Combine the two expressions and the two successor statements.
+ //
+ // C ? {E1 ; S1} : {E2 ; S2}
+ // ==>
+ // (C ? E1 : E2) : combine(S1, S2)
+ //
+ // If E1 and E2 are assignments, we want to propagate these into the
+ // combined statement.
+ //
+ // It might not be possible to combine the statements, so we combine the
+ // expressions, put the result in the environment, and then uncombine the
+ // expressions if the statements could not be combined.
+
+ // Combine the expressions.
+ CombinedExpressions values =
+ combineAsConditional(s.expression, t.expression, condition);
+
+ // Put this into the environment and try to combine the statements.
+ // We are not in risk of reprocessing the original subexpressions because
+ // the combined expression will always hide them inside a Conditional.
+ environment.add(values.combined);
Statement next = combineStatements(s.next, t.next);
- if (next != null) {
- return new ExpressionStatement(combine(s.expression, t.expression),
- next);
+
+ if (next == null) {
+ // Statements could not be combined.
+ // Restore the environment and uncombine expressions again.
+ environment.removeLast();
+ values.uncombine();
+ return null;
+ } else if (!environment.isEmpty && environment.last == values.combined) {
+ // Statements were combined but the combined expression could not be
+ // propagated. Leave it as an expression statement here.
+ environment.removeLast();
+ s.expression = values.combined;
+ s.next = next;
+ return s;
+ } else {
+ // Statements were combined and the combined expressions were
+ // propagated into the combined statement.
+ return next;
}
}
return null;
}
+ /// Creates the expression `[condition] ? [s] : [t]` or an equivalent
+ /// expression if something better can be done.
+ ///
+ /// In particular, assignments will be merged as follows:
+ ///
+ /// C ? (v = E1) : (v = E2)
+ /// ==>
+ /// v = C ? E1 : E2
+ ///
+ /// The latter form is more compact and can also be inlined.
+ CombinedExpressions combineAsConditional(
+ Expression s,
+ Expression t,
+ Expression condition) {
+ if (s is Assign && t is Assign && s.variable == t.variable) {
+ Expression values = new Conditional(condition, s.value, t.value);
+ return new CombinedAssigns(s, t, new CombinedExpressions(values));
+ }
+ return new CombinedExpressions(new Conditional(condition, s, t));
+ }
+
/// 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) {
+ Statement combineStatements(Statement s, Statement t) {
if (s is Break && t is Break && s.target == t.target) {
--t.target.useCount; // Two breaks become one.
+ if (s.target.useCount == 1 && safeForInlining.contains(s.target)) {
+ // Only one break remains; inline it.
+ --s.target.useCount;
+ return visitStatement(s.target.binding.next);
+ }
return s;
}
if (s is Continue && t is Continue && s.target == t.target) {
@@ -555,20 +721,35 @@ class StatementRewriter extends Transformer implements Pass {
return s;
}
if (s is Return && t is Return) {
- Expression e = combineExpressions(s.value, t.value);
- if (e != null) {
- return new Return(e);
+ CombinedExpressions values = combineExpressions(s.value, t.value);
+ if (values != null) {
+ return new Return(values.combined);
}
}
- if (s is Assign && t is Assign &&
- s.variable == t.variable &&
- isSameVariable(s.value, t.value)) {
+ if (s is ExpressionStatement && t is ExpressionStatement) {
+ CombinedExpressions values =
+ combineExpressions(s.expression, t.expression);
+ if (values == null) return null;
+ environment.add(values.combined);
Statement next = combineStatements(s.next, t.next);
- if (next != null) {
+ if (next == null) {
+ // The successors could not be combined.
+ // Restore the environment and uncombine the values again.
+ assert(environment.last == values.combined);
+ environment.removeLast();
+ values.uncombine();
+ return null;
+ } else if (!environment.isEmpty && environment.last == values.combined) {
+ // The successors were combined but the combined expressions were not
+ // propagated. Leave the combined expression as a statement.
+ environment.removeLast();
+ s.expression = values.combined;
s.next = next;
- --t.variable.writeCount;
- --(t.value as VariableUse).variable.readCount;
return s;
+ } else {
+ // The successors were combined, and the combined expressions were
+ // propagated into the successors.
+ return next;
}
}
return null;
@@ -577,13 +758,18 @@ class StatementRewriter extends Transformer implements Pass {
/// 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) {
+ CombinedExpressions 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;
+ return new CombinedUses(e1, e2);
+ }
+ if (e1 is Assign && e2 is Assign && e1.variable == e2.variable) {
+ CombinedExpressions values = combineExpressions(e1.value, e2.value);
+ if (values != null) {
+ return new CombinedAssigns(e1, e2, values);
+ }
}
if (e1 is Constant && e2 is Constant && e1.value == e2.value) {
- return e1;
+ return new CombinedExpressions(e1);
}
return null;
}
@@ -597,8 +783,9 @@ class StatementRewriter extends Transformer implements Pass {
///
/// [branch1] and [branch2] control the position of the S statement.
///
- /// Returns true if another collapse redex might have been introduced.
+ /// Must be called with an empty environment.
void tryCollapseIf(If node) {
+ assert(environment.isEmpty);
// 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:
@@ -653,24 +840,13 @@ class StatementRewriter extends Transformer implements Pass {
makeCondition(outerIf.condition, branch1),
makeCondition(innerIf.condition, branch2));
outerIf.thenStatement = innerThen;
-
- // Try to inline the remaining break. Do not propagate assignments.
- inEmptyEnvironment(() {
- // TODO(asgerf): Avoid quadratic cost from repeated processing. This
- // should be easier after we introduce basic blocks.
- outerIf.elseStatement = visitStatement(combinedElse);
- });
-
+ outerIf.elseStatement = combinedElse;
return outerIf.elseStatement is If;
}
}
return false;
}
- static bool isSameVariable(Expression e1, Expression e2) {
- return e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable;
- }
-
Expression makeCondition(Expression e, bool polarity) {
return polarity ? e : new Not(e);
}
@@ -679,3 +855,63 @@ class StatementRewriter extends Transformer implements Pass {
return polarity ? node.thenStatement : node.elseStatement;
}
}
+
+/// Result of combining two expressions, with the potential for reverting the
+/// combination.
+///
+/// Reverting a combination is done by calling [uncombine]. In this case,
+/// both the original expressions should remain in the tree, and the [combined]
+/// expression should be orphaned.
+///
+/// Explicitly reverting a combination is necessary to maintain variable
+/// reference counts.
+abstract class CombinedExpressions {
+ Expression get combined;
+ void uncombine();
+
+ factory CombinedExpressions(Expression e) = GenericCombinedExpressions;
+}
+
+/// Combines assignments of form `[variable] := E1` and `[variable] := E2` into
+/// a single assignment of form `[variable] := combine(E1, E2)`.
+class CombinedAssigns implements CombinedExpressions {
+ Assign assign1, assign2;
+ CombinedExpressions value;
+ Expression combined;
+
+ CombinedAssigns(this.assign1, this.assign2, this.value) {
+ assert(assign1.variable == assign2.variable);
+ assign1.variable.writeCount -= 2; // Destroy the two original assignemnts.
+ combined = new Assign(assign1.variable, value.combined);
+ }
+
+ void uncombine() {
+ value.uncombine();
+ ++assign1.variable.writeCount; // Restore original reference count.
+ }
+}
+
+/// Combines two variable uses into one.
+class CombinedUses implements CombinedExpressions {
+ VariableUse use1, use2;
+ Expression combined;
+
+ CombinedUses(this.use1, this.use2) {
+ assert(use1.variable == use2.variable);
+ use1.variable.readCount -= 2; // Destroy both the original uses.
+ combined = new VariableUse(use1.variable);
+ }
+
+ void uncombine() {
+ ++use1.variable.readCount; // Restore original reference count.
+ }
+}
+
+/// Result of combining two expressions that do not affect reference counting.
+class GenericCombinedExpressions implements CombinedExpressions {
+ Expression combined;
+
+ GenericCombinedExpressions(this.combined);
+
+ void uncombine() {}
+}

Powered by Google App Engine
This is Rietveld 408576698