Index: pkg/compiler/lib/src/cps_ir/redundant_join.dart |
diff --git a/pkg/compiler/lib/src/cps_ir/redundant_join.dart b/pkg/compiler/lib/src/cps_ir/redundant_join.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..92bd51b07bfd6c0aad3476909d6b0f6a51188850 |
--- /dev/null |
+++ b/pkg/compiler/lib/src/cps_ir/redundant_join.dart |
@@ -0,0 +1,276 @@ |
+// Copyright (c) 2015, 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. |
+ |
+library dart2js.cps_ir.redundant_join_elimination; |
+ |
+import 'cps_ir_nodes.dart'; |
+import 'optimizers.dart'; |
+ |
+/// Eliminates redundant join points. |
+/// |
+/// A redundant join point is a continuation that immediately branches |
+/// based on one of its parameters, and that parameter is a constant value |
+/// at every invocation. Each invocation is redirected to jump directly |
+/// to the branch target. |
+/// |
+/// Internally in this pass, parameters are treated as names with lexical |
+/// scoping, and a given parameter "name" may be declared by more than |
+/// one continuation. The reference chains for parameters are therefore |
+/// meaningless during this pass, until repaired by [AlphaRenamer] at |
+/// the end. |
+class RedundantJoinEliminator extends RecursiveVisitor implements Pass { |
+ String get passName => 'Redundant join elimination'; |
+ |
+ final Set<Branch> workSet = new Set<Branch>(); |
+ |
+ void rewrite(FunctionDefinition node) { |
+ visit(node); |
+ |
+ while (workSet.isNotEmpty) { |
+ Branch branch = workSet.first; |
+ workSet.remove(branch); |
+ rewriteBranch(branch); |
+ } |
+ |
+ new AlphaRenamer().visit(node); |
+ } |
+ |
+ void processBranch(Branch node) { |
+ workSet.add(node); |
+ } |
+ |
+ /// Returns the body of [node], ignoring all LetCont nodes. |
+ Expression getEffectiveBody(InteriorNode node) { |
+ while (true) { |
+ Expression body = node.body; |
+ if (body is LetCont) { |
+ node = body; |
+ } else { |
+ return body; |
+ } |
+ } |
+ } |
+ |
+ /// Returns the parent of [node], ignoring all LetCont nodes. |
+ InteriorNode getEffectiveParent(Expression node) { |
+ while (true) { |
+ Node parent = node.parent; |
+ if (parent is LetCont) { |
+ node = parent; |
+ } else { |
+ return parent; |
+ } |
+ } |
+ } |
+ |
+ /// Removes [movedNode] from its current position and inserts it |
+ /// before [target]. |
+ void moveToBefore(Expression target, LetCont movedNode) { |
+ if (movedNode.parent != null) { |
+ movedNode.parent.body = movedNode.body; |
+ movedNode.body.parent = movedNode.parent; |
+ } |
+ InteriorNode parent = target.parent; |
+ parent.body = movedNode; |
+ movedNode.body = target; |
+ target.parent = movedNode; |
+ movedNode.parent = parent; |
+ } |
+ |
+ void rewriteBranch(Branch branch) { |
+ InteriorNode parent = getEffectiveParent(branch); |
+ if (parent is! Continuation) return; |
+ Continuation branchCont = parent; |
+ |
+ // Other optimizations take care of single-use continuations. |
+ if (!branchCont.hasMultipleUses) return; |
+ |
+ // It might be beneficial to rewrite calls to recursive continuations, |
+ // but we currently do not support this. |
+ if (branchCont.isRecursive) return; |
+ |
+ // Check that the branching condition is a parameter on the |
+ // enclosing continuation. |
+ // Note: Do not use the parent pointer for this check, because parameters |
+ // are temporarily shared between different continuations during this pass. |
+ IsTrue isTrue = branch.condition; |
+ Primitive condition = isTrue.value.definition; |
+ int parameterIndex = branchCont.parameters.indexOf(condition); |
+ if (parameterIndex == -1) return; |
+ |
+ // Check that all callers hit a fixed branch, and count the number |
+ // of times each branch is hit. |
+ // We know all callers are InvokeContinuations because they are the only |
+ // valid uses of a multi-use continuation. |
+ int trueHits = 0, falseHits = 0; |
+ InvokeContinuation trueCall, falseCall; |
+ for (Reference ref = branchCont.firstRef; ref != null; ref = ref.next) { |
+ InvokeContinuation invoke = ref.parent; |
+ Primitive argument = invoke.arguments[parameterIndex].definition; |
+ if (argument is! Constant) return; // Branching condition is unknown. |
+ Constant constant = argument; |
+ if (isFalsyConstant(constant.value)) { |
+ ++falseHits; |
+ falseCall = invoke; |
+ } else { |
+ ++trueHits; |
+ trueCall = invoke; |
+ } |
+ } |
+ |
+ // The optimization is now known to be safe, but it only pays off if |
+ // one of the callers can inline its target, since otherwise we end up |
+ // replacing a boolean variable with a labeled break. |
+ // TODO(asgerf): The labeled break might be better? Evaluate. |
+ if (!(trueHits == 1 && !trueCall.isEscapingTry || |
+ falseHits == 1 && !falseCall.isEscapingTry)) { |
+ return; |
+ } |
+ |
+ // Lift any continuations bound inside branchCont so they are in scope at |
+ // the call sites. When lifting, the parameters of branchCont fall out of |
+ // scope, so they are added as parameters on each lifted continuation. |
+ // Schematically: |
+ // |
+ // (LetCont (branchCont (x1, x2, x3) = |
+ // (LetCont (innerCont (y) = ...) in |
+ // [... innerCont(y') ...])) |
+ // |
+ // => |
+ // |
+ // (LetCont (innerCont (y, x1, x2, x3) = ...) in |
+ // (LetCont (branchCont (x1, x2, x3) = |
+ // [... innerCont(y', x1, x2, x3) ...]) |
+ // |
+ // Parameter objects become shared between branchCont and the lifted |
+ // continuations. [AlphaRenamer] will clean up at the end of this pass. |
+ LetCont outerLetCont = branchCont.parent; |
+ while (branchCont.body is LetCont) { |
+ LetCont innerLetCont = branchCont.body; |
+ for (Continuation innerCont in innerLetCont.continuations) { |
+ innerCont.parameters.addAll(branchCont.parameters); |
+ for (Reference ref = innerCont.firstRef; ref != null; ref = ref.next) { |
+ Expression use = ref.parent; |
+ if (use is InvokeContinuation) { |
+ for (Parameter param in branchCont.parameters) { |
+ use.arguments.add(new Reference<Primitive>(param)); |
+ } |
+ } else { |
+ // The branch will be eliminated, so don't worry about updating it. |
+ assert(use == branch); |
+ } |
+ } |
+ } |
+ moveToBefore(outerLetCont, innerLetCont); |
+ } |
+ |
+ assert(branchCont.body == branch); |
+ |
+ Continuation trueCont = branch.trueContinuation.definition; |
+ Continuation falseCont = branch.falseContinuation.definition; |
+ |
+ assert(branchCont != trueCont); |
+ assert(branchCont != falseCont); |
+ |
+ // Rewrite every invocation of branchCont to call either the true or false |
+ // branch directly. Since these were lifted out above branchCont, they are |
+ // now in scope. |
+ // Since trueCont and falseCont were branch targets, they originally |
+ // had no parameters, and so after the lifting, their parameters are |
+ // exactly the same as those accepted by branchCont. |
+ while (branchCont.firstRef != null) { |
+ Reference reference = branchCont.firstRef; |
+ InvokeContinuation invoke = branchCont.firstRef.parent; |
+ Constant condition = invoke.arguments[parameterIndex].definition; |
+ if (isFalsyConstant(condition.value)) { |
+ invoke.continuation.changeTo(falseCont); |
+ } else { |
+ invoke.continuation.changeTo(trueCont); |
+ } |
+ assert(branchCont.firstRef != reference); |
+ } |
+ |
+ // Remove the now-unused branchCont continuation. |
+ assert(branchCont.hasNoUses); |
+ branch.trueContinuation.unlink(); |
+ branch.falseContinuation.unlink(); |
+ outerLetCont.continuations.remove(branchCont); |
+ if (outerLetCont.continuations.isEmpty) { |
+ InteriorNode parent = outerLetCont.parent; |
+ parent.body = outerLetCont.body; |
+ outerLetCont.body.parent = parent; |
+ } |
+ |
+ // We may have created new redundant join points in the two branches. |
+ enqueueContinuation(trueCont); |
+ enqueueContinuation(falseCont); |
+ } |
+ |
+ void enqueueContinuation(Continuation cont) { |
+ Expression body = getEffectiveBody(cont); |
+ if (body is Branch) { |
+ workSet.add(body); |
+ } |
+ } |
+} |
+ |
+/// Ensures parameter objects are not shared between different continuations, |
+/// akin to alpha-renaming variables so every variable is named uniquely. |
+/// For example: |
+/// |
+/// LetCont (k1 x = (return x)) in |
+/// LetCont (k2 x = (InvokeContinuation k3 x)) in ... |
+/// => |
+/// LetCont (k1 x = (return x)) in |
+/// LetCont (k2 x' = (InvokeContinuation k3 x')) in ... |
+/// |
+/// After lifting LetConts in the main pass above, parameter objects can have |
+/// multiple bindings. Each reference implicitly refers to the binding that |
+/// is currently in scope. |
+/// |
+/// This returns the IR to its normal form after redundant joins have been |
+/// eliminated. |
+class AlphaRenamer extends RecursiveVisitor { |
+ Map<Parameter, Parameter> renaming = <Parameter, Parameter>{}; |
+ |
+ visitContinuation(Continuation cont) { |
+ if (cont.isReturnContinuation) return; |
+ |
+ List<Parameter> shadowedKeys = <Parameter>[]; |
+ List<Parameter> shadowedValues = <Parameter>[]; |
+ |
+ // Create new parameters and update the environment. |
+ for (int i = 0; i < cont.parameters.length; ++i) { |
+ Parameter param = cont.parameters[i]; |
+ shadowedKeys.add(param); |
+ shadowedValues.add(renaming.remove(param)); |
+ // If the parameter appears to belong to another continuation, |
+ // create a new parameter object for this continuation. |
+ if (param.parent != cont) { |
+ Parameter newParam = new Parameter(param.hint); |
+ renaming[param] = newParam; |
+ cont.parameters[i] = newParam; |
+ newParam.parent = cont; |
+ } |
+ } |
+ |
+ // Visit the body with the updated environment. |
+ visit(cont.body); |
+ |
+ // Restore the original environment. |
+ for (int i = 0; i < cont.parameters.length; ++i) { |
+ renaming.remove(cont.parameters[i]); |
+ if (shadowedValues[i] != null) { |
+ renaming[shadowedKeys[i]] = shadowedValues[i]; |
+ } |
+ } |
+ } |
+ |
+ processReference(Reference ref) { |
+ Parameter target = renaming[ref.definition]; |
+ if (target != null) { |
+ ref.changeTo(target); |
+ } |
+ } |
+} |