Skip to main content

argminmax/simd/
simd_i64.rs

1#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
2use super::config::SIMDInstructionSet;
3#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
4use super::generic::{impl_SIMDArgMinMax, impl_SIMDInit_Int};
5#[cfg(any(
6    target_arch = "x86",
7    target_arch = "x86_64",
8    all(target_arch = "arm", feature = "nightly_simd"),
9    target_arch = "aarch64",
10))]
11use super::generic::{SIMDArgMinMax, SIMDInit, SIMDOps};
12#[cfg(any(
13    target_arch = "x86",
14    target_arch = "x86_64",
15    all(target_arch = "arm", feature = "nightly_simd"),
16    target_arch = "aarch64",
17))]
18use crate::SCALAR;
19#[cfg(target_arch = "aarch64")]
20use std::arch::aarch64::*;
21#[cfg(target_arch = "x86")]
22use std::arch::x86::*;
23#[cfg(target_arch = "x86_64")]
24use std::arch::x86_64::*;
25
26/// The dtype-strategy for performing operations on i64 data: (default) Int
27#[cfg(any(
28    target_arch = "x86",
29    target_arch = "x86_64",
30    all(target_arch = "arm", feature = "nightly_simd"),
31    target_arch = "aarch64",
32))]
33use super::super::dtype_strategy::Int;
34
35#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
36const MAX_INDEX: usize = i64::MAX as usize;
37
38// --------------------------------------- AVX2 ----------------------------------------
39
40#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
41mod avx2 {
42    use super::super::config::AVX2;
43    use super::*;
44
45    const LANE_SIZE: usize = AVX2::<Int>::LANE_SIZE_64;
46
47    impl SIMDOps<i64, __m256i, __m256i, LANE_SIZE> for AVX2<Int> {
48        const INITIAL_INDEX: __m256i = unsafe { std::mem::transmute([0i64, 1i64, 2i64, 3i64]) };
49        const INDEX_INCREMENT: __m256i =
50            unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
51        const MAX_INDEX: usize = MAX_INDEX;
52
53        #[inline(always)]
54        unsafe fn _reg_to_arr(reg: __m256i) -> [i64; LANE_SIZE] {
55            std::mem::transmute::<__m256i, [i64; LANE_SIZE]>(reg)
56        }
57
58        #[inline(always)]
59        unsafe fn _mm_loadu(data: *const i64) -> __m256i {
60            _mm256_loadu_si256(data as *const __m256i)
61        }
62
63        #[inline(always)]
64        unsafe fn _mm_add(a: __m256i, b: __m256i) -> __m256i {
65            _mm256_add_epi64(a, b)
66        }
67
68        #[inline(always)]
69        unsafe fn _mm_cmpgt(a: __m256i, b: __m256i) -> __m256i {
70            _mm256_cmpgt_epi64(a, b)
71        }
72
73        #[inline(always)]
74        unsafe fn _mm_cmplt(a: __m256i, b: __m256i) -> __m256i {
75            _mm256_cmpgt_epi64(b, a)
76        }
77
78        #[inline(always)]
79        unsafe fn _mm_blendv(a: __m256i, b: __m256i, mask: __m256i) -> __m256i {
80            _mm256_blendv_epi8(a, b, mask)
81        }
82    }
83
84    impl_SIMDInit_Int!(i64, __m256i, __m256i, LANE_SIZE, AVX2<Int>);
85
86    impl_SIMDArgMinMax!(
87        i64,
88        __m256i,
89        __m256i,
90        LANE_SIZE,
91        SCALAR<Int>,
92        AVX2<Int>,
93        "avx2"
94    );
95}
96
97// ---------------------------------------- SSE ----------------------------------------
98
99#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
100mod sse {
101    use super::super::config::SSE;
102    use super::*;
103
104    const LANE_SIZE: usize = SSE::<Int>::LANE_SIZE_64;
105
106    impl SIMDOps<i64, __m128i, __m128i, LANE_SIZE> for SSE<Int> {
107        const INITIAL_INDEX: __m128i = unsafe { std::mem::transmute([0i64, 1i64]) };
108        const INDEX_INCREMENT: __m128i =
109            unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
110        const MAX_INDEX: usize = MAX_INDEX;
111
112        #[inline(always)]
113        unsafe fn _reg_to_arr(reg: __m128i) -> [i64; LANE_SIZE] {
114            std::mem::transmute::<__m128i, [i64; LANE_SIZE]>(reg)
115        }
116
117        #[inline(always)]
118        unsafe fn _mm_loadu(data: *const i64) -> __m128i {
119            _mm_loadu_si128(data as *const __m128i)
120        }
121
122        #[inline(always)]
123        unsafe fn _mm_add(a: __m128i, b: __m128i) -> __m128i {
124            _mm_add_epi64(a, b)
125        }
126
127        #[inline(always)]
128        unsafe fn _mm_cmpgt(a: __m128i, b: __m128i) -> __m128i {
129            _mm_cmpgt_epi64(a, b)
130        }
131
132        #[inline(always)]
133        unsafe fn _mm_cmplt(a: __m128i, b: __m128i) -> __m128i {
134            _mm_cmpgt_epi64(b, a)
135        }
136
137        #[inline(always)]
138        unsafe fn _mm_blendv(a: __m128i, b: __m128i, mask: __m128i) -> __m128i {
139            _mm_blendv_epi8(a, b, mask)
140        }
141    }
142
143    impl_SIMDInit_Int!(i64, __m128i, __m128i, LANE_SIZE, SSE<Int>);
144
145    impl_SIMDArgMinMax!(
146        i64,
147        __m128i,
148        __m128i,
149        LANE_SIZE,
150        SCALAR<Int>,
151        SSE<Int>,
152        "sse4.2"
153    );
154}
155
156// -------------------------------------- AVX512 ---------------------------------------
157
158#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
159#[cfg(feature = "nightly_simd")]
160mod avx512 {
161    use super::super::config::AVX512;
162    use super::*;
163
164    const LANE_SIZE: usize = AVX512::<Int>::LANE_SIZE_64;
165
166    impl SIMDOps<i64, __m512i, u8, LANE_SIZE> for AVX512<Int> {
167        const INITIAL_INDEX: __m512i =
168            unsafe { std::mem::transmute([0i64, 1i64, 2i64, 3i64, 4i64, 5i64, 6i64, 7i64]) };
169        const INDEX_INCREMENT: __m512i =
170            unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
171        const MAX_INDEX: usize = MAX_INDEX;
172
173        #[inline(always)]
174        unsafe fn _reg_to_arr(reg: __m512i) -> [i64; LANE_SIZE] {
175            std::mem::transmute::<__m512i, [i64; LANE_SIZE]>(reg)
176        }
177
178        #[inline(always)]
179        unsafe fn _mm_loadu(data: *const i64) -> __m512i {
180            _mm512_loadu_epi64(data as *const i64)
181        }
182
183        #[inline(always)]
184        unsafe fn _mm_add(a: __m512i, b: __m512i) -> __m512i {
185            _mm512_add_epi64(a, b)
186        }
187
188        #[inline(always)]
189        unsafe fn _mm_cmpgt(a: __m512i, b: __m512i) -> u8 {
190            _mm512_cmpgt_epi64_mask(a, b)
191        }
192
193        #[inline(always)]
194        unsafe fn _mm_cmplt(a: __m512i, b: __m512i) -> u8 {
195            _mm512_cmpgt_epi64_mask(b, a)
196        }
197
198        #[inline(always)]
199        unsafe fn _mm_blendv(a: __m512i, b: __m512i, mask: u8) -> __m512i {
200            _mm512_mask_blend_epi64(mask, a, b)
201        }
202    }
203
204    impl_SIMDInit_Int!(i64, __m512i, u8, LANE_SIZE, AVX512<Int>);
205
206    impl_SIMDArgMinMax!(
207        i64,
208        __m512i,
209        u8,
210        LANE_SIZE,
211        SCALAR<Int>,
212        AVX512<Int>,
213        "avx512f"
214    );
215}
216
217// --------------------------------------- NEON ----------------------------------------
218
219// There are NEON SIMD intrinsics for i64, but
220//  - for arm we miss the vcgt_ and vclt_ intrinsics.
221//  - for aarch64 the required intrinsics are present (on nightly)
222
223#[cfg(target_arch = "arm")]
224#[cfg(feature = "nightly_simd")]
225mod neon {
226    use super::super::config::NEON;
227    use super::super::generic::{unimpl_SIMDArgMinMax, unimpl_SIMDInit, unimpl_SIMDOps};
228    use super::*;
229
230    // We need to (un)implement the SIMD trait for the NEON struct as otherwise the
231    // compiler will complain that the trait is not implemented for the struct -
232    // even though we are not using the trait for the NEON struct when dealing with
233    // > 64 bit data types.
234    unimpl_SIMDOps!(i64, usize, NEON<Int>);
235    unimpl_SIMDInit!(i64, usize, NEON<Int>);
236    unimpl_SIMDArgMinMax!(i64, usize, SCALAR<Int>, NEON<Int>);
237}
238
239#[cfg(target_arch = "aarch64")] // stable for AArch64
240mod neon {
241    use super::super::config::NEON;
242    use super::*;
243
244    const LANE_SIZE: usize = NEON::<Int>::LANE_SIZE_64;
245
246    impl SIMDOps<i64, int64x2_t, uint64x2_t, LANE_SIZE> for NEON<Int> {
247        const INITIAL_INDEX: int64x2_t = unsafe { std::mem::transmute([0i64, 1i64]) };
248        const INDEX_INCREMENT: int64x2_t =
249            unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
250        const MAX_INDEX: usize = MAX_INDEX;
251
252        #[inline(always)]
253        unsafe fn _reg_to_arr(reg: int64x2_t) -> [i64; LANE_SIZE] {
254            std::mem::transmute::<int64x2_t, [i64; LANE_SIZE]>(reg)
255        }
256
257        #[inline(always)]
258        unsafe fn _mm_loadu(data: *const i64) -> int64x2_t {
259            vld1q_s64(data)
260        }
261
262        #[inline(always)]
263        unsafe fn _mm_add(a: int64x2_t, b: int64x2_t) -> int64x2_t {
264            vaddq_s64(a, b)
265        }
266
267        #[inline(always)]
268        unsafe fn _mm_cmpgt(a: int64x2_t, b: int64x2_t) -> uint64x2_t {
269            vcgtq_s64(a, b)
270        }
271
272        #[inline(always)]
273        unsafe fn _mm_cmplt(a: int64x2_t, b: int64x2_t) -> uint64x2_t {
274            vcltq_s64(a, b)
275        }
276
277        #[inline(always)]
278        unsafe fn _mm_blendv(a: int64x2_t, b: int64x2_t, mask: uint64x2_t) -> int64x2_t {
279            vbslq_s64(mask, b, a)
280        }
281    }
282
283    impl_SIMDInit_Int!(i64, int64x2_t, uint64x2_t, LANE_SIZE, NEON<Int>);
284
285    impl_SIMDArgMinMax!(
286        i64,
287        int64x2_t,
288        uint64x2_t,
289        LANE_SIZE,
290        SCALAR<Int>,
291        NEON<Int>,
292        "neon"
293    );
294}
295
296// ======================================= TESTS =======================================
297
298#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
299#[cfg(test)]
300mod tests {
301    use rstest::rstest;
302    use rstest_reuse::{self, *};
303    use std::marker::PhantomData;
304
305    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
306    #[cfg(feature = "nightly_simd")]
307    use crate::simd::config::AVX512;
308    #[cfg(target_arch = "aarch64")]
309    use crate::simd::config::NEON;
310    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
311    use crate::simd::config::{AVX2, SSE};
312    use crate::{Int, SIMDArgMinMax, SCALAR};
313
314    use super::super::test_utils::{
315        test_first_index_identical_values_argminmax, test_return_same_result_argminmax,
316    };
317
318    use dev_utils::utils;
319
320    fn get_array_i64(n: usize) -> Vec<i64> {
321        utils::SampleUniformFullRange::get_random_array(n)
322    }
323
324    // The scalar implementation
325    const SCALAR_STRATEGY: SCALAR<Int> = SCALAR {
326        _dtype_strategy: PhantomData::<Int>,
327    };
328
329    // ------------ Template for x86 / x86_64 -------------
330
331    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
332    #[template]
333    #[rstest]
334    #[case::sse(SSE {_dtype_strategy: PhantomData::<Int>}, is_x86_feature_detected!("sse4.2"))]
335    #[case::avx2(AVX2 {_dtype_strategy: PhantomData::<Int>}, is_x86_feature_detected!("avx2"))]
336    #[cfg_attr(feature = "nightly_simd", case::avx512(AVX512 {_dtype_strategy: PhantomData::<Int>}, is_x86_feature_detected!("avx512f")))]
337    fn simd_implementations<T, SIMDV, SIMDM, const LANE_SIZE: usize>(
338        #[case] simd: T,
339        #[case] simd_available: bool,
340    ) {
341    }
342
343    // --------------- Template for AArch64 ---------------
344
345    #[cfg(target_arch = "aarch64")]
346    #[template]
347    #[rstest]
348    #[case::neon(NEON {_dtype_strategy: PhantomData::<Int>}, true)]
349    fn simd_implementations<T, SIMDV, SIMDM, const LANE_SIZE: usize>(
350        #[case] simd: T,
351        #[case] simd_available: bool,
352    ) {
353    }
354
355    // ----------------- The actual tests -----------------
356
357    #[apply(simd_implementations)]
358    fn test_first_index_is_returned_when_identical_values_found<
359        T,
360        SIMDV,
361        SIMDM,
362        const LANE_SIZE: usize,
363    >(
364        #[case] simd: T,
365        #[case] simd_available: bool,
366    ) where
367        T: SIMDArgMinMax<i64, SIMDV, SIMDM, LANE_SIZE, SCALAR<Int>>,
368        SIMDV: Copy,
369        SIMDM: Copy,
370    {
371        if !simd_available {
372            return;
373        }
374        test_first_index_identical_values_argminmax(SCALAR_STRATEGY, simd);
375    }
376
377    #[apply(simd_implementations)]
378    fn test_return_same_result<T, SIMDV, SIMDM, const LANE_SIZE: usize>(
379        #[case] simd: T,
380        #[case] simd_available: bool,
381    ) where
382        T: SIMDArgMinMax<i64, SIMDV, SIMDM, LANE_SIZE, SCALAR<Int>>,
383        SIMDV: Copy,
384        SIMDM: Copy,
385    {
386        if !simd_available {
387            return;
388        }
389        test_return_same_result_argminmax(get_array_i64, SCALAR_STRATEGY, simd);
390    }
391}