OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library dart2js.cps_ir.mutable_ssa; |
| 6 |
| 7 import 'cps_ir_nodes.dart'; |
| 8 import 'optimizers.dart'; |
| 9 |
| 10 /// Determines which mutable variables should be rewritten to phi assignments |
| 11 /// in this pass. |
| 12 /// |
| 13 /// We do not rewrite variables that have an assignment inside a try block that |
| 14 /// does not contain its declaration. |
| 15 class MutableVariablePreanalysis extends RecursiveVisitor { |
| 16 // Number of try blocks enclosing the current position. |
| 17 int currentDepth = 0; |
| 18 |
| 19 /// Number of try blocks enclosing the declaration of a given mutable |
| 20 /// variable. |
| 21 /// |
| 22 /// All mutable variables seen will be present in the map after the analysis. |
| 23 Map<MutableVariable, int> variableDepth = <MutableVariable, int>{}; |
| 24 |
| 25 /// Variables with an assignment inside a try block that does not contain |
| 26 /// its declaration. |
| 27 Set<MutableVariable> hasAssignmentInTry = new Set<MutableVariable>(); |
| 28 |
| 29 void visitLetHandler(LetHandler node) { |
| 30 ++currentDepth; |
| 31 visit(node.body); |
| 32 --currentDepth; |
| 33 visit(node.handler); |
| 34 } |
| 35 |
| 36 void processLetMutable(LetMutable node) { |
| 37 variableDepth[node.variable] = currentDepth; |
| 38 } |
| 39 |
| 40 void processSetMutableVariable(SetMutableVariable node) { |
| 41 MutableVariable variable = node.variable.definition; |
| 42 if (currentDepth > variableDepth[variable]) { |
| 43 hasAssignmentInTry.add(variable); |
| 44 } |
| 45 } |
| 46 |
| 47 /// True if there are no mutable variables or they are all assigned inside |
| 48 /// a try block. In this case, there is nothing to do and the pass should |
| 49 /// be skipped. |
| 50 bool get allMutablesAreAssignedInTryBlocks { |
| 51 return hasAssignmentInTry.length == variableDepth.length; |
| 52 } |
| 53 } |
| 54 |
| 55 /// Replaces mutable variables with continuation parameters, effectively |
| 56 /// bringing them into SSA form. |
| 57 /// |
| 58 /// This pass is intended to clean up mutable variables that were introduced |
| 59 /// by an optimization in the type propagation pass. |
| 60 /// |
| 61 /// This implementation potentially creates a lot of redundant and dead phi |
| 62 /// parameters. These will be cleaned up by redundant phi elimination and |
| 63 /// shrinking reductions. |
| 64 /// |
| 65 /// Discussion: |
| 66 /// For methods with a lot of mutable variables, creating all the spurious |
| 67 /// parameters might be too expensive. If this is the case, we should |
| 68 /// improve this pass to avoid most spurious parameters in practice. |
| 69 class MutableVariableEliminator implements Pass { |
| 70 String get passName => 'Mutable variable elimination'; |
| 71 |
| 72 /// Mutable variables currently in scope, in order of declaration. |
| 73 /// This list determines the order of the corresponding phi parameters. |
| 74 final List<MutableVariable> mutableVariables = <MutableVariable>[]; |
| 75 |
| 76 /// Number of phi parameters added to the given continuation. |
| 77 final Map<Continuation, int> continuationPhiCount = <Continuation, int>{}; |
| 78 |
| 79 /// Stack of yet unprocessed continuations interleaved with the |
| 80 /// mutable variables currently in scope. |
| 81 /// |
| 82 /// Continuations are processed when taken off the stack and mutable |
| 83 /// variables fall out of scope (i.e. removed from [mutableVariables]) when |
| 84 /// taken off the stack. |
| 85 final List<StackItem> stack = <StackItem>[]; |
| 86 |
| 87 MutableVariablePreanalysis analysis; |
| 88 |
| 89 void rewrite(FunctionDefinition node) { |
| 90 analysis = new MutableVariablePreanalysis()..visit(node); |
| 91 if (analysis.allMutablesAreAssignedInTryBlocks) { |
| 92 // Skip the pass if there is nothing to do. |
| 93 return; |
| 94 } |
| 95 processBlock(node.body, <MutableVariable, Primitive>{}); |
| 96 while (stack.isNotEmpty) { |
| 97 StackItem item = stack.removeLast(); |
| 98 if (item is ContinuationItem) { |
| 99 processBlock(item.continuation.body, item.environment); |
| 100 } else { |
| 101 assert(item is VariableItem); |
| 102 mutableVariables.removeLast(); |
| 103 } |
| 104 } |
| 105 } |
| 106 |
| 107 bool shouldRewrite(MutableVariable variable) { |
| 108 return !analysis.hasAssignmentInTry.contains(variable); |
| 109 } |
| 110 |
| 111 bool isJoinContinuation(Continuation cont) { |
| 112 return !cont.hasExactlyOneUse || |
| 113 cont.firstRef.parent is InvokeContinuation; |
| 114 } |
| 115 |
| 116 void removeNode(InteriorNode node) { |
| 117 InteriorNode parent = node.parent; |
| 118 parent.body = node.body; |
| 119 node.body.parent = parent; |
| 120 } |
| 121 |
| 122 /// If some useful source information is attached to exactly one of the |
| 123 /// two definitions, the information is copied onto the other. |
| 124 void mergeHints(MutableVariable variable, Primitive value) { |
| 125 if (variable.hint == null) { |
| 126 variable.hint = value.hint; |
| 127 } else if (value.hint == null) { |
| 128 value.hint = variable.hint; |
| 129 } |
| 130 } |
| 131 |
| 132 /// Processes a basic block, replacing mutable variable uses with direct |
| 133 /// references to their values. |
| 134 /// |
| 135 /// [environment] is the current value of each mutable variable. The map |
| 136 /// will be mutated during the processing. |
| 137 /// |
| 138 /// Continuations to be processed are put on the stack for later processing. |
| 139 void processBlock(Expression node, |
| 140 Map<MutableVariable, Primitive> environment) { |
| 141 for (; node is! TailExpression; node = node.next) { |
| 142 if (node is LetMutable && shouldRewrite(node.variable)) { |
| 143 // Put the new mutable variable on the stack while processing the body, |
| 144 // and pop it off again when done with the body. |
| 145 mutableVariables.add(node.variable); |
| 146 stack.add(new VariableItem()); |
| 147 |
| 148 // Put the initial value into the environment. |
| 149 Primitive value = node.value.definition; |
| 150 environment[node.variable] = value; |
| 151 |
| 152 // Preserve variable names. |
| 153 mergeHints(node.variable, value); |
| 154 |
| 155 // Remove the mutable variable binding. |
| 156 node.value.unlink(); |
| 157 removeNode(node); |
| 158 } else if (node is SetMutableVariable && |
| 159 shouldRewrite(node.variable.definition)) { |
| 160 // As above, update the environment, preserve variables and remove |
| 161 // the mutable variable assignment. |
| 162 MutableVariable variable = node.variable.definition; |
| 163 environment[variable] = node.value.definition; |
| 164 mergeHints(variable, node.value.definition); |
| 165 node.value.unlink(); |
| 166 removeNode(node); |
| 167 } else if (node is LetPrim && node.primitive is GetMutableVariable) { |
| 168 GetMutableVariable getter = node.primitive; |
| 169 MutableVariable variable = getter.variable.definition; |
| 170 if (shouldRewrite(variable)) { |
| 171 // Replace with the reaching definition from the environment. |
| 172 Primitive value = environment[variable]; |
| 173 value.substituteFor(getter); |
| 174 mergeHints(variable, value); |
| 175 removeNode(node); |
| 176 } |
| 177 } else if (node is LetCont) { |
| 178 // Create phi parameters for each join continuation bound here, and put |
| 179 // them on the stack for later processing. |
| 180 // Note that non-join continuations are handled at the use-site. |
| 181 for (Continuation cont in node.continuations) { |
| 182 if (!isJoinContinuation(cont)) continue; |
| 183 // Create a phi parameter for every mutable variable in scope. |
| 184 // At the same time, build the environment to use for processing |
| 185 // the continuation (mapping mutables to phi parameters). |
| 186 continuationPhiCount[cont] = mutableVariables.length; |
| 187 Map<MutableVariable, Primitive> environment = |
| 188 <MutableVariable, Primitive>{}; |
| 189 for (MutableVariable variable in mutableVariables) { |
| 190 Parameter phi = new Parameter(variable.hint); |
| 191 cont.parameters.add(phi); |
| 192 phi.parent = cont; |
| 193 environment[variable] = phi; |
| 194 } |
| 195 stack.add(new ContinuationItem(cont, environment)); |
| 196 } |
| 197 } else if (node is LetHandler) { |
| 198 // Process the catch block later and continue into the try block. |
| 199 // We can use the same environment object for the try and catch blocks. |
| 200 // The current environment bindings cannot change inside the try block |
| 201 // because we exclude all variables assigned inside a try block. |
| 202 // The environment might be extended with more bindings before we |
| 203 // analyze the catch block, but that's ok. |
| 204 stack.add(new ContinuationItem(node.handler, environment)); |
| 205 } |
| 206 } |
| 207 |
| 208 // Analyze the terminal node. |
| 209 if (node is InvokeContinuation) { |
| 210 Continuation cont = node.continuation.definition; |
| 211 if (cont.isReturnContinuation) return; |
| 212 // This is a call to a join continuation. Add arguments for the phi |
| 213 // parameters that were added to this continuation. |
| 214 int phiCount = continuationPhiCount[cont]; |
| 215 for (int i = 0; i < phiCount; ++i) { |
| 216 Primitive value = environment[mutableVariables[i]]; |
| 217 Reference<Primitive> arg = new Reference<Primitive>(value); |
| 218 node.arguments.add(arg); |
| 219 arg.parent = node; |
| 220 } |
| 221 } else if (node is Branch) { |
| 222 // Enqueue both branches with the current environment. |
| 223 // Clone the environments once so the processing of one branch does not |
| 224 // mutate the environment needed to process the other branch. |
| 225 stack.add(new ContinuationItem( |
| 226 node.trueContinuation.definition, |
| 227 new Map<MutableVariable, Primitive>.from(environment))); |
| 228 stack.add(new ContinuationItem( |
| 229 node.falseContinuation.definition, |
| 230 environment)); |
| 231 } else { |
| 232 assert(node is Throw || node is Unreachable); |
| 233 } |
| 234 } |
| 235 } |
| 236 |
| 237 abstract class StackItem {} |
| 238 |
| 239 /// Represents a mutable variable that is in scope. |
| 240 /// |
| 241 /// The topmost mutable variable falls out of scope when this item is |
| 242 /// taken off the stack. |
| 243 class VariableItem extends StackItem {} |
| 244 |
| 245 /// Represents a yet unprocessed continuation together with the |
| 246 /// environment in which to process it. |
| 247 class ContinuationItem extends StackItem { |
| 248 final Continuation continuation; |
| 249 final Map<MutableVariable, Primitive> environment; |
| 250 |
| 251 ContinuationItem(this.continuation, this.environment); |
| 252 } |
OLD | NEW |