Skip to main content

wide/
i32x16_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx512f")] {
5    /// A SIMD vector with 16 elements of type [`i32`].
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 i32x16 { pub(crate) avx512: m512i }
14  } else {
15    /// A SIMD vector with 16 elements of type [`i32`].
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 i32x16 { pub(crate) a : i32x8, pub(crate) b : i32x8 }
24  }
25}
26
27impl_simd_int! {
28  unsafe {
29    T = i32,
30    N = 16,
31    Simd = i32x16,
32    UintSimd = u32x16,
33    T_BITS = 32,
34    T_BITS_MUL_2 = 64,
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 = __m512i },
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="avx512f")] {
46        Self { avx512: cmp_op_mask_i32_m512i::<{cmp_int_op!(Lt)}>(self.avx512, rhs.avx512) }
47      } else {
48        Self {
49          a : rhs.a.simd_gt(self.a),
50          b : rhs.b.simd_gt(self.b),
51        }
52      }
53    }
54  }
55
56  #[inline]
57  fn simd_gt(self, rhs: Self) -> Self::Output {
58    pick! {
59      if #[cfg(target_feature="avx512f")] {
60        Self { avx512: cmp_op_mask_i32_m512i::<{cmp_int_op!(Nle)}>(self.avx512, rhs.avx512) }
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="avx512f")] {
74        Self { avx512: cmp_op_mask_i32_m512i::<{cmp_int_op!(Le)}>(self.avx512, rhs.avx512) }
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="avx512f")] {
88        Self { avx512: cmp_op_mask_i32_m512i::<{cmp_int_op!(Nlt)}>(self.avx512, rhs.avx512) }
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: u32x16) -> Self::Output {
100    pick! {
101      if #[cfg(target_feature="avx512f")] {
102        #[cfg(target_arch = "x86")]
103        use core::arch::x86::_mm512_srav_epi32;
104        #[cfg(target_arch = "x86_64")]
105        use core::arch::x86_64::_mm512_srav_epi32;
106
107        // Mask `rhs` to 31 to match `wrapping_shr`.
108        let rhs = bitand_m512i(rhs.avx512, set_splat_i32_m512i(31));
109        // TODO(safe_arch): Add `_mm512_srav_epi32`.
110        Self { avx512: m512i(unsafe { _mm512_srav_epi32(self.avx512.0, rhs.0) }) }
111      } else {
112        Self {
113          a: self.a >> rhs.a,
114          b: self.b >> rhs.b,
115        }
116      }
117    }
118  }
119
120  #[inline]
121  fn shr(self, rhs: u32) -> Self::Output {
122    pick! {
123      if #[cfg(target_feature="avx512f")] {
124        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
125        #[expect(clippy::suspicious_arithmetic_impl)]
126        let shift = rhs & 31;
127        Self { avx512: shr_all_i32_m512i(self.avx512, 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="avx512f")] {
141        Self { avx512: max_i32_m512i(self.avx512, rhs.avx512) }
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="avx512f")] {
155        Self { avx512: min_i32_m512i(self.avx512, rhs.avx512) }
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) -> i32 {
167    let arr: [i32x8; 2] = cast(self);
168    arr[0].max(arr[1]).reduce_max()
169  }
170
171  #[inline]
172  pub fn reduce_min(self) -> i32 {
173    let arr: [i32x8; 2] = cast(self);
174    arr[0].min(arr[1]).reduce_min()
175  }
176
177  #[inline]
178  pub fn unbounded_shr(self, rhs: u32x16) -> Self {
179    pick! {
180      if #[cfg(target_feature="avx512f")] {
181        #[cfg(target_arch = "x86")]
182        use core::arch::x86::_mm512_srav_epi32;
183        #[cfg(target_arch = "x86_64")]
184        use core::arch::x86_64::_mm512_srav_epi32;
185
186        // TODO(safe_arch): Add `_mm512_srav_epi32`.
187        Self { avx512: m512i(unsafe { _mm512_srav_epi32(self.avx512.0, rhs.avx512.0) }) }
188      } else {
189        Self {
190          a: self.a.unbounded_shr(rhs.a),
191          b: self.b.unbounded_shr(rhs.b),
192        }
193      }
194    }
195  }
196
197  #[inline]
198  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
199    pick! {
200      if #[cfg(target_feature="avx512f")] {
201        Self { avx512: shr_all_i32_m512i(self.avx512, rhs) }
202      } else {
203        Self {
204          a: self.a.unbounded_shr_scalar(rhs),
205          b: self.b.unbounded_shr_scalar(rhs),
206        }
207      }
208    }
209  }
210
211  #[inline]
212  pub fn saturating_add(self, rhs: Self) -> Self {
213    pick! {
214      if #[cfg(target_feature="avx512f")] {
215        let result = self + rhs;
216        let overflow = (!(self ^ rhs) & (self ^ result)).is_negative();
217        let negative = self.is_negative();
218
219        // If overflow occurs return `MAX` if positive or `MIN` if negative.
220        overflow.select(Self::MAX ^ negative, result)
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="avx512f")] {
234        let result = self - rhs;
235        let overflow = ((self ^ rhs) & (self ^ result)).is_negative();
236        let negative = self.is_negative();
237
238        // If overflow occurs return `MAX` if positive or `MIN` if negative.
239        overflow.select(Self::MAX ^ negative, result)
240      } else {
241        Self {
242          a: self.a.saturating_sub(rhs.a),
243          b: self.b.saturating_sub(rhs.b),
244        }
245      }
246    }
247  }
248
249  #[inline]
250  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
251    let (low, high) = self.mul_keep_low_high(rhs);
252    let low = cast::<u32x16, i32x16>(low);
253
254    let overflow = high.simd_ne(low.is_negative());
255    (low, overflow)
256  }
257
258  optional_fn_widening_mul {
259    // Cannot have `widening_mul` because there is no `i64x16` type.
260  }
261
262  #[inline]
263  pub fn mul_keep_low_high(self, rhs: Self) -> (u32x16, i32x16) {
264    pick! {
265      if #[cfg(all(target_feature="avx512f", target_feature="avx512dq"))] {
266        #[cfg(target_arch = "x86")]
267        use core::arch::x86::{_mm512_unpackhi_epi64, _mm512_unpacklo_epi64};
268        #[cfg(target_arch = "x86_64")]
269        use core::arch::x86_64::{_mm512_unpackhi_epi64, _mm512_unpacklo_epi64};
270
271        let even_wide_mul = mul_i32_wide_m512i(self.avx512, rhs.avx512);
272        let odd_wide_mul = mul_i32_wide_m512i(
273          shuffle_i32_m512i::<0b_00_11_00_01>(self.avx512),
274          shuffle_i32_m512i::<0b_00_11_00_01>(rhs.avx512),
275        );
276        let ll_hh_1 = unpack_low_i32_m512i(even_wide_mul, odd_wide_mul);
277        let ll_hh_2 = unpack_high_i32_m512i(even_wide_mul, odd_wide_mul);
278        // TODO(safe_arch): Add `_mm512_unpacklo_epi64` and `_mm512_unpackhi_epi64`.
279        (
280          u32x16 {
281            avx512: m512i(unsafe { _mm512_unpacklo_epi64(ll_hh_1.0, ll_hh_2.0) }),
282          },
283          i32x16 {
284            avx512: m512i(unsafe { _mm512_unpackhi_epi64(ll_hh_1.0, ll_hh_2.0) }),
285          },
286        )
287      } else {
288        let [self_a, self_b] = cast::<i32x16, [i32x8; 2]>(self);
289        let [rhs_a, rhs_b] = cast::<i32x16, [i32x8; 2]>(rhs);
290
291        let result_a = self_a.mul_keep_low_high(rhs_a);
292        let result_b = self_b.mul_keep_low_high(rhs_b);
293        (
294          cast([result_a.0, result_b.0]),
295          cast([result_a.1, result_b.1]),
296        )
297      }
298    }
299  }
300
301  #[inline]
302  pub fn mul_keep_high(self, rhs: Self) -> Self {
303    pick! {
304      if #[cfg(all(target_feature="avx512f", target_feature="avx512dq"))] {
305        #[cfg(target_arch = "x86")]
306        use core::arch::x86::_mm512_unpackhi_epi64;
307        #[cfg(target_arch = "x86_64")]
308        use core::arch::x86_64::_mm512_unpackhi_epi64;
309
310        let even_wide_mul = mul_i32_wide_m512i(self.avx512, rhs.avx512);
311        let odd_wide_mul = mul_i32_wide_m512i(
312          shuffle_i32_m512i::<0b_00_11_00_01>(self.avx512),
313          shuffle_i32_m512i::<0b_00_11_00_01>(rhs.avx512),
314        );
315        let ll_hh_1 = unpack_low_i32_m512i(even_wide_mul, odd_wide_mul);
316        let ll_hh_2 = unpack_high_i32_m512i(even_wide_mul, odd_wide_mul);
317        // TODO(safe_arch): Add `_mm512_unpackhi_epi64`.
318        Self {
319          avx512: m512i(unsafe { _mm512_unpackhi_epi64(ll_hh_1.0, ll_hh_2.0) }),
320        }
321      } else {
322        let [self_a, self_b] = cast::<i32x16, [i32x8; 2]>(self);
323        let [rhs_a, rhs_b] = cast::<i32x16, [i32x8; 2]>(rhs);
324
325        cast([self_a.mul_keep_high(rhs_a), self_b.mul_keep_high(rhs_b)])
326      }
327    }
328  }
329
330  #[inline]
331  pub fn abs(self) -> Self {
332    pick! {
333      if #[cfg(target_feature="avx512bw")] {
334        Self { avx512: abs_i32_m512i(self.avx512) }
335      } else {
336        Self {
337          a : self.a.abs(),
338          b : self.b.abs(),
339        }
340      }
341    }
342  }
343
344  #[inline]
345  pub fn is_positive(self) -> Self {
346    pick! {
347      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
348        // `neon` has dedicated greater-than-zero intrinsics.
349        Self {
350          a: self.a.is_positive(),
351          b: self.b.is_positive(),
352        }
353      } else {
354        self.simd_gt(Self::ZERO)
355      }
356    }
357  }
358
359  #[inline]
360  pub fn is_negative(self) -> Self {
361    pick! {
362      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
363        // `neon` has dedicated less-than-zero intrinsics.
364        Self {
365          a: self.a.is_negative(),
366          b: self.b.is_negative(),
367        }
368      } else {
369        self.simd_lt(Self::ZERO)
370      }
371    }
372  }
373
374  optional_fn_deserialize {}
375}
376
377impl From<i16x16> for i32x16 {
378  #[inline]
379  fn from(value: i16x16) -> Self {
380    i32x16::from_i16x16(value)
381  }
382}
383
384/// The following functionality exists only for [`i32x16`], or only for
385/// particular types inconsistently.
386impl i32x16 {
387  /// Converts each element from [`i16`] to [`i32`].
388  #[inline]
389  #[must_use]
390  pub fn from_i16x16(v: i16x16) -> Self {
391    pick! {
392      if #[cfg(target_feature="avx512f")] {
393        Self { avx512: convert_to_i32_m512i_from_i16_m256i(v.avx2) }
394      } else {
395        let [a, b]: [i16x8; 2] = cast(v);
396        Self {
397          a: i32x8::from_i16x8(a),
398          b: i32x8::from_i16x8(b),
399        }
400      }
401    }
402  }
403
404  /// Converts each element from [`i32`] to [`f32`].
405  #[inline]
406  #[must_use]
407  pub fn round_float(self) -> f32x16 {
408    pick! {
409      if #[cfg(target_feature="avx512f")] {
410        cast(convert_to_m512_from_i32_m512i(self.avx512))
411      } else {
412        f32x16 {
413          a: self.a.round_float(),
414          b: self.b.round_float(),
415        }
416      }
417    }
418  }
419}