Skip to main content

wide/
i32x8_.rs

1use super::*;
2
3pick! {
4  if #[cfg(target_feature="avx2")] {
5    /// A SIMD vector with eight 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(32))]
13    pub struct i32x8 { pub(crate) avx2: m256i }
14  } else {
15    /// A SIMD vector with eight 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(32))]
23    pub struct i32x8 { pub(crate) a : i32x4, pub(crate) b : i32x4}
24  }
25}
26
27impl_simd_int! {
28  unsafe {
29    T = i32,
30    N = 8,
31    Simd = i32x8,
32    UintSimd = u32x8,
33    T_BITS = 32,
34    T_BITS_MUL_2 = 64,
35    BitmaskType = u32,
36    [0, 1, 2, 3, 4, 5, 6, 7],
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_i32_m256i(rhs.avx2, self.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_i32_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: u32x8) -> Self::Output {
100    pick! {
101      if #[cfg(target_feature="avx2")] {
102        // ensure same behavior as scalar
103        let shift_by = bitand_m256i(rhs.avx2, set_splat_i32_m256i(31));
104        Self { avx2: shr_each_i32_m256i(self.avx2, shift_by ) }
105      } else {
106        Self {
107          a : self.a.shr(rhs.a),
108          b : self.b.shr(rhs.b),
109        }
110      }
111    }
112  }
113
114  #[inline]
115  fn shr(self, rhs: u32) -> Self::Output {
116    pick! {
117      if #[cfg(target_feature="avx2")] {
118        // Use `rhs % 32` to perform wrapping shift and not unbounded shift.
119        #[expect(clippy::suspicious_arithmetic_impl)]
120        let shift = cast([rhs as u64 & 31, 0]);
121        Self { avx2: shr_all_i32_m256i(self.avx2, shift) }
122      } else {
123        Self {
124          a : self.a.shr(rhs),
125          b : self.b.shr(rhs),
126        }
127      }
128    }
129  }
130
131  #[inline]
132  pub fn max(self, rhs: Self) -> Self {
133    pick! {
134      if #[cfg(target_feature="avx2")] {
135        Self { avx2: max_i32_m256i(self.avx2, rhs.avx2) }
136      } else {
137        Self {
138          a : self.a.max(rhs.a),
139          b : self.b.max(rhs.b),
140        }
141      }
142    }
143  }
144
145  #[inline]
146  pub fn min(self, rhs: Self) -> Self {
147    pick! {
148      if #[cfg(target_feature="avx2")] {
149        Self { avx2: min_i32_m256i(self.avx2, rhs.avx2) }
150      } else {
151        Self {
152          a : self.a.min(rhs.a),
153          b : self.b.min(rhs.b),
154        }
155      }
156    }
157  }
158
159  #[inline]
160  pub fn reduce_max(self) -> i32 {
161    let arr: [i32x4; 2] = cast(self);
162    arr[0].max(arr[1]).reduce_max()
163  }
164
165  #[inline]
166  pub fn reduce_min(self) -> i32 {
167    let arr: [i32x4; 2] = cast(self);
168    arr[0].min(arr[1]).reduce_min()
169  }
170
171  #[inline]
172  pub fn unbounded_shr(self, rhs: u32x8) -> Self {
173    pick! {
174      if #[cfg(target_feature="avx2")] {
175        Self { avx2: shr_each_i32_m256i(self.avx2, rhs.avx2) }
176      } else {
177        Self {
178          a: self.a.unbounded_shr(rhs.a),
179          b: self.b.unbounded_shr(rhs.b),
180        }
181      }
182    }
183  }
184
185  #[inline]
186  pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
187    pick! {
188      if #[cfg(target_feature="avx2")] {
189        Self { avx2: shr_all_i32_m256i(self.avx2, cast([rhs as u64, 0])) }
190      } else {
191        Self {
192          a: self.a.unbounded_shr_scalar(rhs),
193          b: self.b.unbounded_shr_scalar(rhs),
194        }
195      }
196    }
197  }
198
199  #[inline]
200  pub fn saturating_add(self, rhs: Self) -> Self {
201    pick! {
202      if #[cfg(target_feature="avx2")] {
203        let result = self + rhs;
204        let overflow = (!(self ^ rhs) & (self ^ result)).is_negative();
205        let negative = self.is_negative();
206
207        // If overflow occurs return `MAX` if positive or `MIN` if negative.
208        overflow.select(Self::MAX ^ negative, result)
209      } else {
210        Self {
211          a: self.a.saturating_add(rhs.a),
212          b: self.b.saturating_add(rhs.b),
213        }
214      }
215    }
216  }
217
218  #[inline]
219  pub fn saturating_sub(self, rhs: Self) -> Self {
220    pick! {
221      if #[cfg(target_feature="avx2")] {
222        let result = self - rhs;
223        let overflow = ((self ^ rhs) & (self ^ result)).is_negative();
224        let negative = self.is_negative();
225
226        // If overflow occurs return `MAX` if positive or `MIN` if negative.
227        overflow.select(Self::MAX ^ negative, result)
228      } else {
229        Self {
230          a: self.a.saturating_sub(rhs.a),
231          b: self.b.saturating_sub(rhs.b),
232        }
233      }
234    }
235  }
236
237  #[inline]
238  pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
239    let (low, high) = self.mul_keep_low_high(rhs);
240    let low = cast::<u32x8, i32x8>(low);
241
242    let overflow = high.simd_ne(low.is_negative());
243    (low, overflow)
244  }
245
246  optional_fn_widening_mul {
247    #[inline]
248    pub fn widening_mul(self, rhs: Self) -> i64x8 {
249      pick! {
250        if #[cfg(all(target_feature="avx512f", target_feature="avx2"))] {
251          const SHUFFLE_INDICES: m512i = i64x8::new([0, 4, 1, 5, 2, 6, 3, 7]).avx512;
252
253          let even_wide_mul = mul_i64_low_bits_m256i(self.avx2, rhs.avx2);
254          let odd_wide_mul = mul_i64_low_bits_m256i(
255            shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(self.avx2),
256            shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(rhs.avx2),
257          );
258          let even_then_odd = cast::<[m256i; 2], m512i>([even_wide_mul, odd_wide_mul]);
259          i64x8 {
260            avx512: permute_i64_m512i(SHUFFLE_INDICES, even_then_odd),
261          }
262        } else if #[cfg(target_feature="avx2")] {
263          let even_wide_mul = mul_i64_low_bits_m256i(self.avx2, rhs.avx2);
264          let odd_wide_mul = mul_i64_low_bits_m256i(
265            shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(self.avx2),
266            shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(rhs.avx2),
267          );
268          let m0145 = unpack_low_i64_m256i(even_wide_mul, odd_wide_mul);
269          let m2367 = unpack_high_i64_m256i(even_wide_mul, odd_wide_mul);
270
271          cast([
272            shuffle_abi_i128z_all_m256i::<0b_0010_0000>(m0145, m2367),
273            shuffle_abi_i128z_all_m256i::<0b_0011_0001>(m0145, m2367),
274          ])
275        } else {
276          let [self_a, self_b] = cast::<i32x8, [i32x4; 2]>(self);
277          let [rhs_a, rhs_b] = cast::<i32x8, [i32x4; 2]>(rhs);
278
279          cast([self_a.widening_mul(rhs_a), self_b.widening_mul(rhs_b)])
280        }
281      }
282    }
283  }
284
285  #[inline]
286  pub fn mul_keep_low_high(self, rhs: Self) -> (u32x8, i32x8) {
287    pick! {
288      if #[cfg(target_feature="avx2")] {
289        let even_wide_mul = mul_i64_low_bits_m256i(self.avx2, rhs.avx2);
290        let odd_wide_mul = mul_i64_low_bits_m256i(
291          shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(self.avx2),
292          shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(rhs.avx2),
293        );
294        let ll_hh_1 = unpack_low_i32_m256i(even_wide_mul, odd_wide_mul);
295        let ll_hh_2 = unpack_high_i32_m256i(even_wide_mul, odd_wide_mul);
296
297        (
298          u32x8 { avx2: unpack_low_i64_m256i(ll_hh_1, ll_hh_2) },
299          i32x8 { avx2: unpack_high_i64_m256i(ll_hh_1, ll_hh_2) },
300        )
301      } else {
302        let [self_a, self_b] = cast::<i32x8, [i32x4; 2]>(self);
303        let [rhs_a, rhs_b] = cast::<i32x8, [i32x4; 2]>(rhs);
304
305        let result_a = self_a.mul_keep_low_high(rhs_a);
306        let result_b = self_b.mul_keep_low_high(rhs_b);
307        (
308          cast([result_a.0, result_b.0]),
309          cast([result_a.1, result_b.1]),
310        )
311      }
312    }
313  }
314
315  #[inline]
316  pub fn mul_keep_high(self, rhs: Self) -> Self {
317    pick! {
318      if #[cfg(target_feature="avx2")] {
319        let even_wide_mul = mul_i64_low_bits_m256i(self.avx2, rhs.avx2);
320        let odd_wide_mul = mul_i64_low_bits_m256i(
321          shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(self.avx2),
322          shuffle_ai_i32_half_m256i::<0b_00_11_00_01>(rhs.avx2),
323        );
324        let ll_hh_1 = unpack_low_i32_m256i(even_wide_mul, odd_wide_mul);
325        let ll_hh_2 = unpack_high_i32_m256i(even_wide_mul, odd_wide_mul);
326
327        Self { avx2: unpack_high_i64_m256i(ll_hh_1, ll_hh_2) }
328      } else {
329        let [self_a, self_b] = cast::<i32x8, [i32x4; 2]>(self);
330        let [rhs_a, rhs_b] = cast::<i32x8, [i32x4; 2]>(rhs);
331
332        cast([self_a.mul_keep_high(rhs_a), self_b.mul_keep_high(rhs_b)])
333      }
334    }
335  }
336
337  #[inline]
338  pub fn abs(self) -> Self {
339    pick! {
340      if #[cfg(target_feature="avx2")] {
341        Self { avx2: abs_i32_m256i(self.avx2) }
342      } else {
343        Self {
344          a : self.a.abs(),
345          b : self.b.abs(),
346        }
347      }
348    }
349  }
350
351  #[inline]
352  pub fn is_positive(self) -> Self {
353    pick! {
354      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
355        // `neon` has dedicated greater-than-zero intrinsics.
356        Self {
357          a: self.a.is_positive(),
358          b: self.b.is_positive(),
359        }
360      } else {
361        self.simd_gt(Self::ZERO)
362      }
363    }
364  }
365
366  #[inline]
367  pub fn is_negative(self) -> Self {
368    pick! {
369      if #[cfg(all(target_feature="neon", target_arch="aarch64"))] {
370        // `neon` has dedicated less-than-zero intrinsics.
371        Self {
372          a: self.a.is_negative(),
373          b: self.b.is_negative(),
374        }
375      } else {
376        self.simd_lt(Self::ZERO)
377      }
378    }
379  }
380
381  optional_fn_deserialize {}
382}
383
384impl From<i16x8> for i32x8 {
385  #[inline]
386  fn from(value: i16x8) -> Self {
387    i32x8::from_i16x8(value)
388  }
389}
390
391/// The following functionality exists only for [`i32x8`], or only for
392/// particular types inconsistently.
393impl i32x8 {
394  /// Converts each element from [`i16`] to [`i32`].
395  #[inline]
396  #[must_use]
397  pub fn from_i16x8(v: i16x8) -> Self {
398    pick! {
399      if #[cfg(target_feature="avx2")] {
400        i32x8 { avx2:convert_to_i32_m256i_from_i16_m128i(v.sse) }
401      } else if #[cfg(target_feature="sse2")] {
402        i32x8 {
403          a: i32x4 { sse: shr_imm_i32_m128i::<16>( unpack_low_i16_m128i(v.sse, v.sse)) },
404          b: i32x4 { sse: shr_imm_i32_m128i::<16>( unpack_high_i16_m128i(v.sse, v.sse)) },
405        }
406      } else {
407        i32x8::new([
408          i32::from(v.as_array()[0]),
409          i32::from(v.as_array()[1]),
410          i32::from(v.as_array()[2]),
411          i32::from(v.as_array()[3]),
412          i32::from(v.as_array()[4]),
413          i32::from(v.as_array()[5]),
414          i32::from(v.as_array()[6]),
415          i32::from(v.as_array()[7]),
416        ])
417      }
418    }
419  }
420
421  /// Converts each element from [`u16`] to [`i32`].
422  #[inline]
423  #[must_use]
424  pub fn from_u16x8(v: u16x8) -> Self {
425    pick! {
426      if #[cfg(target_feature="avx2")] {
427        i32x8 { avx2:convert_to_i32_m256i_from_u16_m128i(v.sse) }
428      } else if #[cfg(target_feature="sse2")] {
429        i32x8 {
430          a: i32x4 { sse: shr_imm_u32_m128i::<16>( unpack_low_i16_m128i(v.sse, v.sse)) },
431          b: i32x4 { sse: shr_imm_u32_m128i::<16>( unpack_high_i16_m128i(v.sse, v.sse)) },
432        }
433      } else {
434        i32x8::new([
435          i32::from(v.as_array()[0]),
436          i32::from(v.as_array()[1]),
437          i32::from(v.as_array()[2]),
438          i32::from(v.as_array()[3]),
439          i32::from(v.as_array()[4]),
440          i32::from(v.as_array()[5]),
441          i32::from(v.as_array()[6]),
442          i32::from(v.as_array()[7]),
443        ])
444      }
445    }
446  }
447
448  /// Converts each element from [`i32`] to [`f32`].
449  #[inline]
450  #[must_use]
451  pub fn round_float(self) -> f32x8 {
452    pick! {
453      if #[cfg(target_feature="avx2")] {
454        cast(convert_to_m256_from_i32_m256i(self.avx2))
455      } else {
456        cast([
457          self.a.round_float(),
458          self.b.round_float(),
459        ])
460      }
461    }
462  }
463}