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! {
28  unsafe {
29    T = u16,
30    N = 16,
31    Simd = u16x16,
32    optional_type_x86_inner { X86Inner = __m256i },
33    optional_type_arm_inner {},
34    optional_type_wasm_inner {},
35  }
36
37  #[inline]
38  fn simd_eq(self, rhs: Self) -> Self::Output {
39    pick! {
40      if #[cfg(target_feature="avx2")] {
41        Self { avx2: cmp_eq_mask_i16_m256i(self.avx2, rhs.avx2) }
42      } else {
43        Self {
44          a : self.a.simd_eq(rhs.a),
45          b : self.b.simd_eq(rhs.b),
46        }
47      }
48    }
49  }
50
51  #[inline]
52  fn simd_ne(self, rhs: Self) -> Self::Output {
53    pick! {
54      if #[cfg(target_feature="avx2")] {
55        !self.simd_eq(rhs)
56      } else {
57        Self {
58          a : self.a.simd_ne(rhs.a),
59          b : self.b.simd_ne(rhs.b),
60        }
61      }
62    }
63  }
64
65  #[inline]
66  fn simd_lt(self, rhs: Self) -> Self::Output {
67    // no gt, so just reverse to get same answer
68    Self::simd_gt(rhs, self)
69  }
70
71  #[inline]
72  fn simd_gt(self, rhs: Self) -> Self::Output {
73    pick! {
74      if #[cfg(target_feature = "avx2")] {
75        let bias = m256i::from([0x8000u16; 16]);
76        let a_biased = sub_i16_m256i(self.avx2, bias);
77        let b_biased = sub_i16_m256i(rhs.avx2, bias);
78        let mask = cmp_gt_mask_i16_m256i(a_biased, b_biased);
79
80        Self { avx2: mask }
81      } else {
82        Self {
83          a: self.a.simd_gt(rhs.a),
84          b: self.b.simd_gt(rhs.b),
85        }
86      }
87    }
88  }
89
90  #[inline]
91  fn simd_le(self, rhs: Self) -> Self::Output {
92    pick! {
93      if #[cfg(target_feature="avx2")] {
94        !self.simd_gt(rhs)
95      } else {
96        Self {
97          a : self.a.simd_le(rhs.a),
98          b : self.b.simd_le(rhs.b),
99        }
100      }
101    }
102  }
103
104  #[inline]
105  fn simd_ge(self, rhs: Self) -> Self::Output {
106    pick! {
107      if #[cfg(target_feature="avx2")] {
108        !self.simd_lt(rhs)
109      } else {
110        Self {
111          a : self.a.simd_ge(rhs.a),
112          b : self.b.simd_ge(rhs.b),
113        }
114      }
115    }
116  }
117
118  #[inline]
119  pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
120    pick! {
121      if #[cfg(target_feature="avx2")] {
122        Self {
123          avx2: bitor_m256i(
124            bitand_m256i(if_one.avx2, self.avx2),
125            bitandnot_m256i(self.avx2, if_zero.avx2),
126          ),
127        }
128      } else {
129        Self {
130          a: self.a.bitselect(if_one.a, if_zero.a),
131          b: self.b.bitselect(if_one.b, if_zero.b),
132        }
133      }
134    }
135  }
136
137  #[inline]
138  pub fn select(self, if_true: Self, if_false: Self) -> Self {
139    pick! {
140      if #[cfg(target_feature="avx2")] {
141        Self { avx2: blend_varying_i8_m256i(if_false.avx2, if_true.avx2, self.avx2) }
142      } else {
143        Self {
144          a : self.a.select(if_true.a, if_false.a),
145          b : self.b.select(if_true.b, if_false.b),
146        }
147      }
148    }
149  }
150
151  #[inline]
152  pub fn to_bitmask(self) -> u32 {
153    i16x16::to_bitmask(cast(self))
154  }
155
156  #[inline]
157  pub fn any(self) -> bool {
158    i16x16::any(cast(self))
159  }
160
161  #[inline]
162  pub fn all(self) -> bool {
163    i16x16::all(cast(self))
164  }
165
166  ///
167  /// Currently this function is never accelerated.
168  #[inline]
169  pub fn transpose(data: [u16x16; 16]) -> [u16x16; 16] {
170    cast(i16x16::transpose(cast(data)))
171  }
172}
173
174impl_simd_uint! {
175  unsafe {
176    T = u16,
177    N = 16,
178    Simd = u16x16,
179    SignedSimd = i16x16,
180    T_BITS = 16,
181    T_BITS_MUL_2 = 32,
182    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
183  }
184
185  #[inline]
186  fn not(self) -> Self {
187    pick! {
188      if #[cfg(target_feature="avx2")] {
189        Self { avx2: self.avx2.not()  }
190      } else {
191        Self {
192          a : self.a.not(),
193          b : self.b.not(),
194        }
195      }
196    }
197  }
198
199  #[inline]
200  fn add(self, rhs: Self) -> Self::Output {
201    pick! {
202      if #[cfg(target_feature="avx2")] {
203        Self { avx2: add_i16_m256i(self.avx2, rhs.avx2) }
204      } else {
205        Self {
206          a : self.a.add(rhs.a),
207          b : self.b.add(rhs.b),
208        }
209      }
210    }
211  }
212
213  #[inline]
214  fn sub(self, rhs: Self) -> Self::Output {
215    pick! {
216      if #[cfg(target_feature="avx2")] {
217        Self { avx2: sub_i16_m256i(self.avx2, rhs.avx2) }
218      } else {
219        Self {
220          a : self.a.sub(rhs.a),
221          b : self.b.sub(rhs.b),
222        }
223      }
224    }
225  }
226
227  #[inline]
228  fn mul(self, rhs: Self) -> Self::Output {
229    pick! {
230      if #[cfg(target_feature="avx2")] {
231        // non-widening multiplication is the same for unsigned and signed
232        Self { avx2: mul_i16_keep_low_m256i(self.avx2, rhs.avx2) }
233      } else {
234        Self {
235          a : self.a.mul(rhs.a),
236          b : self.b.mul(rhs.b),
237        }
238      }
239    }
240  }
241
242  #[inline]
243  fn shl(self, rhs: Self) -> Self::Output {
244    pick! {
245      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
246        #[cfg(target_arch = "x86")]
247        use core::arch::x86::_mm256_sllv_epi16;
248        #[cfg(target_arch = "x86_64")]
249        use core::arch::x86_64::_mm256_sllv_epi16;
250
251        // Mask `rhs` to 15 to match `wrapping_shl`.
252        let rhs = bitand_m256i(rhs.avx2, set_splat_i16_m256i(15));
253        // TODO(safe_arch): Add `_mm256_sllv_epi16`.
254        cast(unsafe { _mm256_sllv_epi16(self.avx2.0, rhs.0) })
255      } else {
256        let [self_a, self_b]: [u16x8; 2] = cast(self);
257        let [rhs_a, rhs_b]: [u16x8; 2] = cast(rhs);
258
259        cast([self_a << rhs_a, self_b << rhs_b])
260      }
261    }
262  }
263
264  #[inline]
265  fn shl(self, rhs: u32) -> Self::Output {
266    pick! {
267      if #[cfg(target_feature="avx2")] {
268        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
269        #[expect(clippy::suspicious_arithmetic_impl)]
270        let shift = cast([rhs as u64 & 15, 0]);
271        Self { avx2: shl_all_u16_m256i(self.avx2, shift) }
272      } else {
273        Self {
274          a : self.a.shl(rhs),
275          b : self.b.shl(rhs),
276        }
277      }
278    }
279  }
280
281  #[inline]
282  fn shr(self, rhs: Self) -> Self::Output {
283    pick! {
284      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
285        #[cfg(target_arch = "x86")]
286        use core::arch::x86::_mm256_srlv_epi16;
287        #[cfg(target_arch = "x86_64")]
288        use core::arch::x86_64::_mm256_srlv_epi16;
289
290        // Mask `rhs` to 15 to match `wrapping_shr`.
291        let rhs = bitand_m256i(rhs.avx2, set_splat_i16_m256i(15));
292        // TODO(safe_arch): Add `_mm256_srlv_epi16`.
293        cast(unsafe { _mm256_srlv_epi16(self.avx2.0, rhs.0) })
294      } else {
295        let [self_a, self_b]: [u16x8; 2] = cast(self);
296        let [rhs_a, rhs_b]: [u16x8; 2] = cast(rhs);
297
298        cast([self_a >> rhs_a, self_b >> rhs_b])
299      }
300    }
301  }
302
303  #[inline]
304  fn shr(self, rhs: u32) -> Self::Output {
305    pick! {
306      if #[cfg(target_feature="avx2")] {
307        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
308        #[expect(clippy::suspicious_arithmetic_impl)]
309        let shift = cast([rhs as u64 & 15, 0]);
310        Self { avx2: shr_all_u16_m256i(self.avx2, shift) }
311      } else {
312        Self {
313          a : self.a.shr(rhs),
314          b : self.b.shr(rhs),
315        }
316      }
317    }
318  }
319
320  #[inline]
321  fn bitand(self, rhs: Self) -> Self::Output {
322    pick! {
323      if #[cfg(target_feature="avx2")] {
324        Self { avx2: bitand_m256i(self.avx2, rhs.avx2) }
325      } else {
326        Self {
327          a : self.a.bitand(rhs.a),
328          b : self.b.bitand(rhs.b),
329        }
330      }
331    }
332  }
333
334  #[inline]
335  fn bitor(self, rhs: Self) -> Self::Output {
336    pick! {
337      if #[cfg(target_feature="avx2")] {
338        Self { avx2: bitor_m256i(self.avx2, rhs.avx2) }
339      } else {
340        Self {
341          a : self.a.bitor(rhs.a),
342          b : self.b.bitor(rhs.b),
343        }
344      }
345    }
346  }
347
348  #[inline]
349  fn bitxor(self, rhs: Self) -> Self::Output {
350    pick! {
351      if #[cfg(target_feature="avx2")] {
352        Self { avx2: bitxor_m256i(self.avx2, rhs.avx2) }
353      } else {
354        Self {
355          a : self.a.bitxor(rhs.a),
356          b : self.b.bitxor(rhs.b),
357        }
358      }
359    }
360  }
361
362  #[inline]
363  pub fn max(self, rhs: Self) -> Self {
364    pick! {
365      if #[cfg(target_feature="avx2")] {
366        Self { avx2: max_u16_m256i(self.avx2, rhs.avx2) }
367      } else {
368        Self {
369          a : self.a.max(rhs.a),
370          b : self.b.max(rhs.b),
371        }
372      }
373    }
374  }
375
376  #[inline]
377  pub fn min(self, rhs: Self) -> Self {
378    pick! {
379      if #[cfg(target_feature="avx2")] {
380        Self { avx2: min_u16_m256i(self.avx2, rhs.avx2) }
381      } else {
382        Self {
383          a : self.a.min(rhs.a),
384          b : self.b.min(rhs.b),
385        }
386      }
387    }
388  }
389
390  #[inline]
391  pub fn reduce_add(self) -> u16 {
392    let array: [u16x8; 2] = cast(self);
393    (array[0] + array[1]).reduce_add()
394  }
395
396  #[inline]
397  pub fn reduce_mul(self) -> u16 {
398    let array: [u16x8; 2] = cast(self);
399    (array[0] * array[1]).reduce_mul()
400  }
401
402  #[inline]
403  pub fn reduce_max(self) -> u16 {
404    let array: [u16x8; 2] = cast(self);
405    array[0].max(array[1]).reduce_max()
406  }
407
408  #[inline]
409  pub fn reduce_min(self) -> u16 {
410    let array: [u16x8; 2] = cast(self);
411    array[0].min(array[1]).reduce_min()
412  }
413
414  #[inline]
415  pub fn unbounded_shl(self, rhs: Self) -> Self {
416    pick! {
417      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
418        #[cfg(target_arch = "x86")]
419        use core::arch::x86::_mm256_sllv_epi16;
420        #[cfg(target_arch = "x86_64")]
421        use core::arch::x86_64::_mm256_sllv_epi16;
422
423        // TODO(safe_arch): Add `_mm256_sllv_epi16`.
424        cast(unsafe { _mm256_sllv_epi16(self.avx2.0, rhs.avx2.0) })
425      } else {
426        let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
427        let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
428
429        cast([self_a.unbounded_shl(rhs_a), self_b.unbounded_shl(rhs_b)])
430      }
431    }
432  }
433
434  #[inline]
435  pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
436    pick! {
437      if #[cfg(target_feature="avx2")] {
438        Self { avx2: shl_all_u16_m256i(self.avx2, cast([rhs as u64, 0])) }
439      } else {
440        Self {
441          a: self.a.unbounded_shl_scalar(rhs),
442          b: self.b.unbounded_shl_scalar(rhs),
443        }
444      }
445    }
446  }
447
448  #[inline]
449  pub fn unbounded_shr(self, rhs: Self) -> Self {
450    pick! {
451      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
452        #[cfg(target_arch = "x86")]
453        use core::arch::x86::_mm256_srlv_epi16;
454        #[cfg(target_arch = "x86_64")]
455        use core::arch::x86_64::_mm256_srlv_epi16;
456
457        // TODO(safe_arch): Add `_mm256_srlv_epi16`.
458        cast(unsafe { _mm256_srlv_epi16(self.avx2.0, rhs.avx2.0) })
459      } else {
460        let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
461        let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
462
463        cast([self_a.unbounded_shr(rhs_a), self_b.unbounded_shr(rhs_b)])
464      }
465    }
466  }
467
468  #[inline]
469  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
470    pick! {
471      if #[cfg(target_feature="avx2")] {
472        Self { avx2: shr_all_u16_m256i(self.avx2, cast([rhs as u64, 0])) }
473      } else {
474        Self {
475          a: self.a.unbounded_shr_scalar(rhs),
476          b: self.b.unbounded_shr_scalar(rhs),
477        }
478      }
479    }
480  }
481
482  #[inline]
483  pub fn saturating_add(self, rhs: Self) -> Self {
484    pick! {
485      if #[cfg(target_feature="avx2")] {
486        Self { avx2: add_saturating_u16_m256i(self.avx2, rhs.avx2) }
487      } else {
488        Self {
489          a : self.a.saturating_add(rhs.a),
490          b : self.b.saturating_add(rhs.b),
491        }
492      }
493    }
494  }
495
496  #[inline]
497  pub fn saturating_sub(self, rhs: Self) -> Self {
498    pick! {
499      if #[cfg(target_feature="avx2")] {
500        Self { avx2: sub_saturating_u16_m256i(self.avx2, rhs.avx2) }
501      } else {
502        Self {
503          a : self.a.saturating_sub(rhs.a),
504          b : self.b.saturating_sub(rhs.b),
505        }
506      }
507    }
508  }
509
510  #[inline]
511  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
512    let (low, high) = self.mul_keep_low_high(rhs);
513    let overflow = high.simd_ne(Self::ZERO);
514    (low, overflow)
515  }
516
517  optional_fn_widening_mul {
518    #[inline]
519    pub fn widening_mul(self, rhs: Self) -> u32x16 {
520      // x86 has no `_mm256_mul_epu16` intrinsic so there is no `avx2`
521      // optimization.
522
523      let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
524      let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
525
526      cast([self_a.widening_mul(rhs_a), self_b.widening_mul(rhs_b)])
527    }
528  }
529
530  #[inline]
531  pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
532    // x86 has no `_mm256_mul_epu16` intrinsic so there is no `avx2`
533    // optimization.
534
535    let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
536    let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
537
538    let result_a = self_a.mul_keep_low_high(rhs_a);
539    let result_b = self_b.mul_keep_low_high(rhs_b);
540    (cast([result_a.0, result_b.0]), cast([result_a.1, result_b.1]))
541  }
542
543  #[inline]
544  pub fn mul_keep_high(self, rhs: Self) -> Self {
545    // x86 has no `_mm256_mul_epu16` intrinsic so there is no `avx2`
546    // optimization.
547
548    let [self_a, self_b] = cast::<u16x16, [u16x8; 2]>(self);
549    let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
550
551    cast([self_a.mul_keep_high(rhs_a), self_b.mul_keep_high(rhs_b)])
552  }
553}
554
555impl From<u8x16> for u16x16 {
556  /// widens and sign extends to u16x16
557  #[inline]
558  fn from(v: u8x16) -> Self {
559    pick! {
560      if #[cfg(target_feature="avx2")] {
561        u16x16 { avx2:convert_to_i16_m256i_from_u8_m128i(v.sse) }
562      } else if #[cfg(target_feature="sse2")] {
563        u16x16 {
564          a: u16x8 { sse: shr_imm_u16_m128i::<8>( unpack_low_i8_m128i(v.sse, v.sse)) },
565          b: u16x8 { sse: shr_imm_u16_m128i::<8>( unpack_high_i8_m128i(v.sse, v.sse)) },
566        }
567      } else {
568
569        u16x16::new([
570          v.as_array()[0] as u16,
571          v.as_array()[1] as u16,
572          v.as_array()[2] as u16,
573          v.as_array()[3] as u16,
574          v.as_array()[4] as u16,
575          v.as_array()[5] as u16,
576          v.as_array()[6] as u16,
577          v.as_array()[7] as u16,
578          v.as_array()[8] as u16,
579          v.as_array()[9] as u16,
580          v.as_array()[10] as u16,
581          v.as_array()[11] as u16,
582          v.as_array()[12] as u16,
583          v.as_array()[13] as u16,
584          v.as_array()[14] as u16,
585          v.as_array()[15] as u16,
586          ])
587      }
588    }
589  }
590}