1 // Copyright (C) 2020-2023 Free Software Foundation, Inc.
2 //
3 // This file is part of the GNU ISO C++ Library. This library is free
4 // software; you can redistribute it and/or modify it under the
5 // terms of the GNU General Public License as published by the
6 // Free Software Foundation; either version 3, or (at your option)
7 // any later version.
8 //
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this library; see the file COPYING3. If not see
16 // <http://www.gnu.org/licenses/>.
17
18 #ifndef SIMD_TESTS_BITS_TEST_VALUES_H_
19 #define SIMD_TESTS_BITS_TEST_VALUES_H_
20
21 #include "verify.h"
22
23 #include <experimental/simd>
24 #include <initializer_list>
25 #include <random>
26 #include <cfenv>
27
28 template <class T, class A>
29 std::experimental::simd<T, A>
30 iif(std::experimental::simd_mask<T, A> k,
31 const typename std::experimental::simd_mask<T, A>::simd_type& t,
32 const std::experimental::simd<T, A>& f)
33 {
34 auto r = f;
35 where(k, r) = t;
36 return r;
37 }
38
39 template <class V>
40 V
41 epilogue_load(const typename V::value_type* mem, const std::size_t size)
42 {
43 const int rem = size % V::size();
44 return where(V([](int i) { return i; }) < rem, V(0))
45 .copy_from(mem + size / V::size() * V::size(),
46 std::experimental::element_aligned);
47 }
48
49 template <class V, class... F>
50 void
51 test_values(const std::initializer_list<typename V::value_type>& inputs,
52 F&&... fun_pack)
53 {
54 for (auto it = inputs.begin(); it + V::size() <= inputs.end();
55 it += V::size())
56 {
57 [](auto...) {
58 }((fun_pack(V(&it[0], std::experimental::element_aligned)), 0)...);
59 }
60 [](auto...) {
61 }((fun_pack(epilogue_load<V>(inputs.begin(), inputs.size())), 0)...);
62 }
63
64 template <class V>
65 struct RandomValues
66 {
67 using T = typename V::value_type;
68 static constexpr bool isfp = std::is_floating_point_v<T>;
69 const std::size_t count;
70
71 std::conditional_t<std::is_floating_point_v<T>,
72 std::uniform_real_distribution<T>,
73 std::uniform_int_distribution<T>>
74 dist;
75
76 const bool uniform;
77
78 const T abs_max = std::__finite_max_v<T>;
79
80 RandomValues(std::size_t count_, T min, T max)
81 : count(count_), dist(min, max), uniform(true)
82 {
83 if constexpr (std::is_floating_point_v<T>)
84 VERIFY(max - min <= std::__finite_max_v<T>);
85 }
86
87 RandomValues(std::size_t count_)
88 : count(count_), dist(isfp ? 1 : std::__finite_min_v<T>,
89 isfp ? 2 : std::__finite_max_v<T>),
90 uniform(!isfp)
91 {}
92
93 RandomValues(std::size_t count_, T abs_max_)
94 : count(count_), dist(isfp ? 1 : -abs_max_, isfp ? 2 : abs_max_),
95 uniform(!isfp), abs_max(abs_max_)
96 {}
97
98 template <typename URBG>
99 V
100 operator()(URBG& gen)
101 {
102 if constexpr (!isfp)
103 return V([&](int) { return dist(gen); });
104 else if (uniform)
105 return V([&](int) { return dist(gen); });
106 else
107 {
108 auto exp_dist
109 = std::normal_distribution<float>(0.f,
110 std::__max_exponent_v<T> * .5f);
111 return V([&](int) {
112 const T mant = dist(gen);
113 T fp = 0;
114 do {
115 const int exp = exp_dist(gen);
116 fp = std::ldexp(mant, exp);
117 } while (fp >= abs_max || fp <= std::__denorm_min_v<T>);
118 fp = gen() & 0x4 ? fp : -fp;
119 return fp;
120 });
121 }
122 }
123 };
124
125 static std::mt19937 g_mt_gen{0};
126
127 template <class V, class... F>
128 void
129 test_values(const std::initializer_list<typename V::value_type>& inputs,
130 RandomValues<V> random, F&&... fun_pack)
131 {
132 test_values<V>(inputs, fun_pack...);
133 for (size_t i = 0; i < (random.count + V::size() - 1) / V::size(); ++i)
134 {
135 [](auto...) {}((fun_pack(random(g_mt_gen)), 0)...);
136 }
137 }
138
139 template <class V, class... F>
140 void
141 test_values_2arg(const std::initializer_list<typename V::value_type>& inputs,
142 F&&... fun_pack)
143 {
144 for (auto scalar_it = inputs.begin(); scalar_it != inputs.end();
145 ++scalar_it)
146 {
147 for (auto it = inputs.begin(); it + V::size() <= inputs.end();
148 it += V::size())
149 {
150 [](auto...) {
151 }((fun_pack(V(&it[0], std::experimental::element_aligned),
152 V(*scalar_it)),
153 0)...);
154 }
155 [](auto...) {
156 }((fun_pack(epilogue_load<V>(inputs.begin(), inputs.size()),
157 V(*scalar_it)),
158 0)...);
159 }
160 }
161
162 template <class V, class... F>
163 void
164 test_values_2arg(const std::initializer_list<typename V::value_type>& inputs,
165 RandomValues<V> random, F&&... fun_pack)
166 {
167 test_values_2arg<V>(inputs, fun_pack...);
168 for (size_t i = 0; i < (random.count + V::size() - 1) / V::size(); ++i)
169 {
170 [](auto...) {}((fun_pack(random(g_mt_gen), random(g_mt_gen)), 0)...);
171 }
172 }
173
174 template <class V, class... F>
175 void
176 test_values_3arg(const std::initializer_list<typename V::value_type>& inputs,
177 F&&... fun_pack)
178 {
179 for (auto scalar_it1 = inputs.begin(); scalar_it1 != inputs.end();
180 ++scalar_it1)
181 {
182 for (auto scalar_it2 = inputs.begin(); scalar_it2 != inputs.end();
183 ++scalar_it2)
184 {
185 for (auto it = inputs.begin(); it + V::size() <= inputs.end();
186 it += V::size())
187 {
188 [](auto...) {
189 }((fun_pack(V(&it[0], std::experimental::element_aligned),
190 V(*scalar_it1), V(*scalar_it2)),
191 0)...);
192 }
193 [](auto...) {
194 }((fun_pack(epilogue_load<V>(inputs.begin(), inputs.size()),
195 V(*scalar_it1), V(*scalar_it2)),
196 0)...);
197 }
198 }
199 }
200
201 template <class V, class... F>
202 void
203 test_values_3arg(const std::initializer_list<typename V::value_type>& inputs,
204 RandomValues<V> random, F&&... fun_pack)
205 {
206 test_values_3arg<V>(inputs, fun_pack...);
207 for (size_t i = 0; i < (random.count + V::size() - 1) / V::size(); ++i)
208 {
209 [](auto...) {
210 }((fun_pack(random(g_mt_gen), random(g_mt_gen), random(g_mt_gen)),
211 0)...);
212 }
213 }
214
215 #if __GCC_IEC_559 < 2
216 // Without IEC559 we consider -0, subnormals, +/-inf, and all NaNs to be
217 // invalid (potential UB when used or "produced"). This can't use isnormal (or
218 // any other classification function), since they know about the UB.
219 template <class V>
220 typename V::mask_type
221 isvalid(V x)
222 {
223 using namespace std::experimental::parallelism_v2;
224 using namespace std::experimental::parallelism_v2::__proposed;
225 using T = typename V::value_type;
226 if constexpr (sizeof(T) <= sizeof(double))
227 {
228 using I = rebind_simd_t<__int_for_sizeof_t<T>, V>;
229 const I abs_x = simd_bit_cast<I>(abs(x));
230 const I min = simd_bit_cast<I>(V(std::__norm_min_v<T>));
231 const I max = simd_bit_cast<I>(V(std::__finite_max_v<T>));
232 return static_simd_cast<typename V::mask_type>(
233 simd_bit_cast<I>(x) == 0 || (abs_x >= min && abs_x <= max));
234 }
235 else
236 {
237 const V abs_x = abs(x);
238 const V min = std::__norm_min_v<T>;
239 // Make max non-const static to inhibit constprop. Otherwise the
240 // compiler might decide `abs_x <= max` is constexpr true, by definition
241 // (-ffinite-math-only)
242 static V max = std::__finite_max_v<T>;
243 return (x == 0 && copysign(V(1), x) == V(1))
244 || (abs_x >= min && abs_x <= max);
245 }
246 }
247
248 #define MAKE_TESTER_2(name_, reference_) \
249 [&](auto... inputs) { \
250 ((where(!isvalid(inputs), inputs) = 1), ...); \
251 const auto totest = name_(inputs...); \
252 using R = std::remove_const_t<decltype(totest)>; \
253 auto&& expected = [&](const auto&... vs) -> const R { \
254 R tmp = {}; \
255 for (std::size_t i = 0; i < R::size(); ++i) \
256 tmp[i] = reference_(vs[i]...); \
257 return tmp; \
258 }; \
259 const R expect1 = expected(inputs...); \
260 if constexpr (std::is_floating_point_v<typename R::value_type>) \
261 { \
262 ((where(!isvalid(expect1), inputs) = 1), ...); \
263 const R expect2 = expected(inputs...); \
264 ((FUZZY_COMPARE(name_(inputs...), expect2) << "\ninputs = ") \
265 << ... << inputs); \
266 } \
267 else \
268 ((COMPARE(name_(inputs...), expect1) << "\n" #name_ "(") \
269 << ... << inputs) \
270 << ")"; \
271 }
272
273 #define MAKE_TESTER_NOFPEXCEPT(name_) \
274 [&](auto... inputs) { \
275 ((where(!isvalid(inputs), inputs) = 1), ...); \
276 using R = std::remove_const_t<decltype(name_(inputs...))>; \
277 auto&& expected = [&](const auto&... vs) -> const R { \
278 R tmp = {}; \
279 for (std::size_t i = 0; i < R::size(); ++i) \
280 tmp[i] = std::name_(vs[i]...); \
281 return tmp; \
282 }; \
283 const R expect1 = expected(inputs...); \
284 if constexpr (std::is_floating_point_v<typename R::value_type>) \
285 { \
286 ((where(!isvalid(expect1), inputs) = 1), ...); \
287 std::feclearexcept(FE_ALL_EXCEPT); \
288 asm volatile(""); \
289 auto totest = name_(inputs...); \
290 asm volatile(""); \
291 ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \
292 << ... << inputs) \
293 << ")"; \
294 const R expect2 = expected(inputs...); \
295 std::feclearexcept(FE_ALL_EXCEPT); \
296 asm volatile(""); \
297 totest = name_(inputs...); \
298 asm volatile(""); \
299 ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \
300 << ... << inputs) \
301 << ")"; \
302 ((FUZZY_COMPARE(totest, expect2) << "\n" #name_ "(") << ... << inputs) \
303 << ")"; \
304 } \
305 else \
306 { \
307 std::feclearexcept(FE_ALL_EXCEPT); \
308 asm volatile(""); \
309 auto totest = name_(inputs...); \
310 asm volatile(""); \
311 ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \
312 << ... << inputs) \
313 << ")"; \
314 ((COMPARE(totest, expect1) << "\n" #name_ "(") << ... << inputs) \
315 << ")"; \
316 } \
317 }
318
319 #else
320
321 #define MAKE_TESTER_2(name_, reference_) \
322 [&](auto... inputs) { \
323 const auto totest = name_(inputs...); \
324 using R = std::remove_const_t<decltype(totest)>; \
325 auto&& expected = [&](const auto&... vs) -> const R { \
326 R tmp = {}; \
327 for (std::size_t i = 0; i < R::size(); ++i) \
328 tmp[i] = reference_(vs[i]...); \
329 return tmp; \
330 }; \
331 const R expect1 = expected(inputs...); \
332 if constexpr (std::is_floating_point_v<typename R::value_type>) \
333 { \
334 ((COMPARE(isnan(totest), isnan(expect1)) << #name_ "(") \
335 << ... << inputs) \
336 << ") = " << totest << " != " << expect1; \
337 ((where(isnan(expect1), inputs) = 0), ...); \
338 ((FUZZY_COMPARE(name_(inputs...), expected(inputs...)) \
339 << "\nclean = ") \
340 << ... << inputs); \
341 } \
342 else \
343 ((COMPARE(name_(inputs...), expect1) << "\n" #name_ "(") \
344 << ... << inputs) \
345 << ")"; \
346 }
347
348 #define MAKE_TESTER_NOFPEXCEPT(name_) \
349 [&](auto... inputs) { \
350 std::feclearexcept(FE_ALL_EXCEPT); \
351 auto totest = name_(inputs...); \
352 ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \
353 << ... << inputs) \
354 << ")"; \
355 using R = std::remove_const_t<decltype(totest)>; \
356 auto&& expected = [&](const auto&... vs) -> const R { \
357 R tmp = {}; \
358 for (std::size_t i = 0; i < R::size(); ++i) \
359 tmp[i] = std::name_(vs[i]...); \
360 return tmp; \
361 }; \
362 const R expect1 = expected(inputs...); \
363 if constexpr (std::is_floating_point_v<typename R::value_type>) \
364 { \
365 ((COMPARE(isnan(totest), isnan(expect1)) << #name_ "(") \
366 << ... << inputs) \
367 << ") = " << totest << " != " << expect1; \
368 ((where(isnan(expect1), inputs) = 0), ...); \
369 const R expect2 = expected(inputs...); \
370 std::feclearexcept(FE_ALL_EXCEPT); \
371 asm volatile(""); \
372 totest = name_(inputs...); \
373 asm volatile(""); \
374 ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \
375 << ... << inputs) \
376 << ")"; \
377 FUZZY_COMPARE(totest, expect2); \
378 } \
379 else \
380 { \
381 ((COMPARE(totest, expect1) << "\n" #name_ "(") << ... << inputs) \
382 << ")"; \
383 } \
384 }
385
386 #endif
387
388 #define MAKE_TESTER(name_) MAKE_TESTER_2(name_, std::name_)
389 #endif // SIMD_TESTS_BITS_TEST_VALUES_H_