| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of tree_ir.optimization; | 5 part of tree_ir.optimization; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Performs the following transformations on the tree: | 8 * Performs the following transformations on the tree: |
| 9 * - Assignment propagation | 9 * - Assignment inlining |
| 10 * - Assignment expression propagation |
| 10 * - If-to-conditional conversion | 11 * - If-to-conditional conversion |
| 11 * - Flatten nested ifs | 12 * - Flatten nested ifs |
| 12 * - Break inlining | 13 * - Break inlining |
| 13 * - Redirect breaks | 14 * - Redirect breaks |
| 14 * | 15 * |
| 15 * The above transformations all eliminate statements from the tree, and may | 16 * The above transformations all eliminate statements from the tree, and may |
| 16 * introduce redexes of each other. | 17 * introduce redexes of each other. |
| 17 * | 18 * |
| 18 * | 19 * |
| 19 * ASSIGNMENT PROPAGATION: | 20 * ASSIGNMENT INLINING: |
| 20 * Single-use definitions are propagated to their use site when possible. | 21 * Single-use definitions are inlined at their use site when possible. |
| 21 * For example: | 22 * For example: |
| 22 * | 23 * |
| 23 * { v0 = foo(); return v0; } | 24 * { v0 = foo(); return v0; } |
| 24 * ==> | 25 * ==> |
| 25 * return foo() | 26 * return foo() |
| 26 * | 27 * |
| 27 * After translating out of CPS, all intermediate values are bound by [Assign]. | 28 * After translating out of CPS, all intermediate values are bound by [Assign]. |
| 28 * This transformation propagates such definitions to their uses when it is | 29 * This transformation propagates such definitions to their uses when it is |
| 29 * safe and profitable. Bindings are processed "on demand" when their uses are | 30 * safe and profitable. Bindings are processed "on demand" when their uses are |
| 30 * seen, but are only processed once to keep this transformation linear in | 31 * seen, but are only processed once to keep this transformation linear in |
| 31 * the size of the tree. | 32 * the size of the tree. |
| 32 * | 33 * |
| 33 * The transformation builds an environment containing [Assign] bindings that | 34 * The transformation builds an environment containing [Assign] bindings that |
| 34 * are in scope. These bindings have yet-untranslated definitions. When a use | 35 * are in scope. These bindings have yet-untranslated definitions. When a use |
| 35 * is encountered the transformation determines if it is safe and profitable | 36 * is encountered the transformation determines if it is safe and profitable |
| 36 * to propagate the definition to its use. If so, it is removed from the | 37 * to propagate the definition to its use. If so, it is removed from the |
| 37 * environment and the definition is recursively processed (in the | 38 * environment and the definition is recursively processed (in the |
| 38 * new environment at the use site) before being propagated. | 39 * new environment at the use site) before being propagated. |
| 39 * | 40 * |
| 40 * See [visitVariableUse] for the implementation of the heuristic for | 41 * See [visitVariableUse] for the implementation of the heuristic for |
| 41 * propagating a definition. | 42 * propagating a definition. |
| 42 * | 43 * |
| 43 * | 44 * |
| 45 * ASSIGNMENT EXPRESSION PROPAGATION: |
| 46 * Definitions with multiple uses are propagated to their first use site |
| 47 * when possible. For example: |
| 48 * |
| 49 * { v0 = foo(); bar(v0); return v0; } |
| 50 * ==> |
| 51 * { bar(v0 = foo()); return v0; } |
| 52 * |
| 53 * Note that the [RestoreInitializers] phase will later undo this rewrite |
| 54 * in cases where it prevents an assignment from being pulled into an |
| 55 * initializer. |
| 56 * |
| 57 * |
| 44 * IF-TO-CONDITIONAL CONVERSION: | 58 * IF-TO-CONDITIONAL CONVERSION: |
| 45 * If-statement are converted to conditional expressions when possible. | 59 * If-statement are converted to conditional expressions when possible. |
| 46 * For example: | 60 * For example: |
| 47 * | 61 * |
| 48 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } | 62 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } |
| 49 * ==> | 63 * ==> |
| 50 * { v1 = v0 ? foo() : bar(); break L } | 64 * { v1 = v0 ? foo() : bar(); break L } |
| 51 * | 65 * |
| 52 * This can lead to inlining of L, which in turn can lead to further propagation | 66 * This can lead to inlining of L, which in turn can lead to further propagation |
| 53 * of the variable v1. | 67 * of the variable v1. |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 92 * separated two ifs. | 106 * separated two ifs. |
| 93 */ | 107 */ |
| 94 class StatementRewriter extends Transformer implements Pass { | 108 class StatementRewriter extends Transformer implements Pass { |
| 95 String get passName => 'Statement rewriter'; | 109 String get passName => 'Statement rewriter'; |
| 96 | 110 |
| 97 @override | 111 @override |
| 98 void rewrite(RootNode node) { | 112 void rewrite(RootNode node) { |
| 99 node.replaceEachBody(visitStatement); | 113 node.replaceEachBody(visitStatement); |
| 100 } | 114 } |
| 101 | 115 |
| 102 // The binding environment. The rightmost element of the list is the nearest | 116 /// True if targeting Dart. |
| 103 // available enclosing binding. | 117 final bool isDartMode; |
| 104 List<Assign> environment = <Assign>[]; | 118 |
| 119 /// The most recently evaluated impure expressions, with the most recent |
| 120 /// expression being last. |
| 121 /// |
| 122 /// Most importantly, this contains [Assign] expressions that we attempt to |
| 123 /// inline at their use site. It also contains other impure expressions that |
| 124 /// we can propagate to a variable use if they are known to return the value |
| 125 /// of that variable. |
| 126 /// |
| 127 /// Except for [Conditional]s, expressions in the environment have |
| 128 /// not been processed, and all their subexpressions must therefore be |
| 129 /// variables uses. |
| 130 List<Expression> environment = <Expression>[]; |
| 105 | 131 |
| 106 /// Binding environment for variables that are assigned to effectively | 132 /// Binding environment for variables that are assigned to effectively |
| 107 /// constant expressions (see [isEffectivelyConstant]). | 133 /// constant expressions (see [isEffectivelyConstant]). |
| 108 final Map<Variable, Expression> constantEnvironment; | 134 final Map<Variable, Expression> constantEnvironment; |
| 109 | 135 |
| 110 /// Substitution map for labels. Any break to a label L should be substituted | 136 /// Substitution map for labels. Any break to a label L should be substituted |
| 111 /// for a break to L' if L maps to L'. | 137 /// for a break to L' if L maps to L'. |
| 112 Map<Label, Jump> labelRedirects = <Label, Jump>{}; | 138 Map<Label, Jump> labelRedirects = <Label, Jump>{}; |
| 113 | 139 |
| 140 /// Number of uses seen so far. Used to detect the first use of a variable |
| 141 /// (since we do backwards traversal, the first use is the last one seen). |
| 142 Map<Variable, int> seenUses = <Variable, int>{}; |
| 143 |
| 114 /// Rewriter for methods. | 144 /// Rewriter for methods. |
| 115 StatementRewriter() : constantEnvironment = <Variable, Expression>{}; | 145 StatementRewriter({this.isDartMode}) |
| 146 : constantEnvironment = <Variable, Expression>{} { |
| 147 assert(isDartMode != null); |
| 148 } |
| 116 | 149 |
| 117 /// Rewriter for nested functions. | 150 /// Rewriter for nested functions. |
| 118 StatementRewriter.nested(StatementRewriter parent) | 151 StatementRewriter.nested(StatementRewriter parent) |
| 119 : constantEnvironment = parent.constantEnvironment; | 152 : constantEnvironment = parent.constantEnvironment, |
| 153 seenUses = parent.seenUses, |
| 154 isDartMode = parent.isDartMode; |
| 120 | 155 |
| 121 /// A set of labels that can be safely inlined at their use. | 156 /// A set of labels that can be safely inlined at their use. |
| 122 /// | 157 /// |
| 123 /// The successor statements for labeled statements that have only one break | 158 /// The successor statements for labeled statements that have only one break |
| 124 /// from them are normally rewritten inline at the site of the break. This | 159 /// from them are normally rewritten inline at the site of the break. This |
| 125 /// is not safe if the code would be moved inside the scope of an exception | 160 /// is not safe if the code would be moved inside the scope of an exception |
| 126 /// handler (i.e., if the code would be moved into a try from outside it). | 161 /// handler (i.e., if the code would be moved into a try from outside it). |
| 127 Set<Label> safeForInlining = new Set<Label>(); | 162 Set<Label> safeForInlining = new Set<Label>(); |
| 128 | 163 |
| 129 /// Returns the redirect target of [jump] or [jump] itself if it should not | 164 /// Returns the redirect target of [jump] or [jump] itself if it should not |
| 130 /// be redirected. | 165 /// be redirected. |
| 131 Jump redirect(Jump jump) { | 166 Jump redirect(Jump jump) { |
| 132 Jump newJump = labelRedirects[jump.target]; | 167 Jump newJump = labelRedirects[jump.target]; |
| 133 return newJump != null ? newJump : jump; | 168 return newJump != null ? newJump : jump; |
| 134 } | 169 } |
| 135 | 170 |
| 136 void inEmptyEnvironment(void action()) { | 171 void inEmptyEnvironment(void action()) { |
| 137 List<Assign> oldEnvironment = environment; | 172 List oldEnvironment = environment; |
| 138 environment = <Assign>[]; | 173 environment = <Expression>[]; |
| 139 action(); | 174 action(); |
| 140 assert(environment.isEmpty); | 175 assert(environment.isEmpty); |
| 141 environment = oldEnvironment; | 176 environment = oldEnvironment; |
| 142 } | 177 } |
| 143 | 178 |
| 144 Expression visitExpression(Expression e) => e.processed ? e : e.accept(this); | 179 /// Left-hand side of the given assignment, or `null` if not an assignment. |
| 180 Variable getLeftHand(Expression e) { |
| 181 return e is Assign ? e.variable : null; |
| 182 } |
| 183 |
| 184 /// If the given expression always returns the value of one of its |
| 185 /// subexpressions, returns that subexpression, otherwise `null`. |
| 186 Expression getValueSubexpression(Expression e) { |
| 187 if (isDartMode && |
| 188 e is InvokeMethod && |
| 189 (e.selector.isSetter || e.selector.isIndexSet)) { |
| 190 return e.arguments.last; |
| 191 } |
| 192 if (e is SetField) return e.value; |
| 193 return null; |
| 194 } |
| 195 |
| 196 /// If the given expression always returns the value of one of its |
| 197 /// subexpressions, and that subexpression is a variable use, returns that |
| 198 /// variable. Otherwise `null`. |
| 199 Variable getRightHand(Expression e) { |
| 200 Expression value = getValueSubexpression(e); |
| 201 return value is VariableUse ? value.variable : null; |
| 202 } |
| 145 | 203 |
| 146 @override | 204 @override |
| 147 Expression visitVariableUse(VariableUse node) { | 205 Expression visitVariableUse(VariableUse node) { |
| 206 // Count of number of uses seen so far. |
| 207 seenUses[node.variable] = 1 + seenUses.putIfAbsent(node.variable, () => 0); |
| 208 |
| 209 // We traverse the tree right-to-left, so when we have seen all uses, |
| 210 // it means we are looking at the first use. |
| 211 assert(seenUses[node.variable] <= node.variable.readCount); |
| 212 bool isFirstUse = seenUses[node.variable] == node.variable.readCount; |
| 213 |
| 148 // Propagate constant to use site. | 214 // Propagate constant to use site. |
| 149 Expression constant = constantEnvironment[node.variable]; | 215 Expression constant = constantEnvironment[node.variable]; |
| 150 if (constant != null) { | 216 if (constant != null) { |
| 151 --node.variable.readCount; | 217 --node.variable.readCount; |
| 218 --seenUses[node.variable]; // Do not count the use we just destroyed. |
| 152 return constant; | 219 return constant; |
| 153 } | 220 } |
| 154 | 221 |
| 155 // Propagate a variable's definition to its use site if: | 222 // Try to propagate another expression into this variable use. |
| 156 // 1. It has a single use, to avoid code growth and potential duplication | 223 if (!environment.isEmpty) { |
| 157 // of side effects, AND | 224 Expression binding = environment.last; |
| 158 // 2. It was the most recent expression evaluated so that we do not | 225 |
| 159 // reorder expressions with side effects. | 226 // Is this variable assigned by the most recently evaluated impure |
| 160 if (!environment.isEmpty && | 227 // expression? |
| 161 environment.last.variable == node.variable && | 228 // |
| 162 node.variable.readCount == 1) { | 229 // If so, propagate the assignment, e.g: |
| 163 --node.variable.readCount; | 230 // |
| 164 return visitExpression(environment.removeLast().value); | 231 // { x = foo(); bar(x, x) } ==> bar(x = foo(), x) |
| 232 // |
| 233 // We must ensure that no other uses separate this use from the |
| 234 // assignment. We therefore only propagate assignments into the first use. |
| 235 // |
| 236 // Note that if this is only use, `visitAssign` will then remove the |
| 237 // redundant assignment. |
| 238 if (getLeftHand(binding) == node.variable && isFirstUse) { |
| 239 environment.removeLast(); |
| 240 --node.variable.readCount; |
| 241 --seenUses[node.variable]; // Do not count the use we just destroyed. |
| 242 return visitExpression(binding); |
| 243 } |
| 244 |
| 245 // Is the most recently evaluated impure expression known to have the |
| 246 // value of this variable? |
| 247 // |
| 248 // If so, we can replace this use with the impure expression, e.g: |
| 249 // |
| 250 // { E.foo = x; bar(x) } ==> bar(E.foo = x) |
| 251 // |
| 252 if (getRightHand(binding) == node.variable) { |
| 253 environment.removeLast(); |
| 254 --node.variable.readCount; |
| 255 --seenUses[node.variable]; |
| 256 return visitExpression(binding); |
| 257 } |
| 165 } | 258 } |
| 166 | 259 |
| 167 // If the definition could not be propagated, leave the variable use. | 260 // If the definition could not be propagated, leave the variable use. |
| 168 return node; | 261 return node; |
| 169 } | 262 } |
| 170 | 263 |
| 171 /// Returns true if [exp] has no side effects and has a constant value within | 264 /// Returns true if [exp] has no side effects and has a constant value within |
| 172 /// any given activation of the enclosing method. | 265 /// any given activation of the enclosing method. |
| 173 bool isEffectivelyConstant(Expression exp) { | 266 bool isEffectivelyConstant(Expression exp) { |
| 174 // TODO(asgerf): Can be made more aggressive e.g. by checking conditional | 267 // TODO(asgerf): Can be made more aggressive e.g. by checking conditional |
| 175 // expressions recursively. Determine if that is a valuable optimization | 268 // expressions recursively. Determine if that is a valuable optimization |
| 176 // and/or if it is better handled at the CPS level. | 269 // and/or if it is better handled at the CPS level. |
| 177 return exp is Constant || | 270 return exp is Constant || |
| 178 exp is This || | 271 exp is This || |
| 179 exp is ReifyTypeVar || | 272 exp is ReifyTypeVar || |
| 180 exp is VariableUse && constantEnvironment.containsKey(exp.variable); | 273 exp is VariableUse && constantEnvironment.containsKey(exp.variable); |
| 181 } | 274 } |
| 182 | 275 |
| 183 Statement visitAssign(Assign node) { | 276 /// True if [node] is an assignment that can be propagated as a constant. |
| 184 if (isEffectivelyConstant(node.value) && | 277 bool isEffectivelyConstantAssignment(Expression node) { |
| 185 node.variable.writeCount == 1) { | 278 return node is Assign && |
| 279 node.variable.writeCount == 1 && |
| 280 isEffectivelyConstant(node.value); |
| 281 } |
| 282 |
| 283 Statement visitExpressionStatement(ExpressionStatement stmt) { |
| 284 if (isEffectivelyConstantAssignment(stmt.expression)) { |
| 285 Assign assign = stmt.expression; |
| 186 // Handle constant assignments specially. | 286 // Handle constant assignments specially. |
| 187 // They are always safe to propagate (though we should avoid duplication). | 287 // They are always safe to propagate (though we should avoid duplication). |
| 188 // Moreover, they should not prevent other expressions from propagating. | 288 // Moreover, they should not prevent other expressions from propagating. |
| 189 if (node.variable.readCount <= 1) { | 289 if (assign.variable.readCount <= 1) { |
| 190 // A single-use constant should always be propagted to its use site. | 290 // A single-use constant should always be propagted to its use site. |
| 191 constantEnvironment[node.variable] = visitExpression(node.value); | 291 constantEnvironment[assign.variable] = visitExpression(assign.value); |
| 192 --node.variable.writeCount; | 292 --assign.variable.writeCount; |
| 193 return visitStatement(node.next); | 293 return visitStatement(stmt.next); |
| 194 } else { | 294 } else { |
| 195 // With more than one use, we cannot propagate the constant. | 295 // With more than one use, we cannot propagate the constant. |
| 196 // Visit the following statement without polluting [environment] so | 296 // Visit the following statement without polluting [environment] so |
| 197 // that any preceding non-constant assignments might still propagate. | 297 // that any preceding non-constant assignments might still propagate. |
| 198 node.next = visitStatement(node.next); | 298 stmt.next = visitStatement(stmt.next); |
| 199 node.value = visitExpression(node.value); | 299 assign.value = visitExpression(assign.value); |
| 200 return node; | 300 return stmt; |
| 201 } | 301 } |
| 302 } |
| 303 // Try to propagate the expression, and block previous impure expressions |
| 304 // until this has propagated. |
| 305 environment.add(stmt.expression); |
| 306 stmt.next = visitStatement(stmt.next); |
| 307 if (!environment.isEmpty && environment.last == stmt.expression) { |
| 308 // Retain the expression statement. |
| 309 environment.removeLast(); |
| 310 stmt.expression = visitExpression(stmt.expression); |
| 311 return stmt; |
| 202 } else { | 312 } else { |
| 203 // Try to propagate assignment, and block previous assignment until this | 313 // Expression was propagated into the successor. |
| 204 // has propagated. | 314 return stmt.next; |
| 205 environment.add(node); | |
| 206 Statement next = visitStatement(node.next); | |
| 207 if (!environment.isEmpty && environment.last == node) { | |
| 208 // The definition could not be propagated. Residualize the let binding. | |
| 209 node.next = next; | |
| 210 environment.removeLast(); | |
| 211 node.value = visitExpression(node.value); | |
| 212 return node; | |
| 213 } | |
| 214 assert(!environment.contains(node)); | |
| 215 --node.variable.writeCount; // This assignment was removed. | |
| 216 return next; | |
| 217 } | 315 } |
| 218 } | 316 } |
| 219 | 317 |
| 318 Expression visitAssign(Assign node) { |
| 319 node.value = visitExpression(node.value); |
| 320 // Remove assignments to variables without any uses. This can happen |
| 321 // because the assignment was propagated into its use, e.g: |
| 322 // |
| 323 // { x = foo(); bar(x) } ==> bar(x = foo()) ==> bar(foo()) |
| 324 // |
| 325 if (node.variable.readCount == 0) { |
| 326 --node.variable.writeCount; |
| 327 return node.value; |
| 328 } |
| 329 return node; |
| 330 } |
| 331 |
| 332 Statement visitVariableDeclaration(VariableDeclaration node) { |
| 333 if (isEffectivelyConstant(node.value)) { |
| 334 node.next = visitStatement(node.next); |
| 335 } else { |
| 336 inEmptyEnvironment(() { |
| 337 node.next = visitStatement(node.next); |
| 338 }); |
| 339 } |
| 340 node.value = visitExpression(node.value); |
| 341 return node; |
| 342 } |
| 343 |
| 220 Expression visitInvokeStatic(InvokeStatic node) { | 344 Expression visitInvokeStatic(InvokeStatic node) { |
| 221 // Process arguments right-to-left, the opposite of evaluation order. | 345 // Process arguments right-to-left, the opposite of evaluation order. |
| 222 for (int i = node.arguments.length - 1; i >= 0; --i) { | 346 for (int i = node.arguments.length - 1; i >= 0; --i) { |
| 223 node.arguments[i] = visitExpression(node.arguments[i]); | 347 node.arguments[i] = visitExpression(node.arguments[i]); |
| 224 } | 348 } |
| 225 return node; | 349 return node; |
| 226 } | 350 } |
| 227 | 351 |
| 228 Expression visitInvokeMethod(InvokeMethod node) { | 352 Expression visitInvokeMethod(InvokeMethod node) { |
| 229 for (int i = node.arguments.length - 1; i >= 0; --i) { | 353 for (int i = node.arguments.length - 1; i >= 0; --i) { |
| (...skipping 19 matching lines...) Expand all Loading... |
| 249 } | 373 } |
| 250 | 374 |
| 251 Expression visitConcatenateStrings(ConcatenateStrings node) { | 375 Expression visitConcatenateStrings(ConcatenateStrings node) { |
| 252 for (int i = node.arguments.length - 1; i >= 0; --i) { | 376 for (int i = node.arguments.length - 1; i >= 0; --i) { |
| 253 node.arguments[i] = visitExpression(node.arguments[i]); | 377 node.arguments[i] = visitExpression(node.arguments[i]); |
| 254 } | 378 } |
| 255 return node; | 379 return node; |
| 256 } | 380 } |
| 257 | 381 |
| 258 Expression visitConditional(Conditional node) { | 382 Expression visitConditional(Conditional node) { |
| 259 node.condition = visitExpression(node.condition); | 383 // Conditional expressions do not exist in the input, but they are |
| 260 | 384 // introduced by if-to-conditional conversion. |
| 261 inEmptyEnvironment(() { | 385 // Their subexpressions have already been processed; do not reprocess them. |
| 262 node.thenExpression = visitExpression(node.thenExpression); | 386 // |
| 263 node.elseExpression = visitExpression(node.elseExpression); | 387 // Note that this can only happen for conditional expressions. It is an |
| 264 }); | 388 // error for any other type of expression to be visited twice or to be |
| 265 | 389 // created and then visited. We use this special treatment of conditionals |
| 390 // to allow for assignment inlining after if-to-conditional conversion. |
| 391 // |
| 392 // There are several reasons we should not reprocess the subexpressions: |
| 393 // |
| 394 // - It will mess up the [seenUses] counter, since a single use will be |
| 395 // counted twice. |
| 396 // |
| 397 // - Other visit methods assume that all subexpressions are variable uses |
| 398 // because they come fresh out of the tree IR builder. |
| 399 // |
| 400 // - Reprocessing can be expensive. |
| 401 // |
| 266 return node; | 402 return node; |
| 267 } | 403 } |
| 268 | 404 |
| 269 Expression visitLogicalOperator(LogicalOperator node) { | 405 Expression visitLogicalOperator(LogicalOperator node) { |
| 270 node.left = visitExpression(node.left); | 406 node.left = visitExpression(node.left); |
| 271 | 407 |
| 272 // Impure expressions may not propagate across the branch. | 408 // Impure expressions may not propagate across the branch. |
| 273 inEmptyEnvironment(() { | 409 inEmptyEnvironment(() { |
| 274 node.right = visitExpression(node.right); | 410 node.right = visitExpression(node.right); |
| 275 }); | 411 }); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 291 new StatementRewriter.nested(this).rewrite(node.definition); | 427 new StatementRewriter.nested(this).rewrite(node.definition); |
| 292 node.next = visitStatement(node.next); | 428 node.next = visitStatement(node.next); |
| 293 return node; | 429 return node; |
| 294 } | 430 } |
| 295 | 431 |
| 296 Statement visitReturn(Return node) { | 432 Statement visitReturn(Return node) { |
| 297 node.value = visitExpression(node.value); | 433 node.value = visitExpression(node.value); |
| 298 return node; | 434 return node; |
| 299 } | 435 } |
| 300 | 436 |
| 301 | |
| 302 Statement visitBreak(Break node) { | 437 Statement visitBreak(Break node) { |
| 303 // Redirect through chain of breaks. | 438 // Redirect through chain of breaks. |
| 304 // Note that useCount was accounted for at visitLabeledStatement. | 439 // Note that useCount was accounted for at visitLabeledStatement. |
| 305 // Note redirect may return either a Break or Continue statement. | 440 // Note redirect may return either a Break or Continue statement. |
| 306 Jump jump = redirect(node); | 441 Jump jump = redirect(node); |
| 307 if (jump is Break && | 442 if (jump is Break && |
| 308 jump.target.useCount == 1 && | 443 jump.target.useCount == 1 && |
| 309 safeForInlining.contains(jump.target)) { | 444 safeForInlining.contains(jump.target)) { |
| 310 --jump.target.useCount; | 445 --jump.target.useCount; |
| 311 return visitStatement(jump.target.binding.next); | 446 return visitStatement(jump.target.binding.next); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 355 node.condition = visitExpression(node.condition); | 490 node.condition = visitExpression(node.condition); |
| 356 | 491 |
| 357 // Do not propagate assignments into branches. Doing so will lead to code | 492 // Do not propagate assignments into branches. Doing so will lead to code |
| 358 // duplication. | 493 // duplication. |
| 359 // TODO(kmillikin): Rethink this. Propagating some assignments | 494 // TODO(kmillikin): Rethink this. Propagating some assignments |
| 360 // (e.g. variables) is benign. If they can occur here, they should | 495 // (e.g. variables) is benign. If they can occur here, they should |
| 361 // be handled well. | 496 // be handled well. |
| 362 inEmptyEnvironment(() { | 497 inEmptyEnvironment(() { |
| 363 node.thenStatement = visitStatement(node.thenStatement); | 498 node.thenStatement = visitStatement(node.thenStatement); |
| 364 node.elseStatement = visitStatement(node.elseStatement); | 499 node.elseStatement = visitStatement(node.elseStatement); |
| 500 |
| 501 tryCollapseIf(node); |
| 365 }); | 502 }); |
| 366 | 503 |
| 367 tryCollapseIf(node); | 504 Statement reduced = combineStatementsInBranches( |
| 368 | |
| 369 Statement reduced = combineStatementsWithSubexpressions( | |
| 370 node.thenStatement, | 505 node.thenStatement, |
| 371 node.elseStatement, | 506 node.elseStatement, |
| 372 (t,f) => new Conditional(node.condition, t, f)..processed = true); | 507 node.condition); |
| 373 if (reduced != null) { | 508 if (reduced != null) { |
| 374 // TODO(asgerf): Avoid revisiting nodes or visiting nodes that we created. | 509 return reduced; |
| 375 // This breaks the assumption that all subexpressions are | |
| 376 // variable uses, and it can be expensive. | |
| 377 // Revisit in case the break can now be inlined. | |
| 378 return visitStatement(reduced); | |
| 379 } | 510 } |
| 380 | 511 |
| 381 return node; | 512 return node; |
| 382 } | 513 } |
| 383 | 514 |
| 384 Statement visitWhileTrue(WhileTrue node) { | 515 Statement visitWhileTrue(WhileTrue node) { |
| 385 // Do not propagate assignments into loops. Doing so is not safe for | 516 // Do not propagate assignments into loops. Doing so is not safe for |
| 386 // variables modified in the loop (the initial value will be propagated). | 517 // variables modified in the loop (the initial value will be propagated). |
| 387 inEmptyEnvironment(() { | 518 inEmptyEnvironment(() { |
| 388 node.body = visitStatement(node.body); | 519 node.body = visitStatement(node.body); |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 433 entry.key = visitExpression(entry.key); | 564 entry.key = visitExpression(entry.key); |
| 434 } | 565 } |
| 435 return node; | 566 return node; |
| 436 } | 567 } |
| 437 | 568 |
| 438 Expression visitTypeOperator(TypeOperator node) { | 569 Expression visitTypeOperator(TypeOperator node) { |
| 439 node.receiver = visitExpression(node.receiver); | 570 node.receiver = visitExpression(node.receiver); |
| 440 return node; | 571 return node; |
| 441 } | 572 } |
| 442 | 573 |
| 443 Statement visitExpressionStatement(ExpressionStatement node) { | 574 Expression visitSetField(SetField node) { |
| 444 node.expression = visitExpression(node.expression); | |
| 445 // Do not allow propagation of assignments past an expression evaluated | |
| 446 // for its side effects because it risks reordering side effects. | |
| 447 // TODO(kmillikin): Rethink this. Some propagation is benign, | |
| 448 // e.g. variables, or other pure values that are not destroyed by | |
| 449 // the expression statement. If they can occur here they should be | |
| 450 // handled well. | |
| 451 inEmptyEnvironment(() { | |
| 452 node.next = visitStatement(node.next); | |
| 453 }); | |
| 454 return node; | |
| 455 } | |
| 456 | |
| 457 Statement visitSetField(SetField node) { | |
| 458 node.next = visitStatement(node.next); | |
| 459 node.value = visitExpression(node.value); | 575 node.value = visitExpression(node.value); |
| 460 node.object = visitExpression(node.object); | 576 node.object = visitExpression(node.object); |
| 461 return node; | 577 return node; |
| 462 } | 578 } |
| 463 | 579 |
| 464 Expression visitGetField(GetField node) { | 580 Expression visitGetField(GetField node) { |
| 465 node.object = visitExpression(node.object); | 581 node.object = visitExpression(node.object); |
| 466 return node; | 582 return node; |
| 467 } | 583 } |
| 468 | 584 |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 504 /// If [combine] returns E1 then the unified statement is equivalent to [s], | 620 /// If [combine] returns E1 then the unified statement is equivalent to [s], |
| 505 /// and if [combine] returns E2 the unified statement is equivalence to [t]. | 621 /// and if [combine] returns E2 the unified statement is equivalence to [t]. |
| 506 /// | 622 /// |
| 507 /// It is guaranteed that no side effects occur between the beginning of the | 623 /// It is guaranteed that no side effects occur between the beginning of the |
| 508 /// statement and the position of the combined expression. | 624 /// statement and the position of the combined expression. |
| 509 /// | 625 /// |
| 510 /// Returns null if the statements are too different. | 626 /// Returns null if the statements are too different. |
| 511 /// | 627 /// |
| 512 /// If non-null is returned, the caller MUST discard [s] and [t] and use | 628 /// If non-null is returned, the caller MUST discard [s] and [t] and use |
| 513 /// the returned statement instead. | 629 /// the returned statement instead. |
| 514 static Statement combineStatementsWithSubexpressions( | 630 Statement combineStatementsInBranches( |
| 515 Statement s, | 631 Statement s, |
| 516 Statement t, | 632 Statement t, |
| 517 Expression combine(Expression s, Expression t)) { | 633 Expression condition) { |
| 518 if (s is Return && t is Return) { | 634 if (s is Return && t is Return) { |
| 519 return new Return(combine(s.value, t.value)); | 635 return new Return(new Conditional(condition, s.value, t.value)); |
| 520 } | |
| 521 if (s is Assign && t is Assign && s.variable == t.variable) { | |
| 522 Statement next = combineStatements(s.next, t.next); | |
| 523 if (next != null) { | |
| 524 // Destroy both original assignments to the variable. | |
| 525 --s.variable.writeCount; | |
| 526 --t.variable.writeCount; | |
| 527 // The Assign constructor will increment the reference count again. | |
| 528 return new Assign(s.variable, | |
| 529 combine(s.value, t.value), | |
| 530 next); | |
| 531 } | |
| 532 } | 636 } |
| 533 if (s is ExpressionStatement && t is ExpressionStatement) { | 637 if (s is ExpressionStatement && t is ExpressionStatement) { |
| 638 // Combine the two expressions and the two successor statements. |
| 639 // |
| 640 // C ? {E1 ; S1} : {E2 ; S2} |
| 641 // ==> |
| 642 // (C ? E1 : E2) : combine(S1, S2) |
| 643 // |
| 644 // If E1 and E2 are assignments, we want to propagate these into the |
| 645 // combined statement. |
| 646 // |
| 647 // It might not be possible to combine the statements, so we combine the |
| 648 // expressions, put the result in the environment, and then uncombine the |
| 649 // expressions if the statements could not be combined. |
| 650 |
| 651 // Combine the expressions. |
| 652 CombinedExpressions values = |
| 653 combineAsConditional(s.expression, t.expression, condition); |
| 654 |
| 655 // Put this into the environment and try to combine the statements. |
| 656 // We are not in risk of reprocessing the original subexpressions because |
| 657 // the combined expression will always hide them inside a Conditional. |
| 658 environment.add(values.combined); |
| 534 Statement next = combineStatements(s.next, t.next); | 659 Statement next = combineStatements(s.next, t.next); |
| 535 if (next != null) { | 660 |
| 536 return new ExpressionStatement(combine(s.expression, t.expression), | 661 if (next == null) { |
| 537 next); | 662 // Statements could not be combined. |
| 663 // Restore the environment and uncombine expressions again. |
| 664 environment.removeLast(); |
| 665 values.uncombine(); |
| 666 return null; |
| 667 } else if (!environment.isEmpty && environment.last == values.combined) { |
| 668 // Statements were combined but the combined expression could not be |
| 669 // propagated. Leave it as an expression statement here. |
| 670 environment.removeLast(); |
| 671 s.expression = values.combined; |
| 672 s.next = next; |
| 673 return s; |
| 674 } else { |
| 675 // Statements were combined and the combined expressions were |
| 676 // propagated into the combined statement. |
| 677 return next; |
| 538 } | 678 } |
| 539 } | 679 } |
| 540 return null; | 680 return null; |
| 541 } | 681 } |
| 542 | 682 |
| 683 /// Creates the expression `[condition] ? [s] : [t]` or an equivalent |
| 684 /// expression if something better can be done. |
| 685 /// |
| 686 /// In particular, assignments will be merged as follows: |
| 687 /// |
| 688 /// C ? (v = E1) : (v = E2) |
| 689 /// ==> |
| 690 /// v = C ? E1 : E2 |
| 691 /// |
| 692 /// The latter form is more compact and can also be inlined. |
| 693 CombinedExpressions combineAsConditional( |
| 694 Expression s, |
| 695 Expression t, |
| 696 Expression condition) { |
| 697 if (s is Assign && t is Assign && s.variable == t.variable) { |
| 698 Expression values = new Conditional(condition, s.value, t.value); |
| 699 return new CombinedAssigns(s, t, new CombinedExpressions(values)); |
| 700 } |
| 701 return new CombinedExpressions(new Conditional(condition, s, t)); |
| 702 } |
| 703 |
| 543 /// Returns a statement equivalent to both [s] and [t], or null if [s] and | 704 /// Returns a statement equivalent to both [s] and [t], or null if [s] and |
| 544 /// [t] are incompatible. | 705 /// [t] are incompatible. |
| 545 /// If non-null is returned, the caller MUST discard [s] and [t] and use | 706 /// If non-null is returned, the caller MUST discard [s] and [t] and use |
| 546 /// the returned statement instead. | 707 /// the returned statement instead. |
| 547 /// If two breaks are combined, the label's break counter will be decremented. | 708 /// If two breaks are combined, the label's break counter will be decremented. |
| 548 static Statement combineStatements(Statement s, Statement t) { | 709 Statement combineStatements(Statement s, Statement t) { |
| 549 if (s is Break && t is Break && s.target == t.target) { | 710 if (s is Break && t is Break && s.target == t.target) { |
| 550 --t.target.useCount; // Two breaks become one. | 711 --t.target.useCount; // Two breaks become one. |
| 712 if (s.target.useCount == 1 && safeForInlining.contains(s.target)) { |
| 713 // Only one break remains; inline it. |
| 714 --s.target.useCount; |
| 715 return visitStatement(s.target.binding.next); |
| 716 } |
| 551 return s; | 717 return s; |
| 552 } | 718 } |
| 553 if (s is Continue && t is Continue && s.target == t.target) { | 719 if (s is Continue && t is Continue && s.target == t.target) { |
| 554 --t.target.useCount; // Two continues become one. | 720 --t.target.useCount; // Two continues become one. |
| 555 return s; | 721 return s; |
| 556 } | 722 } |
| 557 if (s is Return && t is Return) { | 723 if (s is Return && t is Return) { |
| 558 Expression e = combineExpressions(s.value, t.value); | 724 CombinedExpressions values = combineExpressions(s.value, t.value); |
| 559 if (e != null) { | 725 if (values != null) { |
| 560 return new Return(e); | 726 return new Return(values.combined); |
| 561 } | 727 } |
| 562 } | 728 } |
| 563 if (s is Assign && t is Assign && | 729 if (s is ExpressionStatement && t is ExpressionStatement) { |
| 564 s.variable == t.variable && | 730 CombinedExpressions values = |
| 565 isSameVariable(s.value, t.value)) { | 731 combineExpressions(s.expression, t.expression); |
| 732 if (values == null) return null; |
| 733 environment.add(values.combined); |
| 566 Statement next = combineStatements(s.next, t.next); | 734 Statement next = combineStatements(s.next, t.next); |
| 567 if (next != null) { | 735 if (next == null) { |
| 736 // The successors could not be combined. |
| 737 // Restore the environment and uncombine the values again. |
| 738 assert(environment.last == values.combined); |
| 739 environment.removeLast(); |
| 740 values.uncombine(); |
| 741 return null; |
| 742 } else if (!environment.isEmpty && environment.last == values.combined) { |
| 743 // The successors were combined but the combined expressions were not |
| 744 // propagated. Leave the combined expression as a statement. |
| 745 environment.removeLast(); |
| 746 s.expression = values.combined; |
| 568 s.next = next; | 747 s.next = next; |
| 569 --t.variable.writeCount; | |
| 570 --(t.value as VariableUse).variable.readCount; | |
| 571 return s; | 748 return s; |
| 749 } else { |
| 750 // The successors were combined, and the combined expressions were |
| 751 // propagated into the successors. |
| 752 return next; |
| 572 } | 753 } |
| 573 } | 754 } |
| 574 return null; | 755 return null; |
| 575 } | 756 } |
| 576 | 757 |
| 577 /// Returns an expression equivalent to both [e1] and [e2]. | 758 /// Returns an expression equivalent to both [e1] and [e2]. |
| 578 /// If non-null is returned, the caller must discard [e1] and [e2] and use | 759 /// If non-null is returned, the caller must discard [e1] and [e2] and use |
| 579 /// the resulting expression in the tree. | 760 /// the resulting expression in the tree. |
| 580 static Expression combineExpressions(Expression e1, Expression e2) { | 761 CombinedExpressions combineExpressions(Expression e1, Expression e2) { |
| 581 if (e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable) { | 762 if (e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable) { |
| 582 --e1.variable.readCount; // Two references become one. | 763 return new CombinedUses(e1, e2); |
| 583 return e1; | 764 } |
| 765 if (e1 is Assign && e2 is Assign && e1.variable == e2.variable) { |
| 766 CombinedExpressions values = combineExpressions(e1.value, e2.value); |
| 767 if (values != null) { |
| 768 return new CombinedAssigns(e1, e2, values); |
| 769 } |
| 584 } | 770 } |
| 585 if (e1 is Constant && e2 is Constant && e1.value == e2.value) { | 771 if (e1 is Constant && e2 is Constant && e1.value == e2.value) { |
| 586 return e1; | 772 return new CombinedExpressions(e1); |
| 587 } | 773 } |
| 588 return null; | 774 return null; |
| 589 } | 775 } |
| 590 | 776 |
| 591 /// Try to collapse nested ifs using && and || expressions. | 777 /// Try to collapse nested ifs using && and || expressions. |
| 592 /// For example: | 778 /// For example: |
| 593 /// | 779 /// |
| 594 /// if (E1) { if (E2) S else break L } else break L | 780 /// if (E1) { if (E2) S else break L } else break L |
| 595 /// ==> | 781 /// ==> |
| 596 /// if (E1 && E2) S else break L | 782 /// if (E1 && E2) S else break L |
| 597 /// | 783 /// |
| 598 /// [branch1] and [branch2] control the position of the S statement. | 784 /// [branch1] and [branch2] control the position of the S statement. |
| 599 /// | 785 /// |
| 600 /// Returns true if another collapse redex might have been introduced. | 786 /// Must be called with an empty environment. |
| 601 void tryCollapseIf(If node) { | 787 void tryCollapseIf(If node) { |
| 788 assert(environment.isEmpty); |
| 602 // Repeatedly try to collapse nested ifs. | 789 // Repeatedly try to collapse nested ifs. |
| 603 // The transformation is shrinking (destroys an if) so it remains linear. | 790 // The transformation is shrinking (destroys an if) so it remains linear. |
| 604 // Here is an example where more than one iteration is required: | 791 // Here is an example where more than one iteration is required: |
| 605 // | 792 // |
| 606 // if (E1) | 793 // if (E1) |
| 607 // if (E2) break L2 else break L1 | 794 // if (E2) break L2 else break L1 |
| 608 // else | 795 // else |
| 609 // break L1 | 796 // break L1 |
| 610 // | 797 // |
| 611 // L1.target ::= | 798 // L1.target ::= |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 646 Statement innerThen = getBranch(innerIf, branch2); | 833 Statement innerThen = getBranch(innerIf, branch2); |
| 647 Statement innerElse = getBranch(innerIf, !branch2); | 834 Statement innerElse = getBranch(innerIf, !branch2); |
| 648 Statement combinedElse = combineStatements(innerElse, outerElse); | 835 Statement combinedElse = combineStatements(innerElse, outerElse); |
| 649 if (combinedElse != null) { | 836 if (combinedElse != null) { |
| 650 // We always put S in the then branch of the result, and adjust the | 837 // We always put S in the then branch of the result, and adjust the |
| 651 // condition expression if S was actually found in the else branch(es). | 838 // condition expression if S was actually found in the else branch(es). |
| 652 outerIf.condition = new LogicalOperator.and( | 839 outerIf.condition = new LogicalOperator.and( |
| 653 makeCondition(outerIf.condition, branch1), | 840 makeCondition(outerIf.condition, branch1), |
| 654 makeCondition(innerIf.condition, branch2)); | 841 makeCondition(innerIf.condition, branch2)); |
| 655 outerIf.thenStatement = innerThen; | 842 outerIf.thenStatement = innerThen; |
| 656 | 843 outerIf.elseStatement = combinedElse; |
| 657 // Try to inline the remaining break. Do not propagate assignments. | |
| 658 inEmptyEnvironment(() { | |
| 659 // TODO(asgerf): Avoid quadratic cost from repeated processing. This | |
| 660 // should be easier after we introduce basic blocks. | |
| 661 outerIf.elseStatement = visitStatement(combinedElse); | |
| 662 }); | |
| 663 | |
| 664 return outerIf.elseStatement is If; | 844 return outerIf.elseStatement is If; |
| 665 } | 845 } |
| 666 } | 846 } |
| 667 return false; | 847 return false; |
| 668 } | 848 } |
| 669 | 849 |
| 670 static bool isSameVariable(Expression e1, Expression e2) { | |
| 671 return e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable; | |
| 672 } | |
| 673 | |
| 674 Expression makeCondition(Expression e, bool polarity) { | 850 Expression makeCondition(Expression e, bool polarity) { |
| 675 return polarity ? e : new Not(e); | 851 return polarity ? e : new Not(e); |
| 676 } | 852 } |
| 677 | 853 |
| 678 Statement getBranch(If node, bool polarity) { | 854 Statement getBranch(If node, bool polarity) { |
| 679 return polarity ? node.thenStatement : node.elseStatement; | 855 return polarity ? node.thenStatement : node.elseStatement; |
| 680 } | 856 } |
| 681 } | 857 } |
| 858 |
| 859 /// Result of combining two expressions, with the potential for reverting the |
| 860 /// combination. |
| 861 /// |
| 862 /// Reverting a combination is done by calling [uncombine]. In this case, |
| 863 /// both the original expressions should remain in the tree, and the [combined] |
| 864 /// expression should be orphaned. |
| 865 /// |
| 866 /// Explicitly reverting a combination is necessary to maintain variable |
| 867 /// reference counts. |
| 868 abstract class CombinedExpressions { |
| 869 Expression get combined; |
| 870 void uncombine(); |
| 871 |
| 872 factory CombinedExpressions(Expression e) = GenericCombinedExpressions; |
| 873 } |
| 874 |
| 875 /// Combines assignments of form `[variable] := E1` and `[variable] := E2` into |
| 876 /// a single assignment of form `[variable] := combine(E1, E2)`. |
| 877 class CombinedAssigns implements CombinedExpressions { |
| 878 Assign assign1, assign2; |
| 879 CombinedExpressions value; |
| 880 Expression combined; |
| 881 |
| 882 CombinedAssigns(this.assign1, this.assign2, this.value) { |
| 883 assert(assign1.variable == assign2.variable); |
| 884 assign1.variable.writeCount -= 2; // Destroy the two original assignemnts. |
| 885 combined = new Assign(assign1.variable, value.combined); |
| 886 } |
| 887 |
| 888 void uncombine() { |
| 889 value.uncombine(); |
| 890 ++assign1.variable.writeCount; // Restore original reference count. |
| 891 } |
| 892 } |
| 893 |
| 894 /// Combines two variable uses into one. |
| 895 class CombinedUses implements CombinedExpressions { |
| 896 VariableUse use1, use2; |
| 897 Expression combined; |
| 898 |
| 899 CombinedUses(this.use1, this.use2) { |
| 900 assert(use1.variable == use2.variable); |
| 901 use1.variable.readCount -= 2; // Destroy both the original uses. |
| 902 combined = new VariableUse(use1.variable); |
| 903 } |
| 904 |
| 905 void uncombine() { |
| 906 ++use1.variable.readCount; // Restore original reference count. |
| 907 } |
| 908 } |
| 909 |
| 910 /// Result of combining two expressions that do not affect reference counting. |
| 911 class GenericCombinedExpressions implements CombinedExpressions { |
| 912 Expression combined; |
| 913 |
| 914 GenericCombinedExpressions(this.combined); |
| 915 |
| 916 void uncombine() {} |
| 917 } |
| OLD | NEW |