Skip to main content

wide/
u32x4_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="sse2")] {
5    /// A SIMD vector with four elements of type [`u32`].
6    ///
7    /// See the [crate level documentation] for more information about SIMD
8    /// vectors.
9    ///
10    /// [crate level documentation]: crate
11    #[derive(Default, Clone, Copy, PartialEq, Eq)]
12    #[repr(C, align(16))]
13    pub struct u32x4 { pub(crate) sse: m128i }
14  } else if #[cfg(target_feature="simd128")] {
15    use core::arch::wasm32::*;
16
17    /// A SIMD vector with four elements of type [`u32`].
18    ///
19    /// See the [crate level documentation] for more information about SIMD
20    /// vectors.
21    ///
22    /// [crate level documentation]: crate
23    #[derive(Clone, Copy)]
24    #[repr(transparent)]
25    pub struct u32x4 { pub(crate) simd: v128 }
26
27    impl Default for u32x4 {
28      fn default() -> Self {
29        Self::splat(0)
30      }
31    }
32
33    impl PartialEq for u32x4 {
34      fn eq(&self, other: &Self) -> bool {
35        u32x4_all_true(u32x4_eq(self.simd, other.simd))
36      }
37    }
38
39    impl Eq for u32x4 { }
40  } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
41    use core::arch::aarch64::*;
42
43    /// A SIMD vector with four elements of type [`u32`].
44    ///
45    /// See the [crate level documentation] for more information about SIMD
46    /// vectors.
47    ///
48    /// [crate level documentation]: crate
49    #[repr(C)]
50    #[derive(Copy, Clone)]
51    pub struct u32x4 { pub(crate) neon : uint32x4_t }
52
53    impl Default for u32x4 {
54      #[inline]
55      fn default() -> Self {
56        Self::splat(0)
57      }
58    }
59
60    impl PartialEq for u32x4 {
61      #[inline]
62      fn eq(&self, other: &Self) -> bool {
63        unsafe { vminvq_u32(vceqq_u32(self.neon, other.neon))==u32::MAX }
64      }
65    }
66
67    impl Eq for u32x4 { }
68} else {
69    /// A SIMD vector with four elements of type [`u32`].
70    ///
71    /// See the [crate level documentation] for more information about SIMD
72    /// vectors.
73    ///
74    /// [crate level documentation]: crate
75    #[derive(Default, Clone, Copy, PartialEq, Eq)]
76    #[repr(C, align(16))]
77    pub struct u32x4 { arr: [u32;4] }
78  }
79}
80
81impl_simd_uint! {
82  unsafe {
83    T = u32,
84    N = 4,
85    Simd = u32x4,
86    IntSimd = i32x4,
87    T_BITS = 32,
88    T_BITS_MUL_2 = 64,
89    [0, 1, 2, 3],
90    optional_type_x86_inner { X86Inner = __m128i },
91    optional_type_arm_inner { ArmInner = uint32x4_t },
92    optional_type_wasm_inner { WasmInner = v128 },
93  }
94
95  #[inline]
96  fn not(self) -> Self::Output {
97    self ^ cast::<u128, u32x4>(u128::MAX)
98  }
99
100  #[inline]
101  fn add(self, rhs: Self) -> Self::Output {
102    pick! {
103      if #[cfg(target_feature="sse2")] {
104        Self { sse: add_i32_m128i(self.sse, rhs.sse) }
105      } else if #[cfg(target_feature="simd128")] {
106        Self { simd: u32x4_add(self.simd, rhs.simd) }
107      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
108        unsafe { Self { neon: vaddq_u32(self.neon, rhs.neon) } }
109      } else {
110        Self { arr: [
111          self.arr[0].wrapping_add(rhs.arr[0]),
112          self.arr[1].wrapping_add(rhs.arr[1]),
113          self.arr[2].wrapping_add(rhs.arr[2]),
114          self.arr[3].wrapping_add(rhs.arr[3]),
115        ]}
116      }
117    }
118  }
119
120  #[inline]
121  fn sub(self, rhs: Self) -> Self::Output {
122    pick! {
123      if #[cfg(target_feature="sse2")] {
124        Self { sse: sub_i32_m128i(self.sse, rhs.sse) }
125      } else if #[cfg(target_feature="simd128")] {
126        Self { simd: u32x4_sub(self.simd, rhs.simd) }
127      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
128        unsafe {Self { neon: vsubq_u32(self.neon, rhs.neon) }}
129      } else {
130        Self { arr: [
131          self.arr[0].wrapping_sub(rhs.arr[0]),
132          self.arr[1].wrapping_sub(rhs.arr[1]),
133          self.arr[2].wrapping_sub(rhs.arr[2]),
134          self.arr[3].wrapping_sub(rhs.arr[3]),
135        ]}
136      }
137    }
138  }
139
140  #[inline]
141  fn mul(self, rhs: Self) -> Self::Output {
142    pick! {
143      if #[cfg(target_feature="sse4.1")] {
144        Self { sse: mul_32_m128i(self.sse, rhs.sse) }
145      } else if #[cfg(target_feature="simd128")] {
146        Self { simd: u32x4_mul(self.simd, rhs.simd) }
147      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
148        unsafe {Self { neon: vmulq_u32(self.neon, rhs.neon) }}
149      } else {
150        let arr1: [u32; 4] = cast(self);
151        let arr2: [u32; 4] = cast(rhs);
152        cast([
153          arr1[0].wrapping_mul(arr2[0]),
154          arr1[1].wrapping_mul(arr2[1]),
155          arr1[2].wrapping_mul(arr2[2]),
156          arr1[3].wrapping_mul(arr2[3]),
157        ])
158      }
159    }
160  }
161
162  #[inline]
163  fn bitand(self, rhs: Self) -> Self::Output {
164    pick! {
165      if #[cfg(target_feature="sse2")] {
166        Self { sse: bitand_m128i(self.sse, rhs.sse) }
167      } else if #[cfg(target_feature="simd128")] {
168        Self { simd: v128_and(self.simd, rhs.simd) }
169      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
170        unsafe {Self { neon: vandq_u32(self.neon, rhs.neon) }}
171      } else {
172        Self { arr: [
173          self.arr[0].bitand(rhs.arr[0]),
174          self.arr[1].bitand(rhs.arr[1]),
175          self.arr[2].bitand(rhs.arr[2]),
176          self.arr[3].bitand(rhs.arr[3]),
177        ]}
178      }
179    }
180  }
181
182  #[inline]
183  fn bitor(self, rhs: Self) -> Self::Output {
184    pick! {
185      if #[cfg(target_feature="sse2")] {
186        Self { sse: bitor_m128i(self.sse, rhs.sse) }
187      } else if #[cfg(target_feature="simd128")] {
188        Self { simd: v128_or(self.simd, rhs.simd) }
189      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
190        unsafe {Self { neon: vorrq_u32(self.neon, rhs.neon) }}
191      } else {
192        Self { arr: [
193          self.arr[0].bitor(rhs.arr[0]),
194          self.arr[1].bitor(rhs.arr[1]),
195          self.arr[2].bitor(rhs.arr[2]),
196          self.arr[3].bitor(rhs.arr[3]),
197        ]}
198      }
199    }
200  }
201
202  #[inline]
203  fn bitxor(self, rhs: Self) -> Self::Output {
204    pick! {
205      if #[cfg(target_feature="sse2")] {
206        Self { sse: bitxor_m128i(self.sse, rhs.sse) }
207      } else if #[cfg(target_feature="simd128")] {
208        Self { simd: v128_xor(self.simd, rhs.simd) }
209      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
210        unsafe {Self { neon: veorq_u32(self.neon, rhs.neon) }}
211      } else {
212        Self { arr: [
213          self.arr[0].bitxor(rhs.arr[0]),
214          self.arr[1].bitxor(rhs.arr[1]),
215          self.arr[2].bitxor(rhs.arr[2]),
216          self.arr[3].bitxor(rhs.arr[3]),
217        ]}
218      }
219    }
220  }
221
222  #[inline]
223  fn simd_eq(self, rhs: Self) -> Self::Output {
224    pick! {
225      if #[cfg(target_feature="sse2")] {
226        Self { sse: cmp_eq_mask_i32_m128i(self.sse, rhs.sse) }
227      } else if #[cfg(target_feature="simd128")] {
228        Self { simd: u32x4_eq(self.simd, rhs.simd) }
229      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
230        unsafe {Self { neon: vceqq_u32(self.neon, rhs.neon) }}
231      } else {
232        Self { arr: [
233          if self.arr[0] == rhs.arr[0] { u32::MAX } else { 0 },
234          if self.arr[1] == rhs.arr[1] { u32::MAX } else { 0 },
235          if self.arr[2] == rhs.arr[2] { u32::MAX } else { 0 },
236          if self.arr[3] == rhs.arr[3] { u32::MAX } else { 0 },
237        ]}
238      }
239    }
240  }
241
242  #[inline]
243  fn simd_ne(self, rhs: Self) -> Self::Output {
244    pick! {
245      if #[cfg(target_feature="sse2")] {
246        !self.simd_eq(rhs)
247      } else if #[cfg(target_feature="simd128")] {
248        Self { simd: u32x4_ne(self.simd, rhs.simd) }
249      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
250        !self.simd_eq(rhs)
251      } else {
252        Self { arr: [
253          if self.arr[0] != rhs.arr[0] { u32::MAX } else { 0 },
254          if self.arr[1] != rhs.arr[1] { u32::MAX } else { 0 },
255          if self.arr[2] != rhs.arr[2] { u32::MAX } else { 0 },
256          if self.arr[3] != rhs.arr[3] { u32::MAX } else { 0 },
257        ]}
258      }
259    }
260  }
261
262  #[inline]
263  fn simd_lt(self, rhs: Self) -> Self::Output {
264    // lt is just gt the other way around
265    rhs.simd_gt(self)
266  }
267
268  #[inline]
269  fn simd_gt(self, rhs: Self) -> Self::Output {
270    pick! {
271      if #[cfg(target_feature="sse2")] {
272        // no unsigned less than so inverting the high bit will get the correct result
273        let h = u32x4::splat(1 << 31);
274        Self { sse: cmp_gt_mask_i32_m128i((self ^ h).sse, (rhs ^ h).sse) }
275      } else if #[cfg(target_feature="simd128")] {
276        Self { simd: u32x4_gt(self.simd, rhs.simd) }
277      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))] {
278        unsafe {Self { neon: vcgtq_u32(self.neon, rhs.neon) }}
279      } else {
280        Self { arr: [
281          if self.arr[0] > rhs.arr[0] { u32::MAX } else { 0 },
282          if self.arr[1] > rhs.arr[1] { u32::MAX } else { 0 },
283          if self.arr[2] > rhs.arr[2] { u32::MAX } else { 0 },
284          if self.arr[3] > rhs.arr[3] { u32::MAX } else { 0 },
285        ]}
286      }
287    }
288  }
289
290  #[inline]
291  fn simd_le(self, rhs: Self) -> Self::Output {
292    pick! {
293      if #[cfg(target_feature="sse2")] {
294        !self.simd_gt(rhs)
295      } else if #[cfg(target_feature="simd128")] {
296        Self { simd: u32x4_le(self.simd, rhs.simd) }
297      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
298        !self.simd_gt(rhs)
299      } else {
300        Self { arr: [
301          if self.arr[0] <= rhs.arr[0] { u32::MAX } else { 0 },
302          if self.arr[1] <= rhs.arr[1] { u32::MAX } else { 0 },
303          if self.arr[2] <= rhs.arr[2] { u32::MAX } else { 0 },
304          if self.arr[3] <= rhs.arr[3] { u32::MAX } else { 0 },
305        ]}
306      }
307    }
308  }
309
310  #[inline]
311  fn simd_ge(self, rhs: Self) -> Self::Output {
312    pick! {
313      if #[cfg(target_feature="sse2")] {
314        !self.simd_lt(rhs)
315      } else if #[cfg(target_feature="simd128")] {
316        Self { simd: u32x4_ge(self.simd, rhs.simd) }
317      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
318        !self.simd_lt(rhs)
319      } else {
320        Self { arr: [
321          if self.arr[0] >= rhs.arr[0] { u32::MAX } else { 0 },
322          if self.arr[1] >= rhs.arr[1] { u32::MAX } else { 0 },
323          if self.arr[2] >= rhs.arr[2] { u32::MAX } else { 0 },
324          if self.arr[3] >= rhs.arr[3] { u32::MAX } else { 0 },
325        ]}
326      }
327    }
328  }
329
330  #[inline]
331  pub fn reduce_add(self) -> u32 {
332    pick! {
333      if #[cfg(target_feature="sse2")] {
334        let hi64  = unpack_high_i64_m128i(self.sse, self.sse);
335        let sum64 = add_i32_m128i(hi64, self.sse);
336        let hi32  = shuffle_ai_f32_all_m128i::<0b10_11_00_01>(sum64);    // Swap the low two elements
337        let sum32 = add_i32_m128i(sum64, hi32);
338        get_i32_from_m128i_s(sum32).cast_unsigned()
339      } else {
340        let arr: [u32; 4] = cast(self);
341        arr[0].wrapping_add(arr[1]).wrapping_add(
342        arr[2].wrapping_add(arr[3]))
343      }
344    }
345  }
346
347  #[inline]
348  pub fn reduce_mul(self) -> u32 {
349    pick! {
350      if #[cfg(target_feature="sse4.1")] {
351        let high_64  = unpack_high_i64_m128i(self.sse, self.sse);
352        let reduce_64 = mul_32_m128i(high_64, self.sse);
353        let high_32  = shuffle_ai_f32_all_m128i::<0b10_11_00_01>(reduce_64);
354        let reduce_32 = mul_32_m128i(reduce_64, high_32);
355        get_i32_from_m128i_s(reduce_32).cast_unsigned()
356      } else if #[cfg(target_feature="simd128")] {
357        let high_64 = u64x2_shuffle::<1, 0>(self.simd, self.simd);
358        let reduce_64 = u32x4_mul(self.simd, high_64);
359        let high_32 = u32x4_shuffle::<1, 0, 0, 0>(reduce_64, reduce_64);
360        let reduce_32 = u32x4_mul(reduce_64, high_32);
361        u32x4_extract_lane::<0>(reduce_32)
362      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
363        unsafe {
364          let high_64 = vextq_u32::<2>(self.neon, self.neon);
365          let reduce_64 = vmulq_u32(self.neon, high_64);
366          let high_32 = vrev64q_u32(reduce_64);
367          let reduce_32 = vmulq_u32(reduce_64, high_32);
368          vgetq_lane_u32::<0>(reduce_32)
369        }
370      } else {
371        let array = self.to_array();
372        array[0].wrapping_mul(array[1]).wrapping_mul(array[2].wrapping_mul(array[3]))
373      }
374    }
375  }
376
377  #[inline]
378  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
379    pick! {
380      if #[cfg(target_feature="sse2")] {
381        Self {
382          sse: bitor_m128i(
383            bitand_m128i(if_one.sse, self.sse),
384            bitandnot_m128i(self.sse, if_zero.sse),
385          ),
386        }
387      } else if #[cfg(target_feature="simd128")] {
388        Self { simd: v128_bitselect(if_one.simd, if_zero.simd, self.simd) }
389      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
390        unsafe {Self { neon: vbslq_u32(self.neon, if_one.neon, if_zero.neon) }}
391      } else {
392        generic_bit_blend(self, if_one, if_zero)
393      }
394    }
395  }
396
397  #[inline]
398  fn select(self, if_true: Self, if_false: Self) -> Self {
399    pick! {
400      if #[cfg(target_feature="sse4.1")] {
401        Self { sse: blend_varying_i8_m128i(if_false.sse, if_true.sse, self.sse) }
402      } else if #[cfg(target_feature="simd128")] {
403        Self { simd: v128_bitselect(if_true.simd, if_false.simd, self.simd) }
404      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
405        unsafe {Self { neon: vbslq_u32(self.neon, if_true.neon, if_false.neon) }}
406      } else {
407        generic_bit_blend(self, if_true, if_false)
408      }
409    }
410  }
411
412  #[inline]
413  pub fn to_bitmask(self) -> u32 {
414    pick! {
415      if #[cfg(target_feature="sse2")] {
416        // use f32 move_mask since it is the same size as i32
417        move_mask_m128(cast(self.sse)) as u32
418      } else if #[cfg(target_feature="simd128")] {
419        u32x4_bitmask(self.simd) as u32
420      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
421        unsafe
422        {
423          // set all to 1 if top bit is set, else 0
424          let masked = vcltq_s32(self.cast_signed().neon, vdupq_n_s32(0));
425
426          // select the right bit out of each lane
427          let selectbit : uint32x4_t = core::mem::transmute([1u32, 2, 4, 8]);
428          let r = vandq_u32(masked, selectbit);
429
430          // horizontally add the 32-bit lanes
431          vaddvq_u32(r) as u32
432         }
433      } else {
434        self.arr[0] >> 31 |
435        self.arr[1] >> 31 << 1 |
436        self.arr[2] >> 31 << 2 |
437        self.arr[3] >> 31 << 3
438      }
439    }
440  }
441
442  #[inline]
443  pub fn any(self) -> bool {
444    pick! {
445      if #[cfg(target_feature="sse2")] {
446        // use f32 move_mask since it is the same size as i32
447        move_mask_m128(cast(self.sse)) != 0
448      } else if #[cfg(target_feature="simd128")] {
449        u32x4_bitmask(self.simd) != 0
450      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))] {
451        // some lanes are negative
452        unsafe {
453          vminvq_s32(self.cast_signed().neon) < 0
454        }
455      } else {
456        let v : [u64;2] = cast(self);
457        ((v[0] | v[1]) & 0x8000000080000000) != 0
458      }
459    }
460  }
461
462  #[inline]
463  pub fn all(self) -> bool {
464    pick! {
465      if #[cfg(target_feature="sse2")] {
466        // use f32 move_mask since it is the same size as i32
467        move_mask_m128(cast(self.sse)) == 0b1111
468      } else if #[cfg(target_feature="simd128")] {
469        u32x4_bitmask(self.simd) == 0b1111
470      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
471        // all lanes are negative
472        unsafe {
473          vmaxvq_s32(self.cast_signed().neon) < 0
474        }
475      } else {
476        let v : [u64;2] = cast(self);
477        (v[0] & v[1] & 0x8000000080000000) == 0x8000000080000000
478      }
479    }
480  }
481
482  #[inline]
483  pub fn shuffle(self, indices: u32x4) -> Self {
484    pick! {
485      if #[cfg(target_feature = "avx")] {
486        cast(shuffle_av_f32_all_m128(cast(self), indices.sse))
487      } else if #[cfg(any(
488        target_feature = "ssse3",
489        all(target_arch = "aarch64", target_feature = "neon"),
490        target_feature = "simd128",
491      ))] {
492        let self_bytes = cast::<u32x4, u8x16>(self);
493        let byte_indices = indices.to_byte_indices();
494
495        cast::<u8x16, u32x4>(self_bytes.shuffle(byte_indices))
496      } else {
497        let self_array = self.to_array();
498        let indices_array = indices.to_array();
499
500        let mut result = [0; 4];
501        for i in 0..4 {
502          let index = indices_array[i] as usize;
503          if index < 4 {
504            result[i] = self_array[index];
505          }
506        }
507
508        Self::new(result)
509      }
510    }
511  }
512
513  #[inline]
514  pub fn shuffle_zeroing(self, indices: u32x4) -> Self {
515    pick! {
516      if #[cfg(any(
517        target_feature = "avx",
518        target_feature = "ssse3",
519        all(target_arch = "aarch64", target_feature = "neon"),
520        target_feature = "simd128",
521      ))] {
522        // Even if the `u8x16::shuffle` implementation is zeroing, our 32-bit to
523        // 8-bit can trigger an overflow causing incorrect behavior
524        self.shuffle(indices) & indices.simd_lt(4)
525      } else {
526        // The fallback branch of `shuffle` already has the behavior we want
527        self.shuffle(indices)
528      }
529    }
530  }
531
532  #[inline]
533  pub fn shuffle_wrapping(self, indices: u32x4) -> Self {
534    self.shuffle(indices & 3)
535  }
536
537  #[inline]
538  fn shuffle(self: [u32x4; 2], indices: u32x4) -> u32x4 {
539    pick! {
540      if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
541        u32x4 { sse: shuffle_abv_i32_all_m128i(self[0].sse, indices.sse, self[1].sse) }
542      } else {
543        let self_bytes = cast::<[u32x4; 2], [u8x16; 2]>(self);
544        let byte_indices = indices.to_byte_indices();
545
546        cast::<u8x16, u32x4>(self_bytes.shuffle(byte_indices))
547      }
548    }
549  }
550
551  #[inline]
552  fn shuffle_zeroing(self: [u32x4; 2], indices: u32x4) -> u32x4 {
553    // Even if the `u8x16::shuffle` implementation is zeroing, our 32-bit to
554    // 8-bit can trigger an overflow causing incorrect behavior
555    self.shuffle(indices) & indices.simd_lt(8)
556  }
557
558  #[inline]
559  fn shuffle_wrapping(self: [u32x4; 2], indices: u32x4) -> u32x4 {
560    pick! {
561      if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
562        // `avx512` shuffle intrinsics are wrapping
563        self.shuffle(indices)
564      } else {
565        self.shuffle(indices & 7)
566      }
567    }
568  }
569
570  #[inline]
571  fn shuffle(self: [u32x4; 3], indices: u32x4) -> u32x4 {
572    let self_bytes = cast::<[u32x4; 3], [u8x16; 3]>(self);
573    let byte_indices = indices.to_byte_indices();
574
575    cast::<u8x16, u32x4>(self_bytes.shuffle(byte_indices))
576  }
577
578  #[inline]
579  fn shuffle_zeroing(self: [u32x4; 3], indices: u32x4) -> u32x4 {
580    // Even if the `u8x16::shuffle` implementation is zeroing, our 32-bit to
581    // 8-bit can trigger an overflow causing incorrect behavior
582    self.shuffle(indices) & indices.simd_lt(12)
583  }
584
585  #[inline]
586  fn shuffle_wrapping(self: [u32x4; 3], indices: u32x4) -> u32x4 {
587    self.shuffle(indices % 12)
588  }
589
590  #[inline]
591  fn shuffle(self: [u32x4; 4], indices: u32x4) -> u32x4 {
592    let self_bytes = cast::<[u32x4; 4], [u8x16; 4]>(self);
593    let byte_indices = indices.to_byte_indices();
594
595    cast::<u8x16, u32x4>(self_bytes.shuffle(byte_indices))
596  }
597
598  #[inline]
599  fn shuffle_zeroing(self: [u32x4; 4], indices: u32x4) -> u32x4 {
600    // Even if the `u8x16::shuffle` implementation is zeroing, our 32-bit to
601    // 8-bit can trigger an overflow causing incorrect behavior
602    self.shuffle(indices) & indices.simd_lt(16)
603  }
604
605  #[inline]
606  fn shuffle_wrapping(self: [u32x4; 4], indices: u32x4) -> u32x4 {
607    self.shuffle(indices & 15)
608  }
609
610  ///
611  /// Currently this function is only accelerated on `sse`.
612  #[inline]
613  pub fn transpose(data: [Self; 4]) -> [Self; 4] {
614    pick! {
615      if #[cfg(target_feature="sse")] {
616        let mut e0 = data[0];
617        let mut e1 = data[1];
618        let mut e2 = data[2];
619        let mut e3 = data[3];
620
621        transpose_four_m128(
622          cast_mut(&mut e0.sse),
623          cast_mut(&mut e1.sse),
624          cast_mut(&mut e2.sse),
625          cast_mut(&mut e3.sse),
626        );
627
628        [e0, e1, e2, e3]
629      } else {
630        #[inline(always)]
631        fn transpose_column(data: &[u32x4; 4], index: usize) -> u32x4 {
632          u32x4::new([
633            data[0].as_array()[index],
634            data[1].as_array()[index],
635            data[2].as_array()[index],
636            data[3].as_array()[index],
637          ])
638        }
639
640        [
641          transpose_column(&data, 0),
642          transpose_column(&data, 1),
643          transpose_column(&data, 2),
644          transpose_column(&data, 3),
645        ]
646      }
647    }
648  }
649
650  #[inline]
651  fn shl(self, rhs: u32x4) -> Self::Output {
652    pick! {
653      if #[cfg(target_feature="avx2")] {
654        // mask the shift count to 31 to have same behavior on all platforms
655        let shift_by = bitand_m128i(rhs.sse, set_splat_i32_m128i(31));
656        Self { sse: shl_each_u32_m128i(self.sse, shift_by) }
657      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
658        unsafe {
659          // mask the shift count to 31 to have same behavior on all platforms
660          let shift_by = vreinterpretq_s32_u32(vandq_u32(rhs.neon, vmovq_n_u32(31)));
661          Self { neon: vshlq_u32(self.neon, shift_by) }
662        }
663      } else {
664        let arr: [u32; 4] = cast(self);
665        let rhs: [u32; 4] = cast(rhs);
666        cast([
667          arr[0].wrapping_shl(rhs[0]),
668          arr[1].wrapping_shl(rhs[1]),
669          arr[2].wrapping_shl(rhs[2]),
670          arr[3].wrapping_shl(rhs[3]),
671        ])
672      }
673    }
674  }
675
676  #[inline]
677  fn shl(self, rhs: u32) -> Self::Output {
678    pick! {
679      if #[cfg(target_feature="sse2")] {
680        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
681        #[expect(clippy::suspicious_arithmetic_impl)]
682        let shift = cast([rhs as u64 & 31, 0]);
683        Self { sse: shl_all_u32_m128i(self.sse, shift) }
684      } else if #[cfg(target_feature="simd128")] {
685        Self { simd: u32x4_shl(self.simd, rhs) }
686      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
687        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
688        #[expect(clippy::suspicious_arithmetic_impl)]
689        unsafe {Self { neon: vshlq_u32(self.neon, vmovq_n_s32(rhs as i32 & 31)) }}
690      } else {
691        Self { arr: [
692          self.arr[0].wrapping_shl(rhs),
693          self.arr[1].wrapping_shl(rhs),
694          self.arr[2].wrapping_shl(rhs),
695          self.arr[3].wrapping_shl(rhs),
696        ]}
697      }
698    }
699  }
700
701  #[inline]
702  fn shr(self, rhs: u32x4) -> Self::Output {
703    pick! {
704      if #[cfg(target_feature="avx2")] {
705        // mask the shift count to 31 to have same behavior on all platforms
706        let shift_by = bitand_m128i(rhs.sse, set_splat_i32_m128i(31));
707        Self { sse: shr_each_u32_m128i(self.sse, shift_by) }
708      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
709        unsafe {
710          // mask the shift count to 31 to have same behavior on all platforms
711          // no right shift, have to pass negative value to left shift on neon
712          let shift_by = vnegq_s32(vreinterpretq_s32_u32(vandq_u32(rhs.neon, vmovq_n_u32(31))));
713          Self { neon: vshlq_u32(self.neon, shift_by) }
714        }
715      } else {
716        let arr: [u32; 4] = cast(self);
717        let rhs: [u32; 4] = cast(rhs);
718        cast([
719          arr[0].wrapping_shr(rhs[0]),
720          arr[1].wrapping_shr(rhs[1]),
721          arr[2].wrapping_shr(rhs[2]),
722          arr[3].wrapping_shr(rhs[3]),
723        ])
724      }
725    }
726  }
727
728  #[inline]
729  fn shr(self, rhs: u32) -> Self::Output {
730    pick! {
731      if #[cfg(target_feature="sse2")] {
732        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
733        #[expect(clippy::suspicious_arithmetic_impl)]
734        let shift = cast([rhs as u64 & 31, 0]);
735        Self { sse: shr_all_u32_m128i(self.sse, shift) }
736      } else if #[cfg(target_feature="simd128")] {
737        Self { simd: u32x4_shr(self.simd, rhs) }
738      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
739        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
740        #[expect(clippy::suspicious_arithmetic_impl)]
741        unsafe {Self { neon: vshlq_u32(self.neon, vmovq_n_s32( -(rhs as i32 & 31))) }}
742      } else {
743        Self { arr: [
744          self.arr[0].wrapping_shr(rhs),
745          self.arr[1].wrapping_shr(rhs),
746          self.arr[2].wrapping_shr(rhs),
747          self.arr[3].wrapping_shr(rhs),
748        ]}
749      }
750    }
751  }
752
753  #[inline]
754  pub fn max(self, rhs: Self) -> Self {
755    pick! {
756      if #[cfg(target_feature="sse4.1")] {
757        Self { sse: max_u32_m128i(self.sse, rhs.sse) }
758      } else if #[cfg(target_feature="simd128")] {
759        Self { simd: u32x4_max(self.simd, rhs.simd) }
760      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
761        unsafe {Self { neon: vmaxq_u32(self.neon, rhs.neon) }}
762      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
763        unsafe {Self { neon: vmaxq_u16(self.neon, rhs.neon) }}
764      } else {
765        let arr: [u32; 4] = cast(self);
766        let rhs: [u32; 4] = cast(rhs);
767        cast([
768          arr[0].max(rhs[0]),
769          arr[1].max(rhs[1]),
770          arr[2].max(rhs[2]),
771          arr[3].max(rhs[3]),
772        ])
773      }
774    }
775  }
776
777  #[inline]
778  pub fn min(self, rhs: Self) -> Self {
779    pick! {
780      if #[cfg(target_feature="sse4.1")] {
781        Self { sse: min_u32_m128i(self.sse, rhs.sse) }
782      } else if #[cfg(target_feature="simd128")] {
783        Self { simd: u32x4_min(self.simd, rhs.simd) }
784      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
785        unsafe {Self { neon: vminq_u32(self.neon, rhs.neon) }}
786      } else {
787        let arr: [u32; 4] = cast(self);
788        let rhs: [u32; 4] = cast(rhs);
789        cast([
790          arr[0].min(rhs[0]),
791          arr[1].min(rhs[1]),
792          arr[2].min(rhs[2]),
793          arr[3].min(rhs[3]),
794        ])
795      }
796    }
797  }
798
799  #[inline]
800  pub fn reduce_max(self) -> u32 {
801    let arr: [u32; 4] = cast(self);
802    arr[0].max(arr[1]).max(arr[2].max(arr[3]))
803  }
804
805  #[inline]
806  pub fn reduce_min(self) -> u32 {
807    let arr: [u32; 4] = cast(self);
808    arr[0].min(arr[1]).min(arr[2].min(arr[3]))
809  }
810
811  #[inline]
812  pub fn unbounded_shl(self, rhs: Self) -> Self {
813    pick! {
814      if #[cfg(target_feature="avx2")] {
815        Self { sse: shl_each_u32_m128i(self.sse, rhs.sse) }
816      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
817        unsafe {
818          // The intrinsic has different semantics so we need to mask ourselves.
819          Self { neon: vshlq_u32(self.neon, vreinterpretq_s32_u32(rhs.neon)) } & rhs.simd_lt(32)
820        }
821      } else {
822        let self_array = self.to_array();
823        let rhs_array = rhs.to_array();
824
825        cast([
826          self_array[0].unbounded_shl(rhs_array[0]),
827          self_array[1].unbounded_shl(rhs_array[1]),
828          self_array[2].unbounded_shl(rhs_array[2]),
829          self_array[3].unbounded_shl(rhs_array[3]),
830        ])
831      }
832    }
833  }
834
835  #[inline]
836  pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
837    pick! {
838      if #[cfg(target_feature="sse2")] {
839        Self { sse: shl_all_u32_m128i(self.sse, cast([rhs as u64, 0])) }
840      } else if #[cfg(target_feature="simd128")] {
841        // The intrinsic performs wrapping shift so we need to mask the result.
842        Self { simd: u32x4_shl(self.simd, rhs) } & Self::splat(rhs).simd_lt(32)
843      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
844        // The intrinsic has different semantics so we need to saturate `rhs`.
845        unsafe { Self { neon: vshlq_u32(self.neon, vmovq_n_s32(rhs.min(32) as i32)) } }
846      } else {
847        Self { arr: [
848          self.arr[0].unbounded_shl(rhs),
849          self.arr[1].unbounded_shl(rhs),
850          self.arr[2].unbounded_shl(rhs),
851          self.arr[3].unbounded_shl(rhs),
852        ]}
853      }
854    }
855  }
856
857  #[inline]
858  pub fn unbounded_shr(self, rhs: Self) -> Self {
859    pick! {
860      if #[cfg(target_feature="avx2")] {
861        Self { sse: shr_each_u32_m128i(self.sse, rhs.sse) }
862      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
863        unsafe {
864          // Negate `rhs` because there is no direct shift-right intrinsic, and
865          // mask to hide `rhs` overflow.
866          Self { neon: vshlq_u32(self.neon, vnegq_s32(vreinterpretq_s32_u32(rhs.neon))) } & rhs.simd_lt(32)
867        }
868      } else {
869        let self_array = self.to_array();
870        let rhs_array = rhs.to_array();
871
872        Self::new([
873          self_array[0].unbounded_shr(rhs_array[0]),
874          self_array[1].unbounded_shr(rhs_array[1]),
875          self_array[2].unbounded_shr(rhs_array[2]),
876          self_array[3].unbounded_shr(rhs_array[3]),
877        ])
878      }
879    }
880  }
881
882  #[inline]
883  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
884    pick! {
885      if #[cfg(target_feature="sse2")] {
886        Self { sse: shr_all_u32_m128i(self.sse, cast([rhs as u64, 0])) }
887      } else if #[cfg(target_feature="simd128")] {
888        if rhs < 32 { Self { simd: u32x4_shr(self.simd, rhs) } } else { Self::ZERO }
889      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
890        unsafe {
891          // Negate `rhs` because there is no direct shift-right intrinsic, and
892          // restrict it to prevent overflow.
893          Self { neon: vshlq_u32(self.neon, vmovq_n_s32(-rhs.min(32).cast_signed())) }
894        }
895      } else {
896        Self {
897          arr: [
898            self.arr[0].unbounded_shr(rhs),
899            self.arr[1].unbounded_shr(rhs),
900            self.arr[2].unbounded_shr(rhs),
901            self.arr[3].unbounded_shr(rhs),
902          ],
903        }
904      }
905    }
906  }
907
908  #[inline]
909  pub fn saturating_add(self, rhs: Self) -> Self {
910    pick! {
911      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
912        let result = self + rhs;
913        let overflow = result.simd_lt(self);
914        // Return `MAX` (all bits set) if overflow occurs.
915        result | overflow
916      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
917        unsafe { Self { neon: vqaddq_u32(self.neon, rhs.neon) } }
918      } else {
919        Self {
920          arr: [
921            self.arr[0].saturating_add(rhs.arr[0]),
922            self.arr[1].saturating_add(rhs.arr[1]),
923            self.arr[2].saturating_add(rhs.arr[2]),
924            self.arr[3].saturating_add(rhs.arr[3]),
925          ],
926        }
927      }
928    }
929  }
930
931  #[inline]
932  pub fn saturating_sub(self, rhs: Self) -> Self {
933    pick! {
934      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
935        let result = self - rhs;
936        let no_overflow = result.simd_le(self);
937        // Return `0` (no bits set) if overflow occurs.
938        result & no_overflow
939      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
940        unsafe { Self { neon: vqsubq_u32(self.neon, rhs.neon) } }
941      } else {
942        Self {
943          arr: [
944            self.arr[0].saturating_sub(rhs.arr[0]),
945            self.arr[1].saturating_sub(rhs.arr[1]),
946            self.arr[2].saturating_sub(rhs.arr[2]),
947            self.arr[3].saturating_sub(rhs.arr[3]),
948          ],
949        }
950      }
951    }
952  }
953
954  #[inline]
955  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
956    let (low, high) = self.mul_keep_low_high(rhs);
957    let overflow = high.simd_ne(Self::ZERO);
958    (low, overflow)
959  }
960
961  optional_fn_widening_mul {
962    #[inline]
963    pub fn widening_mul(self, rhs: Self) -> u64x4 {
964      pick! {
965        if #[cfg(target_feature="avx2")] {
966          // ok to sign extend since we are throwing away the high half of the result anyway
967          let a = convert_to_i64_m256i_from_i32_m128i(self.sse);
968          let b = convert_to_i64_m256i_from_i32_m128i(rhs.sse);
969          cast(mul_u64_low_bits_m256i(a, b))
970        } else if #[cfg(target_feature="sse2")] {
971          let evenp = mul_widen_u32_odd_m128i(self.sse, rhs.sse);
972
973          let oddp = mul_widen_u32_odd_m128i(
974            shr_imm_u64_m128i::<32>(self.sse),
975            shr_imm_u64_m128i::<32>(rhs.sse));
976
977          u64x4 {
978            a: u64x2 { sse: unpack_low_i64_m128i(evenp, oddp)},
979            b: u64x2 { sse: unpack_high_i64_m128i(evenp, oddp)}
980          }
981        } else if #[cfg(target_feature="simd128")] {
982          u64x4 {
983            a: u64x2 { simd: u64x2_extmul_low_u32x4(self.simd, rhs.simd) },
984            b: u64x2 { simd: u64x2_extmul_high_u32x4(self.simd, rhs.simd) },
985          }
986        } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))] {
987        unsafe {
988          u64x4 { a: u64x2 { neon: vmull_u32(vget_low_u32(self.neon), vget_low_u32(rhs.neon)) },
989                  b: u64x2 { neon: vmull_u32(vget_high_u32(self.neon), vget_high_u32(rhs.neon)) } }
990          }
991        } else {
992          let a: [u32; 4] = cast(self);
993          let b: [u32; 4] = cast(rhs);
994          cast([
995            u64::from(a[0]) * u64::from(b[0]),
996            u64::from(a[1]) * u64::from(b[1]),
997            u64::from(a[2]) * u64::from(b[2]),
998            u64::from(a[3]) * u64::from(b[3]),
999          ])
1000        }
1001      }
1002    }
1003  }
1004
1005  #[inline]
1006  pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
1007    pick! {
1008      if #[cfg(target_feature="sse4.1")] {
1009        let even_wide_mul = mul_widen_u32_odd_m128i(self.sse, rhs.sse);
1010        let odd_wide_mul = mul_widen_u32_odd_m128i(
1011          shuffle_ai_f32_all_m128i::<0b_00_11_00_01>(self.sse),
1012          shuffle_ai_f32_all_m128i::<0b_00_11_00_01>(rhs.sse),
1013        );
1014
1015        let ll_hh_1 = unpack_low_i32_m128i(even_wide_mul, odd_wide_mul);
1016        let ll_hh_2 = unpack_high_i32_m128i(even_wide_mul, odd_wide_mul);
1017        (
1018          Self { sse: unpack_low_i64_m128i(ll_hh_1, ll_hh_2) },
1019          Self { sse: unpack_high_i64_m128i(ll_hh_1, ll_hh_2) },
1020        )
1021      } else if #[cfg(target_feature="simd128")] {
1022        let low_wide_mul = u64x2_extmul_low_u32x4(self.simd, rhs.simd);
1023        let high_wide_mul = u64x2_extmul_high_u32x4(self.simd, rhs.simd);
1024        (
1025          Self { simd: u32x4_shuffle::<0, 2, 4, 6>(low_wide_mul, high_wide_mul) },
1026          Self { simd: u32x4_shuffle::<1, 3, 5, 7>(low_wide_mul, high_wide_mul) },
1027        )
1028      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
1029        unsafe {
1030          let low_wide_mul = vreinterpretq_u32_u64(
1031            vmull_u32(vget_low_u32(self.neon), vget_low_u32(rhs.neon)),
1032          );
1033          let high_wide_mul = vreinterpretq_u32_u64(
1034            vmull_u32(vget_high_u32(self.neon), vget_high_u32(rhs.neon)),
1035          );
1036          let low_high = vuzpq_u32(low_wide_mul, high_wide_mul);
1037          (
1038            Self { neon: low_high.0 },
1039            Self { neon: low_high.1 },
1040          )
1041        }
1042      } else {
1043        // TODO(perf): This implementation looks quite bad. Is there a better
1044        // one?
1045
1046        let self_array = self.to_array();
1047        let rhs_array = rhs.to_array();
1048
1049        let widening_mul = [
1050          (self_array[0] as u64).wrapping_mul(rhs_array[0] as u64),
1051          (self_array[1] as u64).wrapping_mul(rhs_array[1] as u64),
1052          (self_array[2] as u64).wrapping_mul(rhs_array[2] as u64),
1053          (self_array[3] as u64).wrapping_mul(rhs_array[3] as u64),
1054        ];
1055
1056        (
1057          Self::new([
1058            widening_mul[0] as u32,
1059            widening_mul[1] as u32,
1060            widening_mul[2] as u32,
1061            widening_mul[3] as u32,
1062          ]),
1063          Self::new([
1064            (widening_mul[0] >> 32) as u32,
1065            (widening_mul[1] >> 32) as u32,
1066            (widening_mul[2] >> 32) as u32,
1067            (widening_mul[3] >> 32) as u32,
1068          ]),
1069        )
1070      }
1071    }
1072  }
1073
1074  #[inline]
1075  pub fn mul_keep_high(self, rhs: Self) -> Self {
1076    pick! {
1077      if #[cfg(target_feature="avx2")] {
1078        let a = convert_to_i64_m256i_from_u32_m128i(self.sse);
1079        let b = convert_to_i64_m256i_from_u32_m128i(rhs.sse);
1080        let r = mul_u64_low_bits_m256i(a, b);
1081
1082        // the compiler does a good job shuffling the lanes around
1083        let b : [u32;8] = cast(r);
1084        cast([b[1],b[3],b[5],b[7]])
1085      } else if #[cfg(target_feature="sse2")] {
1086        let evenp = mul_widen_u32_odd_m128i(self.sse, rhs.sse);
1087
1088        let oddp = mul_widen_u32_odd_m128i(
1089          shr_imm_u64_m128i::<32>(self.sse),
1090          shr_imm_u64_m128i::<32>(rhs.sse));
1091
1092        // the compiler does a good job shuffling the lanes around
1093        let a : [u32;4]= cast(evenp);
1094        let b : [u32;4]= cast(oddp);
1095        cast([a[1],b[1],a[3],b[3]])
1096
1097      } else if #[cfg(target_feature="simd128")] {
1098        let low =  u64x2_extmul_low_u32x4(self.simd, rhs.simd);
1099        let high = u64x2_extmul_high_u32x4(self.simd, rhs.simd);
1100
1101        Self { simd: u32x4_shuffle::<1, 3, 5, 7>(low, high) }
1102      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))] {
1103        unsafe {
1104          let l = vmull_u32(vget_low_u32(self.neon), vget_low_u32(rhs.neon));
1105          let h = vmull_u32(vget_high_u32(self.neon), vget_high_u32(rhs.neon));
1106          u32x4 { neon: vcombine_u32(vshrn_n_u64(l,32), vshrn_n_u64(h,32)) }
1107        }
1108      } else {
1109        let a: [u32; 4] = cast(self);
1110        let b: [u32; 4] = cast(rhs);
1111        cast([
1112          ((u64::from(a[0]) * u64::from(b[0])) >> 32) as u32,
1113          ((u64::from(a[1]) * u64::from(b[1])) >> 32) as u32,
1114          ((u64::from(a[2]) * u64::from(b[2])) >> 32) as u32,
1115          ((u64::from(a[3]) * u64::from(b[3])) >> 32) as u32,
1116        ])
1117      }
1118    }
1119  }
1120
1121  optional_fn_deserialize {}
1122}
1123
1124/// The following functionality exists only for [`u32x4`], or only for
1125/// particular types inconsistently.
1126impl u32x4 {
1127  /// Widening multiplication. Computes `self * rhs`, widening to a SIMD
1128  /// vector of larger integers.
1129  ///
1130  /// The returned value is always exact and can never overflow.
1131  ///
1132  /// This function has been renamed to [`widening_mul`].
1133  ///
1134  /// [`widening_mul`]: Self::widening_mul
1135  #[inline]
1136  #[must_use]
1137  #[deprecated(since = "1.6.0", note = "renamed to `widening_mul`")]
1138  pub fn mul_widen(self, rhs: Self) -> u64x4 {
1139    self.widening_mul(rhs)
1140  }
1141
1142  /// Returns `[self[0], b[0], self[1], b[1]]`, interleaving the low two
1143  /// elements of the 128-bit lane.
1144  #[inline]
1145  #[must_use]
1146  pub fn unpack_lo(self, b: Self) -> Self {
1147    pick! {
1148      if #[cfg(target_feature="sse2")] {
1149        Self { sse: unpack_low_i32_m128i(self.sse, b.sse) }
1150      } else if #[cfg(target_feature="simd128")] {
1151        Self { simd: u32x4_shuffle::<0, 4, 1, 5>(self.simd, b.simd) }
1152      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
1153        Self { neon: unsafe { vzip1q_u32(self.neon, b.neon) } }
1154      } else {
1155        let s = self.as_array();
1156        let b = b.as_array();
1157        Self::new([s[0], b[0], s[1], b[1]])
1158      }
1159    }
1160  }
1161
1162  /// Returns `[self[2], b[2], self[3], b[3]]`, interleaving the high two
1163  /// elements of the 128-bit lane.
1164  #[inline]
1165  #[must_use]
1166  pub fn unpack_hi(self, b: Self) -> Self {
1167    pick! {
1168      if #[cfg(target_feature="sse2")] {
1169        Self { sse: unpack_high_i32_m128i(self.sse, b.sse) }
1170      } else if #[cfg(target_feature="simd128")] {
1171        Self { simd: u32x4_shuffle::<2, 6, 3, 7>(self.simd, b.simd) }
1172      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
1173        Self { neon: unsafe { vzip2q_u32(self.neon, b.neon) } }
1174      } else {
1175        let s = self.as_array();
1176        let b = b.as_array();
1177        Self::new([s[2], b[2], s[3], b[3]])
1178      }
1179    }
1180  }
1181
1182  /// `self + ((a * b) mod 2^W)`, reading only the low `W` bits of each lane of
1183  /// `a` and `b`. `W` must be in `1..=32`.
1184  ///
1185  /// There is no IFMA equivalent at this width, and below 17 bits no widening
1186  /// multiply is needed either: the whole product fits a lane, so the ordinary
1187  /// lane multiply already yields both halves.
1188  #[inline]
1189  #[must_use]
1190  pub fn add_mul_lo<const W: u32>(self, a: Self, b: Self) -> Self {
1191    if W <= 16 {
1192      let mask = Self::splat(add_mul_operand_mask_u32::<W>());
1193      return self + (((a & mask) * (b & mask)) & mask);
1194    }
1195
1196    let acc = self.to_array();
1197    let a = a.to_array();
1198    let b = b.to_array();
1199    Self::new(core::array::from_fn(|i| {
1200      add_mul_lo_lane_u32::<W>(acc[i], a[i], b[i])
1201    }))
1202  }
1203
1204  /// `self + ((a * b) >> W)`, reading only the low `W` bits of each lane of `a`
1205  /// and `b`. `W` must be in `1..=32`.
1206  #[inline]
1207  #[must_use]
1208  pub fn add_mul_hi<const W: u32>(self, a: Self, b: Self) -> Self {
1209    // See `add_mul_lo`: the whole product is in the lane, so the high half is a
1210    // shift.
1211    if W <= 16 {
1212      let mask = Self::splat(add_mul_operand_mask_u32::<W>());
1213      return self + (((a & mask) * (b & mask)) >> W);
1214    }
1215
1216    let acc = self.to_array();
1217    let a = a.to_array();
1218    let b = b.to_array();
1219    Self::new(core::array::from_fn(|i| {
1220      add_mul_hi_lane_u32::<W>(acc[i], a[i], b[i])
1221    }))
1222  }
1223
1224  /// A helper for shuffle functions that turns indices of 32-bit lanes into
1225  /// byte indices that can be used with 8-bit shuffle intrinsics.
1226  ///
1227  /// This turns each 32-bit lane `i` into four 8-bit lanes
1228  /// `[4*i, 4*i + 1, 4*i + 2, 4*i + 3]`.
1229  ///
1230  /// This assumes `self` has already been reduced to the table's lane count,
1231  /// which may be at most 64 lanes so that `4 * i` still fits in a byte.
1232  #[allow(dead_code)]
1233  #[inline]
1234  fn to_byte_indices(self) -> u8x16 {
1235    // The byte offset of the lane, broadcast to every byte of the lane.
1236    let base = self.unbounded_shl_scalar(2);
1237    let base = base | base.unbounded_shl_scalar(8);
1238    let base = base | base.unbounded_shl_scalar(16);
1239
1240    // Then the offset of each byte within its lane. These bits are free because
1241    // every byte of `base` is a multiple of four. `from_ne_bytes` keeps this
1242    // correct on big endian, where the bytes of a lane are the other way around.
1243    const WITHIN_LANE: u32x4 = u32x4::splat(u32::from_ne_bytes([0, 1, 2, 3]));
1244
1245    cast::<u32x4, u8x16>(base | WITHIN_LANE)
1246  }
1247}