Skip to content

Commit 25b99ee

Browse files
committed
[CanonicalizeOSSALifetime] Extend Onone lifetimes.
To improve the debugging experience of values whose lifetimes are canonicalized without compromising the semantics expressed in the source language, when canonicalizing OSSA lifetimes at Onone, lengthen lifetimes as much as possible without incurring copies that would be eliminated at O. rdar://99618502
1 parent 2c5ee0f commit 25b99ee

File tree

8 files changed

+628
-13
lines changed

8 files changed

+628
-13
lines changed

include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ class CanonicalizeOSSALifetime {
205205
/// liveness may be pruned during canonicalization.
206206
bool pruneDebugMode;
207207

208+
/// If true, lifetimes will not be shortened except when necessary to avoid
209+
/// copies.
210+
bool maximizeLifetime;
211+
208212
/// If true and we are processing a value of move_only type, emit a diagnostic
209213
/// when-ever we need to insert a copy_value.
210214
std::function<void(Operand *)> moveOnlyCopyValueNotification;
@@ -240,6 +244,9 @@ class CanonicalizeOSSALifetime {
240244
/// Visited set for general def-use traversal that prevents revisiting values.
241245
GraphNodeWorklist<SILValue, 8> defUseWorklist;
242246

247+
/// The blocks that were discovered by PrunedLiveness.
248+
SmallVector<SILBasicBlock *, 32> discoveredBlocks;
249+
243250
/// Pruned liveness for the extended live range including copies. For this
244251
/// purpose, only consuming instructions are considered "lifetime
245252
/// ending". end_borrows do not end a liverange that may include owned copies.
@@ -248,7 +255,7 @@ class CanonicalizeOSSALifetime {
248255
/// The destroys of the value. These are not uses, but need to be recorded so
249256
/// that we know when the last use in a consuming block is (without having to
250257
/// repeatedly do use-def walks from destroys).
251-
SmallPtrSet<SILInstruction *, 8> destroys;
258+
SmallPtrSetVector<SILInstruction *, 8> destroys;
252259

253260
/// Information about consuming instructions discovered in this canonical OSSA
254261
/// lifetime.
@@ -286,15 +293,17 @@ class CanonicalizeOSSALifetime {
286293
}
287294

288295
CanonicalizeOSSALifetime(
289-
bool pruneDebugMode, NonLocalAccessBlockAnalysis *accessBlockAnalysis,
290-
DominanceInfo *domTree, InstructionDeleter &deleter,
296+
bool pruneDebugMode, bool maximizeLifetime,
297+
NonLocalAccessBlockAnalysis *accessBlockAnalysis, DominanceInfo *domTree,
298+
InstructionDeleter &deleter,
291299
std::function<void(Operand *)> moveOnlyCopyValueNotification = nullptr,
292300
std::function<void(Operand *)> moveOnlyFinalConsumingUse = nullptr)
293-
: pruneDebugMode(pruneDebugMode),
301+
: pruneDebugMode(pruneDebugMode), maximizeLifetime(maximizeLifetime),
294302
moveOnlyCopyValueNotification(moveOnlyCopyValueNotification),
295303
moveOnlyFinalConsumingUse(moveOnlyFinalConsumingUse),
296304
accessBlockAnalysis(accessBlockAnalysis), domTree(domTree),
297-
deleter(deleter) {}
305+
deleter(deleter),
306+
liveness(maximizeLifetime ? &discoveredBlocks : nullptr) {}
298307

299308
SILValue getCurrentDef() const { return liveness.getDef(); }
300309

@@ -313,6 +322,7 @@ class CanonicalizeOSSALifetime {
313322
consumingBlocks.clear();
314323
debugValues.clear();
315324
liveness.clear();
325+
discoveredBlocks.clear();
316326
}
317327

318328
/// Top-Level API: rewrites copies and destroys within \p def's extended
@@ -345,6 +355,8 @@ class CanonicalizeOSSALifetime {
345355

346356
void findExtendedBoundary(PrunedLivenessBoundary &boundary);
347357

358+
void extendUnconsumedLiveness(PrunedLivenessBoundary &boundary);
359+
348360
void insertDestroysOnBoundary(PrunedLivenessBoundary &boundary);
349361

350362
void rewriteCopies();

lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,9 @@ struct MoveOnlyChecker {
746746
instToDelete->eraseFromParent();
747747
})),
748748
canonicalizer(
749-
false /*pruneDebugMode*/, accessBlockAnalysis, domTree, deleter,
749+
false /*pruneDebugMode*/,
750+
!fn->shouldOptimize() /*maximizeLifetime*/, accessBlockAnalysis,
751+
domTree, deleter,
750752
[&](Operand *use) { consumingUsesNeedingCopy.push_back(use); },
751753
[&](Operand *use) { finalConsumingUses.push_back(use); }) {}
752754

lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,9 @@ bool MoveOnlyChecker::check(NonLocalAccessBlockAnalysis *accessBlockAnalysis,
401401
};
402402

403403
CanonicalizeOSSALifetime canonicalizer(
404-
false /*pruneDebugMode*/, accessBlockAnalysis, domTree, deleter,
405-
foundConsumingUseNeedingCopy, foundConsumingUseNotNeedingCopy);
404+
false /*pruneDebugMode*/, !fn->shouldOptimize() /*maximizeLifetime*/,
405+
accessBlockAnalysis, domTree, deleter, foundConsumingUseNeedingCopy,
406+
foundConsumingUseNotNeedingCopy);
406407
auto moveIntroducers = llvm::makeArrayRef(moveIntroducersToProcess.begin(),
407408
moveIntroducersToProcess.end());
408409
SmallPtrSet<MarkMustCheckInst *, 4> valuesWithDiagnostics;

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,10 @@ void SILCombiner::canonicalizeOSSALifetimes(SILInstruction *currentInst) {
350350
InstructionDeleter deleter(std::move(canonicalizeCallbacks));
351351

352352
DominanceInfo *domTree = DA->get(&Builder.getFunction());
353-
CanonicalizeOSSALifetime canonicalizer(false /*prune debug*/, NLABA, domTree,
354-
deleter);
353+
CanonicalizeOSSALifetime canonicalizer(
354+
false /*prune debug*/,
355+
!parentTransform->getFunction()->shouldOptimize() /*maximizeLifetime*/,
356+
NLABA, domTree, deleter);
355357
CanonicalizeBorrowScope borrowCanonicalizer(deleter);
356358

357359
while (!defsToCanonicalize.empty()) {

lib/SILOptimizer/Transforms/CopyPropagation.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ static bool sinkOwnedForward(SILInstruction *ownedForward,
373373
namespace {
374374

375375
class CopyPropagation : public SILFunctionTransform {
376-
/// True if debug_value instructions should be pruned.
376+
/// If true, debug_value instructions should be pruned.
377377
bool pruneDebug;
378378
/// True if all values should be canonicalized.
379379
bool canonicalizeAll;
@@ -437,8 +437,9 @@ void CopyPropagation::run() {
437437

438438
// canonicalizer performs all modifications through deleter's callbacks, so we
439439
// don't need to explicitly check for changes.
440-
CanonicalizeOSSALifetime canonicalizer(pruneDebug, accessBlockAnalysis,
441-
domTree, deleter);
440+
CanonicalizeOSSALifetime canonicalizer(
441+
pruneDebug, /*maximizeLifetime=*/!getFunction()->shouldOptimize(),
442+
accessBlockAnalysis, domTree, deleter);
442443

443444
// NOTE: We assume that the function is in reverse post order so visiting the
444445
// blocks and pushing begin_borrows as we see them and then popping them

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
///
5353
/// 2. Liveness is extended out to original destroys to avoid spurious changes.
5454
///
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+
///
5558
//===----------------------------------------------------------------------===//
5659

5760
#define DEBUG_TYPE "copy-propagation"
@@ -64,6 +67,7 @@
6467
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
6568
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
6669
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
70+
#include "llvm/ADT/STLExtras.h"
6771
#include "llvm/ADT/Statistic.h"
6872

6973
using namespace swift;
@@ -542,6 +546,8 @@ class CanonicalizeOSSALifetimeBoundaryExtender {
542546

543547
void CanonicalizeOSSALifetime::findExtendedBoundary(
544548
PrunedLivenessBoundary &boundary) {
549+
assert(boundary.lastUsers.size() == 0 && boundary.boundaryEdges.size() == 0 &&
550+
boundary.deadDefs.size() == 0);
545551
PrunedLivenessBoundary originalBoundary;
546552
liveness.computeBoundary(originalBoundary, consumingBlocks.getArrayRef());
547553

@@ -611,6 +617,130 @@ void CanonicalizeOSSALifetime::insertDestroysOnBoundary(
611617
}
612618
}
613619

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+
614744
//===----------------------------------------------------------------------===//
615745
// MARK: Step 4. Rewrite copies and destroys
616746
//===----------------------------------------------------------------------===//
@@ -781,6 +911,11 @@ bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) {
781911
// Step 2: compute boundary
782912
PrunedLivenessBoundary boundary;
783913
findExtendedBoundary(boundary);
914+
if (maximizeLifetime) {
915+
extendUnconsumedLiveness(boundary);
916+
boundary.clear();
917+
findExtendedBoundary(boundary);
918+
}
784919
// Step 3: insert destroys and record consumes
785920
insertDestroysOnBoundary(boundary);
786921
// Step 4: rewrite copies and delete extra destroys

0 commit comments

Comments
 (0)