Nektar++
Loading...
Searching...
No Matches
sve.hpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: sve.hpp
4//
5// For more information, please see: http://www.nektar.info
6//
7// The MIT License
8//
9// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10// Department of Aeronautics, Imperial College London (UK), and Scientific
11// Computing and Imaging Institute, University of Utah (USA).
12//
13// Permission is hereby granted, free of charge, to any person obtaining a
14// copy of this software and associated documentation files (the "Software"),
15// to deal in the Software without restriction, including without limitation
16// the rights to use, copy, modify, merge, publish, distribute, sublicense,
17// and/or sell copies of the Software, and to permit persons to whom the
18// Software is furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included
21// in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29// DEALINGS IN THE SOFTWARE.
30//
31// Description: Vector type using Armv8 Scalable Vector Extension (SVE).
32//
33///////////////////////////////////////////////////////////////////////////////
34
35#ifndef NEKTAR_LIB_LIBUTILITES_SIMDLIB_SVE_H
36#define NEKTAR_LIB_LIBUTILITES_SIMDLIB_SVE_H
37
38#if defined(__ARM_FEATURE_SVE)
39#include <arm_acle.h>
40#include <arm_sve.h>
41#endif
42
43#include "allocator.hpp"
44#include "traits.hpp"
45#include <vector>
46
47namespace tinysimd::abi
48{
49template <typename scalarType, int width = 0> struct sve
50{
51 using type = void;
52};
53
54} // namespace tinysimd::abi
55
56// requires clang >= 12.0.0 or gcc >= 10
57// requires -msve-vector-bits=<length>
58#if __ARM_FEATURE_SVE_BITS > 0 && defined(NEKTAR_ENABLE_SIMD_SVE)
59
60namespace tinysimd
61{
62
63// from VLA to VLST
64// C++ does not allow for incomplete class member types
65// to get around that we force a known size at compile time
66typedef svfloat64_t svfloat64_vlst_t
67 __attribute__((arm_sve_vector_bits(__ARM_FEATURE_SVE_BITS)));
68typedef svint64_t svint64_vlst_t
69 __attribute__((arm_sve_vector_bits(__ARM_FEATURE_SVE_BITS)));
70typedef svuint64_t svuint64_vlst_t
71 __attribute__((arm_sve_vector_bits(__ARM_FEATURE_SVE_BITS)));
72typedef svfloat32_t svfloat32_vlst_t
73 __attribute__((arm_sve_vector_bits(__ARM_FEATURE_SVE_BITS)));
74typedef svint32_t svint32_vlst_t
75 __attribute__((arm_sve_vector_bits(__ARM_FEATURE_SVE_BITS)));
76typedef svuint32_t svuint32_vlst_t
77 __attribute__((arm_sve_vector_bits(__ARM_FEATURE_SVE_BITS)));
78typedef svbool_t svbool_vlst_t
79 __attribute__((arm_sve_vector_bits(__ARM_FEATURE_SVE_BITS)));
80
81// forward declaration of concrete types
82template <typename T> struct sveInt64;
83template <typename T> struct sveInt32;
84struct sveFloat64;
85struct sveFloat32;
86struct sveMask64;
87struct sveMask32;
88
89namespace abi
90{
91
92// mapping between abstract types and concrete floating point types
93template <> struct sve<double>
94{
95 using type = sveFloat64;
96};
97template <> struct sve<float>
98{
99 using type = sveFloat32;
100};
101// generic index mapping
102// assumes index type width same as floating point type
103template <> struct sve<std::int64_t>
104{
105 using type = sveInt64<std::int64_t>;
106};
107template <> struct sve<std::uint64_t>
108{
109 using type = sveInt64<std::uint64_t>;
110};
111template <> struct sve<std::int32_t>
112{
113 using type = sveInt32<std::int32_t>;
114};
115template <> struct sve<std::uint32_t>
116{
117 using type = sveInt32<std::uint32_t>;
118};
119// specialized index mapping
120template <> struct sve<std::int64_t, __ARM_FEATURE_SVE_BITS / 64>
121{
122 using type = sveInt64<std::int64_t>;
123};
124template <> struct sve<std::uint64_t, __ARM_FEATURE_SVE_BITS / 64>
125{
126 using type = sveInt64<std::uint64_t>;
127};
128// the number of lanes dictate the simd type
129// then we need to make sure we can load properly
130// a 32 bit pointer to a 64 bit vector (zero or sign extend)
131template <> struct sve<std::int32_t, __ARM_FEATURE_SVE_BITS / 64>
132{
133 using type = sveInt64<std::int64_t>;
134};
135template <> struct sve<std::uint32_t, __ARM_FEATURE_SVE_BITS / 64>
136{
137 using type = sveInt64<std::uint64_t>;
138};
139template <> struct sve<std::int32_t, __ARM_FEATURE_SVE_BITS / 32>
140{
141 using type = sveInt32<std::int32_t>;
142};
143template <> struct sve<std::uint32_t, __ARM_FEATURE_SVE_BITS / 32>
144{
145 using type = sveInt32<std::uint32_t>;
146};
147// bool mapping
148template <> struct sve<bool, __ARM_FEATURE_SVE_BITS / 64>
149{
150 using type = sveMask64;
151};
152template <> struct sve<bool, __ARM_FEATURE_SVE_BITS / 32>
153{
154 using type = sveMask32;
155};
156
157} // namespace abi
158
159// concrete types, could add enable if to allow only unsigned long and long...
160template <typename T> struct sveInt32
161{
162 static_assert(std::is_integral_v<T> && sizeof(T) == 4,
163 "4 bytes Integral required.");
164
165 static constexpr unsigned int alignment =
166 __ARM_FEATURE_SVE_BITS / sizeof(T);
167 static constexpr unsigned int width = alignment / 8;
168
169 using scalarType = T;
170 using vectorType =
171 typename std::conditional<std::is_signed_v<T>, svint32_vlst_t,
172 svuint32_vlst_t>::type;
173 using scalarArray = scalarType[width];
174
175 // storage
176 vectorType _data;
177
178 // ctors
179 inline sveInt32() = default;
180 inline sveInt32(const sveInt32 &rhs) = default;
181 inline sveInt32(const vectorType &rhs) : _data(rhs)
182 {
183 }
184 inline sveInt32(const scalarType rhs)
185 {
186 _data = svdup_s32(rhs);
187 }
188 explicit inline sveInt32(scalarArray &rhs)
189 {
190 _data = svld1(svptrue_b32(), rhs);
191 }
192
193 // store packed
194 inline void store(scalarType *p) const
195 {
196 svst1(svptrue_b32(), p, _data);
197 }
198 // refer to x86_64 implementations
199 // sve has no requirements on alignment
200 // nevertheless we should accept valid tags for compatibility
201 template <typename TAG,
202 typename std::enable_if<is_load_tag_v<TAG>, bool>::type = 0>
203 inline void store(scalarType *p, TAG) const
204 {
205 svst1(svptrue_b32(), p, _data);
206 }
207
208 // load packed
209 inline void load(const scalarType *p)
210 {
211 _data = svld1(svptrue_b32(), p);
212 }
213 // refer to x86_64 implementations
214 // sve has no requirements on alignment
215 // nevertheless we should accept valid tags for compatibility
216 template <typename TAG,
217 typename std::enable_if<is_load_tag_v<TAG>, bool>::type = 0>
218 inline void load(const scalarType *p, TAG)
219 {
220 _data = svld1(svptrue_b32(), p);
221 }
222
223 // broadcast
224 inline void broadcast(const scalarType rhs)
225 {
226 _data = svdup(rhs);
227 }
228
229 // subscript
230 // subscript operators are convienient but expensive
231 // should not be used in optimized kernels
232 inline scalarType operator[](size_t i) const
233 {
234 alignas(alignment) scalarArray tmp;
235 store(tmp, is_aligned);
236 return tmp[i];
237 }
238
239 inline scalarType &operator[](size_t i)
240 {
241 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
242 return tmp[i];
243 }
244
245 // unary ops
246 inline void operator+=(sveInt32 rhs)
247 {
248 if constexpr (std::is_signed_v<T>)
249 {
250 _data = svadd_s32_x(svptrue_b32(), _data, rhs._data);
251 }
252 else
253 {
254 _data = svadd_u32_x(svptrue_b32(), _data, rhs._data);
255 }
256 }
257
258 inline void operator-=(sveInt32 rhs)
259 {
260 _data = svsub_x(svptrue_b32(), _data, rhs._data);
261 }
262
263 inline void operator*=(sveInt32 rhs)
264 {
265 _data = svmul_x(svptrue_b32(), _data, rhs._data);
266 }
267
268 inline void operator/=(sveInt32 rhs)
269 {
270 _data = svdiv_x(svptrue_b32(), _data, rhs._data);
271 }
272};
273
274template <typename T>
275inline sveInt32<T> operator+(sveInt32<T> lhs, sveInt32<T> rhs)
276{
277 if constexpr (std::is_signed_v<T>)
278 {
279 return svadd_s32_x(svptrue_b32(), lhs._data, rhs._data);
280 }
281 else
282 {
283 return svadd_u32_x(svptrue_b32(), lhs._data, rhs._data);
284 }
285}
286
287template <typename T> inline sveInt32<T> operator+(sveInt32<T> lhs, T rhs)
288{
289 if constexpr (std::is_signed_v<T>)
290 {
291 return svadd_s32_x(svptrue_b32(), lhs._data, sveInt32<T>(rhs)._data);
292 }
293 else
294 {
295 return svadd_u32_x(svptrue_b32(), lhs._data, sveInt32<T>(rhs)._data);
296 }
297}
298
299template <typename T>
300inline sveInt32<T> operator-(sveInt32<T> lhs, sveInt32<T> rhs)
301{
302 return svsub_x(svptrue_b32(), lhs._data, rhs._data);
303}
304
305template <typename T>
306inline sveInt32<T> operator*(sveInt32<T> lhs, sveInt32<T> rhs)
307{
308 return svmul_x(svptrue_b32(), lhs._data, rhs._data);
309}
310
311template <typename T>
312inline sveInt32<T> operator/(sveInt32<T> lhs, sveInt32<T> rhs)
313{
314 return svdiv_x(svptrue_b32(), lhs._data, rhs._data);
315}
316
317template <typename T> inline sveInt32<T> abs(sveInt32<T> in)
318{
319 return svabs_x(svptrue_b32(), in._data);
320}
321
322////////////////////////////////////////////////////////////////////////////////
323
324template <typename T> struct sveInt64
325{
326 static_assert(std::is_integral_v<T> && sizeof(T) == 8,
327 "8 bytes Integral required.");
328
329 static constexpr unsigned int alignment =
330 __ARM_FEATURE_SVE_BITS / sizeof(T);
331 static constexpr unsigned int width = alignment / 8;
332
333 using scalarType = T;
334 using vectorType =
335 typename std::conditional<std::is_signed_v<T>, svint64_vlst_t,
336 svuint64_vlst_t>::type;
337 using scalarArray = scalarType[width];
338
339 // storage
340 vectorType _data;
341
342 // ctors
343 inline sveInt64() = default;
344 inline sveInt64(const sveInt64 &rhs) = default;
345 inline sveInt64(const vectorType &rhs) : _data(rhs)
346 {
347 }
348 inline sveInt64(const scalarType rhs)
349 {
350 _data = svdup_s64(rhs);
351 }
352 explicit inline sveInt64(scalarArray &rhs)
353 {
354 _data = svld1(svptrue_b64(), rhs);
355 }
356
357 // store packed
358 inline void store(scalarType *p) const
359 {
360 svst1(svptrue_b64(), p, _data);
361 }
362 // refer to x86_64 implementations
363 // sve has no requirements on alignment
364 // nevertheless we should accept valid tags for compatibility
365 template <typename TAG,
366 typename std::enable_if<is_load_tag_v<TAG>, bool>::type = 0>
367 inline void store(scalarType *p, TAG) const
368 {
369 svst1(svptrue_b64(), p, _data);
370 }
371
372 // load packed
373 inline void load(const scalarType *p)
374 {
375 _data = svld1(svptrue_b64(), p);
376 }
377 // refer to x86_64 implementations
378 // sve has no requirements on alignment
379 // nevertheless we should accept valid tags for compatibility
380 template <typename TAG,
381 typename std::enable_if<is_load_tag_v<TAG>, bool>::type = 0>
382 inline void load(const scalarType *p, TAG)
383 {
384 _data = svld1(svptrue_b64(), p);
385 }
386
387 // load packed from 32 bit
388 template <typename I32,
389 typename std::enable_if<std::is_integral_v<I32> &&
390 std::is_signed_v<scalarType> &&
391 sizeof(I32) == 4,
392 bool>::type = 0>
393 inline void load(const I32 *p)
394 {
395 _data = svld1sw_s64(svptrue_b64(), p);
396 }
397 template <typename I32,
398 typename std::enable_if<std::is_integral_v<I32> &&
399 !std::is_signed_v<scalarType> &&
400 sizeof(I32) == 4,
401 bool>::type = 0>
402 inline void load(const I32 *p)
403 {
404 _data = svld1uw_s64(svptrue_b64(), p);
405 }
406 template <typename I32, typename TAG,
407 typename std::enable_if<
408 is_load_tag_v<TAG> && std::is_integral_v<I32> &&
409 std::is_signed_v<scalarType> && sizeof(I32) == 4,
410 bool>::type = 0>
411 inline void load(const I32 *p, TAG)
412 {
413 _data = svld1sw_s64(svptrue_b64(), p);
414 }
415 template <typename I32, typename TAG,
416 typename std::enable_if<
417 is_load_tag_v<TAG> && std::is_integral_v<I32> &&
418 !std::is_signed_v<scalarType> && sizeof(I32) == 4,
419 bool>::type = 0>
420 inline void load(const I32 *p, TAG)
421 {
422 _data = svld1uw_s64(svptrue_b64(), p);
423 }
424
425 // broadcast
426 inline void broadcast(const scalarType rhs)
427 {
428 _data = svdup(rhs);
429 }
430
431 // subscript
432 // subscript operators are convienient but expensive
433 // should not be used in optimized kernels
434 inline scalarType operator[](size_t i) const
435 {
436 alignas(alignment) scalarArray tmp;
437 store(tmp, is_aligned);
438 return tmp[i];
439 }
440
441 // unary ops
442 inline void operator+=(sveInt64 rhs)
443 {
444 if constexpr (std::is_signed_v<T>)
445 {
446 _data = svadd_s64_x(svptrue_b64(), _data, rhs._data);
447 }
448 else
449 {
450 _data = svadd_u64_x(svptrue_b64(), _data, rhs._data);
451 }
452 }
453
454 inline void operator-=(sveInt64 rhs)
455 {
456 if constexpr (std::is_signed_v<T>)
457 {
458 _data = svsub_s64_x(svptrue_b64(), _data, rhs._data);
459 }
460 else
461 {
462 _data = svsub_u64_x(svptrue_b64(), _data, rhs._data);
463 }
464 }
465
466 inline void operator*=(sveInt64 rhs)
467 {
468 if constexpr (std::is_signed_v<T>)
469 {
470 _data = svmul_s64_x(svptrue_b64(), _data, rhs._data);
471 }
472 else
473 {
474 _data = svmul_u64_x(svptrue_b64(), _data, rhs._data);
475 }
476 }
477
478 inline void operator/=(sveInt64 rhs)
479 {
480 if constexpr (std::is_signed_v<T>)
481 {
482 _data = svdiv_s64_x(svptrue_b64(), _data, rhs._data);
483 }
484 else
485 {
486 _data = svdiv_u64_x(svptrue_b64(), _data, rhs._data);
487 }
488 }
489};
490
491template <typename T>
492inline sveInt64<T> operator+(sveInt64<T> lhs, sveInt64<T> rhs)
493{
494 if constexpr (std::is_signed_v<T>)
495 {
496 return svadd_s64_x(svptrue_b64(), lhs._data, rhs._data);
497 }
498 else
499 {
500 return svadd_u64_x(svptrue_b64(), lhs._data, rhs._data);
501 }
502}
503
504template <typename T> inline sveInt64<T> operator+(sveInt64<T> lhs, T rhs)
505{
506 if constexpr (std::is_signed_v<T>)
507 {
508 return svadd_s64_x(svptrue_b64(), lhs._data, sveInt64<T>(rhs)._data);
509 }
510 else
511 {
512 return svadd_u64_x(svptrue_b64(), lhs._data, sveInt64<T>(rhs)._data);
513 }
514}
515
516template <typename T>
517inline sveInt64<T> operator-(sveInt64<T> lhs, sveInt64<T> rhs)
518{
519 if constexpr (std::is_signed_v<T>)
520 {
521 return svsub_s64_x(svptrue_b64(), lhs._data, rhs._data);
522 }
523 else
524 {
525 return svsub_u64_x(svptrue_b64(), lhs._data, rhs._data);
526 }
527}
528
529template <typename T>
530inline sveInt64<T> operator*(sveInt64<T> lhs, sveInt64<T> rhs)
531{
532 if constexpr (std::is_signed_v<T>)
533 {
534 return svmul_s64_x(svptrue_b64(), lhs._data, rhs._data);
535 }
536 else
537 {
538 return svmul_u64_x(svptrue_b64(), lhs._data, rhs._data);
539 }
540}
541
542template <typename T>
543inline sveInt64<T> operator/(sveInt64<T> lhs, sveInt64<T> rhs)
544{
545 if constexpr (std::is_signed_v<T>)
546 {
547 return svdiv_s64_x(svptrue_b64(), lhs._data, rhs._data);
548 }
549 else
550 {
551 return svdiv_u64_x(svptrue_b64(), lhs._data, rhs._data);
552 }
553}
554
555template <typename T> inline sveInt64<T> abs(sveInt64<T> in)
556{
557 if constexpr (std::is_signed_v<T>)
558 {
559 return svabs_s64_x(svptrue_b64(), in._data);
560 }
561 else
562 {
563 return svabs_u64_x(svptrue_b64(), in._data);
564 }
565}
566
567////////////////////////////////////////////////////////////////////////////////
568
569struct sveFloat32
570{
571 static constexpr unsigned int alignment =
572 __ARM_FEATURE_SVE_BITS / sizeof(float);
573 static constexpr unsigned int width = alignment / 8;
574
575 using scalarType = float;
576 using scalarIndexType = std::uint32_t;
577 using vectorType = svfloat32_vlst_t;
578 using scalarArray = scalarType[width];
579
580 // storage
581 vectorType _data;
582
583 // ctors
584 inline sveFloat32() = default;
585 inline sveFloat32(const sveFloat32 &rhs) = default;
586 inline sveFloat32(const vectorType &rhs) : _data(rhs)
587 {
588 }
589 inline sveFloat32(const scalarType rhs)
590 {
591 _data = svdup_f32(rhs);
592 }
593
594 // store packed
595 inline void store(scalarType *p) const
596 {
597 svst1_f32(svptrue_b32(), p, _data);
598 }
599 // refer to x86_64 implementations
600 // sve has no requirements on alignment
601 // nevertheless we should accept valid tags for compatibility
602 template <typename T,
603 typename std::enable_if<is_load_tag_v<T>, bool>::type = 0>
604 inline void store(scalarType *p, T) const
605 {
606 svst1_f32(svptrue_b32(), p, _data);
607 }
608
609 // load packed
610 inline void load(const scalarType *p)
611 {
612 _data = svld1_f32(svptrue_b32(), p);
613 }
614 // refer to x86_64 implementations
615 // sve has no requirements on alignment
616 // nevertheless we should accept valid tags for compatibility
617 template <typename T,
618 typename std::enable_if<is_load_tag_v<T>, bool>::type = 0>
619 inline void load(const scalarType *p, T)
620 {
621 _data = svld1_f32(svptrue_b32(), p);
622 }
623
624 // broadcast
625 inline void broadcast(const scalarType rhs)
626 {
627 _data = svdup_f32(rhs);
628 }
629
630 // gather/scatter
631 template <typename T>
632 inline void gather(scalarType const *p, const sveInt32<T> &indices)
633 {
634 if constexpr (std::is_signed_v<T>)
635 {
636 _data = svld1_gather_s32index_f32(svptrue_b32(), p, indices._data);
637 }
638 else
639 {
640 _data = svld1_gather_u32index_f32(svptrue_b32(), p, indices._data);
641 }
642 }
643
644 template <typename T>
645 inline void scatter(scalarType *out, const sveInt32<T> &indices) const
646 {
647 if constexpr (std::is_signed_v<T>)
648 {
649 svst1_scatter_s32index_f32(svptrue_b32(), out, indices._data,
650 _data);
651 }
652 else
653 {
654 svst1_scatter_u32index_f32(svptrue_b32(), out, indices._data,
655 _data);
656 }
657 }
658
659 // fma
660 // this = this + a * b
661 inline void fma(const sveFloat32 &a, const sveFloat32 &b)
662 {
663 _data = svmad_f32_x(svptrue_b32(), a._data, b._data, _data);
664 }
665
666 // subscript
667 // subscript operators are convienient but expensive
668 // should not be used in optimized kernels
669 inline scalarType operator[](size_t i) const
670 {
671 alignas(alignment) scalarArray tmp;
672 store(tmp, is_aligned);
673 return tmp[i];
674 }
675
676 inline scalarType &operator[](size_t i)
677 {
678 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
679 return tmp[i];
680 }
681
682 // unary ops
683 inline void operator+=(sveFloat32 rhs)
684 {
685 _data = svadd_f32_x(svptrue_b32(), _data, rhs._data);
686 }
687
688 inline void operator-=(sveFloat32 rhs)
689 {
690 _data = svsub_f32_x(svptrue_b32(), _data, rhs._data);
691 }
692
693 inline void operator*=(sveFloat32 rhs)
694 {
695 _data = svmul_f32_x(svptrue_b32(), _data, rhs._data);
696 }
697
698 inline void operator/=(sveFloat32 rhs)
699 {
700 _data = svdiv_f32_x(svptrue_b32(), _data, rhs._data);
701 }
702};
703
704inline sveFloat32 operator+(sveFloat32 lhs, sveFloat32 rhs)
705{
706 return svadd_f32_x(svptrue_b32(), lhs._data, rhs._data);
707}
708
709inline sveFloat32 operator-(sveFloat32 lhs, sveFloat32 rhs)
710{
711 return svsub_f32_x(svptrue_b32(), lhs._data, rhs._data);
712}
713
714inline sveFloat32 operator-(sveFloat32 in)
715{
716 return svsub_f32_x(svptrue_b32(), svdup_f32(-0.0), in._data);
717 // return sveor_f32_x(svptrue_b32(), in._data, svdup_f32(-0.0));
718}
719
720inline sveFloat32 operator*(sveFloat32 lhs, sveFloat32 rhs)
721{
722 return svmul_f32_x(svptrue_b32(), lhs._data, rhs._data);
723}
724
725inline sveFloat32 operator/(sveFloat32 lhs, sveFloat32 rhs)
726{
727 return svdiv_f32_x(svptrue_b32(), lhs._data, rhs._data);
728}
729
730inline sveFloat32 sqrt(sveFloat32 in)
731{
732 return svsqrt_f32_x(svptrue_b32(), in._data);
733}
734
735inline sveFloat32 abs(sveFloat32 in)
736{
737 return svabs_f32_x(svptrue_b32(), in._data);
738}
739
740inline sveFloat32 min(sveFloat32 lhs, sveFloat32 rhs)
741{
742 return svmin_f32_x(svptrue_b32(), lhs._data, rhs._data);
743}
744
745inline sveFloat32 max(sveFloat32 lhs, sveFloat32 rhs)
746{
747 return svmax_f32_x(svptrue_b32(), lhs._data, rhs._data);
748}
749
750inline sveFloat32 log(sveFloat32 in)
751{
752 // there is no sve log intrinsic
753 // this is a dreadful implementation and is simply a stop gap measure
754 alignas(sveFloat32::alignment) sveFloat32::scalarArray tmp;
755 in.store(tmp);
756 for (size_t i = 0; i < sveFloat32::width; ++i)
757 {
758 tmp[i] = std::log(tmp[i]);
759 }
760 sveFloat32 ret;
761 ret.load(tmp);
762 return ret;
763}
764
765inline void load_unalign_interleave(
766 const double *in, const std::uint32_t dataLen,
767 std::vector<sveFloat32, allocator<sveFloat32>> &out)
768{
769 alignas(sveFloat32::alignment) sveFloat32::scalarArray tmp;
770 for (size_t i = 0; i < dataLen; ++i)
771 {
772 for (size_t j = 0; j < sveFloat32::width; ++j)
773 {
774 tmp[j] = in[i + j * dataLen];
775 }
776 out[i].load(tmp);
777 }
778}
779
781 const double *in, const std::uint32_t dataLen, const std::uint32_t nPads,
782 std::vector<sveFloat32, allocator<sveFloat32>> &out)
783{
784 alignas(sveFloat32::alignment) sveFloat32::scalarArray tmp;
785 const size_t nData = sveFloat32::width - nPads;
786 for (size_t i = 0; i < dataLen; ++i)
787 {
788 for (size_t j = 0; j < nData; ++j)
789 {
790 tmp[j] = in[i + j * dataLen];
791 }
792 for (size_t j = nData; j < sveFloat32::width; ++j)
793 {
794 tmp[j] = 0.0;
795 }
796 out[i].load(tmp);
797 }
798}
799
800inline void load_interleave(const float *in, std::uint32_t dataLen,
801 std::vector<sveFloat32, allocator<sveFloat32>> &out)
802{
803
804 alignas(sveFloat32::alignment)
805 sveFloat32::scalarIndexType tmp[sveFloat32::width] = {};
806
807 // populate scalar index of unknown size
808 // (known at compile time)
809 for (size_t i = 0; i < sveFloat32::width; ++i)
810 {
811 tmp[i] = i * dataLen;
812 }
813
814 using index_t = sveInt32<sveFloat32::scalarIndexType>;
815 index_t index0(tmp);
816 index_t index1 = index0 + 1u;
817
818 // 2x unrolled loop -- minimun width is 2
819 size_t nBlocks = dataLen / 2;
820 for (size_t i = 0; i < nBlocks; ++i)
821 {
822 out[2 * i + 0].gather(in, index0);
823 out[2 * i + 1].gather(in, index1);
824 index0 = index0 + 2u;
825 index1 = index1 + 2u;
826 }
827
828 // spillover loop
829 for (size_t i = 2 * nBlocks; i < dataLen; ++i)
830 {
831 out[i].gather(in, index0);
832 index0 = index0 + 1u;
833 }
834}
835
837 const std::vector<sveFloat32, allocator<sveFloat32>> &in,
838 const std::uint32_t dataLen, double *out)
839{
840 alignas(sveFloat32::alignment) sveFloat32::scalarArray tmp;
841 for (size_t i = 0; i < dataLen; ++i)
842 {
843 in[i].store(tmp);
844 for (size_t j = 0; j < sveFloat32::width; ++j)
845 {
846 out[i + j * dataLen] = tmp[j];
847 }
848 }
849}
850
852 const std::vector<sveFloat32, allocator<sveFloat32>> &in,
853 const std::uint32_t dataLen, const std::uint32_t nPads, double *out)
854{
855 alignas(sveFloat32::alignment) sveFloat32::scalarArray tmp;
856 const size_t nData = sveFloat32::width - nPads;
857 for (size_t i = 0; i < dataLen; ++i)
858 {
859 in[i].store(tmp);
860 for (size_t j = 0; j < nData; ++j)
861 {
862 out[i + j * dataLen] = tmp[j];
863 }
864 }
865}
866
867inline void deinterleave_store(
868 const std::vector<sveFloat32, allocator<sveFloat32>> &in,
869 std::uint32_t dataLen, float *out)
870{
871 alignas(sveFloat32::alignment)
872 sveFloat32::scalarIndexType tmp[sveFloat32::width] = {};
873
874 // populate scalar index of unknown size
875 // (known at compile time)
876 for (size_t i = 0; i < sveFloat32::width; ++i)
877 {
878 tmp[i] = i * dataLen;
879 }
880
881 using index_t = sveInt32<sveFloat32::scalarIndexType>;
882 index_t index0(tmp);
883
884 for (size_t i = 0; i < dataLen; ++i)
885 {
886 in[i].scatter(out, index0);
887 index0 = index0 + 1u;
888 }
889}
890
891////////////////////////////////////////////////////////////////////////////////
892
893struct sveFloat64
894{
895 static constexpr unsigned int alignment =
896 __ARM_FEATURE_SVE_BITS / sizeof(double);
897 static constexpr unsigned int width = alignment / 8;
898
899 using scalarType = double;
900 using scalarIndexType = std::uint64_t;
901 using vectorType = svfloat64_vlst_t;
902 using scalarArray = scalarType[width];
903
904 // storage
905 vectorType _data;
906
907 // ctors
908 inline sveFloat64() = default;
909 inline sveFloat64(const sveFloat64 &rhs) = default;
910 inline sveFloat64(const vectorType &rhs) : _data(rhs)
911 {
912 }
913 inline sveFloat64(const scalarType rhs)
914 {
915 _data = svdup_f64(rhs);
916 }
917
918 // store packed
919 inline void store(scalarType *p) const
920 {
921 svst1_f64(svptrue_b64(), p, _data);
922 }
923 // refer to x86_64 implementations
924 // sve has no requirements on alignment
925 // nevertheless we should accept valid tags for compatibility
926 template <typename T,
927 typename std::enable_if<is_load_tag_v<T>, bool>::type = 0>
928 inline void store(scalarType *p, T) const
929 {
930 svst1_f64(svptrue_b64(), p, _data);
931 }
932
933 // load packed
934 inline void load(const scalarType *p)
935 {
936 _data = svld1_f64(svptrue_b64(), p);
937 }
938 // refer to x86_64 implementations
939 // sve has no requirements on alignment
940 // nevertheless we should accept valid tags for compatibility
941 template <typename T,
942 typename std::enable_if<is_load_tag_v<T>, bool>::type = 0>
943 inline void load(const scalarType *p, T)
944 {
945 _data = svld1_f64(svptrue_b64(), p);
946 }
947
948 // broadcast
949 inline void broadcast(const scalarType rhs)
950 {
951 _data = svdup_f64(rhs);
952 }
953
954 // gather/scatter
955 template <typename T>
956 inline void gather(scalarType const *p, const sveInt64<T> &indices)
957 {
958 if constexpr (std::is_signed_v<T>)
959 {
960 _data = svld1_gather_s64index_f64(svptrue_b64(), p, indices._data);
961 }
962 else
963 {
964 _data = svld1_gather_u64index_f64(svptrue_b64(), p, indices._data);
965 }
966 }
967
968 template <typename T>
969 inline void scatter(scalarType *out, const sveInt64<T> &indices) const
970 {
971 if constexpr (std::is_signed_v<T>)
972 {
973 svst1_scatter_s64index_f64(svptrue_b64(), out, indices._data,
974 _data);
975 }
976 else
977 {
978 svst1_scatter_u64index_f64(svptrue_b64(), out, indices._data,
979 _data);
980 }
981 }
982
983 // fma
984 // this = this + a * b
985 inline void fma(const sveFloat64 &a, const sveFloat64 &b)
986 {
987 _data = svmad_f64_x(svptrue_b64(), a._data, b._data, _data);
988 }
989
990 // subscript
991 // subscript operators are convienient but expensive
992 // should not be used in optimized kernels
993 inline scalarType operator[](size_t i) const
994 {
995 alignas(alignment) scalarArray tmp;
996 store(tmp, is_aligned);
997 return tmp[i];
998 }
999
1000 inline scalarType &operator[](size_t i)
1001 {
1002 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
1003 return tmp[i];
1004 }
1005
1006 // unary ops
1007 inline void operator+=(sveFloat64 rhs)
1008 {
1009 _data = svadd_f64_x(svptrue_b64(), _data, rhs._data);
1010 }
1011
1012 inline void operator-=(sveFloat64 rhs)
1013 {
1014 _data = svsub_f64_x(svptrue_b64(), _data, rhs._data);
1015 }
1016
1017 inline void operator*=(sveFloat64 rhs)
1018 {
1019 _data = svmul_f64_x(svptrue_b64(), _data, rhs._data);
1020 }
1021
1022 inline void operator/=(sveFloat64 rhs)
1023 {
1024 _data = svdiv_f64_x(svptrue_b64(), _data, rhs._data);
1025 }
1026};
1027
1028inline sveFloat64 operator+(sveFloat64 lhs, sveFloat64 rhs)
1029{
1030 return svadd_f64_x(svptrue_b64(), lhs._data, rhs._data);
1031}
1032
1033inline sveFloat64 operator-(sveFloat64 lhs, sveFloat64 rhs)
1034{
1035 return svsub_f64_x(svptrue_b64(), lhs._data, rhs._data);
1036}
1037
1038inline sveFloat64 operator-(sveFloat64 in)
1039{
1040 return svsub_f64_x(svptrue_b64(), svdup_f64(-0.0), in._data);
1041 // return sveor_f64_x(svptrue_b64(), in._data, svdup_f64(-0.0));
1042}
1043
1044inline sveFloat64 operator*(sveFloat64 lhs, sveFloat64 rhs)
1045{
1046 return svmul_f64_x(svptrue_b64(), lhs._data, rhs._data);
1047}
1048
1049inline sveFloat64 operator/(sveFloat64 lhs, sveFloat64 rhs)
1050{
1051 return svdiv_f64_x(svptrue_b64(), lhs._data, rhs._data);
1052}
1053
1054inline sveFloat64 sqrt(sveFloat64 in)
1055{
1056 return svsqrt_f64_x(svptrue_b64(), in._data);
1057}
1058
1059inline sveFloat64 abs(sveFloat64 in)
1060{
1061 return svabs_f64_x(svptrue_b64(), in._data);
1062}
1063
1064inline sveFloat64 min(sveFloat64 lhs, sveFloat64 rhs)
1065{
1066 return svmin_f64_x(svptrue_b64(), lhs._data, rhs._data);
1067}
1068
1069inline sveFloat64 max(sveFloat64 lhs, sveFloat64 rhs)
1070{
1071 return svmax_f64_x(svptrue_b64(), lhs._data, rhs._data);
1072}
1073
1074inline sveFloat64 log(sveFloat64 in)
1075{
1076 // there is no sve log intrinsic
1077 // this is a dreadful implementation and is simply a stop gap measure
1078 alignas(sveFloat64::alignment) sveFloat64::scalarArray tmp;
1079 in.store(tmp);
1080 for (size_t i = 0; i < sveFloat64::width; ++i)
1081 {
1082 tmp[i] = std::log(tmp[i]);
1083 }
1084 sveFloat64 ret;
1085 ret.load(tmp);
1086 return ret;
1087}
1088
1089inline void load_unalign_interleave(
1090 const double *in, const std::uint32_t dataLen,
1091 std::vector<sveFloat64, allocator<sveFloat64>> &out)
1092{
1093 alignas(sveFloat64::alignment) sveFloat64::scalarArray tmp;
1094 for (size_t i = 0; i < dataLen; ++i)
1095 {
1096 for (size_t j = 0; j < sveFloat64::width; ++j)
1097 {
1098 tmp[j] = in[i + j * dataLen];
1099 }
1100 out[i].load(tmp);
1101 }
1102}
1103
1105 const double *in, const std::uint32_t dataLen, const std::uint32_t nPads,
1106 std::vector<sveFloat64, allocator<sveFloat64>> &out)
1107{
1108 alignas(sveFloat64::alignment) sveFloat64::scalarArray tmp;
1109 const size_t nData = sveFloat64::width - nPads;
1110 for (size_t i = 0; i < dataLen; ++i)
1111 {
1112 for (size_t j = 0; j < nData; ++j)
1113 {
1114 tmp[j] = in[i + j * dataLen];
1115 }
1116 for (size_t j = nData; j < sveFloat64::width; ++j)
1117 {
1118 tmp[j] = 0.0;
1119 }
1120 out[i].load(tmp);
1121 }
1122}
1123
1124inline void load_interleave(const double *in, std::uint32_t dataLen,
1125 std::vector<sveFloat64, allocator<sveFloat64>> &out)
1126{
1127
1128 alignas(sveFloat64::alignment) size_t tmp[sveFloat64::width] = {};
1129
1130 // populate scalar index of unknown size
1131 // (known at compile time)
1132 for (size_t i = 0; i < sveFloat64::width; ++i)
1133 {
1134 tmp[i] = i * dataLen;
1135 }
1136
1137 using index_t = sveInt64<size_t>;
1138 index_t index0(tmp);
1139 index_t index1 = index0 + 1ul;
1140
1141 // 2x unrolled loop -- minimun width is 2
1142 size_t nBlocks = dataLen / 2;
1143 for (size_t i = 0; i < nBlocks; ++i)
1144 {
1145 out[2 * i + 0].gather(in, index0);
1146 out[2 * i + 1].gather(in, index1);
1147 index0 = index0 + 2ul;
1148 index1 = index1 + 2ul;
1149 }
1150
1151 // spillover loop
1152 for (size_t i = 2 * nBlocks; i < dataLen; ++i)
1153 {
1154 out[i].gather(in, index0);
1155 index0 = index0 + 1ul;
1156 }
1157}
1158
1159inline void deinterleave_unalign_store(
1160 const std::vector<sveFloat64, allocator<sveFloat64>> &in,
1161 const std::uint32_t dataLen, double *out)
1162{
1163 alignas(sveFloat64::alignment) sveFloat64::scalarArray tmp;
1164 for (size_t i = 0; i < dataLen; ++i)
1165 {
1166 in[i].store(tmp);
1167 for (size_t j = 0; j < sveFloat64::width; ++j)
1168 {
1169 out[i + j * dataLen] = tmp[j];
1170 }
1171 }
1172}
1173
1175 const std::vector<sveFloat64, allocator<sveFloat64>> &in,
1176 const std::uint32_t dataLen, const std::uint32_t nPads, double *out)
1177{
1178 alignas(sveFloat64::alignment) sveFloat64::scalarArray tmp;
1179 const size_t nData = sveFloat64::width - nPads;
1180 for (size_t i = 0; i < dataLen; ++i)
1181 {
1182 in[i].store(tmp);
1183 for (size_t j = 0; j < nData; ++j)
1184 {
1185 out[i + j * dataLen] = tmp[j];
1186 }
1187 }
1188}
1189
1190inline void deinterleave_store(
1191 const std::vector<sveFloat64, allocator<sveFloat64>> &in,
1192 std::uint32_t dataLen, double *out)
1193{
1194 alignas(sveFloat64::alignment) size_t tmp[sveFloat64::width] = {};
1195
1196 // populate scalar index of unknown size
1197 // (known at compile time)
1198 for (size_t i = 0; i < sveFloat64::width; ++i)
1199 {
1200 tmp[i] = i * dataLen;
1201 }
1202
1203 using index_t = sveInt64<size_t>;
1204 index_t index0(tmp);
1205
1206 for (size_t i = 0; i < dataLen; ++i)
1207 {
1208 in[i].scatter(out, index0);
1209 index0 = index0 + 1ul;
1210 }
1211}
1212
1213////////////////////////////////////////////////////////////////////////////////
1214
1215// mask type
1216// mask is a int type with special properties (broad boolean vector)
1217// broad boolean vectors defined and allowed values are:
1218// false=0x0 and true=0xFFFFFFFF
1219//
1220// VERY LIMITED SUPPORT...just enough to make cubic eos work...
1221//
1222struct sveMask64 : sveInt64<std::uint64_t>
1223{
1224 // bring in ctors
1225 using sveInt64::sveInt64;
1226
1227 static constexpr scalarType true_v = -1;
1228 static constexpr scalarType false_v = 0;
1229};
1230
1231inline sveMask64 operator>(sveFloat64 lhs, sveFloat64 rhs)
1232{
1233 // set mask
1234 svbool_vlst_t mask = svcmpgt(svptrue_b64(), lhs._data, rhs._data);
1235 // abuse set inactive to zero to convert
1236 sveMask64::vectorType sveTrue_v = svdup_u64(sveMask64::true_v);
1237 return svand_u64_z(mask, sveTrue_v, sveTrue_v);
1238}
1239
1240// logical and
1241inline bool operator&&(sveMask64 lhs, bool rhs)
1242{
1243 // set mask
1244 sveMask64::vectorType sveFalse_v = svdup_u64(sveMask64::false_v);
1245 svbool_vlst_t mask = svcmpne_u64(svptrue_b64(), lhs._data, sveFalse_v);
1246 // is any equal to false (zero)?
1247 bool tmp = svptest_any(svptrue_b64(), mask);
1248 return tmp && rhs;
1249}
1250
1251////////////////////////////////////////////////////////////////////////////////
1252
1253struct sveMask32 : sveInt32<std::uint32_t>
1254{
1255 // bring in ctors
1256 using sveInt32::sveInt32;
1257
1258 static constexpr scalarType true_v = -1;
1259 static constexpr scalarType false_v = 0;
1260};
1261
1262inline sveMask32 operator>(sveFloat32 lhs, sveFloat32 rhs)
1263{
1264 // set mask
1265 svbool_vlst_t mask = svcmpgt(svptrue_b32(), lhs._data, rhs._data);
1266 // abuse set inactive to zero to convert
1267 sveMask32::vectorType sveTrue_v = svdup_u32(sveMask32::true_v);
1268 return svand_u32_z(mask, sveTrue_v, sveTrue_v);
1269}
1270
1271// logical and
1272inline bool operator&&(sveMask32 lhs, bool rhs)
1273{
1274 // set mask
1275 sveMask32::vectorType sveFalse_v = svdup_u32(sveMask32::false_v);
1276 svbool_vlst_t mask = svcmpne_u32(svptrue_b32(), lhs._data, sveFalse_v);
1277 // is any equal to false (zero)?
1278 bool tmp = svptest_any(svptrue_b32(), mask);
1279 return tmp && rhs;
1280}
1281
1282} // namespace tinysimd
1283
1284#endif // defined(__ARM_FEATURE_SVE_BITS)
1285
1286#endif
std::int32_t int32_t
std::uint32_t uint32_t
std::int64_t int64_t
std::uint64_t uint64_t
STL namespace.
void load_interleave(const T *in, const size_t dataLen, std::vector< scalarT< T >, allocator< scalarT< T > > > &out)
Definition scalar.hpp:338
scalarT< T > abs(scalarT< T > in)
Definition scalar.hpp:295
void deinterleave_unalign_store(const std::vector< scalarT< T >, allocator< scalarT< T > > > &in, const size_t dataLen, T *out)
Definition scalar.hpp:348
static constexpr struct tinysimd::is_aligned_t is_aligned
scalarT< T > operator-(scalarT< T > lhs, scalarT< T > rhs)
Definition scalar.hpp:232
scalarT< T > operator/(scalarT< T > lhs, scalarT< T > rhs)
Definition scalar.hpp:273
scalarT< T > max(scalarT< T > lhs, scalarT< T > rhs)
Definition scalar.hpp:305
scalarT< T > log(scalarT< T > in)
Definition scalar.hpp:310
scalarT< T > operator*(scalarT< T > lhs, scalarT< T > rhs)
Definition scalar.hpp:255
scalarMask operator>(scalarT< double > lhs, scalarT< double > rhs)
Definition scalar.hpp:417
bool operator&&(scalarMask lhs, bool rhs)
Definition scalar.hpp:427
void load_unalign_interleave(const T *in, const size_t dataLen, std::vector< scalarT< T >, allocator< scalarT< T > > > &out)
Definition scalar.hpp:316
void deinterleave_store(const std::vector< scalarT< T >, allocator< scalarT< T > > > &in, const size_t dataLen, T *out)
Definition scalar.hpp:370
scalarT< T > min(scalarT< T > lhs, scalarT< T > rhs)
Definition scalar.hpp:300
void deinterleave_unalign_store_skipPads(const std::vector< scalarT< T >, allocator< scalarT< T > > > &in, const size_t dataLen, const size_t skipPads, T *out)
Definition scalar.hpp:359
scalarT< T > sqrt(scalarT< T > in)
Definition scalar.hpp:290
void load_unalign_interleave_skipPads(const T *in, const size_t dataLen, const size_t skipPads, std::vector< scalarT< T >, allocator< scalarT< T > > > &out)
Definition scalar.hpp:327
scalarT< T > operator+(scalarT< T > lhs, scalarT< T > rhs)
Definition scalar.hpp:214