mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
LinearLifetimeChecker - make DeadEndBlocks optional
This commit is contained in:
@@ -57,10 +57,23 @@ private:
|
|||||||
friend class SILOwnershipVerifier;
|
friend class SILOwnershipVerifier;
|
||||||
friend class SILValueOwnershipChecker;
|
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:
|
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) {}
|
: deadEndBlocks(deadEndBlocks) {}
|
||||||
|
|
||||||
/// Returns true that \p value forms a linear lifetime with consuming uses \p
|
/// Returns true that \p value forms a linear lifetime with consuming uses \p
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class DeadEndBlocks;
|
|||||||
class GuaranteedPhiVerifier {
|
class GuaranteedPhiVerifier {
|
||||||
/// A cache of dead-end basic blocks that we use to determine if we can
|
/// A cache of dead-end basic blocks that we use to determine if we can
|
||||||
/// ignore "leaks".
|
/// ignore "leaks".
|
||||||
DeadEndBlocks &deadEndBlocks;
|
DeadEndBlocks *deadEndBlocks = nullptr;
|
||||||
/// The builder that the checker uses to emit error messages, crash if asked
|
/// The builder that the checker uses to emit error messages, crash if asked
|
||||||
/// for, or supply back interesting info to the caller.
|
/// for, or supply back interesting info to the caller.
|
||||||
LinearLifetimeChecker::ErrorBuilder errorBuilder;
|
LinearLifetimeChecker::ErrorBuilder errorBuilder;
|
||||||
@@ -96,7 +96,7 @@ class GuaranteedPhiVerifier {
|
|||||||
dependentPhiToBaseValueMap;
|
dependentPhiToBaseValueMap;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GuaranteedPhiVerifier(const SILFunction *func, DeadEndBlocks &deadEndBlocks,
|
GuaranteedPhiVerifier(const SILFunction *func, DeadEndBlocks *deadEndBlocks,
|
||||||
LinearLifetimeChecker::ErrorBuilder errorBuilder)
|
LinearLifetimeChecker::ErrorBuilder errorBuilder)
|
||||||
: deadEndBlocks(deadEndBlocks), errorBuilder(errorBuilder) {}
|
: deadEndBlocks(deadEndBlocks), errorBuilder(errorBuilder) {}
|
||||||
|
|
||||||
|
|||||||
@@ -161,12 +161,12 @@ struct State {
|
|||||||
/// Once we have setup all of our consuming/non-consuming blocks and have
|
/// 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
|
/// validated that all intra-block dataflow is safe, perform the inter-block
|
||||||
/// dataflow.
|
/// dataflow.
|
||||||
void performDataflow(DeadEndBlocks &deBlocks);
|
void performDataflow(DeadEndBlocks *deBlocks);
|
||||||
|
|
||||||
/// After we have performed the dataflow, check the end state of our dataflow
|
/// 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
|
/// for validity. If this is a linear typed value, return true. Return false
|
||||||
/// otherwise.
|
/// otherwise.
|
||||||
void checkDataflowEndState(DeadEndBlocks &deBlocks);
|
void checkDataflowEndState(DeadEndBlocks *deBlocks);
|
||||||
|
|
||||||
void dumpConsumingUsers() const {
|
void dumpConsumingUsers() const {
|
||||||
llvm::errs() << "Consuming Users:\n";
|
llvm::errs() << "Consuming Users:\n";
|
||||||
@@ -410,7 +410,7 @@ void State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
|
|||||||
// Dataflow
|
// Dataflow
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
void State::performDataflow(DeadEndBlocks &deBlocks) {
|
void State::performDataflow(DeadEndBlocks *deBlocks) {
|
||||||
LLVM_DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n");
|
LLVM_DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n");
|
||||||
// Until the worklist is empty...
|
// Until the worklist is empty...
|
||||||
while (!worklist.empty()) {
|
while (!worklist.empty()) {
|
||||||
@@ -446,7 +446,7 @@ void State::performDataflow(DeadEndBlocks &deBlocks) {
|
|||||||
|
|
||||||
// Then check if the successor is a transitively unreachable block. In
|
// 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.
|
// 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;
|
continue;
|
||||||
|
|
||||||
// Otherwise, add the successor to our SuccessorBlocksThatMustBeVisited
|
// 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 (!successorBlocksThatMustBeVisited.empty()) {
|
||||||
// If we are asked to store any leaking blocks, put them in the leaking
|
// If we are asked to store any leaking blocks, put them in the leaking
|
||||||
// blocks array.
|
// blocks array.
|
||||||
@@ -518,7 +518,7 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
|
|||||||
// be a use-before-def or a use-after-free.
|
// be a use-before-def or a use-after-free.
|
||||||
for (auto pair : blocksWithNonConsumingUses.getRange()) {
|
for (auto pair : blocksWithNonConsumingUses.getRange()) {
|
||||||
auto *block = pair.first;
|
auto *block = pair.first;
|
||||||
if (deBlocks.isDeadEnd(block)) {
|
if (deBlocks && deBlocks->isDeadEnd(block)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,7 +601,7 @@ LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
|
|||||||
for (auto *use : nonConsumingUses) {
|
for (auto *use : nonConsumingUses) {
|
||||||
auto *useParent = use->getUser()->getParent();
|
auto *useParent = use->getUser()->getParent();
|
||||||
if (useParent == value->getParentBlock() ||
|
if (useParent == value->getParentBlock() ||
|
||||||
deadEndBlocks.isDeadEnd(useParent)) {
|
(deadEndBlocks && deadEndBlocks->isDeadEnd(useParent))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2551,11 +2551,11 @@ public:
|
|||||||
bool success = useKind == AddressUseKind::NonEscaping;
|
bool success = useKind == AddressUseKind::NonEscaping;
|
||||||
|
|
||||||
require(!success || checkScopedAddressUses(
|
require(!success || checkScopedAddressUses(
|
||||||
scopedAddress, &scopedAddressLiveness, &DEBlocks),
|
scopedAddress, &scopedAddressLiveness, DEBlocks.get()),
|
||||||
"Ill formed store_borrow scope");
|
"Ill formed store_borrow scope");
|
||||||
|
|
||||||
require(!success || !hasOtherStoreBorrowsInLifetime(
|
require(!success || !hasOtherStoreBorrowsInLifetime(
|
||||||
SI, &scopedAddressLiveness, &DEBlocks),
|
SI, &scopedAddressLiveness, DEBlocks.get()),
|
||||||
"A store_borrow cannot be nested within another "
|
"A store_borrow cannot be nested within another "
|
||||||
"store_borrow to its destination");
|
"store_borrow to its destination");
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ static bool fixupReferenceCounts(
|
|||||||
auto *stackLoc = builder.createAllocStack(loc, v->getType().getObjectType());
|
auto *stackLoc = builder.createAllocStack(loc, v->getType().getObjectType());
|
||||||
builder.createCopyAddr(loc, v, stackLoc, IsNotTake, IsInitialization);
|
builder.createCopyAddr(loc, v, stackLoc, IsNotTake, IsInitialization);
|
||||||
|
|
||||||
LinearLifetimeChecker checker(deadEndBlocks);
|
LinearLifetimeChecker checker(&deadEndBlocks);
|
||||||
bool consumedInLoop = checker.completeConsumingUseSet(
|
bool consumedInLoop = checker.completeConsumingUseSet(
|
||||||
pai, applySite.getCalleeOperand(),
|
pai, applySite.getCalleeOperand(),
|
||||||
[&](SILBasicBlock::iterator insertPt) {
|
[&](SILBasicBlock::iterator insertPt) {
|
||||||
@@ -173,7 +173,7 @@ static bool fixupReferenceCounts(
|
|||||||
// just cares about the block the value is in. In a forthcoming commit, I
|
// 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
|
// am going to change this to use a different API on the linear lifetime
|
||||||
// checker that makes this clearer.
|
// checker that makes this clearer.
|
||||||
LinearLifetimeChecker checker(deadEndBlocks);
|
LinearLifetimeChecker checker(&deadEndBlocks);
|
||||||
bool consumedInLoop = checker.completeConsumingUseSet(
|
bool consumedInLoop = checker.completeConsumingUseSet(
|
||||||
pai, applySite.getCalleeOperand(),
|
pai, applySite.getCalleeOperand(),
|
||||||
[&](SILBasicBlock::iterator insertPt) {
|
[&](SILBasicBlock::iterator insertPt) {
|
||||||
@@ -220,7 +220,7 @@ static bool fixupReferenceCounts(
|
|||||||
// just cares about the block the value is in. In a forthcoming commit, I
|
// 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
|
// am going to change this to use a different API on the linear lifetime
|
||||||
// checker that makes this clearer.
|
// checker that makes this clearer.
|
||||||
LinearLifetimeChecker checker(deadEndBlocks);
|
LinearLifetimeChecker checker(&deadEndBlocks);
|
||||||
checker.completeConsumingUseSet(
|
checker.completeConsumingUseSet(
|
||||||
pai, applySite.getCalleeOperand(),
|
pai, applySite.getCalleeOperand(),
|
||||||
[&](SILBasicBlock::iterator insertPt) {
|
[&](SILBasicBlock::iterator insertPt) {
|
||||||
@@ -257,7 +257,7 @@ static bool fixupReferenceCounts(
|
|||||||
// just cares about the block the value is in. In a forthcoming commit, I
|
// 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
|
// am going to change this to use a different API on the linear lifetime
|
||||||
// checker that makes this clearer.
|
// checker that makes this clearer.
|
||||||
LinearLifetimeChecker checker(deadEndBlocks);
|
LinearLifetimeChecker checker(&deadEndBlocks);
|
||||||
checker.completeConsumingUseSet(
|
checker.completeConsumingUseSet(
|
||||||
pai, applySite.getCalleeOperand(),
|
pai, applySite.getCalleeOperand(),
|
||||||
[&](SILBasicBlock::iterator insertPt) {
|
[&](SILBasicBlock::iterator insertPt) {
|
||||||
|
|||||||
@@ -1197,7 +1197,7 @@ void AvailableValueAggregator::addHandOffCopyDestroysForPhis(
|
|||||||
// Then perform the linear lifetime check. If we succeed, continue. We have
|
// Then perform the linear lifetime check. If we succeed, continue. We have
|
||||||
// no further work to do.
|
// no further work to do.
|
||||||
auto *loadOperand = &load->getAllOperands()[0];
|
auto *loadOperand = &load->getAllOperands()[0];
|
||||||
LinearLifetimeChecker checker(deadEndBlocks);
|
LinearLifetimeChecker checker(&deadEndBlocks);
|
||||||
bool consumedInLoop = checker.completeConsumingUseSet(
|
bool consumedInLoop = checker.completeConsumingUseSet(
|
||||||
phi, loadOperand, [&](SILBasicBlock::iterator iter) {
|
phi, loadOperand, [&](SILBasicBlock::iterator iter) {
|
||||||
SILBuilderWithScope builder(iter);
|
SILBuilderWithScope builder(iter);
|
||||||
@@ -1279,7 +1279,7 @@ void AvailableValueAggregator::addMissingDestroysForCopiedValues(
|
|||||||
// Then perform the linear lifetime check. If we succeed, continue. We have
|
// Then perform the linear lifetime check. If we succeed, continue. We have
|
||||||
// no further work to do.
|
// no further work to do.
|
||||||
auto *loadOperand = &load->getAllOperands()[0];
|
auto *loadOperand = &load->getAllOperands()[0];
|
||||||
LinearLifetimeChecker checker(deadEndBlocks);
|
LinearLifetimeChecker checker(&deadEndBlocks);
|
||||||
bool consumedInLoop = checker.completeConsumingUseSet(
|
bool consumedInLoop = checker.completeConsumingUseSet(
|
||||||
cvi, loadOperand, [&](SILBasicBlock::iterator iter) {
|
cvi, loadOperand, [&](SILBasicBlock::iterator iter) {
|
||||||
SILBuilderWithScope builder(iter);
|
SILBuilderWithScope builder(iter);
|
||||||
|
|||||||
@@ -765,7 +765,7 @@ bool SemanticARCOptVisitor::tryPerformOwnedCopyValueOptimization(
|
|||||||
// Ok, we have an owned value. If we do not have any non-destroying consuming
|
// 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
|
// uses, see if all of our uses (ignoring destroying uses) are within our
|
||||||
// parent owned value's lifetime.
|
// parent owned value's lifetime.
|
||||||
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
|
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
|
||||||
if (!checker.validateLifetime(originalValue, parentLifetimeEndingUses,
|
if (!checker.validateLifetime(originalValue, parentLifetimeEndingUses,
|
||||||
allCopyUses))
|
allCopyUses))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public:
|
|||||||
SmallVector<Operand *, 8> endScopeUses;
|
SmallVector<Operand *, 8> endScopeUses;
|
||||||
transform(access->getEndAccesses(), std::back_inserter(endScopeUses),
|
transform(access->getEndAccesses(), std::back_inserter(endScopeUses),
|
||||||
[](EndAccessInst *eai) { return &eai->getAllOperands()[0]; });
|
[](EndAccessInst *eai) { return &eai->getAllOperands()[0]; });
|
||||||
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
|
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
|
||||||
if (!checker.validateLifetime(access, endScopeUses,
|
if (!checker.validateLifetime(access, endScopeUses,
|
||||||
liveRange.getAllConsumingUses())) {
|
liveRange.getAllConsumingUses())) {
|
||||||
// If we fail the linear lifetime check, then just recur:
|
// 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
|
// 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.
|
// range. If any are, we definitely can not promote to load_borrow.
|
||||||
SmallVector<BeginAccessInst *, 16> foundBeginAccess;
|
SmallVector<BeginAccessInst *, 16> foundBeginAccess;
|
||||||
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
|
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
|
||||||
SILValue introducerValue = liveRange.getIntroducer().value;
|
SILValue introducerValue = liveRange.getIntroducer().value;
|
||||||
if (!checker.usesNotContainedWithinLifetime(introducerValue,
|
if (!checker.usesNotContainedWithinLifetime(introducerValue,
|
||||||
liveRange.getDestroyingUses(),
|
liveRange.getDestroyingUses(),
|
||||||
@@ -244,7 +244,7 @@ public:
|
|||||||
value.visitLocalScopeEndingUses(
|
value.visitLocalScopeEndingUses(
|
||||||
[&](Operand *use) { endScopeInsts.push_back(use); return true; });
|
[&](Operand *use) { endScopeInsts.push_back(use); return true; });
|
||||||
|
|
||||||
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
|
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
|
||||||
|
|
||||||
// Returns true on success. So we invert.
|
// Returns true on success. So we invert.
|
||||||
bool foundError = !checker.validateLifetime(
|
bool foundError = !checker.validateLifetime(
|
||||||
@@ -291,7 +291,7 @@ public:
|
|||||||
|
|
||||||
// Then make sure that all of our load [copy] uses are within the
|
// Then make sure that all of our load [copy] uses are within the
|
||||||
// destroy_addr.
|
// destroy_addr.
|
||||||
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
|
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
|
||||||
// Returns true on success. So we invert.
|
// Returns true on success. So we invert.
|
||||||
bool foundError = !checker.validateLifetime(
|
bool foundError = !checker.validateLifetime(
|
||||||
stack, addrDestroyingOperands /*consuming users*/,
|
stack, addrDestroyingOperands /*consuming users*/,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ bool SemanticARCOptVisitor::visitUncheckedOwnershipConversionInst(
|
|||||||
|
|
||||||
// Ok, now we need to perform our lifetime check.
|
// Ok, now we need to perform our lifetime check.
|
||||||
SmallVector<Operand *, 8> consumingUses(op->getConsumingUses());
|
SmallVector<Operand *, 8> consumingUses(op->getConsumingUses());
|
||||||
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
|
LinearLifetimeChecker checker(&ctx.getDeadEndBlocks());
|
||||||
if (!checker.validateLifetime(op, consumingUses, newUses))
|
if (!checker.validateLifetime(op, consumingUses, newUses))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user