Skip to main content

wide/
i16x16_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx2")] {
5    /// A SIMD vector with 16 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(32))]
13    pub struct i16x16 { pub(crate) avx2: m256i }
14  } else {
15    /// A SIMD vector with 16 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(32))]
23    pub struct i16x16 { pub(crate) a : i16x8, pub(crate) b : i16x8 }
24  }
25}
26
27impl_simd_int! {
28  unsafe {
29    T = i16,
30    N = 16,
31    Simd = i16x16,
32    UintSimd = u16x16,
33    T_BITS = 16,
34    T_BITS_MUL_2 = 32,
35    BitmaskType = u32,
36    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
37    optional_type_x86_inner { X86Inner = __m256i },
38    optional_type_arm_inner {},
39    optional_type_wasm_inner {},
40  }
41
42  #[inline]
43  fn simd_lt(self, rhs: Self) -> Self::Output {
44    pick! {
45      if #[cfg(target_feature="avx2")] {
46        Self { avx2: !cmp_gt_mask_i16_m256i(self.avx2, rhs.avx2) ^ cmp_eq_mask_i16_m256i(self.avx2,rhs.avx2) }
47      } else {
48        Self {
49          a : self.a.simd_lt(rhs.a),
50          b : self.b.simd_lt(rhs.b),
51        }
52      }
53    }
54  }
55
56  #[inline]
57  fn simd_gt(self, rhs: Self) -> Self::Output {
58    pick! {
59      if #[cfg(target_feature="avx2")] {
60        Self { avx2: cmp_gt_mask_i16_m256i(self.avx2, rhs.avx2) }
61      } else {
62        Self {
63          a : self.a.simd_gt(rhs.a),
64          b : self.b.simd_gt(rhs.b),
65        }
66      }
67    }
68  }
69
70  #[inline]
71  fn simd_le(self, rhs: Self) -> Self::Output {
72    pick! {
73      if #[cfg(target_feature="avx2")] {
74        !self.simd_gt(rhs)
75      } else {
76        Self {
77          a : self.a.simd_le(rhs.a),
78          b : self.b.simd_le(rhs.b),
79        }
80      }
81    }
82  }
83
84  #[inline]
85  fn simd_ge(self, rhs: Self) -> Self::Output {
86    pick! {
87      if #[cfg(target_feature="avx2")] {
88        !self.simd_lt(rhs)
89      } else {
90        Self {
91          a : self.a.simd_ge(rhs.a),
92          b : self.b.simd_ge(rhs.b),
93        }
94      }
95    }
96  }
97
98  #[inline]
99  fn shr(self, rhs: u16x16) -> Self::Output {
100    pick! {
101      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
102        #[cfg(target_arch = "x86")]
103        use core::arch::x86::_mm256_srav_epi16;
104        #[cfg(target_arch = "x86_64")]
105        use core::arch::x86_64::_mm256_srav_epi16;
106
107        // Mask `rhs` to 15 to match `wrapping_shr`.
108        let rhs = bitand_m256i(rhs.avx2, set_splat_i16_m256i(15));
109        // TODO(safe_arch): Add `_mm256_srav_epi16`.
110        cast(unsafe { _mm256_srav_epi16(self.avx2.0, rhs.0) })
111      } else {
112        let [self_a, self_b]: [i16x8; 2] = cast(self);
113        let [rhs_a, rhs_b]: [u16x8; 2] = cast(rhs);
114
115        cast([self_a >> rhs_a, self_b >> rhs_b])
116      }
117    }
118  }
119
120  #[inline]
121  fn shr(self, rhs: u32) -> Self::Output {
122    pick! {
123      if #[cfg(target_feature="avx2")] {
124        // Use `rhs % 16` to perform wrapping shift and not unbounded shift.
125        #[expect(clippy::suspicious_arithmetic_impl)]
126        let shift = cast([rhs as u64 & 15, 0]);
127        Self { avx2: shr_all_i16_m256i(self.avx2, shift) }
128      } else {
129        Self {
130          a : self.a.shr(rhs),
131          b : self.b.shr(rhs),
132        }
133      }
134    }
135  }
136
137  #[inline]
138  pub fn max(self, rhs: Self) -> Self {
139    pick! {
140      if #[cfg(target_feature="avx2")] {
141        Self { avx2: max_i16_m256i(self.avx2, rhs.avx2) }
142      } else {
143        Self {
144          a : self.a.max(rhs.a),
145          b : self.b.max(rhs.b),
146        }
147      }
148    }
149  }
150
151  #[inline]
152  pub fn min(self, rhs: Self) -> Self {
153    pick! {
154      if #[cfg(target_feature="avx2")] {
155        Self { avx2: min_i16_m256i(self.avx2, rhs.avx2) }
156      } else {
157        Self {
158          a : self.a.min(rhs.a),
159          b : self.b.min(rhs.b),
160        }
161      }
162    }
163  }
164
165  #[inline]
166  pub fn reduce_max(self) -> i16 {
167    let arr: [i16x8; 2] = cast(self);
168
169    arr[0].max(arr[1]).reduce_max()
170  }
171
172  #[inline]
173  pub fn reduce_min(self) -> i16 {
174    let arr: [i16x8; 2] = cast(self);
175
176    arr[0].min(arr[1]).reduce_min()
177  }
178
179  #[inline]
180  pub fn unbounded_shr(self, rhs: u16x16) -> Self {
181    pick! {
182      if #[cfg(all(target_feature="avx512bw", target_feature="avx512vl"))] {
183        #[cfg(target_arch = "x86")]
184        use core::arch::x86::_mm256_srav_epi16;
185        #[cfg(target_arch = "x86_64")]
186        use core::arch::x86_64::_mm256_srav_epi16;
187
188        // TODO(safe_arch): Add `_mm256_srav_epi16`.
189        cast(unsafe { _mm256_srav_epi16(self.avx2.0, rhs.avx2.0) })
190      } else {
191        let [self_a, self_b] = cast::<i16x16, [i16x8; 2]>(self);
192        let [rhs_a, rhs_b] = cast::<u16x16, [u16x8; 2]>(rhs);
193
194        cast([self_a.unbounded_shr(rhs_a), self_b.unbounded_shr(rhs_b)])
195      }
196    }
197  }
198
199  #[inline]
200  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
201    pick! {
202      if #[cfg(target_feature="avx2")] {
203        Self { avx2: shr_all_i16_m256i(self.avx2, cast([rhs as u64, 0])) }
204      } else {
205        Self {
206          a: self.a.unbounded_shr_scalar(rhs),
207          b: self.b.unbounded_shr_scalar(rhs),
208        }
209      }
210    }
211  }
212
213  #[inline]
214  pub fn saturating_add(self, rhs: Self) -> Self {
215    pick! {
216      if #[cfg(target_feature="avx2")] {
217        Self { avx2: add_saturating_i16_m256i(self.avx2, rhs.avx2) }
218      } else {
219        Self {
220          a : self.a.saturating_add(rhs.a),
221          b : self.b.saturating_add(rhs.b),
222        }
223      }
224    }
225  }
226
227  #[inline]
228  pub fn saturating_sub(self, rhs: Self) -> Self {
229    pick! {
230      if #[cfg(target_feature="avx2")] {
231        Self { avx2: sub_saturating_i16_m256i(self.avx2, rhs.avx2) }
232      } else {
233        Self {
234          a : self.a.saturating_sub(rhs.a),
235          b : self.b.saturating_sub(rhs.b),
236        }
237      }
238    }
239  }
240
241  #[inline]
242  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
243    let (low, high) = self.mul_keep_low_high(rhs);
244    let low = cast::<u16x16, i16x16>(low);
245
246    let overflow = high.simd_ne(low.is_negative());
247    (low, overflow)
248  }
249
250  optional_fn_widening_mul {
251    #[inline]
252    pub fn widening_mul(self, rhs: Self) -> i32x16 {
253      // x86 has no `_mm256_mul_epi16` intrinsic so there is no `avx2`
254      // optimization.
255
256      let [self_a, self_b] = cast::<i16x16, [i16x8; 2]>(self);
257      let [rhs_a, rhs_b] = cast::<i16x16, [i16x8; 2]>(rhs);
258
259      cast([self_a.widening_mul(rhs_a), self_b.widening_mul(rhs_b)])
260    }
261  }
262
263  #[inline]
264  pub fn mul_keep_low_high(self, rhs: Self) -> (u16x16, i16x16) {
265    // x86 has no `_mm256_mul_epi16` intrinsic so there is no `avx2`
266    // optimization.
267
268    let [self_a, self_b] = cast::<i16x16, [i16x8; 2]>(self);
269    let [rhs_a, rhs_b] = cast::<i16x16, [i16x8; 2]>(rhs);
270
271    let result_a = self_a.mul_keep_low_high(rhs_a);
272    let result_b = self_b.mul_keep_low_high(rhs_b);
273    (cast([result_a.0, result_b.0]), cast([result_a.1, result_b.1]))
274  }
275
276  #[inline]
277  pub fn mul_keep_high(self, rhs: Self) -> Self {
278    // x86 has no `_mm256_mul_epi16` intrinsic so there is no `avx2`
279    // optimization.
280
281    let [self_a, self_b] = cast::<i16x16, [i16x8; 2]>(self);
282    let [rhs_a, rhs_b] = cast::<i16x16, [i16x8; 2]>(rhs);
283
284    cast([self_a.mul_keep_high(rhs_a), self_b.mul_keep_high(rhs_b)])
285  }
286
287  #[inline]
288  pub fn abs(self) -> Self {
289    pick! {
290      if #[cfg(target_feature="avx2")] {
291        Self { avx2: abs_i16_m256i(self.avx2) }
292      } else {
293        Self {
294          a : self.a.abs(),
295          b : self.b.abs(),
296        }
297      }
298    }
299  }
300
301  #[inline]
302  pub fn is_positive(self) -> Self {
303    pick! {
304      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
305        // `neon` has dedicated greater-than-zero intrinsics.
306        Self {
307          a: self.a.is_positive(),
308          b: self.b.is_positive(),
309        }
310      } else {
311        self.simd_gt(Self::ZERO)
312      }
313    }
314  }
315
316  #[inline]
317  pub fn is_negative(self) -> Self {
318    pick! {
319      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
320        // `neon` has dedicated less-than-zero intrinsics.
321        Self {
322          a: self.a.is_negative(),
323          b: self.b.is_negative(),
324        }
325      } else {
326        self.simd_lt(Self::ZERO)
327      }
328    }
329  }
330
331  optional_fn_deserialize {}
332}
333
334impl From<i8x16> for i16x16 {
335  /// widen with sign extend from i8 to i16
336  #[inline]
337  fn from(i: i8x16) -> Self {
338    i16x16::from_i8x16(i)
339  }
340}
341
342impl From<u8x16> for i16x16 {
343  /// widen with zero extend from u8 to i16
344  #[inline]
345  fn from(i: u8x16) -> Self {
346    cast(u16x16::from(i))
347  }
348}
349
350/// The following functionality exists only for [`i16x16`], or only for
351/// particular types inconsistently.
352impl i16x16 {
353  /// Converts each element from [`i8`] to [`i16`].
354  #[inline]
355  #[must_use]
356  pub fn from_i8x16(v: i8x16) -> Self {
357    pick! {
358      if #[cfg(target_feature="avx2")] {
359        i16x16 { avx2:convert_to_i16_m256i_from_i8_m128i(v.sse) }
360      } else if #[cfg(target_feature="sse4.1")] {
361        i16x16 {
362          a: i16x8 { sse: convert_to_i16_m128i_from_lower8_i8_m128i(v.sse) },
363          b: i16x8 { sse: convert_to_i16_m128i_from_lower8_i8_m128i(unpack_high_i64_m128i(v.sse, v.sse)) }
364        }
365      } else if #[cfg(target_feature="sse2")] {
366        i16x16 {
367          a: i16x8 { sse: shr_imm_i16_m128i::<8>( unpack_low_i8_m128i(v.sse, v.sse)) },
368          b: i16x8 { sse: shr_imm_i16_m128i::<8>( unpack_high_i8_m128i(v.sse, v.sse)) },
369        }
370      } else {
371
372        i16x16::new([
373          v.as_array()[0] as i16,
374          v.as_array()[1] as i16,
375          v.as_array()[2] as i16,
376          v.as_array()[3] as i16,
377          v.as_array()[4] as i16,
378          v.as_array()[5] as i16,
379          v.as_array()[6] as i16,
380          v.as_array()[7] as i16,
381          v.as_array()[8] as i16,
382          v.as_array()[9] as i16,
383          v.as_array()[10] as i16,
384          v.as_array()[11] as i16,
385          v.as_array()[12] as i16,
386          v.as_array()[13] as i16,
387          v.as_array()[14] as i16,
388          v.as_array()[15] as i16,
389          ])
390      }
391    }
392  }
393
394  /// Partially computes the dot product.
395  ///
396  /// First this multiplies the input 16-bit integers, producing intermediate
397  /// 32-bit integers. Then this horizontally adds adjacent pairs, resulting in
398  /// eight 32-bit integers.
399  #[inline]
400  #[must_use]
401  pub fn dot(self, rhs: Self) -> i32x8 {
402    pick! {
403      if #[cfg(target_feature="avx2")] {
404        i32x8 { avx2:  mul_i16_horizontal_add_m256i(self.avx2, rhs.avx2) }
405      } else {
406        i32x8 {
407          a : self.a.dot(rhs.a),
408          b : self.b.dot(rhs.b),
409        }
410      }
411    }
412  }
413
414  /// Multiply and scale equivalent to `((self * rhs) + 0x4000) >> 15` on each
415  /// lane, effectively multiplying by a 16 bit fixed point number between `-1`
416  /// and `1`. This corresponds to the following instructions:
417  /// - `vqrdmulhq_n_s16` instruction on neon
418  /// - `i16x8_q15mulr_sat` on simd128
419  /// - `_mm256_mulhrs_epi16` on avx2
420  /// - emulated via `mul_i16_*` on sse2
421  #[inline]
422  #[must_use]
423  pub fn mul_scale_round(self, rhs: Self) -> Self {
424    pick! {
425      if #[cfg(target_feature="avx2")] {
426        Self { avx2: mul_i16_scale_round_m256i(self.avx2, rhs.avx2) }
427      } else {
428        Self {
429          a : self.a.mul_scale_round(rhs.a),
430          b : self.b.mul_scale_round(rhs.b),
431        }
432      }
433    }
434  }
435
436  /// Multiply and scale equivalent to `((self * rhs) + 0x4000) >> 15` on each
437  /// lane, effectively multiplying by a 16 bit fixed point number between `-1`
438  /// and `1`. This corresponds to the following instructions:
439  /// - `vqrdmulhq_n_s16` instruction on neon
440  /// - `i16x8_q15mulr_sat` on simd128
441  /// - `_mm256_mulhrs_epi16` on avx2
442  /// - emulated via `mul_i16_*` on sse2
443  #[inline]
444  #[must_use]
445  pub fn mul_scale_round_n(self, rhs: i16) -> Self {
446    pick! {
447      if #[cfg(target_feature="avx2")] {
448        Self { avx2: mul_i16_scale_round_m256i(self.avx2, set_splat_i16_m256i(rhs)) }
449      } else {
450        Self {
451          a : self.a.mul_scale_round_n(rhs),
452          b : self.b.mul_scale_round_n(rhs),
453        }
454      }
455    }
456  }
457}