| 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 library dart2js.cps_ir.shrinking_reductions; | 5 library dart2js.cps_ir.shrinking_reductions; |
| 6 | 6 |
| 7 import 'cps_ir_nodes.dart'; | 7 import 'cps_ir_nodes.dart'; |
| 8 import 'optimizers.dart'; | 8 import 'optimizers.dart'; |
| 9 import 'cps_fragment.dart'; | 9 import 'cps_fragment.dart'; |
| 10 import '../constants/values.dart' as values; |
| 10 | 11 |
| 11 /** | 12 /** |
| 12 * [ShrinkingReducer] applies shrinking reductions to CPS terms as described | 13 * [ShrinkingReducer] applies shrinking reductions to CPS terms as described |
| 13 * in 'Compiling with Continuations, Continued' by Andrew Kennedy. | 14 * in 'Compiling with Continuations, Continued' by Andrew Kennedy. |
| 14 */ | 15 */ |
| 15 class ShrinkingReducer extends Pass { | 16 class ShrinkingReducer extends Pass { |
| 16 String get passName => 'Shrinking reductions'; | 17 String get passName => 'Shrinking reductions'; |
| 17 | 18 |
| 18 List<_ReductionTask> _worklist; | 19 List<_ReductionTask> _worklist; |
| 19 | 20 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 74 break; | 75 break; |
| 75 case _ReductionKind.BETA_CONT_LIN: | 76 case _ReductionKind.BETA_CONT_LIN: |
| 76 _reduceBetaContLin(task); | 77 _reduceBetaContLin(task); |
| 77 break; | 78 break; |
| 78 case _ReductionKind.ETA_CONT: | 79 case _ReductionKind.ETA_CONT: |
| 79 _reduceEtaCont(task); | 80 _reduceEtaCont(task); |
| 80 break; | 81 break; |
| 81 case _ReductionKind.DEAD_PARAMETER: | 82 case _ReductionKind.DEAD_PARAMETER: |
| 82 _reduceDeadParameter(task); | 83 _reduceDeadParameter(task); |
| 83 break; | 84 break; |
| 85 case _ReductionKind.BRANCH: |
| 86 _reduceBranch(task); |
| 87 break; |
| 84 default: | 88 default: |
| 85 assert(false); | 89 assert(false); |
| 86 } | 90 } |
| 87 } | 91 } |
| 88 | 92 |
| 89 /// Applies the dead-val reduction: | 93 /// Applies the dead-val reduction: |
| 90 /// letprim x = V in E -> E (x not free in E). | 94 /// letprim x = V in E -> E (x not free in E). |
| 91 void _reduceDeadVal(_ReductionTask task) { | 95 void _reduceDeadVal(_ReductionTask task) { |
| 92 assert(_isDeadVal(task.node)); | 96 assert(_isDeadVal(task.node)); |
| 93 | 97 |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 184 } | 188 } |
| 185 } | 189 } |
| 186 | 190 |
| 187 // Replace all occurrences with the wrapped continuation. | 191 // Replace all occurrences with the wrapped continuation. |
| 188 cont.replaceUsesWith(wrappedCont); | 192 cont.replaceUsesWith(wrappedCont); |
| 189 | 193 |
| 190 // Perform bookkeeping on removed body and scan for new redexes. | 194 // Perform bookkeeping on removed body and scan for new redexes. |
| 191 new _RemovalVisitor(_worklist).visit(cont); | 195 new _RemovalVisitor(_worklist).visit(cont); |
| 192 } | 196 } |
| 193 | 197 |
| 198 void _reduceBranch(_ReductionTask task) { |
| 199 Branch branch = task.node; |
| 200 // Task can be added as both a useless if and a constant folding. |
| 201 if (branch.parent == _DELETED) return; |
| 202 |
| 203 // Replace Branch with InvokeContinuation of one of the targets. When the |
| 204 // branch is deleted the other target becomes unreferenced and the chosen |
| 205 // target becomes available for eta-cont and further reductions. |
| 206 Continuation target; |
| 207 |
| 208 Primitive condition = branch.condition.definition; |
| 209 if (condition is Constant) { |
| 210 target = isTruthyConstant(condition.value, strict: branch.isStrictCheck) |
| 211 ? branch.trueContinuation.definition |
| 212 : branch.falseContinuation.definition; |
| 213 } else if (_isBranchTargetOfUselessIf(branch.trueContinuation.definition)) { |
| 214 target = branch.trueContinuation.definition; |
| 215 } else { |
| 216 return; |
| 217 } |
| 218 |
| 219 InvokeContinuation invoke = new InvokeContinuation( |
| 220 target, <Primitive>[] |
| 221 // TODO(sra): Add sourceInformation. |
| 222 /*, sourceInformation: branch.sourceInformation*/); |
| 223 branch.parent.body = invoke; |
| 224 invoke.parent = branch.parent; |
| 225 branch.parent = _DELETED; |
| 226 |
| 227 new _RemovalVisitor(_worklist).visit(branch); |
| 228 } |
| 229 |
| 194 void _reduceDeadParameter(_ReductionTask task) { | 230 void _reduceDeadParameter(_ReductionTask task) { |
| 195 // Continuation eta-reduction can destroy a dead parameter redex. For | 231 // Continuation eta-reduction can destroy a dead parameter redex. For |
| 196 // example, in the term: | 232 // example, in the term: |
| 197 // | 233 // |
| 198 // let cont k0(v0) = /* v0 is not used */ in | 234 // let cont k0(v0) = /* v0 is not used */ in |
| 199 // let cont k1(v1) = k0(v1) in | 235 // let cont k1(v1) = k0(v1) in |
| 200 // call foo () k1 | 236 // call foo () k1 |
| 201 // | 237 // |
| 202 // Continuation eta-reduction of k1 gives: | 238 // Continuation eta-reduction of k1 gives: |
| 203 // | 239 // |
| (...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 364 // constant time by using union-find data structure. | 400 // constant time by using union-find data structure. |
| 365 for (int i = 0; i < cont.parameters.length; i++) { | 401 for (int i = 0; i < cont.parameters.length; i++) { |
| 366 if (invoke.arguments[i].definition != cont.parameters[i]) { | 402 if (invoke.arguments[i].definition != cont.parameters[i]) { |
| 367 return false; | 403 return false; |
| 368 } | 404 } |
| 369 } | 405 } |
| 370 | 406 |
| 371 return true; | 407 return true; |
| 372 } | 408 } |
| 373 | 409 |
| 410 bool _isBranchTargetOfUselessIf(Continuation cont) { |
| 411 // A useless-if has an empty then and else branch, e.g. `if (cond);`. |
| 412 // |
| 413 // Detect T or F in |
| 414 // |
| 415 // let cont Join() = ... |
| 416 // in let cont T() = Join() |
| 417 // F() = Join() |
| 418 // in branch condition T F |
| 419 // |
| 420 if (!cont.hasExactlyOneUse) return false; |
| 421 if (cont.firstRef.parent is! Branch) return false; |
| 422 Branch branch = cont.firstRef.parent; |
| 423 Continuation trueCont = branch.trueContinuation.definition; |
| 424 Continuation falseCont = branch.falseContinuation.definition; |
| 425 // Are both continuations the same InvokeContinuation on a join? |
| 426 if (trueCont.body is! InvokeContinuation) return false; |
| 427 if (falseCont.body is! InvokeContinuation) return false; |
| 428 InvokeContinuation trueInvoke = trueCont.body; |
| 429 InvokeContinuation falseInvoke = falseCont.body; |
| 430 if (trueInvoke.continuation.definition != |
| 431 falseInvoke.continuation.definition) { |
| 432 return false; |
| 433 } |
| 434 assert(trueInvoke.arguments.length == falseInvoke.arguments.length); |
| 435 // Matching zero arguments should be adequate, since isomorphic true and false |
| 436 // invocations should result in redundant phis which are removed elsewhere. |
| 437 if (trueInvoke.arguments.isNotEmpty) return false; |
| 438 return true; |
| 439 } |
| 440 |
| 374 bool _isDeadParameter(Parameter parameter) { | 441 bool _isDeadParameter(Parameter parameter) { |
| 375 // We cannot remove function parameters as an intraprocedural optimization. | 442 // We cannot remove function parameters as an intraprocedural optimization. |
| 376 if (parameter.parent is! Continuation || parameter.hasAtLeastOneUse) { | 443 if (parameter.parent is! Continuation || parameter.hasAtLeastOneUse) { |
| 377 return false; | 444 return false; |
| 378 } | 445 } |
| 379 | 446 |
| 380 // We cannot remove exception handler parameters, they have a fixed arity | 447 // We cannot remove exception handler parameters, they have a fixed arity |
| 381 // of two. | 448 // of two. |
| 382 if (parameter.parent.parent is LetHandler) { | 449 if (parameter.parent.parent is LetHandler) { |
| 383 return false; | 450 return false; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 404 final List<_ReductionTask> worklist; | 471 final List<_ReductionTask> worklist; |
| 405 | 472 |
| 406 _RedexVisitor(this.worklist); | 473 _RedexVisitor(this.worklist); |
| 407 | 474 |
| 408 void processLetPrim(LetPrim node) { | 475 void processLetPrim(LetPrim node) { |
| 409 if (_isDeadVal(node)) { | 476 if (_isDeadVal(node)) { |
| 410 worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, node)); | 477 worklist.add(new _ReductionTask(_ReductionKind.DEAD_VAL, node)); |
| 411 } | 478 } |
| 412 } | 479 } |
| 413 | 480 |
| 481 void processBranch(Branch node) { |
| 482 if (node.condition.definition is Constant) { |
| 483 worklist.add(new _ReductionTask(_ReductionKind.BRANCH, node)); |
| 484 } |
| 485 } |
| 486 |
| 414 void processContinuation(Continuation node) { | 487 void processContinuation(Continuation node) { |
| 415 // While it would be nice to remove exception handlers that are provably | 488 // While it would be nice to remove exception handlers that are provably |
| 416 // unnecessary (e.g., the body cannot throw), that takes more sophisticated | 489 // unnecessary (e.g., the body cannot throw), that takes more sophisticated |
| 417 // analysis than we do in this pass. | 490 // analysis than we do in this pass. |
| 418 if (node.parent is LetHandler) return; | 491 if (node.parent is LetHandler) return; |
| 419 | 492 |
| 420 // Continuation beta- and eta-redexes can overlap, namely when an eta-redex | 493 // Continuation beta- and eta-redexes can overlap, namely when an eta-redex |
| 421 // is invoked exactly once. We prioritize continuation beta-redexes over | 494 // is invoked exactly once. We prioritize continuation beta-redexes over |
| 422 // eta-redexes because some reductions (e.g., dead parameter elimination) | 495 // eta-redexes because some reductions (e.g., dead parameter elimination) |
| 423 // can destroy a continuation eta-redex. If we prioritized eta- over | 496 // can destroy a continuation eta-redex. If we prioritized eta- over |
| 424 // beta-redexes, this would implicitly "create" the corresponding beta-redex | 497 // beta-redexes, this would implicitly "create" the corresponding beta-redex |
| 425 // (in the sense that it would still apply) and the algorithm would not | 498 // (in the sense that it would still apply) and the algorithm would not |
| 426 // detect it. | 499 // detect it. |
| 427 if (_isDeadCont(node)) { | 500 if (_isDeadCont(node)) { |
| 428 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, node)); | 501 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, node)); |
| 429 } else if (_isBetaContLin(node)){ | 502 } else if (_isBetaContLin(node)){ |
| 430 worklist.add(new _ReductionTask(_ReductionKind.BETA_CONT_LIN, node)); | 503 worklist.add(new _ReductionTask(_ReductionKind.BETA_CONT_LIN, node)); |
| 431 } else if (_isEtaCont(node)) { | 504 } else if (_isEtaCont(node)) { |
| 432 worklist.add(new _ReductionTask(_ReductionKind.ETA_CONT, node)); | 505 worklist.add(new _ReductionTask(_ReductionKind.ETA_CONT, node)); |
| 506 } else if (_isBranchTargetOfUselessIf(node)) { |
| 507 worklist.add(new _ReductionTask(_ReductionKind.BRANCH, |
| 508 node.firstRef.parent)); |
| 433 } | 509 } |
| 434 } | 510 } |
| 435 | 511 |
| 436 void processParameter(Parameter node) { | 512 void processParameter(Parameter node) { |
| 437 if (_isDeadParameter(node)) { | 513 if (_isDeadParameter(node)) { |
| 438 worklist.add(new _ReductionTask(_ReductionKind.DEAD_PARAMETER, node)); | 514 worklist.add(new _ReductionTask(_ReductionKind.DEAD_PARAMETER, node)); |
| 439 } | 515 } |
| 440 } | 516 } |
| 441 } | 517 } |
| 442 | 518 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 479 if (cont.isRecursive && cont.hasAtMostOneUse) { | 555 if (cont.isRecursive && cont.hasAtMostOneUse) { |
| 480 // Convert recursive to nonrecursive continuations. If the | 556 // Convert recursive to nonrecursive continuations. If the |
| 481 // continuation is still in use, it is either dead and will be | 557 // continuation is still in use, it is either dead and will be |
| 482 // removed, or it is called nonrecursively outside its body. | 558 // removed, or it is called nonrecursively outside its body. |
| 483 cont.isRecursive = false; | 559 cont.isRecursive = false; |
| 484 } | 560 } |
| 485 if (_isDeadCont(cont)) { | 561 if (_isDeadCont(cont)) { |
| 486 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, cont)); | 562 worklist.add(new _ReductionTask(_ReductionKind.DEAD_CONT, cont)); |
| 487 } else if (_isBetaContLin(cont)) { | 563 } else if (_isBetaContLin(cont)) { |
| 488 worklist.add(new _ReductionTask(_ReductionKind.BETA_CONT_LIN, cont)); | 564 worklist.add(new _ReductionTask(_ReductionKind.BETA_CONT_LIN, cont)); |
| 565 } else if (_isBranchTargetOfUselessIf(cont)) { |
| 566 worklist.add( |
| 567 new _ReductionTask(_ReductionKind.BRANCH, cont.firstRef.parent)); |
| 489 } | 568 } |
| 490 } | 569 } |
| 491 } | 570 } |
| 492 } | 571 } |
| 493 } | 572 } |
| 494 | 573 |
| 495 | 574 |
| 496 | 575 |
| 497 class _ReductionKind { | 576 class _ReductionKind { |
| 498 final String name; | 577 final String name; |
| 499 final int hashCode; | 578 final int hashCode; |
| 500 | 579 |
| 501 const _ReductionKind(this.name, this.hashCode); | 580 const _ReductionKind(this.name, this.hashCode); |
| 502 | 581 |
| 503 static const _ReductionKind DEAD_VAL = const _ReductionKind('dead-val', 0); | 582 static const _ReductionKind DEAD_VAL = const _ReductionKind('dead-val', 0); |
| 504 static const _ReductionKind DEAD_CONT = const _ReductionKind('dead-cont', 1); | 583 static const _ReductionKind DEAD_CONT = const _ReductionKind('dead-cont', 1); |
| 505 static const _ReductionKind BETA_CONT_LIN = | 584 static const _ReductionKind BETA_CONT_LIN = |
| 506 const _ReductionKind('beta-cont-lin', 2); | 585 const _ReductionKind('beta-cont-lin', 2); |
| 507 static const _ReductionKind ETA_CONT = const _ReductionKind('eta-cont', 3); | 586 static const _ReductionKind ETA_CONT = const _ReductionKind('eta-cont', 3); |
| 508 static const _ReductionKind DEAD_PARAMETER = | 587 static const _ReductionKind DEAD_PARAMETER = |
| 509 const _ReductionKind('dead-parameter', 4); | 588 const _ReductionKind('dead-parameter', 4); |
| 589 static const _ReductionKind BRANCH = const _ReductionKind('branch', 5); |
| 510 | 590 |
| 511 String toString() => name; | 591 String toString() => name; |
| 512 } | 592 } |
| 513 | 593 |
| 514 /// Represents a reduction task on the worklist. Implements both hashCode and | 594 /// Represents a reduction task on the worklist. Implements both hashCode and |
| 515 /// operator== since instantiations are used as Set elements. | 595 /// operator== since instantiations are used as Set elements. |
| 516 class _ReductionTask { | 596 class _ReductionTask { |
| 517 final _ReductionKind kind; | 597 final _ReductionKind kind; |
| 518 final Node node; | 598 final Node node; |
| 519 | 599 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 532 | 612 |
| 533 String toString() => "$kind: $node"; | 613 String toString() => "$kind: $node"; |
| 534 } | 614 } |
| 535 | 615 |
| 536 /// A dummy class used solely to mark nodes as deleted once they are removed | 616 /// A dummy class used solely to mark nodes as deleted once they are removed |
| 537 /// from a term. | 617 /// from a term. |
| 538 class _DeletedNode extends Node { | 618 class _DeletedNode extends Node { |
| 539 accept(_) {} | 619 accept(_) {} |
| 540 setParentPointers() {} | 620 setParentPointers() {} |
| 541 } | 621 } |
| OLD | NEW |