Dune Core Modules (2.8.0)

test.hh
Go to the documentation of this file.
1#ifndef DUNE_COMMON_SIMD_TEST_HH
2#define DUNE_COMMON_SIMD_TEST_HH
3
10#include <algorithm>
11#include <cstddef>
12#include <iostream>
13#include <sstream>
14#include <string>
15#include <type_traits>
16#include <typeindex>
17#include <typeinfo>
18#include <unordered_set>
19#include <utility>
20
22#include <dune/common/hybridutilities.hh>
25#include <dune/common/simd/loop.hh>
27#include <dune/common/std/type_traits.hh>
28#include <dune/common/typelist.hh>
30
31namespace Dune {
32 namespace Simd {
33
34 namespace Impl {
35
36 template<class Expr, class SFINAE = void>
37 struct CanCall; // not defined unless Expr has the form Op(Args...)
38 template<class Op, class... Args, class SFINAE>
39 struct CanCall<Op(Args...), SFINAE> : std::false_type {};
40 template<class Op, class... Args>
41 struct CanCall<Op(Args...), std::void_t<std::result_of_t<Op(Args...)> > >
42 : std::true_type
43 {};
44
45 template<class T, class SFINAE = void>
46 struct LessThenComparable : std::false_type {};
47 template<class T>
48 struct LessThenComparable<T, std::void_t<decltype(std::declval<T>()
49 < std::declval<T>())> > :
50 std::true_type
51 {};
52
53 template<class Dst, class Src>
54 struct CopyConstHelper
55 {
56 using type = Dst;
57 };
58 template<class Dst, class Src>
59 struct CopyConstHelper<Dst, const Src>
60 {
61 using type = std::add_const_t<Dst>;
62 };
63
64 template<class Dst, class Src>
65 struct CopyVolatileHelper
66 {
67 using type = Dst;
68 };
69 template<class Dst, class Src>
70 struct CopyVolatileHelper<Dst, volatile Src>
71 {
72 using type = std::add_volatile_t<Dst>;
73 };
74
75 template<class Dst, class Src>
76 struct CopyReferenceHelper
77 {
78 using type = Dst;
79 };
80 template<class Dst, class Src>
81 struct CopyReferenceHelper<Dst, Src&>
82 {
83 using type = std::add_lvalue_reference_t<Dst>;
84 };
85
86 template<class Dst, class Src>
87 struct CopyReferenceHelper<Dst, Src&&>
88 {
89 using type = std::add_rvalue_reference_t<Dst>;
90 };
91
92 template<class Dst, class Src>
93 using CopyRefQual = typename CopyReferenceHelper<
94 typename CopyVolatileHelper<
95 typename CopyConstHelper<
96 std::decay_t<Dst>,
97 std::remove_reference_t<Src>
98 >::type,
99 std::remove_reference_t<Src>
100 >::type,
101 Src
102 >::type;
103
104 template<class Mark, class Types,
105 class Indices =
106 std::make_index_sequence<TypeListSize<Types>::value - 1> >
107 struct RemoveEnd;
108 template<class Mark, class Types, std::size_t... I>
109 struct RemoveEnd<Mark, Types, std::index_sequence<I...>>
110 {
111 using Back = TypeListEntry_t<TypeListSize<Types>::value - 1, Types>;
112 static_assert(std::is_same<Mark, Back>::value,
113 "TypeList not terminated by proper EndMark");
114 using type = TypeList<TypeListEntry_t<I, Types>...>;
115 };
116
117 template<class T, class List, class = void>
118 struct TypeInList;
119
120 template<class T>
121 struct TypeInList<T, TypeList<> > : std::false_type {};
122
123 template<class T, class... Rest>
124 struct TypeInList<T, TypeList<T, Rest...> > : std::true_type {};
125
126 template<class T, class Head, class... Rest>
127 struct TypeInList<T, TypeList<Head, Rest...>,
128 std::enable_if_t<!std::is_same<T, Head>::value> > :
129 TypeInList<T, TypeList<Rest...> >::type
130 {};
131
132 template<class T>
133 struct IsLoop : std::false_type {};
134 template<class T, std::size_t S>
135 struct IsLoop<LoopSIMD<T, S> > : std::true_type {};
136
137 // used inside static_assert to trick the compiler into printing a list
138 // of types:
139 //
140 // static_assert(debugTypes<V>(Std::bool_constant<condition>{}), "msg");
141 //
142 // Should include what the type `V` expands to in the error message.
143 template<class...>
144 constexpr bool debugTypes(std::true_type) { return true; }
145 template<class... Types>
146 [[deprecated]]
147 constexpr bool debugTypes(std::false_type) { return false; }
148
149 } // namespace Impl
150
152 struct EndMark {};
154
163 template<class... Types>
165 typename Impl::RemoveEnd<EndMark, TypeList<Types...> >::type;
166
168 template<class T>
169 using IsLoop = typename Impl::IsLoop<T>::type;
170
171 class UnitTest {
172 bool good_ = true;
173 std::ostream &log_ = std::cerr;
174 // records the types for which checks have started running to avoid
175 // infinite recursion
176 std::unordered_set<std::type_index> seen_;
177
179 //
180 // Helper functions
181 //
182
183 void complain(const char *file, int line, const char *func,
184 const char *expr);
185
186 void complain(const char *file, int line, const char *func,
187 const std::string &opname, const char *expr);
188
189 // This macro is defined only within this file, do not use anywhere
190 // else. Doing the actual printing in an external function dramatically
191 // reduces memory use during compilation. Defined in such a way that
192 // the call will only happen for failed checks.
193#define DUNE_SIMD_CHECK(expr) \
194 ((expr) ? void() : complain(__FILE__, __LINE__, __func__, #expr))
195
196 // the function using this macro must define a way to compute the
197 // operator name in DUNE_SIMD_OPNAME
198#define DUNE_SIMD_CHECK_OP(expr) \
199 ((expr) ? void() : complain(__FILE__, __LINE__, __func__, \
200 DUNE_SIMD_OPNAME, #expr))
201
202 // "cast" into a prvalue
203 template<class T>
204 static std::decay_t<T> prvalue(T &&t)
205 {
206 return std::forward<T>(t);
207 }
208
209 // whether the vector is 42 in all lanes
210 template<class V>
211 static bool is42(const V &v)
212 {
213 bool good = true;
214
215 for(std::size_t l = 0; l < lanes(v); ++l)
216 // need to cast in case we have a mask type
217 good &= (lane(l, v) == Scalar<V>(42));
218
219 return good;
220 }
221
222 // make a vector that contains the sequence { 1, 2, ... }
223 template<class V>
224 static V make123()
225 {
226 // initialize to avoid undefined behaviour if assigning to lane()
227 // involves lvalue-to-rvalue conversions, e.g. due to bitmask
228 // operations. Avoid using broadcast<V>() for initialization to avoid
229 // test interdependencies.
230 V vec(Scalar<V>(0));
231 for(std::size_t l = 0; l < lanes(vec); ++l)
232 lane(l, vec) = l + 1;
233 return vec;
234 }
235
236 // whether the vector contains the sequence { 1, 2, ... }
237 template<class V>
238 static bool is123(const V &v)
239 {
240 bool good = true;
241
242 for(std::size_t l = 0; l < lanes(v); ++l)
243 // need to cast in case we have a mask type
244 good &= (lane(l, v) == Scalar<V>(l+1));
245
246 return good;
247 }
248
249 template<class V>
250 static V leftVector()
251 {
252 // Avoid using broadcast<V>() for initialization to avoid test
253 // interdependencies.
254 V res(Scalar<V>(0));
255 for(std::size_t l = 0; l < lanes(res); ++l)
256 lane(l, res) = Scalar<V>(l+1);
257 return res;
258 }
259
260 template<class V>
261 static V rightVector()
262 {
263 // Avoid using broadcast<V>() for initialization to avoid test
264 // interdependencies.
265 V res(Scalar<V>(0));
266 for(std::size_t l = 0; l < lanes(res); ++l)
267 // do not exceed number of bits in char (for shifts)
268 // avoid 0 (for / and %)
269 lane(l, res) = Scalar<V>((l)%7+1);
270 return res;
271 }
272
273 template<class T>
274 static T leftScalar()
275 {
276 return T(42);
277 }
278
279 template<class T>
280 static T rightScalar()
281 {
282 // do not exceed number of bits in char (for shifts)
283 // avoid 0 (for / and %)
284 return T(5);
285 }
286
287 template<class Call>
288 using CanCall = Impl::CanCall<Call>;
289
290 template<class Dst, class Src>
291 using CopyRefQual = Impl::CopyRefQual<Dst, Src>;
292
293 // test whether the Op supports the operation on scalars. We do not use
294 // `lane()` to obtain the scalars, because that might return a proxy
295 // object, and we are interested in what exactly the scalar type can do,
296 // no a proxy that might have more overloads than needed. In addition,
297 // `lane()` may not preserve `const` and reference qualifiers.
298 template<class Op, class... Vectors>
299 using ScalarResult =
300 decltype(std::declval<Op>().
301 scalar(std::declval<CopyRefQual<Scalar<Vectors>,
302 Vectors> >()...));
303
305 //
306 // Check associated types
307 //
308
309 template<class V>
310 void checkScalar()
311 {
312 // check that the type Scalar<V> exists
313 using T = Scalar<V>;
314
315 static_assert(std::is_same<T, std::decay_t<T> >::value, "Scalar types "
316 "must not be references, and must not include "
317 "cv-qualifiers");
318 [[maybe_unused]] T a{};
319 }
320
321 template<class V>
322 [[deprecated("Warning: please include bool in the Rebinds for "
323 "simd type V, as Masks are not checked otherwise.")]]
324 void warnMissingMaskRebind(std::true_type) {}
325 template<class V>
326 void warnMissingMaskRebind(std::false_type) {}
327
328 template<class V, class Rebinds, template<class> class RebindPrune,
329 template<class> class RebindAccept, class Recurse>
330 void checkRebindOf(Recurse recurse)
331 {
332 Hybrid::forEach(Rebinds{}, [this,recurse](auto target) {
333 using T = typename decltype(target)::type;
334
335 // check that the rebound type exists
336 using W = Rebind<T, V>;
337 log_ << "Type " << className<V>() << " rebound to "
338 << className<T>() << " is " << className<W>() << std::endl;
339
340 static_assert(std::is_same<W, std::decay_t<W> >::value, "Rebound "
341 "types must not be references, and must not include "
342 "cv-qualifiers");
343 static_assert(lanes<V>() == lanes<W>(), "Rebound types must have "
344 "the same number of lanes as the original vector "
345 "types");
346 static_assert(std::is_same<T, Scalar<W> >::value, "Rebound types "
347 "must have the bound-to scalar type");
348
349 if constexpr (RebindPrune<W>{}) {
350 log_ << "Pruning check of Simd type " << className<W>()
351 << std::endl;
352 }
353 else {
354 using Impl::debugTypes;
355 static_assert(debugTypes<T, V, W>(RebindAccept<W>{}),
356 "Rebind<T, V> is W, but that is not accepted "
357 "by RebindAccept");
358 recurse(MetaType<W>{});
359 }
360 });
361
362 static_assert(std::is_same<Rebind<Scalar<V>, V>, V>::value, "A type "
363 "rebound to its own scalar type must be the same type "
364 "as the original type");
365 static_assert(std::is_same<Rebind<bool, V>, Mask<V> >::value, "A type "
366 "rebound to bool must be the mask type for that type");
367
368 constexpr bool hasBool = Impl::TypeInList<bool, Rebinds>::value;
369 warnMissingMaskRebind<V>(Std::bool_constant<!hasBool>{});
370 }
371
373 //
374 // Fundamental checks
375 //
376
377 template<class V>
378 void checkLanes()
379 {
380 // check lanes
381 static_assert(std::is_same<std::size_t, decltype(lanes<V>())>::value,
382 "return type of lanes<V>() should be std::size_t");
383 static_assert(std::is_same<std::size_t, decltype(lanes(V{}))>::value,
384 "return type of lanes(V{}) should be std::size_t");
385
386 // the result of lanes<V>() must be constexpr
387 [[maybe_unused]] constexpr auto size = lanes<V>();
388 // but the result of lanes(vec) does not need to be constexpr
389 DUNE_SIMD_CHECK(lanes<V>() == lanes(V{}));
390 }
391
392 template<class V>
393 void checkDefaultConstruct()
394 {
395 { [[maybe_unused]] V vec; }
396 { [[maybe_unused]] V vec{}; }
397 { [[maybe_unused]] V vec = {}; }
398 }
399
400 template<class V>
401 void checkLane()
402 {
403 // Avoid using broadcast<V>() for initialization to avoid test
404 // interdependencies.
405 V vec(Scalar<V>(0));
406 // check lane() on mutable lvalues
407 for(std::size_t l = 0; l < lanes(vec); ++l)
408 lane(l, vec) = l + 1;
409 for(std::size_t l = 0; l < lanes(vec); ++l)
410 DUNE_SIMD_CHECK(lane(l, vec) == Scalar<V>(l + 1));
411 using MLRes = decltype(lane(0, vec));
412 static_assert(std::is_same<MLRes, Scalar<V>&>::value ||
413 std::is_same<MLRes, std::decay_t<MLRes> >::value,
414 "Result of lane() on a mutable lvalue vector must "
415 "either be a mutable reference to a scalar of that "
416 "vector or a proxy object (which itself may not be a "
417 "reference nor const).");
418
419 // check lane() on const lvalues
420 const V &vec2 = vec;
421 for(std::size_t l = 0; l < lanes(vec); ++l)
422 DUNE_SIMD_CHECK(lane(l, vec2) == Scalar<V>(l + 1));
423 using CLRes = decltype(lane(0, vec2));
424 static_assert(std::is_same<CLRes, const Scalar<V>&>::value ||
425 std::is_same<CLRes, std::decay_t<CLRes> >::value,
426 "Result of lane() on a const lvalue vector must "
427 "either be a const lvalue reference to a scalar of that "
428 "vector or a proxy object (which itself may not be a "
429 "reference nor const).");
430 static_assert(!std::is_assignable<CLRes, Scalar<V> >::value,
431 "Result of lane() on a const lvalue vector must not be "
432 "assignable from a scalar.");
433
434 // check lane() on rvalues
435 for(std::size_t l = 0; l < lanes(vec); ++l)
436 DUNE_SIMD_CHECK(lane(l, prvalue(vec)) == Scalar<V>(l + 1));
437 using RRes = decltype(lane(0, prvalue(vec)));
438 // TODO: do we really want to allow Scalar<V>&& here? If we allow it,
439 // then `auto &&res = lane(0, vec*vec);` creates a dangling reference,
440 // and the scalar (and even the vector types) are small enough to be
441 // passed in registers anyway. On the other hand, the only comparable
442 // accessor function in the standard library that I can think of is
443 // std::get(), and that does return an rvalue reference in this
444 // situation. However, that cannot assume anything about the size of
445 // the returned types.
446 static_assert(std::is_same<RRes, Scalar<V> >::value ||
447 std::is_same<RRes, Scalar<V>&&>::value,
448 "Result of lane() on a rvalue vector V must be "
449 "Scalar<V> or Scalar<V>&&.");
450 // Can't assert non-assignable, fails for any typical class,
451 // e.g. std::complex<>. Would need to return const Scalar<V> or const
452 // Scalar<V>&&, which would inhibit moving from the return value.
453 // static_assert(!std::is_assignable<RRes, Scalar<V> >::value,
454 // "Result of lane() on a rvalue vector must not be "
455 // "assignable from a scalar.");
456 }
457
458 // check non-default constructors
459 template<class V>
460 void checkCopyMoveConstruct()
461 {
462 // elided copy/move constructors
463 { V vec (make123<V>()); DUNE_SIMD_CHECK(is123(vec)); }
464 { V vec = make123<V>() ; DUNE_SIMD_CHECK(is123(vec)); }
465 { V vec {make123<V>()}; DUNE_SIMD_CHECK(is123(vec)); }
466 { V vec = {make123<V>()}; DUNE_SIMD_CHECK(is123(vec)); }
467
468 // copy constructors
469 { V ref(make123<V>()); V vec (ref);
470 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
471 { V ref(make123<V>()); V vec = ref ;
472 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
473 { V ref(make123<V>()); V vec {ref};
474 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
475 { V ref(make123<V>()); V vec = {ref};
476 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
477 { const V ref(make123<V>()); V vec (ref);
478 DUNE_SIMD_CHECK(is123(vec)); }
479 { const V ref(make123<V>()); V vec = ref ;
480 DUNE_SIMD_CHECK(is123(vec)); }
481 { const V ref(make123<V>()); V vec {ref};
482 DUNE_SIMD_CHECK(is123(vec)); }
483 { const V ref(make123<V>()); V vec = {ref};
484 DUNE_SIMD_CHECK(is123(vec)); }
485
486 // move constructors
487 { V ref(make123<V>()); V vec (std::move(ref));
488 DUNE_SIMD_CHECK(is123(vec)); }
489 { V ref(make123<V>()); V vec = std::move(ref) ;
490 DUNE_SIMD_CHECK(is123(vec)); }
491 { V ref(make123<V>()); V vec {std::move(ref)};
492 DUNE_SIMD_CHECK(is123(vec)); }
493 { V ref(make123<V>()); V vec = {std::move(ref)};
494 DUNE_SIMD_CHECK(is123(vec)); }
495 }
496
497 template<class V>
498 void checkBroadcastVectorConstruct()
499 {
500 // broadcast copy constructors
501 { Scalar<V> ref = 42; V vec (ref);
502 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
503 { Scalar<V> ref = 42; V vec = ref ;
504 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
505 // { Scalar<V> ref = 42; V vec {ref};
506 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
507 // { Scalar<V> ref = 42; V vec = {ref};
508 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
509 { const Scalar<V> ref = 42; V vec (ref);
510 DUNE_SIMD_CHECK(is42(vec)); }
511 { const Scalar<V> ref = 42; V vec = ref ;
512 DUNE_SIMD_CHECK(is42(vec)); }
513 // { const Scalar<V> ref = 42; V vec {ref};
514 // DUNE_SIMD_CHECK(is42(vec)); }
515 // { const Scalar<V> ref = 42; V vec = {ref};
516 // DUNE_SIMD_CHECK(is42(vec)); }
517
518 // broadcast move constructors
519 { Scalar<V> ref = 42; V vec (std::move(ref));
520 DUNE_SIMD_CHECK(is42(vec)); }
521 { Scalar<V> ref = 42; V vec = std::move(ref) ;
522 DUNE_SIMD_CHECK(is42(vec)); }
523 // { Scalar<V> ref = 42; V vec {std::move(ref)};
524 // DUNE_SIMD_CHECK(is42(vec)); }
525 // { Scalar<V> ref = 42; V vec = {std::move(ref)};
526 // DUNE_SIMD_CHECK(is42(vec)); }
527 }
528
529 template<class V>
530 void checkBroadcastMaskConstruct()
531 {
532 // broadcast copy constructors
533 { Scalar<V> ref = 42; V vec (ref);
534 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
535 // { Scalar<V> ref = 42; V vec = ref ;
536 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
537 { Scalar<V> ref = 42; V vec {ref};
538 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
539 // { Scalar<V> ref = 42; V vec = {ref};
540 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
541 { const Scalar<V> ref = 42; V vec (ref);
542 DUNE_SIMD_CHECK(is42(vec)); }
543 // { const Scalar<V> ref = 42; V vec = ref ;
544 // DUNE_SIMD_CHECK(is42(vec)); }
545 { const Scalar<V> ref = 42; V vec {ref};
546 DUNE_SIMD_CHECK(is42(vec)); }
547 // { const Scalar<V> ref = 42; V vec = {ref};
548 // DUNE_SIMD_CHECK(is42(vec)); }
549
550 // broadcast move constructors
551 { Scalar<V> ref = 42; V vec (std::move(ref));
552 DUNE_SIMD_CHECK(is42(vec)); }
553 // { Scalar<V> ref = 42; V vec = std::move(ref) ;
554 // DUNE_SIMD_CHECK(is42(vec)); }
555 { Scalar<V> ref = 42; V vec {std::move(ref)};
556 DUNE_SIMD_CHECK(is42(vec)); }
557 // { Scalar<V> ref = 42; V vec = {std::move(ref)};
558 // DUNE_SIMD_CHECK(is42(vec)); }
559 }
560
561 // check the implCast function
562 template<class FromV, class ToV>
563 void checkImplCast()
564 {
565 { // lvalue arg
566 FromV fromVec = make123<FromV>();
567 auto toVec = implCast<ToV>(fromVec);
568 static_assert(std::is_same<decltype(toVec), ToV>::value,
569 "Unexpected result type for implCast<ToV>(FromV&)");
570 DUNE_SIMD_CHECK(is123(fromVec));
571 DUNE_SIMD_CHECK(is123(toVec));
572 }
573
574 { // const lvalue arg
575 const FromV fromVec = make123<FromV>();
576 auto toVec = implCast<ToV>(fromVec);
577 static_assert(std::is_same<decltype(toVec), ToV>::value,
578 "Unexpected result type for implCast<ToV>(const "
579 "FromV&)");
580 DUNE_SIMD_CHECK(is123(toVec));
581 }
582
583 { // rvalue arg
584 auto toVec = implCast<ToV>(make123<FromV>());
585 static_assert(std::is_same<decltype(toVec), ToV>::value,
586 "Unexpected result type for implCast<ToV>(FromV&&)");
587 DUNE_SIMD_CHECK(is123(toVec));
588 }
589 }
590
591 // check the implCast function
592 template<class V>
593 void checkImplCast()
594 {
595 // check against LoopSIMD
596 using LoopV = Dune::LoopSIMD<Scalar<V>, lanes<V>()>;
597
598 checkImplCast<V, V>();
599 checkImplCast<V, LoopV>();
600 checkImplCast<LoopV, V>();
601 }
602
603 // check the broadcast function
604 template<class V>
605 void checkBroadcast()
606 {
607 // broadcast function
608 { // lvalue arg
609 Scalar<V> ref = 42;
610 auto vec = broadcast<V>(ref);
611 static_assert(std::is_same<decltype(vec), V>::value,
612 "Unexpected result type for broadcast<V>()");
613 DUNE_SIMD_CHECK(is42(vec));
614 DUNE_SIMD_CHECK(ref == Scalar<V>(42));
615 }
616
617 { // const lvalue arg
618 const Scalar<V> ref = 42;
619 auto vec = broadcast<V>(ref);
620 static_assert(std::is_same<decltype(vec), V>::value,
621 "Unexpected result type for broadcast<V>()");
622 DUNE_SIMD_CHECK(is42(vec));
623 }
624
625 { // rvalue arg
626 auto vec = broadcast<V>(Scalar<V>(42));
627 static_assert(std::is_same<decltype(vec), V>::value,
628 "Unexpected result type for broadcast<V>()");
629 DUNE_SIMD_CHECK(is42(vec));
630 }
631
632 { // int arg
633 auto vec = broadcast<V>(42);
634 static_assert(std::is_same<decltype(vec), V>::value,
635 "Unexpected result type for broadcast<V>()");
636 DUNE_SIMD_CHECK(is42(vec));
637 }
638
639 { // double arg
640 auto vec = broadcast<V>(42.0);
641 static_assert(std::is_same<decltype(vec), V>::value,
642 "Unexpected result type for broadcast<V>()");
643 DUNE_SIMD_CHECK(is42(vec));
644 }
645 }
646
647 template<class V>
648 void checkBracedAssign()
649 {
650 // copy assignment
651 { V ref = make123<V>(); V vec; vec = {ref};
652 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
653 { const V ref = make123<V>(); V vec; vec = {ref};
654 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
655
656 // move assignment
657 { V vec; vec = {make123<V>()}; DUNE_SIMD_CHECK(is123(vec)); }
658 }
659
660 template<class V>
661 void checkBracedBroadcastAssign()
662 {
663 // nothing works here
664 // // broadcast copy assignment
665 // { Scalar<V> ref = 42; V vec; vec = {ref};
666 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
667 // { const Scalar<V> ref = 42; V vec; vec = {ref};
668 // DUNE_SIMD_CHECK(is42(vec)); }
669
670 // // broadcast move assignment
671 // { Scalar<V> ref = 42; V vec; vec = {std::move(ref)};
672 // DUNE_SIMD_CHECK(is42(vec)); }
673 }
674
676 //
677 // checks for unary operators
678 //
679
680#define DUNE_SIMD_POSTFIX_OP(NAME, SYMBOL) \
681 struct OpPostfix##NAME \
682 { \
683 template<class V> \
684 auto operator()(V&& v) const \
685 -> decltype(std::forward<V>(v) SYMBOL) \
686 { \
687 return std::forward<V>(v) SYMBOL; \
688 } \
689 }
690
691#define DUNE_SIMD_PREFIX_OP(NAME, SYMBOL) \
692 struct OpPrefix##NAME \
693 { \
694 template<class V> \
695 auto operator()(V&& v) const \
696 -> decltype(SYMBOL std::forward<V>(v)) \
697 { \
698 return SYMBOL std::forward<V>(v); \
699 } \
700 }
701
702 DUNE_SIMD_POSTFIX_OP(Decrement, -- );
703 DUNE_SIMD_POSTFIX_OP(Increment, ++ );
704
705 DUNE_SIMD_PREFIX_OP (Decrement, -- );
706 DUNE_SIMD_PREFIX_OP (Increment, ++ );
707
708 DUNE_SIMD_PREFIX_OP (Plus, + );
709 DUNE_SIMD_PREFIX_OP (Minus, - );
710 DUNE_SIMD_PREFIX_OP (LogicNot, ! );
711 // Do not warn about ~ being applied to bool. (1) Yes, doing that is
712 // weird, but we do want to test the weird stuff too. (2) It avoids
713 // running into <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82040> on
714 // g++-7.0 through 7.2. Also, ignore -Wpragmas to not warn about an
715 // unknown -Wbool-operation on compilers that do not know that option.
716#pragma GCC diagnostic push
717#pragma GCC diagnostic ignored "-Wpragmas"
718#pragma GCC diagnostic ignored "-Wunknown-warning-option" // clang 6.0.1
719#pragma GCC diagnostic ignored "-Wbool-operation"
720 DUNE_SIMD_PREFIX_OP (BitNot, ~ );
721#pragma GCC diagnostic pop
722
723#undef DUNE_SIMD_POSTFIX_OP
724#undef DUNE_SIMD_PREFIX_OP
725
726 template<class V, class Op>
727 std::enable_if_t<
728 CanCall<Op(decltype(lane(0, std::declval<V>())))>::value>
729 checkUnaryOpV(Op op)
730 {
731#define DUNE_SIMD_OPNAME (className<Op(V)>())
732 // arguments
733 auto val = leftVector<std::decay_t<V>>();
734
735 // copy the arguments in case V is a references
736 auto arg = val;
737 auto &&result = op(static_cast<V>(arg));
738 using T = Scalar<std::decay_t<decltype(result)> >;
739 for(std::size_t l = 0; l < lanes(val); ++l)
740 {
741 // `op` might promote the argument. This is a problem if the
742 // argument of the operation on the right of the `==` is
743 // e.g. `(unsigned short)1` and the operation is e.g. unary `-`.
744 // Then the argument is promoted to `int` before applying the
745 // negation, and the result is `(int)-1`. However, the left side of
746 // the `==` is still `(unsigned short)-1`, which typically is the
747 // same as `(unsigned short)65535`. The `==` promotes the left side
748 // before comparing, so that becomes `(int)65535`. It will then
749 // compare `(int)65535` and `(int)-1` and rightly declare them to be
750 // not equal.
751
752 // To work around this, we explicitly convert the right side of the
753 // `==` to the scalar type before comparing.
754 DUNE_SIMD_CHECK_OP
755 (lane(l, result)
756 == static_cast<T>(op(lane(l, static_cast<V>(val)))));
757 }
758 // op might modify val, verify that any such modification also happens
759 // in the vector case
760 for(std::size_t l = 0; l < lanes<std::decay_t<V> >(); ++l)
761 DUNE_SIMD_CHECK_OP(lane(l, val) == lane(l, arg));
762#undef DUNE_SIMD_OPNAME
763 }
764
765 template<class V, class Op>
766 std::enable_if_t<
767 !CanCall<Op(decltype(lane(0, std::declval<V>())))>::value>
768 checkUnaryOpV(Op op)
769 {
770 // log_ << "No " << className<Op(decltype(lane(0, std::declval<V>())))>()
771 // << std::endl
772 // << " ==> Not checking " << className<Op(V)>() << std::endl;
773 }
774
775 template<class V, class Op>
776 void checkUnaryOpsV(Op op)
777 {
778 checkUnaryOpV<V&>(op);
779 checkUnaryOpV<const V&>(op);
780 checkUnaryOpV<V&&>(op);
781 }
782
784 //
785 // checks for binary operators
786 //
787
788 // The operators contain an `operator()`, which will be invoked for both
789 // scalar and vector arguments. The function `scalar()` is used the
790 // test whether the scalar types support the operation (via
791 // `ScalarResult`). The difference is that `scalar()` should only ever
792 // receive `const`-ref-qualified version of `Scalar<V>`, while the
793 // `operator()` may also be called with proxies representing scalars.
794#define DUNE_SIMD_INFIX_OP(NAME, SYMBOL) \
795 struct OpInfix##NAME \
796 { \
797 template<class V1, class V2> \
798 decltype(auto) operator()(V1&& v1, V2&& v2) const \
799 { \
800 return std::forward<V1>(v1) SYMBOL std::forward<V2>(v2); \
801 } \
802 template<class S1, class S2> \
803 auto scalar(S1&& s1, S2&& s2) const \
804 -> decltype(std::forward<S1>(s1) SYMBOL std::forward<S2>(s2)); \
805 }
806
807 // for assign ops, accept only non-const lvalue arguments for scalars.
808 // This is needed for class scalars (e.g. std::complex) because
809 // non-const class rvalues are actually usually assignable. Though that
810 // assignment happens to a temporary, and thus is lost. Except that the
811 // tests would bind the result of the assignment to a reference. And
812 // because that result is returned from a function by reference, even
813 // though it is a temporary passed as an argument to that function,
814 // accessing the result later is undefined behaviour.
815#define DUNE_SIMD_ASSIGN_OP(NAME, SYMBOL) \
816 struct OpInfix##NAME \
817 { \
818 template<class V1, class V2> \
819 decltype(auto) operator()(V1&& v1, V2&& v2) const \
820 { \
821 return std::forward<V1>(v1) SYMBOL std::forward<V2>(v2); \
822 } \
823 template<class S1, class S2> \
824 auto scalar(S1& s1, S2&& s2) const \
825 -> decltype(s1 SYMBOL std::forward<S2>(s2)); \
826 }
827
828#define DUNE_SIMD_REPL_OP(NAME, REPLFN, SYMBOL) \
829 struct OpInfix##NAME \
830 { \
831 template<class V1, class V2> \
832 decltype(auto) operator()(V1&& v1, V2&& v2) const \
833 { \
834 return Simd::REPLFN(std::forward<V1>(v1), std::forward<V2>(v2)); \
835 } \
836 template<class S1, class S2> \
837 auto scalar(S1&& s1, S2&& s2) const \
838 -> decltype(std::forward<S1>(s1) SYMBOL std::forward<S2>(s2)); \
839 }
840
841 DUNE_SIMD_INFIX_OP(Mul, * );
842 DUNE_SIMD_INFIX_OP(Div, / );
843 DUNE_SIMD_INFIX_OP(Remainder, % );
844
845 DUNE_SIMD_INFIX_OP(Plus, + );
846 DUNE_SIMD_INFIX_OP(Minus, - );
847
848 DUNE_SIMD_INFIX_OP(LeftShift, << );
849 DUNE_SIMD_INFIX_OP(RightShift, >> );
850
851 DUNE_SIMD_INFIX_OP(Less, < );
852 DUNE_SIMD_INFIX_OP(Greater, > );
853 DUNE_SIMD_INFIX_OP(LessEqual, <= );
854 DUNE_SIMD_INFIX_OP(GreaterEqual, >= );
855
856 DUNE_SIMD_INFIX_OP(Equal, == );
857 DUNE_SIMD_INFIX_OP(NotEqual, != );
858
859 DUNE_SIMD_INFIX_OP(BitAnd, & );
860 DUNE_SIMD_INFIX_OP(BitXor, ^ );
861 DUNE_SIMD_INFIX_OP(BitOr, | );
862
863 // Those are not supported in any meaningful way by vectorclass
864 // We need to test replacement functions maskAnd() and maskOr() instead.
865 DUNE_SIMD_REPL_OP(LogicAnd, maskAnd, && );
866 DUNE_SIMD_REPL_OP(LogicOr, maskOr, || );
867
868 DUNE_SIMD_ASSIGN_OP(Assign, = );
869 DUNE_SIMD_ASSIGN_OP(AssignMul, *= );
870 DUNE_SIMD_ASSIGN_OP(AssignDiv, /= );
871 DUNE_SIMD_ASSIGN_OP(AssignRemainder, %= );
872 DUNE_SIMD_ASSIGN_OP(AssignPlus, += );
873 DUNE_SIMD_ASSIGN_OP(AssignMinus, -= );
874 DUNE_SIMD_ASSIGN_OP(AssignLeftShift, <<=);
875 DUNE_SIMD_ASSIGN_OP(AssignRightShift, >>=);
876 DUNE_SIMD_ASSIGN_OP(AssignAnd, &= );
877 DUNE_SIMD_ASSIGN_OP(AssignXor, ^= );
878 DUNE_SIMD_ASSIGN_OP(AssignOr, |= );
879
880#undef DUNE_SIMD_INFIX_OP
881#undef DUNE_SIMD_REPL_OP
882#undef DUNE_SIMD_ASSIGN_OP
883
884 // just used as a tag
885 struct OpInfixComma {};
886
887 template<class T1, class T2>
888 void checkCommaOp(const std::decay_t<T1> &val1,
889 const std::decay_t<T2> &val2)
890 {
891#define DUNE_SIMD_OPNAME (className<OpInfixComma(T1, T2)>())
892 static_assert(std::is_same<decltype((std::declval<T1>(),
893 std::declval<T2>())), T2>::value,
894 "Type and value category of the comma operator must "
895 "match that of the second operand");
896
897 // copy the arguments in case T1 or T2 are references
898 auto arg1 = val1;
899 auto arg2 = val2;
900 // Do not warn that the left side of the comma operator is unused.
901 // Seems to work for g++-4.9 and clang++-3.8. Appears to be harmless
902 // for icpc (14 and 17), and icpc does not seem to issue a warning
903 // anyway.
904#pragma GCC diagnostic push
905#pragma GCC diagnostic ignored "-Wunused-value"
906 auto &&result = (static_cast<T1>(arg1),
907 static_cast<T2>(arg2));
908#pragma GCC diagnostic pop
909 if(std::is_reference<T2>::value)
910 {
911 // comma should return the same object as the second argument for
912 // lvalues and xvalues
913 DUNE_SIMD_CHECK_OP(&result == &arg2);
914 // it should not modify any arguments
915 DUNE_SIMD_CHECK_OP(allTrue(val1 == arg1));
916 DUNE_SIMD_CHECK_OP(allTrue(val2 == arg2));
917 }
918 else
919 {
920 // comma should return the same value as the second argument for
921 // prvalues
922 DUNE_SIMD_CHECK_OP(allTrue(result == arg2));
923 // it should not modify any arguments
924 DUNE_SIMD_CHECK_OP(allTrue(val1 == arg1));
925 // second argument is a prvalue, any modifications happen to a
926 // temporary and we can't detect them
927 }
928#undef DUNE_SIMD_OPNAME
929 }
930
932 //
933 // checks for vector-vector binary operations
934 //
935
936 // We check the following candidate operation
937 //
938 // vopres = vop1 @ vop2
939 //
940 // against the reference operation
941 //
942 // arefres[l] = aref1[l] @ aref2[l] foreach l
943 //
944 // v... variables are simd-vectors and a... variables are arrays. The
945 // operation may modify the operands, but if is does the modification
946 // needs to happen in both the candidate and the reference.
947 //
948 // We do the following checks:
949 // 1. lanes(vopres) == lanes(vop1)
950 // 2. lane(l, vopres) == arefres[l] foreach l
951 // 3. lane(l, vop1) == aref1[l] foreach l
952 // 4. lane(l, vop2) == aref2[l] foreach l
953 template<class V1, class V2, class Op>
954 std::enable_if_t<Std::is_detected_v<ScalarResult, Op, V1, V2> >
955 checkBinaryOpVV(MetaType<V1>, MetaType<V2>, Op op)
956 {
957#define DUNE_SIMD_OPNAME (className<Op(V1, V2)>())
958 static_assert(std::is_same<std::decay_t<V1>, std::decay_t<V2> >::value,
959 "Internal testsystem error: called with two types that "
960 "don't decay to the same thing");
961
962 // reference arguments
963 auto vref1 = leftVector<std::decay_t<V1>>();
964 auto vref2 = rightVector<std::decay_t<V2>>();
965
966 // candidate arguments
967 auto vop1 = vref1;
968 auto vop2 = vref2;
969
970 // candidate operation
971 auto &&vopres = op(static_cast<V1>(vop1), static_cast<V2>(vop2));
972 using VR = decltype(vopres);
973
974 // check 1. lanes(vopres) == lanes(vop1)
975 static_assert(lanes<std::decay_t<VR> >() == lanes<std::decay_t<V1> >(),
976 "The result must have the same number of lanes as the "
977 "operands.");
978
979 // do the reference operation, and simultaneously
980 // check 2. lane(l, vopres) == arefres[l] foreach l
981 using T = Scalar<std::decay_t<VR> >;
982 for(auto l : range(lanes(vopres)))
983 {
984 // see the lengthy comment in `checkUnaryOpV()` as to why the
985 // `static_cast` around the `op()` is necessary
986 DUNE_SIMD_CHECK_OP
987 (lane(l, vopres)
988 == static_cast<T>(op(lane(l, static_cast<V1>(vref1)),
989 lane(l, static_cast<V2>(vref2)))));
990 }
991
992 // check 3. lane(l, vop1) == aref1[l] foreach l
993 for(auto l : range(lanes(vop1)))
994 DUNE_SIMD_CHECK_OP(lane(l, vop1) == lane(l, vref1));
995
996 // check 4. lane(l, vop2) == aref2[l] foreach l
997 for(auto l : range(lanes(vop2)))
998 DUNE_SIMD_CHECK_OP(lane(l, vop2) == lane(l, vref2));
999
1000#undef DUNE_SIMD_OPNAME
1001 }
1002
1003 template<class V1, class V2, class Op>
1004 std::enable_if_t<!Std::is_detected_v<ScalarResult, Op, V1, V2> >
1005 checkBinaryOpVV(MetaType<V1>, MetaType<V2>, Op op)
1006 {
1007 // log_ << "No " << className<Op(decltype(lane(0, std::declval<V1>())),
1008 // decltype(lane(0, std::declval<V2>())))>()
1009 // << std::endl
1010 // << " ==> Not checking " << className<Op(V1, V2)>() << std::endl;
1011 }
1012
1013 template<class V1, class V2>
1014 void checkBinaryOpVV(MetaType<V1>, MetaType<V2>, OpInfixComma)
1015 {
1016 static_assert(std::is_same<std::decay_t<V1>, std::decay_t<V2> >::value,
1017 "Internal testsystem error: called with two types that "
1018 "don't decay to the same thing");
1019
1020 checkCommaOp<V1, V2>(leftVector<std::decay_t<V1>>(),
1021 rightVector<std::decay_t<V2>>());
1022 }
1023
1025 //
1026 // checks for vector-scalar binary operations
1027 //
1028
1029 // We check the following candidate operation
1030 //
1031 // vopres = vop1 @ sop2
1032 //
1033 // against the reference operation
1034 //
1035 // arefres[l] = aref1[l] @ sref2 foreach l
1036 //
1037 // v... variables are simd-vectors, a... variables are arrays, and
1038 // s... variables are scalars. The operation may modify the left
1039 // operand, but if is does the modifications needs to happen in both the
1040 // candidate and the reference.
1041 //
1042 // We do the following checks:
1043 // 1. lanes(vopres) == lanes(vop1)
1044 // 2. lane(l, vopres) == arefres[l] foreach l
1045 // 3. lane(l, vop1) == aref1[l] foreach l
1046 // 4. sop2 is never modified
1047 // 5. sref2 is never modified
1048 //
1049 // In fact, if the property "sref2 is never modified" is violated that
1050 // means the operation is unsuitable for an automatic broadcast of the
1051 // second operand and should not be checked. There are no operations in
1052 // the standard where the second operand is modified like this, but
1053 // there are operations where the first operand is modified -- and this
1054 // check is used for those ops as well by exchanging the first and second
1055 // argument below.
1056
1057 template<class V1, class T2, class Op>
1058 std::enable_if_t<Std::is_detected_v<ScalarResult, Op, V1, T2> >
1059 checkBinaryOpVS(MetaType<V1>, MetaType<T2>, Op op)
1060 {
1061#define DUNE_SIMD_OPNAME (className<Op(V1, T2)>())
1062 static_assert(std::is_same<Scalar<std::decay_t<V1> >,
1063 std::decay_t<T2> >::value,
1064 "Internal testsystem error: called with a scalar that "
1065 "does not match the vector type.");
1066
1067 // initial values
1068 auto sinit2 = rightScalar<std::decay_t<T2>>();
1069
1070 // reference arguments
1071 auto vref1 = leftVector<std::decay_t<V1>>();
1072 auto sref2 = sinit2;
1073
1074 // candidate arguments
1075 auto vop1 = vref1;
1076 auto sop2 = sref2;
1077
1078 // candidate operation
1079 auto &&vopres = op(static_cast<V1>(vop1), static_cast<T2>(sop2));
1080 using VR = decltype(vopres);
1081
1082 // check 1. lanes(vopres) == lanes(vop1)
1083 static_assert(lanes<std::decay_t<VR> >() == lanes<std::decay_t<V1> >(),
1084 "The result must have the same number of lanes as the "
1085 "operands.");
1086
1087 // check 4. sop2 is never modified
1088 DUNE_SIMD_CHECK_OP(sop2 == sinit2);
1089
1090 // do the reference operation, and simultaneously check 2. and 5.
1091 using T = Scalar<std::decay_t<decltype(vopres)> >;
1092 for(auto l : range(lanes(vopres)))
1093 {
1094 // check 2. lane(l, vopres) == arefres[l] foreach l
1095 // see the lengthy comment in `checkUnaryOpV()` as to why the
1096 // `static_cast` around the `op()` is necessary
1097 DUNE_SIMD_CHECK_OP
1098 (lane(l, vopres)
1099 == static_cast<T>(op(lane(l, static_cast<V1>(vref1)),
1100 static_cast<T2>(sref2) )));
1101 // check 5. sref2 is never modified
1102 DUNE_SIMD_CHECK_OP(sref2 == sinit2);
1103 }
1104
1105 // check 3. lane(l, vop1) == aref1[l] foreach l
1106 for(auto l : range(lanes(vop1)))
1107 DUNE_SIMD_CHECK_OP(lane(l, vop1) == lane(l, vref1));
1108
1109#undef DUNE_SIMD_OPNAME
1110 }
1111
1112 template<class V1, class T2, class Op>
1113 std::enable_if_t<!Std::is_detected_v<ScalarResult, Op, V1, T2> >
1114 checkBinaryOpVS(MetaType<V1>, MetaType<T2>, Op op)
1115 {
1116 // log_ << "No "
1117 // << className<Op(decltype(lane(0, std::declval<V1>())), T2)>()
1118 // << std::endl
1119 // << " ==> Not checking " << className<Op(V1, T2)>() << std::endl;
1120 }
1121
1122 template<class V1, class T2>
1123 void checkBinaryOpVS(MetaType<V1>, MetaType<T2>, OpInfixComma)
1124 {
1125 static_assert(std::is_same<Scalar<std::decay_t<V1> >,
1126 std::decay_t<T2> >::value,
1127 "Internal testsystem error: called with a scalar that "
1128 "does not match the vector type.");
1129
1130 checkCommaOp<V1, T2>(leftVector<std::decay_t<V1>>(),
1131 rightScalar<std::decay_t<T2>>());
1132 }
1133
1135 //
1136 // cross-check scalar-vector binary operations against vector-vector
1137 //
1138
1139 // We check the following candidate operation
1140 //
1141 // vopres = vop1 @ vop2, where vop2 = broadcast(sref2)
1142 //
1143 // against the reference operation
1144 //
1145 // vrefres = vref1 @ sref2
1146 //
1147 // v... variables are simd-vectors, a... variables are arrays, and
1148 // s... variables are scalars.
1149 //
1150 // We could check the following properties
1151 // 1. lanes(vopres) == lanes(vop1)
1152 // 2. lane(l, vopres) == lane(l, vrefres) foreach l
1153 // 3. lane(l, vop1) == lane(l, vref1) foreach l
1154 // but these are given by checking the operation against the scalar
1155 // operation in the vector@vector and vector@scalar cases above.
1156 //
1157 // The only thing left to check is:
1158 // 4. lane(l, vop2) foreach l is never modified
1159
1160 template<class V1, class T2, class Op>
1161 std::enable_if_t<Std::is_detected_v<ScalarResult, Op, V1, T2> >
1162 checkBinaryOpVVAgainstVS(MetaType<V1>, MetaType<T2>, Op op)
1163 {
1164#define DUNE_SIMD_OPNAME (className<Op(V1, T2)>())
1165 static_assert(std::is_same<Scalar<std::decay_t<V1> >,
1166 std::decay_t<T2> >::value,
1167 "Internal testsystem error: called with a scalar that "
1168 "does not match the vector type.");
1169
1170 // initial values
1171 auto sinit2 = rightScalar<std::decay_t<T2>>();
1172
1173 // reference arguments
1174 auto vop1 = leftVector<std::decay_t<V1>>();
1175 using V2 = CopyRefQual<V1, T2>;
1176 std::decay_t<V2> vop2(sinit2);
1177
1178 // candidate operation
1179 op(static_cast<V1>(vop1), static_cast<V2>(vop2));
1180
1181 // 4. lane(l, vop2) foreach l is never modified
1182 for(auto l : range(lanes(vop2)))
1183 DUNE_SIMD_CHECK_OP(lane(l, vop2) == sinit2);
1184
1185#undef DUNE_SIMD_OPNAME
1186 }
1187
1188 template<class V1, class T2, class Op>
1189 std::enable_if_t<!Std::is_detected_v<ScalarResult, Op, V1, T2> >
1190 checkBinaryOpVVAgainstVS(MetaType<V1>, MetaType<T2>, Op op)
1191 {
1192 // log_ << "No "
1193 // << className<Op(decltype(lane(0, std::declval<V1>())), T2)>()
1194 // << std::endl
1195 // << " ==> Not checking " << className<Op(V1, T2)>() << std::endl;
1196 }
1197
1198 template<class V1, class T2>
1199 void checkBinaryOpVVAgainstVS(MetaType<V1>, MetaType<T2>, OpInfixComma)
1200 { }
1201
1203 //
1204 // checks for vector-proxy binary operations
1205 //
1206
1207 // We check the following candidate operation
1208 //
1209 // vopres = vop1 @ pop2
1210 //
1211 // against the reference operation
1212 //
1213 // arefres[l] = aref1[l] @ sref2 foreach l
1214 //
1215 // v... variables are simd-vectors, a... variables are arrays,
1216 // p... variables are proxies of simd-vector entries and s... variables
1217 // are scalars. The operation may modify the left operand, but if is
1218 // does the modifications needs to happen in both the candidate and the
1219 // reference.
1220 //
1221 // We do the following checks:
1222 // 1. lanes(vopres) == lanes(vop1)
1223 // 2. lane(l, vopres) == arefres[l] foreach l
1224 // 3. lane(l, vop1) == aref1[l] foreach l
1225 // 4. pop2 is never modified
1226 // 5. sref2 is never modified
1227 //
1228 // In fact, if the property "sref2 is never modified" is violated that
1229 // means the operation is unsuitable for an automatic broadcast of the
1230 // second operand and should not be checked. There are no operations in
1231 // the standard where the second operand is modified like this, but
1232 // there are operations where the first operand is modified -- and this
1233 // check is used for those ops as well by exchanging the first and second
1234 // argument below.
1235
1236 template<class V1, class V2, class Op>
1237 std::enable_if_t<Std::is_detected_v<ScalarResult, Op, V1, V2> >
1238 checkBinaryOpVP(MetaType<V1>, MetaType<V2>, Op op)
1239 {
1240 using P2 = decltype(lane(0, std::declval<V2>()));
1241 using T2 = CopyRefQual<Scalar<V2>, V2>;
1242#define DUNE_SIMD_OPNAME (className<Op(V1, P2)>())
1243 static_assert(std::is_same<Scalar<V1>, Scalar<V2> >::value,
1244 "Internal testsystem error: called with two vector "
1245 "types whose scalar types don't match.");
1246
1247 // initial values
1248 auto sinit2 = rightScalar<Scalar<V2>>();
1249
1250 // reference arguments
1251 auto vref1 = leftVector<std::decay_t<V1>>();
1252 auto sref2 = sinit2;
1253
1254 // candidate arguments
1255 auto vop1 = vref1;
1256 auto vop2 = std::decay_t<V2>(Scalar<V2>(0));
1257 lane(0, vop2) = sref2; // pop2 is just a name for `lane(0, vop2)`
1258
1259 // candidate operation
1260 auto &&vopres =
1261 op(static_cast<V1>(vop1), lane(0, static_cast<V2>(vop2)));
1262 using VR = decltype(vopres);
1263
1264 // check 1. lanes(vopres) == lanes(vop1)
1265 static_assert(lanes<std::decay_t<VR> >() == lanes<std::decay_t<V1> >(),
1266 "The result must have the same number of lanes as the "
1267 "operands.");
1268
1269 // check 4. pop2 is never modified
1270 DUNE_SIMD_CHECK_OP(lane(0, vop2) == sinit2);
1271
1272 // do the reference operation, and simultaneously check 2. and 5.
1273 using T = Scalar<decltype(vopres)>;
1274 for(auto l : range(lanes(vopres)))
1275 {
1276 // check 2. lane(l, vopres) == arefres[l] foreach l
1277 // see the lengthy comment in `checkUnaryOpV()` as to why the
1278 // `static_cast` around the `op()` is necessary
1279 DUNE_SIMD_CHECK_OP
1280 (lane(l, vopres)
1281 == static_cast<T>(op(lane(l, static_cast<V1>(vref1)),
1282 static_cast<T2>(sref2) )));
1283 // check 5. sref2 is never modified
1284 DUNE_SIMD_CHECK_OP(sref2 == sinit2);
1285 }
1286
1287 // check 3. lane(l, vop1) == aref1[l] foreach l
1288 for(auto l : range(lanes(vop1)))
1289 DUNE_SIMD_CHECK_OP(lane(l, vop1) == lane(l, vref1));
1290
1291#undef DUNE_SIMD_OPNAME
1292 }
1293
1294 template<class V1, class V2, class Op>
1295 std::enable_if_t<!Std::is_detected_v<ScalarResult, Op, V1, V2> >
1296 checkBinaryOpVP(MetaType<V1>, MetaType<V2>, Op op)
1297 {
1298 // log_ << "No "
1299 // << className<Op(decltype(lane(0, std::declval<V1>())), T2)>()
1300 // << std::endl
1301 // << " ==> Not checking " << className<Op(V1, T2)>() << std::endl;
1302 }
1303
1304 template<class V1, class V2>
1305 void checkBinaryOpVP(MetaType<V1>, MetaType<V2>, OpInfixComma)
1306 {
1307 // Don't really know how to check comma operator for proxies
1308 }
1309
1311 //
1312 // checks for (scalar/proxy)-vector binary operations
1313 //
1314
1315 template<class Op>
1316 struct OpInfixSwappedArgs
1317 {
1318 Op orig;
1319
1320 template<class V1, class V2>
1321 decltype(auto) operator()(V1&& v1, V2&& v2) const
1322 {
1323 return orig(std::forward<V2>(v2), std::forward<V1>(v1));
1324 }
1325 template<class S1, class S2>
1326 auto scalar(S1&& s1, S2&& s2) const
1327 -> decltype(orig.scalar(std::forward<S2>(s2), std::forward<S1>(s1)));
1328 };
1329
1330 template<class T1, class V2, class Op>
1331 void checkBinaryOpSV(MetaType<T1> t1, MetaType<V2> v2, Op op)
1332 {
1333 checkBinaryOpVS(v2, t1, OpInfixSwappedArgs<Op>{op});
1334 }
1335
1336 template<class T1, class V2>
1337 void checkBinaryOpSV(MetaType<T1>, MetaType<V2>, OpInfixComma)
1338 {
1339 static_assert(std::is_same<std::decay_t<T1>,
1340 Scalar<std::decay_t<V2> > >::value,
1341 "Internal testsystem error: called with a scalar that "
1342 "does not match the vector type.");
1343
1344 checkCommaOp<T1, V2>(leftScalar<std::decay_t<T1>>(),
1345 rightVector<std::decay_t<V2>>());
1346 }
1347
1348 template<class V1, class V2, class Op>
1349 void checkBinaryOpPV(MetaType<V1> v1, MetaType<V2> v2, Op op)
1350 {
1351 checkBinaryOpVP(v2, v1, OpInfixSwappedArgs<Op>{op});
1352 }
1353
1354 template<class V1, class V2>
1355 void checkBinaryOpPV(MetaType<V1>, MetaType<V2>, OpInfixComma)
1356 {
1357 // Don't really know how to check comma operator for proxies
1358 }
1359
1361 //
1362 // cross-check scalar-vector binary operations against vector-vector
1363 //
1364
1365 // We check the following candidate operation
1366 //
1367 // vopres = vop1 @ vop2, where vop2 = broadcast(sref2)
1368 //
1369 // against the reference operation
1370 //
1371 // vrefres = vref1 @ sref2
1372 //
1373 // v... variables are simd-vectors, a... variables are arrays, and
1374 // s... variables are scalars.
1375 //
1376 // We could check the following properties
1377 // 1. lanes(vopres) == lanes(vop1)
1378 // 2. lane(l, vopres) == lane(l, vrefres) foreach l
1379 // 3. lane(l, vop1) == lane(l, vref1) foreach l
1380 // but these are given by checking the operation against the scalar
1381 // operation in the vector@vector and vector@scalar cases above.
1382 //
1383 // The only thing left to check is:
1384 // 4. lane(l, vop2) foreach l is never modified
1385
1386 template<class T1, class V2, class Op>
1387 void checkBinaryOpVVAgainstSV(MetaType<T1> t1, MetaType<V2> v2, Op op)
1388 {
1389 checkBinaryOpVVAgainstVS(v2, t1, OpInfixSwappedArgs<Op>{op});
1390 }
1391
1392 template<class V1, class T2>
1393 void checkBinaryOpVVAgainstSV(MetaType<V1>, MetaType<T2>, OpInfixComma)
1394 { }
1395
1397 //
1398 // Invoke the checks for all combinations
1399 //
1400
1401 template<class T1, class T2, bool condition, class Checker>
1402 void checkBinaryRefQual(Checker checker)
1403 {
1404 if constexpr (condition) {
1407 checker(t1, t2);
1408 });
1409 });
1410 }
1411 }
1412
1413 template<class V, class Checker>
1414 void checkBinaryOps(Checker checker)
1415 {
1416 using Std::bool_constant;
1417
1418 constexpr bool isMask = std::is_same<Scalar<V>, bool>::value;
1419
1420 constexpr bool do_ = false;
1421 constexpr bool do_SV = true;
1422 constexpr bool do_VV = true;
1423 constexpr bool do_VS = true;
1424
1425#define DUNE_SIMD_DO(M1, M2, M3, V1, V2, V3, NAME) \
1426 checker(bool_constant<isMask ? do_##M1 : do_##V1>{}, \
1427 bool_constant<isMask ? do_##M2 : do_##V2>{}, \
1428 bool_constant<isMask ? do_##M3 : do_##V3>{}, \
1429 Op##NAME{})
1430
1431 // (Mask , Vector , Name );
1432
1433 DUNE_SIMD_DO( , , , SV, VV, VS, InfixMul );
1434 DUNE_SIMD_DO( , , , SV, VV, VS, InfixDiv );
1435 DUNE_SIMD_DO( , , , SV, VV, VS, InfixRemainder );
1436
1437 DUNE_SIMD_DO( , , , SV, VV, VS, InfixPlus );
1438 DUNE_SIMD_DO( , , , SV, VV, VS, InfixMinus );
1439
1440 DUNE_SIMD_DO( , , , , VV, VS, InfixLeftShift );
1441 DUNE_SIMD_DO( , , , , VV, VS, InfixRightShift );
1442
1443 DUNE_SIMD_DO( , , , SV, VV, VS, InfixLess );
1444 DUNE_SIMD_DO( , , , SV, VV, VS, InfixGreater );
1445 DUNE_SIMD_DO( , , , SV, VV, VS, InfixLessEqual );
1446 DUNE_SIMD_DO( , , , SV, VV, VS, InfixGreaterEqual );
1447
1448 DUNE_SIMD_DO( , , , SV, VV, VS, InfixEqual );
1449 DUNE_SIMD_DO( , , , SV, VV, VS, InfixNotEqual );
1450
1451 DUNE_SIMD_DO( , VV, , SV, VV, VS, InfixBitAnd );
1452 DUNE_SIMD_DO( , VV, , SV, VV, VS, InfixBitXor );
1453 DUNE_SIMD_DO( , VV, , SV, VV, VS, InfixBitOr );
1454
1455 DUNE_SIMD_DO(SV, VV, VS, SV, VV, VS, InfixLogicAnd );
1456 DUNE_SIMD_DO(SV, VV, VS, SV, VV, VS, InfixLogicOr );
1457
1458 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssign );
1459 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignMul );
1460 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignDiv );
1461 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignRemainder );
1462 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignPlus );
1463 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignMinus );
1464 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignLeftShift );
1465 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignRightShift);
1466 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssignAnd );
1467 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssignXor );
1468 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssignOr );
1469
1470 DUNE_SIMD_DO(SV, VV, VS, SV, , VS, InfixComma );
1471
1472#undef DUNE_SIMD_DO
1473 }
1474
1476 //
1477 // SIMD interface functions
1478 //
1479
1480 template<class V>
1481 void checkAutoCopy()
1482 {
1483 using RValueResult = decltype(autoCopy(lane(0, std::declval<V>())));
1484 static_assert(std::is_same<RValueResult, Scalar<V> >::value,
1485 "Result of autoCopy() must always be Scalar<V>");
1486
1487 using MutableLValueResult =
1488 decltype(autoCopy(lane(0, std::declval<V&>())));
1489 static_assert(std::is_same<MutableLValueResult, Scalar<V> >::value,
1490 "Result of autoCopy() must always be Scalar<V>");
1491
1492 using ConstLValueResult =
1493 decltype(autoCopy(lane(0, std::declval<const V&>())));
1494 static_assert(std::is_same<ConstLValueResult, Scalar<V> >::value,
1495 "Result of autoCopy() must always be Scalar<V>");
1496
1497 V vec = make123<V>();
1498 for(std::size_t l = 0; l < lanes(vec); ++l)
1499 DUNE_SIMD_CHECK(autoCopy(lane(l, vec)) == Scalar<V>(l+1));
1500 }
1501
1502 // may only be called for mask types
1503 template<class M>
1504 void checkBoolReductions()
1505 {
1506 M trueVec(true);
1507
1508 // mutable lvalue
1509 DUNE_SIMD_CHECK(allTrue (static_cast<M&>(trueVec)) == true);
1510 DUNE_SIMD_CHECK(anyTrue (static_cast<M&>(trueVec)) == true);
1511 DUNE_SIMD_CHECK(allFalse(static_cast<M&>(trueVec)) == false);
1512 DUNE_SIMD_CHECK(anyFalse(static_cast<M&>(trueVec)) == false);
1513
1514 // const lvalue
1515 DUNE_SIMD_CHECK(allTrue (static_cast<const M&>(trueVec)) == true);
1516 DUNE_SIMD_CHECK(anyTrue (static_cast<const M&>(trueVec)) == true);
1517 DUNE_SIMD_CHECK(allFalse(static_cast<const M&>(trueVec)) == false);
1518 DUNE_SIMD_CHECK(anyFalse(static_cast<const M&>(trueVec)) == false);
1519
1520 // rvalue
1521 DUNE_SIMD_CHECK(allTrue (M(true)) == true);
1522 DUNE_SIMD_CHECK(anyTrue (M(true)) == true);
1523 DUNE_SIMD_CHECK(allFalse(M(true)) == false);
1524 DUNE_SIMD_CHECK(anyFalse(M(true)) == false);
1525
1526 M falseVec(false);
1527
1528 // mutable lvalue
1529 DUNE_SIMD_CHECK(allTrue (static_cast<M&>(falseVec)) == false);
1530 DUNE_SIMD_CHECK(anyTrue (static_cast<M&>(falseVec)) == false);
1531 DUNE_SIMD_CHECK(allFalse(static_cast<M&>(falseVec)) == true);
1532 DUNE_SIMD_CHECK(anyFalse(static_cast<M&>(falseVec)) == true);
1533
1534 // const lvalue
1535 DUNE_SIMD_CHECK(allTrue (static_cast<const M&>(falseVec)) == false);
1536 DUNE_SIMD_CHECK(anyTrue (static_cast<const M&>(falseVec)) == false);
1537 DUNE_SIMD_CHECK(allFalse(static_cast<const M&>(falseVec)) == true);
1538 DUNE_SIMD_CHECK(anyFalse(static_cast<const M&>(falseVec)) == true);
1539
1540 // rvalue
1541 DUNE_SIMD_CHECK(allTrue (M(false)) == false);
1542 DUNE_SIMD_CHECK(anyTrue (M(false)) == false);
1543 DUNE_SIMD_CHECK(allFalse(M(false)) == true);
1544 DUNE_SIMD_CHECK(anyFalse(M(false)) == true);
1545
1546 auto mixedVec = broadcast<M>(0);
1547 for(std::size_t l = 0; l < lanes(mixedVec); ++l)
1548 lane(l, mixedVec) = (l % 2);
1549
1550 // mutable lvalue
1551 DUNE_SIMD_CHECK
1552 (allTrue (static_cast<M&>(mixedVec)) == false);
1553 DUNE_SIMD_CHECK
1554 (anyTrue (static_cast<M&>(mixedVec)) == (lanes<M>() > 1));
1555 DUNE_SIMD_CHECK
1556 (allFalse(static_cast<M&>(mixedVec)) == (lanes<M>() == 1));
1557 DUNE_SIMD_CHECK
1558 (anyFalse(static_cast<M&>(mixedVec)) == true);
1559
1560 // const lvalue
1561 DUNE_SIMD_CHECK
1562 (allTrue (static_cast<const M&>(mixedVec)) == false);
1563 DUNE_SIMD_CHECK
1564 (anyTrue (static_cast<const M&>(mixedVec)) == (lanes<M>() > 1));
1565 DUNE_SIMD_CHECK
1566 (allFalse(static_cast<const M&>(mixedVec)) == (lanes<M>() == 1));
1567 DUNE_SIMD_CHECK
1568 (anyFalse(static_cast<const M&>(mixedVec)) == true);
1569
1570 // rvalue
1571 DUNE_SIMD_CHECK(allTrue (M(mixedVec)) == false);
1572 DUNE_SIMD_CHECK(anyTrue (M(mixedVec)) == (lanes<M>() > 1));
1573 DUNE_SIMD_CHECK(allFalse(M(mixedVec)) == (lanes<M>() == 1));
1574 DUNE_SIMD_CHECK(anyFalse(M(mixedVec)) == true);
1575 }
1576
1577 template<class V>
1578 void checkCond()
1579 {
1580 using M = Mask<V>;
1581
1582 static_assert
1583 (std::is_same<decltype(cond(std::declval<M>(), std::declval<V>(),
1584 std::declval<V>())), V>::value,
1585 "The result of cond(M, V, V) should have exactly the type V");
1586
1587 static_assert
1588 (std::is_same<decltype(cond(std::declval<const M&>(),
1589 std::declval<const V&>(),
1590 std::declval<const V&>())), V>::value,
1591 "The result of cond(const M&, const V&, const V&) should have "
1592 "exactly the type V");
1593
1594 static_assert
1595 (std::is_same<decltype(cond(std::declval<M&>(), std::declval<V&>(),
1596 std::declval<V&>())), V>::value,
1597 "The result of cond(M&, V&, V&) should have exactly the type V");
1598
1599 V vec1 = leftVector<V>();
1600 V vec2 = rightVector<V>();
1601
1602 DUNE_SIMD_CHECK(allTrue(cond(M(true), vec1, vec2) == vec1));
1603 DUNE_SIMD_CHECK(allTrue(cond(M(false), vec1, vec2) == vec2));
1604
1605 auto mixedResult = broadcast<V>(0);
1606 auto mixedMask = broadcast<M>(false);
1607 for(std::size_t l = 0; l < lanes(mixedMask); ++l)
1608 {
1609 lane(l, mixedMask ) = (l % 2);
1610 lane(l, mixedResult) = lane(l, (l % 2) ? vec1 : vec2);
1611 }
1612
1613 DUNE_SIMD_CHECK(allTrue(cond(mixedMask, vec1, vec2) == mixedResult));
1614 }
1615
1616 template<class V>
1617 void checkBoolCond()
1618 {
1619 static_assert
1620 (std::is_same<decltype(cond(std::declval<bool>(), std::declval<V>(),
1621 std::declval<V>())), V>::value,
1622 "The result of cond(bool, V, V) should have exactly the type V");
1623
1624 static_assert
1625 (std::is_same<decltype(cond(std::declval<const bool&>(),
1626 std::declval<const V&>(),
1627 std::declval<const V&>())), V>::value,
1628 "The result of cond(const bool&, const V&, const V&) should have "
1629 "exactly the type V");
1630
1631 static_assert
1632 (std::is_same<decltype(cond(std::declval<bool&>(),
1633 std::declval<V&>(),
1634 std::declval<V&>())), V>::value,
1635 "The result of cond(bool&, V&, V&) should have exactly the type V");
1636
1637 V vec1 = leftVector<V>();
1638 V vec2 = rightVector<V>();
1639
1640 DUNE_SIMD_CHECK(allTrue(cond(true, vec1, vec2) == vec1));
1641 DUNE_SIMD_CHECK(allTrue(cond(false, vec1, vec2) == vec2));
1642 }
1643
1644 template<class V>
1645 std::enable_if_t<!Impl::LessThenComparable<Scalar<V> >::value>
1646 checkHorizontalMinMax() {}
1647
1648 template<class V>
1649 std::enable_if_t<Impl::LessThenComparable<Scalar<V> >::value>
1650 checkHorizontalMinMax()
1651 {
1652 static_assert
1653 (std::is_same<decltype(max(std::declval<V>())), Scalar<V> >::value,
1654 "The result of max(V) should be exactly Scalar<V>");
1655
1656 static_assert
1657 (std::is_same<decltype(min(std::declval<V>())), Scalar<V> >::value,
1658 "The result of min(V) should be exactly Scalar<V>");
1659
1660 static_assert
1661 (std::is_same<decltype(max(std::declval<V&>())), Scalar<V> >::value,
1662 "The result of max(V) should be exactly Scalar<V>");
1663
1664 static_assert
1665 (std::is_same<decltype(min(std::declval<V&>())), Scalar<V> >::value,
1666 "The result of min(V) should be exactly Scalar<V>");
1667
1668 const V vec1 = leftVector<V>();
1669
1670 DUNE_SIMD_CHECK(max(vec1) == Scalar<V>(lanes(vec1)));
1671 DUNE_SIMD_CHECK(min(vec1) == Scalar<V>(1));
1672 }
1673
1674 template<class V>
1675 std::enable_if_t<!Impl::LessThenComparable<Scalar<V> >::value>
1676 checkBinaryMinMax() {}
1677
1678 template<class V>
1679 std::enable_if_t<Impl::LessThenComparable<Scalar<V> >::value>
1680 checkBinaryMinMax()
1681 {
1682 using std::max;
1683 using std::min;
1684
1685 static_assert
1686 (std::is_same<decltype(Simd::max(std::declval<V>(),
1687 std::declval<V>())), V>::value,
1688 "The result of Simd::max(V, V) should be exactly V");
1689 static_assert
1690 (std::is_same<decltype(Simd::min(std::declval<V>(),
1691 std::declval<V>())), V>::value,
1692 "The result of Simd::min(V, V) should be exactly V");
1693
1694 static_assert
1695 (std::is_same<decltype(Simd::max(std::declval<V&>(),
1696 std::declval<V&>())), V>::value,
1697 "The result of Simd::max(V&, V&) should be exactly V");
1698 static_assert
1699 (std::is_same<decltype(Simd::min(std::declval<V&>(),
1700 std::declval<V&>())), V>::value,
1701 "The result of Simd::min(V&, V&) should be exactly V");
1702
1703 const V arg1 = leftVector<V>();
1704 const V arg2 = rightVector<V>();
1705
1706 V maxExp(Scalar<V>(0)), minExp(Scalar<V>(0));
1707 for(auto l : range(lanes<V>()))
1708 {
1709 lane(l, maxExp) = max(lane(l, arg1), lane(l, arg2));
1710 lane(l, minExp) = min(lane(l, arg1), lane(l, arg2));
1711 }
1712
1713 DUNE_SIMD_CHECK(allTrue(maxExp == Simd::max(arg1, arg2)));
1714 DUNE_SIMD_CHECK(allTrue(minExp == Simd::min(arg1, arg2)));
1715 }
1716
1717 template<class V>
1718 void checkIO()
1719 {
1720 const V vec1 = leftVector<V>();
1721
1722 std::string reference;
1723 {
1724 const char *sep = "";
1725 for(auto l : range(lanes(vec1)))
1726 {
1727 std::ostringstream stream;
1728 stream << lane(l, vec1);
1729
1730 reference += sep;
1731 reference += stream.str();
1732 sep = ", ";
1733 }
1734 }
1735
1736 {
1737 std::ostringstream stream;
1738 stream << io(vec1);
1739 if(lanes(vec1) == 1)
1740 DUNE_SIMD_CHECK(stream.str() == reference);
1741 else
1742 DUNE_SIMD_CHECK(stream.str() == "<" + reference + ">");
1743 }
1744
1745 {
1746 std::ostringstream stream;
1747 stream << vio(vec1);
1748 DUNE_SIMD_CHECK(stream.str() == "<" + reference + ">");
1749 }
1750 }
1751
1752#undef DUNE_SIMD_CHECK
1753
1754 public:
1817 template<class V> void checkType();
1818 template<class V> void checkNonOps();
1819 template<class V> void checkUnaryOps();
1820 template<class V> void checkBinaryOps();
1821 template<class V> void checkBinaryOpsVectorVector();
1822 template<class V> void checkBinaryOpsScalarVector();
1823 template<class V> void checkBinaryOpsVectorScalar();
1824 template<class V> void checkBinaryOpsProxyVector();
1825 template<class V> void checkBinaryOpsVectorProxy();
1829
1846 template<class V, class Rebinds,
1847 template<class> class RebindPrune = IsLoop,
1848 template<class> class RebindAccept = Dune::AlwaysTrue>
1849 void check() {
1850 // check whether the test for this type already started
1851 if(seen_.emplace(typeid (V)).second == false)
1852 {
1853 // type already seen, nothing to do
1854 return;
1855 }
1856
1857 // do these first so everything that appears after "Checking SIMD type
1858 // ..." really pertains to that type
1859 auto recurse = [this](auto w) {
1860 using W = typename decltype(w)::type;
1861 this->template check<W, Rebinds, RebindPrune, RebindAccept>();
1862 };
1863 checkRebindOf<V, Rebinds, RebindPrune, RebindAccept>(recurse);
1864
1865 checkType<V>();
1866 }
1867
1869 bool good() const
1870 {
1871 return good_;
1872 }
1873
1874 }; // class UnitTest
1875
1876 template<class V> void UnitTest::checkType()
1877 {
1878 static_assert(std::is_same<V, std::decay_t<V> >::value, "Simd types "
1879 "must not be references, and must not include "
1880 "cv-qualifiers");
1881
1882 log_ << "Checking SIMD type " << className<V>() << std::endl;
1883
1884 checkNonOps<V>();
1885 checkUnaryOps<V>();
1886 checkBinaryOps<V>();
1887 }
1888 template<class V> void UnitTest::checkNonOps()
1889 {
1890 constexpr auto isMask = typename std::is_same<Scalar<V>, bool>::type{};
1891
1892 checkLanes<V>();
1893 checkScalar<V>();
1894
1895 checkDefaultConstruct<V>();
1896 checkLane<V>();
1897 checkCopyMoveConstruct<V>();
1898 checkImplCast<V>();
1899 checkBroadcast<V>();
1900 if constexpr (isMask)
1901 this->template checkBroadcastMaskConstruct<V>();
1902 else
1903 this->template checkBroadcastVectorConstruct<V>();
1904 checkBracedAssign<V>();
1905 checkBracedBroadcastAssign<V>();
1906
1907 checkAutoCopy<V>();
1908 checkCond<V>();
1909 checkBoolCond<V>();
1910
1911 if constexpr (isMask)
1912 this->template checkBoolReductions<V>();
1913 // checkBoolReductions() is not applicable for non-masks
1914
1915 checkHorizontalMinMax<V>();
1916 checkBinaryMinMax<V>();
1917 checkIO<V>();
1918 }
1919 template<class V> void UnitTest::checkUnaryOps()
1920 {
1921 if constexpr (std::is_same_v<Scalar<V>, bool>) {
1922 // check mask
1923 auto check = [this](auto op) {
1924 this->template checkUnaryOpsV<V>(op);
1925 };
1926
1927 // postfix
1928 // check(OpPostfixDecrement{});
1929 // clang deprecation warning if bool++ is tested
1930 // check(OpPostfixIncrement{});
1931
1932 // prefix
1933 // check(OpPrefixDecrement{});
1934 // clang deprecation warning if ++bool is tested
1935 // check(OpPrefixIncrement{});
1936
1937 // check(OpPrefixPlus{});
1938 // check(OpPrefixMinus{});
1939 check(OpPrefixLogicNot{});
1940 // check(OpPrefixBitNot{});
1941 }
1942 else {
1943 // check vector
1944 auto check = [this](auto op) {
1945 this->template checkUnaryOpsV<V>(op);
1946 };
1947
1948 // postfix
1949 // check(OpPostfixDecrement{});
1950 // check(OpPostfixIncrement{});
1951
1952 // prefix
1953 // check(OpPrefixDecrement{});
1954 // check(OpPrefixIncrement{});
1955
1956 // check(OpPrefixPlus{});
1957 check(OpPrefixMinus{});
1958 check(OpPrefixLogicNot{});
1959 check(OpPrefixBitNot{});
1960 }
1961 }
1962 template<class V> void UnitTest::checkBinaryOps()
1963 {
1964 checkBinaryOpsVectorVector<V>();
1965 checkBinaryOpsScalarVector<V>();
1966 checkBinaryOpsVectorScalar<V>();
1967 checkBinaryOpsProxyVector<V>();
1968 checkBinaryOpsVectorProxy<V>();
1969 }
1970 template<class V> void UnitTest::checkBinaryOpsVectorVector()
1971 {
1972 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
1973 auto check = [this,op](auto t1, auto t2) {
1974 this->checkBinaryOpVV(t1, t2, op);
1975 };
1976 this->checkBinaryRefQual<V, V, doVV>(check);
1977 };
1978 checkBinaryOps<V>(checker);
1979 }
1980 template<class V> void UnitTest::checkBinaryOpsScalarVector()
1981 {
1982 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
1983 auto check = [this,op](auto t1, auto t2) {
1984 this->checkBinaryOpSV(t1, t2, op);
1985 };
1986 this->checkBinaryRefQual<Scalar<V>, V, doSV>(check);
1987
1988 auto crossCheck = [this,op](auto t1, auto t2) {
1989 this->checkBinaryOpVVAgainstSV(t1, t2, op);
1990 };
1991 this->checkBinaryRefQual<Scalar<V>, V, doSV && doVV>(crossCheck);
1992 };
1993 checkBinaryOps<V>(checker);
1994 }
1995 template<class V> void UnitTest::checkBinaryOpsVectorScalar()
1996 {
1997 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
1998 auto check = [this,op](auto t1, auto t2) {
1999 this->checkBinaryOpVS(t1, t2, op);
2000 };
2001 this->checkBinaryRefQual<V, Scalar<V>, doVS>(check);
2002
2003 auto crossCheck = [this,op](auto t1, auto t2) {
2004 this->checkBinaryOpVVAgainstVS(t1, t2, op);
2005 };
2006 this->checkBinaryRefQual<V, Scalar<V>, doVV && doVS>(crossCheck);
2007 };
2008 checkBinaryOps<V>(checker);
2009 }
2010 template<class V> void UnitTest::checkBinaryOpsProxyVector()
2011 {
2012 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
2013 auto check = [this,op](auto t1, auto t2) {
2014 this->checkBinaryOpPV(t1, t2, op);
2015 };
2016 this->checkBinaryRefQual<V, V, doSV>(check);
2017 };
2018 checkBinaryOps<V>(checker);
2019 }
2020 template<class V> void UnitTest::checkBinaryOpsVectorProxy()
2021 {
2022 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
2023 auto check = [this,op](auto t1, auto t2) {
2024 this->checkBinaryOpVP(t1, t2, op);
2025 };
2026 this->checkBinaryRefQual<V, V, doVS>(check);
2027 };
2028 checkBinaryOps<V>(checker);
2029 }
2030
2031 } // namespace Simd
2032} // namespace Dune
2033
2034#endif // DUNE_COMMON_SIMD_TEST_HH
Definition: loop.hh:38
A free function to provide the demangled class name of a given object or type as a string.
IO interface of the SIMD abstraction.
Traits for type conversions and type information.
constexpr AutonomousValue< T > autoCopy(T &&v)
Autonomous copy of an expression's value for use in auto type deduction.
Definition: typetraits.hh:642
typename Impl::voider< Types... >::type void_t
Is void for all valid input types. The workhorse for C++11 SFINAE-techniques.
Definition: typetraits.hh:38
constexpr GeometryType line
GeometryType representing a line.
Definition: type.hh:510
constexpr void forEach(Range &&range, F &&f)
Range based for loop.
Definition: hybridutilities.hh:266
auto min(ADLTag< 0 >, const V &v1, const V &v2)
implements binary Simd::min()
Definition: defaults.hh:87
auto max(ADLTag< 0 >, const V &v1, const V &v2)
implements binary Simd::max()
Definition: defaults.hh:79
bool anyTrue(const Mask &mask)
Whether any entry is true
Definition: interface.hh:427
auto maskOr(const V1 &v1, const V2 &v2)
Logic or of masks.
Definition: interface.hh:497
V cond(M &&mask, const V &ifTrue, const V &ifFalse)
Like the ?: operator.
Definition: interface.hh:384
auto io(const V &v)
construct a stream inserter
Definition: io.hh:104
bool allTrue(const Mask &mask)
Whether all entries are true
Definition: interface.hh:437
auto vio(const V &v)
construct a stream inserter
Definition: io.hh:88
typename Overloads::RebindType< std::decay_t< S >, std::decay_t< V > >::type Rebind
Construct SIMD type with different scalar type.
Definition: interface.hh:251
auto max(const V &v1, const V &v2)
The binary maximum value over two simd objects.
Definition: interface.hh:407
bool anyFalse(const Mask &mask)
Whether any entry is false
Definition: interface.hh:447
constexpr std::size_t lanes()
Number of lanes in a SIMD type.
Definition: interface.hh:303
decltype(auto) lane(std::size_t l, V &&v)
Extract an element of a SIMD type.
Definition: interface.hh:322
Rebind< bool, V > Mask
Mask type type of some SIMD type.
Definition: interface.hh:287
bool allFalse(const Mask &mask)
Whether all entries are false
Definition: interface.hh:457
auto maskAnd(const V1 &v1, const V2 &v2)
Logic and of masks.
Definition: interface.hh:507
typename Overloads::ScalarType< std::decay_t< V > >::type Scalar
Element type of some SIMD type.
Definition: interface.hh:233
auto min(const V &v1, const V &v2)
The binary minimum value over two simd objects.
Definition: interface.hh:417
std::tuple< MetaType< T >... > TypeList
A simple type list.
Definition: typelist.hh:85
Namespace with predefined compile time indices for the range [0,19].
Definition: indices.hh:49
typename Impl::RemoveEnd< EndMark, TypeList< Types... > >::type RebindList
A list of types with the final element removed.
Definition: test.hh:165
typename Impl::IsLoop< T >::type IsLoop
check whether a type is an instance of LoopSIMD
Definition: test.hh:169
Dune namespace.
Definition: alignedallocator.hh:11
STL namespace.
Utilities for reduction like operations on ranges.
Include file for users of the SIMD abstraction layer.
template which always yields a true value
Definition: typetraits.hh:134
A type that refers to another type.
Definition: typelist.hh:31
final element marker for RebindList
Definition: test.hh:152
Creative Commons License   |  Legal Statements / Impressum  |  Hosted by TU Dresden  |  generated with Hugo v0.111.3 (Nov 12, 23:30, 2024)