SuperDex Physics C++ API
Loading...
Searching...
No Matches
x64_simd_int64_2_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, 2>
27*/
28template <>
29class Simd<int64_t, 2> {
30 public:
31 static_assert(sizeof(int64_t) == sizeof(long long));
32
33 MOCHI_NATIVE_SIMD_IMPL_BOILERPLATE(int64_t, 2, __m128i);
34 Simd(int64_t low, int64_t high)
35 : raw(_mm_set_epi64x(static_cast<long long>(high), static_cast<long long>(low))) {} // SSE2
36 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
37 Simd(U a) : raw(_mm_set1_epi64x(static_cast<long long>(a))) {} // SSE2
38
39 template <int i>
40 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar Get(Simd v) {
41 static_assert(i >= 0 && i < kSize, "Index out of range");
42 return v[i];
43 }
44
45 [[nodiscard]] MOCHI_FORCE_INLINE Scalar operator[](int i) const {
46 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
47#if MOCHI_COMPILER_MSVC
48 return raw.m128i_i64[i];
49#else
50 return static_cast<Scalar>(raw[i]);
51#endif
52 }
53
54 template <int N>
55 [[nodiscard]] static MOCHI_FORCE_INLINE bool AllTrue(Simd v) {
56 static_assert(N >= 1 && N <= kSize, "Unsupported N");
57 auto mask = GetMSBitMask(v); // One bit for each byte in the vector
58 if constexpr (N == kSize) {
59 return mask == 0x0000FFFF;
60 } else {
61 return (mask & 0x000000FF) == 0x000000FF;
62 }
63 }
64
65 template <int x, int y>
66 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Blend(Simd a, Simd b) {
67 static_assert(x >= 0 && x < 2 && y >= 0 && y < 2, "invalid blend index");
68 if constexpr (x == 0 && y == 0) {
69 return a;
70 } else if constexpr (x == 1 && y == 1) {
71 return b;
72 } else {
73 return _mm_castpd_si128(
74 _mm_blend_pd(_mm_castsi128_pd(a.raw), _mm_castsi128_pd(b.raw), x | (y << 1))); // SSE4.1
75 }
76 }
77
78 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Scalar const* p) {
79 return Simd{*p};
80 }
81
82 template <int i>
83 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Simd v) {
84 return Shuffle<i, i>(v);
85 }
86
87 template <int N = 2>
88 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMin(Simd a) {
89 static_assert(N == 2, "Unsupported N");
90 return Get<0>(Min(a, Broadcast<1>(a)));
91 }
92
93 template <int N = 2>
94 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMax(Simd a) {
95 static_assert(N == 2, "Unsupported N");
96 return Get<0>(Max(a, Broadcast<1>(a)));
97 }
98
99 template <int N>
100 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HSum(Simd a) {
101 static_assert(N == 2, "Unsupported N");
102 return Get<0>(a) + Get<1>(a);
103 }
104
105 template <int N = kSize>
106 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load([[maybe_unused]] Scalar const* ptr) {
107 static_assert(N >= 0 && N <= kSize);
108 if constexpr (N == 0) {
109 return Simd::Zero();
110 } else if constexpr (N == 1) {
111 return Simd{*ptr, 0};
112 } else if constexpr (N == 2) {
113 return _mm_loadu_si128(reinterpret_cast<__m128i const*>(ptr)); // SSE
114 }
115 }
116
117 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load(Scalar const* ptr, int n) {
118 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
119#if MOCHI_ARCH_X64_AVX512
120 return _mm_maskz_loadu_epi64(x64_simd::kLaneMasksS8[n], ptr); // AVX512VL
121#else
122 switch (n) { // clang-format off
123 case 1: return Load<1>(ptr);
124 case 2: return Load<2>(ptr);
125 MOCHI_UNLIKELY default: return Zero();
126 } // clang-format on
127#endif
128 }
129
130 template <int kTupleCount = kSize>
131 MOCHI_FORCE_INLINE static void
132 LoadTransposed(Scalar const* ptr, Simd& out0, Simd& out1, Simd& out2) {
133 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
134 constexpr int kCount1 = Clamp(kTupleCount * 3 - 2, 0, 2);
135 constexpr int kCount2 = Clamp(kTupleCount * 3 - 4, 0, 2);
136 auto a = _mm_castsi128_pd(Simd::Load<2>(ptr).raw); // [0,1]
137 auto b = _mm_castsi128_pd(Simd::Load<kCount1>(kCount1 == 0 ? ptr : ptr + 2).raw); // [2,3]
138 auto c = _mm_castsi128_pd(Simd::Load<kCount2>(kCount2 == 0 ? ptr : ptr + 4).raw); // [4,5]
139 out0.raw = _mm_castpd_si128(_mm_shuffle_pd(a, b, 0b0010)); // [0,3]
140 out1.raw = _mm_castpd_si128(_mm_shuffle_pd(a, c, 0b0001)); // [1,4]
141 out2.raw = _mm_castpd_si128(_mm_shuffle_pd(b, c, 0b0010)); // [2,5]
142 }
143
144 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Min(Simd a, Simd b) {
145#if MOCHI_ARCH_X64_AVX512
146 return _mm_min_epi64(a.raw, b.raw); // AVX512VL
147#else
148 return Simd{superdex::Min(Get<0>(a), Get<0>(b)), superdex::Min(Get<1>(a), Get<1>(b))};
149#endif
150 }
151
152 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Max(Simd a, Simd b) {
153#if MOCHI_ARCH_X64_AVX512
154 return _mm_max_epi64(a.raw, b.raw); // AVX512VL
155#else
156 return Simd{superdex::Max(Get<0>(a), Get<0>(b)), superdex::Max(Get<1>(a), Get<1>(b))};
157#endif
158 }
159
160 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Select(Simd mask, Simd a, Simd b) {
161 return _mm_blendv_epi8(b.raw, a.raw, mask.raw); // SSE4.1
162 }
163
164 template <int x = 0, int y = 1>
165 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Shuffle(Simd v) {
166 static_assert(x >= 0 && x < 2, "Invalid index");
167 static_assert(y >= 0 && y < 2, "Invalid index");
168 if constexpr (x == 0 && y == 1) {
169 return v;
170 } else {
171 return _mm_castpd_si128(
172 _mm_shuffle_pd(_mm_castsi128_pd(v.raw), _mm_castsi128_pd(v.raw), x | (y << 1))); // SSE2
173 }
174 }
175
176 template <int N = kSize>
177 static MOCHI_FORCE_INLINE void Store([[maybe_unused]] Scalar* ptr, [[maybe_unused]] Simd v) {
178 static_assert(N >= 0 && N <= kSize);
179 if constexpr (N == 0) {
180 } else if constexpr (N < kSize) {
181 // About 3X faster than a masked store on older AMD CPUs. About the same on others.
182 memcpy(ptr, &v.raw, sizeof(Scalar) * N);
183 } else {
184 _mm_storeu_si128(reinterpret_cast<__m128i*>(ptr), v.raw); // SSE
185 }
186 }
187
188 static MOCHI_FORCE_INLINE void Store(Scalar* ptr, Simd v, int n) {
189 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
190#if MOCHI_ARCH_X64_AVX512
191 _mm_mask_storeu_epi64(ptr, x64_simd::kLaneMasksS8[n], v.raw); // AVX512VL
192#else
193 // With AVX2, this is faster than masked store for a predictable value of n.
194 // It is much slower for a random value of n.
195 switch (n) { // clang-format off
196 case 1: Store<1>(ptr, v); break;
197 case 2: Store<2>(ptr, v); break;
198 MOCHI_UNLIKELY default: break;
199 } // clang-format on
200#endif
201 }
202
203 MOCHI_FORCE_INLINE static int StoreSelected(Scalar* ptr, Simd condition, Simd values) {
204#if MOCHI_ARCH_X64_AVX512
205 auto const mask = _mm_movepi64_mask(condition.raw);
206 _mm_mask_compressstoreu_epi64(ptr, mask, values.raw); // AVX512VL
207 return _mm_popcnt_u32(mask);
208#else
209 auto mask = _mm_movemask_pd(_mm_castsi128_pd(condition.raw));
210 auto swapped = _mm_castpd_si128(_mm_shuffle_pd(
211 _mm_castsi128_pd(values.raw), _mm_castsi128_pd(values.raw), 1)); // swap halves
212 auto blendMask = _mm_set1_epi32((mask & 1) - 1); // swap first bit of mask is zero
213 auto packed = _mm_blendv_epi8(values.raw, swapped, blendMask);
214 _mm_storeu_si128(reinterpret_cast<__m128i*>(ptr), packed);
215 return _mm_popcnt_u32(mask);
216#endif
217 }
218
219 template <int kTupleCount = kSize>
220 MOCHI_FORCE_INLINE static void StoreTransposed(Scalar* ptr, Simd a, Simd b, Simd c) {
221 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
222 // a = [0,3], b = [1,4], c = [2,5]
223 auto d = _mm_shuffle_pd(_mm_castsi128_pd(a.raw), _mm_castsi128_pd(b.raw), 0b00); // [0,1]
224 auto e = _mm_shuffle_pd(_mm_castsi128_pd(c.raw), _mm_castsi128_pd(a.raw), 0b10); // [2,3]
225 auto f = _mm_shuffle_pd(_mm_castsi128_pd(b.raw), _mm_castsi128_pd(c.raw), 0b11); // [4,5]
226 Simd::Store<2>(ptr, _mm_castpd_si128(d));
227 constexpr int kCount1 = Clamp(kTupleCount * 3 - 2, 0, 2);
228 constexpr int kCount2 = Clamp(kTupleCount * 3 - 4, 0, 2);
229 if constexpr (kCount1 > 0) {
230 Simd::Store<kCount1>(ptr + 2, _mm_castpd_si128(e));
231 }
232 if constexpr (kCount2 > 0) {
233 Simd::Store<kCount2>(ptr + 4, _mm_castpd_si128(f));
234 }
235 }
236
237 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Zero() {
238 return _mm_setzero_si128(); // SSE2
239 }
240
241 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<(Simd rhs) const {
242 return _mm_cmpgt_epi64(rhs.raw, this->raw); // SSE2
243 }
244
245 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>(Simd rhs) const {
246 return _mm_cmpgt_epi64(this->raw, rhs.raw); // SSE2
247 }
248
249 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<=(Simd rhs) const {
250 return ~(*this > rhs); // No native support until AVX512
251 }
252
253 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>=(Simd rhs) const {
254 return ~(*this < rhs); // No native support until AVX512
255 }
256
257 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Equal(Simd a, Simd b) {
258 return _mm_cmpeq_epi64(a.raw, b.raw); // SSE2
259 }
260
261 [[nodiscard]] static MOCHI_FORCE_INLINE Simd NotEqual(Simd a, Simd b) {
262 return ~Equal(a, b); // // No native support until AVX512
263 }
264
265 [[nodiscard]] MOCHI_FORCE_INLINE bool operator==(Simd rhs) const {
266 auto mask = GetMSBitMask(Equal(*this, rhs));
267 return mask == 0xFFFF; // All values equal
268 }
269
270 [[nodiscard]] MOCHI_FORCE_INLINE bool operator!=(Simd rhs) const {
271 auto mask = GetMSBitMask(NotEqual(*this, rhs));
272 return mask != 0; // Any values not equal
273 }
274
275 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator~() const {
276 __m128i ones = _mm_cmpeq_epi64(raw, raw); // SSE2
277 return _mm_xor_si128(raw, ones); // SSE2
278 }
279
280 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-() const {
281 return _mm_sub_epi64(_mm_setzero_si128(), raw); // SSE2, SSE2
282 }
283
284 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator+(Simd rhs) const {
285 return _mm_add_epi64(raw, rhs.raw); // SSE
286 }
287
288 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-(Simd rhs) const {
289 return _mm_sub_epi64(raw, rhs.raw); // SSE
290 }
291
292 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator*(Simd rhs) const {
293#if MOCHI_ARCH_X64_AVX512
294 return _mm_mullo_epi64(raw, rhs.raw); // AVX512VL
295#else
296 return Simd{Get<0>(*this) * Get<0>(rhs), Get<1>(*this) * Get<1>(rhs)};
297#endif
298 }
299
300 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator/(Simd rhs) const {
301#if MOCHI_ARCH_X64_SVML
302 return _mm_div_epi64(raw, rhs.raw); // SSE
303#else
304 // Fallback
305 return Simd{Get<0>(*this) / Get<0>(rhs), Get<1>(*this) / Get<1>(rhs)};
306#endif
307 }
308
309 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator&(Simd rhs) const {
310 return _mm_and_si128(raw, rhs.raw); // SSE2
311 }
312
313 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator|(Simd rhs) const {
314 return _mm_or_si128(raw, rhs.raw); // SSE2
315 }
316
317 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator^(Simd rhs) const {
318 return _mm_xor_si128(raw, rhs.raw); // SSE2
319 }
320
321 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<<(int rhs) const {
322 return _mm_slli_epi64(raw, rhs); // SSE2
323 }
324
325 template <int kShift>
326 [[nodiscard]] MOCHI_FORCE_INLINE static Simd ShiftRight(Simd a) {
327 static_assert(kShift >= 0 && kShift < 64, "Shift amount out-of-range");
328 if constexpr (kShift == 0) {
329 return a;
330 } else {
331#if MOCHI_ARCH_X64_AVX512
332 return _mm_srai_epi64(a.raw, kShift); // AVX512VL
333#else
334 auto shifted = _mm_srli_epi64(a.raw, kShift); // SSE2
335 auto signMask = _mm_cmpgt_epi64(_mm_setzero_si128(), a.raw); // SSE4.2
336 auto signFill = _mm_slli_epi64(signMask, 64 - kShift); // SSE2
337 return _mm_or_si128(shifted, signFill); // SSE2
338#endif
339 }
340 }
341
342 private:
343 // Integer mask with the most significant bit of each byte in the vector
344 [[nodiscard]] static MOCHI_FORCE_INLINE int GetMSBitMask(Simd a) {
345 return _mm_movemask_epi8(a.raw); // SSE2
346 }
347};
348
349} // namespace superdex
350
351#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
Scalar operator[](int i) 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:273
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:377
T HMin(Simd< T, N > a)
Definition simd_inl.h:389
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:395
V Broadcast(typename V::Scalar a)
Definition simd_inl.h:115
Simd< T, N > Blend(Simd< T, N > a, Simd< T, N > b)
Definition simd_inl.h:288
constexpr T Select(bool condition, T a, T b)
T Get(Simd< T, N > v)
Definition simd_inl.h:303
constexpr ValT Clamp(ValT value, MinT min, MaxT max)
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)