Skip to content

Commit ac6f318

Browse files
committed
[ownership] Fix the linear lifetime checker to emit leak fixups for certain loops it didn't handle correctly before.
Specifically: bb0: br bb1 bb1: cond_br ..., bb2, bb3 bb2: br bb1 bb3: return What would happen in this case is that we wouldn't revisit bb1 after we found the double consume to grab the leak (and would early exit as well). So we would not insert a destroy for the out of loop value causing a leak from the perspective of the ownership checker. This was due to us early exiting and also due to us not revisiting bb1 after we went around the backedge from bb2 -> bb1. Now what we do instead is if we catch a double consume in a block we have already visited, we check if we haven't visited any of that block's successors. If we haven't, we add that to the list of successors we need to visit.
1 parent 37183a7 commit ac6f318

File tree

4 files changed

+223
-73
lines changed

4 files changed

+223
-73
lines changed

lib/SIL/LinearLifetimeChecker.cpp

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,19 @@ struct State {
9898
SILBasicBlock *userBlock);
9999

100100
/// Once we have marked all of our producing blocks.
101-
bool checkPredsForDoubleConsume(BranchPropagatedUser consumingUser,
101+
void checkPredsForDoubleConsume(BranchPropagatedUser consumingUser,
102102
SILBasicBlock *userBlock);
103-
bool checkPredsForDoubleConsume(SILBasicBlock *userBlock);
103+
void checkPredsForDoubleConsume(SILBasicBlock *userBlock);
104104

105105
/// Once we have setup all of our consuming/non-consuming blocks and have
106106
/// validated that all intra-block dataflow is safe, perform the inter-block
107107
/// dataflow.
108-
bool performDataflow(DeadEndBlocks &deBlocks);
108+
void performDataflow(DeadEndBlocks &deBlocks);
109109

110110
/// After we have performed the dataflow, check the end state of our dataflow
111111
/// for validity. If this is a linear typed value, return true. Return false
112112
/// otherwise.
113-
bool checkDataflowEndState(DeadEndBlocks &deBlocks);
113+
void checkDataflowEndState(DeadEndBlocks &deBlocks);
114114
};
115115

116116
} // end anonymous namespace
@@ -166,8 +166,7 @@ void State::initializeAllNonConsumingUses(
166166

167167
void State::initializeAllConsumingUses(
168168
ArrayRef<BranchPropagatedUser> consumingUses,
169-
SmallVectorImpl<std::pair<BranchPropagatedUser, SILBasicBlock *>>
170-
&predsToAddToWorklist) {
169+
SmallVectorImpl<BrPropUserAndBlockPair> &predsToAddToWorklist) {
171170
for (BranchPropagatedUser user : consumingUses) {
172171
SILBasicBlock *userBlock = user.getParent();
173172

@@ -271,41 +270,58 @@ void State::checkForSameBlockUseAfterFree(BranchPropagatedUser consumingUser,
271270
return;
272271
}
273272

274-
bool State::checkPredsForDoubleConsume(BranchPropagatedUser consumingUser,
273+
void State::checkPredsForDoubleConsume(BranchPropagatedUser consumingUser,
275274
SILBasicBlock *userBlock) {
276275
if (!blocksWithConsumingUses.count(userBlock))
277-
return false;
276+
return;
277+
278+
// Check if this is a block that we have already visited. This means that we
279+
// had a back edge of some sort. Double check that we haven't missed any
280+
// successors.
281+
if (visitedBlocks.count(userBlock)) {
282+
for (auto *succ : userBlock->getSuccessorBlocks()) {
283+
if (!visitedBlocks.count(succ)) {
284+
successorBlocksThatMustBeVisited.insert(succ);
285+
}
286+
}
287+
}
278288

279289
error.handleOverConsume([&] {
280290
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
281291
<< "Found over consume?!\n"
282292
<< "Value: " << *value << "User: " << *consumingUser
283293
<< "Block: bb" << userBlock->getDebugID() << "\n\n";
284294
});
285-
286-
// If we reached this point, then we did not assert, but we did flag an
287-
// error. Return true so we continue the walk.
288-
return true;
289295
}
290296

291-
bool State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
297+
void State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
292298
if (!blocksWithConsumingUses.count(userBlock))
293-
return false;
299+
return;
300+
301+
// Check if this is a block that we have already visited. This means that we
302+
// had a back edge of some sort. Double check that we haven't missed any
303+
// successors.
304+
if (visitedBlocks.count(userBlock)) {
305+
for (auto *succ : userBlock->getSuccessorBlocks()) {
306+
if (!visitedBlocks.count(succ)) {
307+
successorBlocksThatMustBeVisited.insert(succ);
308+
}
309+
}
310+
}
294311

295312
error.handleOverConsume([&] {
296313
llvm::errs() << "Function: '" << value->getFunction()->getName() << "'\n"
297314
<< "Found over consume?!\n"
298315
<< "Value: " << *value << "Block: bb"
299316
<< userBlock->getDebugID() << "\n\n";
300317
});
301-
return true;
302318
}
303319

304320
//===----------------------------------------------------------------------===//
305321
// Dataflow
306322
//===----------------------------------------------------------------------===//
307323

308-
bool State::performDataflow(DeadEndBlocks &deBlocks) {
324+
void State::performDataflow(DeadEndBlocks &deBlocks) {
309325
LLVM_DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n");
310326
// Until the worklist is empty...
311327
while (!worklist.empty()) {
@@ -366,11 +382,7 @@ bool State::performDataflow(DeadEndBlocks &deBlocks) {
366382
// 2. We add the predecessor to the worklist if we have not visited it yet.
367383
for (auto *predBlock : block->getPredecessorBlocks()) {
368384
// Check if we have an over consume.
369-
if (checkPredsForDoubleConsume(predBlock)) {
370-
// If we were to assert, it is handled inside check preds for double
371-
// consume. So just return false so that we bail.
372-
return false;
373-
}
385+
checkPredsForDoubleConsume(predBlock);
374386

375387
if (visitedBlocks.count(predBlock)) {
376388
continue;
@@ -380,15 +392,9 @@ bool State::performDataflow(DeadEndBlocks &deBlocks) {
380392
worklist.push_back(predBlock);
381393
}
382394
}
383-
384-
return true;
385395
}
386396

387-
bool State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
388-
// Make sure that we visited all successor blocks that we needed to visit to
389-
// make sure we didn't leak.
390-
bool doesntHaveAnyLeaks = true;
391-
397+
void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
392398
if (!successorBlocksThatMustBeVisited.empty()) {
393399
// If we are asked to store any leaking blocks, put them in the leaking
394400
// blocks array.
@@ -412,13 +418,12 @@ bool State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
412418

413419
// Otherwise... see if we have any other failures. This signals the user
414420
// wants us to tell it where to insert compensating destroys.
415-
doesntHaveAnyLeaks = false;
416421
}
417422

418423
// Make sure that we do not have any lifetime ending uses left to visit that
419424
// are not transitively unreachable blocks.... so return early.
420425
if (blocksWithNonConsumingUses.empty()) {
421-
return doesntHaveAnyLeaks;
426+
return;
422427
}
423428

424429
// If we do have remaining blocks, then these non lifetime ending uses must be
@@ -439,12 +444,7 @@ bool State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
439444
}
440445
llvm::errs() << "\n";
441446
});
442-
return false;
443447
}
444-
445-
// If all of our remaining blocks were dead uses, then return true. We are
446-
// good.
447-
return doesntHaveAnyLeaks;
448448
}
449449

450450
//===----------------------------------------------------------------------===//
@@ -506,19 +506,17 @@ LinearLifetimeError swift::valueHasLinearLifetime(
506506

507507
// Make sure that the predecessor is not in our blocksWithConsumingUses
508508
// list.
509-
if (state.checkPredsForDoubleConsume(user, predBlock)) {
510-
return state.error;
511-
}
509+
state.checkPredsForDoubleConsume(user, predBlock);
512510

513511
if (!state.visitedBlocks.insert(predBlock).second)
514512
continue;
513+
515514
state.worklist.push_back(predBlock);
516515
}
517516

518517
// Now that our algorithm is completely prepared, run the
519518
// dataflow... If we find a failure, return false.
520-
if (!state.performDataflow(deBlocks))
521-
return state.error;
519+
state.performDataflow(deBlocks);
522520

523521
// ...and then check that the end state shows that we have a valid linear
524522
// typed value.

lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ class AvailableValueAggregator {
425425
/// If as a result of us copying values, we may have unconsumed destroys, find
426426
/// the appropriate location and place the values there. Only used when
427427
/// ownership is enabled.
428-
void addMissingDestroysForCopiedValues(LoadInst *li);
428+
LoadInst *addMissingDestroysForCopiedValues(LoadInst *li, SILValue newVal);
429429

430430
void print(llvm::raw_ostream &os) const;
431431
void dump() const LLVM_ATTRIBUTE_USED;
@@ -746,51 +746,69 @@ SILValue AvailableValueAggregator::handlePrimitiveValue(SILType loadTy,
746746
return eltVal;
747747
}
748748

749-
void AvailableValueAggregator::addMissingDestroysForCopiedValues(LoadInst *li) {
749+
LoadInst *
750+
AvailableValueAggregator::addMissingDestroysForCopiedValues(LoadInst *li,
751+
SILValue newVal) {
750752
// If ownership is not enabled... bail. We do not need to do this since we do
751753
// not need to insert an extra copy unless we have ownership since without
752754
// ownership stores do not consume.
753755
if (!B.hasOwnership())
754-
return;
756+
return li;
755757

756-
SmallVector<BranchPropagatedUser, 1> consumingUses;
757758
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
758759
SmallVector<SILBasicBlock *, 8> leakingBlocks;
760+
bool foundLoop = false;
761+
auto loc = RegularLocation::getAutoGeneratedLocation();
759762
while (!insertedInsts.empty()) {
760763
auto *cvi = dyn_cast<CopyValueInst>(insertedInsts.pop_back_val());
761764
if (!cvi)
762765
continue;
763766

764-
// Clear our worklist.
765-
consumingUses.clear();
767+
// Clear our state.
766768
visitedBlocks.clear();
767769
leakingBlocks.clear();
768-
769770
// The linear lifetime checker doesn't care if the passed in load is
770771
// actually a user of our copy_value. What we care about is that the load is
771772
// guaranteed to be in the block where we have reformed the tuple in a
772773
// consuming manner. This means if we add it as the consuming use of the
773774
// copy, we can find the leaking places if any exist.
774-
consumingUses.push_back(li);
775-
775+
//
776776
// Then perform the linear lifetime check. If we succeed, continue. We have
777777
// no further work to do.
778-
auto errorKind =
779-
ownership::ErrorBehaviorKind::ReturnFalseOnLeakAssertOtherwise;
780-
auto error =
781-
valueHasLinearLifetime(cvi, consumingUses, {}, visitedBlocks,
782-
deadEndBlocks, errorKind, &leakingBlocks);
778+
auto errorKind = ownership::ErrorBehaviorKind::ReturnFalse;
779+
auto error = valueHasLinearLifetime(
780+
cvi, {li}, {}, visitedBlocks, deadEndBlocks, errorKind, &leakingBlocks);
783781
if (!error.getFoundError())
784782
continue;
785783

784+
// Ok, we found some leaking blocks. Since we are using the linear lifetime
785+
// checker with memory, we do not have any guarantees that the store is out
786+
// side of a loop and a load is in a loop. In such a case, we want to
787+
// replace the load with a copy_value.
788+
foundLoop |= error.getFoundOverConsume();
789+
786790
// Ok, we found some leaking blocks. Insert destroys at the
787791
// beginning of these blocks for our copy_value.
788-
auto loc = RegularLocation::getAutoGeneratedLocation();
789792
for (auto *bb : leakingBlocks) {
790793
SILBuilderWithScope b(bb->begin());
791794
b.emitDestroyValueOperation(loc, cvi);
792795
}
793796
}
797+
798+
// If we didn't find a loop, we are done, just return li to get RAUWed.
799+
if (!foundLoop)
800+
return li;
801+
802+
// If we found a loop, then we know that our leaking blocks are the exiting
803+
// blocks of the loop. Thus we need to change the load inst to a copy_value
804+
// instead of deleting it.
805+
newVal = SILBuilderWithScope(li).emitCopyValueOperation(loc, newVal);
806+
li->replaceAllUsesWith(newVal);
807+
SILValue addr = li->getOperand();
808+
li->eraseFromParent();
809+
if (auto *addrI = addr->getDefiningInstruction())
810+
recursivelyDeleteTriviallyDeadInstructions(addrI);
811+
return nullptr;
794812
}
795813

796814
//===----------------------------------------------------------------------===//
@@ -1374,27 +1392,31 @@ bool AllocOptimize::promoteLoad(SILInstruction *Inst) {
13741392
auto *Load = cast<LoadInst>(Inst);
13751393
AvailableValueAggregator Agg(Load, AvailableValues, Uses, deadEndBlocks,
13761394
false /*isTake*/);
1377-
SILValue NewVal = Agg.aggregateValues(LoadTy, Load->getOperand(), FirstElt);
1395+
SILValue newVal = Agg.aggregateValues(LoadTy, Load->getOperand(), FirstElt);
1396+
1397+
LLVM_DEBUG(llvm::dbgs() << " *** Promoting load: " << *Load << "\n");
1398+
LLVM_DEBUG(llvm::dbgs() << " To value: " << *newVal << "\n");
13781399

13791400
// If we inserted any copies, we created the copies at our stores. We know
13801401
// that in our load block, we will reform the aggregate as appropriate at the
13811402
// load implying that the value /must/ be fully consumed. Thus any leaking
13821403
// blocks that we may have can be found by performing a linear lifetime check
13831404
// over all copies that we found using the load as the "consuming uses" (just
13841405
// for the purposes of identifying the consuming block).
1385-
Agg.addMissingDestroysForCopiedValues(Load);
1406+
auto *oldLoad = Agg.addMissingDestroysForCopiedValues(Load, newVal);
13861407

13871408
++NumLoadPromoted;
1388-
1389-
// Simply replace the load.
1390-
LLVM_DEBUG(llvm::dbgs() << " *** Promoting load: " << *Load << "\n");
1391-
LLVM_DEBUG(llvm::dbgs() << " To value: " << *NewVal << "\n");
1392-
1393-
Load->replaceAllUsesWith(NewVal);
1394-
SILValue Addr = Load->getOperand();
1395-
Load->eraseFromParent();
1396-
if (auto *AddrI = Addr->getDefiningInstruction())
1397-
recursivelyDeleteTriviallyDeadInstructions(AddrI);
1409+
1410+
// If we are returned the load, eliminate it. Otherwise, it was already
1411+
// handled for us... so return true.
1412+
if (!oldLoad)
1413+
return true;
1414+
1415+
oldLoad->replaceAllUsesWith(newVal);
1416+
SILValue addr = oldLoad->getOperand();
1417+
oldLoad->eraseFromParent();
1418+
if (auto *addrI = addr->getDefiningInstruction())
1419+
recursivelyDeleteTriviallyDeadInstructions(addrI);
13981420
return true;
13991421
}
14001422

0 commit comments

Comments
 (0)