| Index: pkg/compiler/lib/src/cps_ir/loop_invariant_branch.dart
|
| diff --git a/pkg/compiler/lib/src/cps_ir/loop_invariant_branch.dart b/pkg/compiler/lib/src/cps_ir/loop_invariant_branch.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..dfa2a2d780eed1c8ac4838e4f5938da59959785f
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/cps_ir/loop_invariant_branch.dart
|
| @@ -0,0 +1,252 @@
|
| +library dart2js.cps_ir.loop_invariant_branch;
|
| +
|
| +import 'cps_ir_nodes.dart';
|
| +import 'optimizers.dart';
|
| +import 'loop_hierarchy.dart';
|
| +import 'cps_fragment.dart';
|
| +import 'redundant_join.dart' show AlphaRenamer;
|
| +
|
| +/// Hoists branches out of loops, where:
|
| +/// - the branch is at the entry point of a loop
|
| +/// - the branch condition is loop-invariant
|
| +/// - one arm of the branch is not effectively part of the loop
|
| +///
|
| +/// Schematically:
|
| +///
|
| +/// b = COND
|
| +/// while (true) {
|
| +/// if (b)
|
| +/// BRANCH (contains no continue to loop)
|
| +/// else
|
| +/// LOOP
|
| +/// }
|
| +///
|
| +/// ==>
|
| +///
|
| +/// b = COND
|
| +/// if (b)
|
| +/// BRANCH
|
| +/// else
|
| +/// while (true)
|
| +/// LOOP
|
| +///
|
| +/// As in [RedundantJoinEliminator], parameters are treated as names with
|
| +/// lexical scoping during this pass, 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 LoopInvariantBranchMotion extends BlockVisitor implements Pass {
|
| + String get passName => 'Loop invariant branch motion';
|
| +
|
| + LoopHierarchy loopHierarchy;
|
| + final Map<Primitive, Continuation> loopHeaderFor =
|
| + <Primitive, Continuation>{};
|
| + final Map<Continuation, Continuation> catchLoopFor =
|
| + <Continuation, Continuation>{};
|
| + Continuation currentLoopHeader;
|
| + Continuation currentCatchLoop;
|
| + List<Continuation> loops = <Continuation>[];
|
| + bool wasHoisted = false;
|
| +
|
| + void rewrite(FunctionDefinition node) {
|
| + loopHierarchy = new LoopHierarchy(node);
|
| + BlockVisitor.traverseInPreOrder(node, this);
|
| + // Process loops bottom-up so a branch can be hoisted multiple times.
|
| + loops.reversed.forEach(hoistEntryCheck);
|
| + if (wasHoisted) {
|
| + new AlphaRenamer().visit(node);
|
| + }
|
| + }
|
| +
|
| + void visitLetHandler(LetHandler node) {
|
| + currentCatchLoop = loopHierarchy.getLoopHeader(node.handler);
|
| + }
|
| +
|
| + void visitContinuation(Continuation node) {
|
| + currentLoopHeader = loopHierarchy.getLoopHeader(node);
|
| + for (Parameter param in node.parameters) {
|
| + loopHeaderFor[param] = currentLoopHeader;
|
| + }
|
| + catchLoopFor[node] = currentCatchLoop;
|
| + if (node.isRecursive) {
|
| + loops.add(node);
|
| + }
|
| + }
|
| +
|
| + void visitLetPrim(LetPrim node) {
|
| + loopHeaderFor[node.primitive] = currentLoopHeader;
|
| + }
|
| +
|
| + void hoistEntryCheck(Continuation loop) {
|
| + // Keep hoisting branches out of the loop, there can be more than one.
|
| + while (tryHoistEntryCheck(loop));
|
| + }
|
| +
|
| + Expression getEffectiveBody(Expression exp) {
|
| + // TODO(asgerf): We could also bypass constants here but constant pooling
|
| + // is likely to be a better solution for that.
|
| + while (exp is LetCont) {
|
| + exp = exp.next;
|
| + }
|
| + return exp;
|
| + }
|
| +
|
| + /// Adds [parameters] to [cont] and updates every invocation to pass the
|
| + /// corresponding parameter values as arguments. Thus, the parameters are
|
| + /// passed in explicitly instead of being captured.
|
| + ///
|
| + /// This only works because [AlphaRenamer] cleans up after this pass.
|
| + ///
|
| + /// Schematically:
|
| + ///
|
| + /// let outer(x1, x2, x3) =
|
| + /// let inner(y) = BODY
|
| + /// [ .. inner(y') .. ]
|
| + ///
|
| + /// ==> (append parameters)
|
| + ///
|
| + /// let outer(x1, x2, x3) =
|
| + /// let inner(y, x1, x2, x3) = BODY
|
| + /// [ .. inner(y', x1, x2, x3) .. ]
|
| + ///
|
| + /// ==> (hoist, not performed by this method)
|
| + ///
|
| + /// let inner(y, x1, x2, x3) = BODY
|
| + /// let outer(x1, x2, x3) =
|
| + /// [ .. inner(y', x1, x2, x3) .. ]
|
| + ///
|
| + void appendParameters(Continuation cont, List<Parameter> parameters) {
|
| + cont.parameters.addAll(parameters);
|
| + for (Reference ref = cont.firstRef; ref != null; ref = ref.next) {
|
| + Node use = ref.parent;
|
| + if (use is InvokeContinuation) {
|
| + for (Parameter loopParam in parameters) {
|
| + use.arguments.add(new Reference<Primitive>(loopParam)..parent = use);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + bool tryHoistEntryCheck(Continuation loop) {
|
| + // Check if this is a loop starting with a branch.
|
| + Expression body = getEffectiveBody(loop.body);
|
| + if (body is! Branch) return false;
|
| + Branch branch = body;
|
| +
|
| + // Is the condition loop invariant?
|
| + Primitive condition = branch.condition.definition;
|
| + if (loopHeaderFor[condition] == loop) return false;
|
| +
|
| + Continuation trueCont = branch.trueContinuation.definition;
|
| + Continuation falseCont = branch.falseContinuation.definition;
|
| + Continuation hoistedCase; // The branch to hoist.
|
| + Continuation loopCase; // The branch that is part of the loop.
|
| +
|
| + // Check that one branch is part of the loop, and the other is an exit.
|
| + if (loopHierarchy.getLoopHeader(trueCont) != loop &&
|
| + loopHierarchy.getLoopHeader(falseCont) == loop) {
|
| + hoistedCase = trueCont;
|
| + loopCase = falseCont;
|
| + } else if (loopHierarchy.getLoopHeader(falseCont) != loop &&
|
| + loopHierarchy.getLoopHeader(trueCont) == loop) {
|
| + hoistedCase = falseCont;
|
| + loopCase = trueCont;
|
| + } else {
|
| + return false;
|
| + }
|
| +
|
| + // Hoist non-loop continuations out of the loop.
|
| + // The hoisted branch can reference other continuations bound in the loop,
|
| + // so to stay in scope, those need to be hoisted as well.
|
| + //
|
| + // let b = COND
|
| + // let loop(x) =
|
| + // let join(y) = JOIN
|
| + // let hoistCase() = HOIST
|
| + // let loopCase() = LOOP
|
| + // branch b hoistCase loopCase
|
| + // in loop(i)
|
| + //
|
| + // ==>
|
| + //
|
| + // let b = COND
|
| + // let join(y,x) = JOIN
|
| + // let hoistCase(x) = HOIST
|
| + // let loop(x) =
|
| + // let loopCase() = LOOP
|
| + // branch b hoistCase loopCase
|
| + // in loop(i)
|
| + //
|
| + LetCont loopBinding = loop.parent;
|
| + Expression it = loop.body;
|
| + while (it is LetCont) {
|
| + LetCont let = it;
|
| + it = let.body;
|
| + for (Continuation cont in let.continuations) {
|
| + if (loopHierarchy.getEnclosingLoop(cont) != loop) {
|
| + appendParameters(cont, loop.parameters);
|
| + new LetCont(cont, null).insertAbove(loopBinding);
|
| + }
|
| + }
|
| + let.continuations.removeWhere((cont) => cont.parent != let);
|
| + if (let.continuations.isEmpty) {
|
| + let.remove();
|
| + }
|
| + }
|
| +
|
| + // Create a new branch to call the hoisted continuation or the loop:
|
| + //
|
| + // let loop(x) =
|
| + // let loopCase() = LOOP
|
| + // branch b hoistCase loopCase
|
| + // in loop(i)
|
| + //
|
| + // ==>
|
| + //
|
| + // let newTrue() = hoistCase(i)
|
| + // let newFalse() =
|
| + // let loop(x) =
|
| + // let loopCase() = LOOP
|
| + // branch b hoistCase loopCase
|
| + // branch b newTrue newFalse
|
| + //
|
| + InvokeContinuation loopEntry = loopBinding.body;
|
| + List<Primitive> loopArgs =
|
| + loopEntry.arguments.map((ref) => ref.definition).toList();
|
| + CpsFragment cps = new CpsFragment();
|
| + cps.branch(condition,
|
| + strict: branch.isStrictCheck,
|
| + negate: hoistedCase == falseCont)
|
| + .invokeContinuation(hoistedCase, loopArgs);
|
| +
|
| + // The continuations created in the fragment need to have their loop header
|
| + // set so the loop hierarchy remains intact
|
| + loopHierarchy.update(cps,
|
| + exitLoop: loopHierarchy.getEnclosingLoop(loop),
|
| + catchLoop: catchLoopFor[loop]);
|
| +
|
| + // Insert above the loop. This will put the loop itself in a branch.
|
| + cps.insertAbove(loopBinding);
|
| +
|
| + // Replace the old branch with the loopCase, still bound inside the loop:
|
| + //
|
| + // let loop(x) =
|
| + // let loopCase() = LOOP
|
| + // branch b hoistCase loopCase
|
| + // in loop(i)
|
| + //
|
| + // ==>
|
| + //
|
| + // let loop(x) =
|
| + // let loopCase() = LOOP
|
| + // loopCase()
|
| + // in loop(i)
|
| + //
|
| + destroyAndReplace(branch, new InvokeContinuation(loopCase, []));
|
| +
|
| + // Record that at least one branch was hoisted to trigger alpha renaming.
|
| + wasHoisted = true;
|
| +
|
| + return true;
|
| + }
|
| +}
|
|
|