SuperDex Physics C++ API
Loading...
Searching...
No Matches
x64_simd_int_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<int, 4>
27*/
28template <>
29class Simd<int, 4> {
30 public:
32 Simd(int a, int b, int c = 0, int d = 0) : raw(_mm_set_epi32(d, c, b, a)) {} // SSE2
33 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
34 Simd(U a) : raw(_mm_set1_epi32(a)) {} // SSE2
35
36 template <int i>
37 [[nodiscard]] static MOCHI_FORCE_INLINE int Get(Simd v) {
38 static_assert(i >= 0 && i < 4, "Index out of range");
39 if constexpr (i == 0) {
40 return _mm_cvtsi128_si32(v.raw); // SSE2
41 } else if constexpr (i == 1) {
42 return _mm_cvtsi128_si32(_mm_shuffle_epi32(v.raw, _MM_SHUFFLE(1, 1, 1, 1))); // SSE2, SSE2
43 } else if constexpr (i == 2) {
44 return _mm_cvtsi128_si32(_mm_shuffle_epi32(v.raw, _MM_SHUFFLE(2, 2, 2, 2))); // SSE2, SSE2
45 } else if constexpr (i == 3) {
46 return _mm_cvtsi128_si32(_mm_shuffle_epi32(v.raw, _MM_SHUFFLE(3, 3, 3, 3))); // SSE2, SSE2
47 }
48 }
49
50 [[nodiscard]] static MOCHI_FORCE_INLINE int Get(Simd v, int i) {
51 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
52#if MOCHI_COMPILER_MSVC
53 return v.raw.m128i_i32[i];
54#else
55 switch (i) { // clang-format off
56 case 0: return Get<0>(v);
57 case 1: return Get<1>(v);
58 case 2: return Get<2>(v);
59 case 3: return Get<3>(v);
60 MOCHI_UNLIKELY default: return 0;
61 } // clang-format on
62#endif
63 }
64
65 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Set(Simd v, int i, Scalar value) {
66 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
67#if MOCHI_COMPILER_MSVC
68 auto result = v;
69 result.raw.m128i_i32[i] = value;
70 return result;
71#else
72 static constexpr __m128i kMasks[] = {
73 // clang-format off
74 {static_cast<long long>(0x00000000FFFFFFFFLL), static_cast<long long>(0x0000000000000000LL)},
75 {static_cast<long long>(0xFFFFFFFF00000000LL), static_cast<long long>(0x0000000000000000LL)},
76 {static_cast<long long>(0x0000000000000000LL), static_cast<long long>(0x00000000FFFFFFFFLL)},
77 {static_cast<long long>(0x0000000000000000LL), static_cast<long long>(0xFFFFFFFF00000000LL)}
78 }; // clang-format on
79 return _mm_blendv_epi8(v.raw, _mm_set1_epi32(value), kMasks[i]); // SSE4.1
80#endif
81 }
82
83 template <int i>
84 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Set(Simd v, Scalar value) {
85 static_assert(i >= 0 && i < kSize, "Index out of range");
86 // _mm_insert_epi32 exists, but it requires both i and value to be constexpr.
87 // _mm_insert_ps moves the same 4 bytes as if they were floats. The casts are free.
88 return _mm_castps_si128(_mm_insert_ps(
89 _mm_castsi128_ps(v.raw), _mm_castsi128_ps(_mm_set1_epi32(value)), i << 4)); // SSE4.1
90 }
91
92 // Set via 2 int64_t instead of 4 int
93 [[nodiscard]] static MOCHI_FORCE_INLINE Simd SetInt64(int64_t a, int64_t b) {
94 return _mm_set_epi64x(b, a); // SSE2
95 }
96
97 template <int x, int y, int z, int w>
98 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Blend(Simd a, Simd b) {
99 static_assert(
100 x >= 0 && x < 2 && y >= 0 && y < 2 && z >= 0 && z < 2 && w >= 0 && w < 2,
101 "invalid blend index");
102 if constexpr (x == 0 && y == 0 && z == 0 && w == 0) {
103 return a;
104 } else if constexpr (x == 1 && y == 1 && z == 1 && w == 1) {
105 return b;
106 } else {
107 return _mm_blend_epi32(a.raw, b.raw, x | (y << 1) | (z << 2) | (w << 3)); // AVX2
108 }
109 }
110
111 template <int N>
112 [[nodiscard]] static MOCHI_FORCE_INLINE bool AllTrue(Simd v) {
113 static_assert(N >= 1 && N <= kSize, "Unsupported N");
114 int mask = GetMSBitMask(v); // One bit for each byte in the vector
115 if constexpr (N == kSize) {
116 return mask == 0x0000FFFF;
117 } else {
118 int constexpr kNumBits = N * sizeof(Scalar);
119 auto constexpr kMustBeSet = (1UL << kNumBits) - 1;
120 return (mask & kMustBeSet) == kMustBeSet;
121 }
122 }
123
124 template <int N>
125 [[nodiscard]] static MOCHI_FORCE_INLINE bool AnyTrue(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 != 0;
130 } else {
131 int constexpr kNumBits = N * sizeof(Scalar);
132 auto constexpr kMayBeSet = (1UL << kNumBits) - 1;
133 return (mask & kMayBeSet) != 0;
134 }
135 }
136
137 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Scalar const* p) {
138 return Simd{*p};
139 }
140
141 template <int i>
142 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Broadcast(Simd v) {
143 return Shuffle<i, i, i, i>(v);
144 }
145
146 template <int N = 4>
147 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMin(Simd a) {
148 static_assert(N >= 2 && N <= 4, "Unsupported N");
149 if constexpr (N == 2) {
150 return Get<0>(Min(a, Broadcast<1>(a)));
151 } else if constexpr (N == 3) {
152 return Get<0>(Min(Min(a, Broadcast<1>(a)), Broadcast<2>(a)));
153 } else {
154 auto tmp = Min(a, Shuffle<1, 2, 3, 0>(a));
155 return Get<0>(Min(tmp, Shuffle<2, 3, 0, 1>(tmp)));
156 }
157 }
158
159 template <int N = 4>
160 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HMax(Simd a) {
161 static_assert(N >= 2 && N <= 4, "Unsupported N");
162 if constexpr (N == 2) {
163 return Get<0>(Max(a, Broadcast<1>(a)));
164 } else if constexpr (N == 3) {
165 return Get<0>(Max(Max(a, Broadcast<1>(a)), Broadcast<2>(a)));
166 } else {
167 auto tmp = Max(a, Shuffle<1, 2, 3, 0>(a));
168 return Get<0>(Max(tmp, Shuffle<2, 3, 0, 1>(tmp)));
169 }
170 }
171
172 template <int N>
173 [[nodiscard]] static MOCHI_FORCE_INLINE Scalar HSum(Simd a) {
174 static_assert(N >= 2 && N <= 4, "Unsupported N");
175 if constexpr (N == 2) {
176 return Get<0>(a) + Get<1>(a);
177 } else if constexpr (N == 3) {
178 return Get<0>(a) + Get<1>(a) + Get<2>(a);
179 } else {
180 return Get<0>(a) + Get<1>(a) + Get<2>(a) + Get<3>(a);
181 }
182 }
183
184 template <int N = kSize>
185 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load([[maybe_unused]] Scalar const* ptr) {
186 static_assert(N >= 0 && N <= 4);
187 if constexpr (N == 0) {
188 return Simd::Zero();
189 } else if constexpr (N == 1) {
190 return Simd{*ptr, 0};
191 } else if constexpr (N == 2) {
192 __m128i mask = _mm_set_epi32(0, 0, -1, -1); // SSE2
193 return _mm_maskload_epi32(ptr, mask); // AVX2
194 } else if constexpr (N == 3) {
195 __m128i mask = _mm_set_epi32(0, -1, -1, -1); // SSE2
196 return _mm_maskload_epi32(ptr, mask); // AVX2
197 } else if constexpr (N == 4) {
198 return _mm_loadu_si128(reinterpret_cast<__m128i const*>(ptr)); // SSE
199 }
200 }
201
202 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Load(Scalar const* ptr, int n) {
203 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
204 // Use the same masks as Simd<float, 4>
205 return _mm_maskload_epi32(ptr, x64_simd::kLoadMasksS4[n]); // AVX2
206 }
207
208 template <int kTupleCount = kSize>
209 MOCHI_FORCE_INLINE static void
210 LoadTransposed(Scalar const* ptr, Simd& out0, Simd& out1, Simd& out2) {
211 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
212 constexpr int kCount0 = Clamp(kTupleCount * 3 - 0, 0, 4);
213 constexpr int kCount1 = Clamp(kTupleCount * 3 - 4, 0, 4);
214 constexpr int kCount2 = Clamp(kTupleCount * 3 - 8, 0, 4);
215 auto a = Simd::Load<kCount0>(ptr).raw; // [0,1,2,3]
216 auto b = Simd::Load<kCount1>(kCount1 == 0 ? ptr : ptr + 4).raw; // [4,5,6,7]
217 auto c = Simd::Load<kCount2>(kCount2 == 0 ? ptr : ptr + 8).raw; // [8,9,10,11]
218
219 auto t0 = _mm_blend_epi32(a, b, 0b0100); // [0,_,6,3]
220 auto t1 = _mm_blend_epi32(t0, c, 0b0010); // [0,9,6,3]
221 out0 = _mm_shuffle_epi32(t1, _MM_SHUFFLE(1, 2, 3, 0)); // [0,3,6,9]
222
223 t0 = _mm_blend_epi32(a, b, 0b1001); // [4,1,_,7]
224 t1 = _mm_blend_epi32(t0, c, 0b0100); // [4,1,10,7]
225 out1 = _mm_shuffle_epi32(t1, _MM_SHUFFLE(2, 3, 0, 1)); // [1,4,7,10]
226
227 t0 = _mm_blend_epi32(a, b, 0b0010); // [_,5,2,_]
228 t1 = _mm_blend_epi32(c, t0, 0b0110); // [8,5,2,11]
229 out2 = _mm_shuffle_epi32(t1, _MM_SHUFFLE(3, 0, 1, 2)); // [2,5,8,11]
230 }
231
232 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Min(Simd a, Simd b) {
233 return _mm_min_epi32(a.raw, b.raw); // SSE4.1
234 }
235
236 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Max(Simd a, Simd b) {
237 return _mm_max_epi32(a.raw, b.raw); // SSE4.1
238 }
239
240 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Select(Simd mask, Simd a, Simd b) {
241 return _mm_blendv_epi8(b.raw, a.raw, mask.raw); // SSE4.1
242 }
243
244 // return Simd{v[x], v[y], v[z], v[w]}
245 template <int x = 0, int y = 1, int z = 2, int w = 3>
246 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Shuffle(Simd v) {
247 static_assert(
248 x >= 0 && x < 4 && y >= 0 && y < 4 && z >= 0 && z < 4 && w >= 0 && w < 4, "Invalid index");
249 if constexpr (x == 0 && y == 1 && z == 2 && w == 3) {
250 return v;
251 } else {
252 return _mm_shuffle_epi32(v.raw, x | (y << 2) | (z << 4) | (w << 6)); // SSE2
253 }
254 }
255
256 // return Simd{a[x], a[y], b[z], b[w]}
257 template <int x = 0, int y = 1, int z = 2, int w = 3>
258 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Shuffle(Simd a, Simd b) {
259 static_assert(
260 x >= 0 && x < 4 && y >= 0 && y < 4 && z >= 0 && z < 4 && w >= 0 && w < 4, "Invalid index");
261 return _mm_castps_si128(_mm_shuffle_ps(
262 _mm_castsi128_ps(a.raw),
263 _mm_castsi128_ps(b.raw),
264 x | (y << 2) | (z << 4) | (w << 6))); // SSE2
265 }
266
267 template <int N = kSize>
268 static MOCHI_FORCE_INLINE void Store([[maybe_unused]] Scalar* ptr, [[maybe_unused]] Simd v) {
269 static_assert(N >= 0 && N <= kSize);
270 if constexpr (N == 0) {
271 } else if constexpr (N < kSize) {
272 // About 3X faster than a masked store on AMD. About the same on Intel.
273 memcpy(ptr, &v, sizeof(Scalar) * N);
274 } else {
275 _mm_storeu_si128(reinterpret_cast<__m128i*>(ptr), v.raw); // SSE
276 }
277 }
278
279 static MOCHI_FORCE_INLINE void Store(Scalar* ptr, Simd v, int n) {
280 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
281 // Faster than masked store on AMD.
282 // clang-format off
283 switch (n) {
284 case 1: Store<1>(ptr, v); break;
285 case 2: Store<2>(ptr, v); break;
286 case 3: Store<3>(ptr, v); break;
287 case 4: Store<4>(ptr, v); break;
288 MOCHI_UNLIKELY default: break;
289 } // clang-format on
290 }
291
292 MOCHI_FORCE_INLINE static int StoreSelected(Scalar* ptr, Simd condition, Simd values) {
293 auto mask = _mm_movemask_ps(_mm_castsi128_ps(condition.raw));
294 // Load the shuffle pattern from a lookup table.
295 auto const* tableRow =
296 reinterpret_cast<__m128i const*>(x64_simd::kStoreSelectedShuffleTableS4[mask]);
297 auto pattern = _mm_load_si128(tableRow);
298 auto packed = _mm_castps_si128(_mm_permutevar_ps(_mm_castsi128_ps(values.raw), pattern));
299 _mm_storeu_si128(reinterpret_cast<__m128i*>(ptr), packed);
300 return _mm_popcnt_u32(mask);
301 }
302
303 template <int kTupleCount = kSize>
304 MOCHI_FORCE_INLINE static void StoreTransposed(Scalar* ptr, Simd a, Simd b, Simd c) {
305 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
306 // a = [0,3,6,9], b = [1,4,7,10], c = [2,5,8,11]
307 auto d = _mm_shuffle_epi32(a.raw, _MM_SHUFFLE(1, 2, 3, 0)); // [0,9,6,3]
308 auto e = _mm_shuffle_epi32(b.raw, _MM_SHUFFLE(2, 3, 0, 1)); // [4,1,10,7]
309 auto f = _mm_shuffle_epi32(c.raw, _MM_SHUFFLE(3, 0, 1, 2)); // [8,5,2,11]
310 auto g = _mm_blend_epi32(_mm_blend_epi32(d, e, 0b0010), f, 0b0100); // [0,1,2,3];
311 auto h = _mm_blend_epi32(_mm_blend_epi32(d, e, 0b1001), f, 0b0010); // [4,5,6,7];
312 auto i = _mm_blend_epi32(_mm_blend_epi32(d, e, 0b0100), f, 0b1001); // [8,9,10,11]
313 constexpr int kCount0 = Clamp(kTupleCount * 3 - 0, 0, 4);
314 constexpr int kCount1 = Clamp(kTupleCount * 3 - 4, 0, 4);
315 constexpr int kCount2 = Clamp(kTupleCount * 3 - 8, 0, 4);
316 Simd::Store<kCount0>(ptr, g);
317 if constexpr (kCount1 > 0) {
318 Simd::Store<kCount1>(ptr + 4, h);
319 }
320 if constexpr (kCount2 > 0) {
321 Simd::Store<kCount2>(ptr + 8, i);
322 }
323 }
324
325 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Zero() {
326 return _mm_setzero_si128(); // SSE2
327 }
328
329 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<(Simd rhs) const {
330 return _mm_cmplt_epi32(this->raw, rhs.raw); // SSE2
331 }
332
333 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>(Simd rhs) const {
334 return _mm_cmpgt_epi32(this->raw, rhs.raw); // SSE2
335 }
336
337 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<=(Simd rhs) const {
338 return ~(*this > rhs); // No native support until AVX512
339 }
340
341 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>=(Simd rhs) const {
342 return ~(*this < rhs); // No native support until AVX512
343 }
344
345 [[nodiscard]] static MOCHI_FORCE_INLINE Simd Equal(Simd a, Simd b) {
346 return _mm_cmpeq_epi32(a.raw, b.raw); // SSE2
347 }
348
349 [[nodiscard]] static MOCHI_FORCE_INLINE Simd NotEqual(Simd a, Simd b) {
350 return ~Equal(a, b); // // No native support until AVX512
351 }
352
353 [[nodiscard]] MOCHI_FORCE_INLINE bool operator==(Simd rhs) const {
354 auto mask = GetMSBitMask(Equal(*this, rhs));
355 return mask == 0xFFFF; // All values equal
356 }
357
358 [[nodiscard]] MOCHI_FORCE_INLINE bool operator!=(Simd rhs) const {
359 auto mask = GetMSBitMask(NotEqual(*this, rhs));
360 return mask != 0; // Any values not equal
361 }
362
363 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator~() const {
364 // _mm_cmpeq_epi32 appears to be the fastest way to fill an SSE register with ones.
365 __m128i ones = _mm_cmpeq_epi32(raw, raw); // SSE2
366 return _mm_xor_si128(raw, ones); // SSE2
367 }
368
369 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-() const {
370 return _mm_sub_epi32(_mm_setzero_si128(), raw); // SSE2, SSE2
371 }
372
373 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator+(Simd rhs) const {
374 return _mm_add_epi32(raw, rhs.raw); // SSE
375 }
376
377 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-(Simd rhs) const {
378 return _mm_sub_epi32(raw, rhs.raw); // SSE
379 }
380
381 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator*(Simd rhs) const {
382 return _mm_mullo_epi32(raw, rhs.raw); // SSE
383 }
384
385 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator/(Simd rhs) const {
386#if MOCHI_ARCH_X64_SVML
387 return _mm_div_epi32(raw, rhs.raw); // SSE
388#else
389 // Fallback
390 return Simd{
391 Get<0>(*this) / Get<0>(rhs),
392 Get<1>(*this) / Get<1>(rhs),
393 Get<2>(*this) / Get<2>(rhs),
394 Get<3>(*this) / Get<3>(rhs)};
395#endif
396 }
397
398 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator&(Simd rhs) const {
399 return _mm_and_si128(raw, rhs.raw); // SSE2
400 }
401
402 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator|(Simd rhs) const {
403 return _mm_or_si128(raw, rhs.raw); // SSE2
404 }
405
406 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator^(Simd rhs) const {
407 return _mm_xor_si128(raw, rhs.raw); // SSE2
408 }
409
410 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<<(int rhs) const {
411 return _mm_slli_epi32(raw, rhs); // SSE2
412 }
413
414 template <int kShift>
415 [[nodiscard]] MOCHI_FORCE_INLINE static Simd ShiftRight(Simd a) {
416 return _mm_srli_epi32(a.raw, kShift); // SSE2
417 }
418
419 private:
420 // Integer mask with the most significant bit of each byte in the vector
421 [[nodiscard]] static MOCHI_FORCE_INLINE int GetMSBitMask(Simd a) {
422 return _mm_movemask_epi8(a.raw); // SSE2
423 }
424};
425
426} // namespace superdex
427
428#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
Simd< T, N > Set(Simd< T, N > a, T value)
Definition simd_inl.h:315
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
bool AnyTrue(T const &a)
Definition basic_utils.h:66
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)