Files
swift-mirror/lib/SIL/Utils/OSSALifetimeCompletion.cpp
Andrew Trick ccd08ca4b4 Add a completeOSSALifetime utility
Add local lifetime-ending operations to any owned or borrowed value.

This puts a single value into valid OSSA form so that linear lifetime
checking will pass.

Also adds UnreachableLifetimeCompletion which fixes OSSA after
converting a code path to unreachable (e.g. DiagnoseUnreachable and MandatoryInlining).
2023-03-01 21:04:09 -08:00

251 lines
8.5 KiB
C++

//===--- OSSALifetimeCompletion.cpp ---------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// OSSA lifetime completion adds lifetime ending instructions to make
/// linear lifetimes complete.
///
/// Interior liveness handles the following cases naturally:
///
/// When completing the lifetime if the initial value, %v1, transitively
/// include all dominated reborrows. %phi1 in this example:
///
/// %v1 = ...
/// cond_br bb1, bb2
/// bb1:
/// %b1 = begin_borrow %v1
/// br bb3(%b1)
/// bb2:
/// %b2 = begin_borrow %v1
/// br bb3(%b2)
/// bb3(%phi1):
/// %u1 = %phi1
/// end_borrow %phi1
/// %k1 = destroy_value %v1 // must be below end_borrow %phi1
///
/// When completing the lifetime for a (%phi2) transitively include all inner
/// adjacent reborrows (%phi1):
///
/// bb1:
/// %v1 = ...
/// %b1 = begin_borrow %v1
/// br bb3(%b1, %v1)
/// bb2:
/// %v2 = ...
/// %b2 = begin_borrow %v2
/// br bb3(%b2, %v2)
/// bb3(%phi1, %phi2):
/// %u1 = %phi1
/// end_borrow %phi1
/// %k1 = destroy_value %phi1
///
//===----------------------------------------------------------------------===//
#include "swift/SIL/OSSALifetimeCompletion.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILInstruction.h"
using namespace swift;
static SILInstruction *endOSSALifetime(SILValue value, SILBuilder &builder) {
auto loc =
RegularLocation::getAutoGeneratedLocation(builder.getInsertionPointLoc());
if (value->getOwnershipKind() == OwnershipKind::Owned) {
return builder.createDestroyValue(loc, value);
}
return builder.createEndBorrow(loc, value);
}
static bool endLifetimeAtBoundary(SILValue value,
const SSAPrunedLiveness &liveness) {
PrunedLivenessBoundary boundary;
liveness.computeBoundary(boundary);
bool changed = false;
for (SILInstruction *lastUser : boundary.lastUsers) {
if (liveness.isInterestingUser(lastUser)
!= PrunedLiveness::LifetimeEndingUse) {
changed = true;
SILBuilderWithScope::insertAfter(lastUser, [value](SILBuilder &builder) {
endOSSALifetime(value, builder);
});
}
}
for (SILBasicBlock *edge : boundary.boundaryEdges) {
changed = true;
SILBuilderWithScope builder(edge->begin());
endOSSALifetime(value, builder);
}
for (SILNode *deadDef : boundary.deadDefs) {
SILInstruction *next = nullptr;
if (auto *deadInst = dyn_cast<SILInstruction>(deadDef)) {
next = deadInst->getNextInstruction();
} else {
next = cast<ValueBase>(deadDef)->getNextInstruction();
}
changed = true;
SILBuilderWithScope builder(next);
endOSSALifetime(value, builder);
}
return changed;
}
static bool endLifetimeAtUnreachableBlocks(SILValue value,
const SSAPrunedLiveness &liveness) {
PrunedLivenessBoundary boundary;
liveness.computeBoundary(boundary);
BasicBlockWorklist deadEndBlocks(value->getFunction());
for (SILInstruction *lastUser : boundary.lastUsers) {
if (liveness.isInterestingUser(lastUser)
!= PrunedLiveness::LifetimeEndingUse) {
deadEndBlocks.push(lastUser->getParent());
}
}
for (SILBasicBlock *edge : boundary.boundaryEdges) {
deadEndBlocks.push(edge);
}
for (SILNode *deadDef : boundary.deadDefs) {
deadEndBlocks.push(deadDef->getParentBlock());
}
// 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;
}
for (auto *successor : block->getSuccessorBlocks()) {
deadEndBlocks.push(successor);
}
}
return changed;
}
/// End the lifetime of \p value at unreachable instructions.
///
/// Returns true if any new instructions were created to complete the lifetime.
///
/// This is only meant to cleanup lifetimes that lead to dead-end blocks. After
/// recursively completing all nested scopes, it then simply ends the lifetime
/// at the Unreachable instruction.
bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(SILValue value) {
// Called for inner borrows, inner adjacent reborrows, inner reborrows, and
// scoped addresses.
auto handleInnerScope = [this](SILValue innerBorrowedValue) {
completeOSSALifetime(innerBorrowedValue);
};
InteriorLiveness liveness(value);
liveness.compute(domInfo, handleInnerScope);
bool changed = false;
if (value->isLexical()) {
changed |= endLifetimeAtUnreachableBlocks(value, liveness.getLiveness());
} else {
changed |= endLifetimeAtBoundary(value, liveness.getLiveness());
}
// TODO: Rebuild outer adjacent phis on demand (SILGen does not currently
// produce guaranteed phis). See FindEnclosingDefs &
// findSuccessorDefsFromPredDefs. If no enclosing phi is found, we can create
// it here and use updateSSA to recursively populate phis.
assert(liveness.getUnenclosedPhis().empty());
return changed;
}
// TODO: create a fast check for 'mayEndLifetime(SILInstruction *)'. Verify that
// it returns true for every instruction that has a lifetime-ending operand.
void UnreachableLifetimeCompletion::visitUnreachableInst(
SILInstruction *instruction) {
auto *block = instruction->getParent();
bool inReachableBlock = !unreachableBlocks.contains(block);
// If this instruction's block is already marked unreachable, and
// updatingLifetimes is not yet set, then this instruction will be visited
// again later when propagating unreachable blocks.
if (!inReachableBlock && !updatingLifetimes)
return;
for (Operand &operand : instruction->getAllOperands()) {
if (!operand.isLifetimeEnding())
continue;
SILValue value = operand.get();
SILBasicBlock *defBlock = value->getParentBlock();
if (unreachableBlocks.contains(defBlock))
continue;
auto *def = value->getDefiningInstruction();
if (def && unreachableInsts.contains(def))
continue;
// The operand's definition is still reachable and its lifetime ends on a
// newly unreachable path.
//
// Note: The arguments of a no-return try_apply may still appear reachable
// here because the try_apply itself is never visited as unreachable, hence
// its successor blocks are not marked . But it
// seems harmless to recompute their lifetimes.
// Insert this unreachable instruction in unreachableInsts if its parent
// block is not already marked unreachable.
if (inReachableBlock) {
unreachableInsts.insert(instruction);
}
incompleteValues.insert(value);
// Add unreachable successors to the forward traversal worklist.
if (auto *term = dyn_cast<TermInst>(instruction)) {
for (auto *succBlock : term->getSuccessorBlocks()) {
if (llvm::all_of(succBlock->getPredecessorBlocks(),
[&](SILBasicBlock *predBlock) {
if (predBlock == block)
return true;
return unreachableBlocks.contains(predBlock);
})) {
unreachableBlocks.insert(succBlock);
}
}
}
}
}
bool UnreachableLifetimeCompletion::completeLifetimes() {
assert(!updatingLifetimes && "don't call this more than once");
updatingLifetimes = true;
// Now that all unreachable terminator instructions have been visited,
// propagate unreachable blocks.
for (auto blockIt = unreachableBlocks.begin();
blockIt != unreachableBlocks.end(); ++blockIt) {
auto *block = *blockIt;
for (auto &instruction : *block) {
visitUnreachableInst(&instruction);
}
}
OSSALifetimeCompletion completion(function, domInfo);
bool changed = false;
for (auto value : incompleteValues) {
if (completion.completeOSSALifetime(value)
== LifetimeCompletion::WasCompleted) {
changed = true;
}
}
return changed;
}