| OLD | NEW |
| (Empty) | |
| 1 library dart2js.cps_ir.loop_invariant_branch; |
| 2 |
| 3 import 'cps_ir_nodes.dart'; |
| 4 import 'optimizers.dart'; |
| 5 import 'loop_hierarchy.dart'; |
| 6 import 'cps_fragment.dart'; |
| 7 import 'redundant_join.dart' show AlphaRenamer; |
| 8 |
| 9 /// Hoists branches out of loops, where: |
| 10 /// - the branch is at the entry point of a loop |
| 11 /// - the branch condition is loop-invariant |
| 12 /// - one arm of the branch is not effectively part of the loop |
| 13 /// |
| 14 /// Schematically: |
| 15 /// |
| 16 /// b = COND |
| 17 /// while (true) { |
| 18 /// if (b) |
| 19 /// BRANCH (contains no continue to loop) |
| 20 /// else |
| 21 /// LOOP |
| 22 /// } |
| 23 /// |
| 24 /// ==> |
| 25 /// |
| 26 /// b = COND |
| 27 /// if (b) |
| 28 /// BRANCH |
| 29 /// else |
| 30 /// while (true) |
| 31 /// LOOP |
| 32 /// |
| 33 /// As in [RedundantJoinEliminator], parameters are treated as names with |
| 34 /// lexical scoping during this pass, and a given parameter "name" may be |
| 35 /// declared by more than one continuation. The reference chains for parameters |
| 36 /// are therefore meaningless during this pass, until repaired by [AlphaRenamer] |
| 37 /// at the end. |
| 38 class LoopInvariantBranchMotion extends BlockVisitor implements Pass { |
| 39 String get passName => 'Loop invariant branch motion'; |
| 40 |
| 41 LoopHierarchy loopHierarchy; |
| 42 final Map<Primitive, Continuation> loopHeaderFor = |
| 43 <Primitive, Continuation>{}; |
| 44 final Map<Continuation, Continuation> catchLoopFor = |
| 45 <Continuation, Continuation>{}; |
| 46 Continuation currentLoopHeader; |
| 47 Continuation currentCatchLoop; |
| 48 List<Continuation> loops = <Continuation>[]; |
| 49 bool wasHoisted = false; |
| 50 |
| 51 void rewrite(FunctionDefinition node) { |
| 52 loopHierarchy = new LoopHierarchy(node); |
| 53 BlockVisitor.traverseInPreOrder(node, this); |
| 54 // Process loops bottom-up so a branch can be hoisted multiple times. |
| 55 loops.reversed.forEach(hoistEntryCheck); |
| 56 if (wasHoisted) { |
| 57 new AlphaRenamer().visit(node); |
| 58 } |
| 59 } |
| 60 |
| 61 void visitLetHandler(LetHandler node) { |
| 62 currentCatchLoop = loopHierarchy.getLoopHeader(node.handler); |
| 63 } |
| 64 |
| 65 void visitContinuation(Continuation node) { |
| 66 currentLoopHeader = loopHierarchy.getLoopHeader(node); |
| 67 for (Parameter param in node.parameters) { |
| 68 loopHeaderFor[param] = currentLoopHeader; |
| 69 } |
| 70 catchLoopFor[node] = currentCatchLoop; |
| 71 if (node.isRecursive) { |
| 72 loops.add(node); |
| 73 } |
| 74 } |
| 75 |
| 76 void visitLetPrim(LetPrim node) { |
| 77 loopHeaderFor[node.primitive] = currentLoopHeader; |
| 78 } |
| 79 |
| 80 void hoistEntryCheck(Continuation loop) { |
| 81 // Keep hoisting branches out of the loop, there can be more than one. |
| 82 while (tryHoistEntryCheck(loop)); |
| 83 } |
| 84 |
| 85 Expression getEffectiveBody(Expression exp) { |
| 86 // TODO(asgerf): We could also bypass constants here but constant pooling |
| 87 // is likely to be a better solution for that. |
| 88 while (exp is LetCont) { |
| 89 exp = exp.next; |
| 90 } |
| 91 return exp; |
| 92 } |
| 93 |
| 94 /// Adds [parameters] to [cont] and updates every invocation to pass the |
| 95 /// corresponding parameter values as arguments. Thus, the parameters are |
| 96 /// passed in explicitly instead of being captured. |
| 97 /// |
| 98 /// This only works because [AlphaRenamer] cleans up after this pass. |
| 99 /// |
| 100 /// Schematically: |
| 101 /// |
| 102 /// let outer(x1, x2, x3) = |
| 103 /// let inner(y) = BODY |
| 104 /// [ .. inner(y') .. ] |
| 105 /// |
| 106 /// ==> (append parameters) |
| 107 /// |
| 108 /// let outer(x1, x2, x3) = |
| 109 /// let inner(y, x1, x2, x3) = BODY |
| 110 /// [ .. inner(y', x1, x2, x3) .. ] |
| 111 /// |
| 112 /// ==> (hoist, not performed by this method) |
| 113 /// |
| 114 /// let inner(y, x1, x2, x3) = BODY |
| 115 /// let outer(x1, x2, x3) = |
| 116 /// [ .. inner(y', x1, x2, x3) .. ] |
| 117 /// |
| 118 void appendParameters(Continuation cont, List<Parameter> parameters) { |
| 119 cont.parameters.addAll(parameters); |
| 120 for (Reference ref = cont.firstRef; ref != null; ref = ref.next) { |
| 121 Node use = ref.parent; |
| 122 if (use is InvokeContinuation) { |
| 123 for (Parameter loopParam in parameters) { |
| 124 use.arguments.add(new Reference<Primitive>(loopParam)..parent = use); |
| 125 } |
| 126 } |
| 127 } |
| 128 } |
| 129 |
| 130 bool tryHoistEntryCheck(Continuation loop) { |
| 131 // Check if this is a loop starting with a branch. |
| 132 Expression body = getEffectiveBody(loop.body); |
| 133 if (body is! Branch) return false; |
| 134 Branch branch = body; |
| 135 |
| 136 // Is the condition loop invariant? |
| 137 Primitive condition = branch.condition.definition; |
| 138 if (loopHeaderFor[condition] == loop) return false; |
| 139 |
| 140 Continuation trueCont = branch.trueContinuation.definition; |
| 141 Continuation falseCont = branch.falseContinuation.definition; |
| 142 Continuation hoistedCase; // The branch to hoist. |
| 143 Continuation loopCase; // The branch that is part of the loop. |
| 144 |
| 145 // Check that one branch is part of the loop, and the other is an exit. |
| 146 if (loopHierarchy.getLoopHeader(trueCont) != loop && |
| 147 loopHierarchy.getLoopHeader(falseCont) == loop) { |
| 148 hoistedCase = trueCont; |
| 149 loopCase = falseCont; |
| 150 } else if (loopHierarchy.getLoopHeader(falseCont) != loop && |
| 151 loopHierarchy.getLoopHeader(trueCont) == loop) { |
| 152 hoistedCase = falseCont; |
| 153 loopCase = trueCont; |
| 154 } else { |
| 155 return false; |
| 156 } |
| 157 |
| 158 // Hoist non-loop continuations out of the loop. |
| 159 // The hoisted branch can reference other continuations bound in the loop, |
| 160 // so to stay in scope, those need to be hoisted as well. |
| 161 // |
| 162 // let b = COND |
| 163 // let loop(x) = |
| 164 // let join(y) = JOIN |
| 165 // let hoistCase() = HOIST |
| 166 // let loopCase() = LOOP |
| 167 // branch b hoistCase loopCase |
| 168 // in loop(i) |
| 169 // |
| 170 // ==> |
| 171 // |
| 172 // let b = COND |
| 173 // let join(y,x) = JOIN |
| 174 // let hoistCase(x) = HOIST |
| 175 // let loop(x) = |
| 176 // let loopCase() = LOOP |
| 177 // branch b hoistCase loopCase |
| 178 // in loop(i) |
| 179 // |
| 180 LetCont loopBinding = loop.parent; |
| 181 Expression it = loop.body; |
| 182 while (it is LetCont) { |
| 183 LetCont let = it; |
| 184 it = let.body; |
| 185 for (Continuation cont in let.continuations) { |
| 186 if (loopHierarchy.getEnclosingLoop(cont) != loop) { |
| 187 appendParameters(cont, loop.parameters); |
| 188 new LetCont(cont, null).insertAbove(loopBinding); |
| 189 } |
| 190 } |
| 191 let.continuations.removeWhere((cont) => cont.parent != let); |
| 192 if (let.continuations.isEmpty) { |
| 193 let.remove(); |
| 194 } |
| 195 } |
| 196 |
| 197 // Create a new branch to call the hoisted continuation or the loop: |
| 198 // |
| 199 // let loop(x) = |
| 200 // let loopCase() = LOOP |
| 201 // branch b hoistCase loopCase |
| 202 // in loop(i) |
| 203 // |
| 204 // ==> |
| 205 // |
| 206 // let newTrue() = hoistCase(i) |
| 207 // let newFalse() = |
| 208 // let loop(x) = |
| 209 // let loopCase() = LOOP |
| 210 // branch b hoistCase loopCase |
| 211 // branch b newTrue newFalse |
| 212 // |
| 213 InvokeContinuation loopEntry = loopBinding.body; |
| 214 List<Primitive> loopArgs = |
| 215 loopEntry.arguments.map((ref) => ref.definition).toList(); |
| 216 CpsFragment cps = new CpsFragment(); |
| 217 cps.branch(condition, |
| 218 strict: branch.isStrictCheck, |
| 219 negate: hoistedCase == falseCont) |
| 220 .invokeContinuation(hoistedCase, loopArgs); |
| 221 |
| 222 // The continuations created in the fragment need to have their loop header |
| 223 // set so the loop hierarchy remains intact |
| 224 loopHierarchy.update(cps, |
| 225 exitLoop: loopHierarchy.getEnclosingLoop(loop), |
| 226 catchLoop: catchLoopFor[loop]); |
| 227 |
| 228 // Insert above the loop. This will put the loop itself in a branch. |
| 229 cps.insertAbove(loopBinding); |
| 230 |
| 231 // Replace the old branch with the loopCase, still bound inside the loop: |
| 232 // |
| 233 // let loop(x) = |
| 234 // let loopCase() = LOOP |
| 235 // branch b hoistCase loopCase |
| 236 // in loop(i) |
| 237 // |
| 238 // ==> |
| 239 // |
| 240 // let loop(x) = |
| 241 // let loopCase() = LOOP |
| 242 // loopCase() |
| 243 // in loop(i) |
| 244 // |
| 245 destroyAndReplace(branch, new InvokeContinuation(loopCase, [])); |
| 246 |
| 247 // Record that at least one branch was hoisted to trigger alpha renaming. |
| 248 wasHoisted = true; |
| 249 |
| 250 return true; |
| 251 } |
| 252 } |
| OLD | NEW |