/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===--- LoopUnrolling.cpp - Unroll loops -----------------------*- C++ -*-===// |
2 | | // |
3 | | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | | // See https://llvm.org/LICENSE.txt for license information. |
5 | | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | | // |
7 | | //===----------------------------------------------------------------------===// |
8 | | /// |
9 | | /// This file contains functions which are used to decide if a loop worth to be |
10 | | /// unrolled. Moreover, these functions manages the stack of loop which is |
11 | | /// tracked by the ProgramState. |
12 | | /// |
13 | | //===----------------------------------------------------------------------===// |
14 | | |
15 | | #include "clang/ASTMatchers/ASTMatchers.h" |
16 | | #include "clang/ASTMatchers/ASTMatchFinder.h" |
17 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
18 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
19 | | #include "clang/StaticAnalyzer/Core/PathSensitive/LoopUnrolling.h" |
20 | | #include <optional> |
21 | | |
22 | | using namespace clang; |
23 | | using namespace ento; |
24 | | using namespace clang::ast_matchers; |
25 | | |
26 | | static const int MAXIMUM_STEP_UNROLLED = 128; |
27 | | |
28 | | namespace { |
29 | | struct LoopState { |
30 | | private: |
31 | | enum Kind { Normal, Unrolled } K; |
32 | | const Stmt *LoopStmt; |
33 | | const LocationContext *LCtx; |
34 | | unsigned maxStep; |
35 | | LoopState(Kind InK, const Stmt *S, const LocationContext *L, unsigned N) |
36 | 197 | : K(InK), LoopStmt(S), LCtx(L), maxStep(N) {} |
37 | | |
38 | | public: |
39 | | static LoopState getNormal(const Stmt *S, const LocationContext *L, |
40 | 91 | unsigned N) { |
41 | 91 | return LoopState(Normal, S, L, N); |
42 | 91 | } |
43 | | static LoopState getUnrolled(const Stmt *S, const LocationContext *L, |
44 | 106 | unsigned N) { |
45 | 106 | return LoopState(Unrolled, S, L, N); |
46 | 106 | } |
47 | 7.56k | bool isUnrolled() const { return K == Unrolled; } |
48 | 64 | unsigned getMaxStep() const { return maxStep; } |
49 | 1.97k | const Stmt *getLoopStmt() const { return LoopStmt; } |
50 | 1.76k | const LocationContext *getLocationContext() const { return LCtx; } |
51 | 0 | bool operator==(const LoopState &X) const { |
52 | 0 | return K == X.K && LoopStmt == X.LoopStmt; |
53 | 0 | } |
54 | 237 | void Profile(llvm::FoldingSetNodeID &ID) const { |
55 | 237 | ID.AddInteger(K); |
56 | 237 | ID.AddPointer(LoopStmt); |
57 | 237 | ID.AddPointer(LCtx); |
58 | 237 | ID.AddInteger(maxStep); |
59 | 237 | } |
60 | | }; |
61 | | } // namespace |
62 | | |
63 | | // The tracked stack of loops. The stack indicates that which loops the |
64 | | // simulated element contained by. The loops are marked depending if we decided |
65 | | // to unroll them. |
66 | | // TODO: The loop stack should not need to be in the program state since it is |
67 | | // lexical in nature. Instead, the stack of loops should be tracked in the |
68 | | // LocationContext. |
69 | | REGISTER_LIST_WITH_PROGRAMSTATE(LoopStack, LoopState) |
70 | | |
71 | | namespace clang { |
72 | | namespace ento { |
73 | | |
74 | 2.26k | static bool isLoopStmt(const Stmt *S) { |
75 | 2.26k | return isa_and_nonnull<ForStmt, WhileStmt, DoStmt>(S); |
76 | 2.26k | } |
77 | | |
78 | 148 | ProgramStateRef processLoopEnd(const Stmt *LoopStmt, ProgramStateRef State) { |
79 | 148 | auto LS = State->get<LoopStack>(); |
80 | 148 | if (!LS.isEmpty() && LS.getHead().getLoopStmt() == LoopStmt) |
81 | 148 | State = State->set<LoopStack>(LS.getTail()); |
82 | 148 | return State; |
83 | 148 | } |
84 | | |
85 | | static internal::Matcher<Stmt> simpleCondition(StringRef BindName, |
86 | 173 | StringRef RefName) { |
87 | 173 | return binaryOperator( |
88 | 173 | anyOf(hasOperatorName("<"), hasOperatorName(">"), |
89 | 173 | hasOperatorName("<="), hasOperatorName(">="), |
90 | 173 | hasOperatorName("!=")), |
91 | 173 | hasEitherOperand(ignoringParenImpCasts( |
92 | 173 | declRefExpr(to(varDecl(hasType(isInteger())).bind(BindName))) |
93 | 173 | .bind(RefName))), |
94 | 173 | hasEitherOperand( |
95 | 173 | ignoringParenImpCasts(integerLiteral().bind("boundNum")))) |
96 | 173 | .bind("conditionOperator"); |
97 | 173 | } |
98 | | |
99 | | static internal::Matcher<Stmt> |
100 | 173 | changeIntBoundNode(internal::Matcher<Decl> VarNodeMatcher) { |
101 | 173 | return anyOf( |
102 | 173 | unaryOperator(anyOf(hasOperatorName("--"), hasOperatorName("++")), |
103 | 173 | hasUnaryOperand(ignoringParenImpCasts( |
104 | 173 | declRefExpr(to(varDecl(VarNodeMatcher)))))), |
105 | 173 | binaryOperator(isAssignmentOperator(), |
106 | 173 | hasLHS(ignoringParenImpCasts( |
107 | 173 | declRefExpr(to(varDecl(VarNodeMatcher))))))); |
108 | 173 | } |
109 | | |
110 | | static internal::Matcher<Stmt> |
111 | 16.1k | callByRef(internal::Matcher<Decl> VarNodeMatcher) { |
112 | 16.1k | return callExpr(forEachArgumentWithParam( |
113 | 16.1k | declRefExpr(to(varDecl(VarNodeMatcher))), |
114 | 16.1k | parmVarDecl(hasType(references(qualType(unless(isConstQualified()))))))); |
115 | 16.1k | } |
116 | | |
117 | | static internal::Matcher<Stmt> |
118 | 16.1k | assignedToRef(internal::Matcher<Decl> VarNodeMatcher) { |
119 | 16.1k | return declStmt(hasDescendant(varDecl( |
120 | 16.1k | allOf(hasType(referenceType()), |
121 | 16.1k | hasInitializer(anyOf( |
122 | 16.1k | initListExpr(has(declRefExpr(to(varDecl(VarNodeMatcher))))), |
123 | 16.1k | declRefExpr(to(varDecl(VarNodeMatcher))))))))); |
124 | 16.1k | } |
125 | | |
126 | | static internal::Matcher<Stmt> |
127 | 16.1k | getAddrTo(internal::Matcher<Decl> VarNodeMatcher) { |
128 | 16.1k | return unaryOperator( |
129 | 16.1k | hasOperatorName("&"), |
130 | 16.1k | hasUnaryOperand(declRefExpr(hasDeclaration(VarNodeMatcher)))); |
131 | 16.1k | } |
132 | | |
133 | 173 | static internal::Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) { |
134 | 173 | return hasDescendant(stmt( |
135 | 173 | anyOf(gotoStmt(), switchStmt(), returnStmt(), |
136 | | // Escaping and not known mutation of the loop counter is handled |
137 | | // by exclusion of assigning and address-of operators and |
138 | | // pass-by-ref function calls on the loop counter from the body. |
139 | 173 | changeIntBoundNode(equalsBoundNode(std::string(NodeName))), |
140 | 173 | callByRef(equalsBoundNode(std::string(NodeName))), |
141 | 173 | getAddrTo(equalsBoundNode(std::string(NodeName))), |
142 | 173 | assignedToRef(equalsBoundNode(std::string(NodeName)))))); |
143 | 173 | } |
144 | | |
145 | 173 | static internal::Matcher<Stmt> forLoopMatcher() { |
146 | 173 | return forStmt( |
147 | 173 | hasCondition(simpleCondition("initVarName", "initVarRef")), |
148 | | // Initialization should match the form: 'int i = 6' or 'i = 42'. |
149 | 173 | hasLoopInit( |
150 | 173 | anyOf(declStmt(hasSingleDecl( |
151 | 173 | varDecl(allOf(hasInitializer(ignoringParenImpCasts( |
152 | 173 | integerLiteral().bind("initNum"))), |
153 | 173 | equalsBoundNode("initVarName"))))), |
154 | 173 | binaryOperator(hasLHS(declRefExpr(to(varDecl( |
155 | 173 | equalsBoundNode("initVarName"))))), |
156 | 173 | hasRHS(ignoringParenImpCasts( |
157 | 173 | integerLiteral().bind("initNum")))))), |
158 | | // Incrementation should be a simple increment or decrement |
159 | | // operator call. |
160 | 173 | hasIncrement(unaryOperator( |
161 | 173 | anyOf(hasOperatorName("++"), hasOperatorName("--")), |
162 | 173 | hasUnaryOperand(declRefExpr( |
163 | 173 | to(varDecl(allOf(equalsBoundNode("initVarName"), |
164 | 173 | hasType(isInteger())))))))), |
165 | 173 | unless(hasBody(hasSuspiciousStmt("initVarName")))) |
166 | 173 | .bind("forLoop"); |
167 | 173 | } |
168 | | |
169 | 6 | static bool isCapturedByReference(ExplodedNode *N, const DeclRefExpr *DR) { |
170 | | |
171 | | // Get the lambda CXXRecordDecl |
172 | 6 | assert(DR->refersToEnclosingVariableOrCapture()); |
173 | 6 | const LocationContext *LocCtxt = N->getLocationContext(); |
174 | 6 | const Decl *D = LocCtxt->getDecl(); |
175 | 6 | const auto *MD = cast<CXXMethodDecl>(D); |
176 | 6 | assert(MD && MD->getParent()->isLambda() && |
177 | 6 | "Captured variable should only be seen while evaluating a lambda"); |
178 | 6 | const CXXRecordDecl *LambdaCXXRec = MD->getParent(); |
179 | | |
180 | | // Lookup the fields of the lambda |
181 | 6 | llvm::DenseMap<const ValueDecl *, FieldDecl *> LambdaCaptureFields; |
182 | 6 | FieldDecl *LambdaThisCaptureField; |
183 | 6 | LambdaCXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); |
184 | | |
185 | | // Check if the counter is captured by reference |
186 | 6 | const VarDecl *VD = cast<VarDecl>(DR->getDecl()->getCanonicalDecl()); |
187 | 6 | assert(VD); |
188 | 6 | const FieldDecl *FD = LambdaCaptureFields[VD]; |
189 | 6 | assert(FD && "Captured variable without a corresponding field"); |
190 | 6 | return FD->getType()->isReferenceType(); |
191 | 6 | } |
192 | | |
193 | | // A loop counter is considered escaped if: |
194 | | // case 1: It is a global variable. |
195 | | // case 2: It is a reference parameter or a reference capture. |
196 | | // case 3: It is assigned to a non-const reference variable or parameter. |
197 | | // case 4: Has its address taken. |
198 | 120 | static bool isPossiblyEscaped(ExplodedNode *N, const DeclRefExpr *DR) { |
199 | 120 | const VarDecl *VD = cast<VarDecl>(DR->getDecl()->getCanonicalDecl()); |
200 | 120 | assert(VD); |
201 | | // Case 1: |
202 | 120 | if (VD->hasGlobalStorage()) |
203 | 0 | return true; |
204 | | |
205 | 120 | const bool IsRefParamOrCapture = |
206 | 120 | isa<ParmVarDecl>(VD) || DR->refersToEnclosingVariableOrCapture()118 ; |
207 | | // Case 2: |
208 | 120 | if ((DR->refersToEnclosingVariableOrCapture() && |
209 | 120 | isCapturedByReference(N, DR)6 ) || |
210 | 120 | (118 IsRefParamOrCapture118 && VD->getType()->isReferenceType()6 )) |
211 | 2 | return true; |
212 | | |
213 | 18.5k | while (118 !N->pred_empty()) { |
214 | | // FIXME: getStmtForDiagnostics() does nasty things in order to provide |
215 | | // a valid statement for body farms, do we need this behavior here? |
216 | 18.5k | const Stmt *S = N->getStmtForDiagnostics(); |
217 | 18.5k | if (!S) { |
218 | 2.41k | N = N->getFirstPred(); |
219 | 2.41k | continue; |
220 | 2.41k | } |
221 | | |
222 | 16.1k | if (const DeclStmt *DS = dyn_cast<DeclStmt>(S)) { |
223 | 180 | for (const Decl *D : DS->decls()) { |
224 | | // Once we reach the declaration of the VD we can return. |
225 | 180 | if (D->getCanonicalDecl() == VD) |
226 | 106 | return false; |
227 | 180 | } |
228 | 180 | } |
229 | | // Check the usage of the pass-by-ref function calls and adress-of operator |
230 | | // on VD and reference initialized by VD. |
231 | 16.0k | ASTContext &ASTCtx = |
232 | 16.0k | N->getLocationContext()->getAnalysisDeclContext()->getASTContext(); |
233 | | // Case 3 and 4: |
234 | 16.0k | auto Match = |
235 | 16.0k | match(stmt(anyOf(callByRef(equalsNode(VD)), getAddrTo(equalsNode(VD)), |
236 | 16.0k | assignedToRef(equalsNode(VD)))), |
237 | 16.0k | *S, ASTCtx); |
238 | 16.0k | if (!Match.empty()) |
239 | 6 | return true; |
240 | | |
241 | 16.0k | N = N->getFirstPred(); |
242 | 16.0k | } |
243 | | |
244 | | // Reference parameter and reference capture will not be found. |
245 | 6 | if (IsRefParamOrCapture) |
246 | 6 | return false; |
247 | | |
248 | 0 | llvm_unreachable("Reached root without finding the declaration of VD"); |
249 | 0 | } |
250 | | |
251 | | bool shouldCompletelyUnroll(const Stmt *LoopStmt, ASTContext &ASTCtx, |
252 | 173 | ExplodedNode *Pred, unsigned &maxStep) { |
253 | | |
254 | 173 | if (!isLoopStmt(LoopStmt)) |
255 | 0 | return false; |
256 | | |
257 | | // TODO: Match the cases where the bound is not a concrete literal but an |
258 | | // integer with known value |
259 | 173 | auto Matches = match(forLoopMatcher(), *LoopStmt, ASTCtx); |
260 | 173 | if (Matches.empty()) |
261 | 53 | return false; |
262 | | |
263 | 120 | const auto *CounterVarRef = Matches[0].getNodeAs<DeclRefExpr>("initVarRef"); |
264 | 120 | llvm::APInt BoundNum = |
265 | 120 | Matches[0].getNodeAs<IntegerLiteral>("boundNum")->getValue(); |
266 | 120 | llvm::APInt InitNum = |
267 | 120 | Matches[0].getNodeAs<IntegerLiteral>("initNum")->getValue(); |
268 | 120 | auto CondOp = Matches[0].getNodeAs<BinaryOperator>("conditionOperator"); |
269 | 120 | if (InitNum.getBitWidth() != BoundNum.getBitWidth()) { |
270 | 2 | InitNum = InitNum.zext(BoundNum.getBitWidth()); |
271 | 2 | BoundNum = BoundNum.zext(InitNum.getBitWidth()); |
272 | 2 | } |
273 | | |
274 | 120 | if (CondOp->getOpcode() == BO_GE || CondOp->getOpcode() == BO_LE) |
275 | 4 | maxStep = (BoundNum - InitNum + 1).abs().getZExtValue(); |
276 | 116 | else |
277 | 116 | maxStep = (BoundNum - InitNum).abs().getZExtValue(); |
278 | | |
279 | | // Check if the counter of the loop is not escaped before. |
280 | 120 | return !isPossiblyEscaped(Pred, CounterVarRef); |
281 | 173 | } |
282 | | |
283 | 1.40k | bool madeNewBranch(ExplodedNode *N, const Stmt *LoopStmt) { |
284 | 1.40k | const Stmt *S = nullptr; |
285 | 57.9k | while (!N->pred_empty()) { |
286 | 57.9k | if (N->succ_size() > 1) |
287 | 24 | return true; |
288 | | |
289 | 57.9k | ProgramPoint P = N->getLocation(); |
290 | 57.9k | if (std::optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) |
291 | 5.88k | S = BE->getBlock()->getTerminatorStmt(); |
292 | | |
293 | 57.9k | if (S == LoopStmt) |
294 | 1.37k | return false; |
295 | | |
296 | 56.5k | N = N->getFirstPred(); |
297 | 56.5k | } |
298 | | |
299 | 0 | llvm_unreachable("Reached root without encountering the previous step"); |
300 | 0 | } |
301 | | |
302 | | // updateLoopStack is called on every basic block, therefore it needs to be fast |
303 | | ProgramStateRef updateLoopStack(const Stmt *LoopStmt, ASTContext &ASTCtx, |
304 | 2.08k | ExplodedNode *Pred, unsigned maxVisitOnPath) { |
305 | 2.08k | auto State = Pred->getState(); |
306 | 2.08k | auto LCtx = Pred->getLocationContext(); |
307 | | |
308 | 2.08k | if (!isLoopStmt(LoopStmt)) |
309 | 160 | return State; |
310 | | |
311 | 1.92k | auto LS = State->get<LoopStack>(); |
312 | 1.92k | if (!LS.isEmpty() && LoopStmt == LS.getHead().getLoopStmt()1.82k && |
313 | 1.92k | LCtx == LS.getHead().getLocationContext()1.76k ) { |
314 | 1.75k | if (LS.getHead().isUnrolled() && madeNewBranch(Pred, LoopStmt)1.40k ) { |
315 | 24 | State = State->set<LoopStack>(LS.getTail()); |
316 | 24 | State = State->add<LoopStack>( |
317 | 24 | LoopState::getNormal(LoopStmt, LCtx, maxVisitOnPath)); |
318 | 24 | } |
319 | 1.75k | return State; |
320 | 1.75k | } |
321 | 173 | unsigned maxStep; |
322 | 173 | if (!shouldCompletelyUnroll(LoopStmt, ASTCtx, Pred, maxStep)) { |
323 | 61 | State = State->add<LoopStack>( |
324 | 61 | LoopState::getNormal(LoopStmt, LCtx, maxVisitOnPath)); |
325 | 61 | return State; |
326 | 61 | } |
327 | | |
328 | 112 | unsigned outerStep = (LS.isEmpty() ? 148 : LS.getHead().getMaxStep()64 ); |
329 | | |
330 | 112 | unsigned innerMaxStep = maxStep * outerStep; |
331 | 112 | if (innerMaxStep > MAXIMUM_STEP_UNROLLED) |
332 | 6 | State = State->add<LoopStack>( |
333 | 6 | LoopState::getNormal(LoopStmt, LCtx, maxVisitOnPath)); |
334 | 106 | else |
335 | 106 | State = State->add<LoopStack>( |
336 | 106 | LoopState::getUnrolled(LoopStmt, LCtx, innerMaxStep)); |
337 | 112 | return State; |
338 | 173 | } |
339 | | |
340 | 5.92k | bool isUnrolledState(ProgramStateRef State) { |
341 | 5.92k | auto LS = State->get<LoopStack>(); |
342 | 5.92k | if (LS.isEmpty() || !LS.getHead().isUnrolled()5.80k ) |
343 | 1.43k | return false; |
344 | 4.49k | return true; |
345 | 5.92k | } |
346 | | } |
347 | | } |