1 //===--- UnrollLoopsCheck.cpp - clang-tidy --------------------------------===//
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 #include "UnrollLoopsCheck.h"
10 #include "clang/AST/APValue.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/ASTTypeTraits.h"
13 #include "clang/AST/OperationKinds.h"
14 #include "clang/AST/ParentMapContext.h"
15 #include "clang/ASTMatchers/ASTMatchFinder.h"
16 #include <math.h>
17
18 using namespace clang::ast_matchers;
19
20 namespace clang {
21 namespace tidy {
22 namespace altera {
23
UnrollLoopsCheck(StringRef Name,ClangTidyContext * Context)24 UnrollLoopsCheck::UnrollLoopsCheck(StringRef Name, ClangTidyContext *Context)
25 : ClangTidyCheck(Name, Context),
26 MaxLoopIterations(Options.get("MaxLoopIterations", 100U)) {}
27
registerMatchers(MatchFinder * Finder)28 void UnrollLoopsCheck::registerMatchers(MatchFinder *Finder) {
29 const auto HasLoopBound = hasDescendant(
30 varDecl(allOf(matchesName("__end*"),
31 hasDescendant(integerLiteral().bind("cxx_loop_bound")))));
32 const auto CXXForRangeLoop =
33 cxxForRangeStmt(anyOf(HasLoopBound, unless(HasLoopBound)));
34 const auto AnyLoop = anyOf(forStmt(), whileStmt(), doStmt(), CXXForRangeLoop);
35 Finder->addMatcher(
36 stmt(allOf(AnyLoop, unless(hasDescendant(stmt(AnyLoop))))).bind("loop"),
37 this);
38 }
39
check(const MatchFinder::MatchResult & Result)40 void UnrollLoopsCheck::check(const MatchFinder::MatchResult &Result) {
41 const auto *Loop = Result.Nodes.getNodeAs<Stmt>("loop");
42 const auto *CXXLoopBound =
43 Result.Nodes.getNodeAs<IntegerLiteral>("cxx_loop_bound");
44 const ASTContext *Context = Result.Context;
45 switch (unrollType(Loop, Result.Context)) {
46 case NotUnrolled:
47 diag(Loop->getBeginLoc(),
48 "kernel performance could be improved by unrolling this loop with a "
49 "'#pragma unroll' directive");
50 break;
51 case PartiallyUnrolled:
52 // Loop already partially unrolled, do nothing.
53 break;
54 case FullyUnrolled:
55 if (hasKnownBounds(Loop, CXXLoopBound, Context)) {
56 if (hasLargeNumIterations(Loop, CXXLoopBound, Context)) {
57 diag(Loop->getBeginLoc(),
58 "loop likely has a large number of iterations and thus "
59 "cannot be fully unrolled; to partially unroll this loop, use "
60 "the '#pragma unroll <num>' directive");
61 return;
62 }
63 return;
64 }
65 if (isa<WhileStmt, DoStmt>(Loop)) {
66 diag(Loop->getBeginLoc(),
67 "full unrolling requested, but loop bounds may not be known; to "
68 "partially unroll this loop, use the '#pragma unroll <num>' "
69 "directive",
70 DiagnosticIDs::Note);
71 break;
72 }
73 diag(Loop->getBeginLoc(),
74 "full unrolling requested, but loop bounds are not known; to "
75 "partially unroll this loop, use the '#pragma unroll <num>' "
76 "directive");
77 break;
78 }
79 }
80
81 enum UnrollLoopsCheck::UnrollType
unrollType(const Stmt * Statement,ASTContext * Context)82 UnrollLoopsCheck::unrollType(const Stmt *Statement, ASTContext *Context) {
83 const DynTypedNodeList Parents = Context->getParents<Stmt>(*Statement);
84 for (const DynTypedNode &Parent : Parents) {
85 const auto *ParentStmt = Parent.get<AttributedStmt>();
86 if (!ParentStmt)
87 continue;
88 for (const Attr *Attribute : ParentStmt->getAttrs()) {
89 const auto *LoopHint = dyn_cast<LoopHintAttr>(Attribute);
90 if (!LoopHint)
91 continue;
92 switch (LoopHint->getState()) {
93 case LoopHintAttr::Numeric:
94 return PartiallyUnrolled;
95 case LoopHintAttr::Disable:
96 return NotUnrolled;
97 case LoopHintAttr::Full:
98 return FullyUnrolled;
99 case LoopHintAttr::Enable:
100 return FullyUnrolled;
101 case LoopHintAttr::AssumeSafety:
102 return NotUnrolled;
103 case LoopHintAttr::FixedWidth:
104 return NotUnrolled;
105 case LoopHintAttr::ScalableWidth:
106 return NotUnrolled;
107 }
108 }
109 }
110 return NotUnrolled;
111 }
112
hasKnownBounds(const Stmt * Statement,const IntegerLiteral * CXXLoopBound,const ASTContext * Context)113 bool UnrollLoopsCheck::hasKnownBounds(const Stmt *Statement,
114 const IntegerLiteral *CXXLoopBound,
115 const ASTContext *Context) {
116 if (isa<CXXForRangeStmt>(Statement))
117 return CXXLoopBound != nullptr;
118 // Too many possibilities in a while statement, so always recommend partial
119 // unrolling for these.
120 if (isa<WhileStmt, DoStmt>(Statement))
121 return false;
122 // The last loop type is a for loop.
123 const auto *ForLoop = cast<ForStmt>(Statement);
124 const Stmt *Initializer = ForLoop->getInit();
125 const Expr *Conditional = ForLoop->getCond();
126 const Expr *Increment = ForLoop->getInc();
127 if (!Initializer || !Conditional || !Increment)
128 return false;
129 // If the loop variable value isn't known, loop bounds are unknown.
130 if (const auto *InitDeclStatement = dyn_cast<DeclStmt>(Initializer)) {
131 if (const auto *VariableDecl =
132 dyn_cast<VarDecl>(InitDeclStatement->getSingleDecl())) {
133 APValue *Evaluation = VariableDecl->evaluateValue();
134 if (!Evaluation || !Evaluation->hasValue())
135 return false;
136 }
137 }
138 // If increment is unary and not one of ++ and --, loop bounds are unknown.
139 if (const auto *Op = dyn_cast<UnaryOperator>(Increment))
140 if (!Op->isIncrementDecrementOp())
141 return false;
142
143 if (const auto *BinaryOp = dyn_cast<BinaryOperator>(Conditional)) {
144 const Expr *LHS = BinaryOp->getLHS();
145 const Expr *RHS = BinaryOp->getRHS();
146 // If both sides are value dependent or constant, loop bounds are unknown.
147 return LHS->isEvaluatable(*Context) != RHS->isEvaluatable(*Context);
148 }
149 return false; // If it's not a binary operator, loop bounds are unknown.
150 }
151
getCondExpr(const Stmt * Statement)152 const Expr *UnrollLoopsCheck::getCondExpr(const Stmt *Statement) {
153 if (const auto *ForLoop = dyn_cast<ForStmt>(Statement))
154 return ForLoop->getCond();
155 if (const auto *WhileLoop = dyn_cast<WhileStmt>(Statement))
156 return WhileLoop->getCond();
157 if (const auto *DoWhileLoop = dyn_cast<DoStmt>(Statement))
158 return DoWhileLoop->getCond();
159 if (const auto *CXXRangeLoop = dyn_cast<CXXForRangeStmt>(Statement))
160 return CXXRangeLoop->getCond();
161 llvm_unreachable("Unknown loop");
162 }
163
hasLargeNumIterations(const Stmt * Statement,const IntegerLiteral * CXXLoopBound,const ASTContext * Context)164 bool UnrollLoopsCheck::hasLargeNumIterations(const Stmt *Statement,
165 const IntegerLiteral *CXXLoopBound,
166 const ASTContext *Context) {
167 // Because hasKnownBounds is called before this, if this is true, then
168 // CXXLoopBound is also matched.
169 if (isa<CXXForRangeStmt>(Statement)) {
170 assert(CXXLoopBound && "CXX ranged for loop has no loop bound");
171 return exprHasLargeNumIterations(CXXLoopBound, Context);
172 }
173 const auto *ForLoop = cast<ForStmt>(Statement);
174 const Stmt *Initializer = ForLoop->getInit();
175 const Expr *Conditional = ForLoop->getCond();
176 const Expr *Increment = ForLoop->getInc();
177 int InitValue;
178 // If the loop variable value isn't known, we can't know the loop bounds.
179 if (const auto *InitDeclStatement = dyn_cast<DeclStmt>(Initializer)) {
180 if (const auto *VariableDecl =
181 dyn_cast<VarDecl>(InitDeclStatement->getSingleDecl())) {
182 APValue *Evaluation = VariableDecl->evaluateValue();
183 if (!Evaluation || !Evaluation->isInt())
184 return true;
185 InitValue = Evaluation->getInt().getExtValue();
186 }
187 }
188
189 int EndValue;
190 const auto *BinaryOp = cast<BinaryOperator>(Conditional);
191 if (!extractValue(EndValue, BinaryOp, Context))
192 return true;
193
194 double Iterations;
195
196 // If increment is unary and not one of ++, --, we can't know the loop bounds.
197 if (const auto *Op = dyn_cast<UnaryOperator>(Increment)) {
198 if (Op->isIncrementOp())
199 Iterations = EndValue - InitValue;
200 else if (Op->isDecrementOp())
201 Iterations = InitValue - EndValue;
202 else
203 llvm_unreachable("Unary operator neither increment nor decrement");
204 }
205
206 // If increment is binary and not one of +, -, *, /, we can't know the loop
207 // bounds.
208 if (const auto *Op = dyn_cast<BinaryOperator>(Increment)) {
209 int ConstantValue;
210 if (!extractValue(ConstantValue, Op, Context))
211 return true;
212 switch (Op->getOpcode()) {
213 case (BO_AddAssign):
214 Iterations = ceil(float(EndValue - InitValue) / ConstantValue);
215 break;
216 case (BO_SubAssign):
217 Iterations = ceil(float(InitValue - EndValue) / ConstantValue);
218 break;
219 case (BO_MulAssign):
220 Iterations = 1 + (log(EndValue) - log(InitValue)) / log(ConstantValue);
221 break;
222 case (BO_DivAssign):
223 Iterations = 1 + (log(InitValue) - log(EndValue)) / log(ConstantValue);
224 break;
225 default:
226 // All other operators are not handled; assume large bounds.
227 return true;
228 }
229 }
230 return Iterations > MaxLoopIterations;
231 }
232
extractValue(int & Value,const BinaryOperator * Op,const ASTContext * Context)233 bool UnrollLoopsCheck::extractValue(int &Value, const BinaryOperator *Op,
234 const ASTContext *Context) {
235 const Expr *LHS = Op->getLHS();
236 const Expr *RHS = Op->getRHS();
237 Expr::EvalResult Result;
238 if (LHS->isEvaluatable(*Context))
239 LHS->EvaluateAsRValue(Result, *Context);
240 else if (RHS->isEvaluatable(*Context))
241 RHS->EvaluateAsRValue(Result, *Context);
242 else
243 return false; // Cannot evaluate either side.
244 if (!Result.Val.isInt())
245 return false; // Cannot check number of iterations, return false to be
246 // safe.
247 Value = Result.Val.getInt().getExtValue();
248 return true;
249 }
250
exprHasLargeNumIterations(const Expr * Expression,const ASTContext * Context)251 bool UnrollLoopsCheck::exprHasLargeNumIterations(const Expr *Expression,
252 const ASTContext *Context) {
253 Expr::EvalResult Result;
254 if (Expression->EvaluateAsRValue(Result, *Context)) {
255 if (!Result.Val.isInt())
256 return false; // Cannot check number of iterations, return false to be
257 // safe.
258 // The following assumes values go from 0 to Val in increments of 1.
259 return Result.Val.getInt() > MaxLoopIterations;
260 }
261 // Cannot evaluate Expression as an r-value, so cannot check number of
262 // iterations.
263 return false;
264 }
265
storeOptions(ClangTidyOptions::OptionMap & Opts)266 void UnrollLoopsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
267 Options.store(Opts, "MaxLoopIterations", MaxLoopIterations);
268 }
269
270 } // namespace altera
271 } // namespace tidy
272 } // namespace clang
273