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