SuperDex Physics C++ API
Loading...
Searching...
No Matches
arm_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 "arm_simd_inl.h" // for IntelliSense
20
21#if MOCHI_USE_SIMD && MOCHI_ARCH_ARM_NEON
22
23namespace superdex {
24
25/***********************************************************************************************
26 Simd<int, 4>
27*/
28template <>
29class Simd<int, 4> {
30 public:
31 MOCHI_NATIVE_SIMD_IMPL_BOILERPLATE(int, 4, int32x4_t);
32
33 MOCHI_FORCE_INLINE Simd(int a, int b, int c = 0, int d = 0) : raw{a, b, c, d} {}
34 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
35 MOCHI_FORCE_INLINE Simd(U a) : raw{vdupq_n_s32(a)} {}
36
37 template <int i>
38 [[nodiscard]] MOCHI_FORCE_INLINE static Scalar Get(Simd v) {
39 static_assert(i >= 0 && i < 4, "Index out of range");
40 return vgetq_lane_s32(v.raw, i);
41 }
42
43 [[nodiscard]] MOCHI_FORCE_INLINE static Scalar Get(Simd v, int i) {
44 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
45 return v.raw[i];
46 }
47
48 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Set(Simd v, int i, Scalar value) {
49 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
50 auto result = v;
51 result.raw[i] = value;
52 return result;
53 }
54
55 template <int i>
56 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Set(Simd v, Scalar value) {
57 static_assert(i >= 0 && i < kSize, "Index out of range");
58 return Set(v, i, value);
59 }
60
61 // Set via 2 int64_t instead of 4 int
62 [[nodiscard]] MOCHI_FORCE_INLINE static Simd SetInt64(int64_t a, int64_t b) {
63 return vreinterpretq_s32_s64(int64x2_t{a, b});
64 }
65
66 template <int N>
67 [[nodiscard]] MOCHI_FORCE_INLINE static bool AllTrue(Simd v) {
68 static_assert(N >= 1 && N <= 4, "Invalid number of components");
69 uint64_t mask =
70 vget_lane_u64(vreinterpret_u64_u16(vqmovn_u32(vreinterpretq_u32_s32(v.raw))), 0);
71 if constexpr (N == kSize) {
72 return mask == 0xFFFFFFFFFFFFFFFFULL;
73 } else {
74 int constexpr kNumBits = N * 16; // 64-bit mask has 16 bits per lane
75 auto constexpr kMustBeSet = (uint64_t(1) << kNumBits) - 1;
76 return (mask & kMustBeSet) == kMustBeSet;
77 }
78 }
79
80 template <int N>
81 [[nodiscard]] MOCHI_FORCE_INLINE static bool AnyTrue(Simd v) {
82 static_assert(N >= 1 && N <= 4, "Invalid number of components");
83 uint64_t mask =
84 vget_lane_u64(vreinterpret_u64_u16(vqmovn_u32(vreinterpretq_u32_s32(v.raw))), 0);
85 if constexpr (N == kSize) {
86 return mask != 0;
87 } else {
88 int constexpr kNumBits = N * 16; // 64-bit mask has 16 bits per lane
89 auto constexpr kMayBeSet = (uint64_t(1) << kNumBits) - 1;
90 return (mask & kMayBeSet) != 0;
91 }
92 }
93
94 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Broadcast(int const* p) {
95 return Simd{*p};
96 }
97
98 template <int i>
99 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Broadcast(Simd v) {
100 static_assert(i >= 0 && i < kSize, "Index out of range");
101 return vdupq_laneq_s32(v.raw, i);
102 }
103
104 template <int N = 4>
105 [[nodiscard]] MOCHI_FORCE_INLINE static Scalar HMin(Simd a) {
106 static_assert(N >= 2 && N <= 4, "Unsupported N");
107 if constexpr (N == 2) {
108 return superdex::Min(a.raw[0], a.raw[1]);
109 } else if constexpr (N == 3) {
110 // HMin({x, y, z, max()})
111 return vminvq_s32(vsetq_lane_s32(std::numeric_limits<Scalar>::max(), a.raw, 3));
112 } else {
113 return vminvq_s32(a.raw);
114 }
115 }
116
117 template <int N = 4>
118 [[nodiscard]] MOCHI_FORCE_INLINE static Scalar HMax(Simd a) {
119 static_assert(N >= 2 && N <= 4, "Unsupported N");
120 if constexpr (N == 2) {
121 return superdex::Max(a.raw[0], a.raw[1]);
122 } else if constexpr (N == 3) {
123 // HMax({x, y, z, lowest()})
124 return vmaxvq_s32(vsetq_lane_s32(std::numeric_limits<Scalar>::lowest(), a.raw, 3));
125 } else {
126 return vmaxvq_s32(a.raw);
127 }
128 }
129
130 template <int N>
131 [[nodiscard]] MOCHI_FORCE_INLINE static Scalar HSum(Simd a) {
132 static_assert(N >= 2 && N <= 4, "Unsupported N");
133 if constexpr (N == 2) {
134 return a.raw[0] + a.raw[1];
135 } else if constexpr (N == 3) {
136 return a.raw[0] + a.raw[1] + a.raw[2];
137 } else if constexpr (N == 4) {
138 return vaddvq_s32(a.raw);
139 }
140 }
141
142 template <int N = kSize>
143 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Load([[maybe_unused]] int const* ptr) {
144 static_assert(N >= 0 && N <= 4);
145 if constexpr (N == 0) {
146 return Simd::Zero();
147 } else if constexpr (N == 1) {
148 return Simd{ptr[0], 0, 0, 0};
149 } else if constexpr (N == 2) {
150 return Simd{ptr[0], ptr[1], 0, 0};
151 } else if constexpr (N == 3) {
152 return Simd{ptr[0], ptr[1], ptr[2], 0};
153 } else {
154 return vld1q_s32(ptr);
155 }
156 }
157
158 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Load(Scalar const* ptr, int n) {
159 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
160 // clang-format off
161 switch (n) {
162 case 1: return Load<1>(ptr);
163 case 2: return Load<2>(ptr);
164 case 3: return Load<3>(ptr);
165 case 4: return Load<4>(ptr);
166 MOCHI_UNLIKELY default: return Zero();
167 } // clang-format on
168 }
169
170 template <int kTupleCount = kSize>
171 MOCHI_FORCE_INLINE static void
172 LoadTransposed(int const* ptr, Simd& out0, Simd& out1, Simd& out2) {
173 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
174 if constexpr (kTupleCount == 1) {
175 out0.raw = int32x4_t{ptr[0], 0, 0, 0};
176 out1.raw = int32x4_t{ptr[1], 0, 0, 0};
177 out2.raw = int32x4_t{ptr[2], 0, 0, 0};
178 } else if constexpr (kTupleCount == 2) {
179 out0.raw = int32x4_t{ptr[0], ptr[3], 0, 0};
180 out1.raw = int32x4_t{ptr[1], ptr[4], 0, 0};
181 out2.raw = int32x4_t{ptr[2], ptr[5], 0, 0};
182 } else if constexpr (kTupleCount == 3) {
183 out0.raw = int32x4_t{ptr[0], ptr[3], ptr[6], 0};
184 out1.raw = int32x4_t{ptr[1], ptr[4], ptr[7], 0};
185 out2.raw = int32x4_t{ptr[2], ptr[5], ptr[8], 0};
186 } else { // kTupleCount == 4 (kSize)
187 int32x4x3_t result = vld3q_s32(ptr);
188 out0.raw = result.val[0];
189 out1.raw = result.val[1];
190 out2.raw = result.val[2];
191 }
192 }
193
194 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Min(Simd a, Simd b) {
195 return vminq_s32(a.raw, b.raw);
196 }
197
198 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Max(Simd a, Simd b) {
199 return vmaxq_s32(a.raw, b.raw);
200 }
201
202 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Select(Simd mask, Simd a, Simd b) {
203 return vbslq_s32(vreinterpretq_u32_s32(mask.raw), a.raw, b.raw);
204 }
205
206 template <int x, int y, int z, int w>
207 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Blend(Simd a, Simd b) {
208 static_assert(
209 x >= 0 && x <= 1 && y >= 0 && y <= 1 && z >= 0 && z <= 1 && w >= 0 && w <= 1,
210 "invalid blend index");
211 int constexpr kCount = x + y + z + w;
212 if constexpr (kCount == 0) {
213 return a;
214 } else if constexpr (kCount == 4) {
215 return b;
216 } else if constexpr (kCount == 1) {
217 // Replace one lane of a with the corresponding lane of b.
218 int constexpr kLane = x ? 0 : (y ? 1 : (z ? 2 : 3));
219 return vcopyq_laneq_s32(a.raw, kLane, b.raw, kLane);
220 } else if constexpr (kCount == 3) {
221 // Replace one lane of b with the corresponding lane of a.
222 int constexpr kLane = !x ? 0 : (!y ? 1 : (!z ? 2 : 3));
223 return vcopyq_laneq_s32(b.raw, kLane, a.raw, kLane);
224 } else if constexpr (x == 1 && y == 1) { // <1,1,0,0>: low half from b, high from a
225 return vcombine_s32(vget_low_s32(b.raw), vget_high_s32(a.raw));
226 } else if constexpr (z == 1 && w == 1) { // <0,0,1,1>: low half from a, high from b
227 return vcombine_s32(vget_low_s32(a.raw), vget_high_s32(b.raw));
228 } else {
229 // Remaining patterns: <1,0,1,0>, <0,1,0,1>, <1,0,0,1>, <0,1,1,0>.
230 // mask lane = -1 -> select from a; mask lane = 0 -> select from b.
231 Simd const mask{int32x4_t{x ? 0 : -1, y ? 0 : -1, z ? 0 : -1, w ? 0 : -1}};
232 return Select(mask, a, b);
233 }
234 }
235
236 // return Simd{v[x], v[y], v[z], v[w]}
237 template <int x = 0, int y = 1, int z = 2, int w = 3>
238 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Shuffle(Simd v) {
239 static_assert(
240 x >= 0 && x < 4 && y >= 0 && y < 4 && z >= 0 && z < 4 && w >= 0 && w < 4, "Invalid index");
241 return Simd{v.raw[x], v.raw[y], v.raw[z], v.raw[w]};
242 }
243
244 template <int x = 0, int y = 1, int z = 2, int w = 3>
245 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Shuffle(Simd a, Simd b) {
246 static_assert(
247 x >= 0 && x < 4 && y >= 0 && y < 4 && z >= 0 && z < 4 && w >= 0 && w < 4, "Invalid index");
248 return Simd{a.raw[x], a.raw[y], b.raw[z], b.raw[w]};
249 }
250
251 template <int N = kSize>
252 MOCHI_FORCE_INLINE static void Store([[maybe_unused]] int* ptr, [[maybe_unused]] Simd v) {
253 static_assert(N >= 0 && N <= kSize);
254 if constexpr (N == 0) {
255 } else if constexpr (N < kSize) {
256 memcpy(ptr, &v, sizeof(int) * N);
257 } else {
258 vst1q_s32(ptr, v.raw);
259 }
260 }
261
262 MOCHI_FORCE_INLINE static void Store(Scalar* ptr, Simd v, int n) {
263 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
264 // clang-format off
265 switch (n) {
266 case 1: Store<1>(ptr, v); break;
267 case 2: Store<2>(ptr, v); break;
268 case 3: Store<3>(ptr, v); break;
269 case 4: Store<4>(ptr, v); break;
270 MOCHI_UNLIKELY default: break;
271 } // clang-format on
272 }
273
274 MOCHI_FORCE_INLINE static int StoreSelected(int* ptr, Simd condition, Simd values) {
275 uint32x4_t shifted = vshrq_n_u32(vreinterpretq_u32_s32(condition.raw), 31);
276 uint32x4_t multipliers = {1, 2, 4, 8}; // Optimizer can pull this out of a loop when appropriate
277 uint32x4_t weighted = vmulq_u32(shifted, multipliers);
278 uint32_t count = vaddvq_u32(shifted);
279 uint32_t mask = vaddvq_u32(weighted);
280 uint8x16_t pattern = vld1q_u8(arm_simd::kStoreSelectedShuffleTableS4[mask]);
281 uint8x16_t packed = vqtbl1q_u8(vreinterpretq_u8_s32(values.raw), pattern);
282 vst1q_s32(ptr, vreinterpretq_s32_u8(packed));
283 return static_cast<int>(count);
284 }
285
286 template <int kTupleCount = kSize>
287 MOCHI_FORCE_INLINE static void StoreTransposed(int* ptr, Simd a, Simd b, Simd c) {
288 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
289 if constexpr (kTupleCount == 1) {
290 ptr[0] = a.raw[0];
291 ptr[1] = b.raw[0];
292 ptr[2] = c.raw[0];
293 } else if constexpr (kTupleCount == 2) {
294 Simd::Store(ptr, Simd{a[0], b[0], c[0], a[1]});
295 ptr[4] = b.raw[1];
296 ptr[5] = c.raw[1];
297 } else if constexpr (kTupleCount == 3) {
298 Simd::Store(ptr + 0, Simd{a[0], b[0], c[0], a[1]});
299 Simd::Store(ptr + 4, Simd{b[1], c[1], a[2], b[2]});
300 ptr[8] = c.raw[2];
301 } else {
302 vst3q_s32(ptr, int32x4x3_t({a.raw, b.raw, c.raw}));
303 }
304 }
305
306 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Zero() {
307 return vdupq_n_s32(0);
308 }
309
310 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<(Simd rhs) const {
311 return vreinterpretq_s32_u32(vcltq_s32(this->raw, rhs.raw));
312 }
313
314 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>(Simd rhs) const {
315 return vreinterpretq_s32_u32(vcgtq_s32(this->raw, rhs.raw));
316 }
317
318 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<=(Simd rhs) const {
319 return vreinterpretq_s32_u32(vcleq_s32(this->raw, rhs.raw));
320 }
321
322 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator>=(Simd rhs) const {
323 return vreinterpretq_s32_u32(vcgeq_s32(this->raw, rhs.raw));
324 }
325
326 [[nodiscard]] MOCHI_FORCE_INLINE static Simd Equal(Simd a, Simd b) {
327 return vreinterpretq_s32_u32(vceqq_s32(a.raw, b.raw));
328 }
329
330 [[nodiscard]] MOCHI_FORCE_INLINE static Simd NotEqual(Simd a, Simd b) {
331 return ~Equal(a, b);
332 }
333
334 [[nodiscard]] MOCHI_FORCE_INLINE bool operator==(Simd rhs) const {
335 uint16x4_t t = vqmovn_u32(vceqq_s32(raw, rhs.raw));
336 return vget_lane_u64(vreinterpret_u64_u16(t), 0) == uint64_t(-1);
337 }
338
339 [[nodiscard]] MOCHI_FORCE_INLINE bool operator!=(Simd rhs) const {
340 return !(*this == rhs);
341 }
342
343 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator~() const {
344 return vmvnq_s32(raw);
345 }
346
347 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-() const {
348 return vnegq_s32(raw);
349 }
350
351 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator+(Simd rhs) const {
352 return vaddq_s32(raw, rhs.raw);
353 }
354
355 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator-(Simd rhs) const {
356 return vsubq_s32(raw, rhs.raw);
357 }
358
359 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator*(Simd rhs) const {
360 return vmulq_s32(raw, rhs.raw);
361 }
362
363 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator/(Simd rhs) const {
364 return Simd{
365 // NEON does not implement integer division
366 raw[0] / rhs.raw[0],
367 raw[1] / rhs.raw[1],
368 raw[2] / rhs.raw[2],
369 raw[3] / rhs.raw[3]};
370 }
371
372 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator&(Simd rhs) const {
373 return vandq_s32(raw, rhs.raw);
374 }
375
376 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator|(Simd rhs) const {
377 return vorrq_s32(raw, rhs.raw);
378 }
379
380 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator^(Simd rhs) const {
381 return veorq_s32(raw, rhs.raw);
382 }
383
384 [[nodiscard]] MOCHI_FORCE_INLINE Simd operator<<(int shift) const {
385 // NOTE: If shift were a constexpr, then vshlq_n_s32 would be better because the shift amount
386 // could be an immediate value. Fortunately, Clang appears to be smart enough to do the right
387 // thing.
388 auto vShift = Simd(shift);
389 return vshlq_s32(raw, vShift.raw);
390 }
391
392 template <int kShift>
393 [[nodiscard]] MOCHI_FORCE_INLINE static Simd ShiftRight(Simd a) {
394 return vshrq_n_s32(a.raw, kShift);
395 }
396};
397
398} // namespace superdex
399
400#endif // MOCHI_USE_SIMD && MOCHI_ARCH_ARM_NEON
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 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)