DUNE-FEM (unstable)

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