| 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 statement_rewriter; | |
| 6 | |
| 7 import 'tree_ir_nodes.dart'; | |
| 8 | |
| 9 /** | |
| 10 * Performs the following transformations on the tree: | |
| 11 * - Assignment propagation | |
| 12 * - If-to-conditional conversion | |
| 13 * - Flatten nested ifs | |
| 14 * - Break inlining | |
| 15 * - Redirect breaks | |
| 16 * | |
| 17 * The above transformations all eliminate statements from the tree, and may | |
| 18 * introduce redexes of each other. | |
| 19 * | |
| 20 * | |
| 21 * ASSIGNMENT PROPAGATION: | |
| 22 * Single-use definitions are propagated to their use site when possible. | |
| 23 * For example: | |
| 24 * | |
| 25 * { v0 = foo(); return v0; } | |
| 26 * ==> | |
| 27 * return foo() | |
| 28 * | |
| 29 * After translating out of CPS, all intermediate values are bound by [Assign]. | |
| 30 * This transformation propagates such definitions to their uses when it is | |
| 31 * safe and profitable. Bindings are processed "on demand" when their uses are | |
| 32 * seen, but are only processed once to keep this transformation linear in | |
| 33 * the size of the tree. | |
| 34 * | |
| 35 * The transformation builds an environment containing [Assign] bindings that | |
| 36 * are in scope. These bindings have yet-untranslated definitions. When a use | |
| 37 * is encountered the transformation determines if it is safe and profitable | |
| 38 * to propagate the definition to its use. If so, it is removed from the | |
| 39 * environment and the definition is recursively processed (in the | |
| 40 * new environment at the use site) before being propagated. | |
| 41 * | |
| 42 * See [visitVariable] for the implementation of the heuristic for propagating | |
| 43 * a definition. | |
| 44 * | |
| 45 * | |
| 46 * IF-TO-CONDITIONAL CONVERSION: | |
| 47 * If-statement are converted to conditional expressions when possible. | |
| 48 * For example: | |
| 49 * | |
| 50 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } | |
| 51 * ==> | |
| 52 * { v1 = v0 ? foo() : bar(); break L } | |
| 53 * | |
| 54 * This can lead to inlining of L, which in turn can lead to further propagation | |
| 55 * of the variable v1. | |
| 56 * | |
| 57 * See [visitIf]. | |
| 58 * | |
| 59 * | |
| 60 * FLATTEN NESTED IFS: | |
| 61 * An if inside an if is converted to an if with a logical operator. | |
| 62 * For example: | |
| 63 * | |
| 64 * if (E1) { if (E2) {S} else break L } else break L | |
| 65 * ==> | |
| 66 * if (E1 && E2) {S} else break L | |
| 67 * | |
| 68 * This may lead to inlining of L. | |
| 69 * | |
| 70 * | |
| 71 * BREAK INLINING: | |
| 72 * Single-use labels are inlined at [Break] statements. | |
| 73 * For example: | |
| 74 * | |
| 75 * L0: { v0 = foo(); break L0 }; return v0; | |
| 76 * ==> | |
| 77 * v0 = foo(); return v0; | |
| 78 * | |
| 79 * This can lead to propagation of v0. | |
| 80 * | |
| 81 * See [visitBreak] and [visitLabeledStatement]. | |
| 82 * | |
| 83 * | |
| 84 * REDIRECT BREAKS: | |
| 85 * Labeled statements whose next is a break become flattened and all breaks | |
| 86 * to their label are redirected. | |
| 87 * For example, where 'jump' is either break or continue: | |
| 88 * | |
| 89 * L0: {... break L0 ...}; jump L1 | |
| 90 * ==> | |
| 91 * {... jump L1 ...} | |
| 92 * | |
| 93 * This may trigger a flattening of nested ifs in case the eliminated label | |
| 94 * separated two ifs. | |
| 95 */ | |
| 96 class StatementRewriter extends Visitor<Statement, Expression> { | |
| 97 // The binding environment. The rightmost element of the list is the nearest | |
| 98 // available enclosing binding. | |
| 99 List<Assign> environment; | |
| 100 | |
| 101 /// Substitution map for labels. Any break to a label L should be substituted | |
| 102 /// for a break to L' if L maps to L'. | |
| 103 Map<Label, Jump> labelRedirects = <Label, Jump>{}; | |
| 104 | |
| 105 /// Returns the redirect target of [label] or [label] itself if it should not | |
| 106 /// be redirected. | |
| 107 Jump redirect(Jump jump) { | |
| 108 Jump newJump = labelRedirects[jump.target]; | |
| 109 return newJump != null ? newJump : jump; | |
| 110 } | |
| 111 | |
| 112 void rewrite(FunctionDefinition definition) { | |
| 113 if (definition.isAbstract) return; | |
| 114 | |
| 115 environment = <Assign>[]; | |
| 116 definition.body = visitStatement(definition.body); | |
| 117 | |
| 118 // TODO(kmillikin): Allow definitions that are not propagated. Here, | |
| 119 // this means rebuilding the binding with a recursively unnamed definition, | |
| 120 // or else introducing a variable definition and an assignment. | |
| 121 assert(environment.isEmpty); | |
| 122 } | |
| 123 | |
| 124 Expression visitExpression(Expression e) => e.processed ? e : e.accept(this); | |
| 125 | |
| 126 Expression visitVariable(Variable node) { | |
| 127 // Propagate a variable's definition to its use site if: | |
| 128 // 1. It has a single use, to avoid code growth and potential duplication | |
| 129 // of side effects, AND | |
| 130 // 2. It was the most recent expression evaluated so that we do not | |
| 131 // reorder expressions with side effects. | |
| 132 if (!environment.isEmpty && | |
| 133 environment.last.variable == node && | |
| 134 environment.last.hasExactlyOneUse) { | |
| 135 return visitExpression(environment.removeLast().definition); | |
| 136 } | |
| 137 // If the definition could not be propagated, leave the variable use. | |
| 138 return node; | |
| 139 } | |
| 140 | |
| 141 | |
| 142 Statement visitAssign(Assign node) { | |
| 143 environment.add(node); | |
| 144 Statement next = visitStatement(node.next); | |
| 145 | |
| 146 if (!environment.isEmpty && environment.last == node) { | |
| 147 // The definition could not be propagated. Residualize the let binding. | |
| 148 node.next = next; | |
| 149 environment.removeLast(); | |
| 150 node.definition = visitExpression(node.definition); | |
| 151 return node; | |
| 152 } | |
| 153 assert(!environment.contains(node)); | |
| 154 return next; | |
| 155 } | |
| 156 | |
| 157 Expression visitInvokeStatic(InvokeStatic node) { | |
| 158 // Process arguments right-to-left, the opposite of evaluation order. | |
| 159 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
| 160 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 161 } | |
| 162 return node; | |
| 163 } | |
| 164 | |
| 165 Expression visitInvokeMethod(InvokeMethod node) { | |
| 166 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
| 167 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 168 } | |
| 169 node.receiver = visitExpression(node.receiver); | |
| 170 return node; | |
| 171 } | |
| 172 | |
| 173 Expression visitInvokeSuperMethod(InvokeSuperMethod node) { | |
| 174 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
| 175 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 176 } | |
| 177 return node; | |
| 178 } | |
| 179 | |
| 180 Expression visitInvokeConstructor(InvokeConstructor node) { | |
| 181 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
| 182 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 183 } | |
| 184 return node; | |
| 185 } | |
| 186 | |
| 187 Expression visitConcatenateStrings(ConcatenateStrings node) { | |
| 188 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
| 189 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 190 } | |
| 191 return node; | |
| 192 } | |
| 193 | |
| 194 Expression visitConditional(Conditional node) { | |
| 195 node.condition = visitExpression(node.condition); | |
| 196 | |
| 197 List<Assign> savedEnvironment = environment; | |
| 198 environment = <Assign>[]; | |
| 199 node.thenExpression = visitExpression(node.thenExpression); | |
| 200 assert(environment.isEmpty); | |
| 201 node.elseExpression = visitExpression(node.elseExpression); | |
| 202 assert(environment.isEmpty); | |
| 203 environment = savedEnvironment; | |
| 204 | |
| 205 return node; | |
| 206 } | |
| 207 | |
| 208 Expression visitLogicalOperator(LogicalOperator node) { | |
| 209 node.left = visitExpression(node.left); | |
| 210 | |
| 211 environment.add(null); // impure expressions may not propagate across branch | |
| 212 node.right = visitExpression(node.right); | |
| 213 environment.removeLast(); | |
| 214 | |
| 215 return node; | |
| 216 } | |
| 217 | |
| 218 Expression visitNot(Not node) { | |
| 219 node.operand = visitExpression(node.operand); | |
| 220 return node; | |
| 221 } | |
| 222 | |
| 223 Expression visitFunctionExpression(FunctionExpression node) { | |
| 224 new StatementRewriter().rewrite(node.definition); | |
| 225 return node; | |
| 226 } | |
| 227 | |
| 228 Statement visitFunctionDeclaration(FunctionDeclaration node) { | |
| 229 new StatementRewriter().rewrite(node.definition); | |
| 230 node.next = visitStatement(node.next); | |
| 231 return node; | |
| 232 } | |
| 233 | |
| 234 Statement visitReturn(Return node) { | |
| 235 node.value = visitExpression(node.value); | |
| 236 return node; | |
| 237 } | |
| 238 | |
| 239 | |
| 240 Statement visitBreak(Break node) { | |
| 241 // Redirect through chain of breaks. | |
| 242 // Note that useCount was accounted for at visitLabeledStatement. | |
| 243 // Note redirect may return either a Break or Continue statement. | |
| 244 Jump jump = redirect(node); | |
| 245 if (jump is Break && jump.target.useCount == 1) { | |
| 246 --jump.target.useCount; | |
| 247 return visitStatement(jump.target.binding.next); | |
| 248 } | |
| 249 return jump; | |
| 250 } | |
| 251 | |
| 252 Statement visitContinue(Continue node) { | |
| 253 return node; | |
| 254 } | |
| 255 | |
| 256 Statement visitLabeledStatement(LabeledStatement node) { | |
| 257 if (node.next is Jump) { | |
| 258 // Eliminate label if next is a break or continue statement | |
| 259 // Breaks to this label are redirected to the outer label. | |
| 260 // Note that breakCount for the two labels is updated proactively here | |
| 261 // so breaks can reliably tell if they should inline their target. | |
| 262 Jump next = node.next; | |
| 263 Jump newJump = redirect(next); | |
| 264 labelRedirects[node.label] = newJump; | |
| 265 newJump.target.useCount += node.label.useCount - 1; | |
| 266 node.label.useCount = 0; | |
| 267 Statement result = visitStatement(node.body); | |
| 268 labelRedirects.remove(node.label); // Save some space. | |
| 269 return result; | |
| 270 } | |
| 271 | |
| 272 node.body = visitStatement(node.body); | |
| 273 | |
| 274 if (node.label.useCount == 0) { | |
| 275 // Eliminate the label if next was inlined at a break | |
| 276 return node.body; | |
| 277 } | |
| 278 | |
| 279 // Do not propagate assignments into the successor statements, since they | |
| 280 // may be overwritten by assignments in the body. | |
| 281 List<Assign> savedEnvironment = environment; | |
| 282 environment = <Assign>[]; | |
| 283 node.next = visitStatement(node.next); | |
| 284 environment = savedEnvironment; | |
| 285 | |
| 286 return node; | |
| 287 } | |
| 288 | |
| 289 Statement visitIf(If node) { | |
| 290 node.condition = visitExpression(node.condition); | |
| 291 | |
| 292 // Do not propagate assignments into branches. Doing so will lead to code | |
| 293 // duplication. | |
| 294 // TODO(kmillikin): Rethink this. Propagating some assignments (e.g., | |
| 295 // constants or variables) is benign. If they can occur here, they should | |
| 296 // be handled well. | |
| 297 List<Assign> savedEnvironment = environment; | |
| 298 environment = <Assign>[]; | |
| 299 node.thenStatement = visitStatement(node.thenStatement); | |
| 300 assert(environment.isEmpty); | |
| 301 node.elseStatement = visitStatement(node.elseStatement); | |
| 302 assert(environment.isEmpty); | |
| 303 environment = savedEnvironment; | |
| 304 | |
| 305 tryCollapseIf(node); | |
| 306 | |
| 307 Statement reduced = combineStatementsWithSubexpressions( | |
| 308 node.thenStatement, | |
| 309 node.elseStatement, | |
| 310 (t,f) => new Conditional(node.condition, t, f)..processed = true); | |
| 311 if (reduced != null) { | |
| 312 if (reduced.next is Break) { | |
| 313 // In case the break can now be inlined. | |
| 314 reduced = visitStatement(reduced); | |
| 315 } | |
| 316 return reduced; | |
| 317 } | |
| 318 | |
| 319 return node; | |
| 320 } | |
| 321 | |
| 322 Statement visitWhileTrue(WhileTrue node) { | |
| 323 // Do not propagate assignments into loops. Doing so is not safe for | |
| 324 // variables modified in the loop (the initial value will be propagated). | |
| 325 List<Assign> savedEnvironment = environment; | |
| 326 environment = <Assign>[]; | |
| 327 node.body = visitStatement(node.body); | |
| 328 assert(environment.isEmpty); | |
| 329 environment = savedEnvironment; | |
| 330 return node; | |
| 331 } | |
| 332 | |
| 333 Statement visitWhileCondition(WhileCondition node) { | |
| 334 // Not introduced yet | |
| 335 throw "Unexpected WhileCondition in StatementRewriter"; | |
| 336 } | |
| 337 | |
| 338 Expression visitConstant(Constant node) { | |
| 339 return node; | |
| 340 } | |
| 341 | |
| 342 Expression visitThis(This node) { | |
| 343 return node; | |
| 344 } | |
| 345 | |
| 346 Expression visitReifyTypeVar(ReifyTypeVar node) { | |
| 347 return node; | |
| 348 } | |
| 349 | |
| 350 Expression visitLiteralList(LiteralList node) { | |
| 351 // Process values right-to-left, the opposite of evaluation order. | |
| 352 for (int i = node.values.length - 1; i >= 0; --i) { | |
| 353 node.values[i] = visitExpression(node.values[i]); | |
| 354 } | |
| 355 return node; | |
| 356 } | |
| 357 | |
| 358 Expression visitLiteralMap(LiteralMap node) { | |
| 359 // Process arguments right-to-left, the opposite of evaluation order. | |
| 360 for (LiteralMapEntry entry in node.entries.reversed) { | |
| 361 entry.value = visitExpression(entry.value); | |
| 362 entry.key = visitExpression(entry.key); | |
| 363 } | |
| 364 return node; | |
| 365 } | |
| 366 | |
| 367 Expression visitTypeOperator(TypeOperator node) { | |
| 368 node.receiver = visitExpression(node.receiver); | |
| 369 return node; | |
| 370 } | |
| 371 | |
| 372 Statement visitExpressionStatement(ExpressionStatement node) { | |
| 373 node.expression = visitExpression(node.expression); | |
| 374 // Do not allow propagation of assignments past an expression evaluated | |
| 375 // for its side effects because it risks reordering side effects. | |
| 376 // TODO(kmillikin): Rethink this. Some propagation is benign, e.g., | |
| 377 // constants, variables, or other pure values that are not destroyed by | |
| 378 // the expression statement. If they can occur here they should be | |
| 379 // handled well. | |
| 380 List<Assign> savedEnvironment = environment; | |
| 381 environment = <Assign>[]; | |
| 382 node.next = visitStatement(node.next); | |
| 383 assert(environment.isEmpty); | |
| 384 environment = savedEnvironment; | |
| 385 return node; | |
| 386 } | |
| 387 | |
| 388 /// If [s] and [t] are similar statements we extract their subexpressions | |
| 389 /// and returns a new statement of the same type using expressions combined | |
| 390 /// with the [combine] callback. For example: | |
| 391 /// | |
| 392 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) | |
| 393 /// | |
| 394 /// If [combine] returns E1 then the unified statement is equivalent to [s], | |
| 395 /// and if [combine] returns E2 the unified statement is equivalence to [t]. | |
| 396 /// | |
| 397 /// It is guaranteed that no side effects occur between the beginning of the | |
| 398 /// statement and the position of the combined expression. | |
| 399 /// | |
| 400 /// Returns null if the statements are too different. | |
| 401 /// | |
| 402 /// If non-null is returned, the caller MUST discard [s] and [t] and use | |
| 403 /// the returned statement instead. | |
| 404 static Statement combineStatementsWithSubexpressions( | |
| 405 Statement s, | |
| 406 Statement t, | |
| 407 Expression combine(Expression s, Expression t)) { | |
| 408 if (s is Return && t is Return) { | |
| 409 return new Return(combine(s.value, t.value)); | |
| 410 } | |
| 411 if (s is Assign && t is Assign && s.variable == t.variable) { | |
| 412 Statement next = combineStatements(s.next, t.next); | |
| 413 if (next != null) { | |
| 414 --t.variable.writeCount; // Two assignments become one. | |
| 415 return new Assign(s.variable, | |
| 416 combine(s.definition, t.definition), | |
| 417 next); | |
| 418 } | |
| 419 } | |
| 420 if (s is ExpressionStatement && t is ExpressionStatement) { | |
| 421 Statement next = combineStatements(s.next, t.next); | |
| 422 if (next != null) { | |
| 423 return new ExpressionStatement(combine(s.expression, t.expression), | |
| 424 next); | |
| 425 } | |
| 426 } | |
| 427 return null; | |
| 428 } | |
| 429 | |
| 430 /// Returns a statement equivalent to both [s] and [t], or null if [s] and | |
| 431 /// [t] are incompatible. | |
| 432 /// If non-null is returned, the caller MUST discard [s] and [t] and use | |
| 433 /// the returned statement instead. | |
| 434 /// If two breaks are combined, the label's break counter will be decremented. | |
| 435 static Statement combineStatements(Statement s, Statement t) { | |
| 436 if (s is Break && t is Break && s.target == t.target) { | |
| 437 --t.target.useCount; // Two breaks become one. | |
| 438 return s; | |
| 439 } | |
| 440 if (s is Continue && t is Continue && s.target == t.target) { | |
| 441 --t.target.useCount; // Two continues become one. | |
| 442 return s; | |
| 443 } | |
| 444 if (s is Return && t is Return) { | |
| 445 Expression e = combineExpressions(s.value, t.value); | |
| 446 if (e != null) { | |
| 447 return new Return(e); | |
| 448 } | |
| 449 } | |
| 450 return null; | |
| 451 } | |
| 452 | |
| 453 /// Returns an expression equivalent to both [e1] and [e2]. | |
| 454 /// If non-null is returned, the caller must discard [e1] and [e2] and use | |
| 455 /// the resulting expression in the tree. | |
| 456 static Expression combineExpressions(Expression e1, Expression e2) { | |
| 457 if (e1 is Variable && e1 == e2) { | |
| 458 --e1.readCount; // Two references become one. | |
| 459 return e1; | |
| 460 } | |
| 461 if (e1 is Constant && e2 is Constant && e1.value == e2.value) { | |
| 462 return e1; | |
| 463 } | |
| 464 return null; | |
| 465 } | |
| 466 | |
| 467 /// Try to collapse nested ifs using && and || expressions. | |
| 468 /// For example: | |
| 469 /// | |
| 470 /// if (E1) { if (E2) S else break L } else break L | |
| 471 /// ==> | |
| 472 /// if (E1 && E2) S else break L | |
| 473 /// | |
| 474 /// [branch1] and [branch2] control the position of the S statement. | |
| 475 /// | |
| 476 /// Returns true if another collapse redex might have been introduced. | |
| 477 void tryCollapseIf(If node) { | |
| 478 // Repeatedly try to collapse nested ifs. | |
| 479 // The transformation is shrinking (destroys an if) so it remains linear. | |
| 480 // Here is an example where more than one iteration is required: | |
| 481 // | |
| 482 // if (E1) | |
| 483 // if (E2) break L2 else break L1 | |
| 484 // else | |
| 485 // break L1 | |
| 486 // | |
| 487 // L1.target ::= | |
| 488 // if (E3) S else break L2 | |
| 489 // | |
| 490 // After first collapse: | |
| 491 // | |
| 492 // if (E1 && E2) | |
| 493 // break L2 | |
| 494 // else | |
| 495 // {if (E3) S else break L2} (inlined from break L1) | |
| 496 // | |
| 497 // We can then do another collapse using the inlined nested if. | |
| 498 bool changed = true; | |
| 499 while (changed) { | |
| 500 changed = false; | |
| 501 if (tryCollapseIfAux(node, true, true)) { | |
| 502 changed = true; | |
| 503 } | |
| 504 if (tryCollapseIfAux(node, true, false)) { | |
| 505 changed = true; | |
| 506 } | |
| 507 if (tryCollapseIfAux(node, false, true)) { | |
| 508 changed = true; | |
| 509 } | |
| 510 if (tryCollapseIfAux(node, false, false)) { | |
| 511 changed = true; | |
| 512 } | |
| 513 } | |
| 514 } | |
| 515 | |
| 516 bool tryCollapseIfAux(If outerIf, bool branch1, bool branch2) { | |
| 517 // NOTE: We name variables here as if S is in the then-then position. | |
| 518 Statement outerThen = getBranch(outerIf, branch1); | |
| 519 Statement outerElse = getBranch(outerIf, !branch1); | |
| 520 if (outerThen is If && outerElse is Break) { | |
| 521 If innerIf = outerThen; | |
| 522 Statement innerThen = getBranch(innerIf, branch2); | |
| 523 Statement innerElse = getBranch(innerIf, !branch2); | |
| 524 if (innerElse is Break && innerElse.target == outerElse.target) { | |
| 525 // We always put S in the then branch of the result, and adjust the | |
| 526 // condition expression if S was actually found in the else branch(es). | |
| 527 outerIf.condition = new LogicalOperator.and( | |
| 528 makeCondition(outerIf.condition, branch1), | |
| 529 makeCondition(innerIf.condition, branch2)); | |
| 530 outerIf.thenStatement = innerThen; | |
| 531 --innerElse.target.useCount; | |
| 532 | |
| 533 // Try to inline the remaining break. Do not propagate assignments. | |
| 534 List<Assign> savedEnvironment = environment; | |
| 535 environment = <Assign>[]; | |
| 536 outerIf.elseStatement = visitStatement(outerElse); | |
| 537 assert(environment.isEmpty); | |
| 538 environment = savedEnvironment; | |
| 539 | |
| 540 return outerIf.elseStatement is If && innerThen is Break; | |
| 541 } | |
| 542 } | |
| 543 return false; | |
| 544 } | |
| 545 | |
| 546 Expression makeCondition(Expression e, bool polarity) { | |
| 547 return polarity ? e : new Not(e); | |
| 548 } | |
| 549 | |
| 550 Statement getBranch(If node, bool polarity) { | |
| 551 return polarity ? node.thenStatement : node.elseStatement; | |
| 552 } | |
| 553 } | |
| OLD | NEW |