dune-common 2.12-git
Loading...
Searching...
No Matches
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
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>())> > :
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 {
77 };
78
79 template<class Dst, class Src>
80 struct CopyReferenceHelper<Dst, Src&&>
81 {
83 };
84
85 template<class Dst, class Src>
86 using CopyRefQual = typename CopyReferenceHelper<
87 typename CopyVolatileHelper<
88 typename CopyConstHelper<
91 >::type,
93 >::type,
94 Src
95 >::type;
96
97 template<class Mark, class Types,
98 class Indices =
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>;
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 // like IsLoop, but for any alignment
131 template<class T>
132 struct IsAnyLoop : std::false_type {};
133 template<class T, std::size_t S, std::size_t A>
134 struct IsAnyLoop<LoopSIMD<T, S, A> > : std::true_type {};
135
136 // used inside static_assert to trick the compiler into printing a list
137 // of types:
138 //
139 // static_assert(debugTypes<V>(std::bool_constant<condition>{}), "msg");
140 //
141 // Should include what the type `V` expands to in the error message.
142 template<class...>
143 constexpr bool debugTypes(std::true_type) { return true; }
144 template<class... Types>
145 [[deprecated]]
146 constexpr bool debugTypes(std::false_type) { return false; }
147
148 } // namespace Impl
149
151 struct EndMark {};
153
162 template<class... Types>
164 typename Impl::RemoveEnd<EndMark, TypeList<Types...> >::type;
165
167 template<class T>
168 using IsLoop = typename Impl::IsLoop<T>::type;
169
170 class UnitTest {
171 bool good_ = true;
172 std::ostream &log_ = std::cerr;
173 // records the types for which checks have started running to avoid
174 // infinite recursion
176
178 //
179 // Helper functions
180 //
181
182 void complain(const char *file, int line, const char *func,
183 const char *expr);
184
185 void complain(const char *file, int line, const char *func,
186 const std::string &opname, const char *expr);
187
188 // This macro is defined only within this file, do not use anywhere
189 // else. Doing the actual printing in an external function dramatically
190 // reduces memory use during compilation. Defined in such a way that
191 // the call will only happen for failed checks.
192#define DUNE_SIMD_CHECK(expr) \
193 ((expr) ? void() : complain(__FILE__, __LINE__, __func__, #expr))
194
195 // the function using this macro must define a way to compute the
196 // operator name in DUNE_SIMD_OPNAME
197#define DUNE_SIMD_CHECK_OP(expr) \
198 ((expr) ? void() : complain(__FILE__, __LINE__, __func__, \
199 DUNE_SIMD_OPNAME, #expr))
200
201 // "cast" into a prvalue
202 template<class T>
203 static std::decay_t<T> prvalue(T &&t)
204 {
205 return std::forward<T>(t);
206 }
207
208 // whether the vector is 42 in all lanes
209 template<class V>
210 static bool is42(const V &v)
211 {
212 bool good = true;
213
214 for(std::size_t l = 0; l < lanes(v); ++l)
215 // need to cast in case we have a mask type
216 good &= (lane(l, v) == Scalar<V>(42));
217
218 return good;
219 }
220
221 // make a vector that contains the sequence { 1, 2, ... }
222 template<class V>
223 static V make123()
224 {
225 // initialize to avoid undefined behaviour if assigning to lane()
226 // involves lvalue-to-rvalue conversions, e.g. due to bitmask
227 // operations. Avoid using broadcast<V>() for initialization to avoid
228 // test interdependencies.
229 V vec(Scalar<V>(0));
230 for(std::size_t l = 0; l < lanes(vec); ++l)
231 lane(l, vec) = l + 1;
232 return vec;
233 }
234
235 // whether the vector contains the sequence { 1, 2, ... }
236 template<class V>
237 static bool is123(const V &v)
238 {
239 bool good = true;
240
241 for(std::size_t l = 0; l < lanes(v); ++l)
242 // need to cast in case we have a mask type
243 good &= (lane(l, v) == Scalar<V>(l+1));
244
245 return good;
246 }
247
248 template<class V>
249 static V leftVector()
250 {
251 // Avoid using broadcast<V>() for initialization to avoid test
252 // interdependencies.
253 V res(Scalar<V>(0));
254 for(std::size_t l = 0; l < lanes(res); ++l)
255 lane(l, res) = Scalar<V>(l+1);
256 return res;
257 }
258
259 template<class V>
260 static V rightVector()
261 {
262 // Avoid using broadcast<V>() for initialization to avoid test
263 // interdependencies.
264 V res(Scalar<V>(0));
265 for(std::size_t l = 0; l < lanes(res); ++l)
266 // do not exceed number of bits in char (for shifts)
267 // avoid 0 (for / and %)
268 lane(l, res) = Scalar<V>((l)%7+1);
269 return res;
270 }
271
272 template<class T>
273 static T leftScalar()
274 {
275 return T(42);
276 }
277
278 template<class T>
279 static T rightScalar()
280 {
281 // do not exceed number of bits in char (for shifts)
282 // avoid 0 (for / and %)
283 return T(5);
284 }
285
286 template<class Dst, class Src>
287 using CopyRefQual = Impl::CopyRefQual<Dst, Src>;
288
289 // test whether the Op supports the operation on scalars. We do not use
290 // `lane()` to obtain the scalars, because that might return a proxy
291 // object, and we are interested in what exactly the scalar type can do,
292 // no a proxy that might have more overloads than needed. In addition,
293 // `lane()` may not preserve `const` and reference qualifiers.
294 template<class Op, class... Vectors>
295 using ScalarResult =
296 decltype(std::declval<Op>().
297 scalar(std::declval<CopyRefQual<Scalar<Vectors>,
298 Vectors> >()...));
299
301 //
302 // Check associated types
303 //
304
305 template<class V>
306 void checkScalar()
307 {
308 // check that the type Scalar<V> exists
309 using T = Scalar<V>;
310
311 static_assert(std::is_same<T, std::decay_t<T> >::value, "Scalar types "
312 "must not be references, and must not include "
313 "cv-qualifiers");
314 [[maybe_unused]] T a{};
315 }
316
317 template<class V>
318 [[deprecated("Warning: please include bool in the Rebinds for "
319 "simd type V, as Masks are not checked otherwise.")]]
320 void warnMissingMaskRebind(std::true_type) {}
321 template<class V>
322 void warnMissingMaskRebind(std::false_type) {}
323
324 template<class V, class Rebinds, template<class> class RebindPrune,
325 template<class> class RebindAccept, class Recurse>
326 void checkRebindOf(Recurse recurse)
327 {
328 Hybrid::forEach(Rebinds{}, [this,recurse](auto target) {
329 using T = typename decltype(target)::type;
330
331 // check that the rebound type exists
332 using W = Rebind<T, V>;
333 log_ << "Type " << className<V>() << " rebound to "
334 << className<T>() << " is " << className<W>() << std::endl;
335
336 static_assert(std::is_same<W, std::decay_t<W> >::value, "Rebound "
337 "types must not be references, and must not include "
338 "cv-qualifiers");
339 static_assert(lanes<V>() == lanes<W>(), "Rebound types must have "
340 "the same number of lanes as the original vector "
341 "types");
342 static_assert(std::is_same<T, Scalar<W> >::value, "Rebound types "
343 "must have the bound-to scalar type");
344
345 if constexpr (RebindPrune<W>{}) {
346 log_ << "Pruning check of Simd type " << className<W>()
347 << std::endl;
348 }
349 else {
350 using Impl::debugTypes;
351 static_assert(debugTypes<T, V, W>(RebindAccept<W>{}),
352 "Rebind<T, V> is W, but that is not accepted "
353 "by RebindAccept");
354 recurse(MetaType<W>{});
355 }
356 });
357
358 static_assert(std::is_same<Rebind<Scalar<V>, V>, V>::value, "A type "
359 "rebound to its own scalar type must be the same type "
360 "as the original type");
361 static_assert(std::is_same<Rebind<bool, V>, Mask<V> >::value, "A type "
362 "rebound to bool must be the mask type for that type");
363
364 constexpr bool hasBool = Impl::TypeInList<bool, Rebinds>::value;
365 warnMissingMaskRebind<V>(std::bool_constant<!hasBool>{});
366 }
367
369 //
370 // Fundamental checks
371 //
372
373 template<class V>
374 void checkLanes()
375 {
376 // check lanes
377 static_assert(std::is_same<std::size_t, decltype(lanes<V>())>::value,
378 "return type of lanes<V>() should be std::size_t");
379 static_assert(std::is_same<std::size_t, decltype(lanes(V{}))>::value,
380 "return type of lanes(V{}) should be std::size_t");
381
382 // the result of lanes<V>() must be constexpr
383 [[maybe_unused]] constexpr auto size = lanes<V>();
384 // but the result of lanes(vec) does not need to be constexpr
385 DUNE_SIMD_CHECK(lanes<V>() == lanes(V{}));
386 }
387
388 template<class V>
389 void checkDefaultConstruct()
390 {
391 { [[maybe_unused]] V vec; }
392 { [[maybe_unused]] V vec{}; }
393 { [[maybe_unused]] V vec = {}; }
394 }
395
396 template<class V>
397 void checkLane()
398 {
399 // Avoid using broadcast<V>() for initialization to avoid test
400 // interdependencies.
401 V vec(Scalar<V>(0));
402 // check lane() on mutable lvalues
403 for(std::size_t l = 0; l < lanes(vec); ++l)
404 lane(l, vec) = l + 1;
405 for(std::size_t l = 0; l < lanes(vec); ++l)
406 DUNE_SIMD_CHECK(lane(l, vec) == Scalar<V>(l + 1));
407 using MLRes = decltype(lane(0, vec));
408 static_assert(std::is_same<MLRes, Scalar<V>&>::value ||
410 "Result of lane() on a mutable lvalue vector must "
411 "either be a mutable reference to a scalar of that "
412 "vector or a proxy object (which itself may not be a "
413 "reference nor const).");
414
415 // check lane() on const lvalues
416 const V &vec2 = vec;
417 for(std::size_t l = 0; l < lanes(vec); ++l)
418 DUNE_SIMD_CHECK(lane(l, vec2) == Scalar<V>(l + 1));
419 using CLRes = decltype(lane(0, vec2));
420 static_assert(std::is_same<CLRes, const Scalar<V>&>::value ||
422 "Result of lane() on a const lvalue vector must "
423 "either be a const lvalue reference to a scalar of that "
424 "vector or a proxy object (which itself may not be a "
425 "reference nor const).");
426 static_assert(!std::is_assignable<CLRes, Scalar<V> >::value,
427 "Result of lane() on a const lvalue vector must not be "
428 "assignable from a scalar.");
429
430 // check lane() on rvalues
431 for(std::size_t l = 0; l < lanes(vec); ++l)
432 DUNE_SIMD_CHECK(lane(l, prvalue(vec)) == Scalar<V>(l + 1));
433 using RRes = decltype(lane(0, prvalue(vec)));
434 // TODO: do we really want to allow Scalar<V>&& here? If we allow it,
435 // then `auto &&res = lane(0, vec*vec);` creates a dangling reference,
436 // and the scalar (and even the vector types) are small enough to be
437 // passed in registers anyway. On the other hand, the only comparable
438 // accessor function in the standard library that I can think of is
439 // std::get(), and that does return an rvalue reference in this
440 // situation. However, that cannot assume anything about the size of
441 // the returned types.
442 static_assert(std::is_same<RRes, Scalar<V> >::value ||
444 "Result of lane() on a rvalue vector V must be "
445 "Scalar<V> or Scalar<V>&&.");
446 // Can't assert non-assignable, fails for any typical class,
447 // e.g. std::complex<>. Would need to return const Scalar<V> or const
448 // Scalar<V>&&, which would inhibit moving from the return value.
449 // static_assert(!std::is_assignable<RRes, Scalar<V> >::value,
450 // "Result of lane() on a rvalue vector must not be "
451 // "assignable from a scalar.");
452 }
453
454 // check non-default constructors
455 template<class V>
456 void checkCopyMoveConstruct()
457 {
458 // elided copy/move constructors
459 { V vec (make123<V>()); DUNE_SIMD_CHECK(is123(vec)); }
460 { V vec = make123<V>() ; DUNE_SIMD_CHECK(is123(vec)); }
461 { V vec {make123<V>()}; DUNE_SIMD_CHECK(is123(vec)); }
462 { V vec = {make123<V>()}; DUNE_SIMD_CHECK(is123(vec)); }
463
464 // copy constructors
465 { V ref(make123<V>()); V vec (ref);
466 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
467 { V ref(make123<V>()); V vec = ref ;
468 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
469 { V ref(make123<V>()); V vec {ref};
470 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
471 { V ref(make123<V>()); V vec = {ref};
472 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
473 { const V ref(make123<V>()); V vec (ref);
474 DUNE_SIMD_CHECK(is123(vec)); }
475 { const V ref(make123<V>()); V vec = ref ;
476 DUNE_SIMD_CHECK(is123(vec)); }
477 { const V ref(make123<V>()); V vec {ref};
478 DUNE_SIMD_CHECK(is123(vec)); }
479 { const V ref(make123<V>()); V vec = {ref};
480 DUNE_SIMD_CHECK(is123(vec)); }
481
482 // move constructors
483 { V ref(make123<V>()); V vec (std::move(ref));
484 DUNE_SIMD_CHECK(is123(vec)); }
485 { V ref(make123<V>()); V vec = std::move(ref) ;
486 DUNE_SIMD_CHECK(is123(vec)); }
487 { V ref(make123<V>()); V vec {std::move(ref)};
488 DUNE_SIMD_CHECK(is123(vec)); }
489 { V ref(make123<V>()); V vec = {std::move(ref)};
490 DUNE_SIMD_CHECK(is123(vec)); }
491 }
492
493 template<class V>
494 void checkBroadcastVectorConstruct()
495 {
496 // broadcast copy constructors
497 { Scalar<V> ref = 42; V vec (ref);
498 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
499 { Scalar<V> ref = 42; V vec = ref ;
500 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
501 // { Scalar<V> ref = 42; V vec {ref};
502 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
503 // { Scalar<V> ref = 42; V vec = {ref};
504 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
505 { const Scalar<V> ref = 42; V vec (ref);
506 DUNE_SIMD_CHECK(is42(vec)); }
507 { const Scalar<V> ref = 42; V vec = ref ;
508 DUNE_SIMD_CHECK(is42(vec)); }
509 // { const Scalar<V> ref = 42; V vec {ref};
510 // DUNE_SIMD_CHECK(is42(vec)); }
511 // { const Scalar<V> ref = 42; V vec = {ref};
512 // DUNE_SIMD_CHECK(is42(vec)); }
513
514 // broadcast move constructors
515 { Scalar<V> ref = 42; V vec (std::move(ref));
516 DUNE_SIMD_CHECK(is42(vec)); }
517 { Scalar<V> ref = 42; V vec = std::move(ref) ;
518 DUNE_SIMD_CHECK(is42(vec)); }
519 // { Scalar<V> ref = 42; V vec {std::move(ref)};
520 // DUNE_SIMD_CHECK(is42(vec)); }
521 // { Scalar<V> ref = 42; V vec = {std::move(ref)};
522 // DUNE_SIMD_CHECK(is42(vec)); }
523 }
524
525 template<class V>
526 void checkBroadcastMaskConstruct()
527 {
528 // broadcast copy constructors
529 { Scalar<V> ref = 42; V vec (ref);
530 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
531 // { Scalar<V> ref = 42; V vec = ref ;
532 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
533 { Scalar<V> ref = 42; V vec {ref};
534 DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
535 // { Scalar<V> ref = 42; V vec = {ref};
536 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
537 { const Scalar<V> ref = 42; V vec (ref);
538 DUNE_SIMD_CHECK(is42(vec)); }
539 // { const Scalar<V> ref = 42; V vec = ref ;
540 // DUNE_SIMD_CHECK(is42(vec)); }
541 { const Scalar<V> ref = 42; V vec {ref};
542 DUNE_SIMD_CHECK(is42(vec)); }
543 // { const Scalar<V> ref = 42; V vec = {ref};
544 // DUNE_SIMD_CHECK(is42(vec)); }
545
546 // broadcast move constructors
547 { Scalar<V> ref = 42; V vec (std::move(ref));
548 DUNE_SIMD_CHECK(is42(vec)); }
549 // { Scalar<V> ref = 42; V vec = std::move(ref) ;
550 // DUNE_SIMD_CHECK(is42(vec)); }
551 { Scalar<V> ref = 42; V vec {std::move(ref)};
552 DUNE_SIMD_CHECK(is42(vec)); }
553 // { Scalar<V> ref = 42; V vec = {std::move(ref)};
554 // DUNE_SIMD_CHECK(is42(vec)); }
555 }
556
557 // check the implCast function
558 template<class FromV, class ToV>
559 void checkImplCast()
560 {
561 { // lvalue arg
562 FromV fromVec = make123<FromV>();
563 auto toVec = implCast<ToV>(fromVec);
564 static_assert(std::is_same<decltype(toVec), ToV>::value,
565 "Unexpected result type for implCast<ToV>(FromV&)");
566 DUNE_SIMD_CHECK(is123(fromVec));
567 DUNE_SIMD_CHECK(is123(toVec));
568 }
569
570 { // const lvalue arg
571 const FromV fromVec = make123<FromV>();
572 auto toVec = implCast<ToV>(fromVec);
573 static_assert(std::is_same<decltype(toVec), ToV>::value,
574 "Unexpected result type for implCast<ToV>(const "
575 "FromV&)");
576 DUNE_SIMD_CHECK(is123(toVec));
577 }
578
579 { // rvalue arg
580 auto toVec = implCast<ToV>(make123<FromV>());
581 static_assert(std::is_same<decltype(toVec), ToV>::value,
582 "Unexpected result type for implCast<ToV>(FromV&&)");
583 DUNE_SIMD_CHECK(is123(toVec));
584 }
585 }
586
587 // check the implCast function
588 template<class V>
589 void checkImplCast()
590 {
591 // check against LoopSIMD
592 using LoopV = Dune::LoopSIMD<Scalar<V>, lanes<V>()>;
593
594 checkImplCast<V, V>();
595 checkImplCast<V, LoopV>();
596 checkImplCast<LoopV, V>();
597 }
598
599 // check the broadcast function
600 template<class V>
601 void checkBroadcast()
602 {
603 // broadcast function
604 { // lvalue arg
605 Scalar<V> ref = 42;
606 auto vec = broadcast<V>(ref);
607 static_assert(std::is_same<decltype(vec), V>::value,
608 "Unexpected result type for broadcast<V>()");
609 DUNE_SIMD_CHECK(is42(vec));
610 DUNE_SIMD_CHECK(ref == Scalar<V>(42));
611 }
612
613 { // const lvalue arg
614 const Scalar<V> ref = 42;
615 auto vec = broadcast<V>(ref);
616 static_assert(std::is_same<decltype(vec), V>::value,
617 "Unexpected result type for broadcast<V>()");
618 DUNE_SIMD_CHECK(is42(vec));
619 }
620
621 { // rvalue arg
622 auto vec = broadcast<V>(Scalar<V>(42));
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 { // int arg
629 auto vec = broadcast<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 { // double arg
636 auto vec = broadcast<V>(42.0);
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
643 template<class V>
644 void checkBracedAssign()
645 {
646 // copy assignment
647 { V ref = make123<V>(); V vec; vec = {ref};
648 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
649 { const V ref = make123<V>(); V vec; vec = {ref};
650 DUNE_SIMD_CHECK(is123(vec)); DUNE_SIMD_CHECK(is123(ref)); }
651
652 // move assignment
653 { V vec; vec = {make123<V>()}; DUNE_SIMD_CHECK(is123(vec)); }
654 }
655
656 template<class V>
657 void checkBracedBroadcastAssign()
658 {
659 // nothing works here
660 // // broadcast copy assignment
661 // { Scalar<V> ref = 42; V vec; vec = {ref};
662 // DUNE_SIMD_CHECK(is42(vec)); DUNE_SIMD_CHECK(ref == Scalar<V>(42)); }
663 // { const Scalar<V> ref = 42; V vec; vec = {ref};
664 // DUNE_SIMD_CHECK(is42(vec)); }
665
666 // // broadcast move assignment
667 // { Scalar<V> ref = 42; V vec; vec = {std::move(ref)};
668 // DUNE_SIMD_CHECK(is42(vec)); }
669 }
670
672 //
673 // checks for unary operators
674 //
675
676#define DUNE_SIMD_POSTFIX_OP(NAME, SYMBOL) \
677 struct OpPostfix##NAME \
678 { \
679 template<class V> \
680 auto operator()(V&& v) const \
681 -> decltype(std::forward<V>(v) SYMBOL) \
682 { \
683 return std::forward<V>(v) SYMBOL; \
684 } \
685 }
686
687#define DUNE_SIMD_PREFIX_OP(NAME, SYMBOL) \
688 struct OpPrefix##NAME \
689 { \
690 template<class V> \
691 auto operator()(V&& v) const \
692 -> decltype(SYMBOL std::forward<V>(v)) \
693 { \
694 return SYMBOL std::forward<V>(v); \
695 } \
696 }
697
698 DUNE_SIMD_POSTFIX_OP(Decrement, -- );
699 DUNE_SIMD_POSTFIX_OP(Increment, ++ );
700
701 DUNE_SIMD_PREFIX_OP (Decrement, -- );
702 DUNE_SIMD_PREFIX_OP (Increment, ++ );
703
704 DUNE_SIMD_PREFIX_OP (Plus, + );
705 DUNE_SIMD_PREFIX_OP (Minus, - );
706 DUNE_SIMD_PREFIX_OP (LogicNot, ! );
707 // Do not warn about ~ being applied to bool. (1) Yes, doing that is
708 // weird, but we do want to test the weird stuff too. (2) It avoids
709 // running into <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82040> on
710 // g++-7.0 through 7.2. Also, ignore -Wpragmas to not warn about an
711 // unknown -Wbool-operation on compilers that do not know that option.
712#pragma GCC diagnostic push
713#pragma GCC diagnostic ignored "-Wpragmas"
714#pragma GCC diagnostic ignored "-Wunknown-warning-option" // clang 6.0.1
715#pragma GCC diagnostic ignored "-Wbool-operation"
716 DUNE_SIMD_PREFIX_OP (BitNot, ~ );
717#pragma GCC diagnostic pop
718
719#undef DUNE_SIMD_POSTFIX_OP
720#undef DUNE_SIMD_PREFIX_OP
721
722 template<class V, class Op>
724 IsCallable<Op(decltype(lane(0, std::declval<V>())))>::value>
725 checkUnaryOpV(Op op)
726 {
727#define DUNE_SIMD_OPNAME (className<Op(V)>())
728 // arguments
729 auto val = leftVector<std::decay_t<V>>();
730
731 // copy the arguments in case V is a references
732 auto arg = val;
733 auto &&result = op(static_cast<V>(arg));
734 using T = Scalar<std::decay_t<decltype(result)> >;
735 for(std::size_t l = 0; l < lanes(val); ++l)
736 {
737 // `op` might promote the argument. This is a problem if the
738 // argument of the operation on the right of the `==` is
739 // e.g. `(unsigned short)1` and the operation is e.g. unary `-`.
740 // Then the argument is promoted to `int` before applying the
741 // negation, and the result is `(int)-1`. However, the left side of
742 // the `==` is still `(unsigned short)-1`, which typically is the
743 // same as `(unsigned short)65535`. The `==` promotes the left side
744 // before comparing, so that becomes `(int)65535`. It will then
745 // compare `(int)65535` and `(int)-1` and rightly declare them to be
746 // not equal.
747
748 // To work around this, we explicitly convert the right side of the
749 // `==` to the scalar type before comparing.
751 (lane(l, result)
752 == static_cast<T>(op(lane(l, static_cast<V>(val)))));
753 }
754 // op might modify val, verify that any such modification also happens
755 // in the vector case
756 for(std::size_t l = 0; l < lanes<std::decay_t<V> >(); ++l)
757 DUNE_SIMD_CHECK_OP(lane(l, val) == lane(l, arg));
758#undef DUNE_SIMD_OPNAME
759 }
760
761 template<class V, class Op>
763 not IsCallable<Op(decltype(lane(0, std::declval<V>())))>::value>
764 checkUnaryOpV(Op op)
765 {
766 // log_ << "No " << className<Op(decltype(lane(0, std::declval<V>())))>()
767 // << std::endl
768 // << " ==> Not checking " << className<Op(V)>() << std::endl;
769 }
770
771 template<class V, class Op>
772 void checkUnaryOpsV(Op op)
773 {
774 checkUnaryOpV<V&>(op);
775 checkUnaryOpV<const V&>(op);
776 checkUnaryOpV<V&&>(op);
777 }
778
780 //
781 // checks for binary operators
782 //
783
784 // The operators contain an `operator()`, which will be invoked for both
785 // scalar and vector arguments. The function `scalar()` is used the
786 // test whether the scalar types support the operation (via
787 // `ScalarResult`). The difference is that `scalar()` should only ever
788 // receive `const`-ref-qualified version of `Scalar<V>`, while the
789 // `operator()` may also be called with proxies representing scalars.
790#define DUNE_SIMD_INFIX_OP(NAME, SYMBOL) \
791 struct OpInfix##NAME \
792 { \
793 template<class V1, class V2> \
794 decltype(auto) operator()(V1&& v1, V2&& v2) const \
795 { \
796 return std::forward<V1>(v1) SYMBOL std::forward<V2>(v2); \
797 } \
798 template<class S1, class S2> \
799 auto scalar(S1&& s1, S2&& s2) const \
800 -> decltype(std::forward<S1>(s1) SYMBOL std::forward<S2>(s2)); \
801 }
802
803 // for assign ops, accept only non-const lvalue arguments for scalars.
804 // This is needed for class scalars (e.g. std::complex) because
805 // non-const class rvalues are actually usually assignable. Though that
806 // assignment happens to a temporary, and thus is lost. Except that the
807 // tests would bind the result of the assignment to a reference. And
808 // because that result is returned from a function by reference, even
809 // though it is a temporary passed as an argument to that function,
810 // accessing the result later is undefined behaviour.
811#define DUNE_SIMD_ASSIGN_OP(NAME, SYMBOL) \
812 struct OpInfix##NAME \
813 { \
814 template<class V1, class V2> \
815 decltype(auto) operator()(V1&& v1, V2&& v2) const \
816 { \
817 return std::forward<V1>(v1) SYMBOL std::forward<V2>(v2); \
818 } \
819 template<class S1, class S2> \
820 auto scalar(S1& s1, S2&& s2) const \
821 -> decltype(s1 SYMBOL std::forward<S2>(s2)); \
822 }
823
824#define DUNE_SIMD_REPL_OP(NAME, REPLFN, SYMBOL) \
825 struct OpInfix##NAME \
826 { \
827 template<class V1, class V2> \
828 decltype(auto) operator()(V1&& v1, V2&& v2) const \
829 { \
830 return Simd::REPLFN(std::forward<V1>(v1), std::forward<V2>(v2)); \
831 } \
832 template<class S1, class S2> \
833 auto scalar(S1&& s1, S2&& s2) const \
834 -> decltype(std::forward<S1>(s1) SYMBOL std::forward<S2>(s2)); \
835 }
836
837 DUNE_SIMD_INFIX_OP(Mul, * );
838 DUNE_SIMD_INFIX_OP(Div, / );
839 DUNE_SIMD_INFIX_OP(Remainder, % );
840
841 DUNE_SIMD_INFIX_OP(Plus, + );
842 DUNE_SIMD_INFIX_OP(Minus, - );
843
844 DUNE_SIMD_INFIX_OP(LeftShift, << );
845 DUNE_SIMD_INFIX_OP(RightShift, >> );
846
847 DUNE_SIMD_INFIX_OP(Less, < );
848 DUNE_SIMD_INFIX_OP(Greater, > );
849 DUNE_SIMD_INFIX_OP(LessEqual, <= );
850 DUNE_SIMD_INFIX_OP(GreaterEqual, >= );
851
852 DUNE_SIMD_INFIX_OP(Equal, == );
853 DUNE_SIMD_INFIX_OP(NotEqual, != );
854
855 DUNE_SIMD_INFIX_OP(BitAnd, & );
856 DUNE_SIMD_INFIX_OP(BitXor, ^ );
857 DUNE_SIMD_INFIX_OP(BitOr, | );
858
859 // Those are not supported in any meaningful way by vectorclass
860 // We need to test replacement functions maskAnd() and maskOr() instead.
861 DUNE_SIMD_REPL_OP(LogicAnd, maskAnd, && );
862 DUNE_SIMD_REPL_OP(LogicOr, maskOr, || );
863
864 DUNE_SIMD_ASSIGN_OP(Assign, = );
865 DUNE_SIMD_ASSIGN_OP(AssignMul, *= );
866 DUNE_SIMD_ASSIGN_OP(AssignDiv, /= );
867 DUNE_SIMD_ASSIGN_OP(AssignRemainder, %= );
868 DUNE_SIMD_ASSIGN_OP(AssignPlus, += );
869 DUNE_SIMD_ASSIGN_OP(AssignMinus, -= );
870 DUNE_SIMD_ASSIGN_OP(AssignLeftShift, <<=);
871 DUNE_SIMD_ASSIGN_OP(AssignRightShift, >>=);
872 DUNE_SIMD_ASSIGN_OP(AssignAnd, &= );
873 DUNE_SIMD_ASSIGN_OP(AssignXor, ^= );
874 DUNE_SIMD_ASSIGN_OP(AssignOr, |= );
875
876 // comparison operators, their result must be of type Mask<V>
877 template<class Op>
878 static constexpr bool isComparisonOp =
885
886#undef DUNE_SIMD_INFIX_OP
887#undef DUNE_SIMD_REPL_OP
888#undef DUNE_SIMD_ASSIGN_OP
889
890 // just used as a tag
891 struct OpInfixComma {};
892
893 template<class T1, class T2>
894 void checkCommaOp(const std::decay_t<T1> &val1,
895 const std::decay_t<T2> &val2)
896 {
897#define DUNE_SIMD_OPNAME (className<OpInfixComma(T1, T2)>())
899 std::declval<T2>())), T2>::value,
900 "Type and value category of the comma operator must "
901 "match that of the second operand");
902
903 // copy the arguments in case T1 or T2 are references
904 auto arg1 = val1;
905 auto arg2 = val2;
906 // Do not warn that the left side of the comma operator is unused.
907 // Seems to work for g++-4.9 and clang++-3.8. Appears to be harmless
908 // for icpc (14 and 17), and icpc does not seem to issue a warning
909 // anyway.
910#pragma GCC diagnostic push
911#pragma GCC diagnostic ignored "-Wunused-value"
912 auto &&result = (static_cast<T1>(arg1),
913 static_cast<T2>(arg2));
914#pragma GCC diagnostic pop
916 {
917 // comma should return the same object as the second argument for
918 // lvalues and xvalues
919 DUNE_SIMD_CHECK_OP(&result == &arg2);
920 // it should not modify any arguments
921 DUNE_SIMD_CHECK_OP(allTrue(val1 == arg1));
922 DUNE_SIMD_CHECK_OP(allTrue(val2 == arg2));
923 }
924 else
925 {
926 // comma should return the same value as the second argument for
927 // prvalues
928 DUNE_SIMD_CHECK_OP(allTrue(result == arg2));
929 // it should not modify any arguments
930 DUNE_SIMD_CHECK_OP(allTrue(val1 == arg1));
931 // second argument is a prvalue, any modifications happen to a
932 // temporary and we can't detect them
933 }
934#undef DUNE_SIMD_OPNAME
935 }
936
938 //
939 // checks for vector-vector binary operations
940 //
941
942 // We check the following candidate operation
943 //
944 // vopres = vop1 @ vop2
945 //
946 // against the reference operation
947 //
948 // arefres[l] = aref1[l] @ aref2[l] foreach l
949 //
950 // v... variables are simd-vectors and a... variables are arrays. The
951 // operation may modify the operands, but if is does the modification
952 // needs to happen in both the candidate and the reference.
953 //
954 // We do the following checks:
955 // 1. lanes(vopres) == lanes(vop1)
956 // 2. lane(l, vopres) == arefres[l] foreach l
957 // 3. lane(l, vop1) == aref1[l] foreach l
958 // 4. lane(l, vop2) == aref2[l] foreach l
959 template<class V1, class V2, class Op>
961 checkBinaryOpVV(MetaType<V1>, MetaType<V2>, Op op)
962 {
963#define DUNE_SIMD_OPNAME (className<Op(V1, V2)>())
964 static_assert(std::is_same<std::decay_t<V1>, std::decay_t<V2> >::value,
965 "Internal testsystem error: called with two types that "
966 "don't decay to the same thing");
967
968 // reference arguments
969 auto vref1 = leftVector<std::decay_t<V1>>();
970 auto vref2 = rightVector<std::decay_t<V2>>();
971
972 // candidate arguments
973 auto vop1 = vref1;
974 auto vop2 = vref2;
975
976 // candidate operation
977 auto &&vopres = op(static_cast<V1>(vop1), static_cast<V2>(vop2));
978 using VR = decltype(vopres);
979
980 // check 1. lanes(vopres) == lanes(vop1)
981 static_assert(lanes<std::decay_t<VR> >() == lanes<std::decay_t<V1> >(),
982 "The result must have the same number of lanes as the "
983 "operands.");
984
985 // comparisons must yield exactly the mask type, see DESIGN.md
986 static_assert(!isComparisonOp<Op> ||
988 Mask<std::decay_t<V1> > >::value,
989 "The result of a comparison must be of type Mask<V>.");
990
991 // do the reference operation, and simultaneously
992 // check 2. lane(l, vopres) == arefres[l] foreach l
993 using T = Scalar<std::decay_t<VR> >;
994 for(auto l : range(lanes(vopres)))
995 {
996 // see the lengthy comment in `checkUnaryOpV()` as to why the
997 // `static_cast` around the `op()` is necessary
999 (lane(l, vopres)
1000 == static_cast<T>(op(lane(l, static_cast<V1>(vref1)),
1001 lane(l, static_cast<V2>(vref2)))));
1002 }
1003
1004 // check 3. lane(l, vop1) == aref1[l] foreach l
1005 for(auto l : range(lanes(vop1)))
1006 DUNE_SIMD_CHECK_OP(lane(l, vop1) == lane(l, vref1));
1007
1008 // check 4. lane(l, vop2) == aref2[l] foreach l
1009 for(auto l : range(lanes(vop2)))
1010 DUNE_SIMD_CHECK_OP(lane(l, vop2) == lane(l, vref2));
1011
1012#undef DUNE_SIMD_OPNAME
1013 }
1014
1015 template<class V1, class V2, class Op>
1017 checkBinaryOpVV(MetaType<V1>, MetaType<V2>, Op op)
1018 {
1019 // log_ << "No " << className<Op(decltype(lane(0, std::declval<V1>())),
1020 // decltype(lane(0, std::declval<V2>())))>()
1021 // << std::endl
1022 // << " ==> Not checking " << className<Op(V1, V2)>() << std::endl;
1023 }
1024
1025 template<class V1, class V2>
1026 void checkBinaryOpVV(MetaType<V1>, MetaType<V2>, OpInfixComma)
1027 {
1028 static_assert(std::is_same<std::decay_t<V1>, std::decay_t<V2> >::value,
1029 "Internal testsystem error: called with two types that "
1030 "don't decay to the same thing");
1031
1032 checkCommaOp<V1, V2>(leftVector<std::decay_t<V1>>(),
1033 rightVector<std::decay_t<V2>>());
1034 }
1035
1037 //
1038 // checks for vector-scalar binary operations
1039 //
1040
1041 // We check the following candidate operation
1042 //
1043 // vopres = vop1 @ sop2
1044 //
1045 // against the reference operation
1046 //
1047 // arefres[l] = aref1[l] @ sref2 foreach l
1048 //
1049 // v... variables are simd-vectors, a... variables are arrays, and
1050 // s... variables are scalars. The operation may modify the left
1051 // operand, but if is does the modifications needs to happen in both the
1052 // candidate and the reference.
1053 //
1054 // We do the following checks:
1055 // 1. lanes(vopres) == lanes(vop1)
1056 // 2. lane(l, vopres) == arefres[l] foreach l
1057 // 3. lane(l, vop1) == aref1[l] foreach l
1058 // 4. sop2 is never modified
1059 // 5. sref2 is never modified
1060 //
1061 // In fact, if the property "sref2 is never modified" is violated that
1062 // means the operation is unsuitable for an automatic broadcast of the
1063 // second operand and should not be checked. There are no operations in
1064 // the standard where the second operand is modified like this, but
1065 // there are operations where the first operand is modified -- and this
1066 // check is used for those ops as well by exchanging the first and second
1067 // argument below.
1068
1069 template<class V1, class T2, class Op>
1071 checkBinaryOpVS(MetaType<V1>, MetaType<T2>, Op op)
1072 {
1073#define DUNE_SIMD_OPNAME (className<Op(V1, T2)>())
1075 std::decay_t<T2> >::value,
1076 "Internal testsystem error: called with a scalar that "
1077 "does not match the vector type.");
1078
1079 // initial values
1080 auto sinit2 = rightScalar<std::decay_t<T2>>();
1081
1082 // reference arguments
1083 auto vref1 = leftVector<std::decay_t<V1>>();
1084 auto sref2 = sinit2;
1085
1086 // candidate arguments
1087 auto vop1 = vref1;
1088 auto sop2 = sref2;
1089
1090 // candidate operation
1091 auto &&vopres = op(static_cast<V1>(vop1), static_cast<T2>(sop2));
1092 using VR = decltype(vopres);
1093
1094 // check 1. lanes(vopres) == lanes(vop1)
1095 static_assert(lanes<std::decay_t<VR> >() == lanes<std::decay_t<V1> >(),
1096 "The result must have the same number of lanes as the "
1097 "operands.");
1098
1099 // check 4. sop2 is never modified
1100 DUNE_SIMD_CHECK_OP(sop2 == sinit2);
1101
1102 // do the reference operation, and simultaneously check 2. and 5.
1103 using T = Scalar<std::decay_t<decltype(vopres)> >;
1104 for(auto l : range(lanes(vopres)))
1105 {
1106 // check 2. lane(l, vopres) == arefres[l] foreach l
1107 // see the lengthy comment in `checkUnaryOpV()` as to why the
1108 // `static_cast` around the `op()` is necessary
1110 (lane(l, vopres)
1111 == static_cast<T>(op(lane(l, static_cast<V1>(vref1)),
1112 static_cast<T2>(sref2) )));
1113 // check 5. sref2 is never modified
1114 DUNE_SIMD_CHECK_OP(sref2 == sinit2);
1115 }
1116
1117 // check 3. lane(l, vop1) == aref1[l] foreach l
1118 for(auto l : range(lanes(vop1)))
1119 DUNE_SIMD_CHECK_OP(lane(l, vop1) == lane(l, vref1));
1120
1121#undef DUNE_SIMD_OPNAME
1122 }
1123
1124 template<class V1, class T2, class Op>
1126 checkBinaryOpVS(MetaType<V1>, MetaType<T2>, Op op)
1127 {
1128 // log_ << "No "
1129 // << className<Op(decltype(lane(0, std::declval<V1>())), T2)>()
1130 // << std::endl
1131 // << " ==> Not checking " << className<Op(V1, T2)>() << std::endl;
1132 }
1133
1134 template<class V1, class T2>
1135 void checkBinaryOpVS(MetaType<V1>, MetaType<T2>, OpInfixComma)
1136 {
1138 std::decay_t<T2> >::value,
1139 "Internal testsystem error: called with a scalar that "
1140 "does not match the vector type.");
1141
1142 checkCommaOp<V1, T2>(leftVector<std::decay_t<V1>>(),
1143 rightScalar<std::decay_t<T2>>());
1144 }
1145
1147 //
1148 // cross-check scalar-vector binary operations against vector-vector
1149 //
1150
1151 // We check the following candidate operation
1152 //
1153 // vopres = vop1 @ vop2, where vop2 = broadcast(sref2)
1154 //
1155 // against the reference operation
1156 //
1157 // vrefres = vref1 @ sref2
1158 //
1159 // v... variables are simd-vectors, a... variables are arrays, and
1160 // s... variables are scalars.
1161 //
1162 // We could check the following properties
1163 // 1. lanes(vopres) == lanes(vop1)
1164 // 2. lane(l, vopres) == lane(l, vrefres) foreach l
1165 // 3. lane(l, vop1) == lane(l, vref1) foreach l
1166 // but these are given by checking the operation against the scalar
1167 // operation in the vector@vector and vector@scalar cases above.
1168 //
1169 // The only thing left to check is:
1170 // 4. lane(l, vop2) foreach l is never modified
1171
1172 template<class V1, class T2, class Op>
1174 checkBinaryOpVVAgainstVS(MetaType<V1>, MetaType<T2>, Op op)
1175 {
1176#define DUNE_SIMD_OPNAME (className<Op(V1, T2)>())
1178 std::decay_t<T2> >::value,
1179 "Internal testsystem error: called with a scalar that "
1180 "does not match the vector type.");
1181
1182 // initial values
1183 auto sinit2 = rightScalar<std::decay_t<T2>>();
1184
1185 // reference arguments
1186 auto vop1 = leftVector<std::decay_t<V1>>();
1187 using V2 = CopyRefQual<V1, T2>;
1188 std::decay_t<V2> vop2(sinit2);
1189
1190 // candidate operation
1191 op(static_cast<V1>(vop1), static_cast<V2>(vop2));
1192
1193 // 4. lane(l, vop2) foreach l is never modified
1194 for(auto l : range(lanes(vop2)))
1195 DUNE_SIMD_CHECK_OP(lane(l, vop2) == sinit2);
1196
1197#undef DUNE_SIMD_OPNAME
1198 }
1199
1200 template<class V1, class T2, class Op>
1202 checkBinaryOpVVAgainstVS(MetaType<V1>, MetaType<T2>, Op op)
1203 {
1204 // log_ << "No "
1205 // << className<Op(decltype(lane(0, std::declval<V1>())), T2)>()
1206 // << std::endl
1207 // << " ==> Not checking " << className<Op(V1, T2)>() << std::endl;
1208 }
1209
1210 template<class V1, class T2>
1211 void checkBinaryOpVVAgainstVS(MetaType<V1>, MetaType<T2>, OpInfixComma)
1212 { }
1213
1215 //
1216 // checks for vector-proxy binary operations
1217 //
1218
1219 // We check the following candidate operation
1220 //
1221 // vopres = vop1 @ pop2
1222 //
1223 // against the reference operation
1224 //
1225 // arefres[l] = aref1[l] @ sref2 foreach l
1226 //
1227 // v... variables are simd-vectors, a... variables are arrays,
1228 // p... variables are proxies of simd-vector entries and s... variables
1229 // are scalars. The operation may modify the left operand, but if is
1230 // does the modifications needs to happen in both the candidate and the
1231 // reference.
1232 //
1233 // We do the following checks:
1234 // 1. lanes(vopres) == lanes(vop1)
1235 // 2. lane(l, vopres) == arefres[l] foreach l
1236 // 3. lane(l, vop1) == aref1[l] foreach l
1237 // 4. pop2 is never modified
1238 // 5. sref2 is never modified
1239 //
1240 // In fact, if the property "sref2 is never modified" is violated that
1241 // means the operation is unsuitable for an automatic broadcast of the
1242 // second operand and should not be checked. There are no operations in
1243 // the standard where the second operand is modified like this, but
1244 // there are operations where the first operand is modified -- and this
1245 // check is used for those ops as well by exchanging the first and second
1246 // argument below.
1247
1248 template<class V1, class V2, class Op>
1250 checkBinaryOpVP(MetaType<V1>, MetaType<V2>, Op op)
1251 {
1252 using P2 = decltype(lane(0, std::declval<V2>()));
1253 using T2 = CopyRefQual<Scalar<V2>, V2>;
1254#define DUNE_SIMD_OPNAME (className<Op(V1, P2)>())
1255 static_assert(std::is_same<Scalar<V1>, Scalar<V2> >::value,
1256 "Internal testsystem error: called with two vector "
1257 "types whose scalar types don't match.");
1258
1259 // initial values
1260 auto sinit2 = rightScalar<Scalar<V2>>();
1261
1262 // reference arguments
1263 auto vref1 = leftVector<std::decay_t<V1>>();
1264 auto sref2 = sinit2;
1265
1266 // candidate arguments
1267 auto vop1 = vref1;
1268 auto vop2 = std::decay_t<V2>(Scalar<V2>(0));
1269 lane(0, vop2) = sref2; // pop2 is just a name for `lane(0, vop2)`
1270
1271 // candidate operation
1272 auto &&vopres =
1273 op(static_cast<V1>(vop1), lane(0, static_cast<V2>(vop2)));
1274 using VR = decltype(vopres);
1275
1276 // check 1. lanes(vopres) == lanes(vop1)
1277 static_assert(lanes<std::decay_t<VR> >() == lanes<std::decay_t<V1> >(),
1278 "The result must have the same number of lanes as the "
1279 "operands.");
1280
1281 // check 4. pop2 is never modified
1282 DUNE_SIMD_CHECK_OP(lane(0, vop2) == sinit2);
1283
1284 // do the reference operation, and simultaneously check 2. and 5.
1285 using T = Scalar<decltype(vopres)>;
1286 for(auto l : range(lanes(vopres)))
1287 {
1288 // check 2. lane(l, vopres) == arefres[l] foreach l
1289 // see the lengthy comment in `checkUnaryOpV()` as to why the
1290 // `static_cast` around the `op()` is necessary
1292 (lane(l, vopres)
1293 == static_cast<T>(op(lane(l, static_cast<V1>(vref1)),
1294 static_cast<T2>(sref2) )));
1295 // check 5. sref2 is never modified
1296 DUNE_SIMD_CHECK_OP(sref2 == sinit2);
1297 }
1298
1299 // check 3. lane(l, vop1) == aref1[l] foreach l
1300 for(auto l : range(lanes(vop1)))
1301 DUNE_SIMD_CHECK_OP(lane(l, vop1) == lane(l, vref1));
1302
1303#undef DUNE_SIMD_OPNAME
1304 }
1305
1306 template<class V1, class V2, class Op>
1308 checkBinaryOpVP(MetaType<V1>, MetaType<V2>, Op op)
1309 {
1310 // log_ << "No "
1311 // << className<Op(decltype(lane(0, std::declval<V1>())), T2)>()
1312 // << std::endl
1313 // << " ==> Not checking " << className<Op(V1, T2)>() << std::endl;
1314 }
1315
1316 template<class V1, class V2>
1317 void checkBinaryOpVP(MetaType<V1>, MetaType<V2>, OpInfixComma)
1318 {
1319 // Don't really know how to check comma operator for proxies
1320 }
1321
1323 //
1324 // checks for (scalar/proxy)-vector binary operations
1325 //
1326
1327 template<class Op>
1328 struct OpInfixSwappedArgs
1329 {
1330 Op orig;
1331
1332 template<class V1, class V2>
1333 decltype(auto) operator()(V1&& v1, V2&& v2) const
1334 {
1335 return orig(std::forward<V2>(v2), std::forward<V1>(v1));
1336 }
1337 template<class S1, class S2>
1338 auto scalar(S1&& s1, S2&& s2) const
1339 -> decltype(orig.scalar(std::forward<S2>(s2), std::forward<S1>(s1)));
1340 };
1341
1342 template<class T1, class V2, class Op>
1343 void checkBinaryOpSV(MetaType<T1> t1, MetaType<V2> v2, Op op)
1344 {
1345 checkBinaryOpVS(v2, t1, OpInfixSwappedArgs<Op>{op});
1346 }
1347
1348 template<class T1, class V2>
1349 void checkBinaryOpSV(MetaType<T1>, MetaType<V2>, OpInfixComma)
1350 {
1351 static_assert(std::is_same<std::decay_t<T1>,
1352 Scalar<std::decay_t<V2> > >::value,
1353 "Internal testsystem error: called with a scalar that "
1354 "does not match the vector type.");
1355
1356 checkCommaOp<T1, V2>(leftScalar<std::decay_t<T1>>(),
1357 rightVector<std::decay_t<V2>>());
1358 }
1359
1360 template<class V1, class V2, class Op>
1361 void checkBinaryOpPV(MetaType<V1> v1, MetaType<V2> v2, Op op)
1362 {
1363 checkBinaryOpVP(v2, v1, OpInfixSwappedArgs<Op>{op});
1364 }
1365
1366 template<class V1, class V2>
1367 void checkBinaryOpPV(MetaType<V1>, MetaType<V2>, OpInfixComma)
1368 {
1369 // Don't really know how to check comma operator for proxies
1370 }
1371
1373 //
1374 // checks for mixed-precision vector-proxy binary operations
1375 //
1376
1377 // We check the following candidate operation
1378 //
1379 // wopres = wop1 @ pop2
1380 //
1381 // against the reference operation
1382 //
1383 // wrefres = wref1 @ sref2
1384 //
1385 // w... variables are simd-vectors of type W, p... variables are proxies
1386 // of entries of simd-vectors of type V, and s... variables are scalars
1387 // of type Scalar<V>, with Scalar<W> != Scalar<V>. I.e. a proxy must
1388 // behave like the scalar it represents, also when combined with vectors
1389 // of a different scalar type. We only check operations that are
1390 // supported by the scalars, and where `wref1 @ sref2` is supported.
1391 //
1392 // We do the following checks:
1393 // 1. decltype(wopres) == decltype(wrefres) (after decay)
1394 // 2. lane(l, wopres) == lane(l, wrefres) foreach l
1395 // 3. lane(l, wop1) == lane(l, wref1) foreach l
1396 // 4. pop2 is never modified
1397
1398 // use `Op::scalar()`, which is SFINAE-friendly; `Op::operator()` has a
1399 // deduced return type, so checking it would instantiate its body.
1400 // Despite its name, `scalar()` works for any operand types, here a
1401 // vector and a scalar.
1402 template<class Op, class W1, class T2>
1403 using MixedResult =
1404 decltype(std::declval<Op>().scalar(std::declval<W1>(),
1405 std::declval<T2>()));
1406
1407 template<class W1, class V2, class Op>
1408 void checkBinaryOpMixedVP(MetaType<W1>, MetaType<V2>, Op op)
1409 {
1410 using W = std::decay_t<W1>;
1411 using V = std::decay_t<V2>;
1412 using T2 = const Scalar<V>&;
1413
1414 // check the scalar operation first, the vector operation may not be
1415 // SFINAE-friendly
1416 if constexpr (Std::is_detected_v<ScalarResult, Op, W1, V2>)
1417 if constexpr (Std::is_detected_v<MixedResult, Op, W1, T2>)
1418 {
1419#define DUNE_SIMD_OPNAME \
1420 (className<Op(W1, decltype(lane(0, std::declval<V2>())))>())
1421 // initial values
1422 const auto sinit2 = rightScalar<Scalar<V> >();
1423
1424 // reference arguments
1425 auto wref1 = leftVector<W>();
1426 auto sref2 = sinit2;
1427
1428 // candidate arguments
1429 auto wop1 = wref1;
1430 auto vop2 = V(Scalar<V>(0));
1431 lane(0, vop2) = sinit2; // pop2 is just a name for `lane(0, vop2)`
1432
1433 // reference and candidate operation
1434 auto &&wrefres = op(static_cast<W1>(wref1), static_cast<T2>(sref2));
1435 auto &&wopres =
1436 op(static_cast<W1>(wop1), lane(0, static_cast<V2>(vop2)));
1437
1438 // check 1. decltype(wopres) == decltype(wrefres)
1439 static_assert(std::is_same<std::decay_t<decltype(wopres)>,
1440 std::decay_t<decltype(wrefres)> >::value,
1441 "An operation with a proxy must have the same result "
1442 "type as the operation with the scalar it "
1443 "represents.");
1444
1445 // check 2. lane(l, wopres) == lane(l, wrefres) foreach l
1446 for(auto l : range(lanes(wopres)))
1447 DUNE_SIMD_CHECK_OP(lane(l, wopres) == lane(l, wrefres));
1448
1449 // check 3. lane(l, wop1) == lane(l, wref1) foreach l
1450 for(auto l : range(lanes(wop1)))
1451 DUNE_SIMD_CHECK_OP(lane(l, wop1) == lane(l, wref1));
1452
1453 // check 4. pop2 is never modified
1454 DUNE_SIMD_CHECK_OP(lane(0, vop2) == sinit2);
1455#undef DUNE_SIMD_OPNAME
1456 }
1457 }
1458
1459 template<class W1, class V2>
1460 void checkBinaryOpMixedVP(MetaType<W1>, MetaType<V2>, OpInfixComma)
1461 {
1462 // Don't really know how to check comma operator for proxies
1463 }
1464
1465 template<class V1, class W2, class Op>
1466 void checkBinaryOpMixedPV(MetaType<V1> v1, MetaType<W2> w2, Op op)
1467 {
1468 checkBinaryOpMixedVP(w2, v1, OpInfixSwappedArgs<Op>{op});
1469 }
1470
1471 template<class V1, class W2>
1472 void checkBinaryOpMixedPV(MetaType<V1>, MetaType<W2>, OpInfixComma)
1473 {
1474 // Don't really know how to check comma operator for proxies
1475 }
1476
1477 // scalar types used to construct the vector types W = Rebind<S, V> for
1478 // the mixed-precision checks
1479 using MixedPrecisionScalars = TypeList<float, double, int>;
1480
1481 // call f(MetaType<W>) for the vector types W used in the
1482 // mixed-precision checks of V
1483 template<class V, class F>
1484 static void forEachMixedPrecisionPartner(F f)
1485 {
1486 if constexpr (!std::is_same<Scalar<V>, bool>::value)
1487 Hybrid::forEach(MixedPrecisionScalars{}, [&](auto s) {
1488 using W = Rebind<typename decltype(s)::type, V>;
1489 if constexpr (!std::is_same<Scalar<W>, Scalar<V> >::value &&
1490 !Impl::IsAnyLoop<W>::value)
1491 f(MetaType<W>{});
1492 });
1493 }
1494
1496 //
1497 // cross-check scalar-vector binary operations against vector-vector
1498 //
1499
1500 // We check the following candidate operation
1501 //
1502 // vopres = vop1 @ vop2, where vop2 = broadcast(sref2)
1503 //
1504 // against the reference operation
1505 //
1506 // vrefres = vref1 @ sref2
1507 //
1508 // v... variables are simd-vectors, a... variables are arrays, and
1509 // s... variables are scalars.
1510 //
1511 // We could check the following properties
1512 // 1. lanes(vopres) == lanes(vop1)
1513 // 2. lane(l, vopres) == lane(l, vrefres) foreach l
1514 // 3. lane(l, vop1) == lane(l, vref1) foreach l
1515 // but these are given by checking the operation against the scalar
1516 // operation in the vector@vector and vector@scalar cases above.
1517 //
1518 // The only thing left to check is:
1519 // 4. lane(l, vop2) foreach l is never modified
1520
1521 template<class T1, class V2, class Op>
1522 void checkBinaryOpVVAgainstSV(MetaType<T1> t1, MetaType<V2> v2, Op op)
1523 {
1524 checkBinaryOpVVAgainstVS(v2, t1, OpInfixSwappedArgs<Op>{op});
1525 }
1526
1527 template<class V1, class T2>
1528 void checkBinaryOpVVAgainstSV(MetaType<V1>, MetaType<T2>, OpInfixComma)
1529 { }
1530
1532 //
1533 // Invoke the checks for all combinations
1534 //
1535
1536 template<class T1, class T2, bool condition, class Checker>
1537 void checkBinaryRefQual(Checker checker)
1538 {
1539 if constexpr (condition) {
1540 Hybrid::forEach(TypeList<T1&, const T1&, T1&&>{}, [=] (auto t1) {
1541 Hybrid::forEach(TypeList<T2&, const T2&, T2&&>{}, [=] (auto t2) {
1542 checker(t1, t2);
1543 });
1544 });
1545 }
1546 }
1547
1548 template<class V, class Checker>
1549 void checkBinaryOps(Checker checker)
1550 {
1551 using std::bool_constant;
1552
1553 constexpr bool isMask = std::is_same<Scalar<V>, bool>::value;
1554
1555 constexpr bool do_ = false;
1556 constexpr bool do_SV = true;
1557 constexpr bool do_VV = true;
1558 constexpr bool do_VS = true;
1559 // Not all SIMD libraries provide the shift operators `<<` and `>>`
1560 // with two vector operands, e.g. Vc >= 1.4 does not for `SimdArray`.
1561 // Check them only if available. The requirements are checked in
1562 // order, so requiring the scalar shifts first avoids instantiating
1563 // vector shift operators that are not SFINAE-friendly, e.g. for
1564 // `LoopSIMD<float>`.
1565 constexpr bool do_VVShift =
1566 requires(const Scalar<V>& s, const V& v) {
1567 s << s; s >> s;
1568 v << v; v >> v;
1569 };
1570
1571#define DUNE_SIMD_DO(M1, M2, M3, V1, V2, V3, NAME) \
1572 checker(bool_constant<isMask ? do_##M1 : do_##V1>{}, \
1573 bool_constant<isMask ? do_##M2 : do_##V2>{}, \
1574 bool_constant<isMask ? do_##M3 : do_##V3>{}, \
1575 Op##NAME{})
1576
1577 // (Mask , Vector , Name );
1578
1579 DUNE_SIMD_DO( , , , SV, VV, VS, InfixMul );
1580 DUNE_SIMD_DO( , , , SV, VV, VS, InfixDiv );
1581 DUNE_SIMD_DO( , , , SV, VV, VS, InfixRemainder );
1582
1583 DUNE_SIMD_DO( , , , SV, VV, VS, InfixPlus );
1584 DUNE_SIMD_DO( , , , SV, VV, VS, InfixMinus );
1585
1586 DUNE_SIMD_DO( , , , , VVShift, VS, InfixLeftShift );
1587 DUNE_SIMD_DO( , , , , VVShift, VS, InfixRightShift );
1588
1589 DUNE_SIMD_DO( , , , SV, VV, VS, InfixLess );
1590 DUNE_SIMD_DO( , , , SV, VV, VS, InfixGreater );
1591 DUNE_SIMD_DO( , , , SV, VV, VS, InfixLessEqual );
1592 DUNE_SIMD_DO( , , , SV, VV, VS, InfixGreaterEqual );
1593
1594 DUNE_SIMD_DO( , , , SV, VV, VS, InfixEqual );
1595 DUNE_SIMD_DO( , , , SV, VV, VS, InfixNotEqual );
1596
1597 DUNE_SIMD_DO( , VV, , SV, VV, VS, InfixBitAnd );
1598 DUNE_SIMD_DO( , VV, , SV, VV, VS, InfixBitXor );
1599 DUNE_SIMD_DO( , VV, , SV, VV, VS, InfixBitOr );
1600
1601 DUNE_SIMD_DO(SV, VV, VS, SV, VV, VS, InfixLogicAnd );
1602 DUNE_SIMD_DO(SV, VV, VS, SV, VV, VS, InfixLogicOr );
1603
1604 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssign );
1605 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignMul );
1606 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignDiv );
1607 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignRemainder );
1608 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignPlus );
1609 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignMinus );
1610 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignLeftShift );
1611 DUNE_SIMD_DO( , , , , VV, VS, InfixAssignRightShift);
1612 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssignAnd );
1613 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssignXor );
1614 DUNE_SIMD_DO( , VV, , , VV, VS, InfixAssignOr );
1615
1616 DUNE_SIMD_DO(SV, VV, VS, SV, , VS, InfixComma );
1617
1618#undef DUNE_SIMD_DO
1619 }
1620
1622 //
1623 // SIMD interface functions
1624 //
1625
1626 template<class V>
1627 void checkAutoCopy()
1628 {
1629 using RValueResult = decltype(autoCopy(lane(0, std::declval<V>())));
1630 static_assert(std::is_same<RValueResult, Scalar<V> >::value,
1631 "Result of autoCopy() must always be Scalar<V>");
1632
1633 using MutableLValueResult =
1634 decltype(autoCopy(lane(0, std::declval<V&>())));
1635 static_assert(std::is_same<MutableLValueResult, Scalar<V> >::value,
1636 "Result of autoCopy() must always be Scalar<V>");
1637
1638 using ConstLValueResult =
1639 decltype(autoCopy(lane(0, std::declval<const V&>())));
1640 static_assert(std::is_same<ConstLValueResult, Scalar<V> >::value,
1641 "Result of autoCopy() must always be Scalar<V>");
1642
1643 V vec = make123<V>();
1644 for(std::size_t l = 0; l < lanes(vec); ++l)
1645 DUNE_SIMD_CHECK(autoCopy(lane(l, vec)) == Scalar<V>(l+1));
1646 }
1647
1648 // may only be called for mask types
1649 template<class M>
1650 void checkBoolReductions()
1651 {
1652 M trueVec(true);
1653
1654 // mutable lvalue
1655 DUNE_SIMD_CHECK(allTrue (static_cast<M&>(trueVec)) == true);
1656 DUNE_SIMD_CHECK(anyTrue (static_cast<M&>(trueVec)) == true);
1657 DUNE_SIMD_CHECK(allFalse(static_cast<M&>(trueVec)) == false);
1658 DUNE_SIMD_CHECK(anyFalse(static_cast<M&>(trueVec)) == false);
1659
1660 // const lvalue
1661 DUNE_SIMD_CHECK(allTrue (static_cast<const M&>(trueVec)) == true);
1662 DUNE_SIMD_CHECK(anyTrue (static_cast<const M&>(trueVec)) == true);
1663 DUNE_SIMD_CHECK(allFalse(static_cast<const M&>(trueVec)) == false);
1664 DUNE_SIMD_CHECK(anyFalse(static_cast<const M&>(trueVec)) == false);
1665
1666 // rvalue
1667 DUNE_SIMD_CHECK(allTrue (M(true)) == true);
1668 DUNE_SIMD_CHECK(anyTrue (M(true)) == true);
1669 DUNE_SIMD_CHECK(allFalse(M(true)) == false);
1670 DUNE_SIMD_CHECK(anyFalse(M(true)) == false);
1671
1672 M falseVec(false);
1673
1674 // mutable lvalue
1675 DUNE_SIMD_CHECK(allTrue (static_cast<M&>(falseVec)) == false);
1676 DUNE_SIMD_CHECK(anyTrue (static_cast<M&>(falseVec)) == false);
1677 DUNE_SIMD_CHECK(allFalse(static_cast<M&>(falseVec)) == true);
1678 DUNE_SIMD_CHECK(anyFalse(static_cast<M&>(falseVec)) == true);
1679
1680 // const lvalue
1681 DUNE_SIMD_CHECK(allTrue (static_cast<const M&>(falseVec)) == false);
1682 DUNE_SIMD_CHECK(anyTrue (static_cast<const M&>(falseVec)) == false);
1683 DUNE_SIMD_CHECK(allFalse(static_cast<const M&>(falseVec)) == true);
1684 DUNE_SIMD_CHECK(anyFalse(static_cast<const M&>(falseVec)) == true);
1685
1686 // rvalue
1687 DUNE_SIMD_CHECK(allTrue (M(false)) == false);
1688 DUNE_SIMD_CHECK(anyTrue (M(false)) == false);
1689 DUNE_SIMD_CHECK(allFalse(M(false)) == true);
1690 DUNE_SIMD_CHECK(anyFalse(M(false)) == true);
1691
1692 auto mixedVec = broadcast<M>(0);
1693 for(std::size_t l = 0; l < lanes(mixedVec); ++l)
1694 lane(l, mixedVec) = (l % 2);
1695
1696 // mutable lvalue
1698 (allTrue (static_cast<M&>(mixedVec)) == false);
1700 (anyTrue (static_cast<M&>(mixedVec)) == (lanes<M>() > 1));
1702 (allFalse(static_cast<M&>(mixedVec)) == (lanes<M>() == 1));
1704 (anyFalse(static_cast<M&>(mixedVec)) == true);
1705
1706 // const lvalue
1708 (allTrue (static_cast<const M&>(mixedVec)) == false);
1710 (anyTrue (static_cast<const M&>(mixedVec)) == (lanes<M>() > 1));
1712 (allFalse(static_cast<const M&>(mixedVec)) == (lanes<M>() == 1));
1714 (anyFalse(static_cast<const M&>(mixedVec)) == true);
1715
1716 // rvalue
1717 DUNE_SIMD_CHECK(allTrue (M(mixedVec)) == false);
1718 DUNE_SIMD_CHECK(anyTrue (M(mixedVec)) == (lanes<M>() > 1));
1719 DUNE_SIMD_CHECK(allFalse(M(mixedVec)) == (lanes<M>() == 1));
1720 DUNE_SIMD_CHECK(anyFalse(M(mixedVec)) == true);
1721 }
1722
1723 template<class V>
1724 void checkCond()
1725 {
1726 using M = Mask<V>;
1727
1728 static_assert
1729 (std::is_same<decltype(cond(std::declval<M>(), std::declval<V>(),
1730 std::declval<V>())), V>::value,
1731 "The result of cond(M, V, V) should have exactly the type V");
1732
1733 static_assert
1735 std::declval<const V&>(),
1736 std::declval<const V&>())), V>::value,
1737 "The result of cond(const M&, const V&, const V&) should have "
1738 "exactly the type V");
1739
1740 static_assert
1741 (std::is_same<decltype(cond(std::declval<M&>(), std::declval<V&>(),
1742 std::declval<V&>())), V>::value,
1743 "The result of cond(M&, V&, V&) should have exactly the type V");
1744
1745 V vec1 = leftVector<V>();
1746 V vec2 = rightVector<V>();
1747
1748 DUNE_SIMD_CHECK(allTrue(cond(M(true), vec1, vec2) == vec1));
1749 DUNE_SIMD_CHECK(allTrue(cond(M(false), vec1, vec2) == vec2));
1750
1751 auto mixedResult = broadcast<V>(0);
1752 auto mixedMask = broadcast<M>(false);
1753 for(std::size_t l = 0; l < lanes(mixedMask); ++l)
1754 {
1755 lane(l, mixedMask ) = (l % 2);
1756 lane(l, mixedResult) = lane(l, (l % 2) ? vec1 : vec2);
1757 }
1758
1759 DUNE_SIMD_CHECK(allTrue(cond(mixedMask, vec1, vec2) == mixedResult));
1760 }
1761
1762 template<class V>
1763 void checkBoolCond()
1764 {
1765 static_assert
1767 std::declval<V>())), V>::value,
1768 "The result of cond(bool, V, V) should have exactly the type V");
1769
1770 static_assert
1772 std::declval<const V&>(),
1773 std::declval<const V&>())), V>::value,
1774 "The result of cond(const bool&, const V&, const V&) should have "
1775 "exactly the type V");
1776
1777 static_assert
1779 std::declval<V&>(),
1780 std::declval<V&>())), V>::value,
1781 "The result of cond(bool&, V&, V&) should have exactly the type V");
1782
1783 V vec1 = leftVector<V>();
1784 V vec2 = rightVector<V>();
1785
1786 DUNE_SIMD_CHECK(allTrue(cond(true, vec1, vec2) == vec1));
1787 DUNE_SIMD_CHECK(allTrue(cond(false, vec1, vec2) == vec2));
1788 }
1789
1790 template<class V>
1792 checkHorizontalMinMax() {}
1793
1794 template<class V>
1796 checkHorizontalMinMax()
1797 {
1798 static_assert
1799 (std::is_same<decltype(max(std::declval<V>())), Scalar<V> >::value,
1800 "The result of max(V) should be exactly Scalar<V>");
1801
1802 static_assert
1803 (std::is_same<decltype(min(std::declval<V>())), Scalar<V> >::value,
1804 "The result of min(V) should be exactly Scalar<V>");
1805
1806 static_assert
1807 (std::is_same<decltype(max(std::declval<V&>())), Scalar<V> >::value,
1808 "The result of max(V) should be exactly Scalar<V>");
1809
1810 static_assert
1811 (std::is_same<decltype(min(std::declval<V&>())), Scalar<V> >::value,
1812 "The result of min(V) should be exactly Scalar<V>");
1813
1814 const V vec1 = leftVector<V>();
1815
1816 DUNE_SIMD_CHECK(max(vec1) == Scalar<V>(lanes(vec1)));
1817 DUNE_SIMD_CHECK(min(vec1) == Scalar<V>(1));
1818 }
1819
1820 template<class V>
1822 checkBinaryMinMax() {}
1823
1824 template<class V>
1826 checkBinaryMinMax()
1827 {
1828 using std::max;
1829 using std::min;
1830
1831 static_assert
1833 std::declval<V>())), V>::value,
1834 "The result of Simd::max(V, V) should be exactly V");
1835 static_assert
1837 std::declval<V>())), V>::value,
1838 "The result of Simd::min(V, V) should be exactly V");
1839
1840 static_assert
1842 std::declval<V&>())), V>::value,
1843 "The result of Simd::max(V&, V&) should be exactly V");
1844 static_assert
1846 std::declval<V&>())), V>::value,
1847 "The result of Simd::min(V&, V&) should be exactly V");
1848
1849 const V arg1 = leftVector<V>();
1850 const V arg2 = rightVector<V>();
1851
1852 V maxExp(Scalar<V>(0)), minExp(Scalar<V>(0));
1853 for(auto l : range(lanes<V>()))
1854 {
1855 lane(l, maxExp) = max(lane(l, arg1), lane(l, arg2));
1856 lane(l, minExp) = min(lane(l, arg1), lane(l, arg2));
1857 }
1858
1859 DUNE_SIMD_CHECK(allTrue(maxExp == Simd::max(arg1, arg2)));
1860 DUNE_SIMD_CHECK(allTrue(minExp == Simd::min(arg1, arg2)));
1861
1862 // generic code also uses `using std::max; max(v1, v2)` for vectors,
1863 // so the implementation must provide overloads found by ADL
1864 if constexpr (!std::is_same<Scalar<V>, bool>::value)
1865 {
1866 DUNE_SIMD_CHECK(allTrue(maxExp == V(max(arg1, arg2))));
1867 DUNE_SIMD_CHECK(allTrue(minExp == V(min(arg1, arg2))));
1868 }
1869 }
1870
1871 template<class V>
1872 void checkIO()
1873 {
1874 const V vec1 = leftVector<V>();
1875
1876 std::string reference;
1877 {
1878 const char *sep = "";
1879 for(auto l : range(lanes(vec1)))
1880 {
1881 std::ostringstream stream;
1882 stream << lane(l, vec1);
1883
1884 reference += sep;
1885 reference += stream.str();
1886 sep = ", ";
1887 }
1888 }
1889
1890 {
1891 std::ostringstream stream;
1892 stream << io(vec1);
1893 if(lanes(vec1) == 1)
1894 DUNE_SIMD_CHECK(stream.str() == reference);
1895 else
1896 DUNE_SIMD_CHECK(stream.str() == "<" + reference + ">");
1897 }
1898
1899 {
1900 std::ostringstream stream;
1901 stream << vio(vec1);
1902 DUNE_SIMD_CHECK(stream.str() == "<" + reference + ">");
1903 }
1904 }
1905
1906#undef DUNE_SIMD_CHECK
1907
1908 public:
1973 template<class V> void checkType();
1974 template<class V> void checkNonOps();
1975 template<class V> void checkUnaryOps();
1976 template<class V> void checkBinaryOps();
1977 template<class V> void checkBinaryOpsVectorVector();
1978 template<class V> void checkBinaryOpsScalarVector();
1979 template<class V> void checkBinaryOpsVectorScalar();
1980 template<class V> void checkBinaryOpsProxyVector();
1981 template<class V> void checkBinaryOpsVectorProxy();
1982 template<class V> void checkBinaryOpsMixedProxyVector();
1983 template<class V> void checkBinaryOpsMixedVectorProxy();
1987
2004 template<class V, class Rebinds,
2005 template<class> class RebindPrune = IsLoop,
2006 template<class> class RebindAccept = Dune::AlwaysTrue>
2007 void check() {
2008 // check whether the test for this type already started
2009 if(seen_.emplace(typeid (V)).second == false)
2010 {
2011 // type already seen, nothing to do
2012 return;
2013 }
2014
2015 // do these first so everything that appears after "Checking SIMD type
2016 // ..." really pertains to that type
2017 auto recurse = [this](auto w) {
2018 using W = typename decltype(w)::type;
2019 this->template check<W, Rebinds, RebindPrune, RebindAccept>();
2020 };
2021 checkRebindOf<V, Rebinds, RebindPrune, RebindAccept>(recurse);
2022
2023 checkType<V>();
2024 }
2025
2027 bool good() const
2028 {
2029 return good_;
2030 }
2031
2032 }; // class UnitTest
2033
2034 template<class V> void UnitTest::checkType()
2035 {
2036 static_assert(std::is_same<V, std::decay_t<V> >::value, "Simd types "
2037 "must not be references, and must not include "
2038 "cv-qualifiers");
2039
2040 log_ << "Checking SIMD type " << className<V>() << std::endl;
2041
2042 checkNonOps<V>();
2043 checkUnaryOps<V>();
2044 checkBinaryOps<V>();
2045 }
2046 template<class V> void UnitTest::checkNonOps()
2047 {
2048 constexpr auto isMask = typename std::is_same<Scalar<V>, bool>::type{};
2049
2050 checkLanes<V>();
2051 checkScalar<V>();
2052
2053 checkDefaultConstruct<V>();
2054 checkLane<V>();
2055 checkCopyMoveConstruct<V>();
2056 checkImplCast<V>();
2057 checkBroadcast<V>();
2058 if constexpr (isMask)
2059 this->template checkBroadcastMaskConstruct<V>();
2060 else
2061 this->template checkBroadcastVectorConstruct<V>();
2062 checkBracedAssign<V>();
2063 checkBracedBroadcastAssign<V>();
2064
2065 checkAutoCopy<V>();
2066 checkCond<V>();
2067 checkBoolCond<V>();
2068
2069 if constexpr (isMask)
2070 this->template checkBoolReductions<V>();
2071 // checkBoolReductions() is not applicable for non-masks
2072
2073 checkHorizontalMinMax<V>();
2074 checkBinaryMinMax<V>();
2075 checkIO<V>();
2076 }
2077 template<class V> void UnitTest::checkUnaryOps()
2078 {
2079 if constexpr (std::is_same_v<Scalar<V>, bool>) {
2080 // check mask
2081 auto check = [this](auto op) {
2082 this->template checkUnaryOpsV<V>(op);
2083 };
2084
2085 // postfix
2086 // check(OpPostfixDecrement{});
2087 // clang deprecation warning if bool++ is tested
2088 // check(OpPostfixIncrement{});
2089
2090 // prefix
2091 // check(OpPrefixDecrement{});
2092 // clang deprecation warning if ++bool is tested
2093 // check(OpPrefixIncrement{});
2094
2095 // check(OpPrefixPlus{});
2096 // check(OpPrefixMinus{});
2097 check(OpPrefixLogicNot{});
2098 // check(OpPrefixBitNot{});
2099 }
2100 else {
2101 // check vector
2102 auto check = [this](auto op) {
2103 this->template checkUnaryOpsV<V>(op);
2104 };
2105
2106 // postfix
2107 // check(OpPostfixDecrement{});
2108 // check(OpPostfixIncrement{});
2109
2110 // prefix
2111 // check(OpPrefixDecrement{});
2112 // check(OpPrefixIncrement{});
2113
2114 // check(OpPrefixPlus{});
2115 check(OpPrefixMinus{});
2116 check(OpPrefixLogicNot{});
2117 check(OpPrefixBitNot{});
2118 }
2119 }
2120 template<class V> void UnitTest::checkBinaryOps()
2121 {
2122 checkBinaryOpsVectorVector<V>();
2123 checkBinaryOpsScalarVector<V>();
2124 checkBinaryOpsVectorScalar<V>();
2125 checkBinaryOpsProxyVector<V>();
2126 checkBinaryOpsVectorProxy<V>();
2127 checkBinaryOpsMixedProxyVector<V>();
2128 checkBinaryOpsMixedVectorProxy<V>();
2129 }
2131 {
2132 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
2133 auto check = [this,op](auto t1, auto t2) {
2134 this->checkBinaryOpVV(t1, t2, op);
2135 };
2136 this->checkBinaryRefQual<V, V, doVV>(check);
2137 };
2138 checkBinaryOps<V>(checker);
2139 }
2141 {
2142 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
2143 auto check = [this,op](auto t1, auto t2) {
2144 this->checkBinaryOpSV(t1, t2, op);
2145 };
2146 this->checkBinaryRefQual<Scalar<V>, V, doSV>(check);
2147
2148 auto crossCheck = [this,op](auto t1, auto t2) {
2149 this->checkBinaryOpVVAgainstSV(t1, t2, op);
2150 };
2151 this->checkBinaryRefQual<Scalar<V>, V, doSV && doVV>(crossCheck);
2152 };
2153 checkBinaryOps<V>(checker);
2154 }
2156 {
2157 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
2158 auto check = [this,op](auto t1, auto t2) {
2159 this->checkBinaryOpVS(t1, t2, op);
2160 };
2161 this->checkBinaryRefQual<V, Scalar<V>, doVS>(check);
2162
2163 auto crossCheck = [this,op](auto t1, auto t2) {
2164 this->checkBinaryOpVVAgainstVS(t1, t2, op);
2165 };
2166 this->checkBinaryRefQual<V, Scalar<V>, doVV && doVS>(crossCheck);
2167 };
2168 checkBinaryOps<V>(checker);
2169 }
2171 {
2172 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
2173 auto check = [this,op](auto t1, auto t2) {
2174 this->checkBinaryOpPV(t1, t2, op);
2175 };
2176 this->checkBinaryRefQual<V, V, doSV>(check);
2177 };
2178 checkBinaryOps<V>(checker);
2179 }
2181 {
2182 auto checker = [this](auto doSV, auto doVV, auto doVS, auto op) {
2183 auto check = [this,op](auto t1, auto t2) {
2184 this->checkBinaryOpVP(t1, t2, op);
2185 };
2186 this->checkBinaryRefQual<V, V, doVS>(check);
2187 };
2188 checkBinaryOps<V>(checker);
2189 }
2191 {
2192 forEachMixedPrecisionPartner<V>([this](auto w) {
2193 using W = typename decltype(w)::type;
2194 auto checker = [this, TL = TypeList<W&, const W&, W&&>{}]
2195 (auto doSV, auto doVV, auto doVS, auto op) {
2196 if constexpr (doSV)
2197 Hybrid::forEach(TL, [this,op](auto t2) {
2198 this->checkBinaryOpMixedPV(MetaType<V&>{}, t2, op);
2199 });
2200 };
2201 this->template checkBinaryOps<V>(checker);
2202 });
2203 }
2205 {
2206 forEachMixedPrecisionPartner<V>([this](auto w) {
2207 using W = typename decltype(w)::type;
2208 auto checker = [this, TL = TypeList<W&, const W&, W&&>{}]
2209 (auto doSV, auto doVV, auto doVS, auto op) {
2210 if constexpr (doVS)
2211 Hybrid::forEach(TL, [this,op](auto t1) {
2212 this->checkBinaryOpMixedVP(t1, MetaType<V&>{}, op);
2213 });
2214 };
2215 this->template checkBinaryOps<V>(checker);
2216 });
2217 }
2218
2219 } // namespace Simd
2220} // namespace Dune
2221
2222#endif // DUNE_COMMON_SIMD_TEST_HH
A free function to provide the demangled class name of a given object or type as a string.
Utilities for reduction like operations on ranges.
IO interface of the SIMD abstraction.
Include file for users of the SIMD abstraction layer.
#define DUNE_SIMD_POSTFIX_OP(NAME, SYMBOL)
Definition test.hh:676
#define DUNE_SIMD_INFIX_OP(NAME, SYMBOL)
Definition test.hh:790
#define DUNE_SIMD_CHECK_OP(expr)
Definition test.hh:197
#define DUNE_SIMD_CHECK(expr)
Definition test.hh:192
#define DUNE_SIMD_REPL_OP(NAME, REPLFN, SYMBOL)
Definition test.hh:824
#define DUNE_SIMD_ASSIGN_OP(NAME, SYMBOL)
Definition test.hh:811
#define DUNE_SIMD_PREFIX_OP(NAME, SYMBOL)
Definition test.hh:687
#define DUNE_SIMD_DO(M1, M2, M3, V1, V2, V3, NAME)
Traits for type conversions and type information.
static constexpr IntegralRange< T > range(T from, T to) noexcept
free standing function for setting up a range based for loop over an integer range for (auto i: range...
Definition rangeutilities.hh:291
std::tuple< MetaType< T >... > TypeList
A simple type list.
Definition typelist.hh:87
constexpr void forEach(Range &&range, F &&f)
Range based for loop.
Definition hybridutilities.hh:261
constexpr AutonomousValue< T > autoCopy(T &&v)
Autonomous copy of an expression's value for use in auto type deduction.
Definition typetraits.hh:657
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
bool anyTrue(const Mask &mask)
Whether any entry is true
Definition simd/interface.hh:442
auto maskOr(const V1 &v1, const V2 &v2)
Logic or of masks.
Definition simd/interface.hh:512
V cond(M &&mask, const V &ifTrue, const V &ifFalse)
Like the ?: operator.
Definition simd/interface.hh:393
auto io(const V &v)
construct a stream inserter
Definition io.hh:106
bool allTrue(const Mask &mask)
Whether all entries are true
Definition simd/interface.hh:452
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 simd/interface.hh:253
auto max(const V &v1, const V &v2)
The binary maximum value over two simd objects.
Definition simd/interface.hh:422
bool anyFalse(const Mask &mask)
Whether any entry is false
Definition simd/interface.hh:462
constexpr std::size_t lanes()
Number of lanes in a SIMD type.
Definition simd/interface.hh:305
decltype(auto) lane(std::size_t l, V &&v)
Extract an element of a SIMD type.
Definition simd/interface.hh:331
bool allFalse(const Mask &mask)
Whether all entries are false
Definition simd/interface.hh:472
auto maskAnd(const V1 &v1, const V2 &v2)
Logic and of masks.
Definition simd/interface.hh:522
typename Overloads::ScalarType< std::decay_t< V > >::type Scalar
Element type of some SIMD type.
Definition simd/interface.hh:235
auto min(const V &v1, const V &v2)
The binary minimum value over two simd objects.
Definition simd/interface.hh:432
STL namespace.
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
typename Impl::RemoveEnd< EndMark, TypeList< Types... > >::type RebindList
A list of types with the final element removed.
Definition test.hh:164
typename Impl::IsLoop< T >::type IsLoop
check whether a type is an instance of LoopSIMD
Definition test.hh:168
Definition loop.hh:65
final element marker for RebindList
Definition test.hh:151
Definition test.hh:170
void checkUnaryOps()
Definition test.hh:2077
void checkBinaryOps()
Definition test.hh:2120
void checkBinaryOpsScalarVector()
Definition test.hh:2140
void checkType()
Definition test.hh:2034
void checkBinaryOpsMixedVectorProxy()
Definition test.hh:2204
void check()
run unit tests for simd vector type V
Definition test.hh:2007
void checkBinaryOpsVectorScalar()
Definition test.hh:2155
bool good() const
whether all tests succeeded
Definition test.hh:2027
void checkBinaryOpsVectorVector()
Definition test.hh:2130
void checkNonOps()
Definition test.hh:2046
void checkBinaryOpsVectorProxy()
Definition test.hh:2180
void checkBinaryOpsProxyVector()
Definition test.hh:2170
void checkBinaryOpsMixedProxyVector()
Definition test.hh:2190
A type that refers to another type.
Definition typelist.hh:33
template which always yields a true value
Definition typetraits.hh:134
T declval(T... args)
T emplace(T... args)
T endl(T... args)
T max(T... args)
T min(T... args)
T ref(T... args)
T str(T... args)