Skip to content

Commit df86271

Browse files
authored
Merge pull request #36798 from eeckstein/fix-diagnose-weak
DiagnoseLifetimeIssues: handle called functions.
2 parents eeb0ea2 + 99eb0f3 commit df86271

File tree

2 files changed

+219
-105
lines changed

2 files changed

+219
-105
lines changed

lib/SILOptimizer/Mandatory/DiagnoseLifetimeIssues.cpp

Lines changed: 186 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "swift/SILOptimizer/PassManager/Transforms.h"
3737
#include "swift/SILOptimizer/Utils/PrunedLiveness.h"
3838
#include "swift/Demangling/Demangler.h"
39+
#include "llvm/ADT/DenseMap.h"
3940
#include "llvm/Support/Debug.h"
4041
#include "clang/AST/DeclObjC.h"
4142

@@ -45,14 +46,41 @@ namespace {
4546

4647
/// Performs the analysis and prints the warnings.
4748
class DiagnoseLifetimeIssues {
49+
enum State {
50+
/// There are no hidden uses which could keep the object alive.
51+
DoesNotEscape,
52+
53+
/// For example, in case the object is stored somewhere.
54+
CanEscape,
55+
56+
/// The object is stored to a weak reference field.
57+
/// Implies ``DoesNotEscape``.
58+
IsStoredWeakly
59+
};
60+
61+
/// To avoid quadratic complexity in the rare corener case of very deep
62+
/// callgraphs, with pass down references.
63+
static constexpr int maxCallDepth = 8;
64+
65+
/// The liveness of the object in question, computed in visitUses.
4866
PrunedLiveness liveness;
4967

50-
/// Reuse a general worklist for def-use traversal.
51-
SmallSetVector<SILValue, 8> defUseWorklist;
68+
/// All weak stores of the object, which are found in visitUses.
69+
llvm::SmallVector<SILInstruction *, 8> weakStores;
5270

53-
bool computeCanonicalLiveness(SILValue def);
71+
/// A cache for function argument states of called functions.
72+
///
73+
/// We could also cache this information in an Analysis, so that it persists
74+
/// over runs of this pass for different functions. But computing the state
75+
/// is very cheap and we avoid worst case scenarios with maxCallDepth. So it's
76+
/// propably not worth doing it.
77+
llvm::DenseMap<SILFunctionArgument *, State> argumentStates;
5478

55-
void reportDeadStore(SILInstruction *storeInst, SILValue src);
79+
State visitUses(SILValue def, bool updateLivenessAndWeakStores, int callDepth);
80+
81+
State getArgumentState(ApplySite ai, Operand *applyOperand, int callDepth);
82+
83+
void reportDeadStore(SILInstruction *allocationInst);
5684

5785
public:
5886
DiagnoseLifetimeIssues() {}
@@ -61,15 +89,19 @@ class DiagnoseLifetimeIssues {
6189
};
6290

6391
/// Returns true if def is an owned value resulting from an object allocation.
64-
static bool isAllocation(SILValue def) {
65-
if (def.getOwnershipKind() != OwnershipKind::Owned)
92+
static bool isAllocation(SILInstruction *inst) {
93+
auto *svi = dyn_cast<SingleValueInstruction>(inst);
94+
if (!svi)
6695
return false;
6796

68-
if (isa<AllocRefInst>(def))
97+
if (svi->getOwnershipKind() != OwnershipKind::Owned)
98+
return false;
99+
100+
if (isa<AllocRefInst>(svi))
69101
return true;
70102

71103
// Check if it's a call to an allocating initializer.
72-
if (auto *applyInst = dyn_cast<ApplyInst>(def)) {
104+
if (auto *applyInst = dyn_cast<ApplyInst>(svi)) {
73105
SILFunction *callee = applyInst->getReferencedFunctionOrNull();
74106
if (!callee)
75107
return false;
@@ -83,25 +115,82 @@ static bool isAllocation(SILValue def) {
83115
return false;
84116
}
85117

86-
/// Computes the canoncial lifetime of \p def, like the copy-propagation pass
87-
/// would do.
88-
/// The only difference is that we are treating enum instructions (taking a
89-
/// payload) like copies. Enums are important because the operand of a
90-
/// store_weak is always an Optional.
91-
bool DiagnoseLifetimeIssues::computeCanonicalLiveness(SILValue def) {
92-
defUseWorklist.clear();
118+
/// Returns true if \p inst is a call of an ObjC setter to a weak property.
119+
static bool isStoreObjcWeak(SILInstruction *inst, Operand *op) {
120+
auto *apply = dyn_cast<ApplyInst>(inst);
121+
if (!apply || apply->getNumArguments() < 1)
122+
return false;
123+
124+
if (&apply->getArgumentOperands()[0] != op)
125+
return false;
126+
127+
auto *method = dyn_cast<ObjCMethodInst>(apply->getCallee());
128+
if (!method)
129+
return false;
130+
131+
Decl *decl = method->getMember().getDecl();
132+
auto *accessor = dyn_cast<AccessorDecl>(decl);
133+
if (!accessor)
134+
return false;
135+
136+
auto *var = dyn_cast<VarDecl>(accessor->getStorage());
137+
if (!var)
138+
return false;
139+
140+
ClangNode clangNode = var->getClangNode();
141+
if (!clangNode)
142+
return false;
143+
144+
auto *objcDecl = dyn_cast_or_null<clang::ObjCPropertyDecl>(clangNode.getAsDecl());
145+
if (!objcDecl)
146+
return false;
147+
148+
return objcDecl->getSetterKind() == clang::ObjCPropertyDecl::Weak;
149+
}
150+
151+
/// Transitively iterates over all uses of \p def and and - if \p
152+
/// updateLivenessAndWeakStores is true - adds them to self.liveness.
153+
/// If any weak stores are seen, add them to self.weakStores (also only if
154+
/// \p updateLivenessAndWeakStores is true).
155+
///
156+
/// Returns the state of \p def. See DiagnoseLifetimeIssues::State.
157+
DiagnoseLifetimeIssues::State DiagnoseLifetimeIssues::
158+
visitUses(SILValue def, bool updateLivenessAndWeakStores, int callDepth) {
159+
SmallSetVector<SILValue, 32> defUseWorklist;
93160
defUseWorklist.insert(def);
161+
bool foundWeakStore = false;
94162
while (!defUseWorklist.empty()) {
95163
SILValue value = defUseWorklist.pop_back_val();
96164
for (Operand *use : value->getUses()) {
97165
auto *user = use->getUser();
98166

99-
// Recurse through copies and enums.
167+
// Recurse through copies and enums. Enums are important because the
168+
// operand of a store_weak is always an Optional.
100169
if (isa<CopyValueInst>(user) || isa<EnumInst>(user) ||
101170
isa<InitExistentialRefInst>(user)) {
102171
defUseWorklist.insert(cast<SingleValueInstruction>(user));
103172
continue;
104173
}
174+
if (isa<StoreWeakInst>(user) || isStoreObjcWeak(user, use)) {
175+
if (updateLivenessAndWeakStores)
176+
weakStores.push_back(user);
177+
foundWeakStore = true;
178+
continue;
179+
}
180+
if (ApplySite ai = ApplySite::isa(user)) {
181+
// Try to get information from the called function.
182+
switch (getArgumentState(ai, use, callDepth)) {
183+
case DoesNotEscape:
184+
break;
185+
case CanEscape:
186+
return CanEscape;
187+
case IsStoredWeakly:
188+
if (updateLivenessAndWeakStores)
189+
weakStores.push_back(user);
190+
foundWeakStore = true;
191+
}
192+
continue;
193+
}
105194
switch (use->getOperandOwnership()) {
106195
case OperandOwnership::NonUse:
107196
break;
@@ -112,77 +201,74 @@ bool DiagnoseLifetimeIssues::computeCanonicalLiveness(SILValue def) {
112201
// escape. Is it legal to canonicalize ForwardingUnowned?
113202
case OperandOwnership::ForwardingUnowned:
114203
case OperandOwnership::PointerEscape:
115-
return false;
204+
return CanEscape;
116205
case OperandOwnership::InstantaneousUse:
117206
case OperandOwnership::UnownedInstantaneousUse:
118207
case OperandOwnership::BitwiseEscape:
119-
liveness.updateForUse(user, /*lifetimeEnding*/ false);
208+
if (updateLivenessAndWeakStores)
209+
liveness.updateForUse(user, /*lifetimeEnding*/ false);
120210
break;
121211
case OperandOwnership::ForwardingConsume:
122212
// TODO: handle forwarding instructions, e.g. casts.
123-
return false;
213+
return CanEscape;
124214
case OperandOwnership::DestroyingConsume:
125215
// destroy_value does not force pruned liveness (but store etc. does).
126216
if (!isa<DestroyValueInst>(user))
127-
return false;
217+
return CanEscape;
128218
break;
129219
case OperandOwnership::Borrow:
130-
if (!liveness.updateForBorrowingOperand(use))
131-
return false;
220+
if (updateLivenessAndWeakStores &&
221+
!liveness.updateForBorrowingOperand(use))
222+
return CanEscape;
132223
break;
133224
case OperandOwnership::InteriorPointer:
134225
case OperandOwnership::ForwardingBorrow:
135226
case OperandOwnership::EndBorrow:
136227
case OperandOwnership::Reborrow:
137-
llvm_unreachable("operand kind cannot take an owned value");
228+
return CanEscape;
138229
}
139230
}
140231
}
141-
return true;
232+
return foundWeakStore ? IsStoredWeakly : DoesNotEscape;
142233
}
143234

144-
/// Gets the underlying definition of \p val, looking through copy_value and
145-
/// enum instructions.
146-
static SILValue getCanonicalDef(SILValue val) {
147-
while (true) {
148-
if (auto *copyInst = dyn_cast<CopyValueInst>(val)) {
149-
SILValue copySrc = copyInst->getOperand();
150-
if (copySrc.getOwnershipKind() != OwnershipKind::Owned)
151-
return val;
152-
val = copySrc;
153-
continue;
154-
}
155-
if (auto *enumInst = dyn_cast<EnumInst>(val)) {
156-
if (enumInst->hasOperand()) {
157-
val = enumInst->getOperand();
158-
continue;
159-
}
160-
}
161-
if (auto *initExRef = dyn_cast<InitExistentialRefInst>(val)) {
162-
val = initExRef->getOperand();
163-
continue;
164-
}
165-
return val;
166-
}
167-
}
235+
/// Visits uses of an apply argument in the called function.
236+
DiagnoseLifetimeIssues::State DiagnoseLifetimeIssues::
237+
getArgumentState(ApplySite ai, Operand *applyOperand, int callDepth) {
238+
if (callDepth >= maxCallDepth)
239+
return CanEscape;
168240

169-
/// Reports a warning if the stored object \p storedObj is never loaded within
170-
/// the lifetime of the stored object.
171-
void DiagnoseLifetimeIssues::reportDeadStore(SILInstruction *storeInst,
172-
SILValue storedObj) {
173-
SILValue storedDef = getCanonicalDef(storedObj);
241+
if (!FullApplySite::isa(ai.getInstruction()))
242+
return CanEscape;
174243

175-
// Only for allocations we know that a destroy will actually deallocate the
176-
// object. Otherwise the object could be kept alive by other references and
177-
// we would issue a false alarm.
178-
if (!isAllocation(storedDef))
179-
return;
244+
SILFunction *callee = ai.getReferencedFunctionOrNull();
245+
if (!callee || callee->empty())
246+
return CanEscape;
247+
248+
if (!ai.isArgumentOperand(*applyOperand))
249+
return CanEscape;
180250

181-
liveness.clear();
182-
liveness.initializeDefBlock(storedDef->getParentBlock());
183-
if (!computeCanonicalLiveness(storedDef))
184-
return;
251+
SILBasicBlock *entryBlock = callee->getEntryBlock();
252+
unsigned calleeIdx = ai.getCalleeArgIndex(*applyOperand);
253+
auto *arg = cast<SILFunctionArgument>(entryBlock->getArgument(calleeIdx));
185254

255+
// Check if we already cached the analysis result.
256+
auto iter = argumentStates.find(arg);
257+
if (iter != argumentStates.end())
258+
return iter->second;
259+
260+
// Before we continue with the recursion, already set a (conservative) state.
261+
// This avoids infinite recursion in case of a cycle in the callgraph.
262+
argumentStates[arg] = CanEscape;
263+
264+
State argState = visitUses(arg, /*updateLivenessAndWeakStores*/ false,
265+
callDepth + 1);
266+
argumentStates[arg] = argState;
267+
return argState;
268+
}
269+
270+
/// Returns true if \p inst is outside the the pruned \p liveness.
271+
static bool isOutOfLifetime(SILInstruction *inst, PrunedLiveness &liveness) {
186272
// Check if the lifetime of the stored object ends at the store_weak.
187273
//
188274
// A more sophisticated analysis would be to check if there are no
@@ -193,70 +279,65 @@ void DiagnoseLifetimeIssues::reportDeadStore(SILInstruction *storeInst,
193279
// always see a potential load if the lifetime of the object goes beyond the
194280
// store_weak.
195281

196-
SILBasicBlock *storeBlock = storeInst->getParent();
197-
if (liveness.getBlockLiveness(storeBlock) != PrunedLiveBlocks::LiveWithin)
198-
return;
282+
SILBasicBlock *block = inst->getParent();
283+
if (liveness.getBlockLiveness(block) != PrunedLiveBlocks::LiveWithin)
284+
return false;
199285

200286
// If there are any uses after the store_weak, it means that the liferange of
201287
// the object goes beyond the store_weak.
202-
for (SILInstruction &inst : make_range(std::next(storeInst->getIterator()),
203-
storeBlock->end())) {
288+
for (SILInstruction &inst : make_range(std::next(inst->getIterator()),
289+
block->end())) {
204290
switch (liveness.isInterestingUser(&inst)) {
205291
case PrunedLiveness::NonUser:
206292
break;
207293
case PrunedLiveness::NonLifetimeEndingUse:
208294
case PrunedLiveness::LifetimeEndingUse:
209-
return;
295+
return false;
210296
}
211297
}
212-
213-
// Issue the warning.
214-
storeInst->getModule().getASTContext().Diags.diagnose(
215-
storeInst->getLoc().getSourceLoc(), diag::warn_dead_weak_store);
298+
return true;
216299
}
217300

218-
/// Returns true if \p inst is a call of an ObjC setter to a weak property.
219-
static bool isStoreObjcWeak(SILInstruction *inst) {
220-
auto *apply = dyn_cast<ApplyInst>(inst);
221-
if (!apply || apply->getNumArguments() < 1)
222-
return false;
223-
224-
auto *method = dyn_cast<ObjCMethodInst>(apply->getCallee());
225-
if (!method)
226-
return false;
227-
228-
Decl *decl = method->getMember().getDecl();
229-
auto *accessor = dyn_cast<AccessorDecl>(decl);
230-
if (!accessor)
231-
return false;
301+
/// Reports a warning if the stored object \p storedObj is never loaded within
302+
/// the lifetime of the stored object.
303+
void DiagnoseLifetimeIssues::reportDeadStore(SILInstruction *allocationInst) {
304+
liveness.clear();
305+
weakStores.clear();
232306

233-
auto *var = dyn_cast<VarDecl>(accessor->getStorage());
234-
if (!var)
235-
return false;
307+
SILValue storedDef = cast<SingleValueInstruction>(allocationInst);
308+
liveness.initializeDefBlock(storedDef->getParentBlock());
236309

237-
ClangNode clangNode = var->getClangNode();
238-
if (!clangNode)
239-
return false;
240-
241-
auto *objcDecl = dyn_cast_or_null<clang::ObjCPropertyDecl>(clangNode.getAsDecl());
242-
if (!objcDecl)
243-
return false;
310+
// Compute the canoncial lifetime of storedDef, like the copy-propagation pass
311+
// would do.
312+
State state = visitUses(storedDef, /*updateLivenessAndWeakStores*/ true,
313+
/*callDepth*/ 0);
244314

245-
return objcDecl->getSetterKind() == clang::ObjCPropertyDecl::Weak;
315+
// If the allocation escapes (e.g. it is stored somewhere), we should not
316+
// give a warning, becuase it can be a false alarm. The allocation could be
317+
// kept alive by references we don't see.
318+
if (state == CanEscape)
319+
return;
320+
321+
assert((state == IsStoredWeakly) == !weakStores.empty());
322+
323+
for (SILInstruction *storeInst : weakStores) {
324+
if (isOutOfLifetime(storeInst, liveness)) {
325+
// Issue the warning.
326+
storeInst->getModule().getASTContext().Diags.diagnose(
327+
storeInst->getLoc().getSourceLoc(), diag::warn_dead_weak_store);
328+
}
329+
}
246330
}
247331

248332
/// Prints warnings for dead weak stores in \p function.
249333
void DiagnoseLifetimeIssues::diagnose(SILFunction *function) {
250334
for (SILBasicBlock &block : *function) {
251335
for (SILInstruction &inst : block) {
252-
if (auto *stWeak = dyn_cast<StoreWeakInst>(&inst)) {
253-
reportDeadStore(stWeak, stWeak->getSrc());
254-
continue;
255-
}
256-
if (isStoreObjcWeak(&inst)) {
257-
reportDeadStore(&inst, cast<ApplyInst>(&inst)->getArgument(0));
258-
continue;
259-
}
336+
// Only for allocations we know that a destroy will actually deallocate
337+
// the object. Otherwise the object could be kept alive by other
338+
// references and we would issue a false alarm.
339+
if (isAllocation(&inst))
340+
reportDeadStore(&inst);
260341
}
261342
}
262343
}

0 commit comments

Comments
 (0)