@@ -53,17 +53,46 @@ class PerformanceDiagnostics {
53
53
PerformanceDiagnostics (SILModule &module , BasicCalleeAnalysis *bca) :
54
54
module (module ), bca(bca) {}
55
55
56
+ // / Check a function with performance annotation(s) and all called functions
57
+ // / recursively.
56
58
bool visitFunction (SILFunction *function, PerformanceConstraints perfConstr) {
57
59
return visitFunction (function, perfConstr, /* parentLoc*/ nullptr );
58
60
}
59
61
62
+ // / Check functions _without_ performance annotations.
63
+ // /
64
+ // / This is need to check closure arguments of called performance-annotated
65
+ // / functions.
66
+ void checkNonAnnotatedFunction (SILFunction *function);
67
+
60
68
private:
61
69
bool visitFunction (SILFunction *function, PerformanceConstraints perfConstr,
62
70
LocWithParent *parentLoc);
63
71
64
72
bool visitInst (SILInstruction *inst, PerformanceConstraints perfConstr,
65
73
LocWithParent *parentLoc);
66
74
75
+ // / Check if `as` has any non-escaping closure arguments and if all those
76
+ // / passed closures meet the `perfConstr`.
77
+ // /
78
+ // / If `acceptFunctionArgs` is true it is assumed that calls to the function,
79
+ // / which contains `as`, are already checked to have correct closure arguments.
80
+ bool checkClosureArguments (ApplySite as,
81
+ bool acceptFunctionArgs,
82
+ PerformanceConstraints perfConstr,
83
+ LocWithParent *parentLoc);
84
+
85
+ // / Check if `closure` meets the `perfConstr`.
86
+ // /
87
+ // / If `acceptFunctionArgs` is true it is assumed that calls to the function,
88
+ // / which contains `callInst`, are already checked to have correct closure
89
+ // / arguments.
90
+ bool checkClosureValue (SILValue closure,
91
+ bool acceptFunctionArgs,
92
+ SILInstruction *callInst,
93
+ PerformanceConstraints perfConstr,
94
+ LocWithParent *parentLoc);
95
+
67
96
bool visitCallee (SILInstruction *callInst, CalleeList callees,
68
97
PerformanceConstraints perfConstr, LocWithParent *parentLoc);
69
98
@@ -107,6 +136,22 @@ bool PerformanceDiagnostics::visitFunction(SILFunction *function,
107
136
if (isEffectFreeArraySemanticCall (&inst))
108
137
continue ;
109
138
139
+ // Check if closures, which are passed to `as` are okay.
140
+ if (checkClosureArguments (as, /* acceptFunctionArgs=*/ true ,
141
+ perfConstr, parentLoc)) {
142
+ return true ;
143
+ }
144
+
145
+ if (as.getOrigCalleeType ()->isNoEscape ()) {
146
+ // This is a call of a non-escaping closure. Check if the closure is
147
+ // okay. In this case we don't need to `visitCallee`.
148
+ if (checkClosureValue (as.getCallee (), /* acceptFunctionArgs=*/ true ,
149
+ &inst, perfConstr, parentLoc)) {
150
+ return true ;
151
+ }
152
+ continue ;
153
+ }
154
+
110
155
// Recursively walk into the callees.
111
156
if (visitCallee (&inst, bca->getCalleeList (as), perfConstr, parentLoc))
112
157
return true ;
@@ -132,6 +177,62 @@ bool PerformanceDiagnostics::visitFunction(SILFunction *function,
132
177
return false ;
133
178
}
134
179
180
+ bool PerformanceDiagnostics::checkClosureArguments (ApplySite as,
181
+ bool acceptFunctionArgs,
182
+ PerformanceConstraints perfConstr,
183
+ LocWithParent *parentLoc) {
184
+ if (SILFunction *knownCallee = as.getReferencedFunctionOrNull ()) {
185
+ if (knownCallee->hasSemanticsAttr (semantics::NO_PERFORMANCE_ANALYSIS))
186
+ return false ;
187
+ }
188
+
189
+ for (SILValue arg : as.getArguments ()) {
190
+ auto fTy = arg->getType ().getAs <SILFunctionType>();
191
+ if (!fTy || !fTy ->isNoEscape ())
192
+ continue ;
193
+
194
+ if (checkClosureValue (arg, acceptFunctionArgs, as.getInstruction (),
195
+ perfConstr, parentLoc)) {
196
+ return true ;
197
+ }
198
+ }
199
+ return false ;
200
+ }
201
+
202
+ bool PerformanceDiagnostics::checkClosureValue (SILValue closure,
203
+ bool acceptFunctionArgs,
204
+ SILInstruction *callInst,
205
+ PerformanceConstraints perfConstr,
206
+ LocWithParent *parentLoc) {
207
+ // Walk through the definition of the closure until we find the "underlying"
208
+ // function_ref instruction.
209
+ while (!isa<FunctionRefInst>(closure)) {
210
+ if (auto *pai = dyn_cast<PartialApplyInst>(closure)) {
211
+ if (checkClosureArguments (ApplySite::isa (pai), acceptFunctionArgs,
212
+ perfConstr, parentLoc)) {
213
+ return true ;
214
+ }
215
+ closure = pai->getCallee ();
216
+ } else if (auto *tfi = dyn_cast<ThinToThickFunctionInst>(closure)) {
217
+ closure = tfi->getOperand ();
218
+ } else if (acceptFunctionArgs && isa<SILFunctionArgument>(closure)) {
219
+ // We can assume that a function closure argument is already checked at
220
+ // the call site.
221
+ return false ;
222
+ } else {
223
+ diagnose (LocWithParent (callInst->getLoc ().getSourceLoc (), parentLoc), diag::performance_unknown_callees);
224
+ return true ;
225
+ }
226
+ }
227
+ // Check what's happening inside the closure body.
228
+ auto *fri = cast<FunctionRefInst>(closure);
229
+ if (visitCallee (callInst, fri->getReferencedFunction (),
230
+ perfConstr, parentLoc)) {
231
+ return true ;
232
+ }
233
+ return false ;
234
+ }
235
+
135
236
bool PerformanceDiagnostics::visitCallee (SILInstruction *callInst,
136
237
CalleeList callees,
137
238
PerformanceConstraints perfConstr,
@@ -313,6 +414,33 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
313
414
return false ;
314
415
}
315
416
417
+ void PerformanceDiagnostics::checkNonAnnotatedFunction (SILFunction *function) {
418
+ for (SILBasicBlock &block : *function) {
419
+ for (SILInstruction &inst : block) {
420
+ auto as = FullApplySite::isa (&inst);
421
+ if (!as)
422
+ continue ;
423
+
424
+ // We only consider direct calls.
425
+ // TODO: this is a hole in the verification because we are not catching
426
+ // cases where a "bad" closure is passed to a performance-annotated
427
+ // v-table, witness table or closure function.
428
+ SILFunction *callee = as.getReferencedFunctionOrNull ();
429
+ if (!callee)
430
+ continue ;
431
+
432
+ if (callee->getPerfConstraints () == PerformanceConstraints::None)
433
+ continue ;
434
+
435
+ if (checkClosureArguments (as, /* acceptFunctionArgs=*/ false ,
436
+ callee->getPerfConstraints (),
437
+ /* LocWithParent*/ nullptr )) {
438
+ return ;
439
+ }
440
+ }
441
+ }
442
+ }
443
+
316
444
void PerformanceDiagnostics::diagnose (LocWithParent loc, Diagnostic &&D) {
317
445
// Start with a valid location in the call tree.
318
446
LocWithParent *validLoc = &loc;
@@ -346,13 +474,16 @@ class PerformanceDiagnosticsPass : public SILModuleTransform {
346
474
SILModule *module = getModule ();
347
475
348
476
PerformanceDiagnostics diagnoser (*module , getAnalysis<BasicCalleeAnalysis>());
477
+ bool annotatedFunctionsFound = false ;
349
478
350
479
for (SILFunction &function : *module ) {
351
- // Don't rerun diagnostics on deserialized functions.
352
- if (function.wasDeserializedCanonical ())
353
- continue ;
354
-
355
480
if (function.getPerfConstraints () != PerformanceConstraints::None) {
481
+ annotatedFunctionsFound = true ;
482
+
483
+ // Don't rerun diagnostics on deserialized functions.
484
+ if (function.wasDeserializedCanonical ())
485
+ continue ;
486
+
356
487
if (!module ->getOptions ().EnablePerformanceAnnotations ) {
357
488
module ->getASTContext ().Diags .diagnose (
358
489
function.getLocation ().getSourceLoc (),
@@ -363,6 +494,19 @@ class PerformanceDiagnosticsPass : public SILModuleTransform {
363
494
diagnoser.visitFunction (&function, function.getPerfConstraints ());
364
495
}
365
496
}
497
+
498
+ if (!annotatedFunctionsFound)
499
+ return ;
500
+
501
+ for (SILFunction &function : *module ) {
502
+ // Don't rerun diagnostics on deserialized functions.
503
+ if (function.wasDeserializedCanonical ())
504
+ continue ;
505
+
506
+ if (function.getPerfConstraints () == PerformanceConstraints::None) {
507
+ diagnoser.checkNonAnnotatedFunction (&function);
508
+ }
509
+ }
366
510
}
367
511
};
368
512
0 commit comments