15a87f81fSFrank Derry Wanye //===--- UnrollLoopsCheck.cpp - clang-tidy --------------------------------===//
25a87f81fSFrank Derry Wanye //
35a87f81fSFrank Derry Wanye // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45a87f81fSFrank Derry Wanye // See https://llvm.org/LICENSE.txt for license information.
55a87f81fSFrank Derry Wanye // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65a87f81fSFrank Derry Wanye //
75a87f81fSFrank Derry Wanye //===----------------------------------------------------------------------===//
85a87f81fSFrank Derry Wanye
95a87f81fSFrank Derry Wanye #include "UnrollLoopsCheck.h"
105a87f81fSFrank Derry Wanye #include "clang/AST/APValue.h"
115a87f81fSFrank Derry Wanye #include "clang/AST/ASTContext.h"
125a87f81fSFrank Derry Wanye #include "clang/AST/ASTTypeTraits.h"
135a87f81fSFrank Derry Wanye #include "clang/AST/OperationKinds.h"
145a87f81fSFrank Derry Wanye #include "clang/AST/ParentMapContext.h"
155a87f81fSFrank Derry Wanye #include "clang/ASTMatchers/ASTMatchFinder.h"
16c8644b18SPiotr Zegar #include <cmath>
175a87f81fSFrank Derry Wanye
185a87f81fSFrank Derry Wanye using namespace clang::ast_matchers;
195a87f81fSFrank Derry Wanye
207d2ea6c4SCarlos Galvez namespace clang::tidy::altera {
215a87f81fSFrank Derry Wanye
UnrollLoopsCheck(StringRef Name,ClangTidyContext * Context)225a87f81fSFrank Derry Wanye UnrollLoopsCheck::UnrollLoopsCheck(StringRef Name, ClangTidyContext *Context)
235a87f81fSFrank Derry Wanye : ClangTidyCheck(Name, Context),
245a87f81fSFrank Derry Wanye MaxLoopIterations(Options.get("MaxLoopIterations", 100U)) {}
255a87f81fSFrank Derry Wanye
registerMatchers(MatchFinder * Finder)265a87f81fSFrank Derry Wanye void UnrollLoopsCheck::registerMatchers(MatchFinder *Finder) {
275a87f81fSFrank Derry Wanye const auto HasLoopBound = hasDescendant(
28df0c8f25SNathan James varDecl(matchesName("__end*"),
29df0c8f25SNathan James hasDescendant(integerLiteral().bind("cxx_loop_bound"))));
305a87f81fSFrank Derry Wanye const auto CXXForRangeLoop =
315a87f81fSFrank Derry Wanye cxxForRangeStmt(anyOf(HasLoopBound, unless(HasLoopBound)));
325a87f81fSFrank Derry Wanye const auto AnyLoop = anyOf(forStmt(), whileStmt(), doStmt(), CXXForRangeLoop);
335a87f81fSFrank Derry Wanye Finder->addMatcher(
34df0c8f25SNathan James stmt(AnyLoop, unless(hasDescendant(stmt(AnyLoop)))).bind("loop"), this);
355a87f81fSFrank Derry Wanye }
365a87f81fSFrank Derry Wanye
check(const MatchFinder::MatchResult & Result)375a87f81fSFrank Derry Wanye void UnrollLoopsCheck::check(const MatchFinder::MatchResult &Result) {
385a87f81fSFrank Derry Wanye const auto *Loop = Result.Nodes.getNodeAs<Stmt>("loop");
395a87f81fSFrank Derry Wanye const auto *CXXLoopBound =
405a87f81fSFrank Derry Wanye Result.Nodes.getNodeAs<IntegerLiteral>("cxx_loop_bound");
415a87f81fSFrank Derry Wanye const ASTContext *Context = Result.Context;
425a87f81fSFrank Derry Wanye switch (unrollType(Loop, Result.Context)) {
435a87f81fSFrank Derry Wanye case NotUnrolled:
445a87f81fSFrank Derry Wanye diag(Loop->getBeginLoc(),
455a87f81fSFrank Derry Wanye "kernel performance could be improved by unrolling this loop with a "
465a87f81fSFrank Derry Wanye "'#pragma unroll' directive");
475a87f81fSFrank Derry Wanye break;
485a87f81fSFrank Derry Wanye case PartiallyUnrolled:
495a87f81fSFrank Derry Wanye // Loop already partially unrolled, do nothing.
505a87f81fSFrank Derry Wanye break;
515a87f81fSFrank Derry Wanye case FullyUnrolled:
525a87f81fSFrank Derry Wanye if (hasKnownBounds(Loop, CXXLoopBound, Context)) {
535a87f81fSFrank Derry Wanye if (hasLargeNumIterations(Loop, CXXLoopBound, Context)) {
545a87f81fSFrank Derry Wanye diag(Loop->getBeginLoc(),
555a87f81fSFrank Derry Wanye "loop likely has a large number of iterations and thus "
565a87f81fSFrank Derry Wanye "cannot be fully unrolled; to partially unroll this loop, use "
575a87f81fSFrank Derry Wanye "the '#pragma unroll <num>' directive");
585a87f81fSFrank Derry Wanye return;
595a87f81fSFrank Derry Wanye }
605a87f81fSFrank Derry Wanye return;
615a87f81fSFrank Derry Wanye }
625a87f81fSFrank Derry Wanye if (isa<WhileStmt, DoStmt>(Loop)) {
635a87f81fSFrank Derry Wanye diag(Loop->getBeginLoc(),
645a87f81fSFrank Derry Wanye "full unrolling requested, but loop bounds may not be known; to "
655a87f81fSFrank Derry Wanye "partially unroll this loop, use the '#pragma unroll <num>' "
665a87f81fSFrank Derry Wanye "directive",
675a87f81fSFrank Derry Wanye DiagnosticIDs::Note);
685a87f81fSFrank Derry Wanye break;
695a87f81fSFrank Derry Wanye }
705a87f81fSFrank Derry Wanye diag(Loop->getBeginLoc(),
715a87f81fSFrank Derry Wanye "full unrolling requested, but loop bounds are not known; to "
725a87f81fSFrank Derry Wanye "partially unroll this loop, use the '#pragma unroll <num>' "
735a87f81fSFrank Derry Wanye "directive");
745a87f81fSFrank Derry Wanye break;
755a87f81fSFrank Derry Wanye }
765a87f81fSFrank Derry Wanye }
775a87f81fSFrank Derry Wanye
785a87f81fSFrank Derry Wanye enum UnrollLoopsCheck::UnrollType
unrollType(const Stmt * Statement,ASTContext * Context)795a87f81fSFrank Derry Wanye UnrollLoopsCheck::unrollType(const Stmt *Statement, ASTContext *Context) {
805a87f81fSFrank Derry Wanye const DynTypedNodeList Parents = Context->getParents<Stmt>(*Statement);
815a87f81fSFrank Derry Wanye for (const DynTypedNode &Parent : Parents) {
825a87f81fSFrank Derry Wanye const auto *ParentStmt = Parent.get<AttributedStmt>();
835a87f81fSFrank Derry Wanye if (!ParentStmt)
845a87f81fSFrank Derry Wanye continue;
855a87f81fSFrank Derry Wanye for (const Attr *Attribute : ParentStmt->getAttrs()) {
865a87f81fSFrank Derry Wanye const auto *LoopHint = dyn_cast<LoopHintAttr>(Attribute);
875a87f81fSFrank Derry Wanye if (!LoopHint)
885a87f81fSFrank Derry Wanye continue;
895a87f81fSFrank Derry Wanye switch (LoopHint->getState()) {
905a87f81fSFrank Derry Wanye case LoopHintAttr::Numeric:
915a87f81fSFrank Derry Wanye return PartiallyUnrolled;
925a87f81fSFrank Derry Wanye case LoopHintAttr::Disable:
935a87f81fSFrank Derry Wanye return NotUnrolled;
945a87f81fSFrank Derry Wanye case LoopHintAttr::Full:
955a87f81fSFrank Derry Wanye return FullyUnrolled;
965a87f81fSFrank Derry Wanye case LoopHintAttr::Enable:
975a87f81fSFrank Derry Wanye return FullyUnrolled;
985a87f81fSFrank Derry Wanye case LoopHintAttr::AssumeSafety:
995a87f81fSFrank Derry Wanye return NotUnrolled;
1005a87f81fSFrank Derry Wanye case LoopHintAttr::FixedWidth:
1015a87f81fSFrank Derry Wanye return NotUnrolled;
1025a87f81fSFrank Derry Wanye case LoopHintAttr::ScalableWidth:
1035a87f81fSFrank Derry Wanye return NotUnrolled;
1045a87f81fSFrank Derry Wanye }
1055a87f81fSFrank Derry Wanye }
1065a87f81fSFrank Derry Wanye }
1075a87f81fSFrank Derry Wanye return NotUnrolled;
1085a87f81fSFrank Derry Wanye }
1095a87f81fSFrank Derry Wanye
hasKnownBounds(const Stmt * Statement,const IntegerLiteral * CXXLoopBound,const ASTContext * Context)1105a87f81fSFrank Derry Wanye bool UnrollLoopsCheck::hasKnownBounds(const Stmt *Statement,
1115a87f81fSFrank Derry Wanye const IntegerLiteral *CXXLoopBound,
1125a87f81fSFrank Derry Wanye const ASTContext *Context) {
1135a87f81fSFrank Derry Wanye if (isa<CXXForRangeStmt>(Statement))
1145a87f81fSFrank Derry Wanye return CXXLoopBound != nullptr;
1155a87f81fSFrank Derry Wanye // Too many possibilities in a while statement, so always recommend partial
1165a87f81fSFrank Derry Wanye // unrolling for these.
1175a87f81fSFrank Derry Wanye if (isa<WhileStmt, DoStmt>(Statement))
1185a87f81fSFrank Derry Wanye return false;
1195a87f81fSFrank Derry Wanye // The last loop type is a for loop.
1207674bd4dSSimon Pilgrim const auto *ForLoop = cast<ForStmt>(Statement);
1215a87f81fSFrank Derry Wanye const Stmt *Initializer = ForLoop->getInit();
1225a87f81fSFrank Derry Wanye const Expr *Conditional = ForLoop->getCond();
1235a87f81fSFrank Derry Wanye const Expr *Increment = ForLoop->getInc();
1245a87f81fSFrank Derry Wanye if (!Initializer || !Conditional || !Increment)
1255a87f81fSFrank Derry Wanye return false;
1265a87f81fSFrank Derry Wanye // If the loop variable value isn't known, loop bounds are unknown.
1275a87f81fSFrank Derry Wanye if (const auto *InitDeclStatement = dyn_cast<DeclStmt>(Initializer)) {
1285a87f81fSFrank Derry Wanye if (const auto *VariableDecl =
1295a87f81fSFrank Derry Wanye dyn_cast<VarDecl>(InitDeclStatement->getSingleDecl())) {
1305a87f81fSFrank Derry Wanye APValue *Evaluation = VariableDecl->evaluateValue();
1315a87f81fSFrank Derry Wanye if (!Evaluation || !Evaluation->hasValue())
1325a87f81fSFrank Derry Wanye return false;
1335a87f81fSFrank Derry Wanye }
1345a87f81fSFrank Derry Wanye }
1355a87f81fSFrank Derry Wanye // If increment is unary and not one of ++ and --, loop bounds are unknown.
1365a87f81fSFrank Derry Wanye if (const auto *Op = dyn_cast<UnaryOperator>(Increment))
1375a87f81fSFrank Derry Wanye if (!Op->isIncrementDecrementOp())
1385a87f81fSFrank Derry Wanye return false;
1395a87f81fSFrank Derry Wanye
1407674bd4dSSimon Pilgrim if (const auto *BinaryOp = dyn_cast<BinaryOperator>(Conditional)) {
1415a87f81fSFrank Derry Wanye const Expr *LHS = BinaryOp->getLHS();
1425a87f81fSFrank Derry Wanye const Expr *RHS = BinaryOp->getRHS();
1435a87f81fSFrank Derry Wanye // If both sides are value dependent or constant, loop bounds are unknown.
1445a87f81fSFrank Derry Wanye return LHS->isEvaluatable(*Context) != RHS->isEvaluatable(*Context);
1455a87f81fSFrank Derry Wanye }
1465a87f81fSFrank Derry Wanye return false; // If it's not a binary operator, loop bounds are unknown.
1475a87f81fSFrank Derry Wanye }
1485a87f81fSFrank Derry Wanye
getCondExpr(const Stmt * Statement)1495a87f81fSFrank Derry Wanye const Expr *UnrollLoopsCheck::getCondExpr(const Stmt *Statement) {
1505a87f81fSFrank Derry Wanye if (const auto *ForLoop = dyn_cast<ForStmt>(Statement))
1515a87f81fSFrank Derry Wanye return ForLoop->getCond();
1525a87f81fSFrank Derry Wanye if (const auto *WhileLoop = dyn_cast<WhileStmt>(Statement))
1535a87f81fSFrank Derry Wanye return WhileLoop->getCond();
1545a87f81fSFrank Derry Wanye if (const auto *DoWhileLoop = dyn_cast<DoStmt>(Statement))
1555a87f81fSFrank Derry Wanye return DoWhileLoop->getCond();
1565a87f81fSFrank Derry Wanye if (const auto *CXXRangeLoop = dyn_cast<CXXForRangeStmt>(Statement))
1575a87f81fSFrank Derry Wanye return CXXRangeLoop->getCond();
1585a87f81fSFrank Derry Wanye llvm_unreachable("Unknown loop");
1595a87f81fSFrank Derry Wanye }
1605a87f81fSFrank Derry Wanye
hasLargeNumIterations(const Stmt * Statement,const IntegerLiteral * CXXLoopBound,const ASTContext * Context)1615a87f81fSFrank Derry Wanye bool UnrollLoopsCheck::hasLargeNumIterations(const Stmt *Statement,
1625a87f81fSFrank Derry Wanye const IntegerLiteral *CXXLoopBound,
1635a87f81fSFrank Derry Wanye const ASTContext *Context) {
1645a87f81fSFrank Derry Wanye // Because hasKnownBounds is called before this, if this is true, then
1655a87f81fSFrank Derry Wanye // CXXLoopBound is also matched.
1665a87f81fSFrank Derry Wanye if (isa<CXXForRangeStmt>(Statement)) {
1675a87f81fSFrank Derry Wanye assert(CXXLoopBound && "CXX ranged for loop has no loop bound");
1685a87f81fSFrank Derry Wanye return exprHasLargeNumIterations(CXXLoopBound, Context);
1695a87f81fSFrank Derry Wanye }
1707674bd4dSSimon Pilgrim const auto *ForLoop = cast<ForStmt>(Statement);
1715a87f81fSFrank Derry Wanye const Stmt *Initializer = ForLoop->getInit();
1725a87f81fSFrank Derry Wanye const Expr *Conditional = ForLoop->getCond();
1735a87f81fSFrank Derry Wanye const Expr *Increment = ForLoop->getInc();
1749f822096SPiotr Zegar int InitValue = 0;
1755a87f81fSFrank Derry Wanye // If the loop variable value isn't known, we can't know the loop bounds.
1765a87f81fSFrank Derry Wanye if (const auto *InitDeclStatement = dyn_cast<DeclStmt>(Initializer)) {
1775a87f81fSFrank Derry Wanye if (const auto *VariableDecl =
1785a87f81fSFrank Derry Wanye dyn_cast<VarDecl>(InitDeclStatement->getSingleDecl())) {
1795a87f81fSFrank Derry Wanye APValue *Evaluation = VariableDecl->evaluateValue();
1805a87f81fSFrank Derry Wanye if (!Evaluation || !Evaluation->isInt())
1815a87f81fSFrank Derry Wanye return true;
1825a87f81fSFrank Derry Wanye InitValue = Evaluation->getInt().getExtValue();
1835a87f81fSFrank Derry Wanye }
1845a87f81fSFrank Derry Wanye }
1857674bd4dSSimon Pilgrim
1869f822096SPiotr Zegar int EndValue = 0;
1877674bd4dSSimon Pilgrim const auto *BinaryOp = cast<BinaryOperator>(Conditional);
1885a87f81fSFrank Derry Wanye if (!extractValue(EndValue, BinaryOp, Context))
1895a87f81fSFrank Derry Wanye return true;
1905a87f81fSFrank Derry Wanye
1919f822096SPiotr Zegar double Iterations = 0.0;
1925a87f81fSFrank Derry Wanye
1935a87f81fSFrank Derry Wanye // If increment is unary and not one of ++, --, we can't know the loop bounds.
1945a87f81fSFrank Derry Wanye if (const auto *Op = dyn_cast<UnaryOperator>(Increment)) {
1955a87f81fSFrank Derry Wanye if (Op->isIncrementOp())
1965a87f81fSFrank Derry Wanye Iterations = EndValue - InitValue;
1975a87f81fSFrank Derry Wanye else if (Op->isDecrementOp())
1985a87f81fSFrank Derry Wanye Iterations = InitValue - EndValue;
1995a87f81fSFrank Derry Wanye else
2005a87f81fSFrank Derry Wanye llvm_unreachable("Unary operator neither increment nor decrement");
2015a87f81fSFrank Derry Wanye }
2025a87f81fSFrank Derry Wanye
2035a87f81fSFrank Derry Wanye // If increment is binary and not one of +, -, *, /, we can't know the loop
2045a87f81fSFrank Derry Wanye // bounds.
2055a87f81fSFrank Derry Wanye if (const auto *Op = dyn_cast<BinaryOperator>(Increment)) {
206cbdc3e1bSPiotr Zegar int ConstantValue = 0;
2075a87f81fSFrank Derry Wanye if (!extractValue(ConstantValue, Op, Context))
2085a87f81fSFrank Derry Wanye return true;
2095a87f81fSFrank Derry Wanye switch (Op->getOpcode()) {
2105a87f81fSFrank Derry Wanye case (BO_AddAssign):
2115a87f81fSFrank Derry Wanye Iterations = ceil(float(EndValue - InitValue) / ConstantValue);
2125a87f81fSFrank Derry Wanye break;
2135a87f81fSFrank Derry Wanye case (BO_SubAssign):
2145a87f81fSFrank Derry Wanye Iterations = ceil(float(InitValue - EndValue) / ConstantValue);
2155a87f81fSFrank Derry Wanye break;
2165a87f81fSFrank Derry Wanye case (BO_MulAssign):
217*03dff0d4SRainer Orth Iterations = 1 + (log((double)EndValue) - log((double)InitValue)) /
218*03dff0d4SRainer Orth log((double)ConstantValue);
2195a87f81fSFrank Derry Wanye break;
2205a87f81fSFrank Derry Wanye case (BO_DivAssign):
221*03dff0d4SRainer Orth Iterations = 1 + (log((double)InitValue) - log((double)EndValue)) /
222*03dff0d4SRainer Orth log((double)ConstantValue);
2235a87f81fSFrank Derry Wanye break;
2245a87f81fSFrank Derry Wanye default:
2255a87f81fSFrank Derry Wanye // All other operators are not handled; assume large bounds.
2265a87f81fSFrank Derry Wanye return true;
2275a87f81fSFrank Derry Wanye }
2285a87f81fSFrank Derry Wanye }
2295a87f81fSFrank Derry Wanye return Iterations > MaxLoopIterations;
2305a87f81fSFrank Derry Wanye }
2315a87f81fSFrank Derry Wanye
extractValue(int & Value,const BinaryOperator * Op,const ASTContext * Context)2325a87f81fSFrank Derry Wanye bool UnrollLoopsCheck::extractValue(int &Value, const BinaryOperator *Op,
2335a87f81fSFrank Derry Wanye const ASTContext *Context) {
2345a87f81fSFrank Derry Wanye const Expr *LHS = Op->getLHS();
2355a87f81fSFrank Derry Wanye const Expr *RHS = Op->getRHS();
2365a87f81fSFrank Derry Wanye Expr::EvalResult Result;
2375a87f81fSFrank Derry Wanye if (LHS->isEvaluatable(*Context))
2385a87f81fSFrank Derry Wanye LHS->EvaluateAsRValue(Result, *Context);
2395a87f81fSFrank Derry Wanye else if (RHS->isEvaluatable(*Context))
2405a87f81fSFrank Derry Wanye RHS->EvaluateAsRValue(Result, *Context);
2415a87f81fSFrank Derry Wanye else
242ade0662cSSalman Javed return false; // Cannot evaluate either side.
2435a87f81fSFrank Derry Wanye if (!Result.Val.isInt())
2445a87f81fSFrank Derry Wanye return false; // Cannot check number of iterations, return false to be
2455a87f81fSFrank Derry Wanye // safe.
2465a87f81fSFrank Derry Wanye Value = Result.Val.getInt().getExtValue();
2475a87f81fSFrank Derry Wanye return true;
2485a87f81fSFrank Derry Wanye }
2495a87f81fSFrank Derry Wanye
exprHasLargeNumIterations(const Expr * Expression,const ASTContext * Context) const2505a87f81fSFrank Derry Wanye bool UnrollLoopsCheck::exprHasLargeNumIterations(const Expr *Expression,
25124a7587bSPiotr Zegar const ASTContext *Context) const {
2525a87f81fSFrank Derry Wanye Expr::EvalResult Result;
2535a87f81fSFrank Derry Wanye if (Expression->EvaluateAsRValue(Result, *Context)) {
2545a87f81fSFrank Derry Wanye if (!Result.Val.isInt())
2555a87f81fSFrank Derry Wanye return false; // Cannot check number of iterations, return false to be
2565a87f81fSFrank Derry Wanye // safe.
2575a87f81fSFrank Derry Wanye // The following assumes values go from 0 to Val in increments of 1.
2585a87f81fSFrank Derry Wanye return Result.Val.getInt() > MaxLoopIterations;
2595a87f81fSFrank Derry Wanye }
2605a87f81fSFrank Derry Wanye // Cannot evaluate Expression as an r-value, so cannot check number of
2615a87f81fSFrank Derry Wanye // iterations.
2625a87f81fSFrank Derry Wanye return false;
2635a87f81fSFrank Derry Wanye }
2645a87f81fSFrank Derry Wanye
storeOptions(ClangTidyOptions::OptionMap & Opts)2655a87f81fSFrank Derry Wanye void UnrollLoopsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
2665a87f81fSFrank Derry Wanye Options.store(Opts, "MaxLoopIterations", MaxLoopIterations);
2675a87f81fSFrank Derry Wanye }
2685a87f81fSFrank Derry Wanye
2697d2ea6c4SCarlos Galvez } // namespace clang::tidy::altera
270