use super::lanes::Simd;
use core::arch::x86_64::{
__m256i, _mm256_add_epi16, _mm256_add_epi32, _mm256_adds_epi16, _mm256_alignr_epi8,
_mm256_and_si256, _mm256_blendv_epi8, _mm256_castsi256_si128, _mm256_cvtepi16_epi32,
_mm256_extracti128_si256, _mm256_loadu_si256, _mm256_max_epi16, _mm256_max_epi32,
_mm256_min_epi16, _mm256_min_epi32, _mm256_or_si256, _mm256_permute2x128_si256,
_mm256_set1_epi16, _mm256_set1_epi32, _mm256_srai_epi32, _mm256_srli_epi32, _mm256_srli_si256,
_mm256_storeu_si256, _mm256_sub_epi16, _mm256_sub_epi32, _mm256_subs_epi16, _mm256_xor_si256,
};
const NEG_INF_I16: i16 = i16::MIN + 1024;
const NEG_INF_I32: i32 = i32::MIN + 1024;
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn or_si(a: __m256i, b: __m256i) -> __m256i {
_mm256_or_si256(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn slli_si<const N: i32>(a: __m256i) -> __m256i {
let spill = _mm256_permute2x128_si256::<0x08>(a, a);
match N {
0 => a,
2 => _mm256_alignr_epi8::<14>(a, spill),
4 => _mm256_alignr_epi8::<12>(a, spill),
8 => _mm256_alignr_epi8::<8>(a, spill),
16 => spill,
_ => unreachable!("slli_si N out of the ladder's expected set {{0, 2, 4, 8, 16}}"),
}
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn srli_si<const N: i32>(a: __m256i) -> __m256i {
let hi_to_lo = _mm256_permute2x128_si256::<0x81>(a, a);
match N {
0 => a,
2 => _mm256_alignr_epi8::<2>(hi_to_lo, a),
4 => _mm256_alignr_epi8::<4>(hi_to_lo, a),
8 => _mm256_alignr_epi8::<8>(hi_to_lo, a),
16 => hi_to_lo,
28 => _mm256_srli_si256::<12>(hi_to_lo), 30 => _mm256_srli_si256::<14>(hi_to_lo), _ => unreachable!("srli_si N out of the expected set {{0, 2, 4, 8, 16, 28, 30}}"),
}
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn loadu(src: *const u8) -> __m256i {
_mm256_loadu_si256(src.cast::<__m256i>())
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn storeu(dst: *mut u8, v: __m256i) {
_mm256_storeu_si256(dst.cast::<__m256i>(), v);
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn store_widen_i16(dst: *mut i32, v: __m256i) {
let lo = _mm256_cvtepi16_epi32(_mm256_castsi256_si128(v));
let hi = _mm256_cvtepi16_epi32(_mm256_extracti128_si256::<1>(v));
_mm256_storeu_si256(dst.cast::<__m256i>(), lo);
_mm256_storeu_si256(dst.add(8).cast::<__m256i>(), hi);
}
pub(crate) struct Avx2I16;
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn add16(a: __m256i, b: __m256i) -> __m256i {
_mm256_add_epi16(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn sub16(a: __m256i, b: __m256i) -> __m256i {
_mm256_sub_epi16(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn adds16(a: __m256i, b: __m256i) -> __m256i {
_mm256_adds_epi16(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn subs16(a: __m256i, b: __m256i) -> __m256i {
_mm256_subs_epi16(a, b)
}
#[allow(dead_code)]
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn min16(a: __m256i, b: __m256i) -> __m256i {
_mm256_min_epi16(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn max16(a: __m256i, b: __m256i) -> __m256i {
_mm256_max_epi16(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn set1_16(value: i16) -> __m256i {
_mm256_set1_epi16(value)
}
impl Simd for Avx2I16 {
type Elem = i16;
type Vec = __m256i;
const LANES: usize = 16;
const LOG_LANES: u32 = 4;
const LSS: i32 = size_of::<i16>() as i32; const RSS: i32 = 32 - size_of::<i16>() as i32; const NEG_INF: i16 = NEG_INF_I16;
#[inline(always)]
fn splat(value: i16) -> __m256i {
unsafe { set1_16(value) }
}
#[inline(always)]
fn add(a: __m256i, b: __m256i) -> __m256i {
unsafe { add16(a, b) }
}
#[inline(always)]
fn sub(a: __m256i, b: __m256i) -> __m256i {
unsafe { sub16(a, b) }
}
#[inline(always)]
fn adds(a: __m256i, b: __m256i) -> __m256i {
unsafe { adds16(a, b) }
}
#[inline(always)]
fn subs(a: __m256i, b: __m256i) -> __m256i {
unsafe { subs16(a, b) }
}
#[inline(always)]
fn min(a: __m256i, b: __m256i) -> __m256i {
unsafe { min16(a, b) }
}
#[inline(always)]
fn max(a: __m256i, b: __m256i) -> __m256i {
unsafe { max16(a, b) }
}
#[inline(always)]
fn or(a: __m256i, b: __m256i) -> __m256i {
unsafe { or_si(a, b) }
}
#[inline(always)]
fn loadu(src: &[i16]) -> __m256i {
debug_assert!(src.len() >= Self::LANES);
unsafe { loadu(src.as_ptr().cast::<u8>()) }
}
#[inline(always)]
fn storeu(v: __m256i, dst: &mut [i16]) {
debug_assert!(dst.len() >= Self::LANES);
unsafe { storeu(dst.as_mut_ptr().cast::<u8>(), v) }
}
#[inline(always)]
fn store_widened_i32(v: __m256i, dst: &mut [i32]) {
debug_assert!(dst.len() >= Self::LANES);
unsafe { store_widen_i16(dst.as_mut_ptr(), v) }
}
#[inline(always)]
fn slli<const N: i32>(v: __m256i) -> __m256i {
unsafe { slli_si::<N>(v) }
}
#[inline(always)]
fn srli<const N: i32>(v: __m256i) -> __m256i {
unsafe { srli_si::<N>(v) }
}
#[inline(always)]
fn slli_one_lane(v: __m256i) -> __m256i {
unsafe { slli_si::<2>(v) }
}
#[inline(always)]
fn srli_top_lane(v: __m256i) -> __m256i {
unsafe { srli_si::<30>(v) }
}
#[inline(always)]
fn horizontal_max(v: __m256i) -> i16 {
let mut lanes = [0i16; 16];
Self::storeu(v, &mut lanes);
lanes.iter().fold(0i16, |acc, &x| acc.max(x))
}
#[inline(always)]
fn prefix_max(v: __m256i, penalties: &[__m256i], masks: &[__m256i]) -> __m256i {
debug_assert!(penalties.len() >= Self::LOG_LANES as usize);
debug_assert!(masks.len() >= Self::LOG_LANES as usize);
let mut a = v;
a = Self::prefix_max_step::<2>(a, penalties[0], masks[0]);
a = Self::prefix_max_step::<4>(a, penalties[1], masks[1]);
a = Self::prefix_max_step::<8>(a, penalties[2], masks[2]);
a = Self::prefix_max_step::<16>(a, penalties[3], masks[3]);
a
}
}
pub(crate) struct Avx2I32;
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn add32(a: __m256i, b: __m256i) -> __m256i {
_mm256_add_epi32(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn sub32(a: __m256i, b: __m256i) -> __m256i {
_mm256_sub_epi32(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn saturation_target32(a: __m256i) -> __m256i {
_mm256_add_epi32(_mm256_srli_epi32::<31>(a), _mm256_set1_epi32(i32::MAX))
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn adds32(a: __m256i, b: __m256i) -> __m256i {
let r = _mm256_add_epi32(a, b);
let sat = saturation_target32(a);
let overflow = _mm256_and_si256(_mm256_xor_si256(a, r), _mm256_xor_si256(b, r));
let overflow_mask = _mm256_srai_epi32::<31>(overflow);
_mm256_blendv_epi8(r, sat, overflow_mask)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn subs32(a: __m256i, b: __m256i) -> __m256i {
let r = _mm256_sub_epi32(a, b);
let sat = saturation_target32(a);
let overflow = _mm256_and_si256(_mm256_xor_si256(a, b), _mm256_xor_si256(a, r));
let overflow_mask = _mm256_srai_epi32::<31>(overflow);
_mm256_blendv_epi8(r, sat, overflow_mask)
}
#[allow(dead_code)]
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn min32(a: __m256i, b: __m256i) -> __m256i {
_mm256_min_epi32(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn max32(a: __m256i, b: __m256i) -> __m256i {
_mm256_max_epi32(a, b)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn set1_32(value: i32) -> __m256i {
_mm256_set1_epi32(value)
}
impl Simd for Avx2I32 {
type Elem = i32;
type Vec = __m256i;
const LANES: usize = 8;
const LOG_LANES: u32 = 3;
const LSS: i32 = size_of::<i32>() as i32; const RSS: i32 = 32 - size_of::<i32>() as i32; const NEG_INF: i32 = NEG_INF_I32;
#[inline(always)]
fn splat(value: i32) -> __m256i {
unsafe { set1_32(value) }
}
#[inline(always)]
fn add(a: __m256i, b: __m256i) -> __m256i {
unsafe { add32(a, b) }
}
#[inline(always)]
fn sub(a: __m256i, b: __m256i) -> __m256i {
unsafe { sub32(a, b) }
}
#[inline(always)]
fn adds(a: __m256i, b: __m256i) -> __m256i {
unsafe { adds32(a, b) }
}
#[inline(always)]
fn subs(a: __m256i, b: __m256i) -> __m256i {
unsafe { subs32(a, b) }
}
#[inline(always)]
fn min(a: __m256i, b: __m256i) -> __m256i {
unsafe { min32(a, b) }
}
#[inline(always)]
fn max(a: __m256i, b: __m256i) -> __m256i {
unsafe { max32(a, b) }
}
#[inline(always)]
fn or(a: __m256i, b: __m256i) -> __m256i {
unsafe { or_si(a, b) }
}
#[inline(always)]
fn loadu(src: &[i32]) -> __m256i {
debug_assert!(src.len() >= Self::LANES);
unsafe { loadu(src.as_ptr().cast::<u8>()) }
}
#[inline(always)]
fn storeu(v: __m256i, dst: &mut [i32]) {
debug_assert!(dst.len() >= Self::LANES);
unsafe { storeu(dst.as_mut_ptr().cast::<u8>(), v) }
}
#[inline(always)]
fn store_widened_i32(v: __m256i, dst: &mut [i32]) {
debug_assert!(dst.len() >= Self::LANES);
unsafe { storeu(dst.as_mut_ptr().cast::<u8>(), v) }
}
#[inline(always)]
fn slli<const N: i32>(v: __m256i) -> __m256i {
unsafe { slli_si::<N>(v) }
}
#[inline(always)]
fn srli<const N: i32>(v: __m256i) -> __m256i {
unsafe { srli_si::<N>(v) }
}
#[inline(always)]
fn slli_one_lane(v: __m256i) -> __m256i {
unsafe { slli_si::<4>(v) }
}
#[inline(always)]
fn srli_top_lane(v: __m256i) -> __m256i {
unsafe { srli_si::<28>(v) }
}
#[inline(always)]
fn horizontal_max(v: __m256i) -> i32 {
let mut lanes = [0i32; 8];
Self::storeu(v, &mut lanes);
lanes.iter().fold(0i32, |acc, &x| acc.max(x))
}
#[inline(always)]
fn prefix_max(v: __m256i, penalties: &[__m256i], masks: &[__m256i]) -> __m256i {
debug_assert!(penalties.len() >= Self::LOG_LANES as usize);
debug_assert!(masks.len() >= Self::LOG_LANES as usize);
let mut a = v;
a = Self::prefix_max_step::<4>(a, penalties[0], masks[0]);
a = Self::prefix_max_step::<8>(a, penalties[1], masks[1]);
a = Self::prefix_max_step::<16>(a, penalties[2], masks[2]);
a
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::align::simd::profile::{build_masks, build_penalties};
fn avx2_available() -> bool {
is_x86_feature_detected!("avx2")
}
fn unpack16(v: __m256i) -> [i16; 16] {
let mut out = [0i16; 16];
Avx2I16::storeu(v, &mut out);
out
}
fn unpack32(v: __m256i) -> [i32; 8] {
let mut out = [0i32; 8];
Avx2I32::storeu(v, &mut out);
out
}
fn scalar_prefix_max(a: &[i32], penalty: i32) -> Vec<i32> {
a.iter()
.enumerate()
.map(|(j, _)| {
a.iter()
.take(j + 1)
.enumerate()
.map(|(k, &ak)| ak + (j - k) as i32 * penalty)
.max()
.unwrap()
})
.collect()
}
fn shift_left_i16(a: &[i16; 16], nbytes: usize) -> [i16; 16] {
let sh = nbytes / 2;
let mut out = [0i16; 16];
for (i, &x) in a.iter().enumerate() {
if i + sh < 16 {
out[i + sh] = x;
}
}
out
}
fn shift_right_i16(a: &[i16; 16], nbytes: usize) -> [i16; 16] {
let sh = nbytes / 2;
let mut out = [0i16; 16];
for (i, &x) in a.iter().enumerate() {
if i >= sh {
out[i - sh] = x;
}
}
out
}
fn shift_left_i32(a: &[i32; 8], nbytes: usize) -> [i32; 8] {
let sh = nbytes / 4;
let mut out = [0i32; 8];
for (i, &x) in a.iter().enumerate() {
if i + sh < 8 {
out[i + sh] = x;
}
}
out
}
fn shift_right_i32(a: &[i32; 8], nbytes: usize) -> [i32; 8] {
let sh = nbytes / 4;
let mut out = [0i32; 8];
for (i, &x) in a.iter().enumerate() {
if i >= sh {
out[i - sh] = x;
}
}
out
}
#[test]
fn i16_ops_match_scalar_reference() {
if !avx2_available() {
return;
}
let a = [3i16, -2, 5, 1, 0, 7, -4, 2, 9, -8, 11, -6, 13, -1, 15, -3];
let b = [1i16, 1, -3, 4, -5, 2, 6, -1, 2, 2, -7, 8, -9, 3, 4, -2];
let va = Avx2I16::loadu(&a);
let vb = Avx2I16::loadu(&b);
assert_eq!(unpack16(Avx2I16::splat(-9)), [-9i16; 16]);
let mut exp_add = [0i16; 16];
let mut exp_sub = [0i16; 16];
let mut exp_min = [0i16; 16];
let mut exp_max = [0i16; 16];
let mut exp_or = [0i16; 16];
for (i, (&ai, &bi)) in a.iter().zip(b.iter()).enumerate() {
exp_add[i] = ai.wrapping_add(bi);
exp_sub[i] = ai.wrapping_sub(bi);
exp_min[i] = ai.min(bi);
exp_max[i] = ai.max(bi);
exp_or[i] = ai | bi;
}
assert_eq!(unpack16(Avx2I16::add(va, vb)), exp_add);
assert_eq!(unpack16(Avx2I16::sub(va, vb)), exp_sub);
assert_eq!(unpack16(Avx2I16::min(va, vb)), exp_min);
assert_eq!(unpack16(Avx2I16::max(va, vb)), exp_max);
assert_eq!(unpack16(Avx2I16::or(va, vb)), exp_or);
}
#[test]
fn i16_add_sub_are_non_saturating() {
if !avx2_available() {
return;
}
let hi = Avx2I16::splat(i16::MAX);
let lo = Avx2I16::splat(i16::MIN);
let one = Avx2I16::splat(1);
assert_eq!(unpack16(Avx2I16::add(hi, one)), [i16::MIN; 16]);
assert_eq!(unpack16(Avx2I16::sub(lo, one)), [i16::MAX; 16]);
}
#[test]
fn i16_adds_subs_saturate_at_bounds() {
if !avx2_available() {
return;
}
let hi = Avx2I16::splat(i16::MAX);
let lo = Avx2I16::splat(i16::MIN);
let one = Avx2I16::splat(1);
assert_eq!(unpack16(Avx2I16::adds(hi, one)), [i16::MAX; 16]);
assert_eq!(unpack16(Avx2I16::subs(lo, one)), [i16::MIN; 16]);
let a = [
3i16,
i16::MAX,
-2,
i16::MIN,
0,
i16::MAX - 1,
-100,
i16::MIN + 1,
9,
-8,
11,
-6,
13,
-1,
15,
-3,
];
let b = [1i16, 1, -3, -1, -5, 2, -50, -2, 2, 2, -7, 8, -9, 3, 4, -2];
let va = Avx2I16::loadu(&a);
let vb = Avx2I16::loadu(&b);
let mut exp_adds = [0i16; 16];
let mut exp_subs = [0i16; 16];
for (i, (&ai, &bi)) in a.iter().zip(b.iter()).enumerate() {
exp_adds[i] = ai.saturating_add(bi);
exp_subs[i] = ai.saturating_sub(bi);
}
assert_eq!(unpack16(Avx2I16::adds(va, vb)), exp_adds);
assert_eq!(unpack16(Avx2I16::subs(va, vb)), exp_subs);
}
#[test]
fn i16_loadu_storeu_round_trip() {
if !avx2_available() {
return;
}
let src = [
10i16, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
];
let v = Avx2I16::loadu(&src);
let mut dst = [0i16; 16];
Avx2I16::storeu(v, &mut dst);
assert_eq!(dst, src);
}
#[test]
fn i16_slli_srli_cross_the_128_bit_lane_boundary() {
if !avx2_available() {
return;
}
let a = [1i16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let v = Avx2I16::loadu(&a);
assert_eq!(unpack16(Avx2I16::slli::<2>(v)), shift_left_i16(&a, 2));
assert_eq!(unpack16(Avx2I16::slli::<4>(v)), shift_left_i16(&a, 4));
assert_eq!(unpack16(Avx2I16::slli::<8>(v)), shift_left_i16(&a, 8));
assert_eq!(unpack16(Avx2I16::slli::<16>(v)), shift_left_i16(&a, 16));
assert_eq!(unpack16(Avx2I16::srli::<2>(v)), shift_right_i16(&a, 2));
assert_eq!(unpack16(Avx2I16::srli::<8>(v)), shift_right_i16(&a, 8));
assert_eq!(unpack16(Avx2I16::srli::<16>(v)), shift_right_i16(&a, 16));
assert_eq!(unpack16(Avx2I16::srli::<30>(v)), shift_right_i16(&a, 30));
}
#[test]
fn i16_slli_one_lane_and_srli_top_lane_match_lss_rss() {
if !avx2_available() {
return;
}
let a = [1i16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let v = Avx2I16::loadu(&a);
assert_eq!(unpack16(Avx2I16::slli_one_lane(v)), shift_left_i16(&a, 2));
assert_eq!(unpack16(Avx2I16::srli_top_lane(v)), shift_right_i16(&a, 30));
}
#[test]
fn i16_store_widened_i32_sign_extends_all_lanes() {
if !avx2_available() {
return;
}
let src = [
-5i16,
2,
-9,
11,
i16::MIN,
i16::MAX,
0,
-1,
-4,
2,
-1,
9,
-8,
21,
-5,
7,
];
let v = Avx2I16::loadu(&src);
let mut dst = [0i32; 16];
Avx2I16::store_widened_i32(v, &mut dst);
let expected: [i32; 16] = std::array::from_fn(|k| i32::from(src[k]));
assert_eq!(dst, expected);
}
#[test]
fn i32_store_widened_i32_is_a_plain_store() {
if !avx2_available() {
return;
}
let src = [-5i32, 123_456, i32::MIN, i32::MAX, 0, -1, 7, -100_000];
let v = Avx2I32::loadu(&src);
let mut dst = [0i32; 8];
Avx2I32::store_widened_i32(v, &mut dst);
assert_eq!(dst, src);
}
#[test]
fn i16_horizontal_max_seeds_at_zero() {
if !avx2_available() {
return;
}
assert_eq!(Avx2I16::horizontal_max(Avx2I16::splat(-5)), 0);
let mixed = Avx2I16::loadu(&[
-5i16, -2, -9, -1, -3, -8, -7, -6, -4, -2, -1, -9, -8, -3, -5, -7,
]);
assert_eq!(Avx2I16::horizontal_max(mixed), 0);
let positive =
Avx2I16::loadu(&[-5i16, 2, -9, 1, -3, 8, -7, 6, -4, 2, -1, 9, -8, 21, -5, 7]);
assert_eq!(Avx2I16::horizontal_max(positive), 21);
}
#[test]
fn i16_prefix_max_matches_scalar_reference() {
if !avx2_available() {
return;
}
let penalty: i16 = -4;
let penalties = build_penalties::<Avx2I16>(penalty);
let masks = build_masks::<Avx2I16>(Avx2I16::NEG_INF);
for a in [
[3i16, -2, 5, 1, 0, 7, -4, 2, 9, -8, 11, -6, 13, -1, 15, -3],
[0i16; 16],
[
16i16, 14, 12, 10, 8, 6, 4, 2, 0, -2, -4, -6, -8, -10, -12, -14,
],
[100i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[0i16, 1, 2, 3, 4, 5, 6, 72, 8, 9, 10, 11, 12, 13, 14, 15],
] {
let v = Avx2I16::loadu(&a);
let got = unpack16(Avx2I16::prefix_max(v, &penalties, &masks));
let a_i32: Vec<i32> = a.iter().map(|&x| i32::from(x)).collect();
let expected: Vec<i16> = scalar_prefix_max(&a_i32, i32::from(penalty))
.into_iter()
.map(|x| x as i16)
.collect();
assert_eq!(got.to_vec(), expected, "prefix_max i16 mismatch for {a:?}");
}
}
#[test]
fn i32_ops_match_scalar_reference() {
if !avx2_available() {
return;
}
let a = [3i32, -2, 500, -1, 9, -8, 700, -6];
let b = [1i32, 1, -300, 4, -5, 2, 600, -1];
let va = Avx2I32::loadu(&a);
let vb = Avx2I32::loadu(&b);
assert_eq!(unpack32(Avx2I32::splat(-9)), [-9i32; 8]);
let mut exp_add = [0i32; 8];
let mut exp_sub = [0i32; 8];
let mut exp_min = [0i32; 8];
let mut exp_max = [0i32; 8];
let mut exp_or = [0i32; 8];
for (i, (&ai, &bi)) in a.iter().zip(b.iter()).enumerate() {
exp_add[i] = ai.wrapping_add(bi);
exp_sub[i] = ai.wrapping_sub(bi);
exp_min[i] = ai.min(bi);
exp_max[i] = ai.max(bi);
exp_or[i] = ai | bi;
}
assert_eq!(unpack32(Avx2I32::add(va, vb)), exp_add);
assert_eq!(unpack32(Avx2I32::sub(va, vb)), exp_sub);
assert_eq!(unpack32(Avx2I32::min(va, vb)), exp_min);
assert_eq!(unpack32(Avx2I32::max(va, vb)), exp_max);
assert_eq!(unpack32(Avx2I32::or(va, vb)), exp_or);
}
#[test]
fn i32_add_sub_are_non_saturating() {
if !avx2_available() {
return;
}
let hi = Avx2I32::splat(i32::MAX);
let lo = Avx2I32::splat(i32::MIN);
let one = Avx2I32::splat(1);
assert_eq!(unpack32(Avx2I32::add(hi, one)), [i32::MIN; 8]);
assert_eq!(unpack32(Avx2I32::sub(lo, one)), [i32::MAX; 8]);
}
#[test]
fn i32_adds_subs_saturate_at_bounds() {
if !avx2_available() {
return;
}
let hi = Avx2I32::splat(i32::MAX);
let lo = Avx2I32::splat(i32::MIN);
let one = Avx2I32::splat(1);
assert_eq!(unpack32(Avx2I32::adds(hi, one)), [i32::MAX; 8]);
assert_eq!(unpack32(Avx2I32::subs(lo, one)), [i32::MIN; 8]);
let neg = Avx2I32::NEG_INF;
let a = [3i32, i32::MAX, neg, -100, 9, -8, 700, -6];
let b = [1i32, 1, -128, -50, -5, 2, 600, -1];
let va = Avx2I32::loadu(&a);
let vb = Avx2I32::loadu(&b);
let mut exp_adds = [0i32; 8];
let mut exp_subs = [0i32; 8];
for (i, (&ai, &bi)) in a.iter().zip(b.iter()).enumerate() {
exp_adds[i] = ai.saturating_add(bi);
exp_subs[i] = ai.saturating_sub(bi);
}
assert_eq!(unpack32(Avx2I32::adds(va, vb)), exp_adds);
assert_eq!(unpack32(Avx2I32::subs(va, vb)), exp_subs);
let mut state: u32 = 0x9E37_79B9;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state as i32
};
for _ in 0..10_000 {
let a: [i32; 8] = std::array::from_fn(|_| next());
let b: [i32; 8] = std::array::from_fn(|_| next());
let va = Avx2I32::loadu(&a);
let vb = Avx2I32::loadu(&b);
let exp_adds: [i32; 8] = std::array::from_fn(|i| a[i].saturating_add(b[i]));
let exp_subs: [i32; 8] = std::array::from_fn(|i| a[i].saturating_sub(b[i]));
assert_eq!(
unpack32(Avx2I32::adds(va, vb)),
exp_adds,
"adds mismatch for {a:?} + {b:?}"
);
assert_eq!(
unpack32(Avx2I32::subs(va, vb)),
exp_subs,
"subs mismatch for {a:?} - {b:?}"
);
}
}
#[test]
fn i32_loadu_storeu_round_trip() {
if !avx2_available() {
return;
}
let src = [100i32, 200, 300, 400, 500, 600, 700, 800];
let v = Avx2I32::loadu(&src);
let mut dst = [0i32; 8];
Avx2I32::storeu(v, &mut dst);
assert_eq!(dst, src);
}
#[test]
fn i32_slli_srli_cross_the_128_bit_lane_boundary() {
if !avx2_available() {
return;
}
let a = [11i32, 22, 33, 44, 55, 66, 77, 88];
let v = Avx2I32::loadu(&a);
assert_eq!(unpack32(Avx2I32::slli::<4>(v)), shift_left_i32(&a, 4));
assert_eq!(unpack32(Avx2I32::slli::<8>(v)), shift_left_i32(&a, 8));
assert_eq!(unpack32(Avx2I32::slli::<16>(v)), shift_left_i32(&a, 16));
assert_eq!(unpack32(Avx2I32::srli::<4>(v)), shift_right_i32(&a, 4));
assert_eq!(unpack32(Avx2I32::srli::<8>(v)), shift_right_i32(&a, 8));
assert_eq!(unpack32(Avx2I32::srli::<16>(v)), shift_right_i32(&a, 16));
assert_eq!(unpack32(Avx2I32::srli::<28>(v)), shift_right_i32(&a, 28));
}
#[test]
fn i32_slli_one_lane_and_srli_top_lane_match_lss_rss() {
if !avx2_available() {
return;
}
let a = [11i32, 22, 33, 44, 55, 66, 77, 88];
let v = Avx2I32::loadu(&a);
assert_eq!(unpack32(Avx2I32::slli_one_lane(v)), shift_left_i32(&a, 4));
assert_eq!(unpack32(Avx2I32::srli_top_lane(v)), shift_right_i32(&a, 28));
}
#[test]
fn i32_horizontal_max_seeds_at_zero() {
if !avx2_available() {
return;
}
assert_eq!(Avx2I32::horizontal_max(Avx2I32::splat(-5)), 0);
let mixed = Avx2I32::loadu(&[-5i32, -2, -9, -1, -4, -8, -7, -6]);
assert_eq!(Avx2I32::horizontal_max(mixed), 0);
let positive = Avx2I32::loadu(&[-5i32, 42, -9, 11, -3, 8, -7, 96]);
assert_eq!(Avx2I32::horizontal_max(positive), 96);
}
#[test]
fn i32_prefix_max_matches_scalar_reference() {
if !avx2_available() {
return;
}
let penalty: i32 = -6;
let penalties = build_penalties::<Avx2I32>(penalty);
let masks = build_masks::<Avx2I32>(Avx2I32::NEG_INF);
for a in [
[3i32, -2, 5, 1, 9, -8, 7, 2],
[0i32; 8],
[16i32, 12, 8, 4, 0, -4, -8, -12],
[100i32, 1, 2, 3, 4, 5, 6, 7],
[0i32, 1, 2, 82, 4, 5, 6, 7],
] {
let v = Avx2I32::loadu(&a);
let got = unpack32(Avx2I32::prefix_max(v, &penalties, &masks)).to_vec();
let expected = scalar_prefix_max(&a, penalty);
assert_eq!(got, expected, "prefix_max i32 mismatch for {a:?}");
}
}
#[test]
fn lane_constants_match_upstream() {
assert_eq!(Avx2I16::LANES, 16);
assert_eq!(Avx2I16::LOG_LANES, 4);
assert_eq!(Avx2I16::LSS, 2);
assert_eq!(Avx2I16::RSS, 30);
assert_eq!(Avx2I16::NEG_INF, i16::MIN + 1024);
assert_eq!(Avx2I32::LANES, 8);
assert_eq!(Avx2I32::LOG_LANES, 3);
assert_eq!(Avx2I32::LSS, 4);
assert_eq!(Avx2I32::RSS, 28);
assert_eq!(Avx2I32::NEG_INF, i32::MIN + 1024);
}
}