Dune Core Modules (2.7.1)

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