Dune Core Modules (2.8.0)

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