xref: /llvm-project/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.cpp (revision 76bbbcb41bcf4a1d7a26bb11b78cf97b60ea7d4b)
1 //===--- SIMDIntrinsicsCheck.cpp - clang-tidy------------------------------===//
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 "SIMDIntrinsicsCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Basic/TargetInfo.h"
13 #include "llvm/ADT/StringMap.h"
14 #include "llvm/Support/ManagedStatic.h"
15 #include "llvm/Support/Regex.h"
16 #include "llvm/TargetParser/Triple.h"
17 
18 using namespace clang::ast_matchers;
19 
20 namespace clang::tidy::portability {
21 
22 namespace {
23 
24 // If the callee has parameter of VectorType or pointer to VectorType,
25 // or the return type is VectorType, we consider it a vector function
26 // and a candidate for checking.
AST_MATCHER(FunctionDecl,isVectorFunction)27 AST_MATCHER(FunctionDecl, isVectorFunction) {
28   bool IsVector = Node.getReturnType()->isVectorType();
29   for (const ParmVarDecl *Parm : Node.parameters()) {
30     QualType Type = Parm->getType();
31     if (Type->isPointerType())
32       Type = Type->getPointeeType();
33     if (Type->isVectorType())
34       IsVector = true;
35   }
36   return IsVector;
37 }
38 
39 } // namespace
40 
trySuggestPpc(StringRef Name)41 static StringRef trySuggestPpc(StringRef Name) {
42   if (!Name.consume_front("vec_"))
43     return {};
44 
45   return llvm::StringSwitch<StringRef>(Name)
46       // [simd.alg]
47       .Case("max", "$std::max")
48       .Case("min", "$std::min")
49       // [simd.binary]
50       .Case("add", "operator+ on $simd objects")
51       .Case("sub", "operator- on $simd objects")
52       .Case("mul", "operator* on $simd objects")
53       .Default({});
54 }
55 
trySuggestX86(StringRef Name)56 static StringRef trySuggestX86(StringRef Name) {
57   if (!(Name.consume_front("_mm_") || Name.consume_front("_mm256_") ||
58         Name.consume_front("_mm512_")))
59     return {};
60 
61   // [simd.alg]
62   if (Name.starts_with("max_"))
63     return "$simd::max";
64   if (Name.starts_with("min_"))
65     return "$simd::min";
66 
67   // [simd.binary]
68   if (Name.starts_with("add_"))
69     return "operator+ on $simd objects";
70   if (Name.starts_with("sub_"))
71     return "operator- on $simd objects";
72   if (Name.starts_with("mul_"))
73     return "operator* on $simd objects";
74 
75   return {};
76 }
77 
SIMDIntrinsicsCheck(StringRef Name,ClangTidyContext * Context)78 SIMDIntrinsicsCheck::SIMDIntrinsicsCheck(StringRef Name,
79                                          ClangTidyContext *Context)
80     : ClangTidyCheck(Name, Context), Std(Options.get("Std", "")),
81       Suggest(Options.get("Suggest", false)) {}
82 
storeOptions(ClangTidyOptions::OptionMap & Opts)83 void SIMDIntrinsicsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
84   Options.store(Opts, "Std", Std);
85   Options.store(Opts, "Suggest", Suggest);
86 }
87 
registerMatchers(MatchFinder * Finder)88 void SIMDIntrinsicsCheck::registerMatchers(MatchFinder *Finder) {
89   // If Std is not specified, infer it from the language options.
90   // libcxx implementation backports it to C++11 std::experimental::simd.
91   if (Std.empty())
92     Std = getLangOpts().CPlusPlus20 ? "std" : "std::experimental";
93 
94   Finder->addMatcher(callExpr(callee(functionDecl(
95                                   matchesName("^::(_mm_|_mm256_|_mm512_|vec_)"),
96                                   isVectorFunction())),
97                               unless(isExpansionInSystemHeader()))
98                          .bind("call"),
99                      this);
100 }
101 
check(const MatchFinder::MatchResult & Result)102 void SIMDIntrinsicsCheck::check(const MatchFinder::MatchResult &Result) {
103   const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
104   assert(Call != nullptr);
105   const FunctionDecl *Callee = Call->getDirectCallee();
106   if (!Callee)
107     return;
108 
109   StringRef Old = Callee->getName();
110   StringRef New;
111   llvm::Triple::ArchType Arch =
112       Result.Context->getTargetInfo().getTriple().getArch();
113 
114   // We warn or suggest if this SIMD intrinsic function has a std::simd
115   // replacement.
116   switch (Arch) {
117   default:
118     break;
119   case llvm::Triple::ppc:
120   case llvm::Triple::ppc64:
121   case llvm::Triple::ppc64le:
122     New = trySuggestPpc(Old);
123     break;
124   case llvm::Triple::x86:
125   case llvm::Triple::x86_64:
126     New = trySuggestX86(Old);
127     break;
128   }
129 
130   // We have found a std::simd replacement.
131   if (!New.empty()) {
132     // If Suggest is true, give a P0214 alternative, otherwise point it out it
133     // is non-portable.
134     if (Suggest) {
135       static const llvm::Regex StdRegex("\\$std"), SimdRegex("\\$simd");
136       diag(Call->getExprLoc(), "'%0' can be replaced by %1")
137           << Old
138           << SimdRegex.sub(SmallString<32>({Std, "::simd"}),
139                            StdRegex.sub(Std, New));
140     } else {
141       diag(Call->getExprLoc(), "'%0' is a non-portable %1 intrinsic function")
142           << Old << llvm::Triple::getArchTypeName(Arch);
143     }
144   }
145 }
146 
147 } // namespace clang::tidy::portability
148