@@ -2682,6 +2682,140 @@ impl U16x32 {
26822682 }
26832683}
26842684
2685+ #[ cfg( not( all( target_feature = "avx" , not( target_feature = "avx2" ) ) ) ) ]
2686+ impl I32x16 {
2687+ /// Store two 256-bit halves back into a fresh array (see `avx2_halves`).
2688+ #[ inline( always) ]
2689+ fn from_avx2_halves ( lo : __m256i , hi : __m256i ) -> Self {
2690+ let mut o = [ 0i32 ; 16 ] ;
2691+ // SAFETY: two 32-byte `storeu` stores into the 64-byte local array; no alignment needed.
2692+ unsafe {
2693+ let p = o. as_mut_ptr ( ) as * mut __m256i ;
2694+ _mm256_storeu_si256 ( p, lo) ;
2695+ _mm256_storeu_si256 ( p. add ( 1 ) , hi) ;
2696+ }
2697+ Self ( o)
2698+ }
2699+
2700+ /// `SaturatingRoundingDoublingHighMul` on one 256-bit half (exactness argument: see
2701+ /// [`Self::saturating_rounding_doubling_high_mul`]).
2702+ #[ inline( always) ]
2703+ fn srdhm_half ( a : __m256i , b : __m256i ) -> __m256i {
2704+ // SAFETY: AVX2 is a run-time precondition of this arm (see `avx2_halves`); register-only.
2705+ unsafe {
2706+ let nudge = _mm256_set1_epi64x ( 1 << 30 ) ;
2707+ let even = _mm256_srli_epi64 ( _mm256_add_epi64 ( _mm256_mul_epi32 ( a, b) , nudge) , 31 ) ;
2708+ let odd = _mm256_slli_epi64 (
2709+ _mm256_add_epi64 ( _mm256_mul_epi32 ( _mm256_srli_epi64 ( a, 32 ) , _mm256_srli_epi64 ( b, 32 ) ) , nudge) ,
2710+ 1 ,
2711+ ) ;
2712+ let r = _mm256_blend_epi32 :: < 0xAA > ( even, odd) ;
2713+ let min = _mm256_set1_epi32 ( i32:: MIN ) ;
2714+ let ovf = _mm256_and_si256 ( _mm256_cmpeq_epi32 ( a, min) , _mm256_cmpeq_epi32 ( b, min) ) ;
2715+ _mm256_blendv_epi8 ( r, _mm256_set1_epi32 ( i32:: MAX ) , ovf)
2716+ }
2717+ }
2718+
2719+ /// `RoundingDivideByPOT` on one 256-bit half, per-lane exponent.
2720+ #[ inline( always) ]
2721+ fn rdpot_half ( x : __m256i , e : __m256i ) -> __m256i {
2722+ // SAFETY: AVX2 run-time precondition (see `avx2_halves`); register-only. `vpsllvd` /
2723+ // `vpsravd` define all counts, so an out-of-contract exponent is unspecified, not UB.
2724+ unsafe {
2725+ let one = _mm256_set1_epi32 ( 1 ) ;
2726+ let mask = _mm256_sub_epi32 ( _mm256_sllv_epi32 ( one, e) , one) ;
2727+ let rem = _mm256_and_si256 ( x, mask) ;
2728+ let thr = _mm256_add_epi32 ( _mm256_srli_epi32 ( mask, 1 ) , _mm256_srli_epi32 ( x, 31 ) ) ;
2729+ let q = _mm256_srav_epi32 ( x, e) ;
2730+ // `cmpgt` lanes are -1 where rem > thr, so subtracting adds 1 there.
2731+ _mm256_sub_epi32 ( q, _mm256_cmpgt_epi32 ( rem, thr) )
2732+ }
2733+ }
2734+
2735+ /// Saturating rounding doubling high multiply (gemmlowp / TFLM
2736+ /// `SaturatingRoundingDoublingHighMul<i32>`): per lane, `round(a * b / 2^31)`
2737+ /// with the half-way case rounded toward `+inf`, i.e. the C++ definition
2738+ /// `((a*b + nudge) / 2^31)` with `nudge = 1<<30` (product `>= 0`) or `1 - (1<<30)`
2739+ /// (product `< 0`) and **truncating** division. `i32::MIN * i32::MIN` saturates to
2740+ /// `i32::MAX`.
2741+ ///
2742+ /// ```
2743+ /// use ndarray::simd::I32x16;
2744+ /// let a = I32x16::from_array([3, -3, -1, 1, 0, i32::MIN, i32::MAX, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
2745+ /// let r = a.saturating_rounding_doubling_high_mul(I32x16::splat(1 << 30)).to_array();
2746+ /// // 1.5 -> 2, -1.5 -> -1 (tie toward +inf), -0.5 -> 0, 0.5 -> 1.
2747+ /// assert_eq!(r[..7], [2, -1, 0, 1, 0, -1_073_741_824, 1_073_741_824]);
2748+ /// let m = I32x16::splat(i32::MIN).saturating_rounding_doubling_high_mul(I32x16::splat(i32::MIN));
2749+ /// assert!(m.to_array().iter().all(|&x| x == i32::MAX));
2750+ /// ```
2751+ #[ inline( always) ]
2752+ pub fn saturating_rounding_doubling_high_mul ( self , b : Self ) -> Self {
2753+ // Exact for both product signs: C++ truncating division after the sign-dependent nudge
2754+ // equals the flooring shift of `a*b + 2^30` (taking bits 31..=62 as the i32), computed
2755+ // with `vpmuldq` on even lanes and on odd lanes after a 32-bit shift down; `MIN*MIN`
2756+ // (2^31, not representable) is blended to `MAX`.
2757+ let ( alo, ahi) = self . avx2_halves ( ) ;
2758+ let ( blo, bhi) = b. avx2_halves ( ) ;
2759+ Self :: from_avx2_halves ( Self :: srdhm_half ( alo, blo) , Self :: srdhm_half ( ahi, bhi) )
2760+ }
2761+
2762+ /// Rounding arithmetic right shift (gemmlowp / TFLM `RoundingDivideByPOT`) with a
2763+ /// per-lane exponent: `x / 2^e` rounded to nearest, **ties away from zero**.
2764+ /// `exponent` lanes must be in `0..=31`; other values are a contract violation
2765+ /// (the result is unspecified, but no undefined behaviour).
2766+ ///
2767+ /// ```
2768+ /// use ndarray::simd::I32x16;
2769+ /// let x = I32x16::from_array([5, -5, 3, -3, 2, -2, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0]);
2770+ /// let r = x.rounding_divide_by_pot(I32x16::splat(1)).to_array();
2771+ /// assert_eq!(r[..8], [3, -3, 2, -2, 1, -1, 1, -1]); // negative ties go away from zero
2772+ /// let e = I32x16::from_array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31]);
2773+ /// let r = I32x16::splat(-10).rounding_divide_by_pot(e).to_array();
2774+ /// assert_eq!(r[..6], [-10, -5, -3, -1, -1, 0]);
2775+ /// assert_eq!(r[15], 0);
2776+ /// let m = I32x16::splat(i32::MIN).rounding_divide_by_pot(I32x16::splat(31)).to_array();
2777+ /// assert_eq!(m[0], -1);
2778+ /// ```
2779+ #[ inline( always) ]
2780+ pub fn rounding_divide_by_pot ( self , exponent : Self ) -> Self {
2781+ let ( xlo, xhi) = self . avx2_halves ( ) ;
2782+ let ( elo, ehi) = exponent. avx2_halves ( ) ;
2783+ Self :: from_avx2_halves ( Self :: rdpot_half ( xlo, elo) , Self :: rdpot_half ( xhi, ehi) )
2784+ }
2785+
2786+ /// Per-lane fixed-point requantization (gemmlowp / TFLM `MultiplyByQuantizedMultiplier`,
2787+ /// double rounding): with `left = max(shift, 0)`, `right = max(-shift, 0)`,
2788+ /// `rounding_divide_by_pot(srdhm(x << left, multiplier), right)`.
2789+ ///
2790+ /// Contract: `multiplier >= 0`, `-31 <= shift <= 30`, and `x << left` must not
2791+ /// overflow (TFLM asserts; this wraps).
2792+ ///
2793+ /// ```
2794+ /// use ndarray::simd::I32x16;
2795+ /// let x = I32x16::from_array([100, -100, 7, -7, 7, -7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
2796+ /// let s = I32x16::from_array([0, 0, 0, 0, -1, -1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
2797+ /// let r = x.multiply_by_quantized_multiplier(I32x16::splat(1 << 30), s).to_array();
2798+ /// assert_eq!(r[..7], [50, -50, 4, -3, 2, -2, 6]);
2799+ /// ```
2800+ #[ inline( always) ]
2801+ pub fn multiply_by_quantized_multiplier ( self , multiplier : Self , shift : Self ) -> Self {
2802+ let ( xlo, xhi) = self . avx2_halves ( ) ;
2803+ let ( mlo, mhi) = multiplier. avx2_halves ( ) ;
2804+ let ( slo, shi) = shift. avx2_halves ( ) ;
2805+ // SAFETY: AVX2 run-time precondition (see `avx2_halves`); register-only (`vpsllvd` wraps,
2806+ // like the scalar `wrapping_mul(1 << left)`).
2807+ unsafe {
2808+ let zero = _mm256_setzero_si256 ( ) ;
2809+ let part = |x : __m256i , m : __m256i , s : __m256i | {
2810+ let left = _mm256_max_epi32 ( s, zero) ;
2811+ let right = _mm256_max_epi32 ( _mm256_sub_epi32 ( zero, s) , zero) ;
2812+ Self :: rdpot_half ( Self :: srdhm_half ( _mm256_sllv_epi32 ( x, left) , m) , right)
2813+ } ;
2814+ Self :: from_avx2_halves ( part ( xlo, mlo, slo) , part ( xhi, mhi, shi) )
2815+ }
2816+ }
2817+ }
2818+
26852819impl I32x16 {
26862820 /// Narrow two 16×i32 vectors to one 32×i16 vector with signed saturation: lanes 0..16
26872821 /// come from `self`, lanes 16..32 from `hi`, each clamped to `i16::MIN..=i16::MAX`.
0 commit comments