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}