[clang-tidy] Add readability-simd-intrinsics check.

Summary:
Many architectures provide SIMD operations (e.g. x86 SSE/AVX, Power AltiVec/VSX,
ARM NEON). It is common that SIMD code implementing the same algorithm, is
written in multiple target-dispatching pieces to optimize for different
architectures or micro-architectures.

The C++ standard proposal P0214 and its extensions cover many common SIMD
operations. By migrating from target-dependent intrinsics to P0214 operations,
the SIMD code can be simplified and pieces for different targets can be unified.

Refer to http://wg21.link/p0214 for introduction and motivation for the
data-parallel standard library.

Subscribers: klimek, aemerson, mgorny, xazax.hun, kristof.beyls, hintonda, cfe-commits

Differential Revision: https://reviews.llvm.org/D42983

llvm-svn: 325272
This commit is contained in:
Fangrui Song 2018-02-15 17:56:43 +00:00
parent b2c508b43f
commit 258a959084
9 changed files with 283 additions and 0 deletions

View File

@ -24,6 +24,7 @@ add_clang_library(clangTidyReadabilityModule
RedundantStringCStrCheck.cpp
RedundantSmartptrGetCheck.cpp
RedundantStringInitCheck.cpp
SIMDIntrinsicsCheck.cpp
SimplifyBooleanExprCheck.cpp
StaticAccessedThroughInstanceCheck.cpp
StaticDefinitionInAnonymousNamespaceCheck.cpp

View File

@ -31,6 +31,7 @@
#include "RedundantSmartptrGetCheck.h"
#include "RedundantStringCStrCheck.h"
#include "RedundantStringInitCheck.h"
#include "SIMDIntrinsicsCheck.h"
#include "SimplifyBooleanExprCheck.h"
#include "StaticAccessedThroughInstanceCheck.h"
#include "StaticDefinitionInAnonymousNamespaceCheck.h"
@ -92,6 +93,8 @@ public:
"readability-redundant-string-cstr");
CheckFactories.registerCheck<RedundantStringInitCheck>(
"readability-redundant-string-init");
CheckFactories.registerCheck<SIMDIntrinsicsCheck>(
"readability-simd-intrinsics");
CheckFactories.registerCheck<SimplifyBooleanExprCheck>(
"readability-simplify-boolean-expr");
CheckFactories.registerCheck<UniqueptrDeleteReleaseCheck>(

View File

@ -0,0 +1,152 @@
//===--- SIMDIntrinsicsCheck.cpp - clang-tidy------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "SIMDIntrinsicsCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Support/Regex.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace readability {
namespace {
// If the callee has parameter of VectorType or pointer to VectorType,
// or the return type is VectorType, we consider it a vector function
// and a candidate for checking.
AST_MATCHER(FunctionDecl, isVectorFunction) {
bool IsVector = Node.getReturnType()->isVectorType();
for (const ParmVarDecl *Parm : Node.parameters()) {
QualType Type = Parm->getType();
if (Type->isPointerType())
Type = Type->getPointeeType();
if (Type->isVectorType())
IsVector = true;
}
return IsVector;
}
} // namespace
static StringRef TrySuggestPPC(StringRef Name) {
if (!Name.consume_front("vec_"))
return {};
static const llvm::StringMap<StringRef> Mapping{
// [simd.alg]
{"max", "$std::max"},
{"min", "$std::min"},
// [simd.binary]
{"add", "operator+ on $simd objects"},
{"sub", "operator- on $simd objects"},
{"mul", "operator* on $simd objects"},
};
auto It = Mapping.find(Name);
if (It != Mapping.end())
return It->second;
return {};
}
static StringRef TrySuggestX86(StringRef Name) {
if (!(Name.consume_front("_mm_") || Name.consume_front("_mm256_") ||
Name.consume_front("_mm512_")))
return {};
// [simd.alg]
if (Name.startswith("max_"))
return "$simd::max";
if (Name.startswith("min_"))
return "$simd::min";
// [simd.binary]
if (Name.startswith("add_"))
return "operator+ on $simd objects";
if (Name.startswith("sub_"))
return "operator- on $simd objects";
if (Name.startswith("mul_"))
return "operator* on $simd objects";
return {};
}
SIMDIntrinsicsCheck::SIMDIntrinsicsCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context), Suggest(Options.get("Suggest", 0) != 0) {}
void SIMDIntrinsicsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "Suggest", 0);
}
void SIMDIntrinsicsCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus11)
return;
// libcxx implementation backports it to C++11 std::experimental::simd.
Std = getLangOpts().CPlusPlus2a ? "std" : "std::experimental";
Finder->addMatcher(callExpr(callee(functionDecl(allOf(
matchesName("^::(_mm_|_mm256_|_mm512_|vec_)"),
isVectorFunction()))),
unless(isExpansionInSystemHeader()))
.bind("call"),
this);
}
void SIMDIntrinsicsCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
assert(Call != nullptr);
const FunctionDecl *Callee = Call->getDirectCallee();
if (!Callee)
return;
StringRef Old = Callee->getName();
StringRef New;
llvm::Triple::ArchType Arch =
Result.Context->getTargetInfo().getTriple().getArch();
switch (Arch) {
default:
break;
case llvm::Triple::ppc:
case llvm::Triple::ppc64:
case llvm::Triple::ppc64le:
New = TrySuggestPPC(Old);
break;
case llvm::Triple::x86:
case llvm::Triple::x86_64:
New = TrySuggestX86(Old);
break;
}
if (!New.empty()) {
std::string Message;
// If Suggest is true, give a P0214 alternative, otherwise point it out it
// is non-portable.
if (Suggest) {
Message = (Twine("'") + Old + "' can be replaced by " + New).str();
Message = llvm::Regex("\\$std").sub(Std, Message);
Message = llvm::Regex("\\$simd").sub(Std.str() + "::simd", Message);
} else {
Message = (Twine("'") + Old + "' is a non-portable " +
llvm::Triple::getArchTypeName(Arch) + " intrinsic function")
.str();
}
diag(Call->getExprLoc(), Message);
}
}
} // namespace readability
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,40 @@
//===--- SIMDIntrinsicsCheck.h - clang-tidy----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H
#include "../ClangTidy.h"
namespace clang {
namespace tidy {
namespace readability {
/// Find SIMD intrinsics calls and suggest std::experimental::simd alternatives.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/readability-simd-intrinsics.html
class SIMDIntrinsicsCheck : public ClangTidyCheck {
public:
SIMDIntrinsicsCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
const bool Suggest;
StringRef Std;
};
} // namespace readability
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H

View File

@ -88,6 +88,12 @@ Improvements to clang-tidy
Functions that have trailing returns are disallowed, except for those
using decltype specifiers and lambda with otherwise unutterable
return types.
- New `readability-simd-intrinsics
<http://clang.llvm.org/extra/clang-tidy/checks/readability-simd-intrinsics.html>`_ check
Warns if SIMD intrinsics are used which can be replaced by
``std::experimental::simd`` operations.
- New alias `hicpp-avoid-goto
<http://clang.llvm.org/extra/clang-tidy/checks/hicpp-avoid-goto.html>`_ to

View File

@ -217,6 +217,7 @@ Clang-Tidy Checks
readability-redundant-smartptr-get
readability-redundant-string-cstr
readability-redundant-string-init
readability-simd-intrinsics
readability-simplify-boolean-expr
readability-static-accessed-through-instance
readability-static-definition-in-anonymous-namespace

View File

@ -0,0 +1,42 @@
.. title:: clang-tidy - readability-simd-intrinsics
readability-simd-intrinsics
===========================
Finds SIMD intrinsics calls and suggests ``std::experimental::simd`` (`P0214`_) alternatives.
If the option ``Suggest`` is set to non-zero, for
.. code-block:: c++
_mm_add_epi32(a, b); // x86
vec_add(a, b); // Power
the check suggests an alternative:
.. code-block::
operator+ on std::experimental::simd objects
Otherwise, it just complains the intrinsics are non-portable (and there are `P0214`_ alternatives).
Many architectures provide SIMD operations (e.g. x86 SSE/AVX, Power AltiVec/VSX,
ARM NEON). It is common that SIMD code implementing the same algorithm, is
written in multiple target-dispatching pieces to optimize for different
architectures or micro-architectures.
The C++ standard proposal `P0214`_ and its extensions cover many common SIMD
operations. By migrating from target-dependent intrinsics to `P0214` operations,
the SIMD code can be simplified and pieces for different targets can be unified.
Refer to `P0214`_ for introduction and motivation for the data-parallel standard
library.
Options
-------
.. option:: Suggest
If this option is set to non-zero (default is `0`), the check will suggest P0214 alternatives, otherwise it only points out the intrinsic function is non-portable.
.. _P0214: http://wg21.link/p0214

View File

@ -0,0 +1,13 @@
// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \
// RUN: -config='{CheckOptions: [ \
// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \
// RUN: ]}' -- -target ppc64le -maltivec -std=c++11
vector int vec_add(vector int, vector int);
void PPC() {
vector int i0, i1;
vec_add(i0, i1);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'vec_add' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics]
}

View File

@ -0,0 +1,25 @@
// RUN: %check_clang_tidy %s readability-simd-intrinsics %t -- \
// RUN: -config='{CheckOptions: [ \
// RUN: {key: readability-simd-intrinsics.Suggest, value: 1} \
// RUN: ]}' -- -target x86_64 -std=c++11
typedef long long __m128i __attribute__((vector_size(16)));
typedef double __m256 __attribute__((vector_size(32)));
__m128i _mm_add_epi32(__m128i, __m128i);
__m256 _mm256_load_pd(double const *);
void _mm256_store_pd(double *, __m256);
int _mm_add_fake(int, int);
void X86() {
__m128i i0, i1;
__m256 d0;
_mm_add_epi32(i0, i1);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: '_mm_add_epi32' can be replaced by operator+ on std::experimental::simd objects [readability-simd-intrinsics]
d0 = _mm256_load_pd(0);
_mm256_store_pd(0, d0);
_mm_add_fake(0, 1);
}