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>
17#include <dune/common/simd/defaults.hh> // for anyFalse()
18#include <dune/common/simd/loop.hh>
20#include <dune/common/vc.hh>
21
152namespace 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 {
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
Mask< V > mask(ADLTag< 0, std::is_same< V, Mask< V > >::value >, const V &v)
implements Simd::mask()
Definition: defaults.hh:153
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
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
STL namespace.
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.111.3 (Dec 22, 23:30, 2024)