SuperDex Physics C++ API
Loading...
Searching...
No Matches
x64_simd_int64_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<int64_t, 4>
27*/
28template <>
29class Simd<int64_t, 4> {
30 public:
31 static_assert(sizeof(int64_t) == sizeof(long long));
32
33 MOCHI_NATIVE_SIMD_IMPL_BOILERPLATE(int64_t, 4, __m256i);
34 Simd(int64_t a, int64_t b, int64_t c = 0, int64_t d = 0)
35 : raw(_mm256_set_epi64x(
36 static_cast<long long>(d),
37 static_cast<long long>(c),
38 static_cast<long long>(b),
39 static_cast<long long>(a))) {} // AVX
40 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
41 Simd(U a) : raw(_mm256_set1_epi64x(static_cast<long long>(a))) {} // AVX
42
43 template <int i>
44 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar Get(Simd v) {
45 static_assert(i >= 0 && i < kSize, "Index out of range");
46#if MOCHI_COMPILER_MSVC
47 // Do not use _mm256_extract_epi64 for MSVC builds because of a bug in the optimizer. It tries
48 // to use register XMM18 even though "/arch:AVX2" was specified (AVX2 only has 16 vector
49 // registers). Microsoft claims that the behavior is "by design" when an AVX-512 intrinsic is
50 // used. However, _mm256_extract_epi64 is an AVX intrinsic. Maybe MSVC misclassified it?
51 if constexpr (i < 2) {
52 return _mm_extract_epi64(_mm256_castsi256_si128(v.raw), i);
53 } else {
54 return _mm_extract_epi64(_mm256_extracti128_si256(v.raw, 1), i - 2);
55 }
56#else
57 return _mm256_extract_epi64(v.raw, i);
58#endif
59 }
60
61 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar Get(Simd v, int i) {
62 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
63#if MOCHI_COMPILER_MSVC
64 return v.raw.m256i_i64[i];
65#else
66 switch (i) { // clang-format off
67 case 0: return Get<0>(v);
68 case 1: return Get<1>(v);
69 case 2: return Get<2>(v);
70 case 3: return Get<3>(v);
71 MOCHI_UNLIKELY default: return 0;
72 } // clang-format on
73#endif
74 }
75
76 template <int iHalf>
77 [[nodiscard]] static MOCHI_FORCE_INLINE Simd<int64_t, 2> GetHalf(Simd a) {
78 static_assert(iHalf == 0 || iHalf == 1);
79 return _mm256_extracti128_si256(a.raw, iHalf); // AVX2
80 }
81
82 template <int N>
83 [[nodiscard]] static MOCHI_FORCE_INLINE bool AllTrue(Simd v) {
84 static_assert(N >= 1 && N <= kSize, "Unsupported N");
85 int mask = GetMSBitMask(v); // One bit for each byte in the vector
86 if constexpr (N == kSize) {
87 return mask == 0xFFFFFFFF;
88 } else {
89 int constexpr kNumBits = N * sizeof(Scalar);
90 auto constexpr kMustBeSet = (1UL << kNumBits) - 1;
91 return (mask & kMustBeSet) == kMustBeSet;
92 }
93 }
94
95 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Scalar const* p) {
96 return Simd{*p};
97 }
98
99 template <int i>
100 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Simd v) {
101 return Shuffle<i, i, i, i>(v);
102 }
103
104 template <int N = 4>
105 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMin(Simd a) {
106 static_assert(N >= 2 && N <= 4, "Unsupported N");
107 using HalfT = Simd<Scalar, 2>;
108 if constexpr (N == 2) {
109 return Get<0>(Min(a, Broadcast<1>(a)));
110 } else if constexpr (N == 3) {
111 auto lo = GetHalf<0>(a);
112 auto hi = GetHalf<1>(a);
113 return HalfT::Get<0>(HalfT::Min(HalfT::Min(lo, HalfT::Broadcast<1>(lo)), hi));
114 } else {
115 return HalfT::HMin(HalfT::Min(GetHalf<0>(a), GetHalf<1>(a)));
116 }
117 }
118
119 template <int N = 4>
120 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMax(Simd a) {
121 static_assert(N >= 2 && N <= 4, "Unsupported N");
122 using HalfT = Simd<Scalar, 2>;
123 if constexpr (N == 2) {
124 return Get<0>(Max(a, Broadcast<1>(a)));
125 } else if constexpr (N == 3) {
126 auto lo = GetHalf<0>(a);
127 auto hi = GetHalf<1>(a);
128 return HalfT::Get<0>(HalfT::Max(HalfT::Max(lo, HalfT::Broadcast<1>(lo)), hi));
129 } else {
130 return HalfT::HMax(HalfT::Max(GetHalf<0>(a), GetHalf<1>(a)));
131 }
132 }
133
134 template <int N>
135 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HSum(Simd a) {
136 static_assert(N >= 2 && N <= 4, "Unsupported N");
137 if constexpr (N == 2) {
138 return Get<0>(a) + Get<1>(a);
139 } else if constexpr (N == 3) {
140 using HalfT = Simd<int64_t, 2>;
141 HalfT tmp = GetHalf<0>(a) + GetHalf<1>(a);
142 return HalfT::Get<0>(tmp) + Get<1>(a); // (a[0] + a[2]) + a[1]
143 } else if constexpr (N == 4) {
144 using HalfT = Simd<int64_t, 2>;
145 HalfT tmp = GetHalf<0>(a) + GetHalf<1>(a);
146 return HalfT::Get<0>(tmp) + HalfT::Get<1>(tmp); // (a[0] + a[2]) + (a[1] + a[3]);
147 }
148 }
149
150 template <int N = kSize>
151 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load([[maybe_unused]] Scalar const* ptr) {
152 static_assert(N >= 0 && N <= kSize);
153 if constexpr (N == 0) {
154 return Simd::Zero();
155 } else if constexpr (N == 1) {
156 return Simd{*ptr, 0, 0, 0};
157 } else if constexpr (N == 2) {
158 __m256i mask = _mm256_set_epi32(0, 0, 0, 0, -1, -1, -1, -1); // AVX
159 return _mm256_maskload_epi64(reinterpret_cast<long long const*>(ptr), mask); // AVX2
160 } else if constexpr (N == 3) {
161 __m256i mask = _mm256_set_epi32(0, 0, -1, -1, -1, -1, -1, -1); // AVX
162 return _mm256_maskload_epi64(reinterpret_cast<long long const*>(ptr), mask); // AVX2
163 } else {
164 return _mm256_loadu_si256(reinterpret_cast<__m256i const*>(ptr)); // AVX
165 }
166 }
167
168 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load(Scalar const* ptr, int n) {
169 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
170 switch (n) { // clang-format off
171 case 1: return Load<1>(ptr);
172 case 2: return Load<2>(ptr);
173 case 3: return Load<3>(ptr);
174 case 4: return Load<4>(ptr);
175 MOCHI_UNLIKELY default: return Zero();
176 } // clang-format on
177 }
178
179 template <int kTupleCount = kSize>
180 MOCHI_FORCE_INLINE static void
181 LoadTransposed(Scalar const* ptr, Simd& out0, Simd& out1, Simd& out2) {
182 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
183 constexpr int kCount0 = Clamp(kTupleCount * 3 - 0, 0, 4);
184 constexpr int kCount1 = Clamp(kTupleCount * 3 - 4, 0, 4);
185 constexpr int kCount2 = Clamp(kTupleCount * 3 - 8, 0, 4);
186 auto a = _mm256_castsi256_pd(Simd::Load<kCount0>(ptr).raw); // [0,1,2,3]
187 auto b =
188 _mm256_castsi256_pd(Simd::Load<kCount1>(kCount1 == 0 ? ptr : ptr + 4).raw); // [4,5,6,7]
189 auto c =
190 _mm256_castsi256_pd(Simd::Load<kCount2>(kCount2 == 0 ? ptr : ptr + 8).raw); // [8,9,10,11]
191
192 auto d = _mm256_blend_pd(a, b, 0b0100); // [0,_,6,3]
193 d = _mm256_blend_pd(d, c, 0b0010); // [0,9,6,3]
194 auto e = _mm256_permute2f128_pd(d, d, 0x01); // [6,3,0,9]
195 out0.raw = _mm256_castpd_si256(_mm256_blend_pd(d, e, 0b1010)); // [0,3,6,9]
196
197 d = _mm256_blend_pd(a, b, 0b1001); // [4,1,_,7]
198 d = _mm256_blend_pd(d, c, 0b0100); // [4,1,10,7]
199 out1.raw = _mm256_castpd_si256(_mm256_shuffle_pd(d, d, 0b0101)); // [1,4,7,10]
200
201 d = _mm256_blend_pd(a, b, 0b0010); // [_,5,2,_]
202 d = _mm256_blend_pd(d, c, 0b1001); // [8,5,2,11]
203 e = _mm256_permute2f128_pd(d, d, 0x01); // [2,11,8,5]
204 out2.raw = _mm256_castpd_si256(_mm256_blend_pd(d, e, 0b0101)); // [2,5,8,11]
205 }
206
207 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Min(Simd a, Simd b) {
208 // TODO: Use _mm256_min_epi64 for AVX512
209 return Simd{
213 superdex::Min(Get<3>(a), Get<3>(b))};
214 }
215
216 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Max(Simd a, Simd b) {
217 // TODO: Use _mm256_min_epi64 for AVX512
218 return Simd{
222 superdex::Max(Get<3>(a), Get<3>(b))};
223 }
224
225 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Select(Simd mask, Simd a, Simd b) {
226 return _mm256_blendv_epi8(b.raw, a.raw, mask.raw); // AVX2
227 }
228
229 template <int x, int y, int z, int w>
230 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Shuffle(Simd a) {
231 if constexpr (x == 0 && y == 1 && z == 2 && w == 3) {
232 return a;
233 } else {
234 return _mm256_permute4x64_epi64(a.raw, _MM_SHUFFLE(w, z, y, x)); // AVX2
235 }
236 }
237
238 template <int x, int y, int z, int w>
239 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Shuffle(Simd a, Simd b) {
240 return Simd{Get<x>(a), Get<y>(a), Get<z>(b), Get<w>(b)};
241 }
242
243 template <int N = kSize>
244 static MOCHI_FORCE_INLINE void Store([[maybe_unused]] Scalar* ptr, [[maybe_unused]] Simd v) {
245 static_assert(N >= 0 && N <= kSize);
246 if constexpr (N == 0) {
247 } else if constexpr (N < kSize) {
248 memcpy(ptr, &v.raw, sizeof(Scalar) * N);
249 } else {
250 _mm256_storeu_si256(reinterpret_cast<__m256i*>(ptr), v.raw); // AVX2
251 }
252 }
253
254 static MOCHI_FORCE_INLINE void Store(Scalar* ptr, Simd v, int n) {
255 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
256 // Faster than masked store on AMD.
257 // clang-format off
258 switch (n) {
259 case 1: Store<1>(ptr, v); break;
260 case 2: Store<2>(ptr, v); break;
261 case 3: Store<3>(ptr, v); break;
262 case 4: Store<4>(ptr, v); break;
263 MOCHI_UNLIKELY default: break;
264 } // clang-format on
265 }
266
267 MOCHI_FORCE_INLINE static int StoreSelected(Scalar* ptr, Simd condition, Simd values) {
268 auto mask = _mm256_movemask_pd(_mm256_castsi256_pd(condition.raw));
269 // Load 8 bytes from the table, then zero-exend to get the shuffle pattern.
270 auto const* tableRow =
271 reinterpret_cast<__m128i const*>(x64_simd::kStoreSelectedShuffleTableD4[mask]);
272 auto pattern = _mm256_cvtepu8_epi32(_mm_loadl_epi64(tableRow));
273 auto packed = _mm256_permutevar8x32_epi32(values.raw, pattern);
274 _mm256_storeu_si256(reinterpret_cast<__m256i*>(ptr), packed);
275 return _mm_popcnt_u32(mask);
276 }
277
278 template <int kTupleCount = kSize>
279 MOCHI_FORCE_INLINE static void StoreTransposed(Scalar* ptr, Simd a, Simd b, Simd c) {
280 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
281 // a = [0,3,6,9], b = [1,4,7,10], c = [2,5,8,11]
282 auto a_ = _mm256_castsi256_pd(a.raw);
283 auto b_ = _mm256_castsi256_pd(b.raw);
284 auto c_ = _mm256_castsi256_pd(c.raw);
285 auto d = _mm256_shuffle_pd(b_, b_, 0b0101); // [4,1,10,7]
286 auto e = _mm256_blend_pd(a_, c_, 0b0101); // [2,3,8,9]
287 e = _mm256_permute2f128_pd(e, e, 0x01); // [8,9,2,3]
288 auto f = _mm256_blend_pd(a_, d, 0b1010); // [0,1,6,7]
289 auto g = _mm256_blend_pd(d, c_, 0b1010); // [4,5,10,11]
290 constexpr int kCount0 = Clamp(kTupleCount * 3 - 0, 0, 4);
291 constexpr int kCount1 = Clamp(kTupleCount * 3 - 4, 0, 4);
292 constexpr int kCount2 = Clamp(kTupleCount * 3 - 8, 0, 4);
293 Simd::Store<kCount0>(
294 ptr, Simd{_mm256_castpd_si256(_mm256_blend_pd(f, e, 0b1100))}); // [0,1,2,3]
295 if constexpr (kCount1 > 0) {
296 Simd::Store<kCount1>(
297 ptr + 4, Simd{_mm256_castpd_si256(_mm256_blend_pd(g, f, 0b1100))}); // [4,5,6,7]
298 }
299 if constexpr (kCount2 > 0) {
300 Simd::Store<kCount2>(
301 ptr + 8, Simd{_mm256_castpd_si256(_mm256_blend_pd(e, g, 0b1100))}); // [8,9,10,11]
302 }
303 }
304
305 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Zero() {
306 return _mm256_setzero_si256(); // AVX
307 }
308
309 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<(Simd rhs) const {
310 return _mm256_cmpgt_epi64(rhs.raw, this->raw); // AVX2
311 }
312
313 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>(Simd rhs) const {
314 return _mm256_cmpgt_epi64(this->raw, rhs.raw); // AVX2
315 }
316
317 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<=(Simd rhs) const {
318 return ~(*this > rhs); // No native support
319 }
320
321 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>=(Simd rhs) const {
322 return ~(*this < rhs); // No native support
323 }
324
325 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Equal(Simd a, Simd b) {
326 return _mm256_cmpeq_epi64(a.raw, b.raw); // AVX2
327 }
328
329 [[nodiscard]] static MOCHI_FORCE_INLINE Simd NotEqual(Simd a, Simd b) {
330 return ~Equal(a, b); // // No native support
331 }
332
333 [[nodiscard]] MOCHI_FORCE_INLINE bool operator==(Simd rhs) const {
334 auto mask = GetMSBitMask(Equal(*this, rhs));
335 return mask == 0xFFFFFFFF; // All values equal
336 }
337
338 [[nodiscard]] MOCHI_FORCE_INLINE bool operator!=(Simd rhs) const {
339 auto mask = GetMSBitMask(NotEqual(*this, rhs));
340 return mask != 0; // Any values not equal
341 }
342
343 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator~() const {
344 auto ones = _mm256_cmpeq_epi64(raw, raw); // AVX2
345 return _mm256_xor_si256(raw, ones); // AVX2
346 }
347
348 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-() const {
349 return _mm256_sub_epi64(_mm256_setzero_si256(), raw); // AVX2
350 }
351
352 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator+(Simd rhs) const {
353 return _mm256_add_epi64(raw, rhs.raw); // SSE
354 }
355
356 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-(Simd rhs) const {
357 return _mm256_sub_epi64(raw, rhs.raw); // SSE
358 }
359
360 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator*(Simd rhs) const {
361 // Fallback
362 // Requires AVX512 _mm256_mullo_epi64
363 return Simd{
364 Get<0>(*this) * Get<0>(rhs),
365 Get<1>(*this) * Get<1>(rhs),
366 Get<2>(*this) * Get<2>(rhs),
367 Get<3>(*this) * Get<3>(rhs)};
368 }
369
370 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator/(Simd rhs) const {
371#if MOCHI_ARCH_X64_SVML
372 return _mm256_div_epi64(raw, rhs.raw); // SSE
373#else
374 // Fallback
375 return Simd{
376 Get<0>(*this) / Get<0>(rhs),
377 Get<1>(*this) / Get<1>(rhs),
378 Get<2>(*this) / Get<2>(rhs),
379 Get<3>(*this) / Get<3>(rhs)};
380#endif
381 }
382
383 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator&(Simd rhs) const {
384 return _mm256_and_si256(raw, rhs.raw); // AVX2
385 }
386
387 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator|(Simd rhs) const {
388 return _mm256_or_si256(raw, rhs.raw); // AVX2
389 }
390
391 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator^(Simd rhs) const {
392 return _mm256_xor_si256(raw, rhs.raw); // AVX2
393 }
394
395 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<<(int rhs) const {
396 return _mm256_slli_epi64(raw, rhs); // AVX2
397 }
398
399 template <int kShift>
400 [[nodiscard]] MOCHI_FORCE_INLINE static Simd ShiftRight(Simd a) {
401 return _mm256_srli_epi64(a.raw, kShift); // AVX2
402 }
403
404 private:
405 // Integer mask with the most significant bit of each byte in the vector
406 [[nodiscard]] static MOCHI_FORCE_INLINE int GetMSBitMask(Simd a) {
407 return _mm256_movemask_epi8(a.raw); // AVX2
408 }
409};
410
411} // namespace superdex
412
413#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<<(int shift) 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
Simd< T, 2 > Shuffle(Simd< T, 2 > a)
Definition simd_inl.h:270
constexpr T const & Min(T const &a, T const &b)
constexpr auto Equal(T const &a, T const &b)
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 NotEqual(T const &a, T const &b)
T HMax(Simd< T, N > a)
Definition simd_inl.h:397
V Broadcast(typename V::Scalar a)
Definition simd_inl.h:115
constexpr T Select(bool condition, T a, T b)
T Get(Simd< T, N > v)
Definition simd_inl.h:300
constexpr ValT Clamp(ValT value, MinT min, MaxT max)
Simd< T, N/2 > GetHalf(Simd< T, N > a)
Definition simd_inl.h:310
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
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
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
Simd< T, N > ShiftRight(Simd< T, N > a)
Definition simd_inl.h:260
#define MOCHI_NATIVE_SIMD_IMPL_BOILERPLATE(T, N, NativeT)