forked from OSchip/llvm-project
278 lines
10 KiB
C++
278 lines
10 KiB
C++
//===--- 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 {
|
|
namespace tidy {
|
|
namespace 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 = dyn_cast<ForStmt>(Statement);
|
|
if (!ForLoop)
|
|
llvm_unreachable("Unknown loop");
|
|
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 (isa<BinaryOperator>(Conditional)) {
|
|
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 = dyn_cast<ForStmt>(Statement);
|
|
assert(ForLoop && "Unknown loop");
|
|
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();
|
|
}
|
|
}
|
|
assert(isa<BinaryOperator>(Conditional) &&
|
|
"Conditional is not a binary operator");
|
|
int EndValue;
|
|
const auto *BinaryOp = dyn_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 evalue 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 altera
|
|
} // namespace tidy
|
|
} // namespace clang
|