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