Skip to content

Commit 962106f

Browse files
committed
[semantic-arc] When computing Lifetimes include all consuming uses, not just the final destroying use.
TLDR: This fixes an ownership verifier assert caused by not placing end_borrows along paths where an enum is provable to have a trivial case. It only happens if all non-trivial cases in a switch_enum are "dead end blocks" where the program will end and we leak objects. The Problem ----------- The actual bug here only occurs in cases where we have a switch_enum on an enum with mixed trivial, non-trivial cases and all of the non-trivial payloaded cases are "dead end blocks". As an example, lets look at a simple switch_enum over an optional where the .some case is a dead end block and we leak the Klass object into program termination: ``` %0 = load [copy] %mem : $Klass switch_enum %0 : $Optional<Klass>, case #Optional.some: bbDeadEnd, case #Optional.none: bbContinue bbDeadEnd(%0a : @owned $Klass): // %0 is leaked into program end! unreachable bbContinue: ... // program continue. ``` In this case, if we were only looking at final destroying uses, we would pass a def without any uses to the ValueLifetimeChecker causing us to not have a frontier at all causing us to not insert any end_borrows, yielding: ``` %0 = load_borrow %mem : $Klass switch_enum %0 : $Optional<Klass>, case #Optional.some: bbDeadEnd, case #Optional.none: bbContinue bbDeadEnd(%0a : @guaranteed $Klass): // %0 is leaked into program end and // doesnt need an end_borrow! unreachable bbContinue: ... // program continue... we need an end_borrow here though! ``` This then trips the ownership verifier since switch_enum is a transforming terminator that acts like a forwarding instruction implying we need an end_borrow on the base value along all non-dead end paths through the program. Importantly this is not actually a leak of a value or unsafe behavior since the only time that we enter into unsafe territory is along paths where the enum was actually trivial. So the load_borrow is actually just loaded the trivial enum value. The Fix ------- In order to work around this, I realized that the right solution is to also include the forwarding consuming uses (in this case the switch_enum use) when determining the lifetime and that this solves the problem. That being said, after I made that change, I noticed that I needed to remove my previous manner of computing the insertion point to use for arguments when finding the lifetime using ValueLifetimeAnalysis. Previously since I was using only the destroying uses I knew that the destroy_value could not be the first instruction in the block of my argument since I handled that case individually before using the ValueLifetimeAnalysis. That invariant is no longer true as can be seen in the case above if %0 was from a SILArgument itself instead of a load [copy] and we were converting that argument to be a guaranteed argument. To fix this, I taught ValueLifetimeAnalysis how to handle defs from Arguments. The key thing is I noticed while reading the code that the analysis only generally cared about the instruction's parent block. Beyond that, the def being from an instruction was only needed to determine if a user is earlier in the same block as the def instruction. Those concerns not apply to SILArgument which dominate all instructions in the same block, so in this patch, we just skip those conditional checks when we have a SILArgument. The rest of the code that uses the parent block is the same for both SILArgument/SILInstructions. rdar://65244617
1 parent 28b2f1e commit 962106f

File tree

4 files changed

+184
-77
lines changed

4 files changed

+184
-77
lines changed

include/swift/SILOptimizer/Utils/ValueLifetime.h

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,38 @@ namespace swift {
2828
/// of the analysis and can be a super set of the uses of the SILValue
2929
/// e.g. it can be the set of transitive uses of the SILValue.
3030
class ValueLifetimeAnalysis {
31+
/// The instruction or argument that define the value.
32+
PointerUnion<SILInstruction *, SILArgument *> defValue;
33+
34+
/// The set of blocks where the value is live.
35+
llvm::SmallSetVector<SILBasicBlock *, 16> liveBlocks;
36+
37+
/// The set of instructions where the value is used, or the users-list
38+
/// provided with the constructor.
39+
llvm::SmallPtrSet<SILInstruction *, 16> userSet;
40+
41+
/// Indicates whether the basic block containing def has users of def that
42+
/// precede def. This field is initialized by propagateLiveness.
43+
bool hasUsersBeforeDef;
44+
45+
/// Critical edges that couldn't be split to compute the frontier. This could
46+
/// be non-empty when the analysis is invoked with DontModifyCFG mode.
47+
llvm::SmallVector<std::pair<TermInst *, unsigned>, 16> criticalEdges;
48+
3149
public:
3250

3351
/// The lifetime frontier for the value. It is the list of instructions
3452
/// following the last uses of the value. All the frontier instructions
3553
/// end the value's lifetime.
3654
using Frontier = SmallVector<SILInstruction *, 4>;
3755

38-
/// Constructor for the value \p Def with a specific range of users.
56+
/// Constructor for the value \p def with a specific range of users.
3957
///
4058
/// We templatize over the RangeTy so that we can initialize
4159
/// ValueLifetimeAnalysis with misc iterators including transform
4260
/// iterators.
4361
template <typename RangeTy>
44-
ValueLifetimeAnalysis(SILInstruction *def, const RangeTy &userRange)
62+
ValueLifetimeAnalysis(decltype(defValue) def, const RangeTy &userRange)
4563
: defValue(def), userSet(userRange.begin(), userRange.end()) {
4664
propagateLiveness();
4765
}
@@ -102,7 +120,7 @@ class ValueLifetimeAnalysis {
102120
/// Returns true if the value is alive at the begin of block \p bb.
103121
bool isAliveAtBeginOfBlock(SILBasicBlock *bb) {
104122
return liveBlocks.count(bb) &&
105-
(bb != defValue->getParent() || hasUsersBeforeDef);
123+
(hasUsersBeforeDef || bb != getDefValueParentBlock());
106124
}
107125

108126
/// Checks if there is a dealloc_ref inside the value's live range.
@@ -112,24 +130,19 @@ class ValueLifetimeAnalysis {
112130
void dump() const;
113131

114132
private:
133+
SILFunction *getFunction() const {
134+
if (auto *inst = defValue.dyn_cast<SILInstruction *>()) {
135+
return inst->getFunction();
136+
}
137+
return defValue.get<SILArgument *>()->getFunction();
138+
}
115139

116-
/// The value.
117-
SILInstruction *defValue;
118-
119-
/// The set of blocks where the value is live.
120-
llvm::SmallSetVector<SILBasicBlock *, 16> liveBlocks;
121-
122-
/// The set of instructions where the value is used, or the users-list
123-
/// provided with the constructor.
124-
llvm::SmallPtrSet<SILInstruction *, 16> userSet;
125-
126-
/// Indicates whether the basic block containing def has users of def that
127-
/// precede def. This field is initialized by propagateLiveness.
128-
bool hasUsersBeforeDef;
129-
130-
/// Critical edges that couldn't be split to compute the frontier. This could
131-
/// be non-empty when the analysis is invoked with DontModifyCFG mode.
132-
llvm::SmallVector<std::pair<TermInst *, unsigned>, 16> criticalEdges;
140+
SILBasicBlock *getDefValueParentBlock() const {
141+
if (auto *inst = defValue.dyn_cast<SILInstruction *>()) {
142+
return inst->getParent();
143+
}
144+
return defValue.get<SILArgument *>()->getParent();
145+
}
133146

134147
/// Propagates the liveness information up the control flow graph.
135148
void propagateLiveness();

lib/SILOptimizer/Transforms/SemanticARCOpts.cpp

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ class OwnershipLiveRange {
245245
TransformRange<ArrayRef<Operand *>, OperandToUser>;
246246
DestroyingInstsRange getDestroyingInsts() const;
247247

248+
using ConsumingInstsRange =
249+
TransformRange<ArrayRef<Operand *>, OperandToUser>;
250+
ConsumingInstsRange getAllConsumingInsts() const;
251+
248252
/// If this LiveRange has a single destroying use, return that use. Otherwise,
249253
/// return nullptr.
250254
Operand *getSingleDestroyingUse() const {
@@ -335,6 +339,11 @@ OwnershipLiveRange::getDestroyingInsts() const {
335339
return DestroyingInstsRange(getDestroyingUses(), OperandToUser());
336340
}
337341

342+
OwnershipLiveRange::ConsumingInstsRange
343+
OwnershipLiveRange::getAllConsumingInsts() const {
344+
return ConsumingInstsRange(consumingUses, OperandToUser());
345+
}
346+
338347
OwnershipLiveRange::OwnershipLiveRange(SILValue value)
339348
: introducer(*OwnedValueIntroducer::get(value)), destroyingUses(),
340349
ownershipForwardingUses(), unknownConsumingUses() {
@@ -464,36 +473,17 @@ void OwnershipLiveRange::insertEndBorrowsAtDestroys(
464473
//
465474
// TODO: Hoist this out?
466475
SILInstruction *inst = introducer.value->getDefiningInstruction();
476+
Optional<ValueLifetimeAnalysis> analysis;
467477
if (!inst) {
468-
// If our introducer was not for an inst, it should be from an arg. In such
469-
// a case, we handle one of two cases:
470-
//
471-
// 1. If we have one destroy and that destroy is the initial instruction in
472-
// the arguments block, we just insert the end_borrow here before the
473-
// destroy_value and bail. If the destroy is not the initial instruction in
474-
// the arg block, we delegate to the ValueLifetimeAnalysis code.
475-
//
476-
// 2. If we have multiple destroys, by the properties of owned values having
477-
// a linear lifetime, we know that the destroys can not both be first in the
478-
// args block since the only way that we could have two such destroys in the
479-
// arg's block is if we destructured the arg. In such a case, the
480-
// destructure instruction would have to be between the argument and any
481-
// destroy meaning the destroys could not be first. In such a case, we
482-
// delegate to the ValueLifetimeAnalysis code.
483-
auto *arg = cast<SILArgument>(introducer.value);
484-
auto *beginInst = &*arg->getParent()->begin();
485-
if (auto *singleDestroyingUse = getSingleDestroyingUse()) {
486-
if (singleDestroyingUse->getUser() == beginInst) {
487-
auto loc = RegularLocation::getAutoGeneratedLocation();
488-
SILBuilderWithScope builder(beginInst);
489-
builder.createEndBorrow(loc, newGuaranteedValue);
490-
return;
491-
}
492-
}
493-
inst = beginInst;
478+
analysis.emplace(cast<SILArgument>(introducer.value),
479+
getAllConsumingInsts());
480+
} else {
481+
analysis.emplace(inst, getAllConsumingInsts());
494482
}
495-
ValueLifetimeAnalysis analysis(inst, getDestroyingInsts());
496-
bool foundCriticalEdges = !analysis.computeFrontier(
483+
484+
// Use all consuming uses in our value lifetime analysis to ensure correctness
485+
// in the face of unreachable code.
486+
bool foundCriticalEdges = !analysis->computeFrontier(
497487
scratch, ValueLifetimeAnalysis::DontModifyCFG, &deadEndBlocks);
498488
(void)foundCriticalEdges;
499489
assert(!foundCriticalEdges);

lib/SILOptimizer/Utils/ValueLifetime.cpp

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
14+
#include "swift/Basic/STLExtras.h"
1415
#include "swift/SIL/BasicBlockUtils.h"
1516
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
1617

1718
using namespace swift;
1819

1920
void ValueLifetimeAnalysis::propagateLiveness() {
21+
bool defIsInstruction = defValue.is<SILInstruction *>();
2022
assert(liveBlocks.empty() && "frontier computed twice");
21-
assert(!userSet.count(defValue) && "definition cannot be its own use");
23+
assert(
24+
(!defIsInstruction || !userSet.count(defValue.get<SILInstruction *>())) &&
25+
"definition cannot be its own use");
2226

23-
auto defBB = defValue->getParentBlock();
24-
llvm::SmallVector<SILBasicBlock *, 64> worklist;
27+
// Compute the def block only if we have a SILInstruction. If we have a
28+
// SILArgument, this will be nullptr.
29+
auto *defBB = getDefValueParentBlock();
30+
SmallVector<SILBasicBlock *, 64> worklist;
2531
int numUsersBeforeDef = 0;
2632

2733
// Find the initial set of blocks where the value is live, because
@@ -31,20 +37,28 @@ void ValueLifetimeAnalysis::propagateLiveness() {
3137
if (liveBlocks.insert(userBlock))
3238
worklist.push_back(userBlock);
3339

34-
// A user in the defBB could potentially be located before the defValue.
35-
if (userBlock == defBB)
40+
// A user in the defBB could potentially be located before the defValue. If
41+
// we had a SILArgument, defBB will be nullptr, so we should always have
42+
// numUsersBeforeDef is 0. We assert this at the end of the loop.
43+
if (defIsInstruction && userBlock == defBB)
3644
++numUsersBeforeDef;
3745
}
38-
// Don't count any users in the defBB which are actually located _after_
39-
// the defValue.
40-
auto instIter = defValue->getIterator();
41-
while (numUsersBeforeDef > 0 && ++instIter != defBB->end()) {
42-
if (userSet.count(&*instIter))
43-
--numUsersBeforeDef;
46+
assert((defValue.is<SILInstruction *>() || (numUsersBeforeDef == 0)) &&
47+
"Non SILInstruction defValue with users before the def?!");
48+
49+
// Don't count any users in the defBB which are actually located _after_ the
50+
// defValue.
51+
if (defIsInstruction) {
52+
auto instIter = defValue.get<SILInstruction *>()->getIterator();
53+
while (numUsersBeforeDef > 0 && ++instIter != defBB->end()) {
54+
if (userSet.count(&*instIter))
55+
--numUsersBeforeDef;
56+
}
4457
}
4558

4659
// Initialize the hasUsersBeforeDef field.
4760
hasUsersBeforeDef = numUsersBeforeDef > 0;
61+
assert(defIsInstruction || !hasUsersBeforeDef);
4862

4963
// Now propagate liveness backwards until we hit the block that defines the
5064
// value.
@@ -55,30 +69,31 @@ void ValueLifetimeAnalysis::propagateLiveness() {
5569
if (bb == defBB && !hasUsersBeforeDef)
5670
continue;
5771

58-
for (SILBasicBlock *Pred : bb->getPredecessorBlocks()) {
72+
for (auto *predBB : bb->getPredecessorBlocks()) {
5973
// If it's already in the set, then we've already queued and/or
6074
// processed the predecessors.
61-
if (liveBlocks.insert(Pred))
62-
worklist.push_back(Pred);
75+
if (liveBlocks.insert(predBB))
76+
worklist.push_back(predBB);
6377
}
6478
}
6579
}
6680

6781
SILInstruction *ValueLifetimeAnalysis::findLastUserInBlock(SILBasicBlock *bb) {
6882
// Walk backwards in bb looking for last use of the value.
69-
for (auto ii = bb->rbegin(); ii != bb->rend(); ++ii) {
70-
assert(defValue != &*ii && "Found def before finding use!");
83+
for (auto &inst : llvm::reverse(*bb)) {
84+
assert(defValue.dyn_cast<SILInstruction *>() != &inst &&
85+
"Found def before finding use!");
7186

72-
if (userSet.count(&*ii))
73-
return &*ii;
87+
if (userSet.count(&inst))
88+
return &inst;
7489
}
7590
llvm_unreachable("Expected to find use of value in block!");
7691
}
7792

7893
bool ValueLifetimeAnalysis::computeFrontier(Frontier &frontier, Mode mode,
7994
DeadEndBlocks *deBlocks) {
80-
assert(!isAliveAtBeginOfBlock(defValue->getFunction()->getEntryBlock())
81-
&& "Can't compute frontier for def which does not dominate all uses");
95+
assert(!isAliveAtBeginOfBlock(getFunction()->getEntryBlock()) &&
96+
"Can't compute frontier for def which does not dominate all uses");
8297

8398
bool noCriticalEdges = true;
8499

@@ -101,10 +116,16 @@ bool ValueLifetimeAnalysis::computeFrontier(Frontier &frontier, Mode mode,
101116
for (const SILSuccessor &succ : bb->getSuccessors()) {
102117
if (isAliveAtBeginOfBlock(succ)) {
103118
liveInSucc = true;
104-
if (succ == defValue->getParent()) {
119+
if (succ == getDefValueParentBlock()) {
105120
// Here, the basic block bb uses the value but also redefines the
106121
// value inside bb. The new value could be used by the successors
107122
// of succ and therefore could be live at the end of succ as well.
123+
//
124+
// This should never happen if we have a SILArgument since the
125+
// SILArgument can not have any uses before it in a block.
126+
assert(defValue.is<SILInstruction *>() &&
127+
"SILArguments dominate all instructions in their defining "
128+
"blocks");
108129
usedAndRedefinedInSucc = true;
109130
}
110131
} else if (!deBlocks || !deBlocks->isDeadEnd(succ)) {
@@ -115,7 +136,10 @@ bool ValueLifetimeAnalysis::computeFrontier(Frontier &frontier, Mode mode,
115136
// Here, the basic block bb uses the value and later redefines the value.
116137
// Therefore, this value's lifetime ends after its last use preceding the
117138
// re-definition of the value.
118-
auto ii = defValue->getReverseIterator();
139+
//
140+
// We know that we can not have a SILArgument here since the SILArgument
141+
// dominates all instructions in the same block.
142+
auto ii = defValue.get<SILInstruction *>()->getReverseIterator();
119143
for (; ii != bb->rend(); ++ii) {
120144
if (userSet.count(&*ii)) {
121145
frontier.push_back(&*std::next(ii));
@@ -245,15 +269,19 @@ bool ValueLifetimeAnalysis::isWithinLifetime(SILInstruction *inst) {
245269
// Searches \p bb backwards from the instruction before \p frontierInst
246270
// to the beginning of the list and returns true if we find a dealloc_ref
247271
// /before/ we find \p defValue (the instruction that defines our target value).
248-
static bool blockContainsDeallocRef(SILBasicBlock *bb, SILInstruction *defValue,
249-
SILInstruction *frontierInst) {
272+
static bool
273+
blockContainsDeallocRef(SILBasicBlock *bb,
274+
PointerUnion<SILInstruction *, SILArgument *> defValue,
275+
SILInstruction *frontierInst) {
250276
SILBasicBlock::reverse_iterator End = bb->rend();
251277
SILBasicBlock::reverse_iterator iter = frontierInst->getReverseIterator();
252278
for (++iter; iter != End; ++iter) {
253279
SILInstruction *inst = &*iter;
254280
if (isa<DeallocRefInst>(inst))
255281
return true;
256-
if (inst == defValue)
282+
// We know that inst is not a nullptr, so if we have a SILArgument, this
283+
// will always fail as we want.
284+
if (inst == defValue.dyn_cast<SILInstruction *>())
257285
return false;
258286
}
259287
return false;
@@ -281,9 +309,14 @@ bool ValueLifetimeAnalysis::containsDeallocRef(const Frontier &frontier) {
281309
}
282310

283311
void ValueLifetimeAnalysis::dump() const {
284-
llvm::errs() << "lifetime of def: " << *defValue;
285-
for (SILInstruction *Use : userSet) {
286-
llvm::errs() << " use: " << *Use;
312+
llvm::errs() << "lifetime of def: ";
313+
if (auto *ii = defValue.dyn_cast<SILInstruction *>()) {
314+
llvm::errs() << *ii;
315+
} else {
316+
llvm::errs() << *defValue.get<SILArgument *>();
317+
}
318+
for (SILInstruction *use : userSet) {
319+
llvm::errs() << " use: " << *use;
287320
}
288321
llvm::errs() << " live blocks:";
289322
for (SILBasicBlock *bb : liveBlocks) {

0 commit comments

Comments
 (0)