Skip to main content

wide/
i8x32_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx2")] {
5    #[derive(Default, Clone, Copy, PartialEq, Eq)]
6    #[repr(C, align(32))]
7    pub struct i8x32 { avx: m256i }
8  } else {
9    #[derive(Default, Clone, Copy, PartialEq, Eq)]
10    #[repr(C, align(32))]
11    pub struct i8x32 { a : i8x16, b : i8x16 }
12  }
13}
14
15int_uint_consts!(i8, 32, i8x32, 256);
16
17unsafe impl Zeroable for i8x32 {}
18unsafe impl Pod for i8x32 {}
19
20impl AlignTo for i8x32 {
21  type Elem = i8;
22}
23
24impl Add for i8x32 {
25  type Output = Self;
26  #[inline]
27  fn add(self, rhs: Self) -> Self::Output {
28    pick! {
29      if #[cfg(target_feature="avx2")] {
30        Self { avx: add_i8_m256i(self.avx,rhs.avx) }
31      } else {
32        Self {
33          a : self.a.add(rhs.a),
34          b : self.b.add(rhs.b),
35        }
36      }
37    }
38  }
39}
40
41impl Sub for i8x32 {
42  type Output = Self;
43  #[inline]
44  fn sub(self, rhs: Self) -> Self::Output {
45    pick! {
46      if #[cfg(target_feature="avx2")] {
47        Self { avx: sub_i8_m256i(self.avx,rhs.avx) }
48      } else {
49        Self {
50          a : self.a.sub(rhs.a),
51          b : self.b.sub(rhs.b),
52        }
53      }
54    }
55  }
56}
57
58impl Mul for i8x32 {
59  type Output = Self;
60
61  #[inline]
62  fn mul(self, rhs: Self) -> Self::Output {
63    // For x86, this technically can be done explicitly by converting to `i16`
64    // then converting back after multiplication, but that may not actually be
65    // faster than auto-vectorization.
66    let [self_a, self_b]: [i8x16; 2] = cast(self);
67    let [rhs_a, rhs_b]: [i8x16; 2] = cast(rhs);
68    cast([self_a * rhs_a, self_b * rhs_b])
69  }
70}
71
72integer_impl_div_rem!(
73  i8,
74  i8x32,
75  [
76    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
77    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
78  ],
79);
80
81impl Shl for i8x32 {
82  type Output = Self;
83
84  #[inline]
85  fn shl(self, rhs: Self) -> Self::Output {
86    // For x86, this technically can be done explicitly by converting to `i16`
87    // or `i32` then converting back after multiplication, but that may not
88    // actually be faster than auto-vectorization.
89    let [self_a, self_b]: [i8x16; 2] = cast(self);
90    let [rhs_a, rhs_b]: [i8x16; 2] = cast(rhs);
91    cast([self_a << rhs_a, self_b << rhs_b])
92  }
93}
94
95impl Shr for i8x32 {
96  type Output = Self;
97
98  #[inline]
99  fn shr(self, rhs: Self) -> Self::Output {
100    // For x86, this technically can be done explicitly by converting to `i16`
101    // or `i32` then converting back after multiplication, but that may not
102    // actually be faster than auto-vectorization.
103    let [self_a, self_b]: [i8x16; 2] = cast(self);
104    let [rhs_a, rhs_b]: [i8x16; 2] = cast(rhs);
105    cast([self_a >> rhs_a, self_b >> rhs_b])
106  }
107}
108
109impl Add<i8> for i8x32 {
110  type Output = Self;
111  #[inline]
112  fn add(self, rhs: i8) -> Self::Output {
113    self.add(Self::splat(rhs))
114  }
115}
116
117impl Sub<i8> for i8x32 {
118  type Output = Self;
119  #[inline]
120  fn sub(self, rhs: i8) -> Self::Output {
121    self.sub(Self::splat(rhs))
122  }
123}
124
125impl Mul<i8> for i8x32 {
126  type Output = Self;
127
128  #[inline]
129  fn mul(self, rhs: i8) -> Self::Output {
130    self * Self::splat(rhs)
131  }
132}
133
134macro_rules! impl_shl_scalar {
135  ($Rhs:ident) => {
136    impl Shl<$Rhs> for i8x32 {
137      type Output = Self;
138
139      /// Shifts all lanes by a uniform value.
140      #[inline]
141      fn shl(self, rhs: $Rhs) -> Self::Output {
142        // For x86, this technically can be done explicitly by converting
143        // to `i16` or `i32` then converting back after multiplication, but that
144        // may not actually be faster than auto-vectorization.
145        let [self_a, self_b]: [i8x16; 2] = cast(self);
146        cast([self_a << rhs, self_b << rhs])
147      }
148    }
149  };
150}
151impl_shl_scalar!(i8);
152impl_shl_scalar!(u8);
153impl_shl_scalar!(i16);
154impl_shl_scalar!(u16);
155impl_shl_scalar!(i32);
156impl_shl_scalar!(u32);
157impl_shl_scalar!(i64);
158impl_shl_scalar!(u64);
159impl_shl_scalar!(i128);
160impl_shl_scalar!(u128);
161
162macro_rules! impl_shr_scalar {
163  ($Rhs:ident) => {
164    impl Shr<$Rhs> for i8x32 {
165      type Output = Self;
166
167      /// Shifts all lanes by a uniform value.
168      #[inline]
169      fn shr(self, rhs: $Rhs) -> Self::Output {
170        // For x86, this technically can be done explicitly by converting
171        // to `i16` or `i32` then converting back after multiplication, but that
172        // may not actually be faster than auto-vectorization.
173        let [self_a, self_b]: [i8x16; 2] = cast(self);
174        cast([self_a >> rhs, self_b >> rhs])
175      }
176    }
177  };
178}
179impl_shr_scalar!(i8);
180impl_shr_scalar!(u8);
181impl_shr_scalar!(i16);
182impl_shr_scalar!(u16);
183impl_shr_scalar!(i32);
184impl_shr_scalar!(u32);
185impl_shr_scalar!(i64);
186impl_shr_scalar!(u64);
187impl_shr_scalar!(i128);
188impl_shr_scalar!(u128);
189
190impl Add<i8x32> for i8 {
191  type Output = i8x32;
192  #[inline]
193  fn add(self, rhs: i8x32) -> Self::Output {
194    i8x32::splat(self).add(rhs)
195  }
196}
197
198impl Sub<i8x32> for i8 {
199  type Output = i8x32;
200  #[inline]
201  fn sub(self, rhs: i8x32) -> Self::Output {
202    i8x32::splat(self).sub(rhs)
203  }
204}
205
206impl Mul<i8x32> for i8 {
207  type Output = i8x32;
208
209  #[inline]
210  fn mul(self, rhs: i8x32) -> Self::Output {
211    i8x32::splat(self) * rhs
212  }
213}
214
215impl BitAnd for i8x32 {
216  type Output = Self;
217  #[inline]
218  fn bitand(self, rhs: Self) -> Self::Output {
219    pick! {
220      if #[cfg(target_feature="avx2")] {
221          Self { avx : bitand_m256i(self.avx,rhs.avx) }
222      } else {
223          Self {
224            a : self.a.bitand(rhs.a),
225            b : self.b.bitand(rhs.b),
226          }
227      }
228    }
229  }
230}
231
232impl BitOr for i8x32 {
233  type Output = Self;
234  #[inline]
235  fn bitor(self, rhs: Self) -> Self::Output {
236    pick! {
237      if #[cfg(target_feature="avx2")] {
238        Self { avx : bitor_m256i(self.avx,rhs.avx) }
239      } else {
240        Self {
241          a : self.a.bitor(rhs.a),
242          b : self.b.bitor(rhs.b),
243        }
244      }
245    }
246  }
247}
248
249impl BitXor for i8x32 {
250  type Output = Self;
251  #[inline]
252  fn bitxor(self, rhs: Self) -> Self::Output {
253    pick! {
254      if #[cfg(target_feature="avx2")] {
255        Self { avx : bitxor_m256i(self.avx,rhs.avx) }
256      } else {
257        Self {
258          a : self.a.bitxor(rhs.a),
259          b : self.b.bitxor(rhs.b),
260        }
261      }
262    }
263  }
264}
265
266#[expect(deprecated)]
267impl CmpEq for i8x32 {
268  type Output = Self;
269  #[inline]
270  fn simd_eq(self, rhs: Self) -> Self::Output {
271    pick! {
272      if #[cfg(target_feature="avx2")] {
273        Self { avx : cmp_eq_mask_i8_m256i(self.avx,rhs.avx) }
274      } else {
275        Self {
276          a : self.a.simd_eq(rhs.a),
277          b : self.b.simd_eq(rhs.b),
278        }
279      }
280    }
281  }
282}
283
284#[expect(deprecated)]
285impl CmpGt for i8x32 {
286  type Output = Self;
287  #[inline]
288  fn simd_gt(self, rhs: Self) -> Self::Output {
289    pick! {
290      if #[cfg(target_feature="avx2")] {
291        Self { avx : cmp_gt_mask_i8_m256i(self.avx,rhs.avx) }
292      } else {
293        Self {
294          a : self.a.simd_gt(rhs.a),
295          b : self.b.simd_gt(rhs.b),
296        }
297      }
298    }
299  }
300}
301
302#[expect(deprecated)]
303impl CmpLt for i8x32 {
304  type Output = Self;
305  #[inline]
306  fn simd_lt(self, rhs: Self) -> Self::Output {
307    rhs.simd_gt(self)
308  }
309}
310
311#[expect(deprecated)]
312impl CmpNe for i8x32 {
313  type Output = Self;
314  #[inline]
315  fn simd_ne(self, rhs: Self) -> Self::Output {
316    pick! {
317      if #[cfg(target_feature="avx2")] {
318        !self.simd_eq(rhs)
319      } else {
320        Self {
321          a : self.a.simd_ne(rhs.a),
322          b : self.b.simd_ne(rhs.b),
323        }
324      }
325    }
326  }
327}
328
329#[expect(deprecated)]
330impl CmpLe for i8x32 {
331  type Output = Self;
332  #[inline]
333  fn simd_le(self, rhs: Self) -> Self::Output {
334    pick! {
335      if #[cfg(target_feature="avx2")] {
336        !self.simd_gt(rhs)
337      } else {
338        Self {
339          a : self.a.simd_le(rhs.a),
340          b : self.b.simd_le(rhs.b),
341        }
342      }
343    }
344  }
345}
346
347#[expect(deprecated)]
348impl CmpGe for i8x32 {
349  type Output = Self;
350  #[inline]
351  fn simd_ge(self, rhs: Self) -> Self::Output {
352    pick! {
353      if #[cfg(target_feature="avx2")] {
354        !self.simd_lt(rhs)
355      } else {
356        Self {
357          a : self.a.simd_ge(rhs.a),
358          b : self.b.simd_ge(rhs.b),
359        }
360      }
361    }
362  }
363}
364
365impl Not for i8x32 {
366  type Output = Self;
367  #[inline]
368  fn not(self) -> Self {
369    pick! {
370      if #[cfg(target_feature="avx2")] {
371        Self { avx: self.avx.not()  }
372      } else {
373        Self {
374          a : self.a.not(),
375          b : self.b.not(),
376        }
377      }
378    }
379  }
380}
381
382impl i8x32 {
383  #[inline]
384  #[must_use]
385  pub const fn new(array: [i8; 32]) -> Self {
386    unsafe { core::mem::transmute(array) }
387  }
388
389  simd_comparison_fns!();
390
391  #[inline]
392  #[must_use]
393  pub fn blend(self, t: Self, f: Self) -> Self {
394    pick! {
395      if #[cfg(target_feature="avx2")] {
396        Self { avx: blend_varying_i8_m256i(f.avx, t.avx, self.avx) }
397      } else {
398        Self {
399          a : self.a.blend(t.a, f.a),
400          b : self.b.blend(t.b, f.b),
401        }
402      }
403    }
404  }
405
406  /// Returns true for each positive element and false if it is zero or
407  /// negative.
408  #[inline]
409  #[must_use]
410  pub fn is_positive(self) -> Self {
411    pick! {
412      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
413        // `neon` has dedicated greater-than-zero intrinsics.
414        Self {
415          a: self.a.is_positive(),
416          b: self.b.is_positive(),
417        }
418      } else {
419        self.simd_gt(Self::ZERO)
420      }
421    }
422  }
423
424  /// Returns true for each negative element and false if it is zero or
425  /// positive.
426  #[inline]
427  #[must_use]
428  pub fn is_negative(self) -> Self {
429    pick! {
430      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
431        // `neon` has dedicated less-than-zero intrinsics.
432        Self {
433          a: self.a.is_negative(),
434          b: self.b.is_negative(),
435        }
436      } else {
437        self.simd_lt(Self::ZERO)
438      }
439    }
440  }
441
442  #[inline]
443  #[must_use]
444  pub fn reduce_add(self) -> i8 {
445    let array: [i8x16; 2] = cast(self);
446    (array[0] + array[1]).reduce_add()
447  }
448
449  #[inline]
450  #[must_use]
451  pub fn reduce_max(self) -> i8 {
452    let array: [i8x16; 2] = cast(self);
453    array[0].max(array[1]).reduce_max()
454  }
455
456  #[inline]
457  #[must_use]
458  pub fn reduce_min(self) -> i8 {
459    let array: [i8x16; 2] = cast(self);
460    array[0].min(array[1]).reduce_min()
461  }
462
463  #[inline]
464  #[must_use]
465  pub fn abs(self) -> Self {
466    pick! {
467      if #[cfg(target_feature="avx2")] {
468        Self { avx: abs_i8_m256i(self.avx) }
469      } else {
470        Self {
471          a : self.a.abs(),
472          b : self.b.abs(),
473        }
474      }
475    }
476  }
477
478  #[inline]
479  #[must_use]
480  pub fn unsigned_abs(self) -> u8x32 {
481    pick! {
482      if #[cfg(target_feature="avx2")] {
483        u8x32 { avx: abs_i8_m256i(self.avx) }
484      } else {
485        u8x32 {
486          a : self.a.unsigned_abs(),
487          b : self.b.unsigned_abs(),
488        }
489      }
490    }
491  }
492
493  signed_fn_signum!();
494
495  #[inline]
496  #[must_use]
497  pub fn max(self, rhs: Self) -> Self {
498    pick! {
499      if #[cfg(target_feature="avx2")] {
500        Self { avx: max_i8_m256i(self.avx,rhs.avx) }
501      } else {
502        Self {
503          a : self.a.max(rhs.a),
504          b : self.b.max(rhs.b),
505        }
506      }
507    }
508  }
509  #[inline]
510  #[must_use]
511  pub fn min(self, rhs: Self) -> Self {
512    pick! {
513      if #[cfg(target_feature="avx2")] {
514        Self { avx: min_i8_m256i(self.avx,rhs.avx) }
515      } else {
516        Self {
517          a : self.a.min(rhs.a),
518          b : self.b.min(rhs.b),
519        }
520      }
521    }
522  }
523
524  integer_fn_clamp!();
525
526  #[inline]
527  #[must_use]
528  pub fn saturating_add(self, rhs: Self) -> Self {
529    pick! {
530      if #[cfg(target_feature="avx2")] {
531        Self { avx: add_saturating_i8_m256i(self.avx, rhs.avx) }
532      } else {
533        Self {
534          a : self.a.saturating_add(rhs.a),
535          b : self.b.saturating_add(rhs.b),
536        }
537      }
538    }
539  }
540  #[inline]
541  #[must_use]
542  pub fn saturating_sub(self, rhs: Self) -> Self {
543    pick! {
544      if #[cfg(target_feature="avx2")] {
545        Self { avx: sub_saturating_i8_m256i(self.avx, rhs.avx) }
546      } else {
547        Self {
548          a : self.a.saturating_sub(rhs.a),
549          b : self.b.saturating_sub(rhs.b),
550        }
551      }
552    }
553  }
554
555  /// Lanewise saturating multiply.
556  #[inline]
557  #[must_use]
558  pub fn saturating_mul(self, rhs: Self) -> Self {
559    let [self_a, self_b]: [i8x16; 2] = cast(self);
560    let [rhs_a, rhs_b]: [i8x16; 2] = cast(rhs);
561    cast([self_a.saturating_mul(rhs_a), self_b.saturating_mul(rhs_b)])
562  }
563
564  integer_fn_saturating_div!([
565    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
566    21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
567  ]);
568
569  #[inline]
570  #[must_use]
571  #[doc(alias("movemask", "move_mask"))]
572  pub fn to_bitmask(self) -> u32 {
573    pick! {
574      if #[cfg(target_feature="avx2")] {
575        move_mask_i8_m256i(self.avx) as u32
576      } else {
577        self.a.to_bitmask() | (self.b.to_bitmask() << 16)
578      }
579    }
580  }
581
582  #[inline]
583  #[must_use]
584  pub fn any(self) -> bool {
585    pick! {
586      if #[cfg(target_feature="avx2")] {
587        move_mask_i8_m256i(self.avx) != 0
588      } else {
589        (self.a | self.b).any()
590      }
591    }
592  }
593
594  #[inline]
595  #[must_use]
596  pub fn all(self) -> bool {
597    pick! {
598      if #[cfg(target_feature="avx2")] {
599        move_mask_i8_m256i(self.avx) == -1
600      } else {
601        (self.a & self.b).all()
602      }
603    }
604  }
605
606  #[inline]
607  #[must_use]
608  pub fn none(self) -> bool {
609    !self.any()
610  }
611
612  /// Returns a new vector with lanes selected from the lanes of the first input
613  /// vector a specified in the second input vector `rhs`.
614  /// The indices i in range `[0, 15]` select the i-th element of `self`. For
615  /// indices outside of the range the resulting lane is `0`.
616  ///
617  /// This note that is the equivalent of two parallel swizzle operations on the
618  /// two halves of the vector, and the indexes each refer to the
619  /// corresponding half.
620  #[inline]
621  pub fn swizzle_half(self, rhs: i8x32) -> i8x32 {
622    pick! {
623      if #[cfg(target_feature="avx2")] {
624        Self { avx: shuffle_av_i8z_half_m256i(self.avx, rhs.saturating_add(i8x32::splat(0x60)).avx) }
625      } else {
626          Self {
627            a : self.a.swizzle(rhs.a),
628            b : self.b.swizzle(rhs.b),
629          }
630      }
631    }
632  }
633
634  /// Indices in the range `[0, 15]` will select the i-th element of `self`. If
635  /// the high bit of any element of `rhs` is set (negative) then the
636  /// corresponding output lane is guaranteed to be zero. Otherwise if the
637  /// element of `rhs` is within the range `[32, 127]` then the output lane is
638  /// either `0` or `self[rhs[i] % 16]` depending on the implementation.
639  ///
640  /// This is the equivalent to two parallel swizzle operations on the two
641  /// halves of the vector, and the indexes each refer to their corresponding
642  /// half.
643  #[inline]
644  pub fn swizzle_half_relaxed(self, rhs: i8x32) -> i8x32 {
645    pick! {
646      if #[cfg(target_feature="avx2")] {
647        Self { avx: shuffle_av_i8z_half_m256i(self.avx, rhs.avx) }
648      } else {
649        Self {
650          a : self.a.swizzle_relaxed(rhs.a),
651          b : self.b.swizzle_relaxed(rhs.b),
652        }
653      }
654    }
655  }
656
657  /// Transpose matrix of 32x32 `i8` matrix. Currently not accelerated.
658  #[must_use]
659  #[inline]
660  pub fn transpose(data: [i8x32; 32]) -> [i8x32; 32] {
661    // Can this be optimized?
662
663    #[inline(always)]
664    fn transpose_column(data: &[i8x32; 32], index: usize) -> i8x32 {
665      i8x32::new([
666        data[0].as_array()[index],
667        data[1].as_array()[index],
668        data[2].as_array()[index],
669        data[3].as_array()[index],
670        data[4].as_array()[index],
671        data[5].as_array()[index],
672        data[6].as_array()[index],
673        data[7].as_array()[index],
674        data[8].as_array()[index],
675        data[9].as_array()[index],
676        data[10].as_array()[index],
677        data[11].as_array()[index],
678        data[12].as_array()[index],
679        data[13].as_array()[index],
680        data[14].as_array()[index],
681        data[15].as_array()[index],
682        data[16].as_array()[index],
683        data[17].as_array()[index],
684        data[18].as_array()[index],
685        data[19].as_array()[index],
686        data[20].as_array()[index],
687        data[21].as_array()[index],
688        data[22].as_array()[index],
689        data[23].as_array()[index],
690        data[24].as_array()[index],
691        data[25].as_array()[index],
692        data[26].as_array()[index],
693        data[27].as_array()[index],
694        data[28].as_array()[index],
695        data[29].as_array()[index],
696        data[30].as_array()[index],
697        data[31].as_array()[index],
698      ])
699    }
700
701    [
702      transpose_column(&data, 0),
703      transpose_column(&data, 1),
704      transpose_column(&data, 2),
705      transpose_column(&data, 3),
706      transpose_column(&data, 4),
707      transpose_column(&data, 5),
708      transpose_column(&data, 6),
709      transpose_column(&data, 7),
710      transpose_column(&data, 8),
711      transpose_column(&data, 9),
712      transpose_column(&data, 10),
713      transpose_column(&data, 11),
714      transpose_column(&data, 12),
715      transpose_column(&data, 13),
716      transpose_column(&data, 14),
717      transpose_column(&data, 15),
718      transpose_column(&data, 16),
719      transpose_column(&data, 17),
720      transpose_column(&data, 18),
721      transpose_column(&data, 19),
722      transpose_column(&data, 20),
723      transpose_column(&data, 21),
724      transpose_column(&data, 22),
725      transpose_column(&data, 23),
726      transpose_column(&data, 24),
727      transpose_column(&data, 25),
728      transpose_column(&data, 26),
729      transpose_column(&data, 27),
730      transpose_column(&data, 28),
731      transpose_column(&data, 29),
732      transpose_column(&data, 30),
733      transpose_column(&data, 31),
734    ]
735  }
736
737  #[inline]
738  pub fn to_array(self) -> [i8; 32] {
739    cast(self)
740  }
741
742  #[inline]
743  pub fn as_array(&self) -> &[i8; 32] {
744    cast_ref(self)
745  }
746
747  #[inline]
748  pub fn as_mut_array(&mut self) -> &mut [i8; 32] {
749    cast_mut(self)
750  }
751}