36
36
#include " swift/SILOptimizer/PassManager/Transforms.h"
37
37
#include " swift/SILOptimizer/Utils/PrunedLiveness.h"
38
38
#include " swift/Demangling/Demangler.h"
39
+ #include " llvm/ADT/DenseMap.h"
39
40
#include " llvm/Support/Debug.h"
40
41
#include " clang/AST/DeclObjC.h"
41
42
@@ -45,14 +46,41 @@ namespace {
45
46
46
47
// / Performs the analysis and prints the warnings.
47
48
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.
48
66
PrunedLiveness liveness;
49
67
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 ;
52
70
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;
54
78
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);
56
84
57
85
public:
58
86
DiagnoseLifetimeIssues () {}
@@ -61,15 +89,19 @@ class DiagnoseLifetimeIssues {
61
89
};
62
90
63
91
// / 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)
66
95
return false ;
67
96
68
- if (isa<AllocRefInst>(def))
97
+ if (svi->getOwnershipKind () != OwnershipKind::Owned)
98
+ return false ;
99
+
100
+ if (isa<AllocRefInst>(svi))
69
101
return true ;
70
102
71
103
// 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 )) {
73
105
SILFunction *callee = applyInst->getReferencedFunctionOrNull ();
74
106
if (!callee)
75
107
return false ;
@@ -83,25 +115,82 @@ static bool isAllocation(SILValue def) {
83
115
return false ;
84
116
}
85
117
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;
93
160
defUseWorklist.insert (def);
161
+ bool foundWeakStore = false ;
94
162
while (!defUseWorklist.empty ()) {
95
163
SILValue value = defUseWorklist.pop_back_val ();
96
164
for (Operand *use : value->getUses ()) {
97
165
auto *user = use->getUser ();
98
166
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.
100
169
if (isa<CopyValueInst>(user) || isa<EnumInst>(user) ||
101
170
isa<InitExistentialRefInst>(user)) {
102
171
defUseWorklist.insert (cast<SingleValueInstruction>(user));
103
172
continue ;
104
173
}
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
+ }
105
194
switch (use->getOperandOwnership ()) {
106
195
case OperandOwnership::NonUse:
107
196
break ;
@@ -112,77 +201,74 @@ bool DiagnoseLifetimeIssues::computeCanonicalLiveness(SILValue def) {
112
201
// escape. Is it legal to canonicalize ForwardingUnowned?
113
202
case OperandOwnership::ForwardingUnowned:
114
203
case OperandOwnership::PointerEscape:
115
- return false ;
204
+ return CanEscape ;
116
205
case OperandOwnership::InstantaneousUse:
117
206
case OperandOwnership::UnownedInstantaneousUse:
118
207
case OperandOwnership::BitwiseEscape:
119
- liveness.updateForUse (user, /* lifetimeEnding*/ false );
208
+ if (updateLivenessAndWeakStores)
209
+ liveness.updateForUse (user, /* lifetimeEnding*/ false );
120
210
break ;
121
211
case OperandOwnership::ForwardingConsume:
122
212
// TODO: handle forwarding instructions, e.g. casts.
123
- return false ;
213
+ return CanEscape ;
124
214
case OperandOwnership::DestroyingConsume:
125
215
// destroy_value does not force pruned liveness (but store etc. does).
126
216
if (!isa<DestroyValueInst>(user))
127
- return false ;
217
+ return CanEscape ;
128
218
break ;
129
219
case OperandOwnership::Borrow:
130
- if (!liveness.updateForBorrowingOperand (use))
131
- return false ;
220
+ if (updateLivenessAndWeakStores &&
221
+ !liveness.updateForBorrowingOperand (use))
222
+ return CanEscape;
132
223
break ;
133
224
case OperandOwnership::InteriorPointer:
134
225
case OperandOwnership::ForwardingBorrow:
135
226
case OperandOwnership::EndBorrow:
136
227
case OperandOwnership::Reborrow:
137
- llvm_unreachable ( " operand kind cannot take an owned value " ) ;
228
+ return CanEscape ;
138
229
}
139
230
}
140
231
}
141
- return true ;
232
+ return foundWeakStore ? IsStoredWeakly : DoesNotEscape ;
142
233
}
143
234
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;
168
240
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;
174
243
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;
180
250
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));
185
254
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) {
186
272
// Check if the lifetime of the stored object ends at the store_weak.
187
273
//
188
274
// A more sophisticated analysis would be to check if there are no
@@ -193,70 +279,65 @@ void DiagnoseLifetimeIssues::reportDeadStore(SILInstruction *storeInst,
193
279
// always see a potential load if the lifetime of the object goes beyond the
194
280
// store_weak.
195
281
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 ;
199
285
200
286
// If there are any uses after the store_weak, it means that the liferange of
201
287
// 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 ())) {
204
290
switch (liveness.isInterestingUser (&inst)) {
205
291
case PrunedLiveness::NonUser:
206
292
break ;
207
293
case PrunedLiveness::NonLifetimeEndingUse:
208
294
case PrunedLiveness::LifetimeEndingUse:
209
- return ;
295
+ return false ;
210
296
}
211
297
}
212
-
213
- // Issue the warning.
214
- storeInst->getModule ().getASTContext ().Diags .diagnose (
215
- storeInst->getLoc ().getSourceLoc (), diag::warn_dead_weak_store);
298
+ return true ;
216
299
}
217
300
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 ();
232
306
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 ());
236
309
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 );
244
314
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
+ }
246
330
}
247
331
248
332
// / Prints warnings for dead weak stores in \p function.
249
333
void DiagnoseLifetimeIssues::diagnose (SILFunction *function) {
250
334
for (SILBasicBlock &block : *function) {
251
335
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);
260
341
}
261
342
}
262
343
}
0 commit comments