Skip to main content

wide/
u64x2_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="sse2")] {
5    /// A SIMD vector with two 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(16))]
13    pub struct u64x2 { pub(crate) sse: m128i }
14  } else if #[cfg(target_feature="simd128")] {
15    use core::arch::wasm32::*;
16
17    /// A SIMD vector with two elements of type [`u64`].
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 u64x2 { pub(crate) simd: v128 }
26
27    impl Default for u64x2 {
28      fn default() -> Self {
29        Self::splat(0)
30      }
31    }
32
33    impl PartialEq for u64x2 {
34      fn eq(&self, other: &Self) -> bool {
35        u64x2_all_true(u64x2_eq(self.simd, other.simd))
36      }
37    }
38
39    impl Eq for u64x2 { }
40  } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
41    use core::arch::aarch64::*;
42
43    /// A SIMD vector with two elements of type [`u64`].
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 u64x2 { pub(crate) neon : uint64x2_t }
52
53    impl Default for u64x2 {
54      #[inline]
55      fn default() -> Self {
56        unsafe { Self { neon: vdupq_n_u64(0)} }
57      }
58    }
59
60    impl PartialEq for u64x2 {
61      #[inline]
62      fn eq(&self, other: &Self) -> bool {
63        unsafe {
64          vgetq_lane_u64(self.neon,0) == vgetq_lane_u64(other.neon,0) &&
65          vgetq_lane_u64(self.neon,1) == vgetq_lane_u64(other.neon,1)
66        }
67      }
68    }
69
70    impl Eq for u64x2 { }
71  } else {
72    /// A SIMD vector with two elements of type [`u64`].
73    ///
74    /// See the [crate level documentation] for more information about SIMD
75    /// vectors.
76    ///
77    /// [crate level documentation]: crate
78    #[derive(Default, Clone, Copy, PartialEq, Eq)]
79    #[repr(C, align(16))]
80    pub struct u64x2 { arr: [u64;2] }
81  }
82}
83
84impl_simd_uint! {
85  unsafe {
86    T = u64,
87    N = 2,
88    Simd = u64x2,
89    IntSimd = i64x2,
90    T_BITS = 64,
91    T_BITS_MUL_2 = 128,
92    [0, 1],
93    optional_type_x86_inner { X86Inner = __m128i },
94    optional_type_arm_inner { ArmInner = uint64x2_t },
95    optional_type_wasm_inner { WasmInner = v128 },
96  }
97
98  #[inline]
99  fn not(self) -> Self::Output {
100    self ^ cast::<u128, u64x2>(u128::MAX)
101  }
102
103  #[inline]
104  fn add(self, rhs: Self) -> Self::Output {
105    pick! {
106      if #[cfg(target_feature="sse2")] {
107        Self { sse: add_i64_m128i(self.sse, rhs.sse) }
108      } else if #[cfg(target_feature="simd128")] {
109        Self { simd: u64x2_add(self.simd, rhs.simd) }
110      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
111        unsafe { Self { neon: vaddq_u64(self.neon, rhs.neon) } }
112      } else {
113        Self { arr: [
114          self.arr[0].wrapping_add(rhs.arr[0]),
115          self.arr[1].wrapping_add(rhs.arr[1]),
116        ]}
117      }
118    }
119  }
120
121  #[inline]
122  fn sub(self, rhs: Self) -> Self::Output {
123    pick! {
124      if #[cfg(target_feature="sse2")] {
125        Self { sse: sub_i64_m128i(self.sse, rhs.sse) }
126      } else if #[cfg(target_feature="simd128")] {
127        Self { simd: u64x2_sub(self.simd, rhs.simd) }
128      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
129        unsafe { Self { neon: vsubq_u64(self.neon, rhs.neon) } }
130      } else {
131        Self { arr: [
132          self.arr[0].wrapping_sub(rhs.arr[0]),
133          self.arr[1].wrapping_sub(rhs.arr[1]),
134        ]}
135      }
136    }
137  }
138
139  #[inline]
140  fn mul(self, rhs: Self) -> Self::Output {
141    //we should try to implement this on sse2
142    pick! {
143      if #[cfg(target_feature="simd128")] {
144        Self { simd: u64x2_mul(self.simd, rhs.simd) }
145      } else {
146        let arr1: [u64; 2] = cast(self);
147        let arr2: [u64; 2] = cast(rhs);
148        cast([
149          arr1[0].wrapping_mul(arr2[0]),
150          arr1[1].wrapping_mul(arr2[1]),
151        ])
152      }
153    }
154  }
155
156  #[inline]
157  fn bitand(self, rhs: Self) -> Self::Output {
158    pick! {
159      if #[cfg(target_feature="sse2")] {
160        Self { sse: bitand_m128i(self.sse, rhs.sse) }
161      } else if #[cfg(target_feature="simd128")] {
162        Self { simd: v128_and(self.simd, rhs.simd) }
163      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
164        unsafe {Self { neon: vandq_u64(self.neon, rhs.neon) }}
165      } else {
166        Self { arr: [
167          self.arr[0].bitand(rhs.arr[0]),
168          self.arr[1].bitand(rhs.arr[1]),
169        ]}
170      }
171    }
172  }
173
174  #[inline]
175  fn bitor(self, rhs: Self) -> Self::Output {
176    pick! {
177      if #[cfg(target_feature="sse2")] {
178        Self { sse: bitor_m128i(self.sse, rhs.sse) }
179      } else if #[cfg(target_feature="simd128")] {
180        Self { simd: v128_or(self.simd, rhs.simd) }
181      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
182        unsafe {Self { neon: vorrq_u64(self.neon, rhs.neon) }}
183      } else {
184        Self { arr: [
185          self.arr[0].bitor(rhs.arr[0]),
186          self.arr[1].bitor(rhs.arr[1]),
187        ]}
188      }
189    }
190  }
191
192  #[inline]
193  fn bitxor(self, rhs: Self) -> Self::Output {
194    pick! {
195      if #[cfg(target_feature="sse2")] {
196        Self { sse: bitxor_m128i(self.sse, rhs.sse) }
197      } else if #[cfg(target_feature="simd128")] {
198        Self { simd: v128_xor(self.simd, rhs.simd) }
199      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
200        unsafe {Self { neon: veorq_u64(self.neon, rhs.neon) }}
201      } else {
202        Self { arr: [
203          self.arr[0].bitxor(rhs.arr[0]),
204          self.arr[1].bitxor(rhs.arr[1]),
205        ]}
206      }
207    }
208  }
209
210  #[inline]
211  fn simd_eq(self, rhs: Self) -> Self::Output {
212    pick! {
213      if #[cfg(target_feature="sse4.1")] {
214        Self { sse: cmp_eq_mask_i64_m128i(self.sse, rhs.sse) }
215      } else if #[cfg(target_feature="simd128")] {
216        Self { simd: u64x2_eq(self.simd, rhs.simd) }
217      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
218        unsafe {Self { neon: vceqq_u64(self.neon, rhs.neon) } }
219      } else {
220        let s: [u64;2] = cast(self);
221        let r: [u64;2] = cast(rhs);
222        cast([
223          if s[0] == r[0] { -1_i64 } else { 0 },
224          if s[1] == r[1] { -1_i64 } else { 0 },
225        ])
226      }
227    }
228  }
229
230  #[inline]
231  fn simd_ne(self, rhs: Self) -> Self::Output {
232    pick! {
233      if #[cfg(target_feature="sse4.1")] {
234        !self.simd_eq(rhs)
235      } else if #[cfg(target_feature="simd128")] {
236        Self { simd: u64x2_ne(self.simd, rhs.simd) }
237      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
238        !self.simd_eq(rhs)
239      } else {
240        let s: [u64;2] = cast(self);
241        let r: [u64;2] = cast(rhs);
242        cast([
243          if s[0] != r[0] { -1_i64 } else { 0 },
244          if s[1] != r[1] { -1_i64 } else { 0 },
245        ])
246      }
247    }
248  }
249
250  #[inline]
251  fn simd_lt(self, rhs: Self) -> Self::Output {
252    // lt is just gt the other way around
253    rhs.simd_gt(self)
254  }
255
256  #[inline]
257  fn simd_gt(self, rhs: Self) -> Self::Output {
258    pick! {
259      if #[cfg(target_feature="sse4.2")] {
260        // no unsigned gt so inverting the high bit will get the correct result
261        let highbit = u64x2::splat(1 << 63);
262        Self { sse: cmp_gt_mask_i64_m128i((self ^ highbit).sse, (rhs ^ highbit).sse) }
263      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
264        unsafe {Self { neon: vcgtq_u64(self.neon, rhs.neon) }}
265      } else {
266        // u64x2_gt on WASM is not a thing. https://github.com/WebAssembly/simd/pull/414
267        let s: [u64;2] = cast(self);
268        let r: [u64;2] = cast(rhs);
269        cast([
270          if s[0] > r[0] { u64::MAX } else { 0 },
271          if s[1] > r[1] { u64::MAX } else { 0 },
272        ])
273      }
274    }
275  }
276
277  #[inline]
278  fn simd_le(self, rhs: Self) -> Self::Output {
279    pick! {
280      if #[cfg(target_feature="sse4.1")] {
281        !self.simd_gt(rhs)
282      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
283        !self.simd_gt(rhs)
284      } else {
285        let s: [u64;2] = cast(self);
286        let r: [u64;2] = cast(rhs);
287        cast([
288          if s[0] <= r[0] { -1_i64 } else { 0 },
289          if s[1] <= r[1] { -1_i64 } else { 0 },
290        ])
291      }
292    }
293  }
294
295  #[inline]
296  fn simd_ge(self, rhs: Self) -> Self::Output {
297    pick! {
298      if #[cfg(target_feature="sse4.1")] {
299        !self.simd_lt(rhs)
300      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
301        !self.simd_lt(rhs)
302      } else {
303        let s: [u64;2] = cast(self);
304        let r: [u64;2] = cast(rhs);
305        cast([
306          if s[0] >= r[0] { -1_i64 } else { 0 },
307          if s[1] >= r[1] { -1_i64 } else { 0 },
308        ])
309      }
310    }
311  }
312
313  #[inline]
314  pub fn reduce_add(self) -> u64 {
315    pick! {
316      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
317        let array: [u64; 2] = cast(self);
318        array[0].wrapping_add(array[1])
319      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
320        unsafe { vgetq_lane_u64(self.neon, 0).wrapping_add(vgetq_lane_u64(self.neon, 1)) }
321      } else {
322        self.arr[0].wrapping_add(self.arr[1])
323      }
324    }
325  }
326
327  #[inline]
328  pub fn reduce_mul(self) -> u64 {
329    pick! {
330      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
331        let array: [u64; 2] = cast(self);
332        array[0].wrapping_mul(array[1])
333      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
334        unsafe { vgetq_lane_u64(self.neon, 0).wrapping_mul(vgetq_lane_u64(self.neon, 1)) }
335      } else {
336        self.arr[0].wrapping_mul(self.arr[1])
337      }
338    }
339  }
340
341  #[inline]
342  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
343    pick! {
344      if #[cfg(target_feature="sse2")] {
345        Self {
346          sse: bitor_m128i(
347            bitand_m128i(if_one.sse, self.sse),
348            bitandnot_m128i(self.sse, if_zero.sse),
349          ),
350        }
351      } else if #[cfg(target_feature="simd128")] {
352        Self { simd: v128_bitselect(if_one.simd, if_zero.simd, self.simd) }
353      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
354        unsafe {Self { neon: vbslq_u64(self.neon, if_one.neon, if_zero.neon) }}
355      } else {
356        generic_bit_blend(self, if_one, if_zero)
357      }
358    }
359  }
360
361  #[inline]
362  fn select(self, if_true: Self, if_false: Self) -> Self {
363    pick! {
364      if #[cfg(target_feature="sse4.1")] {
365        Self { sse: blend_varying_i8_m128i(if_false.sse, if_true.sse, self.sse) }
366      } else if #[cfg(target_feature="simd128")] {
367        Self { simd: v128_bitselect(if_true.simd, if_false.simd, self.simd) }
368      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
369        unsafe {Self { neon: vbslq_u64(self.neon, if_true.neon, if_false.neon) }}
370      } else {
371        generic_bit_blend(self, if_true, if_false)
372      }
373    }
374  }
375
376  #[inline]
377  pub fn to_bitmask(self) -> u32 {
378    pick! {
379      if #[cfg(target_feature="sse")] {
380        // use f64 move_mask since it is the same size as i64
381        move_mask_m128d(cast(self.sse)) as u32
382      } else if #[cfg(target_feature="simd128")] {
383        i64x2_bitmask(self.simd) as u32
384      } else {
385        // nothing amazingly efficient for neon
386        let arr: [u64; 2] = cast(self);
387        (arr[0] >> 63 | ((arr[1] >> 62) & 2)) as u32
388      }
389    }
390  }
391
392  #[inline]
393  pub fn any(self) -> bool {
394    pick! {
395      if #[cfg(target_feature="sse")] {
396        // use f64 move_mask since it is the same size as i64
397        move_mask_m128d(cast(self.sse)) != 0
398      } else if #[cfg(target_feature="simd128")] {
399        i64x2_bitmask(self.simd) != 0
400      } else {
401        let v : [u64;2] = cast(self);
402        ((v[0] | v[1]) & 0x8000000000000000) != 0
403      }
404    }
405  }
406
407  #[inline]
408  pub fn all(self) -> bool {
409    pick! {
410      if #[cfg(target_feature="avx2")] {
411        // use f64 move_mask since it is the same size as i64
412        move_mask_m128d(cast(self.sse)) == 0b11
413      }  else if #[cfg(target_feature="simd128")] {
414        i64x2_bitmask(self.simd) == 0b11
415      } else {
416        let v : [u64;2] = cast(self);
417        ((v[0] & v[1]) & 0x8000000000000000) == 0x8000000000000000
418      }
419    }
420  }
421
422  #[inline]
423  pub fn shuffle(self, indices: u64x2) -> Self {
424    pick! {
425      if #[cfg(target_feature = "sse2")] {
426        let e0 = Self { sse: shuffle_ai_f32_all_m128i::<0b01_00_01_00>(self.sse) };
427        let e1 = Self { sse: shuffle_ai_f32_all_m128i::<0b11_10_11_10>(self.sse) };
428
429        // We can assume that each index is either `0` or `1`, and negating that
430        // always gives us either all bits zero or all bits one.
431        (-indices).select(e1, e0)
432      } else if #[cfg(target_feature = "simd128")] {
433        let e0 = Self { simd: u64x2_shuffle::<0, 0>(self.simd, self.simd) };
434        let e1 = Self { simd: u64x2_shuffle::<1, 1>(self.simd, self.simd) };
435
436        // We can assume that each index is either `0` or `1`, and negating that
437        // always gives us either all bits zero or all bits one.
438        (-indices).select(e1, e0)
439      } else if #[cfg(all(target_feature = "neon", target_arch = "aarch64"))]{
440        let e0 = unsafe { Self { neon: vdupq_n_u64(vget_lane_u64::<0>(vget_low_u64(self.neon))) } };
441        let e1 = unsafe { Self { neon: vdupq_n_u64(vget_lane_u64::<0>(vget_high_u64(self.neon))) } };
442
443        // We can assume that each index is either `0` or `1`, and negating that
444        // always gives us either all bits zero or all bits one.
445        (-indices).select(e1, e0)
446      } else {
447        let e0 = Self::splat(self.as_array()[0]);
448        let e1 = Self::splat(self.as_array()[1]);
449
450        // We can assume that each index is either `0` or `1`, and negating that
451        // always gives us either all bits zero or all bits one.
452        (-indices).select(e1, e0)
453      }
454    }
455  }
456
457  #[inline]
458  pub fn shuffle_zeroing(self, indices: u64x2) -> Self {
459    // Since all implementations use the `select` trick, we always need to mask
460    self.shuffle(indices) & indices.simd_lt(2)
461  }
462
463  #[inline]
464  pub fn shuffle_wrapping(self, indices: u64x2) -> Self {
465    // Since all implementations use the `select` trick, we always need to mask
466    self.shuffle(indices & 1)
467  }
468
469  #[inline]
470  fn shuffle(self: [u64x2; 2], indices: u64x2) -> u64x2 {
471    pick! {
472      if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
473        u64x2 { sse: shuffle_abv_i64_all_m128i(self[0].sse, indices.sse, self[1].sse) }
474      } else {
475        let self_bytes = cast::<[u64x2; 2], [u8x16; 2]>(self);
476        let byte_indices = indices.to_byte_indices();
477
478        cast::<u8x16, u64x2>(self_bytes.shuffle(byte_indices))
479      }
480    }
481  }
482
483  #[inline]
484  fn shuffle_zeroing(self: [u64x2; 2], indices: u64x2) -> u64x2 {
485    // Even if the `u8x16::shuffle` implementation is zeroing, our 64-bit to
486    // 8-bit can trigger an overflow causing incorrect behavior
487    self.shuffle(indices) & indices.simd_lt(4)
488  }
489
490  #[inline]
491  fn shuffle_wrapping(self: [u64x2; 2], indices: u64x2) -> u64x2 {
492    pick! {
493      if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
494        // `avx512` shuffle intrinsics are wrapping
495        self.shuffle(indices)
496      } else {
497        self.shuffle(indices & 3)
498      }
499    }
500  }
501
502  #[inline]
503  fn shuffle(self: [u64x2; 3], indices: u64x2) -> u64x2 {
504    let self_bytes = cast::<[u64x2; 3], [u8x16; 3]>(self);
505    let byte_indices = indices.to_byte_indices();
506
507    cast::<u8x16, u64x2>(self_bytes.shuffle(byte_indices))
508  }
509
510  #[inline]
511  fn shuffle_zeroing(self: [u64x2; 3], indices: u64x2) -> u64x2 {
512    // Even if the `u8x16::shuffle` implementation is zeroing, our 64-bit to
513    // 8-bit can trigger an overflow causing incorrect behavior
514    self.shuffle(indices) & indices.simd_lt(6)
515  }
516
517  #[inline]
518  fn shuffle_wrapping(self: [u64x2; 3], indices: u64x2) -> u64x2 {
519    self.shuffle(indices % 6)
520  }
521
522  #[inline]
523  fn shuffle(self: [u64x2; 4], indices: u64x2) -> u64x2 {
524    let self_bytes = cast::<[u64x2; 4], [u8x16; 4]>(self);
525    let byte_indices = indices.to_byte_indices();
526
527    cast::<u8x16, u64x2>(self_bytes.shuffle(byte_indices))
528  }
529
530  #[inline]
531  fn shuffle_zeroing(self: [u64x2; 4], indices: u64x2) -> u64x2 {
532    // Even if the `u8x16::shuffle` implementation is zeroing, our 64-bit to
533    // 8-bit can trigger an overflow causing incorrect behavior
534    self.shuffle(indices) & indices.simd_lt(8)
535  }
536
537  #[inline]
538  fn shuffle_wrapping(self: [u64x2; 4], indices: u64x2) -> u64x2 {
539    self.shuffle(indices & 7)
540  }
541
542  ///
543  /// This function is accelerated on multiple target architectures.
544  #[inline]
545  pub fn transpose(data: [Self; 2]) -> [Self; 2] {
546    pick! {
547      if #[cfg(any(
548        target_feature="sse2",
549        all(target_feature="neon",target_arch="aarch64"),
550        target_feature="simd128",
551      ))] {
552        // TODO: Remove the casts once unpack functions exist for `Self`.
553        // [data[0].unpack_lo(data[1]), data[0].unpack_hi(data[1])]
554        [
555          data[0].cast_signed().unpack_lo(data[1].cast_signed()).cast_unsigned(),
556          data[0].cast_signed().unpack_hi(data[1].cast_signed()).cast_unsigned(),
557        ]
558      } else {
559        let [x, y, z, w]: [u64; 4] = cast(data);
560        cast([x, z, y, w])
561      }
562    }
563  }
564
565  #[inline]
566  fn shl(self, rhs: Self) -> Self::Output {
567    pick! {
568      if #[cfg(target_feature="avx2")] {
569        // mask the shift count to 63 to have same behavior on all platforms
570        let shift_by = rhs & Self::splat(63);
571        Self { sse: shl_each_u64_m128i(self.sse, shift_by.sse) }
572      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
573        unsafe {
574          // mask the shift count to 63 to have same behavior on all platforms
575          let shift_by = vreinterpretq_s64_u64(vandq_u64(rhs.neon, vmovq_n_u64(63)));
576          Self { neon: vshlq_u64(self.neon, shift_by) }
577        }
578      } else {
579        let arr: [u64; 2] = cast(self);
580        let rhs: [u64; 2] = cast(rhs);
581        cast([
582          arr[0].wrapping_shl(rhs[0] as u32),
583          arr[1].wrapping_shl(rhs[1] as u32),
584        ])
585      }
586    }
587  }
588
589  #[inline]
590  fn shl(self, rhs: u32) -> Self::Output {
591    pick! {
592      if #[cfg(target_feature="sse2")] {
593        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
594        #[expect(clippy::suspicious_arithmetic_impl)]
595        let shift = cast([rhs as u64 & 63, 0]);
596        Self { sse: shl_all_u64_m128i(self.sse, shift) }
597      } else if #[cfg(target_feature="simd128")] {
598        Self { simd: u64x2_shl(self.simd, rhs) }
599      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
600        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
601        #[expect(clippy::suspicious_arithmetic_impl)]
602        unsafe {Self { neon: vshlq_u64(self.neon, vmovq_n_s64(rhs as i64 & 63)) }}
603      } else {
604        Self { arr: [
605          self.arr[0].wrapping_shl(rhs),
606          self.arr[1].wrapping_shl(rhs),
607        ]}
608      }
609    }
610  }
611
612  #[inline]
613  fn shr(self, rhs: Self) -> Self::Output {
614    pick! {
615      if #[cfg(target_feature="avx2")] {
616        // mask the shift count to 63 to have same behavior on all platforms
617        let shift_by = rhs & Self::splat(63);
618        Self { sse: shr_each_u64_m128i(self.sse, shift_by.sse) }
619      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
620        unsafe {
621          // mask the shift count to 63 to have same behavior on all platforms
622          // no right shift, have to pass negative value to left shift on neon
623          let shift_by = vnegq_s64(vreinterpretq_s64_u64(vandq_u64(rhs.neon, vmovq_n_u64(63))));
624          Self { neon: vshlq_u64(self.neon, shift_by) }
625        }
626      } else {
627        let arr: [u64; 2] = cast(self);
628        let rhs: [u64; 2] = cast(rhs);
629        cast([
630          arr[0].wrapping_shr(rhs[0] as u32),
631          arr[1].wrapping_shr(rhs[1] as u32),
632        ])
633      }
634    }
635  }
636
637  #[inline]
638  fn shr(self, rhs: u32) -> Self::Output {
639    pick! {
640      if #[cfg(target_feature="sse2")] {
641        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
642        #[expect(clippy::suspicious_arithmetic_impl)]
643        let shift = cast([rhs as u64 & 63, 0]);
644        Self { sse: shr_all_u64_m128i(self.sse, shift) }
645      } else if #[cfg(target_feature="simd128")] {
646        Self { simd: u64x2_shr(self.simd, rhs) }
647      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
648        // Use `rhs % 64` to perform wrapping shift and not unbounded shift.
649        #[expect(clippy::suspicious_arithmetic_impl)]
650        unsafe {Self { neon: vshlq_u64(self.neon, vmovq_n_s64(-(rhs as i64 & 63))) }}
651      } else {
652        Self { arr: [
653          self.arr[0].wrapping_shr(rhs),
654          self.arr[1].wrapping_shr(rhs),
655        ]}
656      }
657    }
658  }
659
660  #[inline]
661  pub fn max(self, rhs: Self) -> Self {
662    self.simd_gt(rhs).select(self, rhs)
663  }
664
665  #[inline]
666  pub fn min(self, rhs: Self) -> Self {
667    self.simd_lt(rhs).select(self, rhs)
668  }
669
670  #[inline]
671  pub fn reduce_max(self) -> u64 {
672    pick! {
673      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
674        let array: [u64; 2] = cast(self);
675        array[0].max(array[1])
676      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
677        unsafe { vgetq_lane_u64(self.neon, 0).max(vgetq_lane_u64(self.neon, 1)) }
678      } else {
679        self.arr[0].max(self.arr[1])
680      }
681    }
682  }
683
684  #[inline]
685  pub fn reduce_min(self) -> u64 {
686    pick! {
687      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
688        let array: [u64; 2] = cast(self);
689        array[0].min(array[1])
690      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
691        unsafe { vgetq_lane_u64(self.neon, 0).min(vgetq_lane_u64(self.neon, 1)) }
692      } else {
693        self.arr[0].min(self.arr[1])
694      }
695    }
696  }
697
698  #[inline]
699  pub fn unbounded_shl(self, rhs: Self) -> Self {
700    pick! {
701      if #[cfg(target_feature="avx2")] {
702        Self { sse: shl_each_u64_m128i(self.sse, rhs.sse) }
703      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
704        unsafe {
705          // The intrinsic has different semantics so we need to mask ourselves.
706          Self { neon: vshlq_u64(self.neon, vreinterpretq_s64_u64(rhs.neon)) } & rhs.simd_lt(64)
707        }
708      } else {
709        // Cannot use scalar `unbounded_shl` because it takes `u32`, which is
710        // smaller than `u64`.
711        (self << rhs) & rhs.simd_lt(64)
712      }
713    }
714  }
715
716  #[inline]
717  pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
718    pick! {
719      if #[cfg(target_feature="sse2")] {
720        Self { sse: shl_all_u64_m128i(self.sse, cast([rhs as u64, 0])) }
721      } else if #[cfg(target_feature="simd128")] {
722        // The intrinsic performs wrapping shift so we need to mask the result.
723        Self { simd: u64x2_shl(self.simd, rhs) } & Self::splat(rhs as u64).simd_lt(64)
724      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
725        unsafe { Self { neon: vshlq_u64(self.neon, vmovq_n_s64(rhs.min(64) as i64)) } }
726      } else {
727        Self { arr: [
728          self.arr[0].unbounded_shl(rhs),
729          self.arr[1].unbounded_shl(rhs),
730        ]}
731      }
732    }
733  }
734
735  #[inline]
736  pub fn unbounded_shr(self, rhs: Self) -> Self {
737    pick! {
738      if #[cfg(target_feature="avx2")] {
739        Self { sse: shr_each_u64_m128i(self.sse, rhs.sse) }
740      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
741        unsafe {
742          // Negate `rhs` because there is no direct shift-right intrinsic, and
743          // mask to hide `rhs` overflow.
744          Self { neon: vshlq_u64(self.neon, vnegq_s64(vreinterpretq_s64_u64(rhs.neon))) } & rhs.simd_lt(64)
745        }
746      } else {
747        // Cannot use scalar `unbounded_shr` because it takes `u32`, which is
748        // smaller than `u64`.
749        (self >> rhs) & rhs.simd_lt(64)
750      }
751    }
752  }
753
754  #[inline]
755  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
756    pick! {
757      if #[cfg(target_feature="sse2")] {
758        Self { sse: shr_all_u64_m128i(self.sse, cast([rhs as u64, 0])) }
759      } else if #[cfg(target_feature="simd128")] {
760        if rhs < 64 { Self { simd: u64x2_shr(self.simd, rhs) } } else { Self::ZERO }
761      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
762        unsafe {
763          // Negate `rhs` because there is no direct shift-right intrinsic, and
764          // restrict it to prevent overflow.
765          Self { neon: vshlq_u64(self.neon, vmovq_n_s64(-rhs.min(64).cast_signed() as i64)) }
766        }
767      } else {
768        Self {
769          arr: [
770            self.arr[0].unbounded_shr(rhs),
771            self.arr[1].unbounded_shr(rhs),
772          ],
773        }
774      }
775    }
776  }
777
778  #[inline]
779  pub fn saturating_add(self, rhs: Self) -> Self {
780    pick! {
781      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
782        let result = self + rhs;
783        let overflow = result.simd_lt(self);
784        // Return `MAX` (all bits set) if overflow occurs.
785        result | overflow
786      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
787        unsafe { Self { neon: vqaddq_u64(self.neon, rhs.neon) } }
788      } else {
789        Self {
790          arr: [
791            self.arr[0].saturating_add(rhs.arr[0]),
792            self.arr[1].saturating_add(rhs.arr[1]),
793          ],
794        }
795      }
796    }
797  }
798
799  #[inline]
800  pub fn saturating_sub(self, rhs: Self) -> Self {
801    pick! {
802      if #[cfg(any(target_feature="sse2", target_feature="simd128"))] {
803        let result = self - rhs;
804        let no_overflow = result.simd_le(self);
805        // Return `0` (no bits set) if overflow occurs.
806        result & no_overflow
807      } else if #[cfg(all(target_feature="neon",target_arch="aarch64"))]{
808        unsafe { Self { neon: vqsubq_u64(self.neon, rhs.neon) } }
809      } else {
810        Self {
811          arr: [
812            self.arr[0].saturating_sub(rhs.arr[0]),
813            self.arr[1].saturating_sub(rhs.arr[1]),
814          ],
815        }
816      }
817    }
818  }
819
820  #[inline]
821  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
822    // TODO(perf): This implementation looks quite bad. Is there a better
823    // one? This intentionally avoids `mul_keep_low_high` because getting the
824    // high bits of 64-bit multiplication could be slow.
825
826    let self_array = self.to_array();
827    let rhs_array = rhs.to_array();
828
829    let result = [
830      self_array[0].overflowing_mul(rhs_array[0]),
831      self_array[1].overflowing_mul(rhs_array[1]),
832    ];
833    (
834      Self::new([result[0].0, result[1].0]),
835      Self::new([-(result[0].1 as i64) as u64, -(result[1].1 as i64) as u64]),
836    )
837  }
838
839  optional_fn_widening_mul {
840    // Cannot have `widening_mul` because there is no `u128x2` type.
841  }
842
843  #[inline]
844  pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
845    // TODO(perf): This implementation looks quite bad. Is there a better
846    // one?
847
848    let self_array = self.to_array();
849    let rhs_array = rhs.to_array();
850
851    let widening_mul = [
852      (self_array[0] as u128).wrapping_mul(rhs_array[0] as u128),
853      (self_array[1] as u128).wrapping_mul(rhs_array[1] as u128),
854    ];
855
856    (
857      Self::new([
858        widening_mul[0] as u64,
859        widening_mul[1] as u64,
860      ]),
861      Self::new([
862        (widening_mul[0] >> 64) as u64,
863        (widening_mul[1] >> 64) as u64,
864      ]),
865    )
866  }
867
868  #[inline]
869  pub fn mul_keep_high(self, rhs: Self) -> Self {
870    let arr1: [u64; 2] = cast(self);
871    let arr2: [u64; 2] = cast(rhs);
872    cast([
873      ((arr1[0] as u128 * arr2[0] as u128) >> 64) as u64,
874      ((arr1[1] as u128 * arr2[1] as u128) >> 64) as u64,
875    ])
876  }
877
878  optional_fn_deserialize {}
879}
880
881/// The following functionality exists only for [`u64x2`], or only for
882/// particular types inconsistently.
883impl u64x2 {
884  /// A helper for shuffle functions that turns indices of 64-bit lanes into
885  /// byte indices that can be used with 8-bit shuffle intrinsics.
886  ///
887  /// This turns each 64-bit lane `i` into eight 8-bit lanes
888  /// `[8*i, 8*i + 1, 8*i + 2, 8*i + 3, ...]`.
889  ///
890  /// This assumes `self` has already been reduced to the table's lane count,
891  /// which may be at most 32 lanes so that `8 * i` still fits in a byte.
892  #[allow(dead_code)]
893  #[inline]
894  fn to_byte_indices(self) -> u8x16 {
895    // The byte offset of the lane, broadcast to every byte of the lane.
896    let base = self.unbounded_shl_scalar(3);
897    let base = base | base.unbounded_shl_scalar(8);
898    let base = base | base.unbounded_shl_scalar(16);
899    let base = base | base.unbounded_shl_scalar(32);
900
901    // Then the offset of each byte within its lane. These bits are free because
902    // every byte of `base` is a multiple of eight. `from_ne_bytes` keeps this
903    // correct on big endian, where the bytes of a lane are the other way around.
904    const WITHIN_LANE: u64x2 =
905      u64x2::splat(u64::from_ne_bytes([0, 1, 2, 3, 4, 5, 6, 7]));
906
907    cast::<u64x2, u8x16>(base | WITHIN_LANE)
908  }
909
910  /// Returns `[self[0], b[0]]`, taking the low element of the 128-bit lane.
911  #[inline]
912  #[must_use]
913  pub fn unpack_lo(self, b: Self) -> Self {
914    pick! {
915      if #[cfg(target_feature="sse2")] {
916        Self { sse: unpack_low_i64_m128i(self.sse, b.sse) }
917      } else if #[cfg(target_feature="simd128")] {
918        Self { simd: i64x2_shuffle::<0, 2>(self.simd, b.simd) }
919      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
920        Self { neon: unsafe { vzip1q_u64(self.neon, b.neon) } }
921      } else {
922        Self::new([self.as_array()[0], b.as_array()[0]])
923      }
924    }
925  }
926
927  /// Returns `[self[1], b[1]]`, taking the high element of the 128-bit lane.
928  #[inline]
929  #[must_use]
930  pub fn unpack_hi(self, b: Self) -> Self {
931    pick! {
932      if #[cfg(target_feature="sse2")] {
933        Self { sse: unpack_high_i64_m128i(self.sse, b.sse) }
934      } else if #[cfg(target_feature="simd128")] {
935        Self { simd: i64x2_shuffle::<1, 3>(self.simd, b.simd) }
936      } else if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
937        Self { neon: unsafe { vzip2q_u64(self.neon, b.neon) } }
938      } else {
939        Self::new([self.as_array()[1], b.as_array()[1]])
940      }
941    }
942  }
943
944  /// The exact per-lane product of `a` and `b` masked to `W` bits.
945  ///
946  /// Only exact for `W <= 32`, where the product still fits a lane, and
947  /// callers guard on that. It is instantiated for wider `W` too, since the
948  /// guard is a runtime `if` on a const, so it cannot assert the bound itself.
949  #[inline]
950  #[must_use]
951  fn mul_masked<const W: u32>(a: Self, b: Self) -> Self {
952    pick! {
953      if #[cfg(target_feature="sse2")] {
954        // `pmuludq` reads the low 32 bits of a lane anyway, so at `W == 32` the
955        // operand masks are already implied and can be dropped. A scalar
956        // multiply has no such truncation, so the other arm always masks.
957        let (a, b) = if W == 32 {
958          (a, b)
959        } else {
960          let mask = Self::splat(add_mul_operand_mask_u64::<W>());
961          (a & mask, b & mask)
962        };
963
964        Self { sse: mul_widen_u32_odd_m128i(a.sse, b.sse) }
965      } else {
966        let mask = add_mul_operand_mask_u64::<W>();
967        let a = a.to_array();
968        let b = b.to_array();
969        Self::new(core::array::from_fn(|i| (a[i] & mask) * (b[i] & mask)))
970      }
971    }
972  }
973
974  /// `self + ((a * b) mod 2^W)`, reading only the low `W` bits of each lane of
975  /// `a` and `b`. `W` must be in `1..=64`.
976  #[inline]
977  #[must_use]
978  pub fn add_mul_lo<const W: u32>(self, a: Self, b: Self) -> Self {
979    pick! {
980      if #[cfg(all(target_feature="avx512ifma", target_feature="avx512vl"))] {
981        // IFMA is fixed at 52 bits; any other width takes the generic path.
982        if W == 52 {
983          return Self { sse: add_mul_low_u52_m128i(self.sse, a.sse, b.sse) };
984        }
985      }
986    }
987
988    // Below 33 bits the whole product fits a lane, so one widening multiply
989    // yields both halves and the split is a mask rather than an instruction.
990    if W <= 32 {
991      let mask = Self::splat(add_mul_operand_mask_u64::<W>());
992      return self + (Self::mul_masked::<W>(a, b) & mask);
993    }
994
995    let acc = self.to_array();
996    let a = a.to_array();
997    let b = b.to_array();
998    Self::new(core::array::from_fn(|i| {
999      add_mul_lo_lane_u64::<W>(acc[i], a[i], b[i])
1000    }))
1001  }
1002
1003  /// `self + ((a * b) >> W)`, reading only the low `W` bits of each lane of `a`
1004  /// and `b`. `W` must be in `1..=64`.
1005  #[inline]
1006  #[must_use]
1007  pub fn add_mul_hi<const W: u32>(self, a: Self, b: Self) -> Self {
1008    pick! {
1009      if #[cfg(all(target_feature="avx512ifma", target_feature="avx512vl"))] {
1010        // IFMA is fixed at 52 bits; any other width takes the generic path.
1011        if W == 52 {
1012          return Self { sse: add_mul_high_u52_m128i(self.sse, a.sse, b.sse) };
1013        }
1014      }
1015    }
1016
1017    // See `add_mul_lo`: the whole product is in the lane, so the high half is a
1018    // shift.
1019    if W <= 32 {
1020      return self + (Self::mul_masked::<W>(a, b) >> W);
1021    }
1022
1023    let acc = self.to_array();
1024    let a = a.to_array();
1025    let b = b.to_array();
1026    Self::new(core::array::from_fn(|i| {
1027      add_mul_hi_lane_u64::<W>(acc[i], a[i], b[i])
1028    }))
1029  }
1030}