SuperDex Physics C++ API
Loading...
Searching...
No Matches
x64_simd_double_4_inl.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include "x64_simd_inl.h" // for IntelliSense
20
21#if MOCHI_USE_SIMD && MOCHI_ARCH_X64_AVX2
22
23namespace superdex {
24
25/***********************************************************************************************
26 Simd<double, 4>
27*/
28template <>
29class Simd<double, 4> {
30 public:
31 MOCHI_NATIVE_SIMD_IMPL_BOILERPLATE(double, 4, __m256d);
32 Simd(double a, double b, double c = 0.0, double d = 0.0)
33 : raw(_mm256_set_pd(d, c, b, a)) {} // AVX
34 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
35 Simd(U a) : raw(_mm256_set1_pd(a)) {} // AVX
36
37 // Joint two Vec2d into a single Vec4d
38 Simd(Simd<double, 2> const& low, Simd<double, 2> const& high)
39 : raw(_mm256_set_m128d(high.raw, low.raw)) {} // AVX
40
41 template <int i>
42 [[nodiscard]] static MOCHI_FORCE_INLINE double Get(Simd v) {
43 static_assert(i >= 0 && i < 4, "Index out of range");
44 if constexpr (i == 0) {
45 return _mm256_cvtsd_f64(v.raw); // AVX
46 } else if constexpr (i == 1) {
47 return _mm256_cvtsd_f64(_mm256_shuffle_pd(v.raw, v.raw, 0x01)); // AVX, AVX
48 } else if constexpr (i == 2) {
49 return _mm256_cvtsd_f64(_mm256_permute2f128_pd(v.raw, v.raw, 0x01)); // AVX, AVX
50 } else if constexpr (i == 3) {
51 auto tmp = _mm256_permute2f128_pd(v.raw, v.raw, 0x01); // AVX
52 return _mm256_cvtsd_f64(_mm256_shuffle_pd(tmp, tmp, 0x01)); // AVX, AVX
53 }
54 }
55
56 [[nodiscard]] static MOCHI_FORCE_INLINE double Get(Simd v, int i) {
57 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
58#if MOCHI_COMPILER_MSVC
59 return v.raw.m256d_f64[i];
60#else
61 switch (i) { // clang-format off
62 case 0: return Get<0>(v);
63 case 1: return Get<1>(v);
64 case 2: return Get<2>(v);
65 case 3: return Get<3>(v);
66 MOCHI_UNLIKELY default: return 0.0;
67 } // clang-format on
68#endif
69 }
70
71 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Set(Simd v, int i, Scalar value) {
72 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
73#if MOCHI_COMPILER_MSVC
74 auto result = v;
75 result.raw.m256d_f64[i] = value;
76 return result;
77#else
78 static constexpr __m256i kMasks[] = {
79 {-1LL, 0LL, 0LL, 0LL}, {0LL, -1LL, 0LL, 0LL}, {0LL, 0LL, -1LL, 0LL}, {0LL, 0LL, 0LL, -1LL}};
80 return _mm256_blendv_pd(v.raw, _mm256_set1_pd(value), _mm256_castsi256_pd(kMasks[i])); // AVX
81#endif
82 }
83
84 template <int i>
85 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Set(Simd v, Scalar value) {
86 static_assert(i >= 0 && i < kSize, "Index out of range");
87 return Set(v, i, value);
88 }
89
90 template <int iHalf>
91 [[nodiscard]] static MOCHI_FORCE_INLINE Simd<double, 2> GetHalf(Simd a) {
92 static_assert(iHalf == 0 || iHalf == 1);
93 return _mm256_extractf128_pd(a.raw, iHalf); // AVX
94 }
95
96 [[nodiscard]] static MOCHI_FORCE_INLINE Simd AsPoint(Simd a) {
97 // Replace the 3rd component with an integer that has the same bits as 1.0.
98 auto araw = _mm256_castpd_si256(a.raw); // AVX
99 auto v = _mm256_insert_epi64(araw, 0x3FF0000000000000LL, 3); // AVX
100 return _mm256_castsi256_pd(v); // AVX
101 }
102
103 [[nodiscard]] static MOCHI_FORCE_INLINE Simd AsDirection(Simd a) {
104 // Replace the 3rd component with an integer that has the same bits as 0.0.
105 auto araw = _mm256_castpd_si256(a.raw); // AVX
106 auto v = _mm256_insert_epi64(araw, 0, 3); // AVX
107 return _mm256_castsi256_pd(v); // AVX
108 }
109
110 template <int x, int y, int z, int w>
111 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Blend(Simd a, Simd b) {
112 static_assert(
113 x >= 0 && x < 2 && y >= 0 && y < 2 && z >= 0 && z < 2 && w >= 0 && w < 2,
114 "invalid blend index");
115 if constexpr (x == 0 && y == 0 && z == 0 && w == 0) {
116 return a;
117 } else if constexpr (x == 1 && y == 1 && z == 1 && w == 1) {
118 return b;
119 } else {
120 return _mm256_blend_pd(a.raw, b.raw, x | (y << 1) | (z << 2) | (w << 3)); // SSE4.1
121 }
122 }
123
124 template <int N>
125 [[nodiscard]] static MOCHI_FORCE_INLINE bool AllTrue(Simd v) {
126 static_assert(N >= 1 && N <= kSize, "Unsupported N");
127 int mask = GetMSBitMask(v); // One bit for each byte in the vector
128 if constexpr (N == kSize) {
129 return mask == 0xFFFFFFFF;
130 } else {
131 int constexpr kNumBits = N * sizeof(Scalar);
132 auto constexpr kMustBeSet = (1UL << kNumBits) - 1;
133 return (mask & kMustBeSet) == kMustBeSet;
134 }
135 }
136
137 template <int N>
138 [[nodiscard]] static MOCHI_FORCE_INLINE bool AnyTrue(Simd v) {
139 static_assert(N >= 1 && N <= kSize, "Unsupported N");
140 int mask = GetMSBitMask(v); // One bit for each byte in the vector
141 if constexpr (N == kSize) {
142 return mask != 0;
143 } else {
144 int constexpr kNumBits = N * sizeof(Scalar);
145 auto constexpr kMayBeSet = (1UL << kNumBits) - 1;
146 return (mask & kMayBeSet) != 0;
147 }
148 }
149
150 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Scalar const* p) {
151 return _mm256_broadcast_sd(p); // AVX
152 }
153
154 template <int i>
155 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Simd v) {
156 return Shuffle<i, i, i, i>(v);
157 }
158
159 template <int N = kSize>
160 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load([[maybe_unused]] Scalar const* ptr) {
161 static_assert(N >= 0 && N <= 4);
162 if constexpr (N == 0) {
163 return Simd::Zero();
164 } else if constexpr (N == 1) {
165 return Simd{*ptr, 0.0};
166 } else if constexpr (N == 2) {
167 __m256i mask = _mm256_set_epi64x(0, 0, -1, -1); // AVX
168 return _mm256_maskload_pd(ptr, mask); // AVX
169 } else if constexpr (N == 3) {
170 __m256i mask = _mm256_set_epi64x(0, -1, -1, -1); // AVX
171 return _mm256_maskload_pd(ptr, mask); // AVX
172 } else {
173 return _mm256_loadu_pd(ptr); // AVX
174 }
175 }
176
177#if MOCHI_COMPILER_MSVC
178 // MSVC defines __m128i as a union. Byte arrays are required to initialize it this way.
179 // clang-format off
180 static constexpr __m256i kLoadMasks[] = {
181 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
182 {-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
183 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
184 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0},
185 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}};
186 // clang-format on
187#else
188 // GCC and Clang define __m256i as 'long long' with special attributes.
189 static constexpr __m256i kLoadMasks[] = {
190 {0LL, 0LL, 0LL, 0LL},
191 {-1LL, 0LL, 0LL, 0LL},
192 {-1LL, -1LL, 0LL, 0LL},
193 {-1LL, -1LL, -1LL, 0LL},
194 {-1LL, -1LL, -1LL, -1LL}};
195#endif
196
197 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load(Scalar const* ptr, int n) {
198 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
199 return _mm256_maskload_pd(ptr, kLoadMasks[n]); // AVX
200 }
201
202 [[nodiscard]] static Simd LoadIndexed(Scalar const* ptr, Simd<int, 4> const& indices) {
203 return _mm256_i32gather_pd(ptr, indices.raw, sizeof(double)); // AVX2
204 }
205
206 [[nodiscard]] static Simd LoadIndexed(Scalar const* ptr, Simd<int64_t, 4> const& indices) {
207 return _mm256_i64gather_pd(ptr, indices.raw, sizeof(double)); // AVX2
208 }
209
210 template <int kTupleCount = kSize>
211 MOCHI_FORCE_INLINE static void
212 LoadTransposed(Scalar const* ptr, Simd& out0, Simd& out1, Simd& out2) {
213 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
214 constexpr int kCount0 = Clamp(kTupleCount * 3 - 0, 0, 4);
215 constexpr int kCount1 = Clamp(kTupleCount * 3 - 4, 0, 4);
216 constexpr int kCount2 = Clamp(kTupleCount * 3 - 8, 0, 4);
217 auto a = Simd::Load<kCount0>(ptr).raw; // [0,1,2,3]
218 auto b = Simd::Load<kCount1>(kCount1 == 0 ? ptr : ptr + 4).raw; // [4,5,6,7]
219 auto c = Simd::Load<kCount2>(kCount2 == 0 ? ptr : ptr + 8).raw; // [8,9,10,11]
220
221 auto d = _mm256_blend_pd(a, b, 0b0100); // [0,_,6,3]
222 d = _mm256_blend_pd(d, c, 0b0010); // [0,9,6,3]
223 auto e = _mm256_permute2f128_pd(d, d, 0x01); // [6,3,0,9]
224 out0.raw = _mm256_blend_pd(d, e, 0b1010); // [0,3,6,9]
225
226 d = _mm256_blend_pd(a, b, 0b1001); // [4,1,_,7]
227 d = _mm256_blend_pd(d, c, 0b0100); // [4,1,10,7]
228 out1.raw = _mm256_shuffle_pd(d, d, 0b0101); // [1,4,7,10]
229
230 d = _mm256_blend_pd(a, b, 0b0010); // [_,5,2,_]
231 d = _mm256_blend_pd(d, c, 0b1001); // [8,5,2,11]
232 e = _mm256_permute2f128_pd(d, d, 0x01); // [2,11,8,5]
233 out2.raw = _mm256_blend_pd(d, e, 0b0101); // [2,5,8,11]
234 }
235
236 template <int i>
237 [[nodiscard]] static MOCHI_FORCE_INLINE Simd SetBasisVector() {
238 static_assert(i >= 0 && i <= 3, "Invalid component index");
239 auto zero = _mm256_setzero_si256(); // AVX
240 auto v = _mm256_insert_epi64(zero, 0x3FF0000000000000LL, i); // AVX
241 return _mm256_castsi256_pd(v); // AVX
242 }
243
244 template <int N = kSize>
245 static MOCHI_FORCE_INLINE void Store([[maybe_unused]] Scalar* ptr, [[maybe_unused]] Simd v) {
246 static_assert(N >= 0 && N <= kSize);
247 if constexpr (N == 0) {
248 } else if constexpr (N < kSize) {
249 // About 3X faster than a masked store on AMD. About the same on Intel.
250 memcpy(ptr, &v, sizeof(Scalar) * N);
251 } else {
252 _mm256_storeu_pd(ptr, v.raw); // AVX
253 }
254 }
255
256 static MOCHI_FORCE_INLINE void Store(Scalar* ptr, Simd v, int n) {
257 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
258 // Faster than masked store on AMD.
259 switch (n) { // clang-format off
260 case 1: Store<1>(ptr, v); break;
261 case 2: Store<2>(ptr, v); break;
262 case 3: Store<3>(ptr, v); break;
263 case 4: Store<4>(ptr, v); break;
264 MOCHI_UNLIKELY default: break;
265 } // clang-format on
266 }
267
268 MOCHI_FORCE_INLINE static int StoreSelected(Scalar* ptr, Simd condition, Simd values) {
269 auto mask = _mm256_movemask_pd(condition.raw);
270 // Load 8 bytes from the table, then zero-exend to get the shuffle pattern.
271 auto const* tableRow =
272 reinterpret_cast<__m128i const*>(x64_simd::kStoreSelectedShuffleTableD4[mask]);
273 __m256i pattern = _mm256_cvtepu8_epi32(_mm_loadl_epi64(tableRow));
274 __m256i packed = _mm256_permutevar8x32_epi32(_mm256_castpd_si256(values.raw), pattern);
275 _mm256_storeu_pd(ptr, _mm256_castsi256_pd(packed));
276 return _mm_popcnt_u32(mask);
277 }
278
279 template <int kTupleCount = kSize>
280 MOCHI_FORCE_INLINE static void StoreTransposed(Scalar* ptr, Simd a, Simd b, Simd c) {
281 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
282 // a = [0,3,6,9], b = [1,4,7,10], c = [2,5,8,11]
283 auto d = _mm256_shuffle_pd(b.raw, b.raw, 0b0101); // [4,1,10,7]
284 auto e = _mm256_blend_pd(a.raw, c.raw, 0b0101); // [2,3,8,9]
285 e = _mm256_permute2f128_pd(e, e, 0x01); // [8,9,2,3]
286 auto f = _mm256_blend_pd(a.raw, d, 0b1010); // [0,1,6,7]
287 auto g = _mm256_blend_pd(d, c.raw, 0b1010); // [4,5,10,11]
288 constexpr int kCount0 = Clamp(kTupleCount * 3 - 0, 0, 4);
289 constexpr int kCount1 = Clamp(kTupleCount * 3 - 4, 0, 4);
290 constexpr int kCount2 = Clamp(kTupleCount * 3 - 8, 0, 4);
291 Simd::Store<kCount0>(ptr, _mm256_blend_pd(f, e, 0b1100)); // [0,1,2,3]
292 if constexpr (kCount1 > 0) {
293 Simd::Store<kCount1>(ptr + 4, _mm256_blend_pd(g, f, 0b1100)); // [4,5,6,7]
294 }
295 if constexpr (kCount2 > 0) {
296 Simd::Store<kCount2>(ptr + 8, _mm256_blend_pd(e, g, 0b1100)); // [8,9,10,11]
297 }
298 }
299
300 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Select(Simd mask, Simd a, Simd b) {
301 return _mm256_blendv_pd(b.raw, a.raw, mask.raw); // AVX
302 }
303
304 // return Simd{v[x], v[y], v[z], v[w]}
305 template <int x = 0, int y = 1, int z = 2, int w = 3>
306 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Shuffle(Simd v) {
307 static_assert(x >= 0 && x < 4, "Invalid index");
308 static_assert(y >= 0 && y < 4, "Invalid index");
309 static_assert(z >= 0 && z < 4, "Invalid index");
310 static_assert(w >= 0 && w < 4, "Invalid index");
311 if constexpr (x == 0 && y == 1 && z == 2 && w == 3) {
312 return v;
313 } else {
314 return _mm256_permute4x64_pd(v.raw, x | (y << 2) | (z << 4) | (w << 6)); // AVX2
315 }
316 }
317
318 // return Simd{a[x], a[y], b[z], b[w]}
319 template <int x = 0, int y = 1, int z = 2, int w = 3>
320 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Shuffle(Simd a, Simd b) {
321 static_assert(x >= 0 && x < 4, "Invalid index");
322 static_assert(y >= 0 && y < 4, "Invalid index");
323 static_assert(z >= 0 && z < 4, "Invalid index");
324 static_assert(w >= 0 && w < 4, "Invalid index");
325 return Simd{Get<x>(a), Get<y>(a), Get<z>(b), Get<w>(b)};
326 }
327
328 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Sqrt(Simd v) {
329 return _mm256_sqrt_pd(v.raw); // AVX
330 }
331
332 [[nodiscard]] static MOCHI_FORCE_INLINE Simd RcpApprox(Simd v) {
333 return Simd{1.0} / v;
334 }
335
336 [[nodiscard]] static MOCHI_FORCE_INLINE Simd RcpSqrtApprox(Simd v) {
337 return Simd{1.0} / Sqrt(v);
338 }
339
340 // Broadcast the value -0.0. Use this in bitwise operations to affect just the sign bit.
341 [[nodiscard]] static MOCHI_FORCE_INLINE Simd SignBitMask() {
342 return _mm256_castsi256_pd(_mm256_set1_epi64x(0x8000000000000000LL)); // AVX, AVX
343 }
344
345 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Abs(Simd v) {
346 return _mm256_andnot_pd(SignBitMask().raw, v.raw); // AVX
347 }
348
349 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Min(Simd a, Simd b) {
350 return _mm256_min_pd(a.raw, b.raw); // AVX
351 }
352
353 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Max(Simd a, Simd b) {
354 return _mm256_max_pd(a.raw, b.raw); // AVX
355 }
356
357 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Floor(Simd a) {
358 return _mm256_floor_pd(a.raw); // AVX
359 }
360
361 [[nodiscard]] static MOCHI_FORCE_INLINE Simd FastRound(Simd v) {
362 return _mm256_round_pd(v.raw, _MM_FROUND_TO_NEAREST_INT); // AVX
363 }
364
365#if MOCHI_ARCH_X64_SVML
366 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Cos(Simd a) {
367 return _mm256_cos_pd(a.raw); // AVX
368 }
369
370 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Sin(Simd a) {
371 return _mm256_sin_pd(a.raw); // AVX
372 }
373
374 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Tan(Simd a) {
375 return _mm256_tan_pd(a.raw); // AVX
376 }
377
378 [[nodiscard]] static MOCHI_FORCE_INLINE Simd ACos(Simd a) {
379 return _mm256_acos_pd(a.raw); // AVX
380 }
381
382 [[nodiscard]] static MOCHI_FORCE_INLINE Simd ASin(Simd a) {
383 return _mm256_asin_pd(a.raw); // AVX
384 }
385
386 [[nodiscard]] static MOCHI_FORCE_INLINE Simd ATan(Simd a) {
387 return _mm256_atan_pd(a.raw); // AVX
388 }
389
390 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Exp(Simd a) {
391 return _mm256_exp_pd(a.raw); // AVX
392 }
393
394 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Ln(Simd a) {
395 return _mm256_log_pd(a.raw); // AVX
396 }
397
398 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Tanh(Simd a) {
399 return _mm256_tanh_pd(a.raw); // AVX
400 }
401#endif // MOCHI_ARCH_X64_SVML
402
403 [[nodiscard]] static MOCHI_FORCE_INLINE Simd MulAdd(Simd a, Simd b, Simd c) {
404#if MOCHI_ARCH_X64_FMA
405 return {_mm256_fmadd_pd(a.raw, b.raw, c.raw)}; // FMA
406#else
407 return (a * b) + c;
408#endif
409 }
410
411 [[nodiscard]] static MOCHI_FORCE_INLINE Simd MulSub(Simd a, Simd b, Simd c) {
412#if MOCHI_ARCH_X64_FMA
413 return _mm256_fmsub_pd(a.raw, b.raw, c.raw); // FMA
414#else
415 return (a * b) - c;
416#endif
417 }
418
419 [[nodiscard]] static MOCHI_FORCE_INLINE Simd NegMulAdd(Simd a, Simd b, Simd c) {
420#if MOCHI_ARCH_X64_FMA
421 return _mm256_fnmadd_pd(a.raw, b.raw, c.raw); // FMA
422#else
423 return -(a * b) + c;
424#endif
425 }
426
427 [[nodiscard]] static MOCHI_FORCE_INLINE Simd NegMulSub(Simd a, Simd b, Simd c) {
428#if MOCHI_ARCH_X64_FMA
429 return _mm256_fnmsub_pd(a.raw, b.raw, c.raw); // FMA
430#else
431 return -(a * b) - c;
432#endif
433 }
434
435 template <int N = 4>
436 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMin(Simd a) {
437 static_assert(N >= 2 && N <= 4, "Unsupported N");
438 using HalfT = Simd<Scalar, 2>;
439 if constexpr (N == 2) {
440 return Get<0>(Min(a, Broadcast<1>(a)));
441 } else if constexpr (N == 3) {
442 auto lo = GetHalf<0>(a);
443 auto hi = GetHalf<1>(a);
444 return HalfT::Get<0>(HalfT::Min(HalfT::Min(lo, HalfT::Broadcast<1>(lo)), hi));
445 } else {
446 return HalfT::HMin(HalfT::Min(GetHalf<0>(a), GetHalf<1>(a)));
447 }
448 }
449
450 template <int N = 4>
451 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMax(Simd a) {
452 static_assert(N >= 2 && N <= 4, "Unsupported N");
453 using HalfT = Simd<Scalar, 2>;
454 if constexpr (N == 2) {
455 return Get<0>(Max(a, Broadcast<1>(a)));
456 } else if constexpr (N == 3) {
457 auto lo = GetHalf<0>(a);
458 auto hi = GetHalf<1>(a);
459 return HalfT::Get<0>(HalfT::Max(HalfT::Max(lo, HalfT::Broadcast<1>(lo)), hi));
460 } else {
461 return HalfT::HMax(HalfT::Max(GetHalf<0>(a), GetHalf<1>(a)));
462 }
463 }
464
465 template <int N>
466 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HSum(Simd a) {
467 static_assert(N >= 2 && N <= 4, "Unsupported N");
468 if constexpr (N == 2) {
469 return Get<0>(a) + Get<1>(a);
470 } else if constexpr (N == 3) {
471 using HalfT = Simd<double, 2>;
472 HalfT tmp = GetHalf<0>(a) + GetHalf<1>(a);
473 return HalfT::Get<0>(tmp) + Get<1>(a); // (a[0] + a[2]) + a[1]
474 } else if constexpr (N == 4) {
475 // PERF NOTE: An alternate implementation could be: Get<0>(a) + Get<1>(a) + Get<2>(a) +
476 // Get<3>(a). In comparison, this implementation saves two instructions, while maintaining
477 // identical throughput and latency, when benchmarked on an AMD CPU.
478 using HalfT = Simd<double, 2>;
479 HalfT tmp = GetHalf<0>(a) + GetHalf<1>(a);
480 return HalfT::Get<0>(tmp) + HalfT::Get<1>(tmp); // (a[0] + a[2]) + (a[1] + a[3]);
481 }
482 }
483
484 template <int N>
485 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HProd(Simd a) {
486 static_assert(N >= 2 && N <= 4, "Unsupported N");
487 alignas(alignof(Simd)) Scalar buf[4];
488 Store(buf, a);
489 if constexpr (N == 2) {
490 return buf[0] * buf[1];
491 } else if constexpr (N == 3) {
492 return buf[0] * buf[1] * buf[2];
493 } else if constexpr (N == 4) {
494 return buf[0] * buf[1] * buf[2] * buf[3];
495 }
496 }
497
498 template <int N>
499 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Dot(Simd a, Simd b) {
500 static_assert(N >= 2 && N <= 4, "Unsupported N");
501 return Simd{HSum<N>(a * b)};
502 }
503
504 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<(Simd rhs) const {
505 return _mm256_cmp_pd(this->raw, rhs.raw, _CMP_LT_OQ); // AVX
506 }
507
508 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>(Simd rhs) const {
509 return _mm256_cmp_pd(this->raw, rhs.raw, _CMP_GT_OQ); // AVX
510 }
511
512 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<=(Simd rhs) const {
513 return _mm256_cmp_pd(this->raw, rhs.raw, _CMP_LE_OQ); // AVX
514 }
515
516 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>=(Simd rhs) const {
517 return _mm256_cmp_pd(this->raw, rhs.raw, _CMP_GE_OQ); // AVX
518 }
519
520 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Equal(Simd a, Simd b) {
521 return _mm256_cmp_pd(a.raw, b.raw, _CMP_EQ_OQ); // AVX
522 }
523
524 [[nodiscard]] static MOCHI_FORCE_INLINE Simd NotEqual(Simd a, Simd b) {
525 return _mm256_cmp_pd(a.raw, b.raw, _CMP_NEQ_UQ); // AVX
526 }
527
528 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Zero() {
529 return _mm256_setzero_pd(); // AVX
530 }
531
532 [[nodiscard]] MOCHI_FORCE_INLINE bool operator==(Simd rhs) const {
533 auto mask = GetMSBitMask(Equal(*this, rhs));
534 return mask == 0xFFFFFFFF; // All values equal
535 }
536
537 [[nodiscard]] MOCHI_FORCE_INLINE bool operator!=(Simd rhs) const {
538 auto mask = GetMSBitMask(NotEqual(*this, rhs));
539 return mask != 0; // Any values not equal
540 }
541
542 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator~() const {
543 auto ones = _mm256_castsi256_pd(_mm256_set1_epi64x(-1)); // AVX, AVX
544 return _mm256_xor_pd(raw, ones); // AVX
545 }
546
547 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-() const {
548 return _mm256_xor_pd(raw, SignBitMask().raw); // AVX, AVX
549 }
550
551 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator+(Simd rhs) const {
552 return _mm256_add_pd(raw, rhs.raw); // AVX
553 }
554
555 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-(Simd rhs) const {
556 return _mm256_sub_pd(raw, rhs.raw); // AVX
557 }
558
559 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator*(Simd rhs) const {
560 return _mm256_mul_pd(raw, rhs.raw); // AVX
561 }
562
563 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator/(Simd rhs) const {
564 return _mm256_div_pd(raw, rhs.raw); // AVX
565 }
566
567 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator&(Simd rhs) const {
568 return _mm256_and_pd(raw, rhs.raw); // AVX
569 }
570
571 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator|(Simd rhs) const {
572 return _mm256_or_pd(raw, rhs.raw); // AVX
573 }
574
575 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator^(Simd rhs) const {
576 return _mm256_xor_pd(raw, rhs.raw); // AVX
577 }
578
579 private:
580 // Integer mask with the most significant bit of each byte in the vector
581 [[nodiscard]] static MOCHI_FORCE_INLINE int GetMSBitMask(Simd a) {
582 return _mm256_movemask_epi8(_mm256_castpd_si256(a.raw)); // AVX2, AVX
583 }
584};
585
586} // namespace superdex
587
588#endif // MOCHI_USE_SIMD && MOCHI_ARCH_X64_AVX2
Simd operator&(Simd rhs) const
bool operator==(Simd rhs) const
NativeType raw
Definition simd.h:174
Simd operator>(Simd rhs) const
Simd operator*(Simd rhs) const
Simd operator^(Simd rhs) const
Simd operator-() const
Simd operator>=(Simd rhs) const
Simd operator<(Simd rhs) const
bool operator!=(Simd rhs) const
Simd operator|(Simd rhs) const
static constexpr int kSize
Definition simd.h:96
Simd operator+(Simd rhs) const
Simd operator~() const
Simd operator/(Simd rhs) const
Simd operator<=(Simd rhs) const
#define MOCHI_ASSERT_VERBOSE(condition_without_side_effects,...)
Definition debug.h:102
#define MOCHI_UNLIKELY
#define MOCHI_FORCE_INLINE
T Dot(Simd< T, N > a, Simd< T, N > b)
Definition simd.h:666
constexpr T ACos(T a)
Simd< T, 2 > Shuffle(Simd< T, 2 > a)
Definition simd_inl.h:270
V LoadIndexed(typename V::Scalar const *ptr, Simd< I, V::kSize > indices)
Definition simd_inl.h:200
constexpr T const & Min(T const &a, T const &b)
constexpr auto Equal(T const &a, T const &b)
constexpr T Sin(T a)
T HSum(Simd< T, N > a)
Definition simd_inl.h:379
T HMin(Simd< T, N > a)
Definition simd_inl.h:391
bool AllTrue(T const &a)
Definition basic_utils.h:60
constexpr auto MulAdd(A a, B b, C c)
Simd< T, N > Tanh(Simd< T, N > a)
Definition simd_inl.h:688
Simd< T, N > Set(Simd< T, N > a, T value)
Definition simd_inl.h:315
constexpr auto NotEqual(T const &a, T const &b)
constexpr T Exp(T a)
constexpr T Cos(T a)
T HMax(Simd< T, N > a)
Definition simd_inl.h:397
V Broadcast(typename V::Scalar a)
Definition simd_inl.h:115
constexpr T Abs(T a)
Definition basic_utils.h:50
constexpr auto MulSub(A a, B b, C c)
Simd< T, N > Blend(Simd< T, N > a, Simd< T, N > b)
Definition simd_inl.h:285
constexpr T Tan(T a)
bool AnyTrue(T const &a)
Definition basic_utils.h:66
constexpr T Select(bool condition, T a, T b)
constexpr T Sqrt(T a)
constexpr T ATan(T a)
Simd< T, N > Ln(Simd< T, N > a)
Definition simd_inl.h:678
constexpr auto NegMulAdd(A a, B b, C c)
T Get(Simd< T, N > v)
Definition simd_inl.h:300
constexpr T Floor(T a)
constexpr T ASin(T a)
constexpr ValT Clamp(ValT value, MinT min, MaxT max)
Simd< T, N/2 > GetHalf(Simd< T, N > a)
Definition simd_inl.h:310
T HProd(Simd< T, N > a)
Definition simd_inl.h:385
constexpr T const & Max(T const &a, T const &b)
void LoadTransposed(T const *ptr, Simd< T, N > &out0, Simd< T, N > &out1, Simd< T, N > &out2)
Definition simd_inl.h:207
Simd< T, N > FastRound(Simd< T, N > a)
Definition simd_inl.h:416
void StoreTransposed(T *ptr, Simd< T, N > a, Simd< T, N > b, Simd< T, N > c)
Definition simd_inl.h:245
void Store(T *ptr, Simd< T, N > a)
Definition simd_inl.h:213
Simd< T, N > RcpSqrtApprox(Simd< T, N > a)
Definition simd_inl.h:359
constexpr T RcpApprox(T a)
constexpr auto NegMulSub(A a, B b, C c)
int StoreSelected(T *ptr, Simd< MaskT, N > condition, Simd< T, N > values)
Definition simd_inl.h:225
V Load(typename V::Scalar const *ptr)
Definition simd_inl.h:184
#define MOCHI_NATIVE_SIMD_IMPL_BOILERPLATE(T, N, NativeT)