LinearLifetimeChecker - make DeadEndBlocks optional

This commit is contained in:
Andrew Trick
2021-10-20 15:05:43 -07:00
parent 2743b83b8e
commit 103a6fefb8
9 changed files with 38 additions and 25 deletions

View File

@@ -57,10 +57,23 @@ private:
friend class SILOwnershipVerifier;
friend class SILValueOwnershipChecker;
DeadEndBlocks &deadEndBlocks;
// TODO: migrate away from using dead end blocks for OSSA values. end_borrow
// or destroy_value should ideally exist on all paths. However, deadEndBlocks
// may still be useful for checking memory lifetime for address uses.
DeadEndBlocks *deadEndBlocks;
public:
LinearLifetimeChecker(DeadEndBlocks &deadEndBlocks)
/// \p deadEndBlocks should be provided for lifetimes that do not require
/// consuming uses on dead-end paths, which end in an unreachable terminator.
/// OSSA values require consumes on all paths, so \p deadEndBlocks are *not*
/// required for OSSA lifetimes. Memory lifetimes and access scopes only
/// require destroys on non-dead-end paths.
///
/// TODO: The verifier currently requires OSSA borrow scopes to end on all
/// paths. Owned OSSA lifetimes may still be missing destroys on dead-end
/// paths. Once owned values are fully enforced, the same invariant will hold
/// for all OSSA values.
LinearLifetimeChecker(DeadEndBlocks *deadEndBlocks = nullptr)
: deadEndBlocks(deadEndBlocks) {}
/// Returns true that \p value forms a linear lifetime with consuming uses \p

View File

@@ -85,7 +85,7 @@ class DeadEndBlocks;
class GuaranteedPhiVerifier {
/// A cache of dead-end basic blocks that we use to determine if we can
/// ignore "leaks".
DeadEndBlocks &deadEndBlocks;
DeadEndBlocks *deadEndBlocks = nullptr;
/// The builder that the checker uses to emit error messages, crash if asked
/// for, or supply back interesting info to the caller.
LinearLifetimeChecker::ErrorBuilder errorBuilder;
@@ -96,7 +96,7 @@ class GuaranteedPhiVerifier {
dependentPhiToBaseValueMap;
public:
GuaranteedPhiVerifier(const SILFunction *func, DeadEndBlocks &deadEndBlocks,
GuaranteedPhiVerifier(const SILFunction *func, DeadEndBlocks *deadEndBlocks,
LinearLifetimeChecker::ErrorBuilder errorBuilder)
: deadEndBlocks(deadEndBlocks), errorBuilder(errorBuilder) {}

View File

@@ -161,12 +161,12 @@ struct State {
/// Once we have setup all of our consuming/non-consuming blocks and have
/// validated that all intra-block dataflow is safe, perform the inter-block
/// dataflow.
void performDataflow(DeadEndBlocks &deBlocks);
void performDataflow(DeadEndBlocks *deBlocks);
/// After we have performed the dataflow, check the end state of our dataflow
/// for validity. If this is a linear typed value, return true. Return false
/// otherwise.
void checkDataflowEndState(DeadEndBlocks &deBlocks);
void checkDataflowEndState(DeadEndBlocks *deBlocks);
void dumpConsumingUsers() const {
llvm::errs() << "Consuming Users:\n";
@@ -410,7 +410,7 @@ void State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
// Dataflow
//===----------------------------------------------------------------------===//
void State::performDataflow(DeadEndBlocks &deBlocks) {
void State::performDataflow(DeadEndBlocks *deBlocks) {
LLVM_DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n");
// Until the worklist is empty...
while (!worklist.empty()) {
@@ -446,7 +446,7 @@ void State::performDataflow(DeadEndBlocks &deBlocks) {
// Then check if the successor is a transitively unreachable block. In
// such a case, we ignore it since we are going to leak along that path.
if (deBlocks.isDeadEnd(succBlock))
if (deBlocks && deBlocks->isDeadEnd(succBlock))
continue;
// Otherwise, add the successor to our SuccessorBlocksThatMustBeVisited
@@ -483,7 +483,7 @@ void State::performDataflow(DeadEndBlocks &deBlocks) {
}
}
void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
void State::checkDataflowEndState(DeadEndBlocks *deBlocks) {
if (!successorBlocksThatMustBeVisited.empty()) {
// If we are asked to store any leaking blocks, put them in the leaking
// blocks array.
@@ -518,7 +518,7 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
// be a use-before-def or a use-after-free.
for (auto pair : blocksWithNonConsumingUses.getRange()) {
auto *block = pair.first;
if (deBlocks.isDeadEnd(block)) {
if (deBlocks && deBlocks->isDeadEnd(block)) {
continue;
}
@@ -601,7 +601,7 @@ LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
for (auto *use : nonConsumingUses) {
auto *useParent = use->getUser()->getParent();
if (useParent == value->getParentBlock() ||
deadEndBlocks.isDeadEnd(useParent)) {
(deadEndBlocks && deadEndBlocks->isDeadEnd(useParent))) {
continue;
}

View File

@@ -2551,11 +2551,11 @@ public:
bool success = useKind == AddressUseKind::NonEscaping;
require(!success || checkScopedAddressUses(
scopedAddress, &scopedAddressLiveness, &DEBlocks),
scopedAddress, &scopedAddressLiveness, DEBlocks.get()),
"Ill formed store_borrow scope");
require(!success || !hasOtherStoreBorrowsInLifetime(
SI, &scopedAddressLiveness, &DEBlocks),
SI, &scopedAddressLiveness, DEBlocks.get()),
"A store_borrow cannot be nested within another "
"store_borrow to its destination");

View File

@@ -130,7 +130,7 @@ static bool fixupReferenceCounts(
auto *stackLoc = builder.createAllocStack(loc, v->getType().getObjectType());
builder.createCopyAddr(loc, v, stackLoc, IsNotTake, IsInitialization);
LinearLifetimeChecker checker(deadEndBlocks);
LinearLifetimeChecker checker(&deadEndBlocks);
bool consumedInLoop = checker.completeConsumingUseSet(
pai, applySite.getCalleeOperand(),
[&](SILBasicBlock::iterator insertPt) {
@@ -173,7 +173,7 @@ static bool fixupReferenceCounts(
// just cares about the block the value is in. In a forthcoming commit, I
// am going to change this to use a different API on the linear lifetime
// checker that makes this clearer.
LinearLifetimeChecker checker(deadEndBlocks);
LinearLifetimeChecker checker(&deadEndBlocks);
bool consumedInLoop = checker.completeConsumingUseSet(
pai, applySite.getCalleeOperand(),
[&](SILBasicBlock::iterator insertPt) {
@@ -220,7 +220,7 @@ static bool fixupReferenceCounts(
// just cares about the block the value is in. In a forthcoming commit, I
// am going to change this to use a different API on the linear lifetime
// checker that makes this clearer.
LinearLifetimeChecker checker(deadEndBlocks);
LinearLifetimeChecker checker(&deadEndBlocks);
checker.completeConsumingUseSet(
pai, applySite.getCalleeOperand(),
[&](SILBasicBlock::iterator insertPt) {
@@ -257,7 +257,7 @@ static bool fixupReferenceCounts(
// just cares about the block the value is in. In a forthcoming commit, I
// am going to change this to use a different API on the linear lifetime
// checker that makes this clearer.
LinearLifetimeChecker checker(deadEndBlocks);
LinearLifetimeChecker checker(&deadEndBlocks);
checker.completeConsumingUseSet(
pai, applySite.getCalleeOperand(),
[&](SILBasicBlock::iterator insertPt) {

View File

@@ -1197,7 +1197,7 @@ void AvailableValueAggregator::addHandOffCopyDestroysForPhis(
// Then perform the linear lifetime check. If we succeed, continue. We have
// no further work to do.
auto *loadOperand = &load->getAllOperands()[0];
LinearLifetimeChecker checker(deadEndBlocks);
LinearLifetimeChecker checker(&deadEndBlocks);
bool consumedInLoop = checker.completeConsumingUseSet(
phi, loadOperand, [&](SILBasicBlock::iterator iter) {
SILBuilderWithScope builder(iter);
@@ -1279,7 +1279,7 @@ void AvailableValueAggregator::addMissingDestroysForCopiedValues(
// Then perform the linear lifetime check. If we succeed, continue. We have
// no further work to do.
auto *loadOperand = &load->getAllOperands()[0];
LinearLifetimeChecker checker(deadEndBlocks);
LinearLifetimeChecker checker(&deadEndBlocks);
bool consumedInLoop = checker.completeConsumingUseSet(
cvi, loadOperand, [&](SILBasicBlock::iterator iter) {
SILBuilderWithScope builder(iter);

View File

@@ -765,7 +765,7 @@ bool SemanticARCOptVisitor::tryPerformOwnedCopyValueOptimization(
// Ok, we have an owned value. If we do not have any non-destroying consuming
// uses, see if all of our uses (ignoring destroying uses) are within our
// parent owned value's lifetime.
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
if (!checker.validateLifetime(originalValue, parentLifetimeEndingUses,
allCopyUses))
return false;

View File

@@ -88,7 +88,7 @@ public:
SmallVector<Operand *, 8> endScopeUses;
transform(access->getEndAccesses(), std::back_inserter(endScopeUses),
[](EndAccessInst *eai) { return &eai->getAllOperands()[0]; });
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
if (!checker.validateLifetime(access, endScopeUses,
liveRange.getAllConsumingUses())) {
// If we fail the linear lifetime check, then just recur:
@@ -138,7 +138,7 @@ public:
// Ok, we have some writes. See if any of them are within our live
// range. If any are, we definitely can not promote to load_borrow.
SmallVector<BeginAccessInst *, 16> foundBeginAccess;
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
SILValue introducerValue = liveRange.getIntroducer().value;
if (!checker.usesNotContainedWithinLifetime(introducerValue,
liveRange.getDestroyingUses(),
@@ -244,7 +244,7 @@ public:
value.visitLocalScopeEndingUses(
[&](Operand *use) { endScopeInsts.push_back(use); return true; });
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
// Returns true on success. So we invert.
bool foundError = !checker.validateLifetime(
@@ -291,7 +291,7 @@ public:
// Then make sure that all of our load [copy] uses are within the
// destroy_addr.
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
// Returns true on success. So we invert.
bool foundError = !checker.validateLifetime(
stack, addrDestroyingOperands /*consuming users*/,

View File

@@ -56,7 +56,7 @@ bool SemanticARCOptVisitor::visitUncheckedOwnershipConversionInst(
// Ok, now we need to perform our lifetime check.
SmallVector<Operand *, 8> consumingUses(op->getConsumingUses());
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
if (!checker.validateLifetime(op, consumingUses, newUses))
return false;