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