Nektar++
Loading...
Searching...
No Matches
sse2.hpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: sse2.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 sse2 extension.
32//
33///////////////////////////////////////////////////////////////////////////////
34
35#ifndef NEKTAR_LIB_LIBUTILITES_SIMDLIB_SSE2_H
36#define NEKTAR_LIB_LIBUTILITES_SIMDLIB_SSE2_H
37
38#if defined(__x86_64__)
39#include <immintrin.h>
40#if defined(__INTEL_COMPILER) && !defined(TINYSIMD_HAS_SVML)
41#define TINYSIMD_HAS_SVML
42#endif
43#endif
44#include "allocator.hpp"
45#include "traits.hpp"
46#include <cmath>
47#include <cstdint>
48#include <vector>
49
50namespace tinysimd::abi
51{
52
53template <typename scalarType> struct simd64
54{
55 using type = void;
56};
57
58} // namespace tinysimd::abi
59
60#if defined(__SSE2__) && defined(NEKTAR_ENABLE_SIMD_SSE2)
61
62namespace tinysimd
63{
64
65// forward declaration of concrete types
66template <typename T> struct simd64Int2;
67
68namespace abi
69{
70
71template <> struct simd64<std::int32_t>
72{
73 using type = simd64Int2<std::int32_t>;
74};
75template <> struct simd64<std::uint32_t>
76{
77 using type = simd64Int2<std::uint32_t>;
78};
79
80} // namespace abi
81
82// concrete types
83template <typename T> struct simd64Int2
84{
85 static_assert(std::is_integral_v<T> && sizeof(T) == 4,
86 "4 bytes Integral required.");
87
88 static constexpr unsigned int width = 2;
89 static constexpr unsigned int alignment = 8;
90
91 using scalarType = T;
92 using vectorType = scalarType[width];
93 using scalarArray = scalarType[width];
94
95 // storage
96 vectorType _data;
97
98 // ctors
99 inline simd64Int2() = default;
100 inline simd64Int2(const simd64Int2 &rhs) = default;
101 inline simd64Int2(const vectorType &rhs) : _data(rhs)
102 {
103 }
104 inline simd64Int2(const scalarType rhs)
105 {
106 _data[0] = rhs;
107 _data[1] = rhs;
108 }
109
110 // store
111 inline void store(scalarType *p) const
112 {
113 p[0] = _data[0];
114 p[1] = _data[1];
115 }
116
117 template <class flag,
118 typename std::enable_if<is_requiring_alignment_v<flag> &&
119 !is_streaming_v<flag>,
120 bool>::type = 0>
121 inline void store(scalarType *p, flag) const
122 {
123 p[0] = _data[0];
124 p[1] = _data[1];
125 }
126
127 template <class flag, typename std::enable_if<
128 !is_requiring_alignment_v<flag>, bool>::type = 0>
129 inline void store(scalarType *p, flag) const
130 {
131 p[0] = _data[0];
132 p[1] = _data[1];
133 }
134
135 inline void load(const scalarType *p)
136 {
137 _data[0] = p[0];
138 _data[1] = p[1];
139 }
140
141 template <class flag,
142 typename std::enable_if<is_requiring_alignment_v<flag> &&
143 !is_streaming_v<flag>,
144 bool>::type = 0>
145 inline void load(const scalarType *p, flag)
146 {
147 _data[0] = p[0];
148 _data[1] = p[1];
149 }
150
151 template <class flag, typename std::enable_if<
152 !is_requiring_alignment_v<flag>, bool>::type = 0>
153 inline void load(const scalarType *p, flag)
154 {
155 _data[0] = p[0];
156 _data[1] = p[1];
157 }
158
159 // gather/scatter with sse2
160 inline void gather(scalarType const *p, const simd64Int2<T> &indices)
161 {
162 _data[0] = p[indices[0]];
163 _data[1] = p[indices[1]];
164 }
165
166 inline void scatter(scalarType *out, const simd64Int2<T> &indices) const
167 {
168 out[indices[0]] = _data[0];
169 out[indices[1]] = _data[1];
170 }
171
172 inline void broadcast(const scalarType rhs)
173 {
174 _data[0] = rhs[0];
175 _data[1] = rhs[0];
176 }
177
178 // subscript
179 // subscript operators are convienient but expensive
180 // should not be used in optimized kernels
181 inline scalarType operator[](size_t i) const
182 {
183 return _data[i];
184 }
185};
186} // namespace tinysimd
187
188#endif
189
190namespace tinysimd::abi
191{
192
193template <typename scalarType, int width = 0> struct sse2
194{
195 using type = void;
196};
197
198} // namespace tinysimd::abi
199
200#if defined(__SSE2__) && defined(NEKTAR_ENABLE_SIMD_SSE2)
201
202namespace tinysimd
203{
204
205// forward declaration of concrete types
206template <typename T> struct sse2Long2;
207template <typename T> struct sse2Int4;
208struct sse2Double2;
209struct sse2Float4;
210struct sse2Mask2;
211struct sse2Mask4;
212
213namespace abi
214{
215
216// mapping between abstract types and concrete floating point types
217template <> struct sse2<double>
218{
219 using type = sse2Double2;
220};
221template <> struct sse2<float>
222{
223 using type = sse2Float4;
224};
225// generic index mapping
226// assumes index type width same as floating point type
227template <> struct sse2<std::int64_t>
228{
229 using type = sse2Long2<std::int64_t>;
230};
231template <> struct sse2<std::uint64_t>
232{
233 using type = sse2Long2<std::uint64_t>;
234};
235#if defined(__APPLE__)
236template <> struct sse2<std::size_t>
237{
238 using type = sse2Long2<std::size_t>;
239};
240#endif
241template <> struct sse2<std::int32_t>
242{
243 using type = sse2Int4<std::int32_t>;
244};
245template <> struct sse2<std::uint32_t>
246{
247 using type = sse2Int4<std::uint32_t>;
248};
249// specialized index mapping
250template <> struct sse2<std::int64_t, 2>
251{
252 using type = sse2Long2<std::int64_t>;
253};
254template <> struct sse2<std::uint64_t, 2>
255{
256 using type = sse2Long2<std::uint64_t>;
257};
258#if defined(__APPLE__)
259template <> struct sse2<std::size_t, 2>
260{
261 using type = sse2Long2<std::size_t>;
262};
263#endif
264template <> struct sse2<std::int32_t, 2>
265{
266 using type = simd64Int2<std::int32_t>;
267};
268template <> struct sse2<std::uint32_t, 2>
269{
270 using type = simd64Int2<std::uint32_t>;
271};
272template <> struct sse2<std::int32_t, 4>
273{
274 using type = sse2Int4<std::int32_t>;
275};
276template <> struct sse2<std::uint32_t, 4>
277{
278 using type = sse2Int4<std::uint32_t>;
279};
280// bool mapping
281template <> struct sse2<bool, 2>
282{
283 using type = sse2Mask2;
284};
285template <> struct sse2<bool, 4>
286{
287 using type = sse2Mask4;
288};
289
290} // namespace abi
291
292// concrete types
293template <typename T> struct sse2Int4
294{
295 static_assert(std::is_integral_v<T> && sizeof(T) == 4,
296 "4 bytes Integral required.");
297
298 static constexpr unsigned int width = 4;
299 static constexpr unsigned int alignment = 16;
300
301 using scalarType = T;
302 using vectorType = __m128i;
303 using scalarArray = scalarType[width];
304
305 // storage
306 vectorType _data;
307
308 // ctors
309 inline sse2Int4() = default;
310 inline sse2Int4(const sse2Int4 &rhs) = default;
311 inline sse2Int4(const vectorType &rhs) : _data(rhs)
312 {
313 }
314 inline sse2Int4(const scalarType rhs)
315 {
316 _data = _mm_set1_epi32(rhs);
317 }
318 explicit inline sse2Int4(scalarArray &rhs)
319 {
320 _data = _mm_load_si128(reinterpret_cast<vectorType *>(rhs));
321 }
322
323 // copy assignment
324 inline sse2Int4 &operator=(const sse2Int4 &) = default;
325
326 // store
327 inline void store(scalarType *p) const
328 {
329 _mm_store_si128(reinterpret_cast<vectorType *>(p), _data);
330 }
331
332 template <class flag,
333 typename std::enable_if<is_requiring_alignment_v<flag> &&
334 !is_streaming_v<flag>,
335 bool>::type = 0>
336 inline void store(scalarType *p, flag) const
337 {
338 _mm_store_si128(reinterpret_cast<vectorType *>(p), _data);
339 }
340
341 template <class flag, typename std::enable_if<
342 !is_requiring_alignment_v<flag>, bool>::type = 0>
343 inline void store(scalarType *p, flag) const
344 {
345 _mm_storeu_si128(reinterpret_cast<vectorType *>(p), _data);
346 }
347
348 inline void load(const scalarType *p)
349 {
350 _data = _mm_load_si128(reinterpret_cast<const vectorType *>(p));
351 }
352
353 template <class flag,
354 typename std::enable_if<is_requiring_alignment_v<flag> &&
355 !is_streaming_v<flag>,
356 bool>::type = 0>
357 inline void load(const scalarType *p, flag)
358 {
359 _data = _mm_load_si128(reinterpret_cast<const vectorType *>(p));
360 }
361
362 template <class flag, typename std::enable_if<
363 !is_requiring_alignment_v<flag>, bool>::type = 0>
364 inline void load(const scalarType *p, flag)
365 {
366 _data = _mm_loadu_si128(reinterpret_cast<const vectorType *>(p));
367 }
368
369 inline void broadcast(const scalarType rhs)
370 {
371 _data = _mm_set1_epi32(rhs);
372 }
373
374 /*// gather/scatter with sse2
375 inline void gather(scalarType const *p, const sse2Int4<T> &indices)
376 {
377 _data = _mm_i32gather_epi32(p, indices._data, 8);
378 }
379
380 inline void scatter(scalarType *out, const sse2Int4<T> &indices) const
381 {
382 // no scatter intrinsics for sse2
383 alignas(alignment) scalarArray tmp;
384 _mm_store_epi32(tmp, _data);
385
386 out[_mm_extract_epi32(indices._data, 0)] = tmp[0]; // SSE4.1
387 out[_mm_extract_epi32(indices._data, 1)] = tmp[1];
388 }*/
389
390 // subscript
391 // subscript operators are convienient but expensive
392 // should not be used in optimized kernels
393 inline scalarType operator[](size_t i) const
394 {
395 alignas(alignment) scalarArray tmp;
396 store(tmp, is_aligned);
397 return tmp[i];
398 }
399
400 inline scalarType &operator[](size_t i)
401 {
402 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
403 return tmp[i];
404 }
405};
406
407template <typename T>
408inline sse2Int4<T> operator+(sse2Int4<T> lhs, sse2Int4<T> rhs)
409{
410 return _mm_add_epi32(lhs._data, rhs._data);
411}
412
413template <typename T, typename U,
414 typename = typename std::enable_if<std::is_arithmetic_v<U>>::type>
415inline sse2Int4<T> operator+(sse2Int4<T> lhs, U rhs)
416{
417 return _mm_add_epi32(lhs._data, _mm_set1_epi32(rhs));
418}
419
420////////////////////////////////////////////////////////////////////////////////
421
422template <typename T> struct sse2Long2
423{
424 static_assert(std::is_integral_v<T> && sizeof(T) == 8,
425 "8 bytes Integral required.");
426
427 static constexpr unsigned int width = 2;
428 static constexpr unsigned int alignment = 16;
429
430 using scalarType = T;
431 using vectorType = __m128i;
432 using scalarArray = scalarType[width];
433
434 // storage
435 vectorType _data;
436
437 // ctors
438 inline sse2Long2() = default;
439 inline sse2Long2(const sse2Long2 &rhs) = default;
440 inline sse2Long2(const vectorType &rhs) : _data(rhs)
441 {
442 }
443 inline sse2Long2(const scalarType rhs)
444 {
445 _data = _mm_set1_epi64x(rhs);
446 }
447 explicit inline sse2Long2(scalarArray &rhs)
448 {
449 _data = _mm_load_si128(reinterpret_cast<vectorType *>(rhs));
450 }
451
452 // copy assignment
453 inline sse2Long2 &operator=(const sse2Long2 &) = default;
454
455 // store
456 inline void store(scalarType *p) const
457 {
458 _mm_store_si128(reinterpret_cast<vectorType *>(p), _data);
459 }
460
461 template <class flag,
462 typename std::enable_if<is_requiring_alignment_v<flag> &&
463 !is_streaming_v<flag>,
464 bool>::type = 0>
465 inline void store(scalarType *p, flag) const
466 {
467 _mm_store_si128(reinterpret_cast<vectorType *>(p), _data);
468 }
469
470 template <class flag, typename std::enable_if<
471 !is_requiring_alignment_v<flag>, bool>::type = 0>
472 inline void store(scalarType *p, flag) const
473 {
474 _mm_storeu_si128(reinterpret_cast<vectorType *>(p), _data);
475 }
476
477 inline void load(const scalarType *p)
478 {
479 _data = _mm_load_si128(reinterpret_cast<const vectorType *>(p));
480 }
481
482 template <class flag,
483 typename std::enable_if<is_requiring_alignment_v<flag> &&
484 !is_streaming_v<flag>,
485 bool>::type = 0>
486 inline void load(const scalarType *p, flag)
487 {
488 _data = _mm_load_si128(reinterpret_cast<const vectorType *>(p));
489 }
490
491 template <class flag, typename std::enable_if<
492 !is_requiring_alignment_v<flag>, bool>::type = 0>
493 inline void load(const scalarType *p, flag)
494 {
495 _data = _mm_loadu_si128(reinterpret_cast<const vectorType *>(p));
496 }
497
498 inline void broadcast(const scalarType rhs)
499 {
500 _data = _mm_set1_epi64x(rhs);
501 }
502
503 // subscript
504 // subscript operators are convienient but expensive
505 // should not be used in optimized kernels
506 inline scalarType operator[](size_t i) const
507 {
508 alignas(alignment) scalarArray tmp;
509 store(tmp, is_aligned);
510 return tmp[i];
511 }
512
513 inline scalarType &operator[](size_t i)
514 {
515 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
516 return tmp[i];
517 }
518};
519
520template <typename T>
521inline sse2Long2<T> operator+(sse2Long2<T> lhs, sse2Long2<T> rhs)
522{
523 return _mm_add_epi64(lhs._data, rhs._data);
524}
525
526template <typename T, typename U,
527 typename = typename std::enable_if<std::is_arithmetic_v<U>>::type>
528inline sse2Long2<T> operator+(sse2Long2<T> lhs, U rhs)
529{
530 return _mm_add_epi64(lhs._data, _mm_set1_epi64x(rhs));
531}
532
533////////////////////////////////////////////////////////////////////////////////
534
535struct sse2Double2
536{
537 static constexpr unsigned width = 2;
538 static constexpr unsigned alignment = 16;
539
540 using scalarType = double;
541 using scalarIndexType = std::uint64_t;
542 using vectorType = __m128d;
543 using scalarArray = scalarType[width];
544
545 // storage
546 vectorType _data;
547
548 // ctors
549 inline sse2Double2() = default;
550 inline sse2Double2(const sse2Double2 &rhs) = default;
551 inline sse2Double2(const vectorType &rhs) : _data(rhs)
552 {
553 }
554 inline sse2Double2(const scalarType rhs)
555 {
556 _data = _mm_set1_pd(rhs);
557 }
558
559 // copy assignment
560 inline sse2Double2 &operator=(const sse2Double2 &) = default;
561
562 // store
563 inline void store(scalarType *p) const
564 {
565 _mm_store_pd(p, _data);
566 }
567
568 template <class flag,
569 typename std::enable_if<is_requiring_alignment_v<flag> &&
570 !is_streaming_v<flag>,
571 bool>::type = 0>
572 inline void store(scalarType *p, flag) const
573 {
574 _mm_store_pd(p, _data);
575 }
576
577 template <class flag, typename std::enable_if<
578 !is_requiring_alignment_v<flag>, bool>::type = 0>
579 inline void store(scalarType *p, flag) const
580 {
581 _mm_storeu_pd(p, _data);
582 }
583
584 template <class flag,
585 typename std::enable_if<is_streaming_v<flag>, bool>::type = 0>
586 inline void store(scalarType *p, flag) const
587 {
588 _mm_stream_pd(p, _data);
589 }
590
591 // load packed
592 inline void load(const scalarType *p)
593 {
594 _data = _mm_load_pd(p);
595 }
596
597 template <class flag, typename std::enable_if<
598 is_requiring_alignment_v<flag>, bool>::type = 0>
599 inline void load(const scalarType *p, flag)
600 {
601 _data = _mm_load_pd(p);
602 }
603
604 template <class flag, typename std::enable_if<
605 !is_requiring_alignment_v<flag>, bool>::type = 0>
606 inline void load(const scalarType *p, flag)
607 {
608 _data = _mm_loadu_pd(p);
609 }
610
611 // broadcast
612 inline void broadcast(const scalarType rhs)
613 {
614 _data = _mm_set1_pd(rhs);
615 }
616
617 // gather/scatter with simd64
618 template <typename T>
619 inline void gather(scalarType const *p, const simd64Int2<T> &indices)
620 {
621 // no gather intrinsics for SSE2
622 alignas(alignment) scalarArray tmp;
623 tmp[0] = p[indices[0]];
624 tmp[1] = p[indices[1]];
625 _data = _mm_load_pd(&tmp[0]);
626 }
627
628 template <typename T>
629 inline void scatter(scalarType *out, const simd64Int2<T> &indices) const
630 {
631 // no scatter intrinsics for SSE2
632 alignas(alignment) scalarArray tmp;
633 _mm_store_pd(tmp, _data);
634
635 out[indices[0]] = tmp[0]; // SSE4.1
636 out[indices[1]] = tmp[1];
637 }
638
639 template <typename T>
640 inline void gather(scalarType const *p, const sse2Long2<T> &indices)
641 {
642 // no gather intrinsics for SSE2
643 alignas(alignment) scalarArray tmp;
644 tmp[0] = p[_mm_extract_epi64(indices._data, 0)];
645 tmp[1] = p[_mm_extract_epi64(indices._data, 1)];
646 _data = _mm_load_pd(&tmp[0]);
647 }
648
649 template <typename T>
650 inline void scatter(scalarType *out, const sse2Long2<T> &indices) const
651 {
652 // no scatter intrinsics for SSE2
653 alignas(alignment) scalarArray tmp;
654 _mm_store_pd(tmp, _data);
655
656 out[_mm_extract_epi64(indices._data, 0)] = tmp[0];
657 out[_mm_extract_epi64(indices._data, 1)] = tmp[1];
658 }
659
660 // fma
661 // this = this + a * b
662 inline void fma(const sse2Double2 &a, const sse2Double2 &b)
663 {
664 _data = _mm_fmadd_pd(a._data, b._data, _data);
665 }
666
667 // subscript
668 // subscript operators are convienient but expensive
669 // should not be used in optimized kernels
670 inline scalarType operator[](size_t i) const
671 {
672 alignas(alignment) scalarArray tmp;
673 store(tmp, is_aligned);
674 return tmp[i];
675 }
676
677 inline scalarType &operator[](size_t i)
678 {
679 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
680 return tmp[i];
681 }
682
683 // unary ops
684 inline void operator+=(sse2Double2 rhs)
685 {
686 _data = _mm_add_pd(_data, rhs._data);
687 }
688
689 inline void operator-=(sse2Double2 rhs)
690 {
691 _data = _mm_sub_pd(_data, rhs._data);
692 }
693
694 inline void operator*=(sse2Double2 rhs)
695 {
696 _data = _mm_mul_pd(_data, rhs._data);
697 }
698
699 inline void operator/=(sse2Double2 rhs)
700 {
701 _data = _mm_div_pd(_data, rhs._data);
702 }
703};
704
705inline sse2Double2 operator+(sse2Double2 lhs, sse2Double2 rhs)
706{
707 return _mm_add_pd(lhs._data, rhs._data);
708}
709
710inline sse2Double2 operator-(sse2Double2 lhs, sse2Double2 rhs)
711{
712 return _mm_sub_pd(lhs._data, rhs._data);
713}
714
715inline sse2Double2 operator-(sse2Double2 in)
716{
717 return _mm_xor_pd(in._data, _mm_set1_pd(-0.0));
718}
719
720inline sse2Double2 operator*(sse2Double2 lhs, sse2Double2 rhs)
721{
722 return _mm_mul_pd(lhs._data, rhs._data);
723}
724
725inline sse2Double2 operator/(sse2Double2 lhs, sse2Double2 rhs)
726{
727 return _mm_div_pd(lhs._data, rhs._data);
728}
729
730inline sse2Double2 sqrt(sse2Double2 in)
731{
732 return _mm_sqrt_pd(in._data);
733}
734
735inline sse2Double2 abs(sse2Double2 in)
736{
737 // there is no sse2 _mm_abs_pd intrinsic
738 static const __m128d sign_mask = _mm_set1_pd(-0.); // -0. = 1 << 63
739 return _mm_andnot_pd(sign_mask, in._data); // !sign_mask & x
740}
741
742inline sse2Double2 min(sse2Double2 lhs, sse2Double2 rhs)
743{
744 return _mm_min_pd(lhs._data, rhs._data);
745}
746
747inline sse2Double2 max(sse2Double2 lhs, sse2Double2 rhs)
748{
749 return _mm_max_pd(lhs._data, rhs._data);
750}
751
752inline sse2Double2 log(sse2Double2 in)
753{
754#if defined(TINYSIMD_HAS_SVML)
755 return _mm_log_pd(in._data);
756#else
757 // there is no sse2 log intrinsic
758 // this is a dreadful implementation and is simply a stop gap measure
759 alignas(sse2Double2::alignment) sse2Double2::scalarArray tmp;
760 in.store(tmp);
761 tmp[0] = std::log(tmp[0]);
762 tmp[1] = std::log(tmp[1]);
763 sse2Double2 ret;
764 ret.load(tmp);
765 return ret;
766#endif
767}
768
769inline void load_unalign_interleave(
770 const double *in, const std::uint32_t dataLen,
771 std::vector<sse2Double2, allocator<sse2Double2>> &out)
772{
773 alignas(sse2Double2::alignment) sse2Double2::scalarArray tmp;
774 for (size_t i = 0; i < dataLen; ++i)
775 {
776 tmp[0] = in[i];
777 tmp[1] = in[i + dataLen];
778 out[i].load(tmp);
779 }
780}
781
782inline void load_interleave(
783 const double *in, std::uint32_t dataLen,
784 std::vector<sse2Double2, allocator<sse2Double2>> &out)
785{
786 alignas(sse2Double2::alignment)
787 size_t tmp[sse2Double2::width] = {0, dataLen};
788 using index_t = sse2Long2<size_t>;
789 index_t index0(tmp);
790 index_t index1 = index0 + 1;
791
792 // 4x unrolled loop
793 constexpr uint16_t unrl = 2;
794 size_t nBlocks = dataLen / unrl;
795 for (size_t i = 0; i < nBlocks; ++i)
796 {
797 out[unrl * i + 0].gather(in, index0);
798 out[unrl * i + 1].gather(in, index1);
799 index0 = index0 + unrl;
800 index1 = index1 + unrl;
801 }
802
803 // spillover loop
804 for (size_t i = unrl * nBlocks; i < dataLen; ++i)
805 {
806 out[i].gather(in, index0);
807 index0 = index0 + 1;
808 }
809}
810
812 const std::vector<sse2Double2, allocator<sse2Double2>> &in,
813 const std::uint32_t dataLen, double *out)
814{
815 alignas(sse2Double2::alignment) sse2Double2::scalarArray tmp;
816 for (size_t i = 0; i < dataLen; ++i)
817 {
818 in[i].store(tmp);
819 out[i] = tmp[0];
820 out[i + dataLen] = tmp[1];
821 }
822}
823
824inline void deinterleave_store(
825 const std::vector<sse2Double2, allocator<sse2Double2>> &in,
826 std::uint32_t dataLen, double *out)
827{
828 alignas(sse2Double2::alignment)
829 size_t tmp[sse2Double2::width] = {0, dataLen};
830 using index_t = sse2Long2<size_t>;
831 index_t index0(tmp);
832
833 for (size_t i = 0; i < dataLen; ++i)
834 {
835 in[i].scatter(out, index0);
836 index0 = index0 + 1;
837 }
838}
839
840//////////////////////////////////////////////////////////////////////////////
841
842struct sse2Float4
843{
844 static constexpr unsigned width = 4;
845 static constexpr unsigned alignment = 16;
846
847 using scalarType = float;
848 using scalarIndexType = std::uint32_t;
849 using vectorType = __m128;
850 using scalarArray = scalarType[width];
851
852 // storage
853 vectorType _data;
854
855 // ctors
856 inline sse2Float4() = default;
857 inline sse2Float4(const sse2Float4 &rhs) = default;
858 inline sse2Float4(const vectorType &rhs) : _data(rhs)
859 {
860 }
861 inline sse2Float4(const scalarType rhs)
862 {
863 _data = _mm_set1_ps(rhs);
864 }
865
866 // copy assignment
867 inline sse2Float4 &operator=(const sse2Float4 &) = default;
868
869 // store
870 inline void store(scalarType *p) const
871 {
872 _mm_store_ps(p, _data);
873 }
874
875 template <class flag,
876 typename std::enable_if<is_requiring_alignment_v<flag> &&
877 !is_streaming_v<flag>,
878 bool>::type = 0>
879 inline void store(scalarType *p, flag) const
880 {
881 _mm_store_ps(p, _data);
882 }
883
884 template <class flag, typename std::enable_if<
885 !is_requiring_alignment_v<flag>, bool>::type = 0>
886 inline void store(scalarType *p, flag) const
887 {
888 _mm_storeu_ps(p, _data);
889 }
890
891 template <class flag,
892 typename std::enable_if<is_streaming_v<flag>, bool>::type = 0>
893 inline void store(scalarType *p, flag) const
894 {
895 _mm_stream_ps(p, _data);
896 }
897
898 // load packed
899 inline void load(const scalarType *p)
900 {
901 _data = _mm_load_ps(p);
902 }
903
904 template <class flag, typename std::enable_if<
905 is_requiring_alignment_v<flag>, bool>::type = 0>
906 inline void load(const scalarType *p, flag)
907 {
908 _data = _mm_load_ps(p);
909 }
910
911 template <class flag, typename std::enable_if<
912 !is_requiring_alignment_v<flag>, bool>::type = 0>
913 inline void load(const scalarType *p, flag)
914 {
915 _data = _mm_loadu_ps(p);
916 }
917
918 // broadcast
919 inline void broadcast(const scalarType rhs)
920 {
921 _data = _mm_set1_ps(rhs);
922 }
923
924 // gather scatter with sse2
925 template <typename T>
926 inline void gather(scalarType const *p, const sse2Int4<T> &indices)
927 {
928 // no gather intrinsics for SSE2
929 alignas(alignment) scalarArray tmp;
930 tmp[0] = p[_mm_extract_epi32(indices._data, 0)];
931 tmp[1] = p[_mm_extract_epi32(indices._data, 1)];
932 tmp[2] = p[_mm_extract_epi32(indices._data, 2)];
933 tmp[3] = p[_mm_extract_epi32(indices._data, 3)];
934 _data = _mm_load_ps(&tmp[0]);
935 }
936
937 template <typename T>
938 inline void scatter(scalarType *out, const sse2Int4<T> &indices) const
939 {
940 // no scatter intrinsics for SSE2
941 alignas(alignment) scalarArray tmp;
942 _mm_store_ps(tmp, _data);
943
944 out[_mm_extract_epi32(indices._data, 0)] = tmp[0];
945 out[_mm_extract_epi32(indices._data, 1)] = tmp[1];
946 out[_mm_extract_epi32(indices._data, 2)] = tmp[2];
947 out[_mm_extract_epi32(indices._data, 3)] = tmp[3];
948 }
949
950 // fma
951 // this = this + a * b
952 inline void fma(const sse2Float4 &a, const sse2Float4 &b)
953 {
954 _data = _mm_fmadd_ps(a._data, b._data, _data);
955 }
956
957 // subscript
958 // subscript operators are convienient but expensive
959 // should not be used in optimized kernels
960 inline scalarType operator[](size_t i) const
961 {
962 alignas(alignment) scalarArray tmp;
963 store(tmp, is_aligned);
964 return tmp[i];
965 }
966
967 inline scalarType &operator[](size_t i)
968 {
969 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
970 return tmp[i];
971 }
972
973 inline void operator+=(sse2Float4 rhs)
974 {
975 _data = _mm_add_ps(_data, rhs._data);
976 }
977
978 inline void operator-=(sse2Float4 rhs)
979 {
980 _data = _mm_sub_ps(_data, rhs._data);
981 }
982
983 inline void operator*=(sse2Float4 rhs)
984 {
985 _data = _mm_mul_ps(_data, rhs._data);
986 }
987
988 inline void operator/=(sse2Float4 rhs)
989 {
990 _data = _mm_div_ps(_data, rhs._data);
991 }
992};
993
994inline sse2Float4 operator+(sse2Float4 lhs, sse2Float4 rhs)
995{
996 return _mm_add_ps(lhs._data, rhs._data);
997}
998
999inline sse2Float4 operator-(sse2Float4 lhs, sse2Float4 rhs)
1000{
1001 return _mm_sub_ps(lhs._data, rhs._data);
1002}
1003
1004inline sse2Float4 operator-(sse2Float4 in)
1005{
1006 return _mm_xor_ps(in._data, _mm_set1_ps(-0.0));
1007}
1008
1009inline sse2Float4 operator*(sse2Float4 lhs, sse2Float4 rhs)
1010{
1011 return _mm_mul_ps(lhs._data, rhs._data);
1012}
1013
1014inline sse2Float4 operator/(sse2Float4 lhs, sse2Float4 rhs)
1015{
1016 return _mm_div_ps(lhs._data, rhs._data);
1017}
1018
1019inline sse2Float4 sqrt(sse2Float4 in)
1020{
1021 return _mm_sqrt_ps(in._data);
1022}
1023
1024inline sse2Float4 abs(sse2Float4 in)
1025{
1026 // there is no sse2 _mm_abs_ps intrinsic
1027 static const __m128 sign_mask = _mm_set1_ps(-0.); // -0. = 1 << 63
1028 return _mm_andnot_ps(sign_mask, in._data); // !sign_mask & x
1029}
1030
1031inline sse2Float4 min(sse2Float4 lhs, sse2Float4 rhs)
1032{
1033 return _mm_min_ps(lhs._data, rhs._data);
1034}
1035
1036inline sse2Float4 max(sse2Float4 lhs, sse2Float4 rhs)
1037{
1038 return _mm_max_ps(lhs._data, rhs._data);
1039}
1040
1041inline sse2Float4 log(sse2Float4 in)
1042{
1043 // there is no sse2 log intrinsic
1044 // this is a dreadful implementation and is simply a stop gap measure
1045 alignas(sse2Float4::alignment) sse2Float4::scalarArray tmp;
1046 in.store(tmp);
1047 tmp[0] = std::log(tmp[0]);
1048 tmp[1] = std::log(tmp[1]);
1049 tmp[2] = std::log(tmp[2]);
1050 tmp[3] = std::log(tmp[3]);
1051 sse2Float4 ret;
1052 ret.load(tmp);
1053 return ret;
1054}
1055
1056inline void load_unalign_interleave(
1057 const double *in, const std::uint32_t dataLen,
1058 std::vector<sse2Float4, allocator<sse2Float4>> &out)
1059{
1060 alignas(sse2Float4::alignment) sse2Float4::scalarArray tmp;
1061 for (size_t i = 0; i < dataLen; ++i)
1062 {
1063 tmp[0] = in[i];
1064 tmp[1] = in[i + dataLen];
1065 tmp[2] = in[i + 2 * dataLen];
1066 tmp[3] = in[i + 3 * dataLen];
1067 out[i].load(tmp);
1068 }
1069}
1070
1071inline void load_interleave(const float *in, std::uint32_t dataLen,
1072 std::vector<sse2Float4, allocator<sse2Float4>> &out)
1073{
1074
1075 alignas(sse2Float4::alignment) sse2Float4::scalarIndexType tmp[4] = {
1076 0, dataLen, 2 * dataLen, 3 * dataLen};
1077
1078 using index_t = sse2Int4<sse2Float4::scalarIndexType>;
1079 index_t index0(tmp);
1080 index_t index1 = index0 + 1;
1081
1082 // 4x unrolled loop
1083 size_t nBlocks = dataLen / 2;
1084 for (size_t i = 0; i < nBlocks; ++i)
1085 {
1086 out[2 * i + 0].gather(in, index0);
1087 out[2 * i + 1].gather(in, index1);
1088 index0 = index0 + 2;
1089 index1 = index1 + 2;
1090 }
1091
1092 // spillover loop
1093 for (size_t i = 2 * nBlocks; i < dataLen; ++i)
1094 {
1095 out[i].gather(in, index0);
1096 index0 = index0 + 1;
1097 }
1098}
1099
1100inline void deinterleave_unalign_store(
1101 const std::vector<sse2Float4, allocator<sse2Float4>> &in,
1102 const std::uint32_t dataLen, double *out)
1103{
1104 alignas(sse2Float4::alignment) sse2Float4::scalarArray tmp;
1105 for (size_t i = 0; i < dataLen; ++i)
1106 {
1107 in[i].store(tmp);
1108 out[i] = tmp[0];
1109 out[i + dataLen] = tmp[1];
1110 out[i + 2 * dataLen] = tmp[2];
1111 out[i + 3 * dataLen] = tmp[3];
1112 }
1113}
1114
1115inline void deinterleave_store(
1116 const std::vector<sse2Float4, allocator<sse2Float4>> &in,
1117 std::uint32_t dataLen, float *out)
1118{
1119 alignas(sse2Float4::alignment) sse2Float4::scalarIndexType tmp[4] = {
1120 0, dataLen, 2 * dataLen, 3 * dataLen};
1121 using index_t = sse2Int4<sse2Float4::scalarIndexType>;
1122 index_t index0(tmp);
1123
1124 for (size_t i = 0; i < dataLen; ++i)
1125 {
1126 in[i].scatter(out, index0);
1127 index0 = index0 + 1;
1128 }
1129}
1130
1131////////////////////////////////////////////////////////////////////////////////
1132
1133// mask type
1134// mask is a int type with special properties (broad boolean vector)
1135// broad boolean vectors defined and allowed values are:
1136// false=0x0 and true=0xFFFFFFFF
1137//
1138// VERY LIMITED SUPPORT...just enough to make cubic eos work...
1139//
1140struct sse2Mask2 : sse2Long2<std::uint64_t>
1141{
1142 // bring in ctors
1143 using sse2Long2::sse2Long2;
1144
1145 static constexpr scalarType true_v = -1;
1146 static constexpr scalarType false_v = 0;
1147};
1148
1149inline sse2Mask2 operator>(sse2Double2 lhs, sse2Double2 rhs)
1150{
1151 return reinterpret_cast<__m128i>(
1152 _mm_cmp_pd(lhs._data, rhs._data, _CMP_GT_OQ));
1153}
1154
1155inline bool operator&&(sse2Mask2 lhs, bool rhs)
1156{
1157 bool tmp = _mm_testc_si128(lhs._data, _mm_set1_epi64x(sse2Mask2::true_v));
1158
1159 return tmp && rhs;
1160}
1161
1162struct sse2Mask4 : sse2Int4<std::uint32_t>
1163{
1164 // bring in ctors
1165 using sse2Int4::sse2Int4;
1166
1167 static constexpr scalarType true_v = -1;
1168 static constexpr scalarType false_v = 0;
1169};
1170
1171inline sse2Mask4 operator>(sse2Float4 lhs, sse2Float4 rhs)
1172{
1173 return reinterpret_cast<__m128i>(_mm_cmp_ps(rhs._data, lhs._data, 1));
1174}
1175
1176inline bool operator&&(sse2Mask4 lhs, bool rhs)
1177{
1178 bool tmp = _mm_testc_si128(lhs._data, _mm_set1_epi64x(sse2Mask4::true_v));
1179
1180 return tmp && rhs;
1181}
1182
1183} // namespace tinysimd
1184
1185#endif // defined(__SSE2__) && defined(NEKTAR_ENABLE_SIMD_SSE2)
1186#endif
std::vector< double > p(NPUPPER)
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
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
scalarT< T > sqrt(scalarT< T > in)
Definition scalar.hpp:290
scalarT< T > operator+(scalarT< T > lhs, scalarT< T > rhs)
Definition scalar.hpp:214