Skip to main content

wide/
u16x16_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx2")] {
5    /// A SIMD vector with 16 elements of type [`u16`].
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(32))]
13    pub struct u16x16 { pub(crate) avx2: m256i }
14  } else {
15    /// A SIMD vector with 16 elements of type [`u16`].
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(32))]
23    pub struct u16x16 { pub(crate) a : u16x8, pub(crate) b : u16x8 }
24  }
25}
26
27impl_simd_uint! {
28  unsafe {
29    T = u16,
30    N = 16,
31    Simd = u16x16,
32    IntSimd = i16x16,
33    T_BITS = 16,
34    T_BITS_MUL_2 = 32,
35    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
36    optional_type_x86_inner { X86Inner = __m256i },
37    optional_type_arm_inner {},
38    optional_type_wasm_inner {},
39  }
40
41  #[inline]
42  fn not(self) -> Self {
43    pick! {
44      if #[cfg(target_feature="avx2")] {
45        Self { avx2: self.avx2.not()  }
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="avx2")] {
59        Self { avx2: add_i16_m256i(self.avx2, rhs.avx2) }
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="avx2")] {
73        Self { avx2: sub_i16_m256i(self.avx2, rhs.avx2) }
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="avx2")] {
87        // non-widening multiplication is the same for unsigned and signed
88        Self { avx2: mul_i16_keep_low_m256i(self.avx2, rhs.avx2) }
89      } else {
90        Self {
91          a : self.a.mul(rhs.a),
92          b : self.b.mul(rhs.b),
93        }
94      }
95    }
96  }
97
98  #[inline]
99  fn bitand(self, rhs: Self) -> Self::Output {
100    pick! {
101      if #[cfg(target_feature="avx2")] {
102        Self { avx2: bitand_m256i(self.avx2, rhs.avx2) }
103      } else {
104        Self {
105          a : self.a.bitand(rhs.a),
106          b : self.b.bitand(rhs.b),
107        }
108      }
109    }
110  }
111
112  #[inline]
113  fn bitor(self, rhs: Self) -> Self::Output {
114    pick! {
115      if #[cfg(target_feature="avx2")] {
116        Self { avx2: bitor_m256i(self.avx2, rhs.avx2) }
117      } else {
118        Self {
119          a : self.a.bitor(rhs.a),
120          b : self.b.bitor(rhs.b),
121        }
122      }
123    }
124  }
125
126  #[inline]
127  fn bitxor(self, rhs: Self) -> Self::Output {
128    pick! {
129      if #[cfg(target_feature="avx2")] {
130        Self { avx2: bitxor_m256i(self.avx2, rhs.avx2) }
131      } else {
132        Self {
133          a : self.a.bitxor(rhs.a),
134          b : self.b.bitxor(rhs.b),
135        }
136      }
137    }
138  }
139
140  #[inline]
141  fn simd_eq(self, rhs: Self) -> Self::Output {
142    pick! {
143      if #[cfg(target_feature="avx2")] {
144        Self { avx2: cmp_eq_mask_i16_m256i(self.avx2, rhs.avx2) }
145      } else {
146        Self {
147          a : self.a.simd_eq(rhs.a),
148          b : self.b.simd_eq(rhs.b),
149        }
150      }
151    }
152  }
153
154  #[inline]
155  fn simd_ne(self, rhs: Self) -> Self::Output {
156    pick! {
157      if #[cfg(target_feature="avx2")] {
158        !self.simd_eq(rhs)
159      } else {
160        Self {
161          a : self.a.simd_ne(rhs.a),
162          b : self.b.simd_ne(rhs.b),
163        }
164      }
165    }
166  }
167
168  #[inline]
169  fn simd_lt(self, rhs: Self) -> Self::Output {
170    // no gt, so just reverse to get same answer
171    Self::simd_gt(rhs, self)
172  }
173
174  #[inline]
175  fn simd_gt(self, rhs: Self) -> Self::Output {
176    pick! {
177      if #[cfg(target_feature = "avx2")] {
178        let bias = m256i::from([0x8000u16; 16]);
179        let a_biased = sub_i16_m256i(self.avx2, bias);
180        let b_biased = sub_i16_m256i(rhs.avx2, bias);
181        let mask = cmp_gt_mask_i16_m256i(a_biased, b_biased);
182
183        Self { avx2: mask }
184      } else {
185        Self {
186          a: self.a.simd_gt(rhs.a),
187          b: self.b.simd_gt(rhs.b),
188        }
189      }
190    }
191  }
192
193  #[inline]
194  fn simd_le(self, rhs: Self) -> Self::Output {
195    pick! {
196      if #[cfg(target_feature="avx2")] {
197        !self.simd_gt(rhs)
198      } else {
199        Self {
200          a : self.a.simd_le(rhs.a),
201          b : self.b.simd_le(rhs.b),
202        }
203      }
204    }
205  }
206
207  #[inline]
208  fn simd_ge(self, rhs: Self) -> Self::Output {
209    pick! {
210      if #[cfg(target_feature="avx2")] {
211        !self.simd_lt(rhs)
212      } else {
213        Self {
214          a : self.a.simd_ge(rhs.a),
215          b : self.b.simd_ge(rhs.b),
216        }
217      }
218    }
219  }
220
221  #[inline]
222  pub fn reduce_add(self) -> u16 {
223    let array: [u16x8; 2] = cast(self);
224    (array[0] + array[1]).reduce_add()
225  }
226
227  #[inline]
228  pub fn reduce_mul(self) -> u16 {
229    let array: [u16x8; 2] = cast(self);
230    (array[0] * array[1]).reduce_mul()
231  }
232
233  #[inline]
234  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
235    pick! {
236      if #[cfg(target_feature="avx2")] {
237        Self {
238          avx2: bitor_m256i(
239            bitand_m256i(if_one.avx2, self.avx2),
240            bitandnot_m256i(self.avx2, if_zero.avx2),
241          ),
242        }
243      } else {
244        Self {
245          a: self.a.bitselect(if_one.a, if_zero.a),
246          b: self.b.bitselect(if_one.b, if_zero.b),
247        }
248      }
249    }
250  }
251
252  #[inline]
253  fn select(self, if_true: Self, if_false: Self) -> Self {
254    pick! {
255      if #[cfg(target_feature="avx2")] {
256        Self { avx2: blend_varying_i8_m256i(if_false.avx2, if_true.avx2, self.avx2) }
257      } else {
258        Self {
259          a : self.a.select(if_true.a, if_false.a),
260          b : self.b.select(if_true.b, if_false.b),
261        }
262      }
263    }
264  }
265
266  #[inline]
267  pub fn to_bitmask(self) -> u32 {
268    pick! {
269      if #[cfg(target_feature="sse2")] {
270          let [a,b] = cast::<_,[m128i;2]>(self);
271          move_mask_i8_m128i( pack_i16_to_i8_m128i(a,b)) as u32
272        } else {
273        self.a.to_bitmask() | (self.b.to_bitmask() << 8)
274      }
275    }
276  }
277
278  #[inline]
279  pub fn any(self) -> bool {
280    pick! {
281      if #[cfg(target_feature="avx2")] {
282        ((move_mask_i8_m256i(self.avx2) as u32) & 0b10101010101010101010101010101010) != 0
283      } else {
284        (self.a | self.b).any()
285      }
286    }
287  }
288
289  #[inline]
290  pub fn all(self) -> bool {
291    pick! {
292      if #[cfg(target_feature="avx2")] {
293        ((move_mask_i8_m256i(self.avx2) as u32) & 0b10101010101010101010101010101010) == 0b10101010101010101010101010101010
294      } else {
295        (self.a & self.b).all()
296      }
297    }
298  }
299
300  #[inline]
301  pub fn shuffle(self, indices: u16x16) -> Self {
302    pick! {
303      if #[cfg(all(target_feature = "avx512bw", target_feature = "avx512vl"))] {
304        #[cfg(target_arch = "x86")]
305        use core::arch::x86::_mm256_permutexvar_epi16;
306        #[cfg(target_arch = "x86_64")]
307        use core::arch::x86_64::_mm256_permutexvar_epi16;
308        // TODO(safe_arch): Add `_mm256_permutexvar_epi16`
309        Self { avx2: unsafe { m256i(_mm256_permutexvar_epi16(indices.avx2.0, self.avx2.0)) } }
310      } else if #[cfg(any(
311        target_feature = "ssse3",
312        all(target_arch = "aarch64", target_feature = "neon"),
313        target_feature = "simd128",
314      ))] {
315        let self_bytes = cast::<u16x16, u8x32>(self);
316        let byte_indices = indices.to_byte_indices();
317
318        cast::<u8x32, u16x16>(self_bytes.shuffle(byte_indices))
319      } else {
320        let self_array = self.to_array();
321        let indices_array = indices.to_array();
322
323        let mut result = [0; 16];
324        for i in 0..16 {
325          let index = indices_array[i] as usize;
326          if index < 16 {
327            result[i] = self_array[index];
328          }
329        }
330
331        Self::new(result)
332      }
333    }
334  }
335
336  #[inline]
337  pub fn shuffle_zeroing(self, indices: u16x16) -> Self {
338    pick! {
339      if #[cfg(any(
340        target_feature = "ssse3",
341        all(target_arch = "aarch64", target_feature = "neon"),
342        target_feature = "simd128",
343      ))] {
344        // Even if the `u8x32::shuffle` implementation is zeroing, our 16-bit to
345        // 8-bit can trigger an overflow causing incorrect behavior
346        self.shuffle(indices) & indices.simd_lt(16)
347      } else {
348        // The fallback branch of `shuffle` already has the behavior we want
349        self.shuffle(indices)
350      }
351    }
352  }
353
354  #[inline]
355  pub fn shuffle_wrapping(self, indices: u16x16) -> Self {
356    pick! {
357      if #[cfg(all(target_feature = "avx512bw", target_feature = "avx512vl"))] {
358        // `avx512` shuffle intrinsics are wrapping
359        self.shuffle(indices)
360      } else {
361        self.shuffle(indices & 15)
362      }
363    }
364  }
365
366  #[inline]
367  fn shuffle(self: [u16x16; 2], indices: u16x16) -> u16x16 {
368    pick! {
369      if #[cfg(all(target_feature = "avx512bw", target_feature = "avx512vl"))] {
370        #[cfg(target_arch = "x86")]
371        use core::arch::x86::_mm256_permutex2var_epi16;
372        #[cfg(target_arch = "x86_64")]
373        use core::arch::x86_64::_mm256_permutex2var_epi16;
374        // TODO(safe_arch): add `_mm256_permutex2var_epi16`.
375        u16x16 {
376          avx2: unsafe {
377            m256i(_mm256_permutex2var_epi16(self[0].avx2.0, indices.avx2.0, self[1].avx2.0))
378          },
379        }
380      } else {
381        let self_bytes = cast::<[u16x16; 2], [u8x32; 2]>(self);
382        let byte_indices = indices.to_byte_indices();
383
384        cast::<u8x32, u16x16>(self_bytes.shuffle(byte_indices))
385      }
386    }
387  }
388
389  #[inline]
390  fn shuffle_zeroing(self: [u16x16; 2], indices: u16x16) -> u16x16 {
391    // Even if the `u8x32` shuffle is zeroing, our 16-bit to 8-bit index
392    // conversion breaks for out of bounds indices.
393    self.shuffle(indices) & indices.simd_lt(32)
394  }
395
396  #[inline]
397  fn shuffle_wrapping(self: [u16x16; 2], indices: u16x16) -> u16x16 {
398    pick! {
399      if #[cfg(all(target_feature = "avx512bw", target_feature = "avx512vl"))] {
400        // `avx512` shuffle intrinsics are wrapping
401        self.shuffle(indices)
402      } else {
403        self.shuffle(indices & 31)
404      }
405    }
406  }
407
408  #[inline]
409  fn shuffle(self: [u16x16; 3], indices: u16x16) -> u16x16 {
410    let self_bytes = cast::<[u16x16; 3], [u8x32; 3]>(self);
411    let byte_indices = indices.to_byte_indices();
412
413    cast::<u8x32, u16x16>(self_bytes.shuffle(byte_indices))
414  }
415
416  #[inline]
417  fn shuffle_zeroing(self: [u16x16; 3], indices: u16x16) -> u16x16 {
418    // Even if the `u8x32` shuffle is zeroing, our 16-bit to 8-bit index
419    // conversion breaks for out of bounds indices.
420    self.shuffle(indices) & indices.simd_lt(48)
421  }
422
423  #[inline]
424  fn shuffle_wrapping(self: [u16x16; 3], indices: u16x16) -> u16x16 {
425    self.shuffle(indices % 48)
426  }
427
428  #[inline]
429  fn shuffle(self: [u16x16; 4], indices: u16x16) -> u16x16 {
430    let self_bytes = cast::<[u16x16; 4], [u8x32; 4]>(self);
431    let byte_indices = indices.to_byte_indices();
432
433    cast::<u8x32, u16x16>(self_bytes.shuffle(byte_indices))
434  }
435
436  #[inline]
437  fn shuffle_zeroing(self: [u16x16; 4], indices: u16x16) -> u16x16 {
438    // Even if the `u8x32` shuffle is zeroing, our 16-bit to 8-bit index
439    // conversion breaks for out of bounds indices.
440    self.shuffle(indices) & indices.simd_lt(64)
441  }
442
443  #[inline]
444  fn shuffle_wrapping(self: [u16x16; 4], indices: u16x16) -> u16x16 {
445    self.shuffle(indices & 63)
446  }
447
448  ///
449  /// Currently this function is never accelerated.
450  #[inline]
451  pub fn transpose(data: [Self; 16]) -> [Self; 16] {
452    // Can this be optimized?
453
454    #[inline(always)]
455    fn transpose_column(data: &[u16x16; 16], index: usize) -> u16x16 {
456      u16x16::new([
457        data[0].as_array()[index],
458        data[1].as_array()[index],
459        data[2].as_array()[index],
460        data[3].as_array()[index],
461        data[4].as_array()[index],
462        data[5].as_array()[index],
463        data[6].as_array()[index],
464        data[7].as_array()[index],
465        data[8].as_array()[index],
466        data[9].as_array()[index],
467        data[10].as_array()[index],
468        data[11].as_array()[index],
469        data[12].as_array()[index],
470        data[13].as_array()[index],
471        data[14].as_array()[index],
472        data[15].as_array()[index],
473      ])
474    }
475
476    [
477      transpose_column(&data, 0),
478      transpose_column(&data, 1),
479      transpose_column(&data, 2),
480      transpose_column(&data, 3),
481      transpose_column(&data, 4),
482      transpose_column(&data, 5),
483      transpose_column(&data, 6),
484      transpose_column(&data, 7),
485      transpose_column(&data, 8),
486      transpose_column(&data, 9),
487      transpose_column(&data, 10),
488      transpose_column(&data, 11),
489      transpose_column(&data, 12),
490      transpose_column(&data, 13),
491      transpose_column(&data, 14),
492      transpose_column(&data, 15),
493    ]
494  }
495
496  #[inline]
497  fn shl(self, rhs: Self) -> Self::Output {
498    pick! {
499      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
500        #[cfg(target_arch = "x86")]
501        use core::arch::x86::_mm256_sllv_epi16;
502        #[cfg(target_arch = "x86_64")]
503        use core::arch::x86_64::_mm256_sllv_epi16;
504
505        // Mask `rhs` to 15 to match `wrapping_shl`.
506        let rhs = bitand_m256i(rhs.avx2, set_splat_i16_m256i(15));
507        // TODO(safe_arch): Add `_mm256_sllv_epi16`.
508        cast(unsafe { _mm256_sllv_epi16(self.avx2.0, rhs.0) })
509      } else {
510        let [self_a, self_b]: [u16x8; 2] = cast(self);
511        let [rhs_a, rhs_b]: [u16x8; 2] = cast(rhs);
512
513        cast([self_a << rhs_a, self_b << rhs_b])
514      }
515    }
516  }
517
518  #[inline]
519  fn shl(self, rhs: u32) -> Self::Output {
520    pick! {
521      if #[cfg(target_feature="avx2")] {
522        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
523        #[expect(clippy::suspicious_arithmetic_impl)]
524        let shift = cast([rhs as u64 & 15, 0]);
525        Self { avx2: shl_all_u16_m256i(self.avx2, shift) }
526      } else {
527        Self {
528          a : self.a.shl(rhs),
529          b : self.b.shl(rhs),
530        }
531      }
532    }
533  }
534
535  #[inline]
536  fn shr(self, rhs: Self) -> Self::Output {
537    pick! {
538      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
539        #[cfg(target_arch = "x86")]
540        use core::arch::x86::_mm256_srlv_epi16;
541        #[cfg(target_arch = "x86_64")]
542        use core::arch::x86_64::_mm256_srlv_epi16;
543
544        // Mask `rhs` to 15 to match `wrapping_shr`.
545        let rhs = bitand_m256i(rhs.avx2, set_splat_i16_m256i(15));
546        // TODO(safe_arch): Add `_mm256_srlv_epi16`.
547        cast(unsafe { _mm256_srlv_epi16(self.avx2.0, rhs.0) })
548      } else {
549        let [self_a, self_b]: [u16x8; 2] = cast(self);
550        let [rhs_a, rhs_b]: [u16x8; 2] = cast(rhs);
551
552        cast([self_a >> rhs_a, self_b >> rhs_b])
553      }
554    }
555  }
556
557  #[inline]
558  fn shr(self, rhs: u32) -> Self::Output {
559    pick! {
560      if #[cfg(target_feature="avx2")] {
561        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
562        #[expect(clippy::suspicious_arithmetic_impl)]
563        let shift = cast([rhs as u64 & 15, 0]);
564        Self { avx2: shr_all_u16_m256i(self.avx2, shift) }
565      } else {
566        Self {
567          a : self.a.shr(rhs),
568          b : self.b.shr(rhs),
569        }
570      }
571    }
572  }
573
574  #[inline]
575  pub fn max(self, rhs: Self) -> Self {
576    pick! {
577      if #[cfg(target_feature="avx2")] {
578        Self { avx2: max_u16_m256i(self.avx2, rhs.avx2) }
579      } else {
580        Self {
581          a : self.a.max(rhs.a),
582          b : self.b.max(rhs.b),
583        }
584      }
585    }
586  }
587
588  #[inline]
589  pub fn min(self, rhs: Self) -> Self {
590    pick! {
591      if #[cfg(target_feature="avx2")] {
592        Self { avx2: min_u16_m256i(self.avx2, rhs.avx2) }
593      } else {
594        Self {
595          a : self.a.min(rhs.a),
596          b : self.b.min(rhs.b),
597        }
598      }
599    }
600  }
601
602  #[inline]
603  pub fn reduce_max(self) -> u16 {
604    let array: [u16x8; 2] = cast(self);
605    array[0].max(array[1]).reduce_max()
606  }
607
608  #[inline]
609  pub fn reduce_min(self) -> u16 {
610    let array: [u16x8; 2] = cast(self);
611    array[0].min(array[1]).reduce_min()
612  }
613
614  #[inline]
615  pub fn unbounded_shl(self, rhs: Self) -> Self {
616    pick! {
617      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
618        #[cfg(target_arch = "x86")]
619        use core::arch::x86::_mm256_sllv_epi16;
620        #[cfg(target_arch = "x86_64")]
621        use core::arch::x86_64::_mm256_sllv_epi16;
622
623        // TODO(safe_arch): Add `_mm256_sllv_epi16`.
624        cast(unsafe { _mm256_sllv_epi16(self.avx2.0, rhs.avx2.0) })
625      } else {
626        let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
627        let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
628
629        cast([self_a.unbounded_shl(rhs_a), self_b.unbounded_shl(rhs_b)])
630      }
631    }
632  }
633
634  #[inline]
635  pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
636    pick! {
637      if #[cfg(target_feature="avx2")] {
638        Self { avx2: shl_all_u16_m256i(self.avx2, cast([rhs as u64, 0])) }
639      } else {
640        Self {
641          a: self.a.unbounded_shl_scalar(rhs),
642          b: self.b.unbounded_shl_scalar(rhs),
643        }
644      }
645    }
646  }
647
648  #[inline]
649  pub fn unbounded_shr(self, rhs: Self) -> Self {
650    pick! {
651      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
652        #[cfg(target_arch = "x86")]
653        use core::arch::x86::_mm256_srlv_epi16;
654        #[cfg(target_arch = "x86_64")]
655        use core::arch::x86_64::_mm256_srlv_epi16;
656
657        // TODO(safe_arch): Add `_mm256_srlv_epi16`.
658        cast(unsafe { _mm256_srlv_epi16(self.avx2.0, rhs.avx2.0) })
659      } else {
660        let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
661        let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
662
663        cast([self_a.unbounded_shr(rhs_a), self_b.unbounded_shr(rhs_b)])
664      }
665    }
666  }
667
668  #[inline]
669  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
670    pick! {
671      if #[cfg(target_feature="avx2")] {
672        Self { avx2: shr_all_u16_m256i(self.avx2, cast([rhs as u64, 0])) }
673      } else {
674        Self {
675          a: self.a.unbounded_shr_scalar(rhs),
676          b: self.b.unbounded_shr_scalar(rhs),
677        }
678      }
679    }
680  }
681
682  #[inline]
683  pub fn saturating_add(self, rhs: Self) -> Self {
684    pick! {
685      if #[cfg(target_feature="avx2")] {
686        Self { avx2: add_saturating_u16_m256i(self.avx2, rhs.avx2) }
687      } else {
688        Self {
689          a : self.a.saturating_add(rhs.a),
690          b : self.b.saturating_add(rhs.b),
691        }
692      }
693    }
694  }
695
696  #[inline]
697  pub fn saturating_sub(self, rhs: Self) -> Self {
698    pick! {
699      if #[cfg(target_feature="avx2")] {
700        Self { avx2: sub_saturating_u16_m256i(self.avx2, rhs.avx2) }
701      } else {
702        Self {
703          a : self.a.saturating_sub(rhs.a),
704          b : self.b.saturating_sub(rhs.b),
705        }
706      }
707    }
708  }
709
710  #[inline]
711  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
712    let (low, high) = self.mul_keep_low_high(rhs);
713    let overflow = high.simd_ne(Self::ZERO);
714    (low, overflow)
715  }
716
717  optional_fn_widening_mul {
718    #[inline]
719    pub fn widening_mul(self, rhs: Self) -> u32x16 {
720      // x86 has no `_mm256_mul_epu16` intrinsic so there is no `avx2`
721      // optimization.
722
723      let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
724      let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
725
726      cast([self_a.widening_mul(rhs_a), self_b.widening_mul(rhs_b)])
727    }
728  }
729
730  #[inline]
731  pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
732    // x86 has no `_mm256_mul_epu16` intrinsic so there is no `avx2`
733    // optimization.
734
735    let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
736    let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
737
738    let result_a = self_a.mul_keep_low_high(rhs_a);
739    let result_b = self_b.mul_keep_low_high(rhs_b);
740    (cast([result_a.0, result_b.0]), cast([result_a.1, result_b.1]))
741  }
742
743  #[inline]
744  pub fn mul_keep_high(self, rhs: Self) -> Self {
745    // x86 has no `_mm256_mul_epu16` intrinsic so there is no `avx2`
746    // optimization.
747
748    let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
749    let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
750
751    cast([self_a.mul_keep_high(rhs_a), self_b.mul_keep_high(rhs_b)])
752  }
753
754  optional_fn_deserialize {}
755}
756
757/// The following functionality exists only for [`u16x16`], or only for
758/// particular types inconsistently.
759impl u16x16 {
760  /// A helper for shuffle functions that turns indices of 16-bit lanes into
761  /// byte indices that can be used with 8-bit shuffle intrinsics.
762  ///
763  /// This turns each 16-bit lane `i` into two 8-bit lanes `[2*i, 2*i + 1]`.
764  ///
765  /// This assumes `self` has already been reduced to the table's lane count,
766  /// which may be at most 128 lanes so that `2 * i` still fits in a byte.
767  #[allow(dead_code)]
768  #[inline]
769  fn to_byte_indices(self) -> u8x32 {
770    // The byte offset of the lane, broadcast to every byte of the lane.
771    let base = self.unbounded_shl_scalar(1);
772    let base = base | base.unbounded_shl_scalar(8);
773
774    // Then the offset of each byte within its lane. These bits are free because
775    // every byte of `base` is a multiple of two. `from_ne_bytes` keeps this
776    // correct on big endian, where the bytes of a lane are the other way around.
777    const WITHIN_LANE: u16x16 = u16x16::splat(u16::from_ne_bytes([0, 1]));
778
779    cast::<u16x16, u8x32>(base | WITHIN_LANE)
780  }
781}
782
783impl From<u8x16> for u16x16 {
784  /// widens and sign extends to u16x16
785  #[inline]
786  fn from(v: u8x16) -> Self {
787    pick! {
788      if #[cfg(target_feature="avx2")] {
789        u16x16 { avx2:convert_to_i16_m256i_from_u8_m128i(v.sse) }
790      } else if #[cfg(target_feature="sse2")] {
791        u16x16 {
792          a: u16x8 { sse: shr_imm_u16_m128i::<8>( unpack_low_i8_m128i(v.sse, v.sse)) },
793          b: u16x8 { sse: shr_imm_u16_m128i::<8>( unpack_high_i8_m128i(v.sse, v.sse)) },
794        }
795      } else {
796
797        u16x16::new([
798          v.as_array()[0] as u16,
799          v.as_array()[1] as u16,
800          v.as_array()[2] as u16,
801          v.as_array()[3] as u16,
802          v.as_array()[4] as u16,
803          v.as_array()[5] as u16,
804          v.as_array()[6] as u16,
805          v.as_array()[7] as u16,
806          v.as_array()[8] as u16,
807          v.as_array()[9] as u16,
808          v.as_array()[10] as u16,
809          v.as_array()[11] as u16,
810          v.as_array()[12] as u16,
811          v.as_array()[13] as u16,
812          v.as_array()[14] as u16,
813          v.as_array()[15] as u16,
814          ])
815      }
816    }
817  }
818}