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