123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- //===--- UnrollLoopsCheck.cpp - clang-tidy --------------------------------===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- #include "UnrollLoopsCheck.h"
- #include "clang/AST/APValue.h"
- #include "clang/AST/ASTContext.h"
- #include "clang/AST/ASTTypeTraits.h"
- #include "clang/AST/OperationKinds.h"
- #include "clang/AST/ParentMapContext.h"
- #include "clang/ASTMatchers/ASTMatchFinder.h"
- #include <math.h>
- using namespace clang::ast_matchers;
- namespace clang::tidy::altera {
- UnrollLoopsCheck::UnrollLoopsCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context),
- MaxLoopIterations(Options.get("MaxLoopIterations", 100U)) {}
- void UnrollLoopsCheck::registerMatchers(MatchFinder *Finder) {
- const auto HasLoopBound = hasDescendant(
- varDecl(allOf(matchesName("__end*"),
- hasDescendant(integerLiteral().bind("cxx_loop_bound")))));
- const auto CXXForRangeLoop =
- cxxForRangeStmt(anyOf(HasLoopBound, unless(HasLoopBound)));
- const auto AnyLoop = anyOf(forStmt(), whileStmt(), doStmt(), CXXForRangeLoop);
- Finder->addMatcher(
- stmt(allOf(AnyLoop, unless(hasDescendant(stmt(AnyLoop))))).bind("loop"),
- this);
- }
- void UnrollLoopsCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Loop = Result.Nodes.getNodeAs<Stmt>("loop");
- const auto *CXXLoopBound =
- Result.Nodes.getNodeAs<IntegerLiteral>("cxx_loop_bound");
- const ASTContext *Context = Result.Context;
- switch (unrollType(Loop, Result.Context)) {
- case NotUnrolled:
- diag(Loop->getBeginLoc(),
- "kernel performance could be improved by unrolling this loop with a "
- "'#pragma unroll' directive");
- break;
- case PartiallyUnrolled:
- // Loop already partially unrolled, do nothing.
- break;
- case FullyUnrolled:
- if (hasKnownBounds(Loop, CXXLoopBound, Context)) {
- if (hasLargeNumIterations(Loop, CXXLoopBound, Context)) {
- diag(Loop->getBeginLoc(),
- "loop likely has a large number of iterations and thus "
- "cannot be fully unrolled; to partially unroll this loop, use "
- "the '#pragma unroll <num>' directive");
- return;
- }
- return;
- }
- if (isa<WhileStmt, DoStmt>(Loop)) {
- diag(Loop->getBeginLoc(),
- "full unrolling requested, but loop bounds may not be known; to "
- "partially unroll this loop, use the '#pragma unroll <num>' "
- "directive",
- DiagnosticIDs::Note);
- break;
- }
- diag(Loop->getBeginLoc(),
- "full unrolling requested, but loop bounds are not known; to "
- "partially unroll this loop, use the '#pragma unroll <num>' "
- "directive");
- break;
- }
- }
- enum UnrollLoopsCheck::UnrollType
- UnrollLoopsCheck::unrollType(const Stmt *Statement, ASTContext *Context) {
- const DynTypedNodeList Parents = Context->getParents<Stmt>(*Statement);
- for (const DynTypedNode &Parent : Parents) {
- const auto *ParentStmt = Parent.get<AttributedStmt>();
- if (!ParentStmt)
- continue;
- for (const Attr *Attribute : ParentStmt->getAttrs()) {
- const auto *LoopHint = dyn_cast<LoopHintAttr>(Attribute);
- if (!LoopHint)
- continue;
- switch (LoopHint->getState()) {
- case LoopHintAttr::Numeric:
- return PartiallyUnrolled;
- case LoopHintAttr::Disable:
- return NotUnrolled;
- case LoopHintAttr::Full:
- return FullyUnrolled;
- case LoopHintAttr::Enable:
- return FullyUnrolled;
- case LoopHintAttr::AssumeSafety:
- return NotUnrolled;
- case LoopHintAttr::FixedWidth:
- return NotUnrolled;
- case LoopHintAttr::ScalableWidth:
- return NotUnrolled;
- }
- }
- }
- return NotUnrolled;
- }
- bool UnrollLoopsCheck::hasKnownBounds(const Stmt *Statement,
- const IntegerLiteral *CXXLoopBound,
- const ASTContext *Context) {
- if (isa<CXXForRangeStmt>(Statement))
- return CXXLoopBound != nullptr;
- // Too many possibilities in a while statement, so always recommend partial
- // unrolling for these.
- if (isa<WhileStmt, DoStmt>(Statement))
- return false;
- // The last loop type is a for loop.
- const auto *ForLoop = cast<ForStmt>(Statement);
- const Stmt *Initializer = ForLoop->getInit();
- const Expr *Conditional = ForLoop->getCond();
- const Expr *Increment = ForLoop->getInc();
- if (!Initializer || !Conditional || !Increment)
- return false;
- // If the loop variable value isn't known, loop bounds are unknown.
- if (const auto *InitDeclStatement = dyn_cast<DeclStmt>(Initializer)) {
- if (const auto *VariableDecl =
- dyn_cast<VarDecl>(InitDeclStatement->getSingleDecl())) {
- APValue *Evaluation = VariableDecl->evaluateValue();
- if (!Evaluation || !Evaluation->hasValue())
- return false;
- }
- }
- // If increment is unary and not one of ++ and --, loop bounds are unknown.
- if (const auto *Op = dyn_cast<UnaryOperator>(Increment))
- if (!Op->isIncrementDecrementOp())
- return false;
- if (const auto *BinaryOp = dyn_cast<BinaryOperator>(Conditional)) {
- const Expr *LHS = BinaryOp->getLHS();
- const Expr *RHS = BinaryOp->getRHS();
- // If both sides are value dependent or constant, loop bounds are unknown.
- return LHS->isEvaluatable(*Context) != RHS->isEvaluatable(*Context);
- }
- return false; // If it's not a binary operator, loop bounds are unknown.
- }
- const Expr *UnrollLoopsCheck::getCondExpr(const Stmt *Statement) {
- if (const auto *ForLoop = dyn_cast<ForStmt>(Statement))
- return ForLoop->getCond();
- if (const auto *WhileLoop = dyn_cast<WhileStmt>(Statement))
- return WhileLoop->getCond();
- if (const auto *DoWhileLoop = dyn_cast<DoStmt>(Statement))
- return DoWhileLoop->getCond();
- if (const auto *CXXRangeLoop = dyn_cast<CXXForRangeStmt>(Statement))
- return CXXRangeLoop->getCond();
- llvm_unreachable("Unknown loop");
- }
- bool UnrollLoopsCheck::hasLargeNumIterations(const Stmt *Statement,
- const IntegerLiteral *CXXLoopBound,
- const ASTContext *Context) {
- // Because hasKnownBounds is called before this, if this is true, then
- // CXXLoopBound is also matched.
- if (isa<CXXForRangeStmt>(Statement)) {
- assert(CXXLoopBound && "CXX ranged for loop has no loop bound");
- return exprHasLargeNumIterations(CXXLoopBound, Context);
- }
- const auto *ForLoop = cast<ForStmt>(Statement);
- const Stmt *Initializer = ForLoop->getInit();
- const Expr *Conditional = ForLoop->getCond();
- const Expr *Increment = ForLoop->getInc();
- int InitValue;
- // If the loop variable value isn't known, we can't know the loop bounds.
- if (const auto *InitDeclStatement = dyn_cast<DeclStmt>(Initializer)) {
- if (const auto *VariableDecl =
- dyn_cast<VarDecl>(InitDeclStatement->getSingleDecl())) {
- APValue *Evaluation = VariableDecl->evaluateValue();
- if (!Evaluation || !Evaluation->isInt())
- return true;
- InitValue = Evaluation->getInt().getExtValue();
- }
- }
- int EndValue;
- const auto *BinaryOp = cast<BinaryOperator>(Conditional);
- if (!extractValue(EndValue, BinaryOp, Context))
- return true;
- double Iterations;
- // If increment is unary and not one of ++, --, we can't know the loop bounds.
- if (const auto *Op = dyn_cast<UnaryOperator>(Increment)) {
- if (Op->isIncrementOp())
- Iterations = EndValue - InitValue;
- else if (Op->isDecrementOp())
- Iterations = InitValue - EndValue;
- else
- llvm_unreachable("Unary operator neither increment nor decrement");
- }
- // If increment is binary and not one of +, -, *, /, we can't know the loop
- // bounds.
- if (const auto *Op = dyn_cast<BinaryOperator>(Increment)) {
- int ConstantValue;
- if (!extractValue(ConstantValue, Op, Context))
- return true;
- switch (Op->getOpcode()) {
- case (BO_AddAssign):
- Iterations = ceil(float(EndValue - InitValue) / ConstantValue);
- break;
- case (BO_SubAssign):
- Iterations = ceil(float(InitValue - EndValue) / ConstantValue);
- break;
- case (BO_MulAssign):
- Iterations = 1 + (log(EndValue) - log(InitValue)) / log(ConstantValue);
- break;
- case (BO_DivAssign):
- Iterations = 1 + (log(InitValue) - log(EndValue)) / log(ConstantValue);
- break;
- default:
- // All other operators are not handled; assume large bounds.
- return true;
- }
- }
- return Iterations > MaxLoopIterations;
- }
- bool UnrollLoopsCheck::extractValue(int &Value, const BinaryOperator *Op,
- const ASTContext *Context) {
- const Expr *LHS = Op->getLHS();
- const Expr *RHS = Op->getRHS();
- Expr::EvalResult Result;
- if (LHS->isEvaluatable(*Context))
- LHS->EvaluateAsRValue(Result, *Context);
- else if (RHS->isEvaluatable(*Context))
- RHS->EvaluateAsRValue(Result, *Context);
- else
- return false; // Cannot evaluate either side.
- if (!Result.Val.isInt())
- return false; // Cannot check number of iterations, return false to be
- // safe.
- Value = Result.Val.getInt().getExtValue();
- return true;
- }
- bool UnrollLoopsCheck::exprHasLargeNumIterations(const Expr *Expression,
- const ASTContext *Context) {
- Expr::EvalResult Result;
- if (Expression->EvaluateAsRValue(Result, *Context)) {
- if (!Result.Val.isInt())
- return false; // Cannot check number of iterations, return false to be
- // safe.
- // The following assumes values go from 0 to Val in increments of 1.
- return Result.Val.getInt() > MaxLoopIterations;
- }
- // Cannot evaluate Expression as an r-value, so cannot check number of
- // iterations.
- return false;
- }
- void UnrollLoopsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
- Options.store(Opts, "MaxLoopIterations", MaxLoopIterations);
- }
- } // namespace clang::tidy::altera
|