Nektar++
Loading...
Searching...
No Matches
avx512.hpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: avx512.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 avx512 extension.
32//
33///////////////////////////////////////////////////////////////////////////////
34
35#ifndef NEKTAR_LIB_LIBUTILITES_SIMDLIB_AVX512_H
36#define NEKTAR_LIB_LIBUTILITES_SIMDLIB_AVX512_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 "avx2.hpp"
46#include "traits.hpp"
47#include <vector>
48
49namespace tinysimd::abi
50{
51
52template <typename scalarType, int width = 0> struct avx512
53{
54 using type = void;
55};
56
57} // namespace tinysimd::abi
58
59#if defined(__AVX512F__) && defined(NEKTAR_ENABLE_SIMD_AVX512)
60
61namespace tinysimd
62{
63
64// forward declaration of concrete types
65template <typename T> struct avx512Long8;
66template <typename T> struct avx512Int16;
67struct avx512Double8;
68struct avx512Float16;
69struct avx512Mask8;
70struct avx512Mask16;
71
72namespace abi
73{
74
75template <> struct avx512<double>
76{
77 using type = avx512Double8;
78};
79template <> struct avx512<float>
80{
81 using type = avx512Float16;
82};
83// generic index mapping
84// assumes index type width same as floating point type
85template <> struct avx512<std::int64_t>
86{
87 using type = avx512Long8<std::int64_t>;
88};
89template <> struct avx512<std::uint64_t>
90{
91 using type = avx512Long8<std::uint64_t>;
92};
93#if defined(__APPLE__)
94template <> struct avx512<std::size_t>
95{
96 using type = avx512Long8<std::size_t>;
97};
98#endif
99template <> struct avx512<std::int32_t>
100{
101 using type = avx512Int16<std::int32_t>;
102};
103template <> struct avx512<std::uint32_t>
104{
105 using type = avx512Int16<std::uint32_t>;
106};
107// specialized index mapping
108template <> struct avx512<std::int64_t, 8>
109{
110 using type = avx512Long8<std::int64_t>;
111};
112template <> struct avx512<std::uint64_t, 8>
113{
114 using type = avx512Long8<std::uint64_t>;
115};
116#if defined(__APPLE__)
117template <> struct avx512<std::size_t, 8>
118{
119 using type = avx512Long8<std::size_t>;
120};
121#endif
122template <> struct avx512<std::int32_t, 8>
123{
124 using type = avx2Int8<std::int32_t>;
125};
126template <> struct avx512<std::uint32_t, 8>
127{
128 using type = avx2Int8<std::uint32_t>;
129};
130template <> struct avx512<std::int32_t, 16>
131{
132 using type = avx512Int16<std::int32_t>;
133};
134template <> struct avx512<std::uint32_t, 16>
135{
136 using type = avx512Int16<std::uint32_t>;
137};
138// bool mapping
139template <> struct avx512<bool, 8>
140{
141 using type = avx512Mask8;
142};
143template <> struct avx512<bool, 16>
144{
145 using type = avx512Mask16;
146};
147
148} // namespace abi
149
150// concrete types
151
152// could add enable if to allow only unsigned long and long...
153template <typename T> struct avx512Int16
154{
155 static_assert(std::is_integral_v<T> && sizeof(T) == 4,
156 "4 bytes Integral required.");
157
158 static constexpr unsigned int width = 16;
159 static constexpr unsigned int alignment = 64;
160
161 using scalarType = T;
162 using vectorType = __m512i;
163 using scalarArray = scalarType[width];
164
165 // storage
166 vectorType _data;
167
168 // ctors
169 inline avx512Int16() = default;
170 inline avx512Int16(const avx512Int16 &rhs) = default;
171 inline avx512Int16(const vectorType &rhs) : _data(rhs)
172 {
173 }
174 inline avx512Int16(const scalarType rhs)
175 {
176 _data = _mm512_set1_epi32(rhs);
177 }
178 explicit inline avx512Int16(scalarArray &rhs)
179 {
180 _data = _mm512_load_epi32(rhs);
181 }
182
183 // copy assignment
184 inline avx512Int16 &operator=(const avx512Int16 &) = default;
185
186 // store
187 inline void store(scalarType *p) const
188 {
189 _mm512_store_epi32(p, _data);
190 }
191
192 template <class flag,
193 typename std::enable_if<is_requiring_alignment_v<flag> &&
194 !is_streaming_v<flag>,
195 bool>::type = 0>
196 inline void store(scalarType *p, flag) const
197 {
198 _mm512_store_epi32(p, _data);
199 }
200
201 template <class flag, typename std::enable_if<
202 !is_requiring_alignment_v<flag>, bool>::type = 0>
203 inline void store(scalarType *p, flag) const
204 {
205 _mm512_storeu_epi32(p, _data);
206 }
207
208 inline void load(const scalarType *p)
209 {
210 _data = _mm512_load_epi32(p);
211 }
212
213 template <class flag,
214 typename std::enable_if<is_requiring_alignment_v<flag> &&
215 !is_streaming_v<flag>,
216 bool>::type = 0>
217 inline void load(const scalarType *p, flag)
218 {
219 _data = _mm512_load_epi32(p);
220 }
221
222 template <class flag, typename std::enable_if<
223 !is_requiring_alignment_v<flag>, bool>::type = 0>
224 inline void load(const scalarType *p, flag)
225 {
226 // even though the intel intrisic manual lists
227 // __m512i _mm512_loadu_epi64 (void const* mem_addr)
228 // it is not implemented in some compilers (gcc)
229 // since this is a bitwise load with no extension
230 // the following instruction is equivalent
231 _data = _mm512_loadu_si512(p);
232 }
233
234 inline void broadcast(const scalarType rhs)
235 {
236 _data = _mm512_set1_epi32(rhs);
237 }
238
239 // subscript
240 // subscript operators are convienient but expensive
241 // should not be used in optimized kernels
242 inline scalarType operator[](size_t i) const
243 {
244 alignas(alignment) scalarArray tmp;
245 store(tmp, is_aligned);
246 return tmp[i];
247 }
248
249 inline scalarType &operator[](size_t i)
250 {
251 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
252 return tmp[i];
253 }
254};
255
256template <typename T>
257inline avx512Int16<T> operator+(avx512Int16<T> lhs, avx512Int16<T> rhs)
258{
259 return _mm512_add_epi32(lhs._data, rhs._data);
260}
261
262template <typename T, typename U,
263 typename = typename std::enable_if<std::is_arithmetic_v<U>>::type>
264inline avx512Int16<T> operator+(avx512Int16<T> lhs, U rhs)
265{
266 return _mm512_add_epi32(lhs._data, _mm512_set1_epi32(rhs));
267}
268
269////////////////////////////////////////////////////////////////////////////////
270
271template <typename T> struct avx512Long8
272{
273 static_assert(std::is_integral_v<T> && sizeof(T) == 8,
274 "8 bytes Integral required.");
275
276 static constexpr unsigned int width = 8;
277 static constexpr unsigned int alignment = 64;
278
279 using scalarType = T;
280 using vectorType = __m512i;
281 using scalarArray = scalarType[width];
282
283 // storage
284 vectorType _data;
285
286 // ctors
287 inline avx512Long8() = default;
288 inline avx512Long8(const avx512Long8 &rhs) = default;
289 inline avx512Long8(const vectorType &rhs) : _data(rhs)
290 {
291 }
292 inline avx512Long8(const scalarType rhs)
293 {
294 _data = _mm512_set1_epi64(rhs);
295 }
296 explicit inline avx512Long8(scalarArray &rhs)
297 {
298 _data = _mm512_load_epi64(rhs);
299 }
300
301 // copy assignment
302 inline avx512Long8 &operator=(const avx512Long8 &) = default;
303
304 // store
305 inline void store(scalarType *p) const
306 {
307 _mm512_store_epi64(p, _data);
308 }
309
310 template <class flag,
311 typename std::enable_if<is_requiring_alignment_v<flag> &&
312 !is_streaming_v<flag>,
313 bool>::type = 0>
314 inline void store(scalarType *p, flag) const
315 {
316 _mm512_store_epi64(p, _data);
317 }
318
319 template <class flag, typename std::enable_if<
320 !is_requiring_alignment_v<flag>, bool>::type = 0>
321 inline void store(scalarType *p, flag) const
322 {
323 _mm512_storeu_epi64(p, _data);
324 }
325
326 inline void load(const scalarType *p)
327 {
328 _data = _mm512_load_epi64(p);
329 }
330
331 template <class flag,
332 typename std::enable_if<is_requiring_alignment_v<flag> &&
333 !is_streaming_v<flag>,
334 bool>::type = 0>
335 inline void load(const scalarType *p, flag)
336 {
337 _data = _mm512_load_epi64(p);
338 }
339
340 template <class flag, typename std::enable_if<
341 !is_requiring_alignment_v<flag>, bool>::type = 0>
342 inline void load(const scalarType *p, flag)
343 {
344 // even though the intel intrisic manual lists
345 // __m512i _mm512_loadu_epi64 (void const* mem_addr)
346 // it is not implemented in some compilers (gcc)
347 // since this is a bitwise load with no extension
348 // the following instruction is equivalent
349 _data = _mm512_loadu_si512(p);
350 }
351
352 inline void broadcast(const scalarType rhs)
353 {
354 _data = _mm512_set1_epi64(rhs);
355 }
356
357 // subscript
358 // subscript operators are convienient but expensive
359 // should not be used in optimized kernels
360 inline scalarType operator[](size_t i) const
361 {
362 alignas(alignment) scalarArray tmp;
363 store(tmp, is_aligned);
364 return tmp[i];
365 }
366
367 inline scalarType &operator[](size_t i)
368 {
369 scalarType *tmp = reinterpret_cast<scalarType *>(&_data);
370 return tmp[i];
371 }
372};
373
374template <typename T>
375inline avx512Long8<T> operator+(avx512Long8<T> lhs, avx512Long8<T> rhs)
376{
377 return _mm512_add_epi64(lhs._data, rhs._data);
378}
379
380template <typename T, typename U,
381 typename = typename std::enable_if<std::is_arithmetic_v<U>>::type>
382inline avx512Long8<T> operator+(avx512Long8<T> lhs, U rhs)
383{
384 return _mm512_add_epi64(lhs._data, _mm512_set1_epi64(rhs));
385}
386
387////////////////////////////////////////////////////////////////////////////////
388
389struct avx512Double8
390{
391 static constexpr unsigned int width = 8;
392 static constexpr unsigned int alignment = 64;
393
394 using scalarType = double;
395 using scalarIndexType = std::uint64_t;
396 using vectorType = __m512d;
397 using scalarArray = scalarType[width];
398
399 // storage
400 vectorType _data;
401
402 // ctors
403 inline avx512Double8() = default;
404 inline avx512Double8(const avx512Double8 &rhs) = default;
405 inline avx512Double8(const vectorType &rhs) : _data(rhs)
406 {
407 }
408 inline avx512Double8(const scalarType rhs)
409 {
410 _data = _mm512_set1_pd(rhs);
411 }
412
413 // copy assignment
414 inline avx512Double8 &operator=(const avx512Double8 &) = default;
415
416 // store
417 inline void store(scalarType *p) const
418 {
419 _mm512_store_pd(p, _data);
420 }
421
422 template <class flag,
423 typename std::enable_if<is_requiring_alignment_v<flag> &&
424 !is_streaming_v<flag>,
425 bool>::type = 0>
426 inline void store(scalarType *p, flag) const
427 {
428 _mm512_store_pd(p, _data);
429 }
430
431 template <class flag, typename std::enable_if<
432 !is_requiring_alignment_v<flag>, bool>::type = 0>
433 inline void store(scalarType *p, flag) const
434 {
435 _mm512_storeu_pd(p, _data);
436 }
437
438 template <class flag,
439 typename std::enable_if<is_streaming_v<flag>, bool>::type = 0>
440 inline void store(scalarType *p, flag) const
441 {
442 _mm512_stream_pd(p, _data);
443 }
444
445 // load packed
446 inline void load(const scalarType *p)
447 {
448 _data = _mm512_load_pd(p);
449 }
450
451 template <class flag, typename std::enable_if<
452 is_requiring_alignment_v<flag>, bool>::type = 0>
453 inline void load(const scalarType *p, flag)
454 {
455 _data = _mm512_load_pd(p);
456 }
457
458 template <class flag, typename std::enable_if<
459 !is_requiring_alignment_v<flag>, bool>::type = 0>
460 inline void load(const scalarType *p, flag)
461 {
462 _data = _mm512_loadu_pd(p);
463 }
464
465 // broadcast
466 inline void broadcast(const scalarType rhs)
467 {
468 _data = _mm512_set1_pd(rhs);
469 }
470
471 // gather/scatter
472 template <typename T>
473 inline void gather(scalarType const *p, const avx2Int8<T> &indices)
474 {
475 _data = _mm512_i32gather_pd(indices._data, p, 8);
476 }
477
478 template <typename T>
479 inline void scatter(scalarType *out, const avx2Int8<T> &indices) const
480 {
481 _mm512_i32scatter_pd(out, indices._data, _data, 8);
482 }
483
484 template <typename T>
485 inline void gather(scalarType const *p, const avx512Long8<T> &indices)
486 {
487 _data = _mm512_i64gather_pd(indices._data, p, 8);
488 }
489
490 template <typename T>
491 inline void scatter(scalarType *out, const avx512Long8<T> &indices) const
492 {
493 _mm512_i64scatter_pd(out, indices._data, _data, 8);
494 }
495
496 // fma
497 // this = this + a * b
498 inline void fma(const avx512Double8 &a, const avx512Double8 &b)
499 {
500 _data = _mm512_fmadd_pd(a._data, b._data, _data);
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 // unary ops
520 inline void operator+=(avx512Double8 rhs)
521 {
522 _data = _mm512_add_pd(_data, rhs._data);
523 }
524
525 inline void operator-=(avx512Double8 rhs)
526 {
527 _data = _mm512_sub_pd(_data, rhs._data);
528 }
529
530 inline void operator*=(avx512Double8 rhs)
531 {
532 _data = _mm512_mul_pd(_data, rhs._data);
533 }
534
535 inline void operator/=(avx512Double8 rhs)
536 {
537 _data = _mm512_div_pd(_data, rhs._data);
538 }
539};
540
541inline avx512Double8 operator+(avx512Double8 lhs, avx512Double8 rhs)
542{
543 return _mm512_add_pd(lhs._data, rhs._data);
544}
545
546inline avx512Double8 operator-(avx512Double8 lhs, avx512Double8 rhs)
547{
548 return _mm512_sub_pd(lhs._data, rhs._data);
549}
550
551inline avx512Double8 operator-(avx512Double8 in)
552{
553 return _mm512_sub_pd(_mm512_set1_pd(-0.0), in._data);
554 // return _mm512_xor_pd(in._data, _mm512_set1_pd(-0.0));
555}
556
557inline avx512Double8 operator*(avx512Double8 lhs, avx512Double8 rhs)
558{
559 return _mm512_mul_pd(lhs._data, rhs._data);
560}
561
562inline avx512Double8 operator/(avx512Double8 lhs, avx512Double8 rhs)
563{
564 return _mm512_div_pd(lhs._data, rhs._data);
565}
566
567inline avx512Double8 sqrt(avx512Double8 in)
568{
569 return _mm512_sqrt_pd(in._data);
570}
571
572inline avx512Double8 abs(avx512Double8 in)
573{
574 return _mm512_abs_pd(in._data);
575}
576
577inline avx512Double8 min(avx512Double8 lhs, avx512Double8 rhs)
578{
579 return _mm512_min_pd(lhs._data, rhs._data);
580}
581
582inline avx512Double8 max(avx512Double8 lhs, avx512Double8 rhs)
583{
584 return _mm512_max_pd(lhs._data, rhs._data);
585}
586
587inline avx512Double8 log(avx512Double8 in)
588{
589#if defined(TINYSIMD_HAS_SVML)
590 return _mm512_log_pd(in._data);
591#else
592 // there is no avx512 log intrinsic
593 // this is a dreadful implementation and is simply a stop gap measure
594 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp;
595 in.store(tmp);
596 tmp[0] = std::log(tmp[0]);
597 tmp[1] = std::log(tmp[1]);
598 tmp[2] = std::log(tmp[2]);
599 tmp[3] = std::log(tmp[3]);
600 tmp[4] = std::log(tmp[4]);
601 tmp[5] = std::log(tmp[5]);
602 tmp[6] = std::log(tmp[6]);
603 tmp[7] = std::log(tmp[7]);
604 avx512Double8 ret;
605 ret.load(tmp);
606 return ret;
607#endif
608}
609
610inline void load_unalign_interleave(
611 const double *in, const std::uint32_t dataLen,
612 std::vector<avx512Double8, allocator<avx512Double8>> &out)
613{
614 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp;
615 for (size_t i = 0; i < dataLen; ++i)
616 {
617 tmp[0] = in[i];
618 tmp[1] = in[i + dataLen];
619 tmp[2] = in[i + 2 * dataLen];
620 tmp[3] = in[i + 3 * dataLen];
621 tmp[4] = in[i + 4 * dataLen];
622 tmp[5] = in[i + 5 * dataLen];
623 tmp[6] = in[i + 6 * dataLen];
624 tmp[7] = in[i + 7 * 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<avx512Double8, allocator<avx512Double8>> &out)
632{
633 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp;
634 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp1;
635 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp2;
636 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp3;
637
638 size_t nBlocks = dataLen / 4;
639 const avx512Double8 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 < avx512Double8::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 < avx512Double8::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<avx512Double8, allocator<avx512Double8>> &out)
674{
675
676 alignas(avx512Double8::alignment)
677 avx512Double8::scalarIndexType tmp[avx512Double8::width] = {
678 0, dataLen, 2 * dataLen, 3 * dataLen,
679 4 * dataLen, 5 * dataLen, 6 * dataLen, 7 * dataLen};
680
681 using index_t = avx512Long8<avx512Double8::scalarIndexType>;
682 index_t index0(tmp);
683 index_t index1 = index0 + 1;
684 index_t index2 = index0 + 2;
685 index_t index3 = index0 + 3;
686
687 // 4x unrolled loop
688 constexpr uint16_t unrl = 4;
689 size_t nBlocks = dataLen / unrl;
690 for (size_t i = 0; i < nBlocks; ++i)
691 {
692 out[unrl * i + 0].gather(in, index0);
693 out[unrl * i + 1].gather(in, index1);
694 out[unrl * i + 2].gather(in, index2);
695 out[unrl * i + 3].gather(in, index3);
696 index0 = index0 + unrl;
697 index1 = index1 + unrl;
698 index2 = index2 + unrl;
699 index3 = index3 + unrl;
700 }
701
702 // spillover loop
703 for (size_t i = unrl * nBlocks; i < dataLen; ++i)
704 {
705 out[i].gather(in, index0);
706 index0 = index0 + 1;
707 }
708}
709
711 const std::vector<avx512Double8, allocator<avx512Double8>> &in,
712 const std::uint32_t dataLen, double *out)
713{
714 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp;
715 for (size_t i = 0; i < dataLen; ++i)
716 {
717 in[i].store(tmp);
718 out[i] = tmp[0];
719 out[i + dataLen] = tmp[1];
720 out[i + 2 * dataLen] = tmp[2];
721 out[i + 3 * dataLen] = tmp[3];
722 out[i + 4 * dataLen] = tmp[4];
723 out[i + 5 * dataLen] = tmp[5];
724 out[i + 6 * dataLen] = tmp[6];
725 out[i + 7 * dataLen] = tmp[7];
726 }
727}
728
730 const std::vector<avx512Double8, allocator<avx512Double8>> &in,
731 const std::uint32_t dataLen, const std::uint32_t skipPads, double *out)
732{
733 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp;
734 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp1;
735 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp2;
736 alignas(avx512Double8::alignment) avx512Double8::scalarArray tmp3;
737
738 // 4x unrolled loop
739 size_t nBlocks = dataLen / 4;
740
741 for (size_t i = 0; i < nBlocks; ++i)
742 {
743 in[4 * i].store(tmp);
744 in[4 * i + 1].store(tmp1);
745 in[4 * i + 2].store(tmp2);
746 in[4 * i + 3].store(tmp3);
747 for (size_t j = 0; j < avx512Double8::width - skipPads; ++j)
748 {
749 out[j * dataLen + 4 * i] = tmp[j];
750 out[j * dataLen + 4 * i + 1] = tmp1[j];
751 out[j * dataLen + 4 * i + 2] = tmp2[j];
752 out[j * dataLen + 4 * i + 3] = tmp3[j];
753 }
754 }
755
756 // spill over loop
757 for (size_t i = nBlocks * 4; i < dataLen; ++i)
758 {
759 in[i].store(tmp);
760 for (size_t j = 0; j < avx512Double8::width - skipPads; ++j)
761 {
762 out[j * dataLen + i] = tmp[j];
763 }
764 }
765}
766
767inline void deinterleave_store(
768 const std::vector<avx512Double8, allocator<avx512Double8>> &in,
769 std::uint32_t dataLen, double *out)
770{
771 // size_t nBlocks = dataLen / 4;
772
773 alignas(avx512Double8::alignment)
774 avx512Double8::scalarIndexType tmp[avx512Double8::width] = {
775 0, dataLen, 2 * dataLen, 3 * dataLen,
776 4 * dataLen, 5 * dataLen, 6 * dataLen, 7 * dataLen};
777 using index_t = avx512Long8<avx512Double8::scalarIndexType>;
778 index_t index0(tmp);
779 for (size_t i = 0; i < dataLen; ++i)
780 {
781 in[i].scatter(out, index0);
782 index0 = index0 + 1;
783 }
784}
785
786struct avx512Float16
787{
788 static constexpr unsigned int width = 16;
789 static constexpr unsigned int alignment = 64;
790
791 using scalarType = float;
792 using scalarIndexType = std::uint32_t;
793 using vectorType = __m512;
794 using scalarArray = scalarType[width];
795
796 // storage
797 vectorType _data;
798
799 // ctors
800 inline avx512Float16() = default;
801 inline avx512Float16(const avx512Float16 &rhs) = default;
802 inline avx512Float16(const vectorType &rhs) : _data(rhs)
803 {
804 }
805 inline avx512Float16(const scalarType rhs)
806 {
807 _data = _mm512_set1_ps(rhs);
808 }
809
810 // copy assignment
811 inline avx512Float16 &operator=(const avx512Float16 &) = default;
812
813 // store
814 inline void store(scalarType *p) const
815 {
816 _mm512_store_ps(p, _data);
817 }
818
819 template <class flag,
820 typename std::enable_if<is_requiring_alignment_v<flag> &&
821 !is_streaming_v<flag>,
822 bool>::type = 0>
823 inline void store(scalarType *p, flag) const
824 {
825 _mm512_store_ps(p, _data);
826 }
827
828 template <class flag, typename std::enable_if<
829 !is_requiring_alignment_v<flag>, bool>::type = 0>
830 inline void store(scalarType *p, flag) const
831 {
832 _mm512_storeu_ps(p, _data);
833 }
834
835 template <class flag,
836 typename std::enable_if<is_streaming_v<flag>, bool>::type = 0>
837 inline void store(scalarType *p, flag) const
838 {
839 _mm512_stream_ps(p, _data);
840 }
841
842 // load packed
843 inline void load(const scalarType *p)
844 {
845 _data = _mm512_load_ps(p);
846 }
847
848 template <class flag, typename std::enable_if<
849 is_requiring_alignment_v<flag>, bool>::type = 0>
850 inline void load(const scalarType *p, flag)
851 {
852 _data = _mm512_load_ps(p);
853 }
854
855 template <class flag, typename std::enable_if<
856 !is_requiring_alignment_v<flag>, bool>::type = 0>
857 inline void load(const scalarType *p, flag)
858 {
859 _data = _mm512_loadu_ps(p);
860 }
861
862 // broadcast
863 inline void broadcast(const scalarType rhs)
864 {
865 _data = _mm512_set1_ps(rhs);
866 }
867
868 // gather/scatter
869 template <typename T>
870 inline void gather(scalarType const *p, const avx512Int16<T> &indices)
871 {
872 _data = _mm512_i32gather_ps(indices._data, p, sizeof(scalarType));
873 }
874
875 template <typename T>
876 inline void scatter(scalarType *out, const avx512Int16<T> &indices) const
877 {
878 _mm512_i32scatter_ps(out, indices._data, _data, sizeof(scalarType));
879 }
880
881 // fma
882 // this = this + a * b
883 inline void fma(const avx512Float16 &a, const avx512Float16 &b)
884 {
885 _data = _mm512_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+=(avx512Float16 rhs)
905 {
906 _data = _mm512_add_ps(_data, rhs._data);
907 }
908
909 inline void operator-=(avx512Float16 rhs)
910 {
911 _data = _mm512_sub_ps(_data, rhs._data);
912 }
913
914 inline void operator*=(avx512Float16 rhs)
915 {
916 _data = _mm512_mul_ps(_data, rhs._data);
917 }
918
919 inline void operator/=(avx512Float16 rhs)
920 {
921 _data = _mm512_div_ps(_data, rhs._data);
922 }
923};
924
925inline avx512Float16 operator+(avx512Float16 lhs, avx512Float16 rhs)
926{
927 return _mm512_add_ps(lhs._data, rhs._data);
928}
929
930inline avx512Float16 operator-(avx512Float16 lhs, avx512Float16 rhs)
931{
932 return _mm512_sub_ps(lhs._data, rhs._data);
933}
934
935inline avx512Float16 operator-(avx512Float16 in)
936{
937 return _mm512_sub_ps(_mm512_set1_ps(-0.0), in._data);
938 // return _mm512_xor_ps(in._data, _mm512_set1_ps(-0.0));
939}
940
941inline avx512Float16 operator*(avx512Float16 lhs, avx512Float16 rhs)
942{
943 return _mm512_mul_ps(lhs._data, rhs._data);
944}
945
946inline avx512Float16 operator/(avx512Float16 lhs, avx512Float16 rhs)
947{
948 return _mm512_div_ps(lhs._data, rhs._data);
949}
950
951inline avx512Float16 sqrt(avx512Float16 in)
952{
953 return _mm512_sqrt_ps(in._data);
954}
955
956inline avx512Float16 abs(avx512Float16 in)
957{
958 return _mm512_abs_ps(in._data);
959}
960
961inline avx512Float16 min(avx512Float16 lhs, avx512Float16 rhs)
962{
963 return _mm512_min_ps(lhs._data, rhs._data);
964}
965
966inline avx512Float16 max(avx512Float16 lhs, avx512Float16 rhs)
967{
968 return _mm512_max_ps(lhs._data, rhs._data);
969}
970
971inline avx512Float16 log(avx512Float16 in)
972{
973#if defined(TINYSIMD_HAS_SVML)
974 return _mm512_log_ps(in._data);
975#else
976 // there is no avx512 log intrinsic
977 // this is a dreadful implementation and is simply a stop gap measure
978 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp;
979 in.store(tmp);
980 tmp[0] = std::log(tmp[0]);
981 tmp[1] = std::log(tmp[1]);
982 tmp[2] = std::log(tmp[2]);
983 tmp[3] = std::log(tmp[3]);
984 tmp[4] = std::log(tmp[4]);
985 tmp[5] = std::log(tmp[5]);
986 tmp[6] = std::log(tmp[6]);
987 tmp[7] = std::log(tmp[7]);
988 tmp[8] = std::log(tmp[8]);
989 tmp[9] = std::log(tmp[9]);
990 tmp[10] = std::log(tmp[10]);
991 tmp[11] = std::log(tmp[11]);
992 tmp[12] = std::log(tmp[12]);
993 tmp[13] = std::log(tmp[13]);
994 tmp[14] = std::log(tmp[14]);
995 tmp[15] = std::log(tmp[15]);
996 avx512Float16 ret;
997 ret.load(tmp);
998 return ret;
999#endif
1000}
1001
1002inline void load_unalign_interleave(
1003 const double *in, const std::uint32_t dataLen,
1004 std::vector<avx512Float16, allocator<avx512Float16>> &out)
1005{
1006 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp;
1007 for (size_t i = 0; i < dataLen; ++i)
1008 {
1009 tmp[0] = in[i];
1010 tmp[1] = in[i + dataLen];
1011 tmp[2] = in[i + 2 * dataLen];
1012 tmp[3] = in[i + 3 * dataLen];
1013 tmp[4] = in[i + 4 * dataLen];
1014 tmp[5] = in[i + 5 * dataLen];
1015 tmp[6] = in[i + 6 * dataLen];
1016 tmp[7] = in[i + 7 * dataLen];
1017 tmp[8] = in[i + 8 * dataLen];
1018 tmp[9] = in[i + 9 * dataLen];
1019 tmp[10] = in[i + 10 * dataLen];
1020 tmp[11] = in[i + 11 * dataLen];
1021 tmp[12] = in[i + 12 * dataLen];
1022 tmp[13] = in[i + 13 * dataLen];
1023 tmp[14] = in[i + 14 * dataLen];
1024 tmp[15] = in[i + 15 * dataLen];
1025 out[i].load(tmp);
1026 }
1027}
1028
1030 const double *in, const std::uint32_t dataLen, const std::uint32_t skipPads,
1031 std::vector<avx512Float16, allocator<avx512Float16>> &out)
1032{
1033 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp;
1034 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp1;
1035 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp2;
1036 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp3;
1037
1038 size_t nBlocks = dataLen / 4;
1039 const avx512Float16 zero{0.0};
1040
1041 for (size_t i = 0; i < nBlocks; ++i)
1042 {
1043 zero.store(tmp);
1044 zero.store(tmp1);
1045 zero.store(tmp2);
1046 zero.store(tmp3);
1047 for (size_t j = 0; j < avx512Float16::width - skipPads; ++j)
1048 {
1049 tmp[j] = in[j * dataLen + 4 * i];
1050 tmp1[j] = in[j * dataLen + 4 * i + 1];
1051 tmp2[j] = in[j * dataLen + 4 * i + 2];
1052 tmp3[j] = in[j * dataLen + 4 * i + 3];
1053 }
1054 out[4 * i].load(tmp);
1055 out[4 * i + 1].load(tmp1);
1056 out[4 * i + 2].load(tmp2);
1057 out[4 * i + 3].load(tmp3);
1058 }
1059
1060 for (size_t i = nBlocks * 4; i < dataLen; ++i)
1061 {
1062 zero.store(tmp);
1063 for (size_t j = 0; j < avx512Float16::width - skipPads; ++j)
1064 {
1065 tmp[j] = in[i + j * dataLen];
1066 }
1067 out[i].load(tmp);
1068 }
1069}
1070
1071inline void load_interleave(
1072 const float *in, std::uint32_t dataLen,
1073 std::vector<avx512Float16, allocator<avx512Float16>> &out)
1074{
1075
1076 alignas(avx512Float16::alignment)
1077 avx512Float16::scalarIndexType tmp[avx512Float16::width] = {
1078 0,
1079 dataLen,
1080 2 * dataLen,
1081 3 * dataLen,
1082 4 * dataLen,
1083 5 * dataLen,
1084 6 * dataLen,
1085 7 * dataLen,
1086 8 * dataLen,
1087 9 * dataLen,
1088 10 * dataLen,
1089 11 * dataLen,
1090 12 * dataLen,
1091 13 * dataLen,
1092 14 * dataLen,
1093 15 * dataLen};
1094
1095 using index_t = avx512Int16<avx512Float16::scalarIndexType>;
1096 index_t index0(tmp);
1097 index_t index1 = index0 + 1;
1098 index_t index2 = index0 + 2;
1099 index_t index3 = index0 + 3;
1100
1101 // 4x unrolled loop
1102 constexpr uint16_t unrl = 4;
1103 size_t nBlocks = dataLen / unrl;
1104 for (size_t i = 0; i < nBlocks; ++i)
1105 {
1106 out[unrl * i + 0].gather(in, index0);
1107 out[unrl * i + 1].gather(in, index1);
1108 out[unrl * i + 2].gather(in, index2);
1109 out[unrl * i + 3].gather(in, index3);
1110 index0 = index0 + unrl;
1111 index1 = index1 + unrl;
1112 index2 = index2 + unrl;
1113 index3 = index3 + unrl;
1114 }
1115
1116 // spillover loop
1117 for (size_t i = unrl * nBlocks; i < dataLen; ++i)
1118 {
1119 out[i].gather(in, index0);
1120 index0 = index0 + 1;
1121 }
1122}
1123
1124inline void deinterleave_unalign_store(
1125 const std::vector<avx512Float16, allocator<avx512Float16>> &in,
1126 const std::uint32_t dataLen, double *out)
1127{
1128 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp;
1129 for (size_t i = 0; i < dataLen; ++i)
1130 {
1131 in[i].store(tmp);
1132 out[i] = tmp[0];
1133 out[i + dataLen] = tmp[1];
1134 out[i + 2 * dataLen] = tmp[2];
1135 out[i + 3 * dataLen] = tmp[3];
1136 out[i + 4 * dataLen] = tmp[4];
1137 out[i + 5 * dataLen] = tmp[5];
1138 out[i + 6 * dataLen] = tmp[6];
1139 out[i + 7 * dataLen] = tmp[7];
1140 out[i + 8 * dataLen] = tmp[8];
1141 out[i + 9 * dataLen] = tmp[9];
1142 out[i + 10 * dataLen] = tmp[10];
1143 out[i + 11 * dataLen] = tmp[11];
1144 out[i + 12 * dataLen] = tmp[12];
1145 out[i + 13 * dataLen] = tmp[13];
1146 out[i + 14 * dataLen] = tmp[14];
1147 out[i + 15 * dataLen] = tmp[15];
1148 }
1149}
1150
1152 const std::vector<avx512Float16, allocator<avx512Float16>> &in,
1153 const std::uint32_t dataLen, const std::uint32_t skipPads, double *out)
1154{
1155 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp;
1156 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp1;
1157 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp2;
1158 alignas(avx512Float16::alignment) avx512Float16::scalarArray tmp3;
1159
1160 // 4x unrolled loop
1161 size_t nBlocks = dataLen / 4;
1162
1163 for (size_t i = 0; i < nBlocks; ++i)
1164 {
1165 in[4 * i].store(tmp);
1166 in[4 * i + 1].store(tmp1);
1167 in[4 * i + 2].store(tmp2);
1168 in[4 * i + 3].store(tmp3);
1169 for (size_t j = 0; j < avx512Float16::width - skipPads; ++j)
1170 {
1171 out[j * dataLen + 4 * i] = tmp[j];
1172 out[j * dataLen + 4 * i + 1] = tmp1[j];
1173 out[j * dataLen + 4 * i + 2] = tmp2[j];
1174 out[j * dataLen + 4 * i + 3] = tmp3[j];
1175 }
1176 }
1177
1178 // spill over loop
1179 for (size_t i = nBlocks * 4; i < dataLen; ++i)
1180 {
1181 in[i].store(tmp);
1182 for (size_t j = 0; j < avx512Float16::width - skipPads; ++j)
1183 {
1184 out[j * dataLen + i] = tmp[j];
1185 }
1186 }
1187}
1188
1189inline void deinterleave_store(
1190 const std::vector<avx512Float16, allocator<avx512Float16>> &in,
1191 std::uint32_t dataLen, float *out)
1192{
1193 // size_t nBlocks = dataLen / 4;
1194
1195 alignas(avx512Float16::alignment)
1196 avx512Float16::scalarIndexType tmp[avx512Float16::width] = {
1197 0,
1198 dataLen,
1199 2 * dataLen,
1200 3 * dataLen,
1201 4 * dataLen,
1202 5 * dataLen,
1203 6 * dataLen,
1204 7 * dataLen,
1205 8 * dataLen,
1206 9 * dataLen,
1207 10 * dataLen,
1208 11 * dataLen,
1209 12 * dataLen,
1210 13 * dataLen,
1211 14 * dataLen,
1212 15 * dataLen};
1213 using index_t = avx512Int16<avx512Float16::scalarIndexType>;
1214
1215 index_t index0(tmp);
1216 for (size_t i = 0; i < dataLen; ++i)
1217 {
1218 in[i].scatter(out, index0);
1219 index0 = index0 + 1;
1220 }
1221}
1222
1223////////////////////////////////////////////////////////////////////////////////
1224
1225// mask type
1226// mask is a int type with special properties (broad boolean vector)
1227// broad boolean vectors defined and allowed values are:
1228// false=0x0 and true=0xFFFFFFFF
1229//
1230// VERY LIMITED SUPPORT...just enough to make cubic eos work...
1231//
1232struct avx512Mask8 : avx512Long8<std::uint64_t>
1233{
1234 // bring in ctors
1235 using avx512Long8::avx512Long8;
1236
1237 static constexpr scalarType true_v = -1;
1238 static constexpr scalarType false_v = 0;
1239};
1240
1241inline avx512Mask8 operator>(avx512Double8 lhs, avx512Double8 rhs)
1242{
1243 __mmask8 mask = _mm512_cmp_pd_mask(lhs._data, rhs._data, _CMP_GT_OQ);
1244 return _mm512_maskz_set1_epi64(mask, avx512Mask8::true_v);
1245}
1246
1247inline bool operator&&(avx512Mask8 lhs, bool rhs)
1248{
1249 __m512i val_true = _mm512_set1_epi64(avx512Mask8::true_v);
1250 __mmask8 mask = _mm512_test_epi64_mask(lhs._data, val_true);
1251 unsigned int tmp = _cvtmask16_u32(mask);
1252 return tmp && rhs;
1253}
1254
1255struct avx512Mask16 : avx512Int16<std::uint32_t>
1256{
1257 // bring in ctors
1258 using avx512Int16::avx512Int16;
1259
1260 static constexpr scalarType true_v = -1;
1261 static constexpr scalarType false_v = 0;
1262};
1263
1264inline avx512Mask16 operator>(avx512Float16 lhs, avx512Float16 rhs)
1265{
1266 __mmask16 mask = _mm512_cmp_ps_mask(lhs._data, rhs._data, _CMP_GT_OQ);
1267 return _mm512_maskz_set1_epi32(mask, avx512Mask16::true_v);
1268}
1269
1270inline bool operator&&(avx512Mask16 lhs, bool rhs)
1271{
1272 __m512i val_true = _mm512_set1_epi32(avx512Mask16::true_v);
1273 __mmask16 mask = _mm512_test_epi32_mask(lhs._data, val_true);
1274 unsigned int tmp = _cvtmask16_u32(mask);
1275 return tmp && rhs;
1276}
1277
1278} // namespace tinysimd
1279
1280#endif // defined(__avx512__)
1281
1282#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