Dune Core Modules (unstable)

vc.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_VC_HH
4 #define DUNE_COMMON_SIMD_VC_HH
5 
11 #include <cstddef>
12 #include <type_traits>
13 #include <utility>
14 
15 #include <dune/common/indices.hh>
16 #include <dune/common/simd/base.hh>
17 #include <dune/common/simd/defaults.hh> // for anyFalse()
18 #include <dune/common/simd/loop.hh>
20 #include <dune/common/vc.hh>
21 
152 namespace Dune {
153  namespace Simd {
154 
155  namespace VcImpl {
156 
158  template<class V, class SFINAE = void>
159  struct IsMask : std::false_type {};
160 
161  template<typename T, typename A>
162  struct IsMask<Vc::Mask<T, A> > : std::true_type {};
163 
164  template<typename T, std::size_t n, typename V, std::size_t m>
165  struct IsMask<Vc::SimdMaskArray<T, n, V, m> > : std::true_type {};
166 
168  template<class V, class SFINAE = void>
169  struct IsVector : IsMask<V> {};
170 
171  template<typename T, typename A>
172  struct IsVector<Vc::Vector<T, A> > : std::true_type {};
173 
174  template<typename T, std::size_t n, typename V, std::size_t m>
175  struct IsVector<Vc::SimdArray<T, n, V, m> > : std::true_type {};
176 
177  template<typename T> struct IsVectorizable : std::false_type {};
178  template<> struct IsVectorizable<double> : std::true_type {};
179  template<> struct IsVectorizable<float> : std::true_type {};
180  template<> struct IsVectorizable<std::int32_t> : std::true_type {};
181  template<> struct IsVectorizable<std::uint32_t> : std::true_type {};
182  template<> struct IsVectorizable<std::int16_t> : std::true_type {};
183  template<> struct IsVectorizable<std::uint16_t> : std::true_type {};
184 
186 
198  template<class V>
199  class Proxy
200  {
201  static_assert(std::is_same<V, std::decay_t<V> >::value, "Class Proxy "
202  "may only be instantiated with unqualified types");
203  public:
204  using value_type = typename V::value_type;
205 
206  private:
207  static_assert(std::is_arithmetic<value_type>::value,
208  "Only arithmetic types are supported");
209  V &vec_;
210  std::size_t idx_;
211 
212  public:
213  Proxy(std::size_t idx, V &vec)
214  : vec_(vec), idx_(idx)
215  { }
216 
217  Proxy(const Proxy&) = delete;
218  // allow move construction so we can return proxies from functions
219  Proxy(Proxy&&) = default;
220 
221  operator value_type() const { return vec_[idx_]; }
222 
223  // assignment operators
224 #define DUNE_SIMD_VC_ASSIGNMENT(OP) \
225  template<class T, \
226  class = decltype(std::declval<value_type&>() OP \
227  autoCopy(std::declval<T>()) )> \
228  Proxy operator OP(T &&o) && \
229  { \
230  vec_[idx_] OP autoCopy(std::forward<T>(o)); \
231  return { idx_, vec_ }; \
232  }
233  DUNE_SIMD_VC_ASSIGNMENT(=);
234  DUNE_SIMD_VC_ASSIGNMENT(*=);
235  DUNE_SIMD_VC_ASSIGNMENT(/=);
236  DUNE_SIMD_VC_ASSIGNMENT(%=);
237  DUNE_SIMD_VC_ASSIGNMENT(+=);
238  DUNE_SIMD_VC_ASSIGNMENT(-=);
239  DUNE_SIMD_VC_ASSIGNMENT(<<=);
240  DUNE_SIMD_VC_ASSIGNMENT(>>=);
241  DUNE_SIMD_VC_ASSIGNMENT(&=);
242  DUNE_SIMD_VC_ASSIGNMENT(^=);
243  DUNE_SIMD_VC_ASSIGNMENT(|=);
244 #undef DUNE_SIMD_VC_ASSIGNMENT
245 
246  // unary (prefix) operators
247  template<class T = value_type,
248  class = std::enable_if_t<!std::is_same<T, bool>::value> >
249  Proxy operator++() { ++(vec_[idx_]); return *this; }
250  template<class T = value_type,
251  class = std::enable_if_t<!std::is_same<T, bool>::value> >
252  Proxy operator--() { --(vec_[idx_]); return *this; }
253 
254  // postfix operators
255  template<class T = value_type,
256  class = std::enable_if_t<!std::is_same<T, bool>::value> >
257  value_type operator++(int) { return vec_[idx_]++; }
258  template<class T = value_type,
259  class = std::enable_if_t<!std::is_same<T, bool>::value> >
260  value_type operator--(int) { return vec_[idx_]--; }
261 
262 
263  // swap on proxies swaps the proxied vector entries. As such, it
264  // applies to rvalues of proxies too, not just lvalues
265  friend void swap(const Proxy &a, const Proxy &b) {
266  // don't use swap() ourselves -- not supported by Vc 1.3.0 (but is
267  // supported by Vc 1.3.2)
268  value_type tmp = std::move(a.vec_[a.idx_]);
269  a.vec_[a.idx_] = std::move(b.vec_[b.idx_]);
270  b.vec_[b.idx_] = std::move(tmp);
271  }
272  friend void swap(value_type &a, const Proxy &b) {
273  // don't use swap() ourselves -- not supported by Vc 1.3.0 (but is
274  // supported by Vc 1.3.2)
275  value_type tmp = std::move(a);
276  a = std::move(b.vec_[b.idx_]);
277  b.vec_[b.idx_] = std::move(tmp);
278  }
279  friend void swap(const Proxy &a, value_type &b) {
280  // don't use swap() ourselves -- not supported by Vc 1.3.0 (but is
281  // supported by Vc 1.3.2)
282  value_type tmp = std::move(a.vec_[a.idx_]);
283  a.vec_[a.idx_] = std::move(b);
284  b = std::move(tmp);
285  }
286 
287  // binary operators
288  //
289  // Normally, these are provided by the conversion operator in
290  // combination with C++'s builtin binary operators. Other classes
291  // that need to provide the binary operators themselves should either
292  // 1. deduce the "foreign" operand type independently, i.e. use
293  // template<class... Args, class Foreign>
294  // auto operator@(MyClass<Args...>, Foreign);
295  // or
296  // 2. not deduce anything from the foreign argument, i.e.
297  // template<class... Args>
298  // auto operator@(MyClass<Args...>,
299  // typename MyClass<Args...>::value_type);
300  // or
301  // template<class T, class... Args>
302  // struct MyClass {
303  // auto operator@(T);
304  // }
305  // or
306  // template<class T, class... Args>
307  // struct MyClass {
308  // friend auto operator@(MyClass, T);
309  // }
310  //
311  // This allows either for an exact match (in the case of option 1.) or
312  // for conversions to be applied to the foreign argument (options 2.).
313  // In contrast, allowing some of the template parameters being deduced
314  // from the self argument also being deduced from the foreign argument
315  // will likely lead to ambiguous deduction when the foreign argument is
316  // a proxy:
317  // template<class T, class... Args>
318  // auto operator@(MyClass<T, Args...>, T);
319  // One class that suffers from this problem is std::complex.
320  //
321  // Note that option 1. is a bit dangerous, as the foreign argument is
322  // catch-all. This seems tempting in the case of a proxy class, as
323  // the operator could just be forwarded to the proxied object with the
324  // foreign argument unchanged, immediately creating interoperability
325  // with arbitrary foreign classes. However, if the foreign class also
326  // choses option 1., this will result in ambiguous overloads, and there
327  // is no clear guide to decide which class should provide the overload
328  // and which should not.
329  //
330  // Fortunately, deferring to the conversion and the built-in operators
331  // mostly works in the case of this proxy class, because only built-in
332  // types can be proxied anyway. Unfortunately, the Vc vectors and
333  // arrays suffer from a slightly different problem. They chose option
334  // 1., but they can't just accept the argument type they are given,
335  // since they need to somehow implement the operation in terms of
336  // intrinsics. So they check the argument whether it is one of the
337  // expected types, and remove the operator from the overload set if it
338  // isn't via SFINAE. Of course, this proxy class is not one of the
339  // expected types, even though it would convert to them...
340  //
341  // So what we have to do here, unfortunately, is to provide operators
342  // for the Vc types explicitly, and hope that there won't be some Vc
343  // version that gets the operators right, thus creating ambiguous
344  // overloads. Well, if guess it will be #ifdef time if it comes to
345  // that.
346 #define DUNE_SIMD_VC_BINARY(OP) \
347  template<class T, class Abi> \
348  friend auto operator OP(const Vc::Vector<T, Abi> &l, Proxy&& r) \
349  -> decltype(l OP std::declval<value_type>()) \
350  { \
351  return l OP value_type(r); \
352  } \
353  template<class T, class Abi> \
354  auto operator OP(const Vc::Vector<T, Abi> &r) && \
355  -> decltype(std::declval<value_type>() OP r) \
356  { \
357  return value_type(*this) OP r; \
358  } \
359  template<class T, std::size_t n, class Vec, std::size_t m> \
360  friend auto \
361  operator OP(const Vc::SimdArray<T, n, Vec, m> &l, Proxy&& r) \
362  -> decltype(l OP std::declval<value_type>()) \
363  { \
364  return l OP value_type(r); \
365  } \
366  template<class T, std::size_t n, class Vec, std::size_t m> \
367  auto operator OP(const Vc::SimdArray<T, n, Vec, m> &r) && \
368  -> decltype(std::declval<value_type>() OP r) \
369  { \
370  return value_type(*this) OP r; \
371  }
372 
373  DUNE_SIMD_VC_BINARY(*);
374  DUNE_SIMD_VC_BINARY(/);
375  DUNE_SIMD_VC_BINARY(%);
376  DUNE_SIMD_VC_BINARY(+);
377  DUNE_SIMD_VC_BINARY(-);
378  DUNE_SIMD_VC_BINARY(<<);
379  DUNE_SIMD_VC_BINARY(>>);
380  DUNE_SIMD_VC_BINARY(&);
381  DUNE_SIMD_VC_BINARY(^);
382  DUNE_SIMD_VC_BINARY(|);
383  DUNE_SIMD_VC_BINARY(<);
384  DUNE_SIMD_VC_BINARY(>);
385  DUNE_SIMD_VC_BINARY(<=);
386  DUNE_SIMD_VC_BINARY(>=);
387  DUNE_SIMD_VC_BINARY(==);
388  DUNE_SIMD_VC_BINARY(!=);
389 #undef DUNE_SIMD_VC_BINARY
390 
391  // this is needed to implement broadcast construction from proxy as
392  // the unadorned assignment operator cannot be a non-member
393  template<class T, class Abi,
394  class = std::enable_if_t<std::is_convertible<value_type,
395  T>::value> >
396  operator Vc::Vector<T, Abi>() &&
397  {
398  return value_type(*this);
399  }
400  template<class T, std::size_t n, class Vec, std::size_t m,
401  class = std::enable_if_t<std::is_convertible<value_type,
402  T>::value> >
403  operator Vc::SimdArray<T, n, Vec, m>() &&
404  {
405  return value_type(*this);
406  }
407 
408 #define DUNE_SIMD_VC_ASSIGN(OP) \
409  template<class T, class Abi> \
410  friend auto operator OP(Vc::Vector<T, Abi> &l, Proxy&& r) \
411  -> decltype(l OP std::declval<value_type>()) \
412  { \
413  return l OP value_type(r); \
414  }
415 
416  DUNE_SIMD_VC_ASSIGN(*=);
417  DUNE_SIMD_VC_ASSIGN(/=);
418  DUNE_SIMD_VC_ASSIGN(%=);
419  DUNE_SIMD_VC_ASSIGN(+=);
420  DUNE_SIMD_VC_ASSIGN(-=);
421  DUNE_SIMD_VC_ASSIGN(&=);
422  DUNE_SIMD_VC_ASSIGN(^=);
423  DUNE_SIMD_VC_ASSIGN(|=);
424  // The shift assignment would not be needed for Vc::Vector since it
425  // has overloads for `int` rhs and the proxy can convert to that --
426  // except that there is also overloads for Vector, and because of the
427  // conversion operator needed to support unadorned assignments, the
428  // proxy can convert to that, too.
429  DUNE_SIMD_VC_ASSIGN(<<=);
430  DUNE_SIMD_VC_ASSIGN(>>=);
431 #undef DUNE_SIMD_VC_ASSIGN
432  };
433 
434  } // namespace VcImpl
435 
436  namespace Overloads {
437 
444 
447  template<class V>
448  struct ScalarType<V, std::enable_if_t<VcImpl::IsVector<V>::value> >
449  {
450  using type = typename V::value_type;
451  };
452 
454 
461  template<class V>
462  struct RebindType<Simd::Scalar<V>, V,
463  std::enable_if_t<VcImpl::IsVector<V>::value> >
464  {
465  using type = V;
466  };
467 
469 
475  template<class V>
476  struct RebindType<bool, V, std::enable_if_t<VcImpl::IsVector<V>::value &&
477  !VcImpl::IsMask<V>::value>>
478  {
479  using type = typename V::mask_type;
480  };
481 
483 
489  template<class M>
490  struct RebindType<Scalar<typename M::Vector>, M,
491  std::enable_if_t<VcImpl::IsMask<M>::value>>
492  {
493  using type = typename M::Vector;
494  };
495 
497 
503  template<class S, class M>
504  struct RebindType<S, M,
505  std::enable_if_t<
506  VcImpl::IsMask<M>::value &&
507  VcImpl::IsVectorizable<S>::value &&
508  !std::is_same<S, Scalar<typename M::Vector> >::value
509  > >
510  {
511  using type = Vc::SimdArray<S, Simd::lanes<M>()>;
512  };
513 
515 
521  template<class S, class V>
522  struct RebindType<S, V,
523  std::enable_if_t<VcImpl::IsVector<V>::value &&
524  !VcImpl::IsMask<V>::value &&
525  VcImpl::IsVectorizable<S>::value &&
526  !std::is_same<S, Scalar<V> >::value> >
527  {
528  using type = Vc::SimdArray<S, Simd::lanes<V>()>;
529  };
530 
532 
539  template<class S, class V>
540  struct RebindType<S, V,
541  std::enable_if_t<VcImpl::IsVector<V>::value &&
542  !VcImpl::IsVectorizable<S>::value &&
543  !std::is_same<S, bool>::value &&
544  !std::is_same<S, Scalar<V> >::value> >
545  {
546  using type = LoopSIMD<S, Simd::lanes<V>()>;
547  };
548 
550 
553  template<class V>
554  struct LaneCount<V, std::enable_if_t<VcImpl::IsVector<V>::value> >
555  : public index_constant<V::size()>
556  { };
557 
559  template<class V>
561  std::size_t l, V &v)
562  {
563  return { l, v };
564  }
565 
567  template<class V>
569  std::size_t l, const V &v)
570  {
571  return v[l];
572  }
573 
575  /*
576  * The hack with the SFINAE is necessary, because if I use just
577  * Scalar<V> as the return type, the compiler still errors out if V is
578  * an lvalue-reference T&. You'd think he'd notice that he can't
579  * instantiate this declaration for this template parameter, and would
580  * simply remove it from the overload set, but no...
581  */
582  template<class V,
583  class = std::enable_if_t<!std::is_reference<V>::value> >
585  std::size_t l, V &&v)
586  {
587  return std::forward<V>(v)[l];
588  }
589 
591  template<class V>
594  const Mask<V> &mask, const V &ifTrue, const V &ifFalse)
595  {
596  return Vc::iif(mask, ifTrue, ifFalse);
597  }
598 
600  /*
601  * Kludge because iif seems to be unimplemented for masks
602  */
603  template<class V>
605  const V &mask, const V &ifTrue, const V &ifFalse)
606  {
607  return (mask && ifTrue) || (!mask && ifFalse);
608  }
609 
611  template<class V>
614  const V &v1, const V &v2)
615  {
616  return Simd::cond(v1 < v2, v2, v1);
617  }
618 
620  template<class M>
622  const M &m1, const M &m2)
623  {
624  return m1 || m2;
625  }
626 
628  template<class V>
631  const V &v1, const V &v2)
632  {
633  return Simd::cond(v1 < v2, v1, v2);
634  }
635 
637  template<class M>
639  const M &m1, const M &m2)
640  {
641  return m1 && m2;
642  }
643 
645  template<class M>
646  bool anyTrue (ADLTag<5, VcImpl::IsMask<M>::value>, const M &mask)
647  {
648  return Vc::any_of(mask);
649  }
650 
652  template<class M>
654  {
655  return Vc::all_of(mask);
656  }
657 
658  // nothing like anyFalse() in Vc, so let defaults.hh handle it
659 
661  template<class M>
663  {
664  return Vc::none_of(mask);
665  }
666 
668  template<class V>
671  const V &v)
672  {
673  return v.max();
674  }
675 
677  template<class M>
679  {
680  return Vc::any_of(mask);
681  }
682 
684  template<class V>
687  const V &v)
688  {
689  return v.min();
690  }
691 
693  template<class M>
695  {
696  return !Vc::any_of(!mask);
697  }
698 
700  template<class S1, class V2>
701  auto maskAnd(ADLTag<5, std::is_same<Mask<S1>, bool>::value &&
703  const S1 &s1, const V2 &v2)
704  {
705  return Simd::Mask<V2>(Simd::mask(s1)) && Simd::mask(v2);
706  }
707 
709  template<class V1, class S2>
711  std::is_same<Mask<S2>, bool>::value>,
712  const V1 &v1, const S2 &s2)
713  {
714  return Simd::mask(v1) && Simd::Mask<V1>(Simd::mask(s2));
715  }
716 
718  template<class S1, class V2>
719  auto maskOr(ADLTag<5, std::is_same<Mask<S1>, bool>::value &&
721  const S1 &s1, const V2 &v2)
722  {
723  return Simd::Mask<V2>(Simd::mask(s1)) || Simd::mask(v2);
724  }
725 
727  template<class V1, class S2>
729  std::is_same<Mask<S2>, bool>::value>,
730  const V1 &v1, const S2 &s2)
731  {
732  return Simd::mask(v1) || Simd::Mask<V1>(Simd::mask(s2));
733  }
734 
736 
737  } // namespace Overloads
738 
739  } // namespace Simd
740 
741  /*
742  * Specialize IsNumber for Vc::SimdArray and Vc::Vector to be able to use
743  * it as a scalar in DenseMatrix etc.
744  */
745  template <typename T, std::size_t N, class V, size_t Wt>
746  struct IsNumber<Vc::SimdArray<T, N, V, Wt>>
747  : public std::integral_constant<bool, IsNumber<T>::value> {
748  };
749 
750  template <typename T, typename Abi>
751  struct IsNumber<Vc::Vector<T, Abi>>
752  : public std::integral_constant<bool, IsNumber<T>::value> {
753  };
754 
756  template<class V>
757  struct AutonomousValueType<Simd::VcImpl::Proxy<V> > :
758  AutonomousValueType<typename Simd::VcImpl::Proxy<V>::value_type> {};
759 
760 } // namespace Dune
761 
762 #endif // DUNE_COMMON_SIMD_VC_HH
Basic definitions for SIMD Implementations.
Definition: loop.hh:65
A reference-like proxy for elements of random-access vectors.
Definition: vc.hh:200
Default implementations for SIMD Implementations.
std::integral_constant< std::size_t, i > index_constant
An index constant with value i.
Definition: indices.hh:29
bool allFalse(ADLTag< 0 >, const Mask &mask)
implements Simd::allFalse()
Definition: defaults.hh:124
bool allTrue(ADLTag< 0 >, const Mask &mask)
implements Simd::allTrue()
Definition: defaults.hh:104
Mask< V > mask(ADLTag< 0, std::is_same< V, Mask< V > >::value >, const V &v)
implements Simd::mask()
Definition: defaults.hh:153
auto maskAnd(ADLTag< 0 >, const V1 &v1, const V2 &v2)
implements Simd::maskAnd()
Definition: defaults.hh:177
auto maskOr(ADLTag< 0 >, const V1 &v1, const V2 &v2)
implements Simd::maskOr()
Definition: defaults.hh:170
auto min(ADLTag< 0 >, const V &v1, const V &v2)
implements binary Simd::min()
Definition: defaults.hh:89
auto max(ADLTag< 0 >, const V &v1, const V &v2)
implements binary Simd::max()
Definition: defaults.hh:81
V cond(M &&mask, const V &ifTrue, const V &ifFalse)
Like the ?: operator.
Definition: interface.hh:386
auto mask(const V &v)
Convert to mask, analogue of bool(s) for scalars.
Definition: interface.hh:489
Rebind< bool, V > Mask
Mask type type of some SIMD type.
Definition: interface.hh:289
typename Overloads::ScalarType< std::decay_t< V > >::type Scalar
Element type of some SIMD type.
Definition: interface.hh:235
Dune namespace.
Definition: alignedallocator.hh:13
const T1 cond(bool b, const T1 &v1, const T2 &v2)
conditional evaluate
Definition: conditional.hh:28
Type free of internal references that T can be converted to.
Definition: typetraits.hh:531
Tag used to force late-binding lookup in Dune::Simd::Overloads.
Definition: base.hh:182
should be derived from a Dune::index_constant
Definition: standard.hh:74
should have a member type type
Definition: standard.hh:67
should have a member type type
Definition: standard.hh:60
specialized to true for Vc mask types
Definition: vc.hh:159
specialized to true for Vc vector and mask types
Definition: vc.hh:169
Traits for type conversions and type information.
Compatibility header for including <Vc/Vc>
Creative Commons License   |  Legal Statements / Impressum  |  Hosted by TU Dresden  |  generated with Hugo v0.80.0 (May 1, 22:29, 2024)