Skip to main content

wide/
u64x8_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx512f")] {
5    /// A SIMD vector with eight elements of type [`u64`].
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 u64x8 { pub(crate) avx512: m512i }
14  } else {
15    /// A SIMD vector with eight elements of type [`u64`].
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 u64x8 { pub(crate) a : u64x4, pub(crate) b : u64x4 }
24  }
25}
26
27impl_simd_uint! {
28  unsafe {
29    T = u64,
30    N = 8,
31    Simd = u64x8,
32    IntSimd = i64x8,
33    T_BITS = 64,
34    T_BITS_MUL_2 = 128,
35    [0, 1, 2, 3, 4, 5, 6, 7],
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_i64_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_i64_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_i64_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        let arr1: [u64; 8] = cast(self);
88        let arr2: [u64; 8] = cast(rhs);
89        cast([
90          arr1[0].wrapping_mul(arr2[0]),
91          arr1[1].wrapping_mul(arr2[1]),
92          arr1[2].wrapping_mul(arr2[2]),
93          arr1[3].wrapping_mul(arr2[3]),
94          arr1[4].wrapping_mul(arr2[4]),
95          arr1[5].wrapping_mul(arr2[5]),
96          arr1[6].wrapping_mul(arr2[6]),
97          arr1[7].wrapping_mul(arr2[7]),
98        ])
99      } else {
100        Self { a: self.a.mul(rhs.a), b: self.b.mul(rhs.b) }
101      }
102    }
103  }
104
105  #[inline]
106  fn bitand(self, rhs: Self) -> Self::Output {
107    pick! {
108      if #[cfg(target_feature="avx512f")] {
109        Self { avx512: bitand_m512i(self.avx512, rhs.avx512) }
110      } else {
111        Self {
112          a : self.a.bitand(rhs.a),
113          b : self.b.bitand(rhs.b),
114        }
115      }
116    }
117  }
118
119  #[inline]
120  fn bitor(self, rhs: Self) -> Self::Output {
121    pick! {
122    if #[cfg(target_feature="avx512f")] {
123        Self { avx512: bitor_m512i(self.avx512, rhs.avx512) }
124      } else {
125        Self {
126          a : self.a.bitor(rhs.a),
127          b : self.b.bitor(rhs.b),
128        }
129      }
130    }
131  }
132
133  #[inline]
134  fn bitxor(self, rhs: Self) -> Self::Output {
135    pick! {
136      if #[cfg(target_feature="avx512f")] {
137        Self { avx512: bitxor_m512i(self.avx512, rhs.avx512) }
138      } else {
139        Self {
140          a : self.a.bitxor(rhs.a),
141          b : self.b.bitxor(rhs.b),
142        }
143      }
144    }
145  }
146
147  #[inline]
148  fn simd_eq(self, rhs: Self) -> Self::Output {
149    pick! {
150      if #[cfg(target_feature="avx512f")] {
151        Self { avx512: cmp_op_mask_u64_m512i::<{cmp_int_op!(Eq)}>(self.avx512, rhs.avx512) }
152      } else {
153        Self {
154          a : self.a.simd_eq(rhs.a),
155          b : self.b.simd_eq(rhs.b),
156        }
157      }
158    }
159  }
160
161  #[inline]
162  fn simd_ne(self, rhs: Self) -> Self::Output {
163    pick! {
164      if #[cfg(target_feature="avx512f")] {
165        Self { avx512: cmp_op_mask_u64_m512i::<{cmp_int_op!(Ne)}>(self.avx512, rhs.avx512) }
166      } else {
167        Self {
168          a : self.a.simd_ne(rhs.a),
169          b : self.b.simd_ne(rhs.b),
170        }
171      }
172    }
173  }
174
175  #[inline]
176  fn simd_lt(self, rhs: Self) -> Self::Output {
177    pick! {
178      if #[cfg(target_feature="avx512f")] {
179        Self { avx512: cmp_op_mask_u64_m512i::<{cmp_int_op!(Lt)}>(self.avx512, rhs.avx512) }
180      } else {
181        Self {
182          a : self.a.simd_lt(rhs.a),
183          b : self.b.simd_lt(rhs.b),
184        }
185      }
186    }
187  }
188
189  #[inline]
190  fn simd_gt(self, rhs: Self) -> Self::Output {
191    pick! {
192      if #[cfg(target_feature="avx512f")] {
193        Self { avx512: cmp_op_mask_u64_m512i::<{cmp_int_op!(Nle)}>(self.avx512, rhs.avx512) }
194      } else {
195        Self {
196          a : self.a.simd_gt(rhs.a),
197          b : self.b.simd_gt(rhs.b),
198        }
199      }
200    }
201  }
202
203  #[inline]
204  fn simd_le(self, rhs: Self) -> Self::Output {
205    pick! {
206      if #[cfg(target_feature="avx512f")] {
207        Self { avx512: cmp_op_mask_u64_m512i::<{cmp_int_op!(Le)}>(self.avx512, rhs.avx512) }
208      } else {
209        Self {
210          a : self.a.simd_le(rhs.a),
211          b : self.b.simd_le(rhs.b),
212        }
213      }
214    }
215  }
216
217  #[inline]
218  fn simd_ge(self, rhs: Self) -> Self::Output {
219    pick! {
220      if #[cfg(target_feature="avx512f")] {
221        Self { avx512: cmp_op_mask_u64_m512i::<{cmp_int_op!(Nlt)}>(self.avx512, rhs.avx512) }
222      } else {
223        Self {
224          a : self.a.simd_ge(rhs.a),
225          b : self.b.simd_ge(rhs.b),
226        }
227      }
228    }
229  }
230
231  #[inline]
232  pub fn reduce_add(self) -> u64 {
233    let array: [u64x4; 2] = cast(self);
234    (array[0] + array[1]).reduce_add()
235  }
236
237
238  #[inline]
239  pub fn reduce_mul(self) -> u64 {
240    let array: [u64x4; 2] = cast(self);
241    (array[0] * array[1]).reduce_mul()
242  }
243
244  #[inline]
245  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
246    pick! {
247      if #[cfg(target_feature="avx512f")] {
248        Self {
249          avx512: bitor_m512i(
250            bitand_m512i(if_one.avx512, self.avx512),
251            bitandnot_m512i(self.avx512, if_zero.avx512),
252          ),
253        }
254      } else {
255        Self {
256          a: self.a.bitselect(if_one.a, if_zero.a),
257          b: self.b.bitselect(if_one.b, if_zero.b),
258        }
259      }
260    }
261  }
262
263  #[inline]
264  fn select(self, if_true: Self, if_false: Self) -> Self {
265    pick! {
266      if #[cfg(target_feature="avx512f")] {
267        Self { avx512: blend_varying_i8_m512i(if_false.avx512,if_true.avx512,movepi8_mask_m512i(self.avx512)) }
268      } else {
269        Self {
270          a : self.a.select(if_true.a, if_false.a),
271          b : self.b.select(if_true.b, if_false.b),
272        }
273      }
274    }
275  }
276
277  #[inline]
278  pub fn to_bitmask(self) -> u32 {
279    pick! {
280      if #[cfg(target_feature="avx512dq")] {
281        // use f64 move_mask since it is the same size as i64
282        movepi64_mask_m512d(cast(self.avx512)) as u32
283      } else {
284        f64x8::to_bitmask(cast(self))
285      }
286    }
287  }
288
289  #[inline]
290  pub fn any(self) -> bool {
291    pick! {
292      if #[cfg(target_feature="avx512f")] {
293        movepi64_mask_m512d(cast(self.avx512)) != 0
294      } else {
295        let [a, b]: [i64x4; 2] = cast(self);
296        (a | b).any()
297      }
298    }
299  }
300
301  #[inline]
302  pub fn all(self) -> bool {
303    pick! {
304      if #[cfg(target_feature="avx512bw")] {
305        movepi64_mask_m512d(cast(self.avx512)) == 0b11111111
306      } else {
307        let [a, b]: [i64x4; 2] = cast(self);
308        (a & b).all()
309      }
310    }
311  }
312
313  #[inline]
314  pub fn shuffle(self, indices: u64x8) -> Self {
315    pick! {
316      if #[cfg(all(target_feature = "avx512f"))] {
317        Self { avx512: permute_i64_m512i(indices.avx512, self.avx512) }
318      } else {
319        let self_halfs = cast::<u64x8, [u64x4; 2]>(self);
320        let [indices_a, indices_b] = cast::<u64x8, [u64x4; 2]>(indices);
321
322        cast([self_halfs.shuffle(indices_a), self_halfs.shuffle(indices_b)])
323      }
324    }
325  }
326
327  #[inline]
328  pub fn shuffle_zeroing(self, indices: u64x8) -> Self {
329    pick! {
330      if #[cfg(all(target_feature = "avx512f"))] {
331        self.shuffle(indices) & indices.simd_lt(8)
332      } else {
333        let self_halfs = cast::<u64x8, [u64x4; 2]>(self);
334        let [indices_a, indices_b] = cast::<u64x8, [u64x4; 2]>(indices);
335
336        cast([self_halfs.shuffle_zeroing(indices_a), self_halfs.shuffle_zeroing(indices_b)])
337      }
338    }
339  }
340
341  #[inline]
342  pub fn shuffle_wrapping(self, indices: u64x8) -> Self {
343    pick! {
344      if #[cfg(all(target_feature = "avx512f"))] {
345        // `avx512` shuffle intrinsics are wrapping
346        self.shuffle(indices)
347      } else {
348        let self_halfs = cast::<u64x8, [u64x4; 2]>(self);
349        let [indices_a, indices_b] = cast::<u64x8, [u64x4; 2]>(indices);
350
351        cast([self_halfs.shuffle_wrapping(indices_a), self_halfs.shuffle_wrapping(indices_b)])
352      }
353    }
354  }
355
356  #[inline]
357  fn shuffle(self: [u64x8; 2], indices: u64x8) -> u64x8 {
358    pick! {
359      if #[cfg(all(target_feature = "avx512f"))] {
360        u64x8 { avx512: shuffle_abv_i64_all_m512i(self[0].avx512, indices.avx512, self[1].avx512) }
361      } else {
362        self[0].shuffle_zeroing(indices) | self[1].shuffle_zeroing(indices - 8)
363      }
364    }
365  }
366
367  #[inline]
368  fn shuffle_zeroing(self: [u64x8; 2], indices: u64x8) -> u64x8 {
369    pick! {
370      if #[cfg(all(target_feature = "avx512f"))] {
371        self.shuffle(indices) & indices.simd_lt(16)
372      } else {
373        self.shuffle(indices)
374      }
375    }
376  }
377
378  #[inline]
379  fn shuffle_wrapping(self: [u64x8; 2], indices: u64x8) -> u64x8 {
380    pick! {
381      if #[cfg(target_feature = "avx512f")] {
382        // `avx512` shuffle intrinsics are wrapping
383        self.shuffle(indices)
384      } else {
385        self.shuffle(indices & 15)
386      }
387    }
388  }
389
390  #[inline]
391  fn shuffle(self: [u64x8; 3], indices: u64x8) -> u64x8 {
392    [self[0], self[1]].shuffle_zeroing(indices) | self[2].shuffle_zeroing(indices - 16)
393  }
394
395  #[inline]
396  fn shuffle_zeroing(self: [u64x8; 3], indices: u64x8) -> u64x8 {
397    self.shuffle(indices)
398  }
399
400  #[inline]
401  fn shuffle_wrapping(self: [u64x8; 3], indices: u64x8) -> u64x8 {
402    self.shuffle(indices % 24)
403  }
404
405  #[inline]
406  fn shuffle(self: [u64x8; 4], indices: u64x8) -> u64x8 {
407    [self[0], self[1]].shuffle_zeroing(indices) | [self[2], self[3]].shuffle_zeroing(indices - 16)
408  }
409
410  #[inline]
411  fn shuffle_zeroing(self: [u64x8; 4], indices: u64x8) -> u64x8 {
412    self.shuffle(indices)
413  }
414
415  #[inline]
416  fn shuffle_wrapping(self: [u64x8; 4], indices: u64x8) -> u64x8 {
417    self.shuffle(indices & 31)
418  }
419
420  ///
421  /// Currently this function is never accelerated.
422  #[inline]
423  pub fn transpose(data: [Self; 8]) -> [Self; 8] {
424    // Can this be optimized?
425
426    #[inline(always)]
427    fn transpose_column(data: &[u64x8; 8], index: usize) -> u64x8 {
428      u64x8::new([
429        data[0].as_array()[index],
430        data[1].as_array()[index],
431        data[2].as_array()[index],
432        data[3].as_array()[index],
433        data[4].as_array()[index],
434        data[5].as_array()[index],
435        data[6].as_array()[index],
436        data[7].as_array()[index],
437      ])
438    }
439
440    [
441      transpose_column(&data, 0),
442      transpose_column(&data, 1),
443      transpose_column(&data, 2),
444      transpose_column(&data, 3),
445      transpose_column(&data, 4),
446      transpose_column(&data, 5),
447      transpose_column(&data, 6),
448      transpose_column(&data, 7),
449    ]
450  }
451
452  #[inline]
453  fn shl(self, rhs: Self) -> Self::Output {
454    pick! {
455      if #[cfg(target_feature="avx512f")] {
456        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
457        let rhs = bitand_m512i(rhs.avx512, set_splat_i64_m512i(63));
458        Self { avx512: shl_each_u64_m512i(self.avx512, rhs) }
459      } else {
460        Self {
461          a : self.a.shl(rhs.a),
462          b : self.b.shl(rhs.b),
463        }
464      }
465    }
466  }
467
468  #[inline]
469  fn shl(self, rhs: u32) -> Self::Output {
470    pick! {
471      if #[cfg(target_feature="avx512f")] {
472        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
473        #[expect(clippy::suspicious_arithmetic_impl)]
474        let shift = rhs as u64 & 63;
475        Self { avx512: shl_all_u64_m512i(self.avx512, shift) }
476      } else {
477        Self {
478          a : self.a.shl(rhs),
479          b : self.b.shl(rhs),
480        }
481      }
482    }
483  }
484
485  #[inline]
486  fn shr(self, rhs: Self) -> Self::Output {
487    pick! {
488      if #[cfg(target_feature="avx512f")] {
489        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
490        let rhs = bitand_m512i(rhs.avx512, set_splat_i64_m512i(63));
491        Self { avx512: shr_each_u64_m512i(self.avx512, rhs) }
492      } else {
493        Self {
494          a : self.a.shr(rhs.a),
495          b : self.b.shr(rhs.b),
496        }
497      }
498    }
499  }
500
501  #[inline]
502  fn shr(self, rhs: u32) -> Self::Output {
503    pick! {
504      if #[cfg(target_feature="avx512f")] {
505        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
506        #[expect(clippy::suspicious_arithmetic_impl)]
507        let shift = rhs as u64 & 63;
508        Self { avx512: shr_all_u64_m512i(self.avx512, shift) }
509      } else {
510        Self {
511          a : self.a.shr(rhs),
512          b : self.b.shr(rhs),
513        }
514      }
515    }
516  }
517
518  #[inline]
519  pub fn max(self, rhs: Self) -> Self {
520    pick! {
521      if #[cfg(target_feature="avx512f")] {
522        Self { avx512: max_u64_m512i(self.avx512, rhs.avx512) }
523      } else {
524        Self {
525          a: self.a.max(rhs.a),
526          b: self.b.max(rhs.b),
527        }
528      }
529    }
530  }
531
532  #[inline]
533  pub fn min(self, rhs: Self) -> Self {
534    pick! {
535      if #[cfg(target_feature="avx512f")] {
536        Self { avx512: min_u64_m512i(self.avx512, rhs.avx512) }
537      } else {
538        Self {
539          a: self.a.min(rhs.a),
540          b: self.b.min(rhs.b),
541        }
542      }
543    }
544  }
545
546  #[inline]
547  pub fn reduce_max(self) -> u64 {
548    let array: [u64x4; 2] = cast(self);
549    array[0].max(array[1]).reduce_max()
550  }
551
552  #[inline]
553  pub fn reduce_min(self) -> u64 {
554    let array: [u64x4; 2] = cast(self);
555    array[0].min(array[1]).reduce_min()
556  }
557
558  #[inline]
559  pub fn unbounded_shl(self, rhs: Self) -> Self {
560    pick! {
561      if #[cfg(target_feature="avx512f")] {
562        Self { avx512: shl_each_u64_m512i(self.avx512, rhs.avx512) }
563      } else {
564        Self {
565          a: self.a.unbounded_shl(rhs.a),
566          b: self.b.unbounded_shl(rhs.b),
567        }
568      }
569    }
570  }
571
572  #[inline]
573  pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
574    pick! {
575      if #[cfg(target_feature="avx512f")] {
576        Self { avx512: shl_all_u64_m512i(self.avx512, rhs as u64) }
577      } else {
578        Self {
579          a: self.a.unbounded_shl_scalar(rhs),
580          b: self.b.unbounded_shl_scalar(rhs),
581        }
582      }
583    }
584  }
585
586  #[inline]
587  pub fn unbounded_shr(self, rhs: Self) -> Self {
588    pick! {
589      if #[cfg(target_feature="avx512f")] {
590        Self { avx512: shr_each_u64_m512i(self.avx512, rhs.avx512) }
591      } else {
592        Self {
593          a: self.a.unbounded_shr(rhs.a),
594          b: self.b.unbounded_shr(rhs.b),
595        }
596      }
597    }
598  }
599
600  #[inline]
601  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
602    pick! {
603      if #[cfg(target_feature="avx512f")] {
604        Self { avx512: shr_all_u64_m512i(self.avx512, rhs as u64) }
605      } else {
606        Self {
607          a: self.a.unbounded_shr_scalar(rhs),
608          b: self.b.unbounded_shr_scalar(rhs),
609        }
610      }
611    }
612  }
613
614  #[inline]
615  pub fn saturating_add(self, rhs: Self) -> Self {
616    pick! {
617      if #[cfg(target_feature="avx512f")] {
618        let result = self + rhs;
619        let overflow = result.simd_lt(self);
620        // Return `MAX` (all bits set) if overflow occurs.
621        result | overflow
622      } else {
623        Self {
624          a: self.a.saturating_add(rhs.a),
625          b: self.b.saturating_add(rhs.b),
626        }
627      }
628    }
629  }
630
631  #[inline]
632  pub fn saturating_sub(self, rhs: Self) -> Self {
633    pick! {
634      if #[cfg(target_feature="avx512f")] {
635        let result = self - rhs;
636        let no_overflow = result.simd_le(self);
637        // Return `0` (no bits set) if overflow occurs.
638        result & no_overflow
639      } else {
640        Self {
641          a: self.a.saturating_sub(rhs.a),
642          b: self.b.saturating_sub(rhs.b),
643        }
644      }
645    }
646  }
647
648  #[inline]
649  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
650    // TODO(perf): This implementation looks quite bad. Is there a better
651    // one? This intentionally avoids `mul_keep_low_high` because getting the
652    // high bits of 64-bit multiplication could be slow.
653
654    let self_array = self.to_array();
655    let rhs_array = rhs.to_array();
656
657    let result = [
658      self_array[0].overflowing_mul(rhs_array[0]),
659      self_array[1].overflowing_mul(rhs_array[1]),
660      self_array[2].overflowing_mul(rhs_array[2]),
661      self_array[3].overflowing_mul(rhs_array[3]),
662      self_array[4].overflowing_mul(rhs_array[4]),
663      self_array[5].overflowing_mul(rhs_array[5]),
664      self_array[6].overflowing_mul(rhs_array[6]),
665      self_array[7].overflowing_mul(rhs_array[7]),
666    ];
667    (
668      Self::new([
669        result[0].0,
670        result[1].0,
671        result[2].0,
672        result[3].0,
673        result[4].0,
674        result[5].0,
675        result[6].0,
676        result[7].0,
677      ]),
678      Self::new([
679        -(result[0].1 as i64) as u64,
680        -(result[1].1 as i64) as u64,
681        -(result[2].1 as i64) as u64,
682        -(result[3].1 as i64) as u64,
683        -(result[4].1 as i64) as u64,
684        -(result[5].1 as i64) as u64,
685        -(result[6].1 as i64) as u64,
686        -(result[7].1 as i64) as u64,
687      ]),
688    )
689  }
690
691  optional_fn_widening_mul {
692    // Cannot have `widening_mul` because there is no `u128x8` type.
693  }
694
695  #[inline]
696  pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
697    // TODO(perf): This implementation looks quite bad. Is there a better
698    // one?
699
700    let self_array = self.to_array();
701    let rhs_array = rhs.to_array();
702
703    let widening_mul = [
704      (self_array[0] as u128).wrapping_mul(rhs_array[0] as u128),
705      (self_array[1] as u128).wrapping_mul(rhs_array[1] as u128),
706      (self_array[2] as u128).wrapping_mul(rhs_array[2] as u128),
707      (self_array[3] as u128).wrapping_mul(rhs_array[3] as u128),
708      (self_array[4] as u128).wrapping_mul(rhs_array[4] as u128),
709      (self_array[5] as u128).wrapping_mul(rhs_array[5] as u128),
710      (self_array[6] as u128).wrapping_mul(rhs_array[6] as u128),
711      (self_array[7] as u128).wrapping_mul(rhs_array[7] as u128),
712    ];
713
714    (
715      Self::new([
716        widening_mul[0] as u64,
717        widening_mul[1] as u64,
718        widening_mul[2] as u64,
719        widening_mul[3] as u64,
720        widening_mul[4] as u64,
721        widening_mul[5] as u64,
722        widening_mul[6] as u64,
723        widening_mul[7] as u64,
724      ]),
725      Self::new([
726        (widening_mul[0] >> 64) as u64,
727        (widening_mul[1] >> 64) as u64,
728        (widening_mul[2] >> 64) as u64,
729        (widening_mul[3] >> 64) as u64,
730        (widening_mul[4] >> 64) as u64,
731        (widening_mul[5] >> 64) as u64,
732        (widening_mul[6] >> 64) as u64,
733        (widening_mul[7] >> 64) as u64,
734      ]),
735    )
736  }
737
738  #[inline]
739  pub fn mul_keep_high(self, rhs: Self) -> Self {
740    pick! {
741      if #[cfg(target_feature="avx512f")] {
742        let arr1: [u64; 8] = cast(self);
743        let arr2: [u64; 8] = cast(rhs);
744        cast([
745          (arr1[0] as u128 * arr2[0] as u128 >> 64) as u64,
746          (arr1[1] as u128 * arr2[1] as u128 >> 64) as u64,
747          (arr1[2] as u128 * arr2[2] as u128 >> 64) as u64,
748          (arr1[3] as u128 * arr2[3] as u128 >> 64) as u64,
749          (arr1[4] as u128 * arr2[4] as u128 >> 64) as u64,
750          (arr1[5] as u128 * arr2[5] as u128 >> 64) as u64,
751          (arr1[6] as u128 * arr2[6] as u128 >> 64) as u64,
752          (arr1[7] as u128 * arr2[7] as u128 >> 64) as u64,
753        ])
754      } else {
755        Self {
756          a: self.a.mul_keep_high(rhs.a),
757          b: self.b.mul_keep_high(rhs.b),
758        }
759      }
760    }
761  }
762
763  optional_fn_deserialize {}
764}
765
766/// The following functionality exists only for [`u64x8`], or only for
767/// particular types inconsistently.
768impl u64x8 {
769  /// Returns `[self[0], b[0], self[1], b[1], ...]`, interleaving the low half
770  /// of each vector.
771  #[inline]
772  #[must_use]
773  pub fn unpack_lo(self, b: Self) -> Self {
774    pick! {
775      if #[cfg(target_feature="avx512f")] {
776        // `_mm512_unpacklo_epi64` cannot be used because it acts within each
777        // 128-bit lane, which is a different operation.
778        let [aa, _]: [u64x4; 2] = cast(self);
779        let [ba, _]: [u64x4; 2] = cast(b);
780        cast([aa.unpack_lo(ba), aa.unpack_hi(ba)])
781      } else {
782        Self { a: self.a.unpack_lo(b.a), b: self.a.unpack_hi(b.a) }
783      }
784    }
785  }
786
787  /// Returns `[self[4], b[4], self[5], b[5], ...]`, interleaving the high half
788  /// of each vector.
789  #[inline]
790  #[must_use]
791  pub fn unpack_hi(self, b: Self) -> Self {
792    pick! {
793      if #[cfg(target_feature="avx512f")] {
794        // `_mm512_unpackhi_epi64` cannot be used because it acts within each
795        // 128-bit lane, which is a different operation.
796        let [_, ab]: [u64x4; 2] = cast(self);
797        let [_, bb]: [u64x4; 2] = cast(b);
798        cast([ab.unpack_lo(bb), ab.unpack_hi(bb)])
799      } else {
800        Self { a: self.b.unpack_lo(b.b), b: self.b.unpack_hi(b.b) }
801      }
802    }
803  }
804
805  /// The exact per-lane product of `a` and `b` masked to `W` bits.
806  ///
807  /// Only exact for `W <= 32`, where the product still fits a lane, and
808  /// callers guard on that. It is instantiated for wider `W` too, since the
809  /// guard is a runtime `if` on a const, so it cannot assert the bound itself.
810  #[inline]
811  #[must_use]
812  fn mul_masked<const W: u32>(a: Self, b: Self) -> Self {
813    pick! {
814      if #[cfg(target_feature="avx512f")] {
815        // `vpmuludq` reads the low 32 bits of a lane anyway, so at `W == 32` the
816        // operand masks are already implied and can be dropped.
817        let (a, b) = if W == 32 {
818          (a, b)
819        } else {
820          let mask = Self::splat(add_mul_operand_mask_u64::<W>());
821          (a & mask, b & mask)
822        };
823
824        Self { avx512: mul_u32_wide_m512i(a.avx512, b.avx512) }
825      } else {
826        // Lane-wise, so each half is independent.
827        Self {
828          a: u64x4::mul_masked::<W>(a.a, b.a),
829          b: u64x4::mul_masked::<W>(a.b, b.b),
830        }
831      }
832    }
833  }
834
835  /// `self + ((a * b) mod 2^W)`, reading only the low `W` bits of each lane of
836  /// `a` and `b`. `W` must be in `1..=64`.
837  #[inline]
838  #[must_use]
839  pub fn add_mul_lo<const W: u32>(self, a: Self, b: Self) -> Self {
840    pick! {
841      if #[cfg(target_feature="avx512ifma")] {
842        // IFMA is fixed at 52 bits; any other width takes the generic path.
843        if W == 52 {
844          return Self {
845            avx512: add_mul_low_u52_m512i(self.avx512, a.avx512, b.avx512),
846          };
847        }
848      }
849    }
850
851    // Below 33 bits the whole product fits a lane, so one widening multiply
852    // yields both halves and the split is a mask rather than an instruction.
853    if W <= 32 {
854      let mask = Self::splat(add_mul_operand_mask_u64::<W>());
855      return self + (Self::mul_masked::<W>(a, b) & mask);
856    }
857
858    let acc = self.to_array();
859    let a = a.to_array();
860    let b = b.to_array();
861    Self::new(core::array::from_fn(|i| {
862      add_mul_lo_lane_u64::<W>(acc[i], a[i], b[i])
863    }))
864  }
865
866  /// `self + ((a * b) >> W)`, reading only the low `W` bits of each lane of `a`
867  /// and `b`. `W` must be in `1..=64`.
868  #[inline]
869  #[must_use]
870  pub fn add_mul_hi<const W: u32>(self, a: Self, b: Self) -> Self {
871    pick! {
872      if #[cfg(target_feature="avx512ifma")] {
873        // IFMA is fixed at 52 bits; any other width takes the generic path.
874        if W == 52 {
875          return Self {
876            avx512: add_mul_high_u52_m512i(self.avx512, a.avx512, b.avx512),
877          };
878        }
879      }
880    }
881
882    // See `add_mul_lo`: the whole product is in the lane, so the high half is a
883    // shift.
884    if W <= 32 {
885      return self + (Self::mul_masked::<W>(a, b) >> W);
886    }
887
888    let acc = self.to_array();
889    let a = a.to_array();
890    let b = b.to_array();
891    Self::new(core::array::from_fn(|i| {
892      add_mul_hi_lane_u64::<W>(acc[i], a[i], b[i])
893    }))
894  }
895}