1 | //===- VPIntrinsicTest.cpp - VPIntrinsic unit tests ---------===// |
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 "llvm/ADT/SmallVector.h" |
10 | #include "llvm/AsmParser/Parser.h" |
11 | #include "llvm/CodeGen/ISDOpcodes.h" |
12 | #include "llvm/IR/Constants.h" |
13 | #include "llvm/IR/IRBuilder.h" |
14 | #include "llvm/IR/IntrinsicInst.h" |
15 | #include "llvm/IR/LLVMContext.h" |
16 | #include "llvm/IR/Module.h" |
17 | #include "llvm/IR/Verifier.h" |
18 | #include "llvm/Support/SourceMgr.h" |
19 | #include "gtest/gtest.h" |
20 | #include <optional> |
21 | #include <sstream> |
22 | |
23 | using namespace llvm; |
24 | |
25 | namespace { |
26 | |
27 | static const char *ReductionIntOpcodes[] = { |
28 | "add" , "mul" , "and" , "or" , "xor" , "smin" , "smax" , "umin" , "umax" }; |
29 | |
30 | static const char *ReductionFPOpcodes[] = {"fadd" , "fmul" , "fmin" , "fmax" }; |
31 | |
32 | class VPIntrinsicTest : public testing::Test { |
33 | protected: |
34 | LLVMContext Context; |
35 | |
36 | VPIntrinsicTest() : Context() {} |
37 | |
38 | LLVMContext C; |
39 | SMDiagnostic Err; |
40 | |
41 | std::unique_ptr<Module> createVPDeclarationModule() { |
42 | const char *BinaryIntOpcodes[] = {"add" , "sub" , "mul" , "sdiv" , "srem" , |
43 | "udiv" , "urem" , "and" , "xor" , "or" , |
44 | "ashr" , "lshr" , "shl" , "smin" , "smax" , |
45 | "umin" , "umax" }; |
46 | std::stringstream Str; |
47 | for (const char *BinaryIntOpcode : BinaryIntOpcodes) |
48 | Str << " declare <8 x i32> @llvm.vp." << BinaryIntOpcode |
49 | << ".v8i32(<8 x i32>, <8 x i32>, <8 x i1>, i32) " ; |
50 | |
51 | const char *BinaryFPOpcodes[] = {"fadd" , "fsub" , "fmul" , "fdiv" , |
52 | "frem" , "minnum" , "maxnum" , "minimum" , |
53 | "maximum" , "copysign" }; |
54 | for (const char *BinaryFPOpcode : BinaryFPOpcodes) |
55 | Str << " declare <8 x float> @llvm.vp." << BinaryFPOpcode |
56 | << ".v8f32(<8 x float>, <8 x float>, <8 x i1>, i32) " ; |
57 | |
58 | Str << " declare <8 x float> @llvm.vp.floor.v8f32(<8 x float>, <8 x i1>, " |
59 | "i32)" ; |
60 | Str << " declare <8 x float> @llvm.vp.round.v8f32(<8 x float>, <8 x i1>, " |
61 | "i32)" ; |
62 | Str << " declare <8 x float> @llvm.vp.roundeven.v8f32(<8 x float>, <8 x " |
63 | "i1>, " |
64 | "i32)" ; |
65 | Str << " declare <8 x float> @llvm.vp.roundtozero.v8f32(<8 x float>, <8 x " |
66 | "i1>, " |
67 | "i32)" ; |
68 | Str << " declare <8 x float> @llvm.vp.rint.v8f32(<8 x float>, <8 x i1>, " |
69 | "i32)" ; |
70 | Str << " declare <8 x float> @llvm.vp.nearbyint.v8f32(<8 x float>, <8 x " |
71 | "i1>, " |
72 | "i32)" ; |
73 | Str << " declare <8 x float> @llvm.vp.ceil.v8f32(<8 x float>, <8 x i1>, " |
74 | "i32)" ; |
75 | Str << " declare <8 x i32> @llvm.vp.lrint.v8i32.v8f32(<8 x float>, " |
76 | "<8 x i1>, i32)" ; |
77 | Str << " declare <8 x i64> @llvm.vp.llrint.v8i64.v8f32(<8 x float>, " |
78 | "<8 x i1>, i32)" ; |
79 | Str << " declare <8 x float> @llvm.vp.fneg.v8f32(<8 x float>, <8 x i1>, " |
80 | "i32)" ; |
81 | Str << " declare <8 x float> @llvm.vp.fabs.v8f32(<8 x float>, <8 x i1>, " |
82 | "i32)" ; |
83 | Str << " declare <8 x float> @llvm.vp.sqrt.v8f32(<8 x float>, <8 x i1>, " |
84 | "i32)" ; |
85 | Str << " declare <8 x float> @llvm.vp.fma.v8f32(<8 x float>, <8 x float>, " |
86 | "<8 x float>, <8 x i1>, i32) " ; |
87 | Str << " declare <8 x float> @llvm.vp.fmuladd.v8f32(<8 x float>, " |
88 | "<8 x float>, <8 x float>, <8 x i1>, i32) " ; |
89 | |
90 | Str << " declare void @llvm.vp.store.v8i32.p0v8i32(<8 x i32>, <8 x i32>*, " |
91 | "<8 x i1>, i32) " ; |
92 | Str << "declare void " |
93 | "@llvm.experimental.vp.strided.store.v8i32.i32(<8 x i32>, " |
94 | "i32*, i32, <8 x i1>, i32) " ; |
95 | Str << "declare void " |
96 | "@llvm.experimental.vp.strided.store.v8i32.p1i32.i32(<8 x i32>, " |
97 | "i32 addrspace(1)*, i32, <8 x i1>, i32) " ; |
98 | Str << " declare void @llvm.vp.scatter.v8i32.v8p0i32(<8 x i32>, <8 x " |
99 | "i32*>, <8 x i1>, i32) " ; |
100 | Str << " declare <8 x i32> @llvm.vp.load.v8i32.p0v8i32(<8 x i32>*, <8 x " |
101 | "i1>, i32) " ; |
102 | Str << "declare <8 x i32> " |
103 | "@llvm.experimental.vp.strided.load.v8i32.i32(i32*, i32, <8 " |
104 | "x i1>, i32) " ; |
105 | Str << "declare <8 x i32> " |
106 | "@llvm.experimental.vp.strided.load.v8i32.p1i32.i32(i32 " |
107 | "addrspace(1)*, i32, <8 x i1>, i32) " ; |
108 | Str << " declare <8 x i32> @llvm.vp.gather.v8i32.v8p0i32(<8 x i32*>, <8 x " |
109 | "i1>, i32) " ; |
110 | |
111 | for (const char *ReductionOpcode : ReductionIntOpcodes) |
112 | Str << " declare i32 @llvm.vp.reduce." << ReductionOpcode |
113 | << ".v8i32(i32, <8 x i32>, <8 x i1>, i32) " ; |
114 | |
115 | for (const char *ReductionOpcode : ReductionFPOpcodes) |
116 | Str << " declare float @llvm.vp.reduce." << ReductionOpcode |
117 | << ".v8f32(float, <8 x float>, <8 x i1>, i32) " ; |
118 | |
119 | Str << " declare <8 x i32> @llvm.vp.merge.v8i32(<8 x i1>, <8 x i32>, <8 x " |
120 | "i32>, i32)" ; |
121 | Str << " declare <8 x i32> @llvm.vp.select.v8i32(<8 x i1>, <8 x i32>, <8 x " |
122 | "i32>, i32)" ; |
123 | Str << " declare <8 x i1> @llvm.vp.is.fpclass.v8f32(<8 x float>, i32, <8 x " |
124 | "i1>, i32)" ; |
125 | Str << " declare <8 x i32> @llvm.experimental.vp.splice.v8i32(<8 x " |
126 | "i32>, <8 x i32>, i32, <8 x i1>, i32, i32) " ; |
127 | |
128 | Str << " declare <8 x i32> @llvm.vp.fptoui.v8i32" |
129 | << ".v8f32(<8 x float>, <8 x i1>, i32) " ; |
130 | Str << " declare <8 x i32> @llvm.vp.fptosi.v8i32" |
131 | << ".v8f32(<8 x float>, <8 x i1>, i32) " ; |
132 | Str << " declare <8 x float> @llvm.vp.uitofp.v8f32" |
133 | << ".v8i32(<8 x i32>, <8 x i1>, i32) " ; |
134 | Str << " declare <8 x float> @llvm.vp.sitofp.v8f32" |
135 | << ".v8i32(<8 x i32>, <8 x i1>, i32) " ; |
136 | Str << " declare <8 x float> @llvm.vp.fptrunc.v8f32" |
137 | << ".v8f64(<8 x double>, <8 x i1>, i32) " ; |
138 | Str << " declare <8 x double> @llvm.vp.fpext.v8f64" |
139 | << ".v8f32(<8 x float>, <8 x i1>, i32) " ; |
140 | Str << " declare <8 x i32> @llvm.vp.trunc.v8i32" |
141 | << ".v8i64(<8 x i64>, <8 x i1>, i32) " ; |
142 | Str << " declare <8 x i64> @llvm.vp.zext.v8i64" |
143 | << ".v8i32(<8 x i32>, <8 x i1>, i32) " ; |
144 | Str << " declare <8 x i64> @llvm.vp.sext.v8i64" |
145 | << ".v8i32(<8 x i32>, <8 x i1>, i32) " ; |
146 | Str << " declare <8 x i32> @llvm.vp.ptrtoint.v8i32" |
147 | << ".v8p0i32(<8 x i32*>, <8 x i1>, i32) " ; |
148 | Str << " declare <8 x i32*> @llvm.vp.inttoptr.v8p0i32" |
149 | << ".v8i32(<8 x i32>, <8 x i1>, i32) " ; |
150 | |
151 | Str << " declare <8 x i1> @llvm.vp.fcmp.v8f32" |
152 | << "(<8 x float>, <8 x float>, metadata, <8 x i1>, i32) " ; |
153 | Str << " declare <8 x i1> @llvm.vp.icmp.v8i16" |
154 | << "(<8 x i16>, <8 x i16>, metadata, <8 x i1>, i32) " ; |
155 | |
156 | Str << " declare <8 x i32> @llvm.experimental.vp.reverse.v8i32(<8 x i32>, " |
157 | "<8 x i1>, i32) " ; |
158 | Str << " declare <8 x i16> @llvm.vp.abs.v8i16" |
159 | << "(<8 x i16>, i1 immarg, <8 x i1>, i32) " ; |
160 | Str << " declare <8 x i16> @llvm.vp.bitreverse.v8i16" |
161 | << "(<8 x i16>, <8 x i1>, i32) " ; |
162 | Str << " declare <8 x i16> @llvm.vp.bswap.v8i16" |
163 | << "(<8 x i16>, <8 x i1>, i32) " ; |
164 | Str << " declare <8 x i16> @llvm.vp.ctpop.v8i16" |
165 | << "(<8 x i16>, <8 x i1>, i32) " ; |
166 | Str << " declare <8 x i16> @llvm.vp.ctlz.v8i16" |
167 | << "(<8 x i16>, i1 immarg, <8 x i1>, i32) " ; |
168 | Str << " declare <8 x i16> @llvm.vp.cttz.v8i16" |
169 | << "(<8 x i16>, i1 immarg, <8 x i1>, i32) " ; |
170 | Str << " declare <8 x i16> @llvm.vp.sadd.sat.v8i16" |
171 | << "(<8 x i16>, <8 x i16>, <8 x i1>, i32) " ; |
172 | Str << " declare <8 x i16> @llvm.vp.uadd.sat.v8i16" |
173 | << "(<8 x i16>, <8 x i16>, <8 x i1>, i32) " ; |
174 | Str << " declare <8 x i16> @llvm.vp.ssub.sat.v8i16" |
175 | << "(<8 x i16>, <8 x i16>, <8 x i1>, i32) " ; |
176 | Str << " declare <8 x i16> @llvm.vp.usub.sat.v8i16" |
177 | << "(<8 x i16>, <8 x i16>, <8 x i1>, i32) " ; |
178 | Str << " declare <8 x i16> @llvm.vp.fshl.v8i16" |
179 | << "(<8 x i16>, <8 x i16>, <8 x i16>, <8 x i1>, i32) " ; |
180 | Str << " declare <8 x i16> @llvm.vp.fshr.v8i16" |
181 | << "(<8 x i16>, <8 x i16>, <8 x i16>, <8 x i1>, i32) " ; |
182 | |
183 | return parseAssemblyString(AsmString: Str.str(), Err, Context&: C); |
184 | } |
185 | }; |
186 | |
187 | /// Check that the property scopes include/llvm/IR/VPIntrinsics.def are closed. |
188 | TEST_F(VPIntrinsicTest, VPIntrinsicsDefScopes) { |
189 | std::optional<Intrinsic::ID> ScopeVPID; |
190 | #define BEGIN_REGISTER_VP_INTRINSIC(VPID, ...) \ |
191 | ASSERT_FALSE(ScopeVPID.has_value()); \ |
192 | ScopeVPID = Intrinsic::VPID; |
193 | #define END_REGISTER_VP_INTRINSIC(VPID) \ |
194 | ASSERT_TRUE(ScopeVPID.has_value()); \ |
195 | ASSERT_EQ(*ScopeVPID, Intrinsic::VPID); \ |
196 | ScopeVPID = std::nullopt; |
197 | |
198 | std::optional<ISD::NodeType> ScopeOPC; |
199 | #define BEGIN_REGISTER_VP_SDNODE(SDOPC, ...) \ |
200 | ASSERT_FALSE(ScopeOPC.has_value()); \ |
201 | ScopeOPC = ISD::SDOPC; |
202 | #define END_REGISTER_VP_SDNODE(SDOPC) \ |
203 | ASSERT_TRUE(ScopeOPC.has_value()); \ |
204 | ASSERT_EQ(*ScopeOPC, ISD::SDOPC); \ |
205 | ScopeOPC = std::nullopt; |
206 | #include "llvm/IR/VPIntrinsics.def" |
207 | |
208 | ASSERT_FALSE(ScopeVPID.has_value()); |
209 | ASSERT_FALSE(ScopeOPC.has_value()); |
210 | } |
211 | |
212 | /// Check that every VP intrinsic in the test module is recognized as a VP |
213 | /// intrinsic. |
214 | TEST_F(VPIntrinsicTest, VPModuleComplete) { |
215 | std::unique_ptr<Module> M = createVPDeclarationModule(); |
216 | assert(M); |
217 | |
218 | // Check that all @llvm.vp.* functions in the module are recognized vp |
219 | // intrinsics. |
220 | std::set<Intrinsic::ID> SeenIDs; |
221 | for (const auto &VPDecl : *M) { |
222 | ASSERT_TRUE(VPDecl.isIntrinsic()); |
223 | ASSERT_TRUE(VPIntrinsic::isVPIntrinsic(VPDecl.getIntrinsicID())); |
224 | SeenIDs.insert(x: VPDecl.getIntrinsicID()); |
225 | } |
226 | |
227 | // Check that every registered VP intrinsic has an instance in the test |
228 | // module. |
229 | #define BEGIN_REGISTER_VP_INTRINSIC(VPID, ...) \ |
230 | ASSERT_TRUE(SeenIDs.count(Intrinsic::VPID)); |
231 | #include "llvm/IR/VPIntrinsics.def" |
232 | } |
233 | |
234 | /// Check that VPIntrinsic:canIgnoreVectorLengthParam() returns true |
235 | /// if the vector length parameter does not mask off any lanes. |
236 | TEST_F(VPIntrinsicTest, CanIgnoreVectorLength) { |
237 | LLVMContext C; |
238 | SMDiagnostic Err; |
239 | |
240 | std::unique_ptr<Module> M = |
241 | parseAssemblyString( |
242 | AsmString: "declare <256 x i64> @llvm.vp.mul.v256i64(<256 x i64>, <256 x i64>, <256 x i1>, i32)" |
243 | "declare <vscale x 2 x i64> @llvm.vp.mul.nxv2i64(<vscale x 2 x i64>, <vscale x 2 x i64>, <vscale x 2 x i1>, i32)" |
244 | "declare <vscale x 1 x i64> @llvm.vp.mul.nxv1i64(<vscale x 1 x i64>, <vscale x 1 x i64>, <vscale x 1 x i1>, i32)" |
245 | "declare i32 @llvm.vscale.i32()" |
246 | "define void @test_static_vlen( " |
247 | " <256 x i64> %i0, <vscale x 2 x i64> %si0x2, <vscale x 1 x i64> %si0x1," |
248 | " <256 x i64> %i1, <vscale x 2 x i64> %si1x2, <vscale x 1 x i64> %si1x1," |
249 | " <256 x i1> %m, <vscale x 2 x i1> %smx2, <vscale x 1 x i1> %smx1, i32 %vl) { " |
250 | " %r0 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 %vl)" |
251 | " %r1 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 256)" |
252 | " %r2 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 0)" |
253 | " %r3 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 7)" |
254 | " %r4 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 123)" |
255 | " %vs = call i32 @llvm.vscale.i32()" |
256 | " %vs.x2 = mul i32 %vs, 2" |
257 | " %r5 = call <vscale x 2 x i64> @llvm.vp.mul.nxv2i64(<vscale x 2 x i64> %si0x2, <vscale x 2 x i64> %si1x2, <vscale x 2 x i1> %smx2, i32 %vs.x2)" |
258 | " %r6 = call <vscale x 2 x i64> @llvm.vp.mul.nxv2i64(<vscale x 2 x i64> %si0x2, <vscale x 2 x i64> %si1x2, <vscale x 2 x i1> %smx2, i32 %vs)" |
259 | " %r7 = call <vscale x 2 x i64> @llvm.vp.mul.nxv2i64(<vscale x 2 x i64> %si0x2, <vscale x 2 x i64> %si1x2, <vscale x 2 x i1> %smx2, i32 99999)" |
260 | " %r8 = call <vscale x 1 x i64> @llvm.vp.mul.nxv1i64(<vscale x 1 x i64> %si0x1, <vscale x 1 x i64> %si1x1, <vscale x 1 x i1> %smx1, i32 %vs)" |
261 | " %r9 = call <vscale x 1 x i64> @llvm.vp.mul.nxv1i64(<vscale x 1 x i64> %si0x1, <vscale x 1 x i64> %si1x1, <vscale x 1 x i1> %smx1, i32 1)" |
262 | " %r10 = call <vscale x 1 x i64> @llvm.vp.mul.nxv1i64(<vscale x 1 x i64> %si0x1, <vscale x 1 x i64> %si1x1, <vscale x 1 x i1> %smx1, i32 %vs.x2)" |
263 | " %vs.wat = add i32 %vs, 2" |
264 | " %r11 = call <vscale x 2 x i64> @llvm.vp.mul.nxv2i64(<vscale x 2 x i64> %si0x2, <vscale x 2 x i64> %si1x2, <vscale x 2 x i1> %smx2, i32 %vs.wat)" |
265 | " ret void " |
266 | "}" , |
267 | Err, Context&: C); |
268 | |
269 | auto *F = M->getFunction(Name: "test_static_vlen" ); |
270 | assert(F); |
271 | |
272 | const bool Expected[] = {false, true, false, false, false, true, |
273 | false, false, true, false, true, false}; |
274 | const auto *ExpectedIt = std::begin(arr: Expected); |
275 | for (auto &I : F->getEntryBlock()) { |
276 | VPIntrinsic *VPI = dyn_cast<VPIntrinsic>(Val: &I); |
277 | if (!VPI) |
278 | continue; |
279 | |
280 | ASSERT_NE(ExpectedIt, std::end(Expected)); |
281 | ASSERT_EQ(*ExpectedIt, VPI->canIgnoreVectorLengthParam()); |
282 | ++ExpectedIt; |
283 | } |
284 | } |
285 | |
286 | /// Check that the argument returned by |
287 | /// VPIntrinsic::get<X>ParamPos(Intrinsic::ID) has the expected type. |
288 | TEST_F(VPIntrinsicTest, GetParamPos) { |
289 | std::unique_ptr<Module> M = createVPDeclarationModule(); |
290 | assert(M); |
291 | |
292 | for (Function &F : *M) { |
293 | ASSERT_TRUE(F.isIntrinsic()); |
294 | std::optional<unsigned> MaskParamPos = |
295 | VPIntrinsic::getMaskParamPos(IntrinsicID: F.getIntrinsicID()); |
296 | if (MaskParamPos) { |
297 | Type *MaskParamType = F.getArg(i: *MaskParamPos)->getType(); |
298 | ASSERT_TRUE(MaskParamType->isVectorTy()); |
299 | ASSERT_TRUE( |
300 | cast<VectorType>(MaskParamType)->getElementType()->isIntegerTy(1)); |
301 | } |
302 | |
303 | std::optional<unsigned> VecLenParamPos = |
304 | VPIntrinsic::getVectorLengthParamPos(IntrinsicID: F.getIntrinsicID()); |
305 | if (VecLenParamPos) { |
306 | Type *VecLenParamType = F.getArg(i: *VecLenParamPos)->getType(); |
307 | ASSERT_TRUE(VecLenParamType->isIntegerTy(32)); |
308 | } |
309 | } |
310 | } |
311 | |
312 | /// Check that going from Opcode to VP intrinsic and back results in the same |
313 | /// Opcode. |
314 | TEST_F(VPIntrinsicTest, OpcodeRoundTrip) { |
315 | std::vector<unsigned> Opcodes; |
316 | Opcodes.reserve(n: 100); |
317 | |
318 | { |
319 | #define HANDLE_INST(OCNum, OCName, Class) Opcodes.push_back(OCNum); |
320 | #include "llvm/IR/Instruction.def" |
321 | } |
322 | |
323 | unsigned FullTripCounts = 0; |
324 | for (unsigned OC : Opcodes) { |
325 | Intrinsic::ID VPID = VPIntrinsic::getForOpcode(OC); |
326 | // No equivalent VP intrinsic available. |
327 | if (VPID == Intrinsic::not_intrinsic) |
328 | continue; |
329 | |
330 | std::optional<unsigned> RoundTripOC = |
331 | VPIntrinsic::getFunctionalOpcodeForVP(ID: VPID); |
332 | // No equivalent Opcode available. |
333 | if (!RoundTripOC) |
334 | continue; |
335 | |
336 | ASSERT_EQ(*RoundTripOC, OC); |
337 | ++FullTripCounts; |
338 | } |
339 | ASSERT_NE(FullTripCounts, 0u); |
340 | } |
341 | |
342 | /// Check that going from VP intrinsic to Opcode and back results in the same |
343 | /// intrinsic id. |
344 | TEST_F(VPIntrinsicTest, IntrinsicIDRoundTrip) { |
345 | std::unique_ptr<Module> M = createVPDeclarationModule(); |
346 | assert(M); |
347 | |
348 | unsigned FullTripCounts = 0; |
349 | for (const auto &VPDecl : *M) { |
350 | auto VPID = VPDecl.getIntrinsicID(); |
351 | std::optional<unsigned> OC = VPIntrinsic::getFunctionalOpcodeForVP(ID: VPID); |
352 | |
353 | // no equivalent Opcode available |
354 | if (!OC) |
355 | continue; |
356 | |
357 | Intrinsic::ID RoundTripVPID = VPIntrinsic::getForOpcode(OC: *OC); |
358 | |
359 | ASSERT_EQ(RoundTripVPID, VPID); |
360 | ++FullTripCounts; |
361 | } |
362 | ASSERT_NE(FullTripCounts, 0u); |
363 | } |
364 | |
365 | /// Check that VPIntrinsic::getDeclarationForParams works. |
366 | TEST_F(VPIntrinsicTest, VPIntrinsicDeclarationForParams) { |
367 | std::unique_ptr<Module> M = createVPDeclarationModule(); |
368 | assert(M); |
369 | |
370 | auto OutM = std::make_unique<Module>(args: "" , args&: M->getContext()); |
371 | |
372 | for (auto &F : *M) { |
373 | auto *FuncTy = F.getFunctionType(); |
374 | |
375 | // Declare intrinsic anew with explicit types. |
376 | std::vector<Value *> Values; |
377 | for (auto *ParamTy : FuncTy->params()) |
378 | Values.push_back(x: UndefValue::get(T: ParamTy)); |
379 | |
380 | ASSERT_NE(F.getIntrinsicID(), Intrinsic::not_intrinsic); |
381 | auto *NewDecl = VPIntrinsic::getDeclarationForParams( |
382 | M: OutM.get(), F.getIntrinsicID(), ReturnType: FuncTy->getReturnType(), Params: Values); |
383 | ASSERT_TRUE(NewDecl); |
384 | |
385 | // Check that 'old decl' == 'new decl'. |
386 | ASSERT_EQ(F.getIntrinsicID(), NewDecl->getIntrinsicID()); |
387 | FunctionType::param_iterator ItNewParams = |
388 | NewDecl->getFunctionType()->param_begin(); |
389 | FunctionType::param_iterator EndItNewParams = |
390 | NewDecl->getFunctionType()->param_end(); |
391 | for (auto *ParamTy : FuncTy->params()) { |
392 | ASSERT_NE(ItNewParams, EndItNewParams); |
393 | ASSERT_EQ(*ItNewParams, ParamTy); |
394 | ++ItNewParams; |
395 | } |
396 | } |
397 | } |
398 | |
399 | /// Check that the HANDLE_VP_TO_CONSTRAINEDFP maps to an existing intrinsic with |
400 | /// the right amount of constrained-fp metadata args. |
401 | TEST_F(VPIntrinsicTest, HandleToConstrainedFP) { |
402 | #define VP_PROPERTY_CONSTRAINEDFP(HASROUND, HASEXCEPT, CFPID) \ |
403 | { \ |
404 | SmallVector<Intrinsic::IITDescriptor, 5> T; \ |
405 | Intrinsic::getIntrinsicInfoTableEntries(Intrinsic::CFPID, T); \ |
406 | unsigned NumMetadataArgs = 0; \ |
407 | for (auto TD : T) \ |
408 | NumMetadataArgs += (TD.Kind == Intrinsic::IITDescriptor::Metadata); \ |
409 | bool IsCmp = Intrinsic::CFPID == Intrinsic::experimental_constrained_fcmp; \ |
410 | ASSERT_EQ(NumMetadataArgs, (unsigned)(IsCmp + HASROUND + HASEXCEPT)); \ |
411 | } |
412 | #include "llvm/IR/VPIntrinsics.def" |
413 | } |
414 | |
415 | } // end anonymous namespace |
416 | |
417 | /// Check various properties of VPReductionIntrinsics |
418 | TEST_F(VPIntrinsicTest, VPReductions) { |
419 | LLVMContext C; |
420 | SMDiagnostic Err; |
421 | |
422 | std::stringstream Str; |
423 | Str << "declare <8 x i32> @llvm.vp.mul.v8i32(<8 x i32>, <8 x i32>, <8 x i1>, " |
424 | "i32)" ; |
425 | for (const char *ReductionOpcode : ReductionIntOpcodes) |
426 | Str << " declare i32 @llvm.vp.reduce." << ReductionOpcode |
427 | << ".v8i32(i32, <8 x i32>, <8 x i1>, i32) " ; |
428 | |
429 | for (const char *ReductionOpcode : ReductionFPOpcodes) |
430 | Str << " declare float @llvm.vp.reduce." << ReductionOpcode |
431 | << ".v8f32(float, <8 x float>, <8 x i1>, i32) " ; |
432 | |
433 | Str << "define void @test_reductions(i32 %start, <8 x i32> %val, float " |
434 | "%fpstart, <8 x float> %fpval, <8 x i1> %m, i32 %vl) {" ; |
435 | |
436 | // Mix in a regular non-reduction intrinsic to check that the |
437 | // VPReductionIntrinsic subclass works as intended. |
438 | Str << " %r0 = call <8 x i32> @llvm.vp.mul.v8i32(<8 x i32> %val, <8 x i32> " |
439 | "%val, <8 x i1> %m, i32 %vl)" ; |
440 | |
441 | unsigned Idx = 1; |
442 | for (const char *ReductionOpcode : ReductionIntOpcodes) |
443 | Str << " %r" << Idx++ << " = call i32 @llvm.vp.reduce." << ReductionOpcode |
444 | << ".v8i32(i32 %start, <8 x i32> %val, <8 x i1> %m, i32 %vl)" ; |
445 | for (const char *ReductionOpcode : ReductionFPOpcodes) |
446 | Str << " %r" << Idx++ << " = call float @llvm.vp.reduce." |
447 | << ReductionOpcode |
448 | << ".v8f32(float %fpstart, <8 x float> %fpval, <8 x i1> %m, i32 %vl)" ; |
449 | |
450 | Str << " ret void" |
451 | "}" ; |
452 | |
453 | std::unique_ptr<Module> M = parseAssemblyString(AsmString: Str.str(), Err, Context&: C); |
454 | assert(M); |
455 | |
456 | auto *F = M->getFunction(Name: "test_reductions" ); |
457 | assert(F); |
458 | |
459 | for (const auto &I : F->getEntryBlock()) { |
460 | const VPIntrinsic *VPI = dyn_cast<VPIntrinsic>(Val: &I); |
461 | if (!VPI) |
462 | continue; |
463 | |
464 | Intrinsic::ID ID = VPI->getIntrinsicID(); |
465 | const auto *VPRedI = dyn_cast<VPReductionIntrinsic>(Val: &I); |
466 | |
467 | if (!VPReductionIntrinsic::isVPReduction(ID)) { |
468 | EXPECT_EQ(VPRedI, nullptr); |
469 | EXPECT_EQ(VPReductionIntrinsic::getStartParamPos(ID).has_value(), false); |
470 | EXPECT_EQ(VPReductionIntrinsic::getVectorParamPos(ID).has_value(), false); |
471 | continue; |
472 | } |
473 | |
474 | EXPECT_EQ(VPReductionIntrinsic::getStartParamPos(ID).has_value(), true); |
475 | EXPECT_EQ(VPReductionIntrinsic::getVectorParamPos(ID).has_value(), true); |
476 | ASSERT_NE(VPRedI, nullptr); |
477 | EXPECT_EQ(VPReductionIntrinsic::getStartParamPos(ID), |
478 | VPRedI->getStartParamPos()); |
479 | EXPECT_EQ(VPReductionIntrinsic::getVectorParamPos(ID), |
480 | VPRedI->getVectorParamPos()); |
481 | EXPECT_EQ(VPRedI->getStartParamPos(), 0u); |
482 | EXPECT_EQ(VPRedI->getVectorParamPos(), 1u); |
483 | } |
484 | } |
485 | |