1#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
15use super::config::SIMDInstructionSet;
16#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
17use super::generic::{impl_SIMDArgMinMax, impl_SIMDInit_Int};
18#[cfg(any(
19 target_arch = "x86",
20 target_arch = "x86_64",
21 all(target_arch = "arm", feature = "nightly_simd"),
22 target_arch = "aarch64",
23))]
24use super::generic::{SIMDArgMinMax, SIMDInit, SIMDOps};
25#[cfg(any(
26 target_arch = "x86",
27 target_arch = "x86_64",
28 all(target_arch = "arm", feature = "nightly_simd"),
29 target_arch = "aarch64",
30))]
31use crate::SCALAR;
32#[cfg(target_arch = "aarch64")]
33use std::arch::aarch64::*;
34#[cfg(target_arch = "x86")]
35use std::arch::x86::*;
36#[cfg(target_arch = "x86_64")]
37use std::arch::x86_64::*;
38
39#[cfg(any(
41 target_arch = "x86",
42 target_arch = "x86_64",
43 all(target_arch = "arm", feature = "nightly_simd"),
44 target_arch = "aarch64",
45))]
46use super::super::dtype_strategy::Int;
47
48#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
49use super::task::{max_index_value, min_index_value};
50
51#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
52const XOR_VALUE: i64 = -0x8000000000000000; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
55#[inline(always)]
56#[allow(unnecessary_transmutes)]
57fn _i64ord_to_u64(ord_i64: i64) -> u64 {
58 unsafe { std::mem::transmute::<i64, u64>(ord_i64 ^ XOR_VALUE) }
60}
61
62#[cfg(any(target_arch = "x86", target_arch = "x86_64",))]
63const MAX_INDEX: usize = i64::MAX as usize; #[cfg(target_arch = "aarch64")]
65const MAX_INDEX: usize = u64::MAX as usize; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
70mod avx2 {
71 use super::super::config::AVX2;
72 use super::*;
73
74 const LANE_SIZE: usize = AVX2::<Int>::LANE_SIZE_64;
75 const XOR_MASK: __m256i = unsafe { std::mem::transmute([XOR_VALUE; LANE_SIZE]) };
76
77 #[inline(always)]
78 unsafe fn _u64_as_m256i_to_i64ord(u64_as_m256i: __m256i) -> __m256i {
79 _mm256_xor_si256(u64_as_m256i, XOR_MASK)
82 }
83
84 #[inline(always)]
85 unsafe fn _reg_to_i64_arr(reg: __m256i) -> [i64; LANE_SIZE] {
86 std::mem::transmute::<__m256i, [i64; LANE_SIZE]>(reg)
87 }
88
89 impl SIMDOps<u64, __m256i, __m256i, LANE_SIZE> for AVX2<Int> {
90 const INITIAL_INDEX: __m256i = unsafe { std::mem::transmute([0i64, 1i64, 2i64, 3i64]) };
91 const INDEX_INCREMENT: __m256i =
92 unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
93 const MAX_INDEX: usize = MAX_INDEX;
94
95 #[inline(always)]
96 unsafe fn _reg_to_arr(_: __m256i) -> [u64; LANE_SIZE] {
97 unimplemented!()
101 }
102
103 #[inline(always)]
104 unsafe fn _mm_loadu(data: *const u64) -> __m256i {
105 _u64_as_m256i_to_i64ord(_mm256_loadu_si256(data as *const __m256i))
106 }
107
108 #[inline(always)]
109 unsafe fn _mm_add(a: __m256i, b: __m256i) -> __m256i {
110 _mm256_add_epi64(a, b)
111 }
112
113 #[inline(always)]
114 unsafe fn _mm_cmpgt(a: __m256i, b: __m256i) -> __m256i {
115 _mm256_cmpgt_epi64(a, b)
116 }
117
118 #[inline(always)]
119 unsafe fn _mm_cmplt(a: __m256i, b: __m256i) -> __m256i {
120 _mm256_cmpgt_epi64(b, a)
121 }
122
123 #[inline(always)]
124 unsafe fn _mm_blendv(a: __m256i, b: __m256i, mask: __m256i) -> __m256i {
125 _mm256_blendv_epi8(a, b, mask)
126 }
127
128 #[inline(always)]
129 unsafe fn _horiz_min(index: __m256i, value: __m256i) -> (usize, u64) {
130 let index_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(index);
131 let value_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(value);
132 let (min_index, min_value) = min_index_value(&index_arr, &value_arr);
133 (min_index as usize, _i64ord_to_u64(min_value))
134 }
135
136 #[inline(always)]
137 unsafe fn _horiz_max(index: __m256i, value: __m256i) -> (usize, u64) {
138 let index_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(index);
139 let value_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(value);
140 let (max_index, max_value) = max_index_value(&index_arr, &value_arr);
141 (max_index as usize, _i64ord_to_u64(max_value))
142 }
143 }
144
145 impl_SIMDInit_Int!(u64, __m256i, __m256i, LANE_SIZE, AVX2<Int>);
146
147 impl_SIMDArgMinMax!(
148 u64,
149 __m256i,
150 __m256i,
151 LANE_SIZE,
152 SCALAR<Int>,
153 AVX2<Int>,
154 "avx2"
155 );
156}
157
158#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
161mod sse {
162 use super::super::config::SSE;
163 use super::*;
164
165 const LANE_SIZE: usize = SSE::<Int>::LANE_SIZE_64;
166 const XOR_MASK: __m128i = unsafe { std::mem::transmute([XOR_VALUE; LANE_SIZE]) };
167
168 #[inline(always)]
169 unsafe fn _u64_as_m128i_to_i64ord(u64_as_m128i: __m128i) -> __m128i {
170 _mm_xor_si128(u64_as_m128i, XOR_MASK)
173 }
174
175 #[inline(always)]
176 unsafe fn _reg_to_i64_arr(reg: __m128i) -> [i64; LANE_SIZE] {
177 std::mem::transmute::<__m128i, [i64; LANE_SIZE]>(reg)
178 }
179
180 impl SIMDOps<u64, __m128i, __m128i, LANE_SIZE> for SSE<Int> {
181 const INITIAL_INDEX: __m128i = unsafe { std::mem::transmute([0i64, 1i64]) };
182 const INDEX_INCREMENT: __m128i =
183 unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
184 const MAX_INDEX: usize = MAX_INDEX;
185
186 #[inline(always)]
187 unsafe fn _reg_to_arr(_: __m128i) -> [u64; LANE_SIZE] {
188 unimplemented!()
192 }
193
194 #[inline(always)]
195 unsafe fn _mm_loadu(data: *const u64) -> __m128i {
196 _u64_as_m128i_to_i64ord(_mm_loadu_si128(data as *const __m128i))
197 }
198
199 #[inline(always)]
200 unsafe fn _mm_add(a: __m128i, b: __m128i) -> __m128i {
201 _mm_add_epi64(a, b)
202 }
203
204 #[inline(always)]
205 unsafe fn _mm_cmpgt(a: __m128i, b: __m128i) -> __m128i {
206 _mm_cmpgt_epi64(a, b)
207 }
208
209 #[inline(always)]
210 unsafe fn _mm_cmplt(a: __m128i, b: __m128i) -> __m128i {
211 _mm_cmpgt_epi64(b, a)
212 }
213
214 #[inline(always)]
215 unsafe fn _mm_blendv(a: __m128i, b: __m128i, mask: __m128i) -> __m128i {
216 _mm_blendv_epi8(a, b, mask)
217 }
218
219 #[inline(always)]
220 unsafe fn _horiz_min(index: __m128i, value: __m128i) -> (usize, u64) {
221 let index_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(index);
222 let value_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(value);
223 let (min_index, min_value) = min_index_value(&index_arr, &value_arr);
224 (min_index as usize, _i64ord_to_u64(min_value))
225 }
226
227 #[inline(always)]
228 unsafe fn _horiz_max(index: __m128i, value: __m128i) -> (usize, u64) {
229 let index_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(index);
230 let value_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(value);
231 let (max_index, max_value) = max_index_value(&index_arr, &value_arr);
232 (max_index as usize, _i64ord_to_u64(max_value))
233 }
234 }
235
236 impl_SIMDInit_Int!(u64, __m128i, __m128i, LANE_SIZE, SSE<Int>);
237
238 impl_SIMDArgMinMax!(
239 u64,
240 __m128i,
241 __m128i,
242 LANE_SIZE,
243 SCALAR<Int>,
244 SSE<Int>,
245 "sse4.2"
246 );
247}
248
249#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
252#[cfg(feature = "nightly_simd")]
253mod avx512 {
254 use super::super::config::AVX512;
255 use super::*;
256
257 const LANE_SIZE: usize = AVX512::<Int>::LANE_SIZE_64;
258 const XOR_MASK: __m512i = unsafe { std::mem::transmute([XOR_VALUE; LANE_SIZE]) };
259
260 #[inline(always)]
261 unsafe fn _u64_as_m512i_to_i64ord(u64_as_m512i: __m512i) -> __m512i {
262 _mm512_xor_si512(u64_as_m512i, XOR_MASK)
265 }
266
267 #[inline(always)]
268 unsafe fn _reg_to_i64_arr(reg: __m512i) -> [i64; LANE_SIZE] {
269 std::mem::transmute::<__m512i, [i64; LANE_SIZE]>(reg)
270 }
271
272 impl SIMDOps<u64, __m512i, u8, LANE_SIZE> for AVX512<Int> {
273 const INITIAL_INDEX: __m512i =
274 unsafe { std::mem::transmute([0i64, 1i64, 2i64, 3i64, 4i64, 5i64, 6i64, 7i64]) };
275 const INDEX_INCREMENT: __m512i =
276 unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
277 const MAX_INDEX: usize = MAX_INDEX;
278
279 #[inline(always)]
280 unsafe fn _reg_to_arr(_: __m512i) -> [u64; LANE_SIZE] {
281 unimplemented!()
285 }
286
287 #[inline(always)]
288 unsafe fn _mm_loadu(data: *const u64) -> __m512i {
289 _u64_as_m512i_to_i64ord(_mm512_loadu_epi64(data as *const i64))
290 }
291
292 #[inline(always)]
293 unsafe fn _mm_add(a: __m512i, b: __m512i) -> __m512i {
294 _mm512_add_epi64(a, b)
295 }
296
297 #[inline(always)]
298 unsafe fn _mm_cmpgt(a: __m512i, b: __m512i) -> u8 {
299 _mm512_cmpgt_epi64_mask(a, b)
300 }
301
302 #[inline(always)]
303 unsafe fn _mm_cmplt(a: __m512i, b: __m512i) -> u8 {
304 _mm512_cmplt_epi64_mask(a, b)
305 }
306
307 #[inline(always)]
308 unsafe fn _mm_blendv(a: __m512i, b: __m512i, mask: u8) -> __m512i {
309 _mm512_mask_blend_epi64(mask, a, b)
310 }
311
312 #[inline(always)]
313 unsafe fn _horiz_min(index: __m512i, value: __m512i) -> (usize, u64) {
314 let index_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(index);
315 let value_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(value);
316 let (min_index, min_value) = min_index_value(&index_arr, &value_arr);
317 (min_index as usize, _i64ord_to_u64(min_value))
318 }
319
320 #[inline(always)]
321 unsafe fn _horiz_max(index: __m512i, value: __m512i) -> (usize, u64) {
322 let index_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(index);
323 let value_arr: [i64; LANE_SIZE] = _reg_to_i64_arr(value);
324 let (max_index, max_value) = max_index_value(&index_arr, &value_arr);
325 (max_index as usize, _i64ord_to_u64(max_value))
326 }
327 }
328
329 impl_SIMDInit_Int!(u64, __m512i, u8, LANE_SIZE, AVX512<Int>);
330
331 impl_SIMDArgMinMax!(
332 u64,
333 __m512i,
334 u8,
335 LANE_SIZE,
336 SCALAR<Int>,
337 AVX512<Int>,
338 "avx512f"
339 );
340}
341
342#[cfg(target_arch = "arm")]
349#[cfg(feature = "nightly_simd")]
350mod neon {
351 use super::super::config::NEON;
352 use super::super::generic::{unimpl_SIMDArgMinMax, unimpl_SIMDInit, unimpl_SIMDOps};
353 use super::*;
354
355 unimpl_SIMDOps!(u64, usize, NEON<Int>);
360 unimpl_SIMDInit!(u64, usize, NEON<Int>);
361 unimpl_SIMDArgMinMax!(u64, usize, SCALAR<Int>, NEON<Int>);
362}
363
364#[cfg(target_arch = "aarch64")] mod neon {
366 use super::super::config::NEON;
367 use super::*;
368
369 const LANE_SIZE: usize = NEON::<Int>::LANE_SIZE_64;
370
371 impl SIMDOps<u64, uint64x2_t, uint64x2_t, LANE_SIZE> for NEON<Int> {
372 const INITIAL_INDEX: uint64x2_t = unsafe { std::mem::transmute([0u64, 1u64]) };
373 const INDEX_INCREMENT: uint64x2_t =
374 unsafe { std::mem::transmute([LANE_SIZE as i64; LANE_SIZE]) };
375 const MAX_INDEX: usize = MAX_INDEX;
376
377 #[inline(always)]
378 unsafe fn _reg_to_arr(reg: uint64x2_t) -> [u64; LANE_SIZE] {
379 std::mem::transmute::<uint64x2_t, [u64; LANE_SIZE]>(reg)
380 }
381
382 #[inline(always)]
383 unsafe fn _mm_loadu(data: *const u64) -> uint64x2_t {
384 vld1q_u64(data)
385 }
386
387 #[inline(always)]
388 unsafe fn _mm_add(a: uint64x2_t, b: uint64x2_t) -> uint64x2_t {
389 vaddq_u64(a, b)
390 }
391
392 #[inline(always)]
393 unsafe fn _mm_cmpgt(a: uint64x2_t, b: uint64x2_t) -> uint64x2_t {
394 vcgtq_u64(a, b)
395 }
396
397 #[inline(always)]
398 unsafe fn _mm_cmplt(a: uint64x2_t, b: uint64x2_t) -> uint64x2_t {
399 vcltq_u64(a, b)
400 }
401
402 #[inline(always)]
403 unsafe fn _mm_blendv(a: uint64x2_t, b: uint64x2_t, mask: uint64x2_t) -> uint64x2_t {
404 vbslq_u64(mask, b, a)
405 }
406 }
407
408 impl_SIMDInit_Int!(u64, uint64x2_t, uint64x2_t, LANE_SIZE, NEON<Int>);
409
410 impl_SIMDArgMinMax!(
411 u64,
412 uint64x2_t,
413 uint64x2_t,
414 LANE_SIZE,
415 SCALAR<Int>,
416 NEON<Int>,
417 "neon"
418 );
419}
420
421#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
424#[cfg(test)]
425mod tests {
426 use rstest::rstest;
427 use rstest_reuse::{self, *};
428 use std::marker::PhantomData;
429
430 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
431 #[cfg(feature = "nightly_simd")]
432 use crate::simd::config::AVX512;
433 #[cfg(target_arch = "aarch64")]
434 use crate::simd::config::NEON;
435 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
436 use crate::simd::config::{AVX2, SSE};
437 use crate::{Int, SIMDArgMinMax, SCALAR};
438
439 use super::super::test_utils::{
440 test_first_index_identical_values_argminmax, test_return_same_result_argminmax,
441 };
442
443 use dev_utils::utils;
444
445 fn get_array_u64(n: usize) -> Vec<u64> {
446 utils::SampleUniformFullRange::get_random_array(n)
447 }
448
449 const SCALAR_STRATEGY: SCALAR<Int> = SCALAR {
451 _dtype_strategy: PhantomData::<Int>,
452 };
453
454 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
457 #[template]
458 #[rstest]
459 #[case::sse(SSE {_dtype_strategy: PhantomData::<Int>}, is_x86_feature_detected!("sse4.2"))]
460 #[case::avx2(AVX2 {_dtype_strategy: PhantomData::<Int>}, is_x86_feature_detected!("avx2"))]
461 #[cfg_attr(feature = "nightly_simd", case::avx512(AVX512 {_dtype_strategy: PhantomData::<Int>}, is_x86_feature_detected!("avx512f")))]
462 fn simd_implementations<T, SIMDV, SIMDM, const LANE_SIZE: usize>(
463 #[case] simd: T,
464 #[case] simd_available: bool,
465 ) {
466 }
467
468 #[cfg(target_arch = "aarch64")]
471 #[template]
472 #[rstest]
473 #[case::neon(NEON {_dtype_strategy: PhantomData::<Int>}, true)]
474 fn simd_implementations<T, SIMDV, SIMDM, const LANE_SIZE: usize>(
475 #[case] simd: T,
476 #[case] simd_available: bool,
477 ) {
478 }
479
480 #[apply(simd_implementations)]
483 fn test_first_index_is_returned_when_identical_values_found<
484 T,
485 SIMDV,
486 SIMDM,
487 const LANE_SIZE: usize,
488 >(
489 #[case] simd: T,
490 #[case] simd_available: bool,
491 ) where
492 T: SIMDArgMinMax<u64, SIMDV, SIMDM, LANE_SIZE, SCALAR<Int>>,
493 SIMDV: Copy,
494 SIMDM: Copy,
495 {
496 if !simd_available {
497 return;
498 }
499 test_first_index_identical_values_argminmax(SCALAR_STRATEGY, simd);
500 }
501
502 #[apply(simd_implementations)]
503 fn test_return_same_result<T, SIMDV, SIMDM, const LANE_SIZE: usize>(
504 #[case] simd: T,
505 #[case] simd_available: bool,
506 ) where
507 T: SIMDArgMinMax<u64, SIMDV, SIMDM, LANE_SIZE, SCALAR<Int>>,
508 SIMDV: Copy,
509 SIMDM: Copy,
510 {
511 if !simd_available {
512 return;
513 }
514 test_return_same_result_argminmax(get_array_u64, SCALAR_STRATEGY, simd);
515 }
516}