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