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. |
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 | |
41 | static StringRef trySuggestPpc(StringRef Name) { |
42 | if (!Name.consume_front(Prefix: "vec_" )) |
43 | return {}; |
44 | |
45 | return llvm::StringSwitch<StringRef>(Name) |
46 | // [simd.alg] |
47 | .Case(S: "max" , Value: "$std::max" ) |
48 | .Case(S: "min" , Value: "$std::min" ) |
49 | // [simd.binary] |
50 | .Case(S: "add" , Value: "operator+ on $simd objects" ) |
51 | .Case(S: "sub" , Value: "operator- on $simd objects" ) |
52 | .Case(S: "mul" , Value: "operator* on $simd objects" ) |
53 | .Default(Value: {}); |
54 | } |
55 | |
56 | static StringRef trySuggestX86(StringRef Name) { |
57 | if (!(Name.consume_front(Prefix: "_mm_" ) || Name.consume_front(Prefix: "_mm256_" ) || |
58 | Name.consume_front(Prefix: "_mm512_" ))) |
59 | return {}; |
60 | |
61 | // [simd.alg] |
62 | if (Name.starts_with(Prefix: "max_" )) |
63 | return "$simd::max" ; |
64 | if (Name.starts_with(Prefix: "min_" )) |
65 | return "$simd::min" ; |
66 | |
67 | // [simd.binary] |
68 | if (Name.starts_with(Prefix: "add_" )) |
69 | return "operator+ on $simd objects" ; |
70 | if (Name.starts_with(Prefix: "sub_" )) |
71 | return "operator- on $simd objects" ; |
72 | if (Name.starts_with(Prefix: "mul_" )) |
73 | return "operator* on $simd objects" ; |
74 | |
75 | return {}; |
76 | } |
77 | |
78 | SIMDIntrinsicsCheck::SIMDIntrinsicsCheck(StringRef Name, |
79 | ClangTidyContext *Context) |
80 | : ClangTidyCheck(Name, Context), Std(Options.get(LocalName: "Std" , Default: "" )), |
81 | Suggest(Options.get(LocalName: "Suggest" , Default: false)) {} |
82 | |
83 | void SIMDIntrinsicsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
84 | Options.store(Options&: Opts, LocalName: "Std" , Value: Std); |
85 | Options.store(Options&: Opts, LocalName: "Suggest" , Value: Suggest); |
86 | } |
87 | |
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(NodeMatch: callExpr(callee(InnerMatcher: functionDecl( |
95 | matchesName(RegExp: "^::(_mm_|_mm256_|_mm512_|vec_)" ), |
96 | isVectorFunction())), |
97 | unless(isExpansionInSystemHeader())) |
98 | .bind(ID: "call" ), |
99 | Action: this); |
100 | } |
101 | |
102 | void SIMDIntrinsicsCheck::check(const MatchFinder::MatchResult &Result) { |
103 | const auto *Call = Result.Nodes.getNodeAs<CallExpr>(ID: "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(Name: Old); |
123 | break; |
124 | case llvm::Triple::x86: |
125 | case llvm::Triple::x86_64: |
126 | New = trySuggestX86(Name: 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(Repl: SmallString<32>({Std, "::simd" }), |
139 | String: StdRegex.sub(Repl: Std, String: New)); |
140 | } else { |
141 | diag(Call->getExprLoc(), "'%0' is a non-portable %1 intrinsic function" ) |
142 | << Old << llvm::Triple::getArchTypeName(Kind: Arch); |
143 | } |
144 | } |
145 | } |
146 | |
147 | } // namespace clang::tidy::portability |
148 | |