Skip to main content

wide/
u32x16_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx512f")] {
5    /// A SIMD vector with 16 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(64))]
13    pub struct u32x16 { pub(crate) avx512: m512i }
14  } else {
15    /// A SIMD vector with 16 elements of type [`u32`].
16    ///
17    /// See the [crate level documentation] for more information about SIMD
18    /// vectors.
19    ///
20    /// [crate level documentation]: crate
21    #[derive(Default, Clone, Copy, PartialEq, Eq)]
22    #[repr(C, align(64))]
23    pub struct u32x16 { pub(crate) a : u32x8, pub(crate) b : u32x8 }
24  }
25}
26
27impl_simd_uint! {
28  unsafe {
29    T = u32,
30    N = 16,
31    Simd = u32x16,
32    IntSimd = i32x16,
33    T_BITS = 32,
34    T_BITS_MUL_2 = 64,
35    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
36    optional_type_x86_inner { X86Inner = __m512i },
37    optional_type_arm_inner {},
38    optional_type_wasm_inner {},
39  }
40
41  #[inline]
42  fn not(self) -> Self::Output {
43    pick! {
44      if #[cfg(target_feature="avx512f")] {
45        Self { avx512: bitxor_m512i(self.avx512, set_splat_i32_m512i(-1)) }
46      } else {
47        Self {
48          a : self.a.not(),
49          b : self.b.not(),
50        }
51      }
52    }
53  }
54
55  #[inline]
56  fn add(self, rhs: Self) -> Self::Output {
57    pick! {
58      if #[cfg(target_feature="avx512f")] {
59        Self { avx512: add_i32_m512i(self.avx512, rhs.avx512) }
60      } else {
61        Self {
62          a : self.a.add(rhs.a),
63          b : self.b.add(rhs.b),
64        }
65      }
66    }
67  }
68
69  #[inline]
70  fn sub(self, rhs: Self) -> Self::Output {
71    pick! {
72      if #[cfg(target_feature="avx512f")] {
73        Self { avx512: sub_i32_m512i(self.avx512, rhs.avx512) }
74      } else {
75        Self {
76          a : self.a.sub(rhs.a),
77          b : self.b.sub(rhs.b),
78        }
79      }
80    }
81  }
82
83  #[inline]
84  fn mul(self, rhs: Self) -> Self::Output {
85    pick! {
86      if #[cfg(target_feature="avx512f")] {
87        Self { avx512: mul_i32_keep_low_m512i(self.avx512, rhs.avx512) }
88      } else {
89        Self {
90          a : self.a.mul(rhs.a),
91          b : self.b.mul(rhs.b),
92        }
93      }
94    }
95  }
96
97  #[inline]
98  fn bitand(self, rhs: Self) -> Self::Output {
99    pick! {
100      if #[cfg(target_feature="avx512f")] {
101        Self { avx512: bitand_m512i(self.avx512, rhs.avx512) }
102      } else {
103        Self {
104          a : self.a.bitand(rhs.a),
105          b : self.b.bitand(rhs.b),
106        }
107      }
108    }
109  }
110
111  #[inline]
112  fn bitor(self, rhs: Self) -> Self::Output {
113    pick! {
114    if #[cfg(target_feature="avx512f")] {
115        Self { avx512: bitor_m512i(self.avx512, rhs.avx512) }
116      } else {
117        Self {
118          a : self.a.bitor(rhs.a),
119          b : self.b.bitor(rhs.b),
120        }
121      }
122    }
123  }
124
125  #[inline]
126  fn bitxor(self, rhs: Self) -> Self::Output {
127    pick! {
128      if #[cfg(target_feature="avx512f")] {
129        Self { avx512: bitxor_m512i(self.avx512, rhs.avx512) }
130      } else {
131        Self {
132          a : self.a.bitxor(rhs.a),
133          b : self.b.bitxor(rhs.b),
134        }
135      }
136    }
137  }
138
139  #[inline]
140  fn simd_eq(self, rhs: Self) -> Self::Output {
141    pick! {
142      if #[cfg(target_feature="avx512f")] {
143        Self { avx512: cmp_op_mask_u32_m512i::<{cmp_int_op!(Eq)}>(self.avx512, rhs.avx512) }
144      } else {
145        Self {
146          a : self.a.simd_eq(rhs.a),
147          b : self.b.simd_eq(rhs.b),
148        }
149      }
150    }
151  }
152
153  #[inline]
154  fn simd_ne(self, rhs: Self) -> Self::Output {
155    pick! {
156      if #[cfg(target_feature="avx512f")] {
157        Self { avx512: cmp_op_mask_u32_m512i::<{cmp_int_op!(Ne)}>(self.avx512, rhs.avx512) }
158      } else {
159        Self {
160          a : self.a.simd_ne(rhs.a),
161          b : self.b.simd_ne(rhs.b),
162        }
163      }
164    }
165  }
166
167  #[inline]
168  fn simd_lt(self, rhs: Self) -> Self::Output {
169    pick! {
170      if #[cfg(target_feature="avx512f")] {
171        Self { avx512: cmp_op_mask_u32_m512i::<{cmp_int_op!(Lt)}>(self.avx512, rhs.avx512) }
172      } else {
173        Self {
174          a : rhs.a.simd_gt(self.a),
175          b : rhs.b.simd_gt(self.b),
176        }
177      }
178    }
179  }
180
181  #[inline]
182  fn simd_gt(self, rhs: Self) -> Self::Output {
183    pick! {
184      if #[cfg(target_feature="avx512f")] {
185        Self { avx512: cmp_op_mask_u32_m512i::<{cmp_int_op!(Nle)}>(self.avx512, rhs.avx512) }
186      } else {
187        Self {
188          a : self.a.simd_gt(rhs.a),
189          b : self.b.simd_gt(rhs.b),
190        }
191      }
192    }
193  }
194
195  #[inline]
196  fn simd_le(self, rhs: Self) -> Self::Output {
197    pick! {
198      if #[cfg(target_feature="avx512f")] {
199        Self { avx512: cmp_op_mask_u32_m512i::<{cmp_int_op!(Le)}>(self.avx512, rhs.avx512) }
200      } else {
201        Self {
202          a : self.a.simd_le(rhs.a),
203          b : self.b.simd_le(rhs.b),
204        }
205      }
206    }
207  }
208
209  #[inline]
210  fn simd_ge(self, rhs: Self) -> Self::Output {
211    pick! {
212      if #[cfg(target_feature="avx512f")] {
213        Self { avx512: cmp_op_mask_u32_m512i::<{cmp_int_op!(Nlt)}>(self.avx512, rhs.avx512) }
214      } else {
215        Self {
216          a : self.a.simd_ge(rhs.a),
217          b : self.b.simd_ge(rhs.b),
218        }
219      }
220    }
221  }
222
223  #[inline]
224  pub fn reduce_add(self) -> u32 {
225    let array: [u32x8; 2] = cast(self);
226    (array[0] + array[1]).reduce_add()
227  }
228
229  #[inline]
230  pub fn reduce_mul(self) -> u32 {
231    let array: [u32x8; 2] = cast(self);
232    (array[0] * array[1]).reduce_mul()
233  }
234
235  #[inline]
236  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
237    pick! {
238      if #[cfg(target_feature="avx512f")] {
239        Self {
240          avx512: bitor_m512i(
241            bitand_m512i(if_one.avx512, self.avx512),
242            bitandnot_m512i(self.avx512, if_zero.avx512),
243          ),
244        }
245      } else {
246        Self {
247          a: self.a.bitselect(if_one.a, if_zero.a),
248          b: self.b.bitselect(if_one.b, if_zero.b),
249        }
250      }
251    }
252  }
253
254  #[inline]
255  fn select(self, if_true: Self, if_false: Self) -> Self {
256    pick! {
257      if #[cfg(target_feature="avx512f")] {
258        Self { avx512: blend_varying_i8_m512i(if_false.avx512,if_true.avx512,movepi8_mask_m512i(self.avx512)) }
259      } else {
260        Self {
261          a : self.a.select(if_true.a, if_false.a),
262          b : self.b.select(if_true.b, if_false.b),
263        }
264      }
265    }
266  }
267
268  #[inline]
269  pub fn to_bitmask(self) -> u32 {
270    pick! {
271      if #[cfg(target_feature="avx512dq")] {
272        movepi32_mask_m512i(self.avx512) as u32
273      } else {
274        f32x16::to_bitmask(cast(self))
275      }
276    }
277  }
278
279  #[inline]
280  pub fn any(self) -> bool {
281    pick! {
282      if #[cfg(target_feature="avx512bw")] {
283        movepi32_mask_m512i(self.avx512) != 0
284      } else {
285        let [a, b]: [u32x8; 2] = cast(self);
286        (a | b).any()
287      }
288    }
289  }
290
291  #[inline]
292  pub fn all(self) -> bool {
293    pick! {
294      if #[cfg(target_feature="avx512bw")] {
295        movepi32_mask_m512i(self.avx512) == 0xFFFF
296      } else {
297        let [a, b]: [u32x8; 2] = cast(self);
298        (a & b).all()
299      }
300    }
301  }
302
303  #[inline]
304  pub fn shuffle(self, indices: u32x16) -> Self {
305    pick! {
306      if #[cfg(all(target_feature = "avx512f"))] {
307        Self { avx512: permute_i32_m512i(indices.avx512, self.avx512) }
308      } else {
309        let self_halfs = cast::<u32x16, [u32x8; 2]>(self);
310        let [indices_a, indices_b] = cast::<u32x16, [u32x8; 2]>(indices);
311
312        cast([self_halfs.shuffle(indices_a), self_halfs.shuffle(indices_b)])
313      }
314    }
315  }
316
317  #[inline]
318  pub fn shuffle_zeroing(self, indices: u32x16) -> Self {
319    pick! {
320      if #[cfg(all(target_feature = "avx512f"))] {
321        self.shuffle(indices) & indices.simd_lt(16)
322      } else {
323        let self_halfs = cast::<u32x16, [u32x8; 2]>(self);
324        let [indices_a, indices_b] = cast::<u32x16, [u32x8; 2]>(indices);
325
326        cast([self_halfs.shuffle_zeroing(indices_a), self_halfs.shuffle_zeroing(indices_b)])
327      }
328    }
329  }
330
331  #[inline]
332  pub fn shuffle_wrapping(self, indices: u32x16) -> Self {
333    pick! {
334      if #[cfg(all(target_feature = "avx512f"))] {
335        // `avx512` shuffle intrinsics are wrapping
336        self.shuffle(indices)
337      } else {
338        let self_halfs = cast::<u32x16, [u32x8; 2]>(self);
339        let [indices_a, indices_b] = cast::<u32x16, [u32x8; 2]>(indices);
340
341        cast([self_halfs.shuffle_wrapping(indices_a), self_halfs.shuffle_wrapping(indices_b)])
342      }
343    }
344  }
345
346  #[inline]
347  fn shuffle(self: [u32x16; 2], indices: u32x16) -> u32x16 {
348    pick! {
349      if #[cfg(all(target_feature = "avx512f"))] {
350        u32x16 { avx512: shuffle_abv_i32_all_m512i(self[0].avx512, indices.avx512, self[1].avx512) }
351      } else {
352        self[0].shuffle_zeroing(indices) | self[1].shuffle_zeroing(indices - 16)
353      }
354    }
355  }
356
357  #[inline]
358  fn shuffle_zeroing(self: [u32x16; 2], indices: u32x16) -> u32x16 {
359    pick! {
360      if #[cfg(all(target_feature = "avx512f"))] {
361        self.shuffle(indices) & indices.simd_lt(32)
362      } else {
363        self.shuffle(indices)
364      }
365    }
366  }
367
368  #[inline]
369  fn shuffle_wrapping(self: [u32x16; 2], indices: u32x16) -> u32x16 {
370    pick! {
371      if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
372        // `avx512` shuffle intrinsics are wrapping
373        self.shuffle(indices)
374      } else {
375        self.shuffle(indices & 31)
376      }
377    }
378  }
379
380  #[inline]
381  fn shuffle(self: [u32x16; 3], indices: u32x16) -> u32x16 {
382    [self[0], self[1]].shuffle_zeroing(indices) | self[2].shuffle_zeroing(indices - 32)
383  }
384
385  #[inline]
386  fn shuffle_zeroing(self: [u32x16; 3], indices: u32x16) -> u32x16 {
387    self.shuffle(indices)
388  }
389
390  #[inline]
391  fn shuffle_wrapping(self: [u32x16; 3], indices: u32x16) -> u32x16 {
392    self.shuffle(indices % 48)
393  }
394
395  #[inline]
396  fn shuffle(self: [u32x16; 4], indices: u32x16) -> u32x16 {
397    [self[0], self[1]].shuffle_zeroing(indices) | [self[2], self[3]].shuffle_zeroing(indices - 32)
398  }
399
400  #[inline]
401  fn shuffle_zeroing(self: [u32x16; 4], indices: u32x16) -> u32x16 {
402    self.shuffle(indices)
403  }
404
405  #[inline]
406  fn shuffle_wrapping(self: [u32x16; 4], indices: u32x16) -> u32x16 {
407    self.shuffle(indices & 63)
408  }
409
410  ///
411  /// Currently this function is never accelerated.
412  #[inline]
413  pub fn transpose(data: [Self; 16]) -> [Self; 16] {
414    // Can this be optimized?
415
416    #[inline(always)]
417    fn transpose_column(data: &[u32x16; 16], index: usize) -> u32x16 {
418      u32x16::new([
419        data[0].as_array()[index],
420        data[1].as_array()[index],
421        data[2].as_array()[index],
422        data[3].as_array()[index],
423        data[4].as_array()[index],
424        data[5].as_array()[index],
425        data[6].as_array()[index],
426        data[7].as_array()[index],
427        data[8].as_array()[index],
428        data[9].as_array()[index],
429        data[10].as_array()[index],
430        data[11].as_array()[index],
431        data[12].as_array()[index],
432        data[13].as_array()[index],
433        data[14].as_array()[index],
434        data[15].as_array()[index],
435      ])
436    }
437
438    [
439      transpose_column(&data, 0),
440      transpose_column(&data, 1),
441      transpose_column(&data, 2),
442      transpose_column(&data, 3),
443      transpose_column(&data, 4),
444      transpose_column(&data, 5),
445      transpose_column(&data, 6),
446      transpose_column(&data, 7),
447      transpose_column(&data, 8),
448      transpose_column(&data, 9),
449      transpose_column(&data, 10),
450      transpose_column(&data, 11),
451      transpose_column(&data, 12),
452      transpose_column(&data, 13),
453      transpose_column(&data, 14),
454      transpose_column(&data, 15),
455    ]
456  }
457
458  #[inline]
459  fn shl(self, rhs: u32x16) -> Self::Output {
460    pick! {
461      if #[cfg(target_feature="avx512f")] {
462        let shift_by = bitand_m512i(rhs.avx512, set_splat_i32_m512i(31));
463        Self { avx512: shl_each_u32_m512i(self.avx512, shift_by) }
464      } else {
465        Self {
466          a : self.a.shl(rhs.a),
467          b : self.b.shl(rhs.b),
468        }
469      }
470    }
471  }
472
473  #[inline]
474  fn shl(self, rhs: u32) -> Self::Output {
475    pick! {
476      if #[cfg(target_feature="avx512f")] {
477        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
478        #[expect(clippy::suspicious_arithmetic_impl)]
479        let shift = rhs & 31;
480        Self { avx512: shl_all_u32_m512i(self.avx512, shift) }
481      } else {
482        Self {
483          a : self.a.shl(rhs),
484          b : self.b.shl(rhs),
485        }
486      }
487    }
488  }
489
490  #[inline]
491  fn shr(self, rhs: u32x16) -> Self::Output {
492    pick! {
493      if #[cfg(target_feature="avx512f")] {
494        let shift_by = bitand_m512i(rhs.avx512, set_splat_i32_m512i(31));
495        Self { avx512: shr_each_u32_m512i(self.avx512, shift_by ) }
496      } else {
497        Self {
498          a : self.a.shr(rhs.a),
499          b : self.b.shr(rhs.b),
500        }
501      }
502    }
503  }
504
505  #[inline]
506  fn shr(self, rhs: u32) -> Self::Output {
507    pick! {
508      if #[cfg(target_feature="avx512f")] {
509        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
510        #[expect(clippy::suspicious_arithmetic_impl)]
511        let shift = rhs & 31;
512        Self { avx512: shr_all_u32_m512i(self.avx512, shift) }
513      } else {
514        Self {
515          a : self.a.shr(rhs),
516          b : self.b.shr(rhs),
517        }
518      }
519    }
520  }
521
522  #[inline]
523  pub fn max(self, rhs: Self) -> Self {
524    pick! {
525      if #[cfg(target_feature="avx512f")] {
526        Self { avx512: max_u32_m512i(self.avx512, rhs.avx512) }
527      } else {
528        Self {
529          a: self.a.max(rhs.a),
530          b: self.b.max(rhs.b),
531        }
532      }
533    }
534  }
535
536  #[inline]
537  pub fn min(self, rhs: Self) -> Self {
538    pick! {
539      if #[cfg(target_feature="avx512f")] {
540        Self { avx512: min_u32_m512i(self.avx512, rhs.avx512) }
541      } else {
542        Self {
543          a: self.a.min(rhs.a),
544          b: self.b.min(rhs.b),
545        }
546      }
547    }
548  }
549
550  #[inline]
551  pub fn reduce_max(self) -> u32 {
552    let array: [u32x8; 2] = cast(self);
553    array[0].max(array[1]).reduce_max()
554  }
555
556  #[inline]
557  pub fn reduce_min(self) -> u32 {
558    let array: [u32x8; 2] = cast(self);
559    array[0].min(array[1]).reduce_min()
560  }
561
562  #[inline]
563  pub fn unbounded_shl(self, rhs: Self) -> Self {
564    pick! {
565      if #[cfg(target_feature="avx512f")] {
566        Self { avx512: shl_each_u32_m512i(self.avx512, rhs.avx512) }
567      } else {
568        Self {
569          a: self.a.unbounded_shl(rhs.a),
570          b: self.b.unbounded_shl(rhs.b),
571        }
572      }
573    }
574  }
575
576  #[inline]
577  pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
578    pick! {
579      if #[cfg(target_feature="avx512f")] {
580        Self { avx512: shl_all_u32_m512i(self.avx512, rhs) }
581      } else {
582        Self {
583          a: self.a.unbounded_shl_scalar(rhs),
584          b: self.b.unbounded_shl_scalar(rhs),
585        }
586      }
587    }
588  }
589
590  #[inline]
591  pub fn unbounded_shr(self, rhs: Self) -> Self {
592    pick! {
593      if #[cfg(target_feature="avx512f")] {
594        Self { avx512: shr_each_u32_m512i(self.avx512, rhs.avx512) }
595      } else {
596        Self {
597          a: self.a.unbounded_shr(rhs.a),
598          b: self.b.unbounded_shr(rhs.b),
599        }
600      }
601    }
602  }
603
604  #[inline]
605  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
606    pick! {
607      if #[cfg(target_feature="avx512f")] {
608        Self { avx512: shr_all_u32_m512i(self.avx512, rhs) }
609      } else {
610        Self {
611          a: self.a.unbounded_shr_scalar(rhs),
612          b: self.b.unbounded_shr_scalar(rhs),
613        }
614      }
615    }
616  }
617
618  #[inline]
619  pub fn saturating_add(self, rhs: Self) -> Self {
620    pick! {
621      if #[cfg(target_feature="avx512f")] {
622        let result = self + rhs;
623        let overflow = result.simd_lt(self);
624        // Return `MAX` (all bits set) if overflow occurs.
625        result | overflow
626      } else {
627        Self {
628          a: self.a.saturating_add(rhs.a),
629          b: self.b.saturating_add(rhs.b),
630        }
631      }
632    }
633  }
634
635  #[inline]
636  pub fn saturating_sub(self, rhs: Self) -> Self {
637    pick! {
638      if #[cfg(target_feature="avx512f")] {
639        let result = self - rhs;
640        let no_overflow = result.simd_le(self);
641        // Return `0` (no bits set) if overflow occurs.
642        result & no_overflow
643      } else {
644        Self {
645          a: self.a.saturating_sub(rhs.a),
646          b: self.b.saturating_sub(rhs.b),
647        }
648      }
649    }
650  }
651
652  #[inline]
653  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
654    let (low, high) = self.mul_keep_low_high(rhs);
655    let overflow = high.simd_ne(Self::ZERO);
656    (low, overflow)
657  }
658
659  optional_fn_widening_mul {
660    // Cannot have `widening_mul` because there is no `u64x16` type.
661  }
662
663  #[inline]
664  pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
665    pick! {
666      if #[cfg(all(target_feature="avx512f", target_feature="avx512dq"))] {
667        #[cfg(target_arch = "x86")]
668        use core::arch::x86::{_mm512_unpackhi_epi64, _mm512_unpacklo_epi64};
669        #[cfg(target_arch = "x86_64")]
670        use core::arch::x86_64::{_mm512_unpackhi_epi64, _mm512_unpacklo_epi64};
671
672        let even_wide_mul = mul_u32_wide_m512i(self.avx512, rhs.avx512);
673        let odd_wide_mul = mul_u32_wide_m512i(
674          shuffle_i32_m512i::<0b_00_11_00_01>(self.avx512),
675          shuffle_i32_m512i::<0b_00_11_00_01>(rhs.avx512),
676        );
677
678        let ll_hh_1 = unpack_low_i32_m512i(even_wide_mul, odd_wide_mul);
679        let ll_hh_2 = unpack_high_i32_m512i(even_wide_mul, odd_wide_mul);
680        // TODO(safe_arch): Add `_mm512_unpacklo_epi64` and `_mm512_unpackhi_epi64`.
681        (
682          Self {
683            avx512: m512i(unsafe { _mm512_unpacklo_epi64(ll_hh_1.0, ll_hh_2.0) }),
684          },
685          Self {
686            avx512: m512i(unsafe { _mm512_unpackhi_epi64(ll_hh_1.0, ll_hh_2.0) }),
687          },
688        )
689      } else {
690        let [self_a, self_b] = cast::<u32x16, [u32x8; 2]>(self);
691        let [rhs_a, rhs_b] = cast::<u32x16, [u32x8; 2]>(rhs);
692
693        let result_a = self_a.mul_keep_low_high(rhs_a);
694        let result_b = self_b.mul_keep_low_high(rhs_b);
695        (
696          cast([result_a.0, result_b.0]),
697          cast([result_a.1, result_b.1]),
698        )
699      }
700    }
701  }
702
703  #[inline]
704  pub fn mul_keep_high(self, rhs: Self) -> Self {
705    pick! {
706      if #[cfg(target_feature="avx512f")] {
707        let alo = extract_m256i32_from_m512i::<0>(self.avx512);
708        let ahi = extract_m256i32_from_m512i::<1>(self.avx512);
709        let blo = extract_m256i32_from_m512i::<0>(rhs.avx512);
710        let bhi = extract_m256i32_from_m512i::<1>(rhs.avx512);
711
712        let lo_res: m256i = {
713          let a8 = u32x8 { avx2: alo };
714          let b8 = u32x8 { avx2: blo };
715          a8.mul_keep_high(b8).avx2
716        };
717        let hi_res: m256i = {
718          let a8 = u32x8 { avx2: ahi };
719          let b8 = u32x8 { avx2: bhi };
720          a8.mul_keep_high(b8).avx2
721        };
722
723        let zero = zeroed_m512i();
724        let with_lo = insert_m256i32_to_m512i::<0>(zero, lo_res);
725        let combined = insert_m256i32_to_m512i::<1>(with_lo, hi_res);
726
727        Self { avx512: combined }
728      } else {
729        Self {
730          a: self.a.mul_keep_high(rhs.a),
731          b: self.b.mul_keep_high(rhs.b),
732        }
733      }
734    }
735  }
736
737  optional_fn_deserialize {}
738}
739
740impl From<u16x16> for u32x16 {
741  /// Widens and zero-extends each u16 lane to u32
742  #[inline]
743  fn from(v: u16x16) -> Self {
744    pick! {
745      if #[cfg(target_feature = "avx512f")] {
746        Self {
747          avx512: convert_to_u32_m512i_from_u16_m256i(v.avx2)
748        }
749      } else if #[cfg(target_feature = "avx2")] {
750        let lo: m128i = extract_m128i_from_m256i::<0>(v.avx2);
751        let hi: m128i = extract_m128i_from_m256i::<1>(v.avx2);
752        Self {
753          a: u32x8 { avx2: convert_to_i32_m256i_from_u16_m128i(lo) },
754          b: u32x8 { avx2: convert_to_i32_m256i_from_u16_m128i(hi) },
755        }
756      } else if #[cfg(target_feature = "sse2")] {
757        Self {
758          a: u32x8 {
759            a: u32x4 {
760              sse: shr_imm_u32_m128i::<16>(unpack_low_i16_m128i(v.a.sse, v.a.sse))
761            },
762            b: u32x4 {
763              sse: shr_imm_u32_m128i::<16>(unpack_high_i16_m128i(v.a.sse, v.a.sse))
764            },
765          },
766          b: u32x8 {
767            a: u32x4 {
768              sse: shr_imm_u32_m128i::<16>(unpack_low_i16_m128i(v.b.sse, v.b.sse))
769            },
770            b: u32x4 {
771              sse: shr_imm_u32_m128i::<16>(unpack_high_i16_m128i(v.b.sse, v.b.sse))
772            },
773          },
774        }
775      } else {
776        // Portable fallback
777        let arr = v.as_array();
778        Self::new([
779          arr[0] as u32,  arr[1] as u32,  arr[2] as u32,  arr[3] as u32,
780          arr[4] as u32,  arr[5] as u32,  arr[6] as u32,  arr[7] as u32,
781          arr[8] as u32,  arr[9] as u32,  arr[10] as u32, arr[11] as u32,
782          arr[12] as u32, arr[13] as u32, arr[14] as u32, arr[15] as u32,
783        ])
784      }
785    }
786  }
787}
788
789/// The following functionality exists only for [`u32x16`], or only for
790/// particular types inconsistently.
791impl u32x16 {
792  /// Returns `[self[0], b[0], self[1], b[1], ...]`, interleaving the low half
793  /// of each vector.
794  #[inline]
795  #[must_use]
796  pub fn unpack_lo(self, b: Self) -> Self {
797    pick! {
798      if #[cfg(target_feature="avx512f")] {
799        // `_mm512_unpacklo_epi32` cannot be used because it acts within each
800        // 128-bit lane, which is a different operation.
801        let [aa, _]: [u32x8; 2] = cast(self);
802        let [ba, _]: [u32x8; 2] = cast(b);
803        cast([aa.unpack_lo(ba), aa.unpack_hi(ba)])
804      } else {
805        Self { a: self.a.unpack_lo(b.a), b: self.a.unpack_hi(b.a) }
806      }
807    }
808  }
809
810  /// Returns `[self[8], b[8], self[9], b[9], ...]`, interleaving the high half
811  /// of each vector.
812  #[inline]
813  #[must_use]
814  pub fn unpack_hi(self, b: Self) -> Self {
815    pick! {
816      if #[cfg(target_feature="avx512f")] {
817        // `_mm512_unpackhi_epi32` cannot be used because it acts within each
818        // 128-bit lane, which is a different operation.
819        let [_, ab]: [u32x8; 2] = cast(self);
820        let [_, bb]: [u32x8; 2] = cast(b);
821        cast([ab.unpack_lo(bb), ab.unpack_hi(bb)])
822      } else {
823        Self { a: self.b.unpack_lo(b.b), b: self.b.unpack_hi(b.b) }
824      }
825    }
826  }
827
828  /// `self + ((a * b) mod 2^W)`, reading only the low `W` bits of each lane of
829  /// `a` and `b`. `W` must be in `1..=32`.
830  ///
831  /// There is no IFMA equivalent at this width, and below 17 bits no widening
832  /// multiply is needed either: the whole product fits a lane, so the ordinary
833  /// lane multiply already yields both halves.
834  #[inline]
835  #[must_use]
836  pub fn add_mul_lo<const W: u32>(self, a: Self, b: Self) -> Self {
837    if W <= 16 {
838      let mask = Self::splat(add_mul_operand_mask_u32::<W>());
839      return self + (((a & mask) * (b & mask)) & mask);
840    }
841
842    let acc = self.to_array();
843    let a = a.to_array();
844    let b = b.to_array();
845    Self::new(core::array::from_fn(|i| {
846      add_mul_lo_lane_u32::<W>(acc[i], a[i], b[i])
847    }))
848  }
849
850  /// `self + ((a * b) >> W)`, reading only the low `W` bits of each lane of `a`
851  /// and `b`. `W` must be in `1..=32`.
852  #[inline]
853  #[must_use]
854  pub fn add_mul_hi<const W: u32>(self, a: Self, b: Self) -> Self {
855    // See `add_mul_lo`: the whole product is in the lane, so the high half is a
856    // shift.
857    if W <= 16 {
858      let mask = Self::splat(add_mul_operand_mask_u32::<W>());
859      return self + (((a & mask) * (b & mask)) >> W);
860    }
861
862    let acc = self.to_array();
863    let a = a.to_array();
864    let b = b.to_array();
865    Self::new(core::array::from_fn(|i| {
866      add_mul_hi_lane_u32::<W>(acc[i], a[i], b[i])
867    }))
868  }
869}