Skip to content

Commit 03253db

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 0289a6b commit 03253db

File tree

8 files changed

+895
-22
lines changed

8 files changed

+895
-22
lines changed

include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ class CanonicalizeOSSALifetime final {
209209
/// liveness may be pruned during canonicalization.
210210
bool pruneDebugMode;
211211

212+
/// If true, lifetimes will not be shortened except when necessary to avoid
213+
/// copies.
214+
bool maximizeLifetime;
215+
212216
/// If true and we are processing a value of move_only type, emit a diagnostic
213217
/// when-ever we need to insert a copy_value.
214218
std::function<void(Operand *)> moveOnlyCopyValueNotification;
@@ -244,6 +248,9 @@ class CanonicalizeOSSALifetime final {
244248
/// Visited set for general def-use traversal that prevents revisiting values.
245249
GraphNodeWorklist<SILValue, 8> defUseWorklist;
246250

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

257264
/// Information about consuming instructions discovered in this canonical OSSA
258265
/// lifetime.
@@ -290,15 +297,17 @@ class CanonicalizeOSSALifetime final {
290297
}
291298

292299
CanonicalizeOSSALifetime(
293-
bool pruneDebugMode, NonLocalAccessBlockAnalysis *accessBlockAnalysis,
294-
DominanceInfo *domTree, InstructionDeleter &deleter,
300+
bool pruneDebugMode, bool maximizeLifetime,
301+
NonLocalAccessBlockAnalysis *accessBlockAnalysis, DominanceInfo *domTree,
302+
InstructionDeleter &deleter,
295303
std::function<void(Operand *)> moveOnlyCopyValueNotification = nullptr,
296304
std::function<void(Operand *)> moveOnlyFinalConsumingUse = nullptr)
297-
: pruneDebugMode(pruneDebugMode),
305+
: pruneDebugMode(pruneDebugMode), maximizeLifetime(maximizeLifetime),
298306
moveOnlyCopyValueNotification(moveOnlyCopyValueNotification),
299307
moveOnlyFinalConsumingUse(moveOnlyFinalConsumingUse),
300308
accessBlockAnalysis(accessBlockAnalysis), domTree(domTree),
301-
deleter(deleter) {}
309+
deleter(deleter),
310+
liveness(maximizeLifetime ? &discoveredBlocks : nullptr) {}
302311

303312
SILValue getCurrentDef() const { return liveness.getDef(); }
304313

@@ -317,6 +326,7 @@ class CanonicalizeOSSALifetime final {
317326
consumingBlocks.clear();
318327
debugValues.clear();
319328
liveness.clear();
329+
discoveredBlocks.clear();
320330
}
321331

322332
/// Top-Level API: rewrites copies and destroys within \p def's extended
@@ -352,6 +362,8 @@ class CanonicalizeOSSALifetime final {
352362
void findExtendedBoundary(PrunedLivenessBoundary const &originalBoundary,
353363
PrunedLivenessBoundary &boundary);
354364

365+
void extendUnconsumedLiveness(PrunedLivenessBoundary const &boundary);
366+
355367
void insertDestroysOnBoundary(PrunedLivenessBoundary const &boundary);
356368

357369
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() /*maximize lifetime*/,
356+
NLABA, domTree, deleter);
355357
CanonicalizeBorrowScope borrowCanonicalizer(deleter);
356358

357359
while (!defsToCanonicalize.empty()) {

lib/SILOptimizer/Transforms/CopyPropagation.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 155 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
/// 2. Find the "original" boundary of liveness using
2424
/// PrunedLiveness::computeBoundary.
2525
///
26-
/// 3. Find the "extended" boundary of liveness by walking out from the boundary
26+
/// 3. (Optional) At Onone, extend liveness up to original extent when possible
27+
/// without incurring extra copies.
28+
///
29+
/// 4. Find the "extended" boundary of liveness by walking out from the boundary
2730
/// computed by PrunedLiveness out to destroys which aren't separated from
2831
/// the original destory by "interesting" instructions.
2932
///
30-
/// 4. Initializes `consumes` and inserts new destroy_value instructions.
33+
/// 5. Initializes `consumes` and inserts new destroy_value instructions.
3134
///
32-
/// 5. Rewrite `def`s original copies and destroys, inserting new copies where
35+
/// 6. Rewrite `def`s original copies and destroys, inserting new copies where
3336
/// needed. Deletes original copies and destroys and inserts new copies.
3437
///
3538
/// See CanonicalizeOSSALifetime.h for examples.
@@ -55,6 +58,9 @@
5558
///
5659
/// 2. Liveness is extended out to original destroys to avoid spurious changes.
5760
///
61+
/// 3. In the Onone mode, liveness is preserved to its previous extent whenever
62+
/// doing so doesn't incur extra copies compared to what is done in the O mode.
63+
///
5864
//===----------------------------------------------------------------------===//
5965

6066
#define DEBUG_TYPE "copy-propagation"
@@ -425,7 +431,134 @@ void CanonicalizeOSSALifetime::findOriginalBoundary(
425431
}
426432

427433
//===----------------------------------------------------------------------===//
428-
// MARK: Step 3. Extend the "original" boundary from step 2 up to destroys that
434+
// MARK: Step 3. (Optional) Maximize lifetimes.
435+
//===----------------------------------------------------------------------===//
436+
437+
/// At -Onone, there are some conflicting goals:
438+
/// On the one hand: good debugging experience.
439+
/// (1) do not shorten value's lifetime
440+
/// On the other: demonstrate semantics.
441+
/// (2) consume value at same places it will be consumed at -O
442+
/// (3) ensure there are no more copies than there would be at -O
443+
///
444+
/// (2) and (3) are equivalent--extra (compared to -O) copies arise from failing
445+
/// to end lifetimes at consuming uses (which then need their own copies).
446+
///
447+
/// We achieve (2) and (3) always. We achieve (1) where possible.
448+
///
449+
/// Conceptually, the strategy is the following:
450+
/// - Collect the blocks in which the value was live before canonicalization.
451+
/// These are the "original" live blocks (originalLiveBlocks).
452+
/// [Color these blocks green.]
453+
/// - From within that collection, collect the blocks which contain a _final_
454+
/// consuming, non-destroy use, and their successors.
455+
/// These are the "consumed" blocks (consumedAtExitBlocks).
456+
/// [Color these blocks red.]
457+
/// - Extend liveness down to the boundary between originalLiveBlocks and
458+
/// consumedAtExitBlocks blocks.
459+
/// [Extend liveness down to the boundary between green blocks and red.]
460+
/// - In particular, in regions of originalLiveBlocks which have no boundary
461+
/// with consumedAtExitBlocks, liveness should be extended to its original
462+
/// extent.
463+
/// [Extend liveness down to the boundary between green blocks and uncolored.]
464+
void CanonicalizeOSSALifetime::extendUnconsumedLiveness(
465+
PrunedLivenessBoundary const &boundary) {
466+
auto currentDef = getCurrentDef();
467+
468+
// First, collect the blocks that were _originally_ live. We can't use
469+
// liveness here because it doesn't include blocks that occur before a
470+
// destroy_value.
471+
BasicBlockSet originalLiveBlocks(currentDef->getFunction());
472+
{
473+
// Some of the work here was already done by computeCanonicalLiveness.
474+
// Specifically, it already discovered all blocks containing (transitive)
475+
// uses and blocks that appear between them and the def.
476+
//
477+
// Seed the set with what it already discovered.
478+
for (auto *discoveredBlock : liveness.getDiscoveredBlocks())
479+
originalLiveBlocks.insert(discoveredBlock);
480+
481+
// Start the walk from the consuming blocks (which includes destroys as well
482+
// as the other consuming uses).
483+
BasicBlockWorklist worklist(currentDef->getFunction());
484+
for (auto *consumingBlock : consumingBlocks) {
485+
worklist.push(consumingBlock);
486+
}
487+
488+
// Walk backwards from consuming blocks.
489+
while (auto *block = worklist.pop()) {
490+
originalLiveBlocks.insert(block);
491+
for (auto *predecessor : block->getPredecessorBlocks()) {
492+
// If the block was discovered by liveness, we already added it to the
493+
// set.
494+
if (originalLiveBlocks.contains(predecessor))
495+
continue;
496+
worklist.pushIfNotVisited(predecessor);
497+
}
498+
}
499+
}
500+
501+
// Second, collect the blocks which occur after a _final_ consuming use.
502+
BasicBlockSet consumedAtExitBlocks(currentDef->getFunction());
503+
StackList<SILBasicBlock *> consumedAtEntryBlocks(currentDef->getFunction());
504+
{
505+
// Start the forward walk from blocks which contain _final_ non-destroy
506+
// consumes. These are just the instructions on the boundary which aren't
507+
// destroys.
508+
BasicBlockWorklist worklist(currentDef->getFunction());
509+
for (auto *instruction : boundary.lastUsers) {
510+
if (dynCastToDestroyOf(instruction, getCurrentDef()))
511+
continue;
512+
if (liveness.isInterestingUser(instruction) !=
513+
PrunedLiveness::IsInterestingUser::LifetimeEndingUse)
514+
continue;
515+
worklist.push(instruction->getParent());
516+
}
517+
while (auto *block = worklist.pop()) {
518+
consumedAtExitBlocks.insert(block);
519+
for (auto *successor : block->getSuccessorBlocks()) {
520+
if (!originalLiveBlocks.contains(successor))
521+
continue;
522+
worklist.pushIfNotVisited(successor);
523+
consumedAtEntryBlocks.push_back(successor);
524+
}
525+
}
526+
}
527+
528+
// Third, find the blocks on the boundary between the originally-live blocks
529+
// and the originally-live-but-consumed blocks. Extend liveness "to the end"
530+
// of these blocks.
531+
for (auto *block : consumedAtEntryBlocks) {
532+
for (auto *predecessor : block->getPredecessorBlocks()) {
533+
if (consumedAtExitBlocks.contains(predecessor))
534+
continue;
535+
// Add "the instruction(s) before the terminator" of the predecessor to
536+
// liveness.
537+
if (auto *inst = predecessor->getTerminator()->getPreviousInstruction()) {
538+
liveness.updateForUse(inst, /*lifetimeEnding*/ false);
539+
} else {
540+
for (auto *grandPredecessor : predecessor->getPredecessorBlocks()) {
541+
liveness.updateForUse(grandPredecessor->getTerminator(),
542+
/*lifetimeEnding*/ false);
543+
}
544+
}
545+
}
546+
}
547+
548+
// Finally, preserve the destroys which weren't in the consumed region in
549+
// place: hoisting such destroys would not avoid copies.
550+
for (auto *destroy : destroys) {
551+
auto *block = destroy->getParent();
552+
// If the destroy is in a consumed block or a final consuming block,
553+
// hoisting it would avoid a copy.
554+
if (consumedAtExitBlocks.contains(block))
555+
continue;
556+
liveness.updateForUse(destroy, /*lifetimeEnding*/ true);
557+
}
558+
}
559+
560+
//===----------------------------------------------------------------------===//
561+
// MARK: Step 4. Extend the "original" boundary from step 2 up to destroys that
429562
// aren't separated from it by "interesting" instructions.
430563
//===----------------------------------------------------------------------===//
431564

@@ -639,7 +772,7 @@ void CanonicalizeOSSALifetime::findExtendedBoundary(
639772
}
640773

641774
//===----------------------------------------------------------------------===//
642-
// MARK: Step 4. Insert destroys onto the boundary found in step 3 where needed.
775+
// MARK: Step 5. Insert destroys onto the boundary found in step 3 where needed.
643776
//===----------------------------------------------------------------------===//
644777

645778
/// Create a new destroy_value instruction before the specified instruction and
@@ -729,7 +862,7 @@ void CanonicalizeOSSALifetime::insertDestroysOnBoundary(
729862
}
730863

731864
//===----------------------------------------------------------------------===//
732-
// MARK: Step 5. Rewrite copies and destroys
865+
// MARK: Step 6. Rewrite copies and destroys
733866
//===----------------------------------------------------------------------===//
734867

735868
/// The lifetime extends beyond given consuming use. Copy the value.
@@ -898,12 +1031,24 @@ bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) {
8981031
// Step 2: compute original boundary
8991032
PrunedLivenessBoundary originalBoundary;
9001033
findOriginalBoundary(originalBoundary);
901-
// Step 3: extend boundary to destroys
9021034
PrunedLivenessBoundary boundary;
903-
findExtendedBoundary(originalBoundary, boundary);
904-
// Step 4: insert destroys and record consumes
1035+
if (maximizeLifetime) {
1036+
// Step 3. (optional) maximize lifetimes
1037+
extendUnconsumedLiveness(originalBoundary);
1038+
originalBoundary.clear();
1039+
// Step 2: (again) recompute the original boundary since we've extended
1040+
// liveness
1041+
findOriginalBoundary(originalBoundary);
1042+
// Step 4: extend boundary to destroys
1043+
findExtendedBoundary(originalBoundary, boundary);
1044+
} else {
1045+
// Step 3: (skipped)
1046+
// Step 4: extend boundary to destroys
1047+
findExtendedBoundary(originalBoundary, boundary);
1048+
}
1049+
// Step 5: insert destroys and record consumes
9051050
insertDestroysOnBoundary(boundary);
906-
// Step 5: rewrite copies and delete extra destroys
1051+
// Step 6: rewrite copies and delete extra destroys
9071052
rewriteCopies();
9081053

9091054
clearLiveness();

0 commit comments

Comments
 (0)