Skip to main content

wide/
i16x32_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx512bw")] {
5    /// A SIMD vector with 32 elements of type [`i16`].
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 i16x32 { pub(crate) avx512: m512i }
14  } else {
15    /// A SIMD vector with 32 elements of type [`i16`].
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 i16x32 { pub(crate) a : i16x16, pub(crate) b : i16x16 }
24  }
25}
26
27impl_simd_int! {
28  unsafe {
29    T = i16,
30    N = 32,
31    Simd = i16x32,
32    UintSimd = u16x32,
33    T_BITS = 16,
34    T_BITS_MUL_2 = 32,
35    BitmaskType = u32,
36    [
37      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
38      21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
39    ],
40    optional_type_x86_inner { X86Inner = __m512i },
41    optional_type_arm_inner {},
42    optional_type_wasm_inner {},
43  }
44
45  #[inline]
46  fn simd_lt(self, rhs: Self) -> Self::Output {
47    pick! {
48      if #[cfg(target_feature="avx512bw")] {
49        Self { avx512: cmp_op_mask_i16_m512i::<{cmp_int_op!(Lt)}>(self.avx512, rhs.avx512) }
50      } else {
51        Self {
52          a : rhs.a.simd_gt(self.a),
53          b : rhs.b.simd_gt(self.b),
54        }
55      }
56    }
57  }
58
59  #[inline]
60  fn simd_gt(self, rhs: Self) -> Self::Output {
61    pick! {
62      if #[cfg(target_feature="avx512bw")] {
63        Self { avx512: cmp_op_mask_i16_m512i::<{cmp_int_op!(Nle)}>(self.avx512, rhs.avx512) }
64      } else {
65        Self {
66          a : self.a.simd_gt(rhs.a),
67          b : self.b.simd_gt(rhs.b),
68        }
69      }
70    }
71  }
72
73  #[inline]
74  fn simd_le(self, rhs: Self) -> Self::Output {
75    pick! {
76      if #[cfg(target_feature="avx512bw")] {
77        Self { avx512: cmp_op_mask_i16_m512i::<{cmp_int_op!(Le)}>(self.avx512, rhs.avx512) }
78      } else {
79        Self {
80          a : self.a.simd_le(rhs.a),
81          b : self.b.simd_le(rhs.b),
82        }
83      }
84    }
85  }
86
87  #[inline]
88  fn simd_ge(self, rhs: Self) -> Self::Output {
89    pick! {
90      if #[cfg(target_feature="avx512bw")] {
91        Self { avx512: cmp_op_mask_i16_m512i::<{cmp_int_op!(Nlt)}>(self.avx512, rhs.avx512) }
92      } else {
93        Self {
94          a : self.a.simd_ge(rhs.a),
95          b : self.b.simd_ge(rhs.b),
96        }
97      }
98    }
99  }
100
101  #[inline]
102  fn shr(self, rhs: u16x32) -> Self::Output {
103    pick! {
104      if #[cfg(target_feature="avx512bw")] {
105        #[cfg(target_arch = "x86")]
106        use core::arch::x86::_mm512_srav_epi16;
107        #[cfg(target_arch = "x86_64")]
108        use core::arch::x86_64::_mm512_srav_epi16;
109
110        // Mask `rhs` to 15 to match `wrapping_shr`.
111        let rhs = bitand_m512i(rhs.avx512, set_splat_i16_m512i(15));
112        // TODO(safe_arch): Add `_mm512_srav_epi16`.
113        Self { avx512: m512i(unsafe { _mm512_srav_epi16(self.avx512.0, rhs.0) }) }
114      } else {
115        let [self_a, self_b]: [i16x16; 2] = cast(self);
116        let [rhs_a, rhs_b]: [u16x16; 2] = cast(rhs);
117
118        cast([self_a >> rhs_a, self_b >> rhs_b])
119      }
120    }
121  }
122
123  #[inline]
124  fn shr(self, rhs: u32) -> Self::Output {
125    pick! {
126      if #[cfg(target_feature="avx512bw")] {
127        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
128        #[expect(clippy::suspicious_arithmetic_impl)]
129        let shift = rhs as u16 & 15;
130        Self { avx512: shr_all_i16_m512i(self.avx512, shift) }
131      } else {
132        Self {
133          a : self.a.shr(rhs),
134          b : self.b.shr(rhs),
135        }
136      }
137    }
138  }
139
140  #[inline]
141  pub fn max(self, rhs: Self) -> Self {
142    pick! {
143      if #[cfg(target_feature="avx512bw")] {
144        Self { avx512: max_i16_m512i(self.avx512, rhs.avx512) }
145      } else {
146        Self {
147          a: self.a.max(rhs.a),
148          b: self.b.max(rhs.b),
149        }
150      }
151    }
152  }
153
154  #[inline]
155  pub fn min(self, rhs: Self) -> Self {
156    pick! {
157      if #[cfg(target_feature="avx512bw")] {
158        Self { avx512: min_i16_m512i(self.avx512, rhs.avx512) }
159      } else {
160        Self {
161          a: self.a.min(rhs.a),
162          b: self.b.min(rhs.b),
163        }
164      }
165    }
166  }
167
168  #[inline]
169  pub fn reduce_max(self) -> i16 {
170    let arr: [i16x16; 2] = cast(self);
171    arr[0].max(arr[1]).reduce_max()
172  }
173
174  #[inline]
175  pub fn reduce_min(self) -> i16 {
176    let arr: [i16x16; 2] = cast(self);
177    arr[0].min(arr[1]).reduce_min()
178  }
179
180  #[inline]
181  pub fn unbounded_shr(self, rhs: u16x32) -> Self {
182    pick! {
183      if #[cfg(target_feature="avx512bw")] {
184        #[cfg(target_arch = "x86")]
185        use core::arch::x86::_mm512_srav_epi16;
186        #[cfg(target_arch = "x86_64")]
187        use core::arch::x86_64::_mm512_srav_epi16;
188
189        // TODO(safe_arch): Add `_mm512_srav_epi16`.
190        Self { avx512: m512i(unsafe { _mm512_srav_epi16(self.avx512.0, rhs.avx512.0) }) }
191      } else {
192        let [self_a, self_b] = cast::<i16x32, [i16x16; 2]>(self);
193        let [rhs_a, rhs_b] = cast::<u16x32, [u16x16; 2]>(rhs);
194
195        cast([self_a.unbounded_shr(rhs_a), self_b.unbounded_shr(rhs_b)])
196      }
197    }
198  }
199
200  #[inline]
201  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
202    pick! {
203      if #[cfg(target_feature="avx512bw")] {
204        // `u32 as u16` truncates the higher half so we need to manually
205        // saturate.
206        Self { avx512: shr_all_i16_m512i(self.avx512, rhs.min(u16::MAX as u32) as u16) }
207      } else {
208        Self {
209          a: self.a.unbounded_shr_scalar(rhs),
210          b: self.b.unbounded_shr_scalar(rhs),
211        }
212      }
213    }
214  }
215
216  #[inline]
217  pub fn saturating_add(self, rhs: Self) -> Self {
218    pick! {
219      if #[cfg(target_feature="avx512bw")] {
220        Self { avx512: add_saturating_i16_m512i(self.avx512, rhs.avx512) }
221      } else {
222        Self {
223          a: self.a.saturating_add(rhs.a),
224          b: self.b.saturating_add(rhs.b),
225        }
226      }
227    }
228  }
229
230  #[inline]
231  pub fn saturating_sub(self, rhs: Self) -> Self {
232    pick! {
233      if #[cfg(target_feature="avx512bw")] {
234        Self { avx512: sub_saturating_i16_m512i(self.avx512, rhs.avx512) }
235      } else {
236        Self {
237          a: self.a.saturating_sub(rhs.a),
238          b: self.b.saturating_sub(rhs.b),
239        }
240      }
241    }
242  }
243
244  #[inline]
245  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
246    let (low, high) = self.mul_keep_low_high(rhs);
247    let low = cast::<u16x32, i16x32>(low);
248
249    let overflow = high.simd_ne(low.is_negative());
250    (low, overflow)
251  }
252
253  optional_fn_widening_mul {
254    // Cannot have `widening_mul` because there is no `i32x32` type.
255  }
256
257  #[inline]
258  pub fn mul_keep_low_high(self, rhs: Self) -> (u16x32, i16x32) {
259    // x86 has no `_mm512_mul_epi16` intrinsic so there is no `avx512`
260    // optimization.
261
262    let [self_a, self_b] = cast::<i16x32, [i16x16; 2]>(self);
263    let [rhs_a, rhs_b] = cast::<i16x32, [i16x16; 2]>(rhs);
264
265    let result_a = self_a.mul_keep_low_high(rhs_a);
266    let result_b = self_b.mul_keep_low_high(rhs_b);
267    (cast([result_a.0, result_b.0]), cast([result_a.1, result_b.1]))
268  }
269
270  #[inline]
271  pub fn mul_keep_high(self, rhs: Self) -> Self {
272    // x86 has no `_mm512_mul_epi16` intrinsic so there is no `avx512`
273    // optimization.
274
275    let [self_a, self_b] = cast::<i16x32, [i16x16; 2]>(self);
276    let [rhs_a, rhs_b] = cast::<i16x32, [i16x16; 2]>(rhs);
277
278    cast([self_a.mul_keep_high(rhs_a), self_b.mul_keep_high(rhs_b)])
279  }
280
281  #[inline]
282  pub fn abs(self) -> Self {
283    pick! {
284      if #[cfg(target_feature="avx512bw")] {
285        Self { avx512: abs_i16_m512i(self.avx512) }
286      } else {
287        Self {
288          a : self.a.abs(),
289          b : self.b.abs(),
290        }
291      }
292    }
293  }
294
295  #[inline]
296  pub fn is_positive(self) -> Self {
297    pick! {
298      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
299        // `neon` has dedicated greater-than-zero intrinsics.
300        Self {
301          a: self.a.is_positive(),
302          b: self.b.is_positive(),
303        }
304      } else {
305        self.simd_gt(Self::ZERO)
306      }
307    }
308  }
309
310  #[inline]
311  pub fn is_negative(self) -> Self {
312    pick! {
313      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
314        // `neon` has dedicated less-than-zero intrinsics.
315        Self {
316          a: self.a.is_negative(),
317          b: self.b.is_negative(),
318        }
319      } else {
320        self.simd_lt(Self::ZERO)
321      }
322    }
323  }
324
325  optional_fn_deserialize {}
326}
327
328impl From<i8x32> for i16x32 {
329  /// widen with sign extend from i8 to i16
330  #[inline]
331  fn from(i: i8x32) -> Self {
332    i16x32::from_i8x32(i)
333  }
334}
335
336/// The following functionality exists only for [`i16x32`], or only for
337/// particular types inconsistently.
338impl i16x32 {
339  /// Converts each element from [`i8`] to [`i16`].
340  #[inline]
341  #[must_use]
342  pub fn from_i8x32(v: i8x32) -> Self {
343    pick! {
344      if #[cfg(target_feature="avx512bw")] {
345        Self { avx512: convert_to_i16_m512i_from_i8_m256i(cast(v)) }
346      } else {
347        let [a, b]: [i8x16; 2] = cast(v);
348        Self {
349          a: i16x16::from_i8x16(a),
350          b: i16x16::from_i8x16(b),
351        }
352      }
353    }
354  }
355
356  /// Partially computes the dot product.
357  ///
358  /// First this multiplies the input 16-bit integers, producing intermediate
359  /// 32-bit integers. Then this horizontally adds adjacent pairs, resulting in
360  /// sixteen 32-bit integers.
361  #[inline]
362  #[must_use]
363  pub fn dot(self, rhs: Self) -> i32x16 {
364    pick! {
365      if #[cfg(target_feature="avx512bw")] {
366        i32x16 { avx512: mul_i16_horizontal_add_m512i(self.avx512, rhs.avx512) }
367      } else {
368        i32x16 {
369          a : self.a.dot(rhs.a),
370          b : self.b.dot(rhs.b),
371        }
372      }
373    }
374  }
375}