| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library tree_ir.optimization.statement_rewriter; | |
| 6 | |
| 7 import '../../elements/elements.dart'; | |
| 8 import '../../io/source_information.dart'; | |
| 9 import '../../js/placeholder_safety.dart'; | |
| 10 import '../tree_ir_nodes.dart'; | |
| 11 import 'optimization.dart' show Pass; | |
| 12 | |
| 13 /** | |
| 14 * Translates to direct-style. | |
| 15 * | |
| 16 * In addition to the general IR constraints (see [CheckTreeIntegrity]), | |
| 17 * the input is assumed to satisfy the following criteria: | |
| 18 * | |
| 19 * All expressions other than those nested in [Assign] or [ExpressionStatement] | |
| 20 * must be simple. A [VariableUse] and [This] is a simple expression. | |
| 21 * The right-hand of an [Assign] may not be an [Assign]. | |
| 22 * | |
| 23 * Moreover, every variable must either be an SSA variable or a mutable | |
| 24 * variable, and must satisfy the corresponding criteria: | |
| 25 * | |
| 26 * SSA VARIABLE: | |
| 27 * An SSA variable must have a unique definition site, which is either an | |
| 28 * assignment or label. In case of a label, its target must act as the unique | |
| 29 * reaching definition of that variable at all uses of the variable and at | |
| 30 * all other label targets where the variable is in scope. | |
| 31 * | |
| 32 * (The second criterion is to ensure that we can move a use of an SSA variable | |
| 33 * across a label without changing its reaching definition). | |
| 34 * | |
| 35 * MUTABLE VARIABLE: | |
| 36 * Uses of mutable variables are considered complex expressions, and hence must | |
| 37 * not be nested in other expressions. Assignments to mutable variables must | |
| 38 * have simple right-hand sides. | |
| 39 * | |
| 40 * ---- | |
| 41 * | |
| 42 * This pass performs the following transformations on the tree: | |
| 43 * - Assignment inlining | |
| 44 * - Assignment expression propagation | |
| 45 * - If-to-conditional conversion | |
| 46 * - Flatten nested ifs | |
| 47 * - Break inlining | |
| 48 * - Redirect breaks | |
| 49 * | |
| 50 * The above transformations all eliminate statements from the tree, and may | |
| 51 * introduce redexes of each other. | |
| 52 * | |
| 53 * | |
| 54 * ASSIGNMENT INLINING: | |
| 55 * Single-use definitions are inlined at their use site when possible. | |
| 56 * For example: | |
| 57 * | |
| 58 * { v0 = foo(); return v0; } | |
| 59 * ==> | |
| 60 * return foo() | |
| 61 * | |
| 62 * After translating out of CPS, all intermediate values are bound by [Assign]. | |
| 63 * This transformation propagates such definitions to their uses when it is | |
| 64 * safe and profitable. Bindings are processed "on demand" when their uses are | |
| 65 * seen, but are only processed once to keep this transformation linear in | |
| 66 * the size of the tree. | |
| 67 * | |
| 68 * The transformation builds an environment containing [Assign] bindings that | |
| 69 * are in scope. These bindings have yet-untranslated definitions. When a use | |
| 70 * is encountered the transformation determines if it is safe and profitable | |
| 71 * to propagate the definition to its use. If so, it is removed from the | |
| 72 * environment and the definition is recursively processed (in the | |
| 73 * new environment at the use site) before being propagated. | |
| 74 * | |
| 75 * See [visitVariableUse] for the implementation of the heuristic for | |
| 76 * propagating a definition. | |
| 77 * | |
| 78 * | |
| 79 * ASSIGNMENT EXPRESSION PROPAGATION: | |
| 80 * Definitions with multiple uses are propagated to their first use site | |
| 81 * when possible. For example: | |
| 82 * | |
| 83 * { v0 = foo(); bar(v0); return v0; } | |
| 84 * ==> | |
| 85 * { bar(v0 = foo()); return v0; } | |
| 86 * | |
| 87 * Note that the [RestoreInitializers] phase will later undo this rewrite | |
| 88 * in cases where it prevents an assignment from being pulled into an | |
| 89 * initializer. | |
| 90 * | |
| 91 * | |
| 92 * IF-TO-CONDITIONAL CONVERSION: | |
| 93 * If-statement are converted to conditional expressions when possible. | |
| 94 * For example: | |
| 95 * | |
| 96 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } | |
| 97 * ==> | |
| 98 * { v1 = v0 ? foo() : bar(); break L } | |
| 99 * | |
| 100 * This can lead to inlining of L, which in turn can lead to further propagation | |
| 101 * of the variable v1. | |
| 102 * | |
| 103 * See [visitIf]. | |
| 104 * | |
| 105 * | |
| 106 * FLATTEN NESTED IFS: | |
| 107 * An if inside an if is converted to an if with a logical operator. | |
| 108 * For example: | |
| 109 * | |
| 110 * if (E1) { if (E2) {S} else break L } else break L | |
| 111 * ==> | |
| 112 * if (E1 && E2) {S} else break L | |
| 113 * | |
| 114 * This may lead to inlining of L. | |
| 115 * | |
| 116 * | |
| 117 * BREAK INLINING: | |
| 118 * Single-use labels are inlined at [Break] statements. | |
| 119 * For example: | |
| 120 * | |
| 121 * L0: { v0 = foo(); break L0 }; return v0; | |
| 122 * ==> | |
| 123 * v0 = foo(); return v0; | |
| 124 * | |
| 125 * This can lead to propagation of v0. | |
| 126 * | |
| 127 * See [visitBreak] and [visitLabeledStatement]. | |
| 128 * | |
| 129 * | |
| 130 * REDIRECT BREAKS: | |
| 131 * Labeled statements whose next is a break become flattened and all breaks | |
| 132 * to their label are redirected. | |
| 133 * For example, where 'jump' is either break or continue: | |
| 134 * | |
| 135 * L0: {... break L0 ...}; jump L1 | |
| 136 * ==> | |
| 137 * {... jump L1 ...} | |
| 138 * | |
| 139 * This may trigger a flattening of nested ifs in case the eliminated label | |
| 140 * separated two ifs. | |
| 141 */ | |
| 142 class StatementRewriter extends Transformer implements Pass { | |
| 143 String get passName => 'Statement rewriter'; | |
| 144 | |
| 145 @override | |
| 146 void rewrite(FunctionDefinition node) { | |
| 147 node.parameters.forEach(pushDominatingAssignment); | |
| 148 node.body = visitStatement(node.body); | |
| 149 node.parameters.forEach(popDominatingAssignment); | |
| 150 } | |
| 151 | |
| 152 /// The most recently evaluated impure expressions, with the most recent | |
| 153 /// expression being last. | |
| 154 /// | |
| 155 /// Most importantly, this contains [Assign] expressions that we attempt to | |
| 156 /// inline at their use site. It also contains other impure expressions that | |
| 157 /// we can propagate to a variable use if they are known to return the value | |
| 158 /// of that variable. | |
| 159 /// | |
| 160 /// Assignments with constant right-hand sides (see [isEffectivelyConstant]) | |
| 161 /// are not considered impure and are put in [constantEnvironment] instead. | |
| 162 /// | |
| 163 /// Except for [Conditional]s, expressions in the environment have | |
| 164 /// not been processed, and all their subexpressions must therefore be | |
| 165 /// variables uses. | |
| 166 List<Expression> environment = <Expression>[]; | |
| 167 | |
| 168 /// Binding environment for variables that are assigned to effectively | |
| 169 /// constant expressions (see [isEffectivelyConstant]). | |
| 170 Map<Variable, Expression> constantEnvironment = <Variable, Expression>{}; | |
| 171 | |
| 172 /// Substitution map for labels. Any break to a label L should be substituted | |
| 173 /// for a break to L' if L maps to L'. | |
| 174 Map<Label, Jump> labelRedirects = <Label, Jump>{}; | |
| 175 | |
| 176 /// Number of uses of the given variable that are still unseen. | |
| 177 /// Used to detect the first use of a variable (since we do backwards | |
| 178 /// traversal, the first use is the last one seen). | |
| 179 Map<Variable, int> unseenUses = <Variable, int>{}; | |
| 180 | |
| 181 /// Number of assignments to a given variable that dominate the current | |
| 182 /// position. | |
| 183 /// | |
| 184 /// Pure expressions will not be inlined if it uses a variable with more than | |
| 185 /// one dominating assignment, because the reaching definition of the used | |
| 186 /// variable might have changed since it was put in the environment. | |
| 187 final Map<Variable, int> dominatingAssignments = <Variable, int>{}; | |
| 188 | |
| 189 /// A set of labels that can be safely inlined at their use. | |
| 190 /// | |
| 191 /// The successor statements for labeled statements that have only one break | |
| 192 /// from them are normally rewritten inline at the site of the break. This | |
| 193 /// is not safe if the code would be moved inside the scope of an exception | |
| 194 /// handler (i.e., if the code would be moved into a try from outside it). | |
| 195 Set<Label> safeForInlining = new Set<Label>(); | |
| 196 | |
| 197 /// If the top element is true, assignments of form "x = CONST" may be | |
| 198 /// propagated into a following occurence of CONST. This may confuse the JS | |
| 199 /// engine so it is disabled in some cases. | |
| 200 final List<bool> allowRhsPropagation = <bool>[true]; | |
| 201 | |
| 202 bool get isRhsPropagationAllowed => allowRhsPropagation.last; | |
| 203 | |
| 204 /// Returns the redirect target of [jump] or [jump] itself if it should not | |
| 205 /// be redirected. | |
| 206 Jump redirect(Jump jump) { | |
| 207 Jump newJump = labelRedirects[jump.target]; | |
| 208 return newJump != null ? newJump : jump; | |
| 209 } | |
| 210 | |
| 211 void inEmptyEnvironment(void action(), {bool keepConstants: true}) { | |
| 212 List oldEnvironment = environment; | |
| 213 Map oldConstantEnvironment = constantEnvironment; | |
| 214 environment = <Expression>[]; | |
| 215 if (!keepConstants) { | |
| 216 constantEnvironment = <Variable, Expression>{}; | |
| 217 } | |
| 218 action(); | |
| 219 assert(environment.isEmpty); | |
| 220 environment = oldEnvironment; | |
| 221 if (!keepConstants) { | |
| 222 constantEnvironment = oldConstantEnvironment; | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 /// Left-hand side of the given assignment, or `null` if not an assignment. | |
| 227 Variable getLeftHand(Expression e) { | |
| 228 return e is Assign ? e.variable : null; | |
| 229 } | |
| 230 | |
| 231 /// If the given expression always returns the value of one of its | |
| 232 /// subexpressions, returns that subexpression, otherwise `null`. | |
| 233 Expression getValueSubexpression(Expression e) { | |
| 234 if (e is SetField) return e.value; | |
| 235 return null; | |
| 236 } | |
| 237 | |
| 238 /// If the given expression always returns the value of one of its | |
| 239 /// subexpressions, and that subexpression is a variable use, returns that | |
| 240 /// variable. Otherwise `null`. | |
| 241 Variable getRightHandVariable(Expression e) { | |
| 242 Expression value = getValueSubexpression(e); | |
| 243 return value is VariableUse ? value.variable : null; | |
| 244 } | |
| 245 | |
| 246 Constant getRightHandConstant(Expression e) { | |
| 247 Expression value = getValueSubexpression(e); | |
| 248 return value is Constant ? value : null; | |
| 249 } | |
| 250 | |
| 251 /// True if the given expression (taken from [constantEnvironment]) uses a | |
| 252 /// variable that might have been reassigned since [node] was evaluated. | |
| 253 bool hasUnsafeVariableUse(Expression node) { | |
| 254 bool wasFound = false; | |
| 255 VariableUseVisitor.visit(node, (VariableUse use) { | |
| 256 if (dominatingAssignments[use.variable] > 1) { | |
| 257 wasFound = true; | |
| 258 } | |
| 259 }); | |
| 260 return wasFound; | |
| 261 } | |
| 262 | |
| 263 void pushDominatingAssignment(Variable variable) { | |
| 264 if (variable != null) { | |
| 265 dominatingAssignments.putIfAbsent(variable, () => 0); | |
| 266 ++dominatingAssignments[variable]; | |
| 267 } | |
| 268 } | |
| 269 | |
| 270 void popDominatingAssignment(Variable variable) { | |
| 271 if (variable != null) { | |
| 272 --dominatingAssignments[variable]; | |
| 273 } | |
| 274 } | |
| 275 | |
| 276 @override | |
| 277 Expression visitVariableUse(VariableUse node) { | |
| 278 // Count of number of unseen uses remaining. | |
| 279 unseenUses.putIfAbsent(node.variable, () => node.variable.readCount); | |
| 280 --unseenUses[node.variable]; | |
| 281 | |
| 282 // We traverse the tree right-to-left, so when we have seen all uses, | |
| 283 // it means we are looking at the first use. | |
| 284 assert(unseenUses[node.variable] < node.variable.readCount); | |
| 285 assert(unseenUses[node.variable] >= 0); | |
| 286 | |
| 287 // We cannot reliably find the first dynamic use of a variable that is | |
| 288 // accessed from a JS function in a foreign code fragment. | |
| 289 if (node.variable.isCaptured) return node; | |
| 290 | |
| 291 bool isFirstUse = unseenUses[node.variable] == 0; | |
| 292 | |
| 293 // Propagate constant to use site. | |
| 294 Expression constant = constantEnvironment[node.variable]; | |
| 295 if (constant != null && !hasUnsafeVariableUse(constant)) { | |
| 296 --node.variable.readCount; | |
| 297 return visitExpression(constant); | |
| 298 } | |
| 299 | |
| 300 // Try to propagate another expression into this variable use. | |
| 301 if (!environment.isEmpty) { | |
| 302 Expression binding = environment.last; | |
| 303 | |
| 304 // Is this variable assigned by the most recently evaluated impure | |
| 305 // expression? | |
| 306 // | |
| 307 // If so, propagate the assignment, e.g: | |
| 308 // | |
| 309 // { x = foo(); bar(x, x) } ==> bar(x = foo(), x) | |
| 310 // | |
| 311 // We must ensure that no other uses separate this use from the | |
| 312 // assignment. We therefore only propagate assignments into the first use. | |
| 313 // | |
| 314 // Note that if this is only use, `visitAssign` will then remove the | |
| 315 // redundant assignment. | |
| 316 if (getLeftHand(binding) == node.variable && isFirstUse) { | |
| 317 environment.removeLast(); | |
| 318 --node.variable.readCount; | |
| 319 return visitExpression(binding); | |
| 320 } | |
| 321 | |
| 322 // Is the most recently evaluated impure expression known to have the | |
| 323 // value of this variable? | |
| 324 // | |
| 325 // If so, we can replace this use with the impure expression, e.g: | |
| 326 // | |
| 327 // { E.foo = x; bar(x) } ==> bar(E.foo = x) | |
| 328 // | |
| 329 if (isRhsPropagationAllowed && | |
| 330 getRightHandVariable(binding) == node.variable) { | |
| 331 environment.removeLast(); | |
| 332 --node.variable.readCount; | |
| 333 return visitExpression(binding); | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 // If the definition could not be propagated, leave the variable use. | |
| 338 return node; | |
| 339 } | |
| 340 | |
| 341 /// True if [exp] contains a use of a variable that was assigned to by the | |
| 342 /// most recently evaluated impure expression (constant assignments are not | |
| 343 /// considered impure). | |
| 344 /// | |
| 345 /// This implies that the assignment can be propagated into this use unless | |
| 346 /// the use is moved further away. | |
| 347 /// | |
| 348 /// In this case, we will refrain from moving [exp] across other impure | |
| 349 /// expressions, even when this is safe, because doing so would immediately | |
| 350 /// prevent the previous expression from propagating, canceling out the | |
| 351 /// benefit we might otherwise gain from propagating [exp]. | |
| 352 /// | |
| 353 /// [exp] must be an unprocessed expression, i.e. either a [Conditional] or | |
| 354 /// an expression whose subexpressions are all variable uses. | |
| 355 bool usesRecentlyAssignedVariable(Expression exp) { | |
| 356 if (environment.isEmpty) return false; | |
| 357 Variable variable = getLeftHand(environment.last); | |
| 358 if (variable == null) return false; | |
| 359 IsVariableUsedVisitor visitor = new IsVariableUsedVisitor(variable); | |
| 360 visitor.visitExpression(exp); | |
| 361 return visitor.wasFound; | |
| 362 } | |
| 363 | |
| 364 /// Returns true if [exp] has no side effects and has a constant value within | |
| 365 /// any given activation of the enclosing method. | |
| 366 bool isEffectivelyConstant(Expression exp) { | |
| 367 // TODO(asgerf): Can be made more aggressive e.g. by checking conditional | |
| 368 // expressions recursively. Determine if that is a valuable optimization | |
| 369 // and/or if it is better handled at the CPS level. | |
| 370 return exp is Constant || | |
| 371 exp is This || | |
| 372 exp is CreateInvocationMirror || | |
| 373 exp is CreateInstance || | |
| 374 exp is CreateBox || | |
| 375 exp is TypeExpression || | |
| 376 exp is GetStatic && exp.element.isFunction || | |
| 377 exp is Interceptor || | |
| 378 exp is ApplyBuiltinOperator || | |
| 379 exp is VariableUse && constantEnvironment.containsKey(exp.variable); | |
| 380 } | |
| 381 | |
| 382 /// True if [node] is an assignment that can be propagated as a constant. | |
| 383 bool isEffectivelyConstantAssignment(Expression node) { | |
| 384 return node is Assign && | |
| 385 node.variable.writeCount == 1 && | |
| 386 isEffectivelyConstant(node.value); | |
| 387 } | |
| 388 | |
| 389 Statement visitExpressionStatement(ExpressionStatement inputNode) { | |
| 390 // Analyze chains of expression statements. | |
| 391 // To avoid deep recursion, [processExpressionStatement] returns a callback | |
| 392 // to invoke after its successor node has been processed. | |
| 393 // These callbacks are stored in a list and invoked in reverse at the end. | |
| 394 List<Function> stack = []; | |
| 395 Statement node = inputNode; | |
| 396 while (node is ExpressionStatement) { | |
| 397 stack.add(processExpressionStatement(node)); | |
| 398 node = node.next; | |
| 399 } | |
| 400 Statement result = visitStatement(node); | |
| 401 for (Function fun in stack.reversed) { | |
| 402 result = fun(result); | |
| 403 } | |
| 404 return result; | |
| 405 } | |
| 406 | |
| 407 /// Attempts to propagate an assignment in an expression statement. | |
| 408 /// | |
| 409 /// Returns a callback to be invoked after the sucessor statement has | |
| 410 /// been processed. | |
| 411 Function processExpressionStatement(ExpressionStatement stmt) { | |
| 412 Variable leftHand = getLeftHand(stmt.expression); | |
| 413 pushDominatingAssignment(leftHand); | |
| 414 if (isEffectivelyConstantAssignment(stmt.expression) && | |
| 415 !usesRecentlyAssignedVariable(stmt.expression)) { | |
| 416 Assign assign = stmt.expression; | |
| 417 // Handle constant assignments specially. | |
| 418 // They are always safe to propagate (though we should avoid duplication). | |
| 419 // Moreover, they should not prevent other expressions from propagating. | |
| 420 if (assign.variable.readCount == 1) { | |
| 421 // A single-use constant should always be propagated to its use site. | |
| 422 constantEnvironment[assign.variable] = assign.value; | |
| 423 return (Statement next) { | |
| 424 popDominatingAssignment(leftHand); | |
| 425 if (assign.variable.readCount > 0) { | |
| 426 // The assignment could not be propagated into the successor, | |
| 427 // either because it [hasUnsafeVariableUse] or because the | |
| 428 // use is outside the current try block, and we do not currently | |
| 429 // support constant propagation out of a try block. | |
| 430 constantEnvironment.remove(assign.variable); | |
| 431 assign.value = visitExpression(assign.value); | |
| 432 stmt.next = next; | |
| 433 return stmt; | |
| 434 } else { | |
| 435 --assign.variable.writeCount; | |
| 436 return next; | |
| 437 } | |
| 438 }; | |
| 439 } else { | |
| 440 // With more than one use, we cannot propagate the constant. | |
| 441 // Visit the following statement without polluting [environment] so | |
| 442 // that any preceding non-constant assignments might still propagate. | |
| 443 return (Statement next) { | |
| 444 stmt.next = next; | |
| 445 popDominatingAssignment(leftHand); | |
| 446 assign.value = visitExpression(assign.value); | |
| 447 return stmt; | |
| 448 }; | |
| 449 } | |
| 450 } else { | |
| 451 // Try to propagate the expression, and block previous impure expressions | |
| 452 // until this has propagated. | |
| 453 environment.add(stmt.expression); | |
| 454 return (Statement next) { | |
| 455 stmt.next = next; | |
| 456 popDominatingAssignment(leftHand); | |
| 457 if (!environment.isEmpty && environment.last == stmt.expression) { | |
| 458 // Retain the expression statement. | |
| 459 environment.removeLast(); | |
| 460 stmt.expression = visitExpression(stmt.expression); | |
| 461 return stmt; | |
| 462 } else { | |
| 463 // Expression was propagated into the successor. | |
| 464 return stmt.next; | |
| 465 } | |
| 466 }; | |
| 467 } | |
| 468 } | |
| 469 | |
| 470 Expression visitAssign(Assign node) { | |
| 471 allowRhsPropagation.add(true); | |
| 472 node.value = visitExpression(node.value); | |
| 473 allowRhsPropagation.removeLast(); | |
| 474 // Remove assignments to variables without any uses. This can happen | |
| 475 // because the assignment was propagated into its use, e.g: | |
| 476 // | |
| 477 // { x = foo(); bar(x) } ==> bar(x = foo()) ==> bar(foo()) | |
| 478 // | |
| 479 if (node.variable.readCount == 0) { | |
| 480 --node.variable.writeCount; | |
| 481 return node.value; | |
| 482 } | |
| 483 return node; | |
| 484 } | |
| 485 | |
| 486 /// Process nodes right-to-left, the opposite of evaluation order in the case | |
| 487 /// of argument lists.. | |
| 488 void _rewriteList(List<Node> nodes, {bool rhsPropagation: true}) { | |
| 489 allowRhsPropagation.add(rhsPropagation); | |
| 490 for (int i = nodes.length - 1; i >= 0; --i) { | |
| 491 nodes[i] = visitExpression(nodes[i]); | |
| 492 } | |
| 493 allowRhsPropagation.removeLast(); | |
| 494 } | |
| 495 | |
| 496 Expression visitInvokeStatic(InvokeStatic node) { | |
| 497 _rewriteList(node.arguments); | |
| 498 return node; | |
| 499 } | |
| 500 | |
| 501 Expression visitInvokeMethod(InvokeMethod node) { | |
| 502 if (node.receiverIsNotNull) { | |
| 503 _rewriteList(node.arguments); | |
| 504 node.receiver = visitExpression(node.receiver); | |
| 505 } else { | |
| 506 // Impure expressions cannot be propagated across the method lookup, | |
| 507 // because it throws when the receiver is null. | |
| 508 inEmptyEnvironment(() { | |
| 509 _rewriteList(node.arguments); | |
| 510 }); | |
| 511 node.receiver = visitExpression(node.receiver); | |
| 512 } | |
| 513 return node; | |
| 514 } | |
| 515 | |
| 516 Expression visitOneShotInterceptor(OneShotInterceptor node) { | |
| 517 _rewriteList(node.arguments); | |
| 518 return node; | |
| 519 } | |
| 520 | |
| 521 Expression visitApplyBuiltinMethod(ApplyBuiltinMethod node) { | |
| 522 if (node.receiverIsNotNull) { | |
| 523 _rewriteList(node.arguments); | |
| 524 node.receiver = visitExpression(node.receiver); | |
| 525 } else { | |
| 526 // Impure expressions cannot be propagated across the method lookup, | |
| 527 // because it throws when the receiver is null. | |
| 528 inEmptyEnvironment(() { | |
| 529 _rewriteList(node.arguments); | |
| 530 }); | |
| 531 node.receiver = visitExpression(node.receiver); | |
| 532 } | |
| 533 return node; | |
| 534 } | |
| 535 | |
| 536 Expression visitInvokeMethodDirectly(InvokeMethodDirectly node) { | |
| 537 _rewriteList(node.arguments); | |
| 538 // The target function might not exist before the enclosing class has been | |
| 539 // instantitated for the first time. If the receiver might be the first | |
| 540 // instantiation of its class, we cannot propgate it into the receiver | |
| 541 // expression, because the target function is evaluated before the receiver. | |
| 542 // Calls to constructor bodies are compiled so that the receiver is | |
| 543 // evaluated first, so they are safe. | |
| 544 if (node.target is! ConstructorBodyElement) { | |
| 545 inEmptyEnvironment(() { | |
| 546 node.receiver = visitExpression(node.receiver); | |
| 547 }); | |
| 548 } else { | |
| 549 node.receiver = visitExpression(node.receiver); | |
| 550 } | |
| 551 return node; | |
| 552 } | |
| 553 | |
| 554 Expression visitInvokeConstructor(InvokeConstructor node) { | |
| 555 _rewriteList(node.arguments); | |
| 556 return node; | |
| 557 } | |
| 558 | |
| 559 Expression visitConditional(Conditional node) { | |
| 560 // Conditional expressions do not exist in the input, but they are | |
| 561 // introduced by if-to-conditional conversion. | |
| 562 // Their subexpressions have already been processed; do not reprocess them. | |
| 563 // | |
| 564 // Note that this can only happen for conditional expressions. It is an | |
| 565 // error for any other type of expression to be visited twice or to be | |
| 566 // created and then visited. We use this special treatment of conditionals | |
| 567 // to allow for assignment inlining after if-to-conditional conversion. | |
| 568 // | |
| 569 // There are several reasons we should not reprocess the subexpressions: | |
| 570 // | |
| 571 // - It will mess up the [seenUses] counter, since a single use will be | |
| 572 // counted twice. | |
| 573 // | |
| 574 // - Other visit methods assume that all subexpressions are variable uses | |
| 575 // because they come fresh out of the tree IR builder. | |
| 576 // | |
| 577 // - Reprocessing can be expensive. | |
| 578 // | |
| 579 return node; | |
| 580 } | |
| 581 | |
| 582 Expression visitLogicalOperator(LogicalOperator node) { | |
| 583 // Impure expressions may not propagate across the branch. | |
| 584 inEmptyEnvironment(() { | |
| 585 node.right = visitExpression(node.right); | |
| 586 }); | |
| 587 node.left = visitExpression(node.left); | |
| 588 return node; | |
| 589 } | |
| 590 | |
| 591 Expression visitNot(Not node) { | |
| 592 node.operand = visitExpression(node.operand); | |
| 593 return node; | |
| 594 } | |
| 595 | |
| 596 bool isNullConstant(Expression node) { | |
| 597 return node is Constant && node.value.isNull; | |
| 598 } | |
| 599 | |
| 600 Statement visitReturn(Return node) { | |
| 601 if (!isNullConstant(node.value)) { | |
| 602 // Do not chain assignments into a null return. | |
| 603 node.value = visitExpression(node.value); | |
| 604 } | |
| 605 return node; | |
| 606 } | |
| 607 | |
| 608 Statement visitThrow(Throw node) { | |
| 609 node.value = visitExpression(node.value); | |
| 610 return node; | |
| 611 } | |
| 612 | |
| 613 Statement visitUnreachable(Unreachable node) { | |
| 614 return node; | |
| 615 } | |
| 616 | |
| 617 Statement visitBreak(Break node) { | |
| 618 // Redirect through chain of breaks. | |
| 619 // Note that useCount was accounted for at visitLabeledStatement. | |
| 620 // Note redirect may return either a Break or Continue statement. | |
| 621 Jump jump = redirect(node); | |
| 622 if (jump is Break && | |
| 623 jump.target.useCount == 1 && | |
| 624 safeForInlining.contains(jump.target)) { | |
| 625 --jump.target.useCount; | |
| 626 return visitStatement(jump.target.binding.next); | |
| 627 } | |
| 628 return jump; | |
| 629 } | |
| 630 | |
| 631 Statement visitContinue(Continue node) { | |
| 632 return node; | |
| 633 } | |
| 634 | |
| 635 Statement visitLabeledStatement(LabeledStatement node) { | |
| 636 if (node.next is Jump) { | |
| 637 // Eliminate label if next is a break or continue statement | |
| 638 // Breaks to this label are redirected to the outer label. | |
| 639 // Note that breakCount for the two labels is updated proactively here | |
| 640 // so breaks can reliably tell if they should inline their target. | |
| 641 Jump next = node.next; | |
| 642 Jump newJump = redirect(next); | |
| 643 labelRedirects[node.label] = newJump; | |
| 644 newJump.target.useCount += node.label.useCount - 1; | |
| 645 node.label.useCount = 0; | |
| 646 Statement result = visitStatement(node.body); | |
| 647 labelRedirects.remove(node.label); // Save some space. | |
| 648 return result; | |
| 649 } | |
| 650 | |
| 651 safeForInlining.add(node.label); | |
| 652 node.body = visitStatement(node.body); | |
| 653 safeForInlining.remove(node.label); | |
| 654 | |
| 655 if (node.label.useCount == 0) { | |
| 656 // Eliminate the label if next was inlined at a break | |
| 657 return node.body; | |
| 658 } | |
| 659 | |
| 660 // Do not propagate assignments into the successor statements, since they | |
| 661 // may be overwritten by assignments in the body. | |
| 662 inEmptyEnvironment(() { | |
| 663 node.next = visitStatement(node.next); | |
| 664 }); | |
| 665 | |
| 666 return node; | |
| 667 } | |
| 668 | |
| 669 Statement visitIf(If node) { | |
| 670 // Do not propagate assignments into branches. | |
| 671 inEmptyEnvironment(() { | |
| 672 node.thenStatement = visitStatement(node.thenStatement); | |
| 673 node.elseStatement = visitStatement(node.elseStatement); | |
| 674 }); | |
| 675 | |
| 676 node.condition = visitExpression(node.condition); | |
| 677 | |
| 678 inEmptyEnvironment(() { | |
| 679 tryCollapseIf(node); | |
| 680 }); | |
| 681 | |
| 682 Statement reduced = combineStatementsInBranches( | |
| 683 node.thenStatement, node.elseStatement, node.condition); | |
| 684 if (reduced != null) { | |
| 685 return reduced; | |
| 686 } | |
| 687 | |
| 688 return node; | |
| 689 } | |
| 690 | |
| 691 Statement visitWhileTrue(WhileTrue node) { | |
| 692 // Do not propagate assignments into loops. Doing so is not safe for | |
| 693 // variables modified in the loop (the initial value will be propagated). | |
| 694 // Do not propagate effective constant expressions into loops, since | |
| 695 // computing them is not free (e.g. interceptors are expensive). | |
| 696 inEmptyEnvironment(() { | |
| 697 node.body = visitStatement(node.body); | |
| 698 }, keepConstants: false); | |
| 699 return node; | |
| 700 } | |
| 701 | |
| 702 Statement visitFor(For node) { | |
| 703 // Not introduced yet | |
| 704 throw "Unexpected For in StatementRewriter"; | |
| 705 } | |
| 706 | |
| 707 Statement visitTry(Try node) { | |
| 708 inEmptyEnvironment(() { | |
| 709 Set<Label> saved = safeForInlining; | |
| 710 safeForInlining = new Set<Label>(); | |
| 711 node.tryBody = visitStatement(node.tryBody); | |
| 712 safeForInlining = saved; | |
| 713 node.catchParameters.forEach(pushDominatingAssignment); | |
| 714 node.catchBody = visitStatement(node.catchBody); | |
| 715 node.catchParameters.forEach(popDominatingAssignment); | |
| 716 }); | |
| 717 return node; | |
| 718 } | |
| 719 | |
| 720 Expression visitConstant(Constant node) { | |
| 721 if (isRhsPropagationAllowed && !environment.isEmpty) { | |
| 722 Constant constant = getRightHandConstant(environment.last); | |
| 723 if (constant != null && constant.value == node.value) { | |
| 724 return visitExpression(environment.removeLast()); | |
| 725 } | |
| 726 } | |
| 727 return node; | |
| 728 } | |
| 729 | |
| 730 Expression visitThis(This node) { | |
| 731 return node; | |
| 732 } | |
| 733 | |
| 734 Expression visitLiteralList(LiteralList node) { | |
| 735 _rewriteList(node.values); | |
| 736 return node; | |
| 737 } | |
| 738 | |
| 739 Expression visitTypeOperator(TypeOperator node) { | |
| 740 _rewriteList(node.typeArguments); | |
| 741 node.value = visitExpression(node.value); | |
| 742 return node; | |
| 743 } | |
| 744 | |
| 745 bool isCompoundableBuiltin(Expression e) { | |
| 746 return e is ApplyBuiltinOperator && | |
| 747 e.arguments.length >= 2 && | |
| 748 isCompoundableOperator(e.operator); | |
| 749 } | |
| 750 | |
| 751 /// Converts a compoundable operator application into the right-hand side for | |
| 752 /// use in a compound assignment, discarding the left-hand value. | |
| 753 /// | |
| 754 /// For example, for `x + y + z` it returns `y + z`. | |
| 755 Expression contractCompoundableBuiltin(ApplyBuiltinOperator e) { | |
| 756 assert(isCompoundableBuiltin(e)); | |
| 757 if (e.arguments.length > 2) { | |
| 758 assert(e.operator == BuiltinOperator.StringConcatenate); | |
| 759 return new ApplyBuiltinOperator( | |
| 760 e.operator, e.arguments.skip(1).toList(), e.sourceInformation); | |
| 761 } else { | |
| 762 return e.arguments[1]; | |
| 763 } | |
| 764 } | |
| 765 | |
| 766 void destroyVariableUse(VariableUse node) { | |
| 767 --node.variable.readCount; | |
| 768 } | |
| 769 | |
| 770 Expression visitSetField(SetField node) { | |
| 771 allowRhsPropagation.add(true); | |
| 772 node.value = visitExpression(node.value); | |
| 773 if (isCompoundableBuiltin(node.value)) { | |
| 774 ApplyBuiltinOperator rhs = node.value; | |
| 775 Expression left = rhs.arguments[0]; | |
| 776 if (left is GetField && | |
| 777 left.field == node.field && | |
| 778 samePrimary(left.object, node.object)) { | |
| 779 destroyPrimaryExpression(left.object); | |
| 780 node.compound = rhs.operator; | |
| 781 node.value = contractCompoundableBuiltin(rhs); | |
| 782 } | |
| 783 } | |
| 784 node.object = visitExpression(node.object); | |
| 785 allowRhsPropagation.removeLast(); | |
| 786 return node; | |
| 787 } | |
| 788 | |
| 789 Expression visitGetField(GetField node) { | |
| 790 node.object = visitExpression(node.object); | |
| 791 return node; | |
| 792 } | |
| 793 | |
| 794 Expression visitGetStatic(GetStatic node) { | |
| 795 return node; | |
| 796 } | |
| 797 | |
| 798 Expression visitSetStatic(SetStatic node) { | |
| 799 allowRhsPropagation.add(true); | |
| 800 node.value = visitExpression(node.value); | |
| 801 if (isCompoundableBuiltin(node.value)) { | |
| 802 ApplyBuiltinOperator rhs = node.value; | |
| 803 Expression left = rhs.arguments[0]; | |
| 804 if (left is GetStatic && | |
| 805 left.element == node.element && | |
| 806 !left.useLazyGetter) { | |
| 807 node.compound = rhs.operator; | |
| 808 node.value = contractCompoundableBuiltin(rhs); | |
| 809 } | |
| 810 } | |
| 811 allowRhsPropagation.removeLast(); | |
| 812 return node; | |
| 813 } | |
| 814 | |
| 815 Expression visitGetTypeTestProperty(GetTypeTestProperty node) { | |
| 816 node.object = visitExpression(node.object); | |
| 817 return node; | |
| 818 } | |
| 819 | |
| 820 Expression visitCreateBox(CreateBox node) { | |
| 821 return node; | |
| 822 } | |
| 823 | |
| 824 Expression visitCreateInstance(CreateInstance node) { | |
| 825 if (node.typeInformation != null) { | |
| 826 node.typeInformation = visitExpression(node.typeInformation); | |
| 827 } | |
| 828 _rewriteList(node.arguments); | |
| 829 return node; | |
| 830 } | |
| 831 | |
| 832 Expression visitReifyRuntimeType(ReifyRuntimeType node) { | |
| 833 node.value = visitExpression(node.value); | |
| 834 return node; | |
| 835 } | |
| 836 | |
| 837 Expression visitReadTypeVariable(ReadTypeVariable node) { | |
| 838 node.target = visitExpression(node.target); | |
| 839 return node; | |
| 840 } | |
| 841 | |
| 842 Expression visitTypeExpression(TypeExpression node) { | |
| 843 _rewriteList(node.arguments); | |
| 844 return node; | |
| 845 } | |
| 846 | |
| 847 Expression visitCreateInvocationMirror(CreateInvocationMirror node) { | |
| 848 _rewriteList(node.arguments); | |
| 849 return node; | |
| 850 } | |
| 851 | |
| 852 Expression visitInterceptor(Interceptor node) { | |
| 853 node.input = visitExpression(node.input); | |
| 854 return node; | |
| 855 } | |
| 856 | |
| 857 Expression visitGetLength(GetLength node) { | |
| 858 node.object = visitExpression(node.object); | |
| 859 return node; | |
| 860 } | |
| 861 | |
| 862 Expression visitGetIndex(GetIndex node) { | |
| 863 node.index = visitExpression(node.index); | |
| 864 node.object = visitExpression(node.object); | |
| 865 return node; | |
| 866 } | |
| 867 | |
| 868 Expression visitSetIndex(SetIndex node) { | |
| 869 node.value = visitExpression(node.value); | |
| 870 if (isCompoundableBuiltin(node.value)) { | |
| 871 ApplyBuiltinOperator rhs = node.value; | |
| 872 Expression left = rhs.arguments[0]; | |
| 873 if (left is GetIndex && | |
| 874 samePrimary(left.object, node.object) && | |
| 875 samePrimary(left.index, node.index)) { | |
| 876 destroyPrimaryExpression(left.object); | |
| 877 destroyPrimaryExpression(left.index); | |
| 878 node.compound = rhs.operator; | |
| 879 node.value = contractCompoundableBuiltin(rhs); | |
| 880 } | |
| 881 } | |
| 882 node.index = visitExpression(node.index); | |
| 883 node.object = visitExpression(node.object); | |
| 884 return node; | |
| 885 } | |
| 886 | |
| 887 /// True if [operator] is a binary operator that always has the same value | |
| 888 /// if its arguments are swapped. | |
| 889 bool isSymmetricOperator(BuiltinOperator operator) { | |
| 890 switch (operator) { | |
| 891 case BuiltinOperator.StrictEq: | |
| 892 case BuiltinOperator.StrictNeq: | |
| 893 case BuiltinOperator.LooseEq: | |
| 894 case BuiltinOperator.LooseNeq: | |
| 895 case BuiltinOperator.NumAnd: | |
| 896 case BuiltinOperator.NumOr: | |
| 897 case BuiltinOperator.NumXor: | |
| 898 case BuiltinOperator.NumAdd: | |
| 899 case BuiltinOperator.NumMultiply: | |
| 900 return true; | |
| 901 default: | |
| 902 return false; | |
| 903 } | |
| 904 } | |
| 905 | |
| 906 /// If [operator] is a commutable binary operator, returns the commuted | |
| 907 /// operator, possibly the operator itself, otherwise returns `null`. | |
| 908 BuiltinOperator commuteBinaryOperator(BuiltinOperator operator) { | |
| 909 if (isSymmetricOperator(operator)) { | |
| 910 // Symmetric operators are their own commutes. | |
| 911 return operator; | |
| 912 } | |
| 913 switch (operator) { | |
| 914 case BuiltinOperator.NumLt: | |
| 915 return BuiltinOperator.NumGt; | |
| 916 case BuiltinOperator.NumLe: | |
| 917 return BuiltinOperator.NumGe; | |
| 918 case BuiltinOperator.NumGt: | |
| 919 return BuiltinOperator.NumLt; | |
| 920 case BuiltinOperator.NumGe: | |
| 921 return BuiltinOperator.NumLe; | |
| 922 default: | |
| 923 return null; | |
| 924 } | |
| 925 } | |
| 926 | |
| 927 /// Built-in binary operators are commuted when it is safe and can enable an | |
| 928 /// assignment propagation. For example: | |
| 929 /// | |
| 930 /// var x = foo(); | |
| 931 /// var y = bar(); | |
| 932 /// var z = y < x; | |
| 933 /// | |
| 934 /// ==> | |
| 935 /// | |
| 936 /// var z = foo() > bar(); | |
| 937 /// | |
| 938 /// foo() must be evaluated before bar(), so the propagation is only possible | |
| 939 /// by commuting the operator. | |
| 940 Expression visitApplyBuiltinOperator(ApplyBuiltinOperator node) { | |
| 941 if (!environment.isEmpty && getLeftHand(environment.last) != null) { | |
| 942 Variable propagatableVariable = getLeftHand(environment.last); | |
| 943 BuiltinOperator commuted = commuteBinaryOperator(node.operator); | |
| 944 if (commuted != null) { | |
| 945 // Only binary operators can commute. | |
| 946 assert(node.arguments.length == 2); | |
| 947 Expression left = node.arguments[0]; | |
| 948 if (left is VariableUse && propagatableVariable == left.variable) { | |
| 949 Expression right = node.arguments[1]; | |
| 950 if (right is This || | |
| 951 (right is VariableUse && | |
| 952 propagatableVariable != right.variable && | |
| 953 !constantEnvironment.containsKey(right.variable))) { | |
| 954 // An assignment can be propagated if we commute the operator. | |
| 955 node.operator = commuted; | |
| 956 node.arguments[0] = right; | |
| 957 node.arguments[1] = left; | |
| 958 } | |
| 959 } | |
| 960 } | |
| 961 } | |
| 962 // Avoid code like `p == (q.f = null)`. JS operators with a constant operand | |
| 963 // can sometimes be compiled to a specialized instruction in the JS engine, | |
| 964 // so retain syntactically constant operands. | |
| 965 _rewriteList(node.arguments, rhsPropagation: false); | |
| 966 return node; | |
| 967 } | |
| 968 | |
| 969 /// If [s] and [t] are similar statements we extract their subexpressions | |
| 970 /// and returns a new statement of the same type using expressions combined | |
| 971 /// with the [combine] callback. For example: | |
| 972 /// | |
| 973 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) | |
| 974 /// | |
| 975 /// If [combine] returns E1 then the unified statement is equivalent to [s], | |
| 976 /// and if [combine] returns E2 the unified statement is equivalence to [t]. | |
| 977 /// | |
| 978 /// It is guaranteed that no side effects occur between the beginning of the | |
| 979 /// statement and the position of the combined expression. | |
| 980 /// | |
| 981 /// Returns null if the statements are too different. | |
| 982 /// | |
| 983 /// If non-null is returned, the caller MUST discard [s] and [t] and use | |
| 984 /// the returned statement instead. | |
| 985 Statement combineStatementsInBranches( | |
| 986 Statement s, Statement t, Expression condition) { | |
| 987 if (s is Return && t is Return) { | |
| 988 return new Return(new Conditional(condition, s.value, t.value)); | |
| 989 } | |
| 990 if (s is ExpressionStatement && t is ExpressionStatement) { | |
| 991 // Combine the two expressions and the two successor statements. | |
| 992 // | |
| 993 // C ? {E1 ; S1} : {E2 ; S2} | |
| 994 // ==> | |
| 995 // (C ? E1 : E2) : combine(S1, S2) | |
| 996 // | |
| 997 // If E1 and E2 are assignments, we want to propagate these into the | |
| 998 // combined statement. | |
| 999 // | |
| 1000 // It might not be possible to combine the statements, so we combine the | |
| 1001 // expressions, put the result in the environment, and then uncombine the | |
| 1002 // expressions if the statements could not be combined. | |
| 1003 | |
| 1004 // Combine the expressions. | |
| 1005 CombinedExpressions values = | |
| 1006 combineAsConditional(s.expression, t.expression, condition); | |
| 1007 | |
| 1008 // Put this into the environment and try to combine the statements. | |
| 1009 // We are not in risk of reprocessing the original subexpressions because | |
| 1010 // the combined expression will always hide them inside a Conditional. | |
| 1011 environment.add(values.combined); | |
| 1012 | |
| 1013 Variable leftHand = getLeftHand(values.combined); | |
| 1014 pushDominatingAssignment(leftHand); | |
| 1015 Statement next = combineStatements(s.next, t.next); | |
| 1016 popDominatingAssignment(leftHand); | |
| 1017 | |
| 1018 if (next == null) { | |
| 1019 // Statements could not be combined. | |
| 1020 // Restore the environment and uncombine expressions again. | |
| 1021 environment.removeLast(); | |
| 1022 values.uncombine(); | |
| 1023 return null; | |
| 1024 } else if (!environment.isEmpty && environment.last == values.combined) { | |
| 1025 // Statements were combined but the combined expression could not be | |
| 1026 // propagated. Leave it as an expression statement here. | |
| 1027 environment.removeLast(); | |
| 1028 s.expression = values.combined; | |
| 1029 s.next = next; | |
| 1030 return s; | |
| 1031 } else { | |
| 1032 // Statements were combined and the combined expressions were | |
| 1033 // propagated into the combined statement. | |
| 1034 return next; | |
| 1035 } | |
| 1036 } | |
| 1037 return null; | |
| 1038 } | |
| 1039 | |
| 1040 /// Creates the expression `[condition] ? [s] : [t]` or an equivalent | |
| 1041 /// expression if something better can be done. | |
| 1042 /// | |
| 1043 /// In particular, assignments will be merged as follows: | |
| 1044 /// | |
| 1045 /// C ? (v = E1) : (v = E2) | |
| 1046 /// ==> | |
| 1047 /// v = C ? E1 : E2 | |
| 1048 /// | |
| 1049 /// The latter form is more compact and can also be inlined. | |
| 1050 CombinedExpressions combineAsConditional( | |
| 1051 Expression s, Expression t, Expression condition) { | |
| 1052 if (s is Assign && t is Assign && s.variable == t.variable) { | |
| 1053 Expression values = new Conditional(condition, s.value, t.value); | |
| 1054 return new CombinedAssigns(s, t, new CombinedExpressions(values)); | |
| 1055 } | |
| 1056 return new CombinedExpressions(new Conditional(condition, s, t)); | |
| 1057 } | |
| 1058 | |
| 1059 /// Returns a statement equivalent to both [s] and [t], or null if [s] and | |
| 1060 /// [t] are incompatible. | |
| 1061 /// If non-null is returned, the caller MUST discard [s] and [t] and use | |
| 1062 /// the returned statement instead. | |
| 1063 /// If two breaks are combined, the label's break counter will be decremented. | |
| 1064 Statement combineStatements(Statement s, Statement t) { | |
| 1065 if (s is Break && t is Break && s.target == t.target) { | |
| 1066 --t.target.useCount; // Two breaks become one. | |
| 1067 if (s.target.useCount == 1 && safeForInlining.contains(s.target)) { | |
| 1068 // Only one break remains; inline it. | |
| 1069 --s.target.useCount; | |
| 1070 return visitStatement(s.target.binding.next); | |
| 1071 } | |
| 1072 return s; | |
| 1073 } | |
| 1074 if (s is Continue && t is Continue && s.target == t.target) { | |
| 1075 --t.target.useCount; // Two continues become one. | |
| 1076 return s; | |
| 1077 } | |
| 1078 if (s is Return && t is Return) { | |
| 1079 CombinedExpressions values = combineExpressions(s.value, t.value); | |
| 1080 if (values != null) { | |
| 1081 // TODO(johnniwinther): Handle multiple source informations. | |
| 1082 SourceInformation sourceInformation = s.sourceInformation != null | |
| 1083 ? s.sourceInformation | |
| 1084 : t.sourceInformation; | |
| 1085 return new Return(values.combined, | |
| 1086 sourceInformation: sourceInformation); | |
| 1087 } | |
| 1088 } | |
| 1089 if (s is ExpressionStatement && t is ExpressionStatement) { | |
| 1090 CombinedExpressions values = | |
| 1091 combineExpressions(s.expression, t.expression); | |
| 1092 if (values == null) return null; | |
| 1093 environment.add(values.combined); | |
| 1094 Variable leftHand = getLeftHand(values.combined); | |
| 1095 pushDominatingAssignment(leftHand); | |
| 1096 Statement next = combineStatements(s.next, t.next); | |
| 1097 popDominatingAssignment(leftHand); | |
| 1098 if (next == null) { | |
| 1099 // The successors could not be combined. | |
| 1100 // Restore the environment and uncombine the values again. | |
| 1101 assert(environment.last == values.combined); | |
| 1102 environment.removeLast(); | |
| 1103 values.uncombine(); | |
| 1104 return null; | |
| 1105 } else if (!environment.isEmpty && environment.last == values.combined) { | |
| 1106 // The successors were combined but the combined expressions were not | |
| 1107 // propagated. Leave the combined expression as a statement. | |
| 1108 environment.removeLast(); | |
| 1109 s.expression = values.combined; | |
| 1110 s.next = next; | |
| 1111 return s; | |
| 1112 } else { | |
| 1113 // The successors were combined, and the combined expressions were | |
| 1114 // propagated into the successors. | |
| 1115 return next; | |
| 1116 } | |
| 1117 } | |
| 1118 return null; | |
| 1119 } | |
| 1120 | |
| 1121 /// Returns an expression equivalent to both [e1] and [e2]. | |
| 1122 /// If non-null is returned, the caller must discard [e1] and [e2] and use | |
| 1123 /// the resulting expression in the tree. | |
| 1124 CombinedExpressions combineExpressions(Expression e1, Expression e2) { | |
| 1125 if (e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable) { | |
| 1126 return new CombinedUses(e1, e2); | |
| 1127 } | |
| 1128 if (e1 is Assign && e2 is Assign && e1.variable == e2.variable) { | |
| 1129 CombinedExpressions values = combineExpressions(e1.value, e2.value); | |
| 1130 if (values != null) { | |
| 1131 return new CombinedAssigns(e1, e2, values); | |
| 1132 } | |
| 1133 } | |
| 1134 if (e1 is Constant && e2 is Constant && e1.value == e2.value) { | |
| 1135 return new CombinedExpressions(e1); | |
| 1136 } | |
| 1137 return null; | |
| 1138 } | |
| 1139 | |
| 1140 /// Try to collapse nested ifs using && and || expressions. | |
| 1141 /// For example: | |
| 1142 /// | |
| 1143 /// if (E1) { if (E2) S else break L } else break L | |
| 1144 /// ==> | |
| 1145 /// if (E1 && E2) S else break L | |
| 1146 /// | |
| 1147 /// [branch1] and [branch2] control the position of the S statement. | |
| 1148 /// | |
| 1149 /// Must be called with an empty environment. | |
| 1150 void tryCollapseIf(If node) { | |
| 1151 assert(environment.isEmpty); | |
| 1152 // Repeatedly try to collapse nested ifs. | |
| 1153 // The transformation is shrinking (destroys an if) so it remains linear. | |
| 1154 // Here is an example where more than one iteration is required: | |
| 1155 // | |
| 1156 // if (E1) | |
| 1157 // if (E2) break L2 else break L1 | |
| 1158 // else | |
| 1159 // break L1 | |
| 1160 // | |
| 1161 // L1.target ::= | |
| 1162 // if (E3) S else break L2 | |
| 1163 // | |
| 1164 // After first collapse: | |
| 1165 // | |
| 1166 // if (E1 && E2) | |
| 1167 // break L2 | |
| 1168 // else | |
| 1169 // {if (E3) S else break L2} (inlined from break L1) | |
| 1170 // | |
| 1171 // We can then do another collapse using the inlined nested if. | |
| 1172 bool changed = true; | |
| 1173 while (changed) { | |
| 1174 changed = false; | |
| 1175 if (tryCollapseIfAux(node, true, true)) { | |
| 1176 changed = true; | |
| 1177 } | |
| 1178 if (tryCollapseIfAux(node, true, false)) { | |
| 1179 changed = true; | |
| 1180 } | |
| 1181 if (tryCollapseIfAux(node, false, true)) { | |
| 1182 changed = true; | |
| 1183 } | |
| 1184 if (tryCollapseIfAux(node, false, false)) { | |
| 1185 changed = true; | |
| 1186 } | |
| 1187 } | |
| 1188 } | |
| 1189 | |
| 1190 bool tryCollapseIfAux(If outerIf, bool branch1, bool branch2) { | |
| 1191 // NOTE: We name variables here as if S is in the then-then position. | |
| 1192 Statement outerThen = getBranch(outerIf, branch1); | |
| 1193 Statement outerElse = getBranch(outerIf, !branch1); | |
| 1194 if (outerThen is If) { | |
| 1195 If innerIf = outerThen; | |
| 1196 Statement innerThen = getBranch(innerIf, branch2); | |
| 1197 Statement innerElse = getBranch(innerIf, !branch2); | |
| 1198 Statement combinedElse = combineStatements(innerElse, outerElse); | |
| 1199 if (combinedElse != null) { | |
| 1200 // We always put S in the then branch of the result, and adjust the | |
| 1201 // condition expression if S was actually found in the else branch(es). | |
| 1202 outerIf.condition = new LogicalOperator.and( | |
| 1203 makeCondition(outerIf.condition, branch1), | |
| 1204 makeCondition(innerIf.condition, branch2)); | |
| 1205 outerIf.thenStatement = innerThen; | |
| 1206 outerIf.elseStatement = combinedElse; | |
| 1207 return outerIf.elseStatement is If; | |
| 1208 } | |
| 1209 } | |
| 1210 return false; | |
| 1211 } | |
| 1212 | |
| 1213 Expression makeCondition(Expression e, bool polarity) { | |
| 1214 return polarity ? e : new Not(e); | |
| 1215 } | |
| 1216 | |
| 1217 Statement getBranch(If node, bool polarity) { | |
| 1218 return polarity ? node.thenStatement : node.elseStatement; | |
| 1219 } | |
| 1220 | |
| 1221 void handleForeignCode(ForeignCode node) { | |
| 1222 // Some arguments will get inserted in a JS code template. The arguments | |
| 1223 // will not always be evaluated (e.g. the second placeholder in the template | |
| 1224 // '# && #'). | |
| 1225 bool isNullable(int position) => node.nullableArguments[position]; | |
| 1226 | |
| 1227 int safeArguments = | |
| 1228 PlaceholderSafetyAnalysis.analyze(node.codeTemplate.ast, isNullable); | |
| 1229 inEmptyEnvironment(() { | |
| 1230 for (int i = node.arguments.length - 1; i >= safeArguments; --i) { | |
| 1231 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 1232 } | |
| 1233 }); | |
| 1234 for (int i = safeArguments - 1; i >= 0; --i) { | |
| 1235 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 1236 } | |
| 1237 } | |
| 1238 | |
| 1239 @override | |
| 1240 Expression visitForeignExpression(ForeignExpression node) { | |
| 1241 handleForeignCode(node); | |
| 1242 return node; | |
| 1243 } | |
| 1244 | |
| 1245 @override | |
| 1246 Statement visitForeignStatement(ForeignStatement node) { | |
| 1247 handleForeignCode(node); | |
| 1248 return node; | |
| 1249 } | |
| 1250 | |
| 1251 @override | |
| 1252 Expression visitAwait(Await node) { | |
| 1253 node.input = visitExpression(node.input); | |
| 1254 return node; | |
| 1255 } | |
| 1256 | |
| 1257 @override | |
| 1258 Statement visitYield(Yield node) { | |
| 1259 node.next = visitStatement(node.next); | |
| 1260 node.input = visitExpression(node.input); | |
| 1261 return node; | |
| 1262 } | |
| 1263 | |
| 1264 @override | |
| 1265 Statement visitReceiverCheck(ReceiverCheck node) { | |
| 1266 inEmptyEnvironment(() { | |
| 1267 node.next = visitStatement(node.next); | |
| 1268 }); | |
| 1269 if (node.condition != null) { | |
| 1270 inEmptyEnvironment(() { | |
| 1271 // Value occurs in conditional context. | |
| 1272 node.value = visitExpression(node.value); | |
| 1273 }); | |
| 1274 node.condition = visitExpression(node.condition); | |
| 1275 } else { | |
| 1276 node.value = visitExpression(node.value); | |
| 1277 } | |
| 1278 return node; | |
| 1279 } | |
| 1280 } | |
| 1281 | |
| 1282 /// Result of combining two expressions, with the potential for reverting the | |
| 1283 /// combination. | |
| 1284 /// | |
| 1285 /// Reverting a combination is done by calling [uncombine]. In this case, | |
| 1286 /// both the original expressions should remain in the tree, and the [combined] | |
| 1287 /// expression should be orphaned. | |
| 1288 /// | |
| 1289 /// Explicitly reverting a combination is necessary to maintain variable | |
| 1290 /// reference counts. | |
| 1291 abstract class CombinedExpressions { | |
| 1292 Expression get combined; | |
| 1293 void uncombine(); | |
| 1294 | |
| 1295 factory CombinedExpressions(Expression e) = GenericCombinedExpressions; | |
| 1296 } | |
| 1297 | |
| 1298 /// Combines assignments of form `[variable] := E1` and `[variable] := E2` into | |
| 1299 /// a single assignment of form `[variable] := combine(E1, E2)`. | |
| 1300 class CombinedAssigns implements CombinedExpressions { | |
| 1301 Assign assign1, assign2; | |
| 1302 CombinedExpressions value; | |
| 1303 Expression combined; | |
| 1304 | |
| 1305 CombinedAssigns(this.assign1, this.assign2, this.value) { | |
| 1306 assert(assign1.variable == assign2.variable); | |
| 1307 assign1.variable.writeCount -= 2; // Destroy the two original assignemnts. | |
| 1308 combined = new Assign(assign1.variable, value.combined); | |
| 1309 } | |
| 1310 | |
| 1311 void uncombine() { | |
| 1312 value.uncombine(); | |
| 1313 ++assign1.variable.writeCount; // Restore original reference count. | |
| 1314 } | |
| 1315 } | |
| 1316 | |
| 1317 /// Combines two variable uses into one. | |
| 1318 class CombinedUses implements CombinedExpressions { | |
| 1319 VariableUse use1, use2; | |
| 1320 Expression combined; | |
| 1321 | |
| 1322 CombinedUses(this.use1, this.use2) { | |
| 1323 assert(use1.variable == use2.variable); | |
| 1324 use1.variable.readCount -= 2; // Destroy both the original uses. | |
| 1325 combined = new VariableUse(use1.variable); | |
| 1326 } | |
| 1327 | |
| 1328 void uncombine() { | |
| 1329 ++use1.variable.readCount; // Restore original reference count. | |
| 1330 } | |
| 1331 } | |
| 1332 | |
| 1333 /// Result of combining two expressions that do not affect reference counting. | |
| 1334 class GenericCombinedExpressions implements CombinedExpressions { | |
| 1335 Expression combined; | |
| 1336 | |
| 1337 GenericCombinedExpressions(this.combined); | |
| 1338 | |
| 1339 void uncombine() {} | |
| 1340 } | |
| 1341 | |
| 1342 /// Looks for uses of a specific variable. | |
| 1343 /// | |
| 1344 /// Note that this visitor is only applied to expressions where all | |
| 1345 /// sub-expressions are known to be variable uses, so there is no risk of | |
| 1346 /// explosive reprocessing. | |
| 1347 class IsVariableUsedVisitor extends RecursiveVisitor { | |
| 1348 Variable variable; | |
| 1349 bool wasFound = false; | |
| 1350 | |
| 1351 IsVariableUsedVisitor(this.variable); | |
| 1352 | |
| 1353 visitVariableUse(VariableUse node) { | |
| 1354 if (node.variable == variable) { | |
| 1355 wasFound = true; | |
| 1356 } | |
| 1357 } | |
| 1358 } | |
| 1359 | |
| 1360 typedef VariableUseCallback(VariableUse use); | |
| 1361 | |
| 1362 class VariableUseVisitor extends RecursiveVisitor { | |
| 1363 VariableUseCallback callback; | |
| 1364 | |
| 1365 VariableUseVisitor(this.callback); | |
| 1366 | |
| 1367 visitVariableUse(VariableUse use) => callback(use); | |
| 1368 | |
| 1369 static void visit(Expression node, VariableUseCallback callback) { | |
| 1370 new VariableUseVisitor(callback).visitExpression(node); | |
| 1371 } | |
| 1372 } | |
| 1373 | |
| 1374 bool sameVariable(Expression e1, Expression e2) { | |
| 1375 return e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable; | |
| 1376 } | |
| 1377 | |
| 1378 /// True if [e1] and [e2] are primary expressions (expressions without | |
| 1379 /// subexpressions) with the same value. | |
| 1380 bool samePrimary(Expression e1, Expression e2) { | |
| 1381 return sameVariable(e1, e2) || (e1 is This && e2 is This); | |
| 1382 } | |
| 1383 | |
| 1384 /// Decrement the reference count for [e] if it is a variable use. | |
| 1385 void destroyPrimaryExpression(Expression e) { | |
| 1386 if (e is VariableUse) { | |
| 1387 --e.variable.readCount; | |
| 1388 } else { | |
| 1389 assert(e is This); | |
| 1390 } | |
| 1391 } | |
| OLD | NEW |