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