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