Skip to main content

wide/
u16x32_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx512bw")] {
5    /// A SIMD vector with 32 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(64))]
13    pub struct u16x32 { pub(crate) avx512: m512i }
14  } else {
15    /// A SIMD vector with 32 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(64))]
23    pub struct u16x32 { pub(crate) a : u16x16, pub(crate) b : u16x16 }
24  }
25}
26
27impl_simd_uint! {
28  unsafe {
29    T = u16,
30    N = 32,
31    Simd = u16x32,
32    IntSimd = i16x32,
33    T_BITS = 16,
34    T_BITS_MUL_2 = 32,
35    [
36      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
37      21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
38    ],
39    optional_type_x86_inner { X86Inner = __m512i },
40    optional_type_arm_inner {},
41    optional_type_wasm_inner {},
42  }
43
44  #[inline]
45  fn not(self) -> Self::Output {
46    pick! {
47      if #[cfg(target_feature="avx512bw")] {
48        Self { avx512: bitxor_m512i(self.avx512, set_splat_i16_m512i(-1)) }
49      } else {
50        Self {
51          a : self.a.not(),
52          b : self.b.not(),
53        }
54      }
55    }
56  }
57
58  #[inline]
59  fn add(self, rhs: Self) -> Self::Output {
60    pick! {
61      if #[cfg(target_feature="avx512bw")] {
62        Self { avx512: add_i16_m512i(self.avx512, rhs.avx512) }
63      } else {
64        Self {
65          a : self.a.add(rhs.a),
66          b : self.b.add(rhs.b),
67        }
68      }
69    }
70  }
71
72  #[inline]
73  fn sub(self, rhs: Self) -> Self::Output {
74    pick! {
75      if #[cfg(target_feature="avx512bw")] {
76        Self { avx512: sub_i16_m512i(self.avx512, rhs.avx512) }
77      } else {
78        Self {
79          a : self.a.sub(rhs.a),
80          b : self.b.sub(rhs.b),
81        }
82      }
83    }
84  }
85
86  #[inline]
87  fn mul(self, rhs: Self) -> Self::Output {
88    pick! {
89      if #[cfg(target_feature="avx512bw")] {
90        Self { avx512: mul_i16_keep_low_m512i(self.avx512, rhs.avx512) }
91      } else {
92        Self { a: self.a.mul(rhs.a), b: self.b.mul(rhs.b) }
93      }
94    }
95  }
96
97  #[inline]
98  fn bitand(self, rhs: Self) -> Self::Output {
99    pick! {
100      if #[cfg(target_feature="avx512bw")] {
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="avx512bw")] {
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="avx512bw")] {
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="avx512bw")] {
143        Self { avx512: cmp_op_mask_u16_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="avx512bw")] {
157        Self { avx512: cmp_op_mask_u16_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="avx512bw")] {
171        Self { avx512: cmp_op_mask_u16_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="avx512bw")] {
185        Self { avx512: cmp_op_mask_u16_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="avx512bw")] {
199        Self { avx512: cmp_op_mask_u16_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="avx512bw")] {
213        Self { avx512: cmp_op_mask_u16_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) -> u16 {
225    let array: [u16x16; 2] = cast(self);
226    (array[0] + array[1]).reduce_add()
227  }
228
229  #[inline]
230  pub fn reduce_mul(self) -> u16 {
231    let array: [u16x16; 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="avx512bw")] {
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="avx512bw")] {
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="avx512bw")] {
272        // use f16 move_mask since it is the same size as i16
273        movepi16_mask_m512i(self.avx512) as u32
274      } else {
275        self.a.to_bitmask() | (self.b.to_bitmask() << 16)
276      }
277    }
278  }
279
280  #[inline]
281  pub fn any(self) -> bool {
282    pick! {
283      if #[cfg(target_feature="avx512bw")] {
284        movepi16_mask_m512i(self.avx512) != 0
285      } else {
286        (self.a | self.b).any()
287      }
288    }
289  }
290
291  #[inline]
292  pub fn all(self) -> bool {
293    pick! {
294      if #[cfg(target_feature="avx512bw")] {
295        movepi16_mask_m512i(self.avx512) == 0xFFFFFFFF
296      } else {
297        (self.a & self.b).all()
298      }
299    }
300  }
301
302  #[inline]
303  pub fn shuffle(self, indices: u16x32) -> Self {
304    pick! {
305      if #[cfg(all(target_feature = "avx512bw"))] {
306        #[cfg(target_arch = "x86")]
307        use core::arch::x86::_mm512_permutexvar_epi16;
308        #[cfg(target_arch = "x86_64")]
309        use core::arch::x86_64::_mm512_permutexvar_epi16;
310        // TODO(safe_arch): Add `_mm512_permutexvar_epi16`
311        Self { avx512: unsafe { m512i(_mm512_permutexvar_epi16(indices.avx512.0, self.avx512.0)) } }
312      } else {
313        let self_halfs = cast::<u16x32, [u16x16; 2]>(self);
314        let [indices_a, indices_b] = cast::<u16x32, [u16x16; 2]>(indices);
315
316        cast([self_halfs.shuffle(indices_a), self_halfs.shuffle(indices_b)])
317      }
318    }
319  }
320
321  #[inline]
322  pub fn shuffle_zeroing(self, indices: u16x32) -> Self {
323    pick! {
324      if #[cfg(all(target_feature = "avx512bw"))] {
325        self.shuffle(indices) & indices.simd_lt(32)
326      } else {
327        let self_halfs = cast::<u16x32, [u16x16; 2]>(self);
328        let [indices_a, indices_b] = cast::<u16x32, [u16x16; 2]>(indices);
329
330        cast([self_halfs.shuffle_zeroing(indices_a), self_halfs.shuffle_zeroing(indices_b)])
331      }
332    }
333  }
334
335  #[inline]
336  pub fn shuffle_wrapping(self, indices: u16x32) -> Self {
337    pick! {
338      if #[cfg(all(target_feature = "avx512bw"))] {
339        // `avx512` shuffle intrinsics are wrapping
340        self.shuffle(indices)
341      } else {
342        let self_halfs = cast::<u16x32, [u16x16; 2]>(self);
343        let [indices_a, indices_b] = cast::<u16x32, [u16x16; 2]>(indices);
344
345        cast([self_halfs.shuffle_wrapping(indices_a), self_halfs.shuffle_wrapping(indices_b)])
346      }
347    }
348  }
349
350  #[inline]
351  fn shuffle(self: [u16x32; 2], indices: u16x32) -> u16x32 {
352    pick! {
353      if #[cfg(all(target_feature = "avx512bw"))] {
354        #[cfg(target_arch = "x86")]
355        use core::arch::x86::_mm512_permutex2var_epi16;
356        #[cfg(target_arch = "x86_64")]
357        use core::arch::x86_64::_mm512_permutex2var_epi16;
358        // TODO(safe_arch): add `_mm512_permutex2var_epi16`.
359        u16x32 {
360          avx512: unsafe {
361            m512i(_mm512_permutex2var_epi16(self[0].avx512.0, indices.avx512.0, self[1].avx512.0))
362          },
363        }
364      } else {
365        self[0].shuffle_zeroing(indices) | self[1].shuffle_zeroing(indices - 32)
366      }
367    }
368  }
369
370  #[inline]
371  fn shuffle_zeroing(self: [u16x32; 2], indices: u16x32) -> u16x32 {
372    pick! {
373      if #[cfg(all(target_feature = "avx512bw", target_feature = "avx512vl"))] {
374        self.shuffle(indices) & indices.simd_lt(64)
375      } else {
376        self.shuffle(indices)
377      }
378    }
379  }
380
381  #[inline]
382  fn shuffle_wrapping(self: [u16x32; 2], indices: u16x32) -> u16x32 {
383    pick! {
384      if #[cfg(all(target_feature = "avx512bw"))] {
385        // `avx512` shuffle intrinsics are wrapping
386        self.shuffle(indices)
387      } else {
388        self.shuffle(indices & 63)
389      }
390    }
391  }
392
393  #[inline]
394  fn shuffle(self: [u16x32; 3], indices: u16x32) -> u16x32 {
395    [self[0], self[1]].shuffle_zeroing(indices) | self[2].shuffle_zeroing(indices - 64)
396  }
397
398  #[inline]
399  fn shuffle_zeroing(self: [u16x32; 3], indices: u16x32) -> u16x32 {
400    self.shuffle(indices)
401  }
402
403  #[inline]
404  fn shuffle_wrapping(self: [u16x32; 3], indices: u16x32) -> u16x32 {
405    self.shuffle(indices % 96)
406  }
407
408  #[inline]
409  fn shuffle(self: [u16x32; 4], indices: u16x32) -> u16x32 {
410    [self[0], self[1]].shuffle_zeroing(indices) | [self[2], self[3]].shuffle_zeroing(indices - 64)
411  }
412
413  #[inline]
414  fn shuffle_zeroing(self: [u16x32; 4], indices: u16x32) -> u16x32 {
415    self.shuffle(indices)
416  }
417
418  #[inline]
419  fn shuffle_wrapping(self: [u16x32; 4], indices: u16x32) -> u16x32 {
420    self.shuffle(indices & 127)
421  }
422
423  ///
424  /// Currently this function is never accelerated.
425  #[inline]
426  pub fn transpose(data: [Self; 32]) -> [Self; 32] {
427    // Can this be optimized?
428
429    #[inline(always)]
430    fn transpose_column(data: &[u16x32; 32], index: usize) -> u16x32 {
431      u16x32::new([
432        data[0].as_array()[index],
433        data[1].as_array()[index],
434        data[2].as_array()[index],
435        data[3].as_array()[index],
436        data[4].as_array()[index],
437        data[5].as_array()[index],
438        data[6].as_array()[index],
439        data[7].as_array()[index],
440        data[8].as_array()[index],
441        data[9].as_array()[index],
442        data[10].as_array()[index],
443        data[11].as_array()[index],
444        data[12].as_array()[index],
445        data[13].as_array()[index],
446        data[14].as_array()[index],
447        data[15].as_array()[index],
448        data[16].as_array()[index],
449        data[17].as_array()[index],
450        data[18].as_array()[index],
451        data[19].as_array()[index],
452        data[20].as_array()[index],
453        data[21].as_array()[index],
454        data[22].as_array()[index],
455        data[23].as_array()[index],
456        data[24].as_array()[index],
457        data[25].as_array()[index],
458        data[26].as_array()[index],
459        data[27].as_array()[index],
460        data[28].as_array()[index],
461        data[29].as_array()[index],
462        data[30].as_array()[index],
463        data[31].as_array()[index],
464      ])
465    }
466
467    [
468      transpose_column(&data, 0),
469      transpose_column(&data, 1),
470      transpose_column(&data, 2),
471      transpose_column(&data, 3),
472      transpose_column(&data, 4),
473      transpose_column(&data, 5),
474      transpose_column(&data, 6),
475      transpose_column(&data, 7),
476      transpose_column(&data, 8),
477      transpose_column(&data, 9),
478      transpose_column(&data, 10),
479      transpose_column(&data, 11),
480      transpose_column(&data, 12),
481      transpose_column(&data, 13),
482      transpose_column(&data, 14),
483      transpose_column(&data, 15),
484      transpose_column(&data, 16),
485      transpose_column(&data, 17),
486      transpose_column(&data, 18),
487      transpose_column(&data, 19),
488      transpose_column(&data, 20),
489      transpose_column(&data, 21),
490      transpose_column(&data, 22),
491      transpose_column(&data, 23),
492      transpose_column(&data, 24),
493      transpose_column(&data, 25),
494      transpose_column(&data, 26),
495      transpose_column(&data, 27),
496      transpose_column(&data, 28),
497      transpose_column(&data, 29),
498      transpose_column(&data, 30),
499      transpose_column(&data, 31),
500    ]
501  }
502
503  #[inline]
504  fn shl(self, rhs: Self) -> Self::Output {
505    pick! {
506      if #[cfg(target_feature="avx512bw")] {
507        // Mask `rhs` to 15 to match `wrapping_shl`.
508        let rhs = bitand_m512i(rhs.avx512, set_splat_i16_m512i(15));
509        Self { avx512: shl_each_u16_m512i(self.avx512, rhs) }
510      } else {
511        let [self_a, self_b]: [u16x16; 2] = cast(self);
512        let [rhs_a, rhs_b]: [u16x16; 2] = cast(rhs);
513
514        cast([self_a << rhs_a, self_b << rhs_b])
515      }
516    }
517  }
518
519  #[inline]
520  fn shl(self, rhs: u32) -> Self::Output {
521    pick! {
522      if #[cfg(target_feature="avx512bw")] {
523        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
524        #[expect(clippy::suspicious_arithmetic_impl)]
525        let shift = rhs as u16 & 15;
526        Self { avx512: shl_all_u16_m512i(self.avx512, shift) }
527      } else {
528        Self {
529          a : self.a.shl(rhs),
530          b : self.b.shl(rhs),
531        }
532      }
533    }
534  }
535
536  #[inline]
537  fn shr(self, rhs: Self) -> Self::Output {
538    pick! {
539      if #[cfg(target_feature="avx512bw")] {
540        // Mask `rhs` to 15 to match `wrapping_shr`.
541        let rhs = bitand_m512i(rhs.avx512, set_splat_i16_m512i(15));
542        Self { avx512: shr_each_u16_m512i(self.avx512, rhs) }
543      } else {
544        let [self_a, self_b]: [u16x16; 2] = cast(self);
545        let [rhs_a, rhs_b]: [u16x16; 2] = cast(rhs);
546
547        cast([self_a >> rhs_a, self_b >> rhs_b])
548      }
549    }
550  }
551
552  #[inline]
553  fn shr(self, rhs: u32) -> Self::Output {
554    pick! {
555      if #[cfg(target_feature="avx512bw")] {
556        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
557        #[expect(clippy::suspicious_arithmetic_impl)]
558        let shift = rhs as u16 & 15;
559        Self { avx512: shr_all_u16_m512i(self.avx512, shift) }
560      } else {
561        Self {
562          a : self.a.shr(rhs),
563          b : self.b.shr(rhs),
564        }
565      }
566    }
567  }
568
569  #[inline]
570  pub fn max(self, rhs: Self) -> Self {
571    pick! {
572      if #[cfg(target_feature="avx512bw")] {
573        Self { avx512: max_u16_m512i(self.avx512, rhs.avx512) }
574      } else {
575        Self {
576          a: self.a.max(rhs.a),
577          b: self.b.max(rhs.b),
578        }
579      }
580    }
581  }
582
583  #[inline]
584  pub fn min(self, rhs: Self) -> Self {
585    pick! {
586      if #[cfg(target_feature="avx512bw")] {
587        Self { avx512: min_u16_m512i(self.avx512, rhs.avx512) }
588      } else {
589        Self {
590          a: self.a.min(rhs.a),
591          b: self.b.min(rhs.b),
592        }
593      }
594    }
595  }
596
597  #[inline]
598  pub fn reduce_max(self) -> u16 {
599    let array: [u16x16; 2] = cast(self);
600    array[0].max(array[1]).reduce_max()
601  }
602
603  #[inline]
604  pub fn reduce_min(self) -> u16 {
605    let array: [u16x16; 2] = cast(self);
606    array[0].min(array[1]).reduce_min()
607  }
608
609  #[inline]
610  pub fn unbounded_shl(self, rhs: Self) -> Self {
611    pick! {
612      if #[cfg(target_feature="avx512bw")] {
613        Self { avx512: shl_each_u16_m512i(self.avx512, rhs.avx512) }
614      } else {
615        let [self_a, self_b] = cast::<u16x32, [u16x16; 2]>(self);
616        let [rhs_a, rhs_b] = cast::<u16x32, [u16x16; 2]>(rhs);
617
618        cast([self_a.unbounded_shl(rhs_a), self_b.unbounded_shl(rhs_b)])
619      }
620    }
621  }
622
623  #[inline]
624  pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
625    pick! {
626      if #[cfg(target_feature="avx512bw")] {
627        // `u32 as u16` truncates the higher half so we need to manually
628        // saturate.
629        Self { avx512: shl_all_u16_m512i(self.avx512, rhs.min(u16::MAX as u32) as u16) }
630      } else {
631        Self {
632          a: self.a.unbounded_shl_scalar(rhs),
633          b: self.b.unbounded_shl_scalar(rhs),
634        }
635      }
636    }
637  }
638
639  #[inline]
640  pub fn unbounded_shr(self, rhs: Self) -> Self {
641    pick! {
642      if #[cfg(target_feature="avx512bw")] {
643        Self { avx512: shr_each_u16_m512i(self.avx512, rhs.avx512) }
644      } else {
645        let [self_a, self_b] = cast::<u16x32, [u16x16; 2]>(self);
646        let [rhs_a, rhs_b] = cast::<u16x32, [u16x16; 2]>(rhs);
647
648        cast([self_a.unbounded_shr(rhs_a), self_b.unbounded_shr(rhs_b)])
649      }
650    }
651  }
652
653  #[inline]
654  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
655    pick! {
656      if #[cfg(target_feature="avx512bw")] {
657        // `u32 as u16` truncates the higher half so we need to manually
658        // saturate.
659        Self { avx512: shr_all_u16_m512i(self.avx512, rhs.min(u16::MAX as u32) as u16) }
660      } else {
661        Self {
662          a: self.a.unbounded_shr_scalar(rhs),
663          b: self.b.unbounded_shr_scalar(rhs),
664        }
665      }
666    }
667  }
668
669  #[inline]
670  pub fn saturating_add(self, rhs: Self) -> Self {
671    pick! {
672      if #[cfg(target_feature="avx512bw")] {
673        Self { avx512: add_saturating_u16_m512i(self.avx512, rhs.avx512) }
674      } else {
675        Self {
676          a: self.a.saturating_add(rhs.a),
677          b: self.b.saturating_add(rhs.b),
678        }
679      }
680    }
681  }
682
683  #[inline]
684  pub fn saturating_sub(self, rhs: Self) -> Self {
685    pick! {
686      if #[cfg(target_feature="avx512bw")] {
687        Self { avx512: sub_saturating_u16_m512i(self.avx512, rhs.avx512) }
688      } else {
689        Self {
690          a: self.a.saturating_sub(rhs.a),
691          b: self.b.saturating_sub(rhs.b),
692        }
693      }
694    }
695  }
696
697  #[inline]
698  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
699    let (low, high) = self.mul_keep_low_high(rhs);
700    let overflow = high.simd_ne(Self::ZERO);
701    (low, overflow)
702  }
703
704  optional_fn_widening_mul {
705    // Cannot have `widening_mul` because there is no `u32x32` type.
706  }
707
708  #[inline]
709  pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
710    // x86 has no `_mm512_mul_epu16` intrinsic so there is no `avx512`
711    // optimization.
712
713    let [self_a, self_b] = cast::<u16x32, [u16x16; 2]>(self);
714    let [rhs_a, rhs_b] = cast::<u16x32, [u16x16; 2]>(rhs);
715
716    let result_a = self_a.mul_keep_low_high(rhs_a);
717    let result_b = self_b.mul_keep_low_high(rhs_b);
718    (cast([result_a.0, result_b.0]), cast([result_a.1, result_b.1]))
719  }
720
721  #[inline]
722  pub fn mul_keep_high(self, rhs: Self) -> Self {
723    // x86 has no `_mm512_mul_epu16` intrinsic so there is no `avx512`
724    // optimization.
725
726    let [self_a, self_b] = cast::<u16x32, [u16x16; 2]>(self);
727    let [rhs_a, rhs_b] = cast::<u16x32, [u16x16; 2]>(rhs);
728
729    cast([self_a.mul_keep_high(rhs_a), self_b.mul_keep_high(rhs_b)])
730  }
731
732  optional_fn_deserialize {}
733}