Skip to content

[CanonicalizeOSSALifetime] Extend lexical lifetimes to unreachables. #68608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/SIL/OSSALifetimeCompletion.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ class OSSALifetimeCompletion {
: LifetimeCompletion::AlreadyComplete;
}

static void visitUnreachableLifetimeEnds(
SILValue value, const SSAPrunedLiveness &liveness,
llvm::function_ref<void(UnreachableInst *)> visit);

protected:
bool analyzeAndUpdateLifetime(SILValue value, bool forceBoundaryCompletion);
};
Expand Down
29 changes: 27 additions & 2 deletions include/swift/SIL/PrunedLiveness.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ class DeadEndBlocks;
/// blocks to the blocks that occur on or after the def block. If any uses is
/// not dominated by a def block, then liveness will include the entry block,
/// as if defined by a function argument
///
/// TODO: For efficiency, use BasicBlockBitfield rather than SmallDenseMap.
class PrunedLiveBlocks {
public:
/// Per-block liveness state computed during backward dataflow propagation.
Expand Down Expand Up @@ -200,6 +198,17 @@ class PrunedLiveBlocks {
assert(!discoveredBlocks || discoveredBlocks->empty());
}

PrunedLiveBlocks(PrunedLiveBlocks const &other,
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
: liveBlocks(other.liveBlocks.getFunction(), 2),
discoveredBlocks(discoveredBlocks) {
assert(!discoveredBlocks || other.discoveredBlocks);
for (auto &block : *other.liveBlocks.getFunction()) {
liveBlocks.set(&block, other.liveBlocks.get(&block));
}
initializedFlag = other.initializedFlag;
}

bool isInitialized() const { return initializedFlag; }

void initializeDiscoveredBlocks(
Expand Down Expand Up @@ -363,6 +372,10 @@ class PrunedLiveness {
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
: liveBlocks(function, discoveredBlocks) {}

PrunedLiveness(PrunedLiveness const &other,
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
: liveBlocks(other.liveBlocks, discoveredBlocks), users(other.users) {}

bool isInitialized() const { return liveBlocks.isInitialized(); }

bool empty() const { return users.empty(); }
Expand Down Expand Up @@ -531,6 +544,10 @@ class PrunedLiveRange : public PrunedLiveness {
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
: PrunedLiveness(function, discoveredBlocks) {}

PrunedLiveRange(PrunedLiveRange const &other,
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
: PrunedLiveness(other, discoveredBlocks) {}

LiveRangeSummary recursivelyUpdateForDef(SILValue initialDef,
ValueSet &visited,
SILValue value);
Expand Down Expand Up @@ -628,6 +645,14 @@ class SSAPrunedLiveness : public PrunedLiveRange<SSAPrunedLiveness> {
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
: PrunedLiveRange(function, discoveredBlocks) {}

SSAPrunedLiveness(
SSAPrunedLiveness const &other,
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
: PrunedLiveRange(other, discoveredBlocks) {
def = other.def;
defInst = other.defInst;
}

SILValue getDef() const { return def; }

void initializeDef(SILValue def) {
Expand Down
14 changes: 11 additions & 3 deletions include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ class CanonicalizeOSSALifetime final {

currentDef = def;

if (maximizeLifetime) {
if (maximizeLifetime || respectsDeinitBarriers()) {
liveness->initializeDiscoveredBlocks(&discoveredBlocks);
}
liveness->initializeDef(getCurrentDef());
Expand Down Expand Up @@ -396,10 +396,18 @@ class CanonicalizeOSSALifetime final {
UserRange getUsers() const { return liveness->getAllUsers(); }

private:
bool respectsDeinitBarriers() const {
if (!currentDef->isLexical())
return false;
auto &module = currentDef->getFunction()->getModule();
return module.getASTContext().SILOpts.supportsLexicalLifetimes(module);
}

void recordDebugValue(DebugValueInst *dvi) { debugValues.insert(dvi); }

void recordConsumingUse(Operand *use) {
consumingBlocks.insert(use->getUser()->getParent());
void recordConsumingUse(Operand *use) { recordConsumingUser(use->getUser()); }
void recordConsumingUser(SILInstruction *user) {
consumingBlocks.insert(user->getParent());
}
bool computeCanonicalLiveness();

Expand Down
21 changes: 15 additions & 6 deletions lib/SIL/Utils/OSSALifetimeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ static bool endLifetimeAtBoundary(SILValue value,
return changed;
}

static bool endLifetimeAtUnreachableBlocks(SILValue value,
const SSAPrunedLiveness &liveness) {
void OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
SILValue value, const SSAPrunedLiveness &liveness,
llvm::function_ref<void(UnreachableInst *)> visit) {
PrunedLivenessBoundary boundary;
liveness.computeBoundary(boundary);

Expand All @@ -119,20 +120,28 @@ static bool endLifetimeAtUnreachableBlocks(SILValue value,
}
// Forward CFG walk from the non-lifetime-ending boundary to the unreachable
// instructions.
bool changed = false;
while (auto *block = deadEndBlocks.pop()) {
if (block->succ_empty()) {
// This assert will fail unless there are already lifetime-ending
// instruction on all paths to normal function exits.
auto *unreachable = cast<UnreachableInst>(block->getTerminator());
SILBuilderWithScope builder(unreachable);
endOSSALifetime(value, builder);
changed = true;
visit(unreachable);
}
for (auto *successor : block->getSuccessorBlocks()) {
deadEndBlocks.pushIfNotVisited(successor);
}
}
}

static bool endLifetimeAtUnreachableBlocks(SILValue value,
const SSAPrunedLiveness &liveness) {
bool changed = false;
OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
value, liveness, [&](auto *unreachable) {
SILBuilderWithScope builder(unreachable);
endOSSALifetime(value, builder);
changed = true;
});
return changed;
}

Expand Down
42 changes: 36 additions & 6 deletions lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -836,17 +836,47 @@ SILInstruction *SILCombiner::visitCondFailInst(CondFailInst *CFI) {
if (!I->getValue().getBoolValue())
return eraseInstFromFunction(*CFI);

// Remove any code that follows a (cond_fail 1) and set the block's
// terminator to unreachable.
// Remove non-lifetime-ending code that follows a (cond_fail 1) and set the
// block's terminator to unreachable.

// Nothing more to do here
// Are there instructions after this point to delete?

// First check if the next instruction is unreachable.
if (isa<UnreachableInst>(std::next(SILBasicBlock::iterator(CFI))))
return nullptr;

// Collect together all the instructions after this point
// Otherwise, check if the only instructions are unreachables and destroys of
// lexical values.

// Collect all instructions and, in OSSA, the values they define.
llvm::SmallVector<SILInstruction *, 32> ToRemove;
for (auto Inst = CFI->getParent()->rbegin(); &*Inst != CFI; ++Inst)
ToRemove.push_back(&*Inst);
ValueSet DefinedValues(CFI->getFunction());
for (auto Iter = std::next(CFI->getIterator());
Iter != CFI->getParent()->end(); ++Iter) {
if (!CFI->getFunction()->hasOwnership()) {
ToRemove.push_back(&*Iter);
continue;
}

for (auto result : Iter->getResults()) {
DefinedValues.insert(result);
}
// Look for destroys of lexical values whose def isn't after the cond_fail.
if (auto *dvi = dyn_cast<DestroyValueInst>(&*Iter)) {
auto value = dvi->getOperand();
if (!DefinedValues.contains(value) && value->isLexical())
continue;
}
ToRemove.push_back(&*Iter);
}

unsigned instructionsToDelete = ToRemove.size();
// If the last instruction is an unreachable already, it needn't be deleted.
if (isa<UnreachableInst>(ToRemove.back())) {
--instructionsToDelete;
}
if (instructionsToDelete == 0)
return nullptr;

for (auto *Inst : ToRemove) {
// Replace any still-remaining uses with undef and erase.
Expand Down
53 changes: 52 additions & 1 deletion lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/NodeDatastructures.h"
#include "swift/SIL/OSSALifetimeCompletion.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/PrunedLiveness.h"
#include "swift/SIL/Test.h"
Expand Down Expand Up @@ -249,6 +250,35 @@ void CanonicalizeOSSALifetime::extendLivenessToDeinitBarriers() {
SmallVector<SILInstruction *, 4> outsideDestroys;
findDestroysOutsideBoundary(outsideDestroys);

// OSSALifetimeCompletion: With complete lifetimes, creating completeLiveness
// and using it to visiti unreachable lifetime ends should be deleted.
SmallVector<SILBasicBlock *, 32> discoveredBlocks(this->discoveredBlocks);
SSAPrunedLiveness completeLiveness(*liveness, &discoveredBlocks);

for (auto *end : outsideDestroys) {
completeLiveness.updateForUse(end, /*lifetimeEnding*/ true);
}

OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
getCurrentDef(), completeLiveness, [&](auto *unreachable) {
recordConsumingUser(unreachable);
if (auto *previous = unreachable->getPreviousInstruction()) {
if (liveness->isInterestingUser(previous) ==
PrunedLiveness::IsInterestingUser::NonUser) {
liveness->updateForUse(previous, /*lifetimeEnding=*/false);
}
return;
}
for (auto *predecessor :
unreachable->getParent()->getPredecessorBlocks()) {
auto *previous = &predecessor->back();
if (liveness->isInterestingUser(previous) ==
PrunedLiveness::IsInterestingUser::NonUser) {
liveness->updateForUse(previous, /*lifetimeEnding=*/false);
}
}
});

auto *def = getCurrentDef()->getDefiningInstruction();
using InitialBlocks = ArrayRef<SILBasicBlock *>;
auto *defBlock = getCurrentDef()->getParentBlock();
Expand Down Expand Up @@ -890,6 +920,27 @@ static void insertDestroyBeforeInstruction(SILInstruction *nextInstruction,
SILValue currentDef,
CanonicalOSSAConsumeInfo &consumes,
InstModCallbacks &callbacks) {
// OSSALifetimeCompletion: This conditional clause can be deleted with
// complete lifetimes.
if (isa<UnreachableInst>(nextInstruction)) {
// Don't create a destroy_value if the next instruction is an unreachable.
// If there was a destroy here already, it would be reused. Avoids
// creating an explicit destroy of a value which might have an unclosed
// borrow scope. Doing so would result in
//
// somewhere:
// %def
// %borrow = begin_borrow ...
//
// die:
// destroy_value %def
// unreachable
//
// which is invalid (although the verifier doesn't catch
// it--rdar://115850528) because there must be an `end_borrow %borrow`
// before the destroy_value.
return;
}
SILBuilderWithScope builder(nextInstruction);
auto loc =
RegularLocation::getAutoGeneratedLocation(nextInstruction->getLoc());
Expand Down Expand Up @@ -1124,7 +1175,7 @@ bool CanonicalizeOSSALifetime::computeLiveness() {
clear();
return false;
}
if (currentDef->isLexical()) {
if (respectsDeinitBarriers()) {
extendLivenessToDeinitBarriers();
}
if (accessBlockAnalysis) {
Expand Down
Loading