|
52 | 52 | ///
|
53 | 53 | /// 2. Liveness is extended out to original destroys to avoid spurious changes.
|
54 | 54 | ///
|
| 55 | +/// 3. In the Onone mode, liveness is preserved to its previous extent whenever |
| 56 | +/// doing so doesn't incur extra copies compared to what is done in the O mode. |
| 57 | +/// |
55 | 58 | //===----------------------------------------------------------------------===//
|
56 | 59 |
|
57 | 60 | #define DEBUG_TYPE "copy-propagation"
|
|
64 | 67 | #include "swift/SILOptimizer/Utils/DebugOptUtils.h"
|
65 | 68 | #include "swift/SILOptimizer/Utils/InstructionDeleter.h"
|
66 | 69 | #include "swift/SILOptimizer/Utils/ValueLifetime.h"
|
| 70 | +#include "llvm/ADT/STLExtras.h" |
67 | 71 | #include "llvm/ADT/Statistic.h"
|
68 | 72 |
|
69 | 73 | using namespace swift;
|
@@ -427,13 +431,15 @@ class CanonicalizeOSSALifetimeBoundaryExtender {
|
427 | 431 | SSAPrunedLiveness &liveness;
|
428 | 432 | PrunedLivenessBoundary &originalBoundary;
|
429 | 433 | SILValue currentDef;
|
| 434 | + BasicBlockSet edgeDestinationsExtendedFrom; |
430 | 435 |
|
431 | 436 | public:
|
432 | 437 | CanonicalizeOSSALifetimeBoundaryExtender(
|
433 | 438 | SSAPrunedLiveness &liveness, PrunedLivenessBoundary &originalBoundary,
|
434 | 439 | SILValue currentDef)
|
435 | 440 | : liveness(liveness), originalBoundary(originalBoundary),
|
436 |
| - currentDef(currentDef){}; |
| 441 | + currentDef(currentDef), |
| 442 | + edgeDestinationsExtendedFrom(currentDef->getFunction()){}; |
437 | 443 |
|
438 | 444 | /// Compute the extended boundary by walking out from the original boundary
|
439 | 445 | /// (from PrunedLiveness::computeBoundary) down to any destroys that appear
|
@@ -495,6 +501,10 @@ class CanonicalizeOSSALifetimeBoundaryExtender {
|
495 | 501 | /// which aren't separated from the original boundary by "interesting" users.
|
496 | 502 | void extendBoundaryFromCFGEdge(SILBasicBlock *destination,
|
497 | 503 | PrunedLivenessBoundary &boundary) {
|
| 504 | + // If multiple predecessors had live terminators, liveness would be extended |
| 505 | + // from each. Do that only once. |
| 506 | + if (!edgeDestinationsExtendedFrom.insert(destination)) |
| 507 | + return; |
498 | 508 | if (auto *dvi = findDestroyOnCFGEdge(destination, currentDef)) {
|
499 | 509 | boundary.lastUsers.push_back(dvi);
|
500 | 510 | } else {
|
@@ -542,6 +552,8 @@ class CanonicalizeOSSALifetimeBoundaryExtender {
|
542 | 552 |
|
543 | 553 | void CanonicalizeOSSALifetime::findExtendedBoundary(
|
544 | 554 | PrunedLivenessBoundary &boundary) {
|
| 555 | + assert(boundary.lastUsers.size() == 0 && boundary.boundaryEdges.size() == 0 && |
| 556 | + boundary.deadDefs.size() == 0); |
545 | 557 | PrunedLivenessBoundary originalBoundary;
|
546 | 558 | liveness.computeBoundary(originalBoundary, consumingBlocks.getArrayRef());
|
547 | 559 |
|
@@ -611,6 +623,129 @@ void CanonicalizeOSSALifetime::insertDestroysOnBoundary(
|
611 | 623 | }
|
612 | 624 | }
|
613 | 625 |
|
| 626 | +/// At -Onone, there are some conflicting goals: |
| 627 | +/// On the one hand: good debugging experience. |
| 628 | +/// (1) do not shorten value's lifetime |
| 629 | +/// On the other: demonstrate semantics. |
| 630 | +/// (2) consume value at same places it will be consumed at -O |
| 631 | +/// (3) ensure there are no more copies than there would be at -O |
| 632 | +/// |
| 633 | +/// (2) and (3) are equivalent--extra (compared to -O) copies arise from failing |
| 634 | +/// to end lifetimes at consuming uses (which then need their own copies). |
| 635 | +/// |
| 636 | +/// We achieve (2) and (3) always. We achieve (1) where possible. |
| 637 | +/// |
| 638 | +/// Conceptually, the strategy is the following: |
| 639 | +/// - Collect the blocks in which the value was live before canonicalization. |
| 640 | +/// These are the "original" live blocks (originalLiveBlocks). |
| 641 | +/// [Color these blocks green.] |
| 642 | +/// - From within that collection, collect the blocks which contain a _final_ |
| 643 | +/// consuming, non-destroy use, and their successors. |
| 644 | +/// These are the "consumed" blocks (consumedAtExitBlocks). |
| 645 | +/// [Color these blocks red.] |
| 646 | +/// - Extend liveness down to the boundary between originalLiveBlocks and |
| 647 | +/// consumedAtExitBlocks blocks. |
| 648 | +/// [Extend liveness down to the boundary between green blocks and red.] |
| 649 | +/// - In particular, in regions of originalLiveBlocks which have no boundary |
| 650 | +/// with consumedAtExitBlocks, liveness should be extended to its original |
| 651 | +/// extent. |
| 652 | +/// [Extend liveness down to the boundary between green blocks and uncolored.] |
| 653 | +void CanonicalizeOSSALifetime::extendUnconsumedLiveness( |
| 654 | + PrunedLivenessBoundary &boundary) { |
| 655 | + auto currentDef = getCurrentDef(); |
| 656 | + |
| 657 | + // First, collect the blocks that were _originally_ live. We can't use |
| 658 | + // liveness here because it doesn't include blocks that occur before a |
| 659 | + // destroy_value. |
| 660 | + BasicBlockSet originalLiveBlocks(currentDef->getFunction()); |
| 661 | + { |
| 662 | + // Some of the work here was already done by computeCanonicalLiveness. |
| 663 | + // Specifically, it already discovered all blocks containing (transitive) |
| 664 | + // uses and blocks that appear between them and the def. |
| 665 | + // |
| 666 | + // Seed the set with what it already discovered. |
| 667 | + for (auto *discoveredBlock : liveness.getDiscoveredBlocks()) |
| 668 | + originalLiveBlocks.insert(discoveredBlock); |
| 669 | + |
| 670 | + // Start the walk from the consuming blocks (which includes destroys as well |
| 671 | + // as the other consuming uses). |
| 672 | + BasicBlockWorklist worklist(currentDef->getFunction()); |
| 673 | + for (auto *consumingBlock : consumingBlocks) { |
| 674 | + worklist.push(consumingBlock); |
| 675 | + } |
| 676 | + |
| 677 | + // Walk backwards from consuming blocks. |
| 678 | + while (auto *block = worklist.pop()) { |
| 679 | + originalLiveBlocks.insert(block); |
| 680 | + for (auto *predecessor : block->getPredecessorBlocks()) { |
| 681 | + // If the block was discovered by liveness, we already added it to the |
| 682 | + // set. |
| 683 | + if (originalLiveBlocks.contains(predecessor)) |
| 684 | + continue; |
| 685 | + worklist.pushIfNotVisited(predecessor); |
| 686 | + } |
| 687 | + } |
| 688 | + } |
| 689 | + |
| 690 | + // Second, collect the blocks which occur after a _final_ consuming use. |
| 691 | + BasicBlockSet consumedAtExitBlocks(currentDef->getFunction()); |
| 692 | + StackList<SILBasicBlock *> consumedAtEntryBlocks(currentDef->getFunction()); |
| 693 | + { |
| 694 | + // Start the forward walk from blocks which contain _final_ non-destroy |
| 695 | + // consumes. These are just the instructions on the boundary which aren't |
| 696 | + // destroys. |
| 697 | + BasicBlockWorklist worklist(currentDef->getFunction()); |
| 698 | + for (auto *instruction : boundary.lastUsers) { |
| 699 | + if (dynCastToDestroyOf(instruction, getCurrentDef())) |
| 700 | + continue; |
| 701 | + if (liveness.isInterestingUser(instruction) != |
| 702 | + PrunedLiveness::IsInterestingUser::LifetimeEndingUse) |
| 703 | + continue; |
| 704 | + worklist.push(instruction->getParent()); |
| 705 | + } |
| 706 | + while (auto *block = worklist.pop()) { |
| 707 | + consumedAtExitBlocks.insert(block); |
| 708 | + for (auto *successor : block->getSuccessorBlocks()) { |
| 709 | + if (!originalLiveBlocks.contains(successor)) |
| 710 | + continue; |
| 711 | + worklist.pushIfNotVisited(successor); |
| 712 | + consumedAtEntryBlocks.push_back(successor); |
| 713 | + } |
| 714 | + } |
| 715 | + } |
| 716 | + |
| 717 | + // Third, find the blocks on the boundary between the originally-live blocks |
| 718 | + // and the originally-live-but-consumed blocks. Extend liveness "to the end" |
| 719 | + // of these blocks. |
| 720 | + for (auto *block : consumedAtEntryBlocks) { |
| 721 | + for (auto *predecessor : block->getPredecessorBlocks()) { |
| 722 | + if (consumedAtExitBlocks.contains(predecessor)) |
| 723 | + continue; |
| 724 | + // Add "the instruction(s) before the terminator" of the predecessor to |
| 725 | + // liveness. |
| 726 | + if (auto *inst = predecessor->getTerminator()->getPreviousInstruction()) { |
| 727 | + liveness.updateForUse(inst, /*lifetimeEnding*/ false); |
| 728 | + } else { |
| 729 | + for (auto *grandPredecessor : predecessor->getPredecessorBlocks()) { |
| 730 | + liveness.updateForUse(grandPredecessor->getTerminator(), |
| 731 | + /*lifetimeEnding*/ false); |
| 732 | + } |
| 733 | + } |
| 734 | + } |
| 735 | + } |
| 736 | + |
| 737 | + // Finally, preserve the destroys which weren't in the consumed region in |
| 738 | + // place: hoisting such destroys would not avoid copies. |
| 739 | + for (auto *destroy : destroys) { |
| 740 | + auto *block = destroy->getParent(); |
| 741 | + // If the destroy is in a consumed block or a final consuming block, |
| 742 | + // hoisting it would avoid a copy. |
| 743 | + if (consumedAtExitBlocks.contains(block)) |
| 744 | + continue; |
| 745 | + liveness.updateForUse(destroy, /*lifetimeEnding*/ true); |
| 746 | + } |
| 747 | +} |
| 748 | + |
614 | 749 | //===----------------------------------------------------------------------===//
|
615 | 750 | // MARK: Step 4. Rewrite copies and destroys
|
616 | 751 | //===----------------------------------------------------------------------===//
|
@@ -781,6 +916,11 @@ bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) {
|
781 | 916 | // Step 2: compute boundary
|
782 | 917 | PrunedLivenessBoundary boundary;
|
783 | 918 | findExtendedBoundary(boundary);
|
| 919 | + if (maximizeLifetime) { |
| 920 | + extendUnconsumedLiveness(boundary); |
| 921 | + boundary.clear(); |
| 922 | + findExtendedBoundary(boundary); |
| 923 | + } |
784 | 924 | // Step 3: insert destroys and record consumes
|
785 | 925 | insertDestroysOnBoundary(boundary);
|
786 | 926 | // Step 4: rewrite copies and delete extra destroys
|
|
0 commit comments