x86_simd/integers/
int256.rs

1//! AVX family 256 bit SIMD values over integer data.
2
3// Allow path statements so that the compiler runs into a reference to the Simd256Integer::_ASSERT_LANES_MATCH_SIZE
4// it will fail to compile appropriately/successfully without warning us about "effectless" code.
5#![allow(path_statements)]
6
7#[cfg(target_arch = "x86_64")]
8use core::arch::x86_64::__m256i;
9
10#[cfg(target_arch = "x86")]
11use core::arch::x86::__m256i;
12
13use core::any::TypeId;
14use core::fmt::Debug;
15use core::marker::PhantomData;
16use core::mem::{transmute, transmute_copy};
17use core::ops::{Add, AddAssign, Not};
18use crate::sealed::Sealed;
19
20/// Marker trait implemented on all scalar (primitive) types that can be packed into a [`Simd256Integer`].
21pub trait Simd256Scalar: Sealed 
22    + Sized 
23    + Copy 
24    + Add<Self, Output = Self> 
25    + AddAssign 
26    + Default 
27    + Not<Output = Self>
28    + PartialEq
29    + 'static
30{
31    /// A value of this scalar type with all bits set to `0`.
32    const ZERO: Self;
33}
34
35/// Marker trait implemented on all scalar (primitive) types that support saturating addition AVX2 operations.
36pub trait Simd256SaturatingAdd: Simd256Scalar {}
37
38/// Marker trait on all scalar (primitive) types that support the absolute value AVX2 operations.
39pub trait Simd256IntegerAbs: Simd256Scalar {}
40
41macro_rules! impl_scalars {
42    ( $($t:ty $(| $extra:ident )*)* ) => {$(
43            impl Simd256Scalar for $t {
44                const ZERO: $t = 0;
45            }
46
47            $(
48                impl $extra for $t {}
49            )*
50    )*};
51}
52
53impl_scalars! {
54    u8
55    | Simd256SaturatingAdd
56
57    i8
58    | Simd256SaturatingAdd
59    | Simd256IntegerAbs
60
61    u16
62    | Simd256SaturatingAdd
63
64    i16
65    | Simd256SaturatingAdd
66    | Simd256IntegerAbs 
67
68    u32
69
70    i32
71    | Simd256IntegerAbs
72    
73    u64
74    i64
75}
76
77/// 32 [u8] values in a SIMD vector backed by AVX-family operations or a fallback.
78#[allow(non_camel_case_types)]
79pub type u8x32 = Simd256Integer<u8, 32>;
80
81/// 32 [i8] values in a SIMD vector backed by AVX-family operations or a fallback.
82#[allow(non_camel_case_types)]
83pub type i8x32 = Simd256Integer<i8, 32>;
84
85/// 16 [u16] values in a SIMD vector backed by AVX-family operations or a fallback.
86#[allow(non_camel_case_types)]
87pub type u16x16 = Simd256Integer<u16, 16>;
88
89/// 16 [i16] values in a SIMD vector backed by AVX-family operations or a fallback.
90#[allow(non_camel_case_types)]
91pub type i16x16 = Simd256Integer<i16, 16>;
92
93/// 8 [u32] values in a SIMD vector backed by AVX-family operations or a fallback.
94#[allow(non_camel_case_types)]
95pub type u32x8 = Simd256Integer<u32, 8>;
96
97/// 8 [i32] values in a SIMD vector backed by AVX-family operations or a fallback.
98#[allow(non_camel_case_types)]
99pub type i32x8 = Simd256Integer<i32, 8>;
100
101/// 4 [u64] values in a SIMD vector backed by AVX-family operations or a fallback.
102#[allow(non_camel_case_types)]
103pub type u64x4 = Simd256Integer<u64, 4>;
104
105/// 4 [i64] values in a SIMD vector backed by AVX-family operations or a fallback.
106#[allow(non_camel_case_types)]
107pub type i64x4 = Simd256Integer<i64, 4>;
108
109/// This type packs integer data into a 256 bit value and attempts to use AVX family instructions if available for all
110/// operations.
111///
112/// If AVX is not determined to be available, this struct has a fallback implementation that will be slower, but still
113/// mathematically correct, and may attempt to use SSE family instructions if possible.
114#[derive(Clone, Copy, Debug)]
115pub struct Simd256Integer<S: Simd256Scalar, const LANES: usize> {
116    /// phantom data to make generics are used.
117    phantom: PhantomData<[S; LANES]>,
118
119    /// Underlying bit storage.
120    pub inner: Simd256IntegerInner,
121}
122
123/// The internal representation for 256-bit integer data SIMD values used by [Simd256Integer].
124#[derive(Clone, Copy)]
125pub union Simd256IntegerInner {
126    /// If the AVX CPU feature is available, this field of the union will be active and contain am [__m256i] value.
127    #[cfg(any(feature = "std", target_feature = "avx"))]
128    pub avx: __m256i,
129
130    /// Fallback representation if we cannot confirm that AVX or AVX2 instructions are available, depending on the 
131    /// function (some need specifically AVX or AVX2).
132    /// 
133    /// This may be slower than the AVX/AVX2 version (depending on how the compiler optimizes things), 
134    /// but at least still mathematically correct.
135    pub fallback: [u8; size_of::<__m256i>() / size_of::<u8>()],
136}
137
138impl Debug for Simd256IntegerInner {
139    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
140        // If std is disabled, try to use compiler flags to determine AVX support.
141        #[cfg(all(not(feature = "std"), target_feature = "avx2"))]
142        {
143            return f
144                .debug_struct(stringify!(Simd256IntegerInner))
145                // SAFETY: We just checked that AVX is supported -- if it is, we are using it.
146                .field("avx", &unsafe { self.avx })
147                .finish();
148        }
149
150        // If we have libstd, we can just ask the CPU if it supports AVX2.
151        #[cfg(feature = "std")]
152        if std::is_x86_feature_detected!("avx2") {
153            return f
154                .debug_struct(stringify!(Simd256IntegerInner))
155                // SAFETY: We just checked that AVX is supported -- if it is, we are using it.
156                .field("avx", &unsafe { self.avx })
157                .finish();
158        }
159
160        // If we haven't returned yet, we're using the fallback representation.
161        f.debug_struct(stringify!(Simd256IntegerInner))
162            // SAFETY: We checked above that AVX is not available.
163            .field("fallback", &unsafe { self.fallback.as_ref() })
164            .finish()
165    }
166}
167
168impl<S: Simd256Scalar, const LANES: usize> Simd256Integer<S, LANES> {
169    /// Compile-time assertion that number of lanes size of SIMD vector.
170    const _MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE: () = assert!(
171        LANES == size_of::<__m256i>() / size_of::<S>(),
172        "The number of lanes needs to be consistent with the size of the SIMD vector for the scalar type."
173    );
174
175    /// Construct a [Simd256Integer] value from an array of scalar values.
176    ///
177    /// This function will eventually be made `const` after <https://github.com/rust-lang/rust/issues/80384> is
178    /// resolved (it can't currently since the compiler can't/doesn't prove that S cannot contain an unsafe cell).
179    ///
180    /// Note that this function will fail at compile time if you attempt to construct a [Simd256Integer] with
181    /// a number of `LANES` inconsistent with the size of the scalar type `S`. See below:
182    /// ```compile_fail
183    /// use x86_simd::integers::int256::Simd256Integer;
184    /// let splat = Simd256Integer::from_array([0; 50]);
185    /// ```
186    pub fn from_array(array: [S; LANES]) -> Self {
187        // Check that the number of lanes is good (this is a compile-time check triggered by seeing this const).
188        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
189
190        // SAFETY: This is not amazing, but these types are the same size (as asserted at compile time above), and
191        // are both just plain old data, so this transmute should do what we like, and we can confirm with unit tests.
192        unsafe { transmute_copy(&array) }
193    }
194
195    /// "splat" a given scalar across all lanes of this SIMD value.
196    pub fn splat(s: S) -> Self {
197        Simd256Integer::from_array([s; LANES])
198    }
199
200    /// Take `LANES` items from an [Iterator] into the lanes of a [Simd256Integer].
201    ///
202    /// If the [Iterator::next] ever returns [`None`], immediately stop and return [`None`] (this means that
203    /// the iterator will be partially consumed if the end of it is reached before a SIMD vector can be filled).
204    pub fn try_from_iter(iter: &mut impl Iterator<Item = S>) -> Option<Self> {
205        // Check that the number of lanes is good (this is a compile-time check triggered by seeing this const).
206        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
207
208        // Use zero here, despite it always being overwritten.
209        let mut array: [S; LANES] = [S::ZERO; LANES];
210
211        #[allow(clippy::needless_range_loop)]
212        for i in 0..LANES {
213            array[i] = iter.next()?;
214        }
215
216        Some(Simd256Integer::from_array(array))
217    }
218
219    /// Turn this [Simd256Integer] into an array of its lanes.
220    pub const fn to_array(self) -> [S; LANES] {
221        // Check that the number of lanes is good (this is a compile-time check triggered by seeing this const).
222        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
223
224        // SAFETY: This is safe/acceptable for the same reasons as from_array.
225        unsafe { transmute_copy(&self) }
226    }
227
228    /// Get a reference to the underlying data of this SIMD value as an array.
229    pub fn as_array_ref(&self) -> &[S; LANES] {
230        // Check that the number of lanes is good (this is a compile-time check triggered by seeing this const).
231        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
232
233        // SAFETY: This is valid because the reciever type has a constant/known size, and therefore should not be
234        // a wide pointer, and because the lifetimes/liveness guarantees against this struct's underlying
235        // representation continue to hold true for the returned. All this in addtion to the above state reasons under
236        // (to|from)_array.
237        unsafe { transmute(self) }
238    }
239
240    /// Wrap a given intrinsic value with this type.
241    /// 
242    /// To retrieve the intrinsic underlying this value (the reverse of this operation), 
243    /// use [Simd256Integer::inner] and [Simd256IntegerInner::avx]:
244    /// ```rust, ignore
245    /// use x86_simd::integers::int256::{Simd256Integer, Simd256IntegerInner, u64x4};
246    /// 
247    /// let simd_value = u64x4::splat(0);
248    /// 
249    /// if std::is_x86_feature_detected!("avx2") {
250    ///     // SAFETY: We have confirmed that this field of the inner union is active by checking that the 
251    ///     // AVX2 CPU feature is available.
252    ///     let intrinsic = unsafe { simd_value.inner.avx };
253    /// }
254    /// ```
255    #[inline(always)]
256    pub const fn from_intrinsic(intrinsic: __m256i) -> Self {
257        // Check that the number of lanes is good (this is a compile-time check triggered by seeing this const).
258        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
259
260        // SAFETY: Honestly you should be used to me transmuting between plain data of the same sizes at this point.
261        unsafe { transmute(intrinsic) }
262    }
263
264    /// Check if the element type of this [Simd256Integer] (`S`) matches the given type `T` using [`core::any::TypeId`].
265    #[inline(always)]
266    pub fn element_type_is<T: Simd256Scalar>() -> bool {
267        TypeId::of::<S>() == TypeId::of::<T>()
268    }
269
270    /// "vertically" Add two SIMD values to eachother using AVX2 instructions.
271    ///
272    /// "vertical" means each lane of the resulting SIMD value contains the sum of the coresponding
273    /// lanes of `a` and `b`.
274    ///
275    /// # Safety
276    /// The caller must ensure that AVX2 CPU features are supported, otherwise calling this function will
277    /// execute unsupoorted instructions (which is immediate undefined behaviour).
278    #[cfg(any(feature = "std", target_feature = "avx2"))]
279    #[target_feature(enable = "avx2")]
280    pub unsafe fn avx2_vertical_add(a: Self, b: Self) -> Self {
281        // Check that the number of lanes is good (this is a compile-time check triggered by seeing this const).
282        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
283
284        #[cfg(target_arch = "x86")]
285        use core::arch::x86::*;
286        #[cfg(target_arch = "x86_64")]
287        use core::arch::x86_64::*;
288
289        let result = match size_of::<S>() {
290            1 => _mm256_add_epi8(a.inner.avx, b.inner.avx),
291            2 => _mm256_add_epi16(a.inner.avx, b.inner.avx),
292            4 => _mm256_add_epi32(a.inner.avx, b.inner.avx),
293            8 => _mm256_add_epi64(a.inner.avx, b.inner.avx),
294            _ => crate::unreachable_uncheched_on_release(),
295        };
296
297        Self::from_intrinsic(result)
298    }
299
300    /// Saturating vertical SIMD add using AVX2 instructions.
301    /// Saturated adds are generally slower than [Self::avx2_vertical_add] so use only when needed if you care about
302    /// performance (if you don't care about performance then why are you using this SIMD library anyway).
303    ///
304    /// # Safety
305    /// The caller must ensure that AVX2 CPU features are supported, otherwise calling this function will
306    /// execute unsupoorted instructions (which is immediate undefined behaviour).
307    #[cfg(any(feature = "std", target_feature = "avx2"))]
308    #[target_feature(enable = "avx2")]
309    pub unsafe fn avx2_vertical_saturating_add(a: Self, b: Self) -> Self
310    where
311        S: Simd256SaturatingAdd,
312    {
313        // Check that the number of lanes is good (this is a compile-time check triggered by seeing this const).
314        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
315
316        #[cfg(target_arch = "x86")]
317        use core::arch::x86::*;
318        #[cfg(target_arch = "x86_64")]
319        use core::arch::x86_64::*;
320
321        // I do not love using `TypeId` here but the compiler will optimize it out according to `cargo asm`
322        // https://crates.io/crates/cargo-show-asm, so this is how it works for now I suppose.
323        let result = match size_of::<S>() {
324            1 if Self::element_type_is::<u8>() => {
325                _mm256_adds_epu8(a.inner.avx, b.inner.avx)
326            }
327            
328            1 if Self::element_type_is::<i8>() => {
329                _mm256_adds_epi8(a.inner.avx, b.inner.avx)
330            }
331            
332            2 if Self::element_type_is::<u16>() => {
333                _mm256_adds_epu16(a.inner.avx, b.inner.avx)
334            }
335            
336            2 if Self::element_type_is::<i16>() => {
337                _mm256_adds_epi16(a.inner.avx, b.inner.avx)
338            }
339
340            _ => crate::unreachable_uncheched_on_release(),
341        };
342
343        Self::from_intrinsic(result)
344    }
345
346    /// Saturating add on two SIMD vectors backed by AVX2 operations or using fallback iterative/scalar instructions.
347    pub fn saturating_add(a: Self, b: Self) -> Self
348    where S: Simd256SaturatingAdd
349    {
350        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
351
352        #[cfg(target_feature = "avx2")]
353        // SAFETY: We checked if the CPU supports AVX2.
354        return unsafe { Self::avx2_vertical_saturating_add(a, b) };
355
356        #[cfg(feature = "std")]
357        if std::is_x86_feature_detected!("avx2") {
358            // SAFETY: We checked if the CPU supports AVX2.
359            return unsafe { Self::avx2_vertical_saturating_add(a, b) };
360        }
361
362
363        // Hate to use type-of again here but it's safe and gets compiled away on release.
364        // This is all fallback for when avx2 is not available.
365        match size_of::<S>() {
366            1 if Self::element_type_is::<u8>() => {
367                // SAFETY: We have just checked the type.
368                let a = unsafe { transmute::<_, u8x32>(a) }.to_array();
369                let b = unsafe { transmute::<_, u8x32>(b) }.to_array();
370                let mut result: [u8; 32] = [0; 32];
371
372                for i in 0..LANES {
373                    result[i] = u8::saturating_add(a[i], b[i]);
374                }
375
376                unsafe { transmute(result) }
377            }
378
379            1 if Self::element_type_is::<i8>() => {
380                // SAFETY: We have just checked the type.
381                let a = unsafe { transmute::<_, i8x32>(a) }.to_array();
382                let b = unsafe { transmute::<_, i8x32>(b) }.to_array();
383                let mut result: [i8; 32] = [0; 32];
384
385                for i in 0..LANES {
386                    result[i] = i8::saturating_add(a[i], b[i]);
387                }
388
389                unsafe { transmute(result) }
390            }
391            
392            2 if Self::element_type_is::<u16>() => {
393                // SAFETY: We have just checked the type.
394                let a = unsafe { transmute::<_, u16x16>(a) }.to_array();
395                let b = unsafe { transmute::<_, u16x16>(b) }.to_array();
396                let mut result: [u16; 16] = [0; 16];
397
398                for i in 0..LANES {
399                    result[i] = u16::saturating_add(a[i], b[i]);
400                }
401
402                unsafe { transmute(result) }
403            }
404
405            2 if Self::element_type_is::<i16>() => {
406                // SAFETY: We have just checked the type.
407                let a = unsafe { transmute::<_, i16x16>(a) }.to_array();
408                let b = unsafe { transmute::<_, i16x16>(b) }.to_array();
409                let mut result: [i16; 16] = [0; 16];
410
411                for i in 0..LANES {
412                    result[i] = i16::saturating_add(a[i], b[i]);
413                }
414
415                unsafe { transmute(result) }
416            }
417
418            // SAFETY: We checked all types that implement AVX2-backed saturating addition, and that trait is Sealed.
419            _ => unsafe { crate::unreachable_uncheched_on_release() }
420        }
421    }
422
423
424    /// Compare two SIMD vectors for equality of elements vertically. Lanes of the result are defined as so: 
425    /// If the elements of the coresponding lane of each of the input vectors are equal, then the output vector will 
426    /// have all `1` bits in that lane (e.g. an `0xFF` value in the lane for [u8x32] or [i8x32]). 
427    /// If not equal, then all `0` bits.
428    /// 
429    /// # Safety
430    /// The caller must ensure that AVX2 CPU features are supported, otherwise calling this function will
431    /// execute unsupoorted instructions (which is immediate undefined behaviour).
432    #[cfg(any(feature = "std", target_feature = "avx2"))]
433    #[target_feature(enable = "avx2")]
434    pub unsafe fn avx2_vertical_cmp_eq(a: Self, b: Self) -> Self {
435        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
436
437        #[cfg(target_arch = "x86")]
438        use core::arch::x86::*;
439        #[cfg(target_arch = "x86_64")]
440        use core::arch::x86_64::*;
441        
442        let result = match size_of::<S>() {
443            1 => _mm256_cmpeq_epi8(a.inner.avx, b.inner.avx),
444            2 => _mm256_cmpeq_epi16(a.inner.avx, b.inner.avx),
445            4 => _mm256_cmpeq_epi32(a.inner.avx, b.inner.avx),
446            8 => _mm256_cmpeq_epi64(a.inner.avx, b.inner.avx),
447            _ => crate::unreachable_uncheched_on_release(),
448        };
449
450        Self::from_intrinsic(result)
451    }
452
453    /// Compare the elements/lanes of two SIMD vectors for equality, setting each lane of the returned SIMD vector 
454    /// to all `1` bits if the coresponding elements of the input vectors are equal, and all `0` bits otherwise.
455    pub fn vertical_cmp_eq(a: Self, b: Self) -> Self {
456        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
457
458        #[cfg(target_feature = "avx2")]
459        // SAFETY: We checked if the CPU supports AVX2.
460        return unsafe { Self::avx2_vertical_cmp_eq(a, b) };
461
462        #[cfg(feature = "std")]
463        if std::is_x86_feature_detected!("avx2") {
464            // SAFETY: We checked if the CPU supports AVX2.
465            return unsafe { Self::avx2_vertical_cmp_eq(a, b) };
466        }
467
468        // If we don't have AVX2, fallback.
469        let mut result = [S::ZERO; LANES];
470
471        #[allow(clippy::needless_range_loop)]
472        for i in 0..LANES {
473            if a.as_array_ref()[i] == b.as_array_ref()[i] {
474                // Use a bitwise not here to get 0xFF.
475                result[i] = !S::ZERO;
476            }
477        }
478
479        Self::from_array(result)
480    }
481
482    /// Get the absolute value of each lane of this SIMD vector using AVX2 absolute value intrinsics.
483    /// 
484    /// # Safety
485    /// The caller must ensure that AVX2 CPU features are supported, otherwise calling this function will
486    /// execute unsupoorted instructions (which is immediate undefined behaviour).
487    #[cfg(any(feature = "std", target_feature = "avx2"))]
488    #[target_feature(enable = "avx2")]
489    pub unsafe fn avx2_vertical_abs(self) -> Self 
490    where S: Simd256IntegerAbs
491    {
492        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
493
494        #[cfg(target_arch = "x86")]
495        use core::arch::x86::*;
496        #[cfg(target_arch = "x86_64")]
497        use core::arch::x86_64::*;
498        
499        let result = match size_of::<S>() {
500            1 => _mm256_abs_epi8(self.inner.avx),
501            2 => _mm256_abs_epi16(self.inner.avx),
502            4 => _mm256_abs_epi32(self.inner.avx),
503            _ => crate::unreachable_uncheched_on_release(),
504        };
505
506        Self::from_intrinsic(result)
507    }
508
509    /// Return a SIMD vector containing the absolute value of all of the elements of this SIMD vector.
510    pub fn abs(self) -> Self
511    where S: Simd256IntegerAbs {
512        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
513
514        #[cfg(target_feature = "avx2")]
515        // SAFETY: We checked if the CPU supports AVX2.
516        return unsafe { Self::avx2_vertical_abs(self) };
517
518        #[cfg(feature = "std")]
519        if std::is_x86_feature_detected!("avx2") {
520            // SAFETY: We checked if the CPU supports AVX2.
521            return unsafe { Self::avx2_vertical_abs(self) };
522        }
523
524        // Fallback if AVX2 is not supported.
525        // SAFETY: We match on the size of `S` and know all the types that implement the sealed trait.
526        match size_of::<S>() {
527            1 => unsafe { 
528                let mut array = transmute::<_, i8x32>(self).to_array();
529
530                for element in &mut array {
531                    *element = i8::abs(*element);
532                }
533
534                transmute(array)
535            }
536
537            2 => unsafe { 
538                let mut array = transmute::<_, i16x16>(self).to_array();
539
540                for element in &mut array {
541                    *element = i16::abs(*element);
542                }
543
544                transmute(array)
545            }
546
547            
548            4 => unsafe { 
549                let mut array = transmute::<_, i32x8>(self).to_array();
550
551                for element in &mut array {
552                    *element = i32::abs(*element);
553                }
554
555                transmute(array)
556            }
557
558            _ => unsafe { crate::unreachable_uncheched_on_release() }
559        }
560    }
561
562    /// Load a SIMD Vector from the given pointer using AVX intrinsics.
563    /// 
564    /// # Safety
565    /// The caller must ensure that AVX CPU features are supported, otherwise calling this function will
566    /// execute unsupoorted instructions (which is immediate undefined behaviour).
567    #[cfg(any(feature = "std", target_feature = "avx"))]
568    #[target_feature(enable = "avx")]
569    pub unsafe fn avx_load(ptr: *const __m256i) -> Self {
570        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
571
572        #[cfg(target_arch = "x86")]
573        use core::arch::x86::*;
574        #[cfg(target_arch = "x86_64")]
575        use core::arch::x86_64::*;
576
577        // Just call the intrinsic directly and transmute to self, since it returns a SIMD vector
578        // and is the same for all element types and lane counts.
579        transmute(_mm256_loadu_si256(ptr))
580    }
581
582    /// Read `LANES` items from the beginning of the given `slice` into a SIMD vector.
583    /// 
584    /// # Panics
585    /// This function will panic if the length of the slice is less than `LANES`.
586    pub fn load_from_slice(slice: &[S]) -> Self {
587        Self::try_load_from_slice(slice)
588            .expect("slice must contain enough elements to load into a SIMD vector")
589    }
590
591    /// Read `LANES` items from the beginning of the given `slice` into a SIMD vector. 
592    /// Returns [`None`] if the slice is not large enough.
593    pub fn try_load_from_slice(slice: &[S]) -> Option<Self> {
594        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
595
596        if slice.len() < LANES {
597            return None;
598        }
599
600        // Use raw pointer casting to get a const pointer to the slice that we can pass to the intrinsic.
601        #[cfg(any(feature = "std", target_feature = "avx"))]
602        let cptr = slice as *const [S] as *const () as *const __m256i;
603
604        #[cfg(target_feature = "avx")]
605        // SAFETY: We checked if the CPU supports AVX.
606        return Some(unsafe { Self::avx_load(cptr) });
607
608        #[cfg(feature = "std")]
609        if std::is_x86_feature_detected!("avx") {
610            // SAFETY: We checked if the CPU supports AVX.
611            return Some(unsafe { Self::avx_load(cptr) });
612        }
613
614        // No std or avx -- fallback to memcpy.
615        let mut result = [S::ZERO; LANES];
616        result[..LANES].copy_from_slice(&slice[..LANES]);
617        Some(Self::from_array(result))
618    }
619}
620
621impl<S: Simd256Scalar, const LANES: usize> core::ops::Add for Simd256Integer<S, LANES> {
622    type Output = Self;
623
624    fn add(self, rhs: Self) -> Self::Output {
625        Self::_MENTION_ME_TO_ASSERT_LANES_MATCH_SIZE;
626    
627        #[cfg(target_feature = "avx2")]
628        // SAFETY: We statically check if the CPU supports AVX2.
629        return unsafe { Simd256Integer::avx2_vertical_add(self, rhs) };
630
631        // Attempt to use avx2 based SIMD add.
632        #[cfg(feature = "std")]
633        if std::is_x86_feature_detected!("avx2") {
634            // SAFETY: We just checked if the CPU supports AVX2.
635            return unsafe { Simd256Integer::avx2_vertical_add(self, rhs) };
636        }
637
638        // If neither of the above has returned already, use a fallback.
639        // This is fully safe, thanks to guarantees made elsewhere, and is just an iterative vertical add across two
640        // scalar arrays.
641        let mut result: [S; LANES] = [S::ZERO; LANES];
642        let a = self.to_array();
643        let b = rhs.to_array();
644
645        for i in 0..result.len() {
646            result[i] = a[i] + b[i];
647        }
648
649        Simd256Integer::from_array(result)
650    }
651}
652
653impl<S: Simd256Scalar, const LANES: usize> PartialEq for Simd256Integer<S, LANES> {
654    fn eq(&self, other: &Self) -> bool {
655        self.as_array_ref() == other.as_array_ref()
656    }
657}
658
659impl<S: Simd256Scalar, const LANES: usize> Eq for Simd256Integer<S, LANES> {}
660
661// impl<S: Simd256Scalar, const LANES: usize> PartialEq for Simd256Integer<S, LANES> {
662//     fn eq(&self, other: &Self) -> bool {
663//         self.inner == other.inner
664//     }
665// }
666
667// pub fn avx_sadd_i16(a: Simd256Integer<i16, 16>, b: Simd256Integer<i16, 16>) -> Simd256Integer<i16, 16> {
668//     Simd256Integer::<i16, 16>::saturating_add(a, b)
669// }
670
671#[cfg(test)]
672mod tests {
673    use core::u16;
674
675    use crate::integers::int256::i64x4;
676
677    use super::i32x8;
678    use super::i8x32;
679    use super::u64x4;
680    use super::u8x32;
681    use super::u16x16;
682    use super::i16x16;
683
684    // This test should fail to compile if you un-comment it.
685    // #[test]
686    // fn comp_fail() {
687    //     let splat = Simd256Integer::from_array([0u8; 100]);
688    // }
689
690    #[test]
691    #[cfg(feature = "std")]
692    fn test_debug() {
693        let simd_value = u8x32::try_from_iter(&mut (0..32).into_iter()).unwrap();
694
695        println!("{simd_value:#x?}");
696    }
697
698    #[test]
699    fn test_add() {
700        let simd_10x16 = u16x16::splat(10);
701        let simd_12x16 = u16x16::splat(12);
702
703        let added =  simd_10x16 + simd_12x16;
704
705        assert_eq!(added.to_array(), [22; 16]);
706    }
707
708    #[test]
709    fn test_saturating_add() {
710        let simd_max = u16x16::splat(u16::MAX);
711
712        assert_eq!(u16x16::saturating_add(simd_max, simd_max).as_array_ref(), simd_max.as_array_ref());
713    }
714
715    #[test]
716    fn test_abs() {
717        let simd_neg1 = i16x16::splat(-1);
718
719        assert_eq!(simd_neg1.abs().to_array(), [1; 16]);
720    }
721
722    #[test]
723    fn test_vertical_cmp_eq() {
724        let simd_a = u64x4::from_array([10, 20, 30, 40]);
725        let simd_b = u64x4::from_array([0, 20, 40, 60]);
726
727        assert_eq!(u64x4::vertical_cmp_eq(simd_a, simd_b).to_array(), [0, 0xFFFF_FFFF_FFFF_FFFF, 0, 0]);
728    }
729
730    #[test]
731    fn test_try_load_fails() {
732        assert!(i8x32::try_load_from_slice(&[0;10]).is_none());
733    }
734
735    #[test]
736    #[should_panic]
737    fn test_load_panics() {
738        i32x8::load_from_slice(&[0; 1]);
739    }
740
741    #[test]
742    fn test_load() {
743        assert_eq!(i64x4::load_from_slice(&[100; 4]).to_array(), [100; 4]);
744    }
745}