|
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;
|
@@ -542,6 +546,8 @@ class CanonicalizeOSSALifetimeBoundaryExtender {
|
542 | 546 |
|
543 | 547 | void CanonicalizeOSSALifetime::findExtendedBoundary(
|
544 | 548 | PrunedLivenessBoundary &boundary) {
|
| 549 | + assert(boundary.lastUsers.size() == 0 && boundary.boundaryEdges.size() == 0 && |
| 550 | + boundary.deadDefs.size() == 0); |
545 | 551 | PrunedLivenessBoundary originalBoundary;
|
546 | 552 | liveness.computeBoundary(originalBoundary, consumingBlocks.getArrayRef());
|
547 | 553 |
|
@@ -611,6 +617,130 @@ void CanonicalizeOSSALifetime::insertDestroysOnBoundary(
|
611 | 617 | }
|
612 | 618 | }
|
613 | 619 |
|
| 620 | +/// At -Onone, there are some conflicting goals: |
| 621 | +/// On the one hand: good debugging experience. |
| 622 | +/// (1) do not shorten value's lifetime |
| 623 | +/// On the other: demonstrate semantics. |
| 624 | +/// (2) consume value at same places it will be consumed at -O |
| 625 | +/// (3) ensure there are no more copies than there would be at -O |
| 626 | +/// |
| 627 | +/// (2) and (3) are equivalent--extra (compared to -O) copies arise from failing |
| 628 | +/// to end lifetimes at consuming uses (which then need their own copies). |
| 629 | +/// |
| 630 | +/// We achieve (2) and (3) always. We achieve (1) where possible. |
| 631 | +/// |
| 632 | +/// Conceptually, the strategy is the following: |
| 633 | +/// - Collect the blocks in which the value was live before canonicalization. |
| 634 | +/// These are the "original" live blocks (originalLiveBlocks). |
| 635 | +/// [Color these blocks green.] |
| 636 | +/// - From within that collection, collect the blocks which contain a _final_ |
| 637 | +/// consuming, non-destroy use, and their successors. |
| 638 | +/// These are the "consumed" blocks (consumedAtExitBlocks). |
| 639 | +/// [Color these blocks red.] |
| 640 | +/// - Extend liveness down to the boundary between originalLiveBlocks and |
| 641 | +/// consumedAtExitBlocks blocks. |
| 642 | +/// [Extend liveness down to the boundary between green blocks and red.] |
| 643 | +/// - In particular, in regions of originalLiveBlocks which have no boundary |
| 644 | +/// with consumedAtExitBlocks, liveness should be extended to its original |
| 645 | +/// extent. |
| 646 | +/// [Extend liveness down to the boundary between green blocks and uncolored.] |
| 647 | +void CanonicalizeOSSALifetime::extendUnconsumedLiveness( |
| 648 | + PrunedLivenessBoundary &boundary) { |
| 649 | + auto currentDef = getCurrentDef(); |
| 650 | + |
| 651 | + // First, collect the blocks that were _originally_ live. We can't use |
| 652 | + // liveness here because it doesn't include blocks that occur before a |
| 653 | + // destroy_value. |
| 654 | + BasicBlockSet originalLiveBlocks(currentDef->getFunction()); |
| 655 | + { |
| 656 | + // Some of the work here was already done by computeCanonicalLiveness. |
| 657 | + // Specifically, it already discovered all blocks containing (transitive) |
| 658 | + // uses and blocks that appear between them and the def. |
| 659 | + // |
| 660 | + // Seed the set with what it already discovered. |
| 661 | + for (auto *discoveredBlock : liveness.getDiscoveredBlocks()) |
| 662 | + originalLiveBlocks.insert(discoveredBlock); |
| 663 | + |
| 664 | + // Start the walk from the consuming blocks (which includes destroys as well |
| 665 | + // as the other consuming uses). |
| 666 | + BasicBlockWorklist worklist(currentDef->getFunction()); |
| 667 | + for (auto *consumingBlock : consumingBlocks) { |
| 668 | + worklist.push(consumingBlock); |
| 669 | + } |
| 670 | + |
| 671 | + // Walk backwards from consuming blocks. |
| 672 | + while (auto *block = worklist.pop()) { |
| 673 | + originalLiveBlocks.insert(block); |
| 674 | + for (auto *predecessor : block->getPredecessorBlocks()) { |
| 675 | + // If the block was discovered by liveness, we already added it to the |
| 676 | + // set. |
| 677 | + if (originalLiveBlocks.contains(predecessor)) |
| 678 | + continue; |
| 679 | + worklist.pushIfNotVisited(predecessor); |
| 680 | + } |
| 681 | + } |
| 682 | + } |
| 683 | + |
| 684 | + // Second, collect the blocks which occur after a _final_ consuming use. |
| 685 | + BasicBlockSet consumedAtExitBlocks(currentDef->getFunction()); |
| 686 | + StackList<SILBasicBlock *> consumedAtEntryBlocks(currentDef->getFunction()); |
| 687 | + { |
| 688 | + // Start the forward walk from blocks which contain _final_ non-destroy |
| 689 | + // consumes. These are just the instructions on the boundary which aren't |
| 690 | + // destroys. |
| 691 | + BasicBlockWorklist worklist(currentDef->getFunction()); |
| 692 | + for (auto *instruction : boundary.lastUsers) { |
| 693 | + if (dynCastToDestroyOf(instruction, getCurrentDef())) |
| 694 | + continue; |
| 695 | + if (liveness.isInterestingUser(instruction) != |
| 696 | + PrunedLiveness::IsInterestingUser::LifetimeEndingUse) |
| 697 | + continue; |
| 698 | + worklist.push(instruction->getParent()); |
| 699 | + } |
| 700 | + while (auto *block = worklist.pop()) { |
| 701 | + consumedAtExitBlocks.insert(block); |
| 702 | + for (auto *successor : block->getSuccessorBlocks()) { |
| 703 | + if (!originalLiveBlocks.contains(successor)) |
| 704 | + continue; |
| 705 | + worklist.pushIfNotVisited(successor); |
| 706 | + consumedAtEntryBlocks.push_back(successor); |
| 707 | + } |
| 708 | + } |
| 709 | + } |
| 710 | + |
| 711 | + // Third, find the blocks on the boundary between the originally-live blocks |
| 712 | + // and the originally-live-but-consumed blocks. These are new boundary |
| 713 | + // blocks. |
| 714 | + for (auto *block : consumedAtEntryBlocks) { |
| 715 | + for (auto *predecessor : block->getPredecessorBlocks()) { |
| 716 | + if (!consumedAtExitBlocks.contains(predecessor)) { |
| 717 | + // Add "the instruction(s) before the terminator" of the predecessor to |
| 718 | + // liveness. |
| 719 | + if (auto *inst = |
| 720 | + predecessor->getTerminator()->getPreviousInstruction()) { |
| 721 | + liveness.updateForUse(inst, /*lifetimeEnding*/ false); |
| 722 | + } else { |
| 723 | + for (auto *grandPredecessor : predecessor->getPredecessorBlocks()) { |
| 724 | + liveness.updateForUse(grandPredecessor->getTerminator(), |
| 725 | + /*lifetimeEnding*/ false); |
| 726 | + } |
| 727 | + } |
| 728 | + } |
| 729 | + } |
| 730 | + } |
| 731 | + |
| 732 | + // Finally, preserve the destroys which weren't in the consumed region in |
| 733 | + // place: hoisting such destroys would not avoid copies. |
| 734 | + for (auto *destroy : destroys) { |
| 735 | + auto *block = destroy->getParent(); |
| 736 | + // If the destroy is in a consumed block or a final consuming block, |
| 737 | + // hoisting it would avoid a copy. |
| 738 | + if (consumedAtExitBlocks.contains(block)) |
| 739 | + continue; |
| 740 | + liveness.updateForUse(destroy, /*lifetimeEnding*/ true); |
| 741 | + } |
| 742 | +} |
| 743 | + |
614 | 744 | //===----------------------------------------------------------------------===//
|
615 | 745 | // MARK: Step 4. Rewrite copies and destroys
|
616 | 746 | //===----------------------------------------------------------------------===//
|
@@ -781,6 +911,11 @@ bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) {
|
781 | 911 | // Step 2: compute boundary
|
782 | 912 | PrunedLivenessBoundary boundary;
|
783 | 913 | findExtendedBoundary(boundary);
|
| 914 | + if (maximizeLifetime) { |
| 915 | + extendUnconsumedLiveness(boundary); |
| 916 | + boundary.clear(); |
| 917 | + findExtendedBoundary(boundary); |
| 918 | + } |
784 | 919 | // Step 3: insert destroys and record consumes
|
785 | 920 | insertDestroysOnBoundary(boundary);
|
786 | 921 | // Step 4: rewrite copies and delete extra destroys
|
|
0 commit comments