Dune Core Modules (unstable)

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