use super::lanes::Simd;
use core::arch::x86_64::{
__m128i, _mm_add_epi16, _mm_add_epi32, _mm_adds_epi16, _mm_and_si128, _mm_blendv_epi8,
_mm_cvtepi16_epi32, _mm_loadu_si128, _mm_max_epi16, _mm_max_epi32, _mm_min_epi16,
_mm_min_epi32, _mm_or_si128, _mm_set1_epi16, _mm_set1_epi32, _mm_slli_si128, _mm_srai_epi32,
_mm_srli_epi32, _mm_srli_si128, _mm_storeu_si128, _mm_sub_epi16, _mm_sub_epi32, _mm_subs_epi16,
_mm_xor_si128,
};
const NEG_INF_I16: i16 = i16::MIN + 1024;
const NEG_INF_I32: i32 = i32::MIN + 1024;
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn or_si(a: __m128i, b: __m128i) -> __m128i {
_mm_or_si128(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn slli_si<const N: i32>(v: __m128i) -> __m128i {
_mm_slli_si128::<N>(v)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn srli_si<const N: i32>(v: __m128i) -> __m128i {
_mm_srli_si128::<N>(v)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn loadu(src: *const u8) -> __m128i {
_mm_loadu_si128(src.cast::<__m128i>())
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn storeu(dst: *mut u8, v: __m128i) {
_mm_storeu_si128(dst.cast::<__m128i>(), v);
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn store_widen_i16(dst: *mut i32, v: __m128i) {
let lo = _mm_cvtepi16_epi32(v);
let hi = _mm_cvtepi16_epi32(_mm_srli_si128::<8>(v));
_mm_storeu_si128(dst.cast::<__m128i>(), lo);
_mm_storeu_si128(dst.add(4).cast::<__m128i>(), hi);
}
pub(crate) struct Sse41I16;
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn add16(a: __m128i, b: __m128i) -> __m128i {
_mm_add_epi16(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn sub16(a: __m128i, b: __m128i) -> __m128i {
_mm_sub_epi16(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn adds16(a: __m128i, b: __m128i) -> __m128i {
_mm_adds_epi16(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn subs16(a: __m128i, b: __m128i) -> __m128i {
_mm_subs_epi16(a, b)
}
#[allow(dead_code)]
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn min16(a: __m128i, b: __m128i) -> __m128i {
_mm_min_epi16(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn max16(a: __m128i, b: __m128i) -> __m128i {
_mm_max_epi16(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn set1_16(value: i16) -> __m128i {
_mm_set1_epi16(value)
}
impl Simd for Sse41I16 {
type Elem = i16;
type Vec = __m128i;
const LANES: usize = 8;
const LOG_LANES: u32 = 3;
const LSS: i32 = size_of::<i16>() as i32; const RSS: i32 = 16 - size_of::<i16>() as i32; const NEG_INF: i16 = NEG_INF_I16;
#[inline(always)]
fn splat(value: i16) -> __m128i {
unsafe { set1_16(value) }
}
#[inline(always)]
fn add(a: __m128i, b: __m128i) -> __m128i {
unsafe { add16(a, b) }
}
#[inline(always)]
fn sub(a: __m128i, b: __m128i) -> __m128i {
unsafe { sub16(a, b) }
}
#[inline(always)]
fn adds(a: __m128i, b: __m128i) -> __m128i {
unsafe { adds16(a, b) }
}
#[inline(always)]
fn subs(a: __m128i, b: __m128i) -> __m128i {
unsafe { subs16(a, b) }
}
#[inline(always)]
fn min(a: __m128i, b: __m128i) -> __m128i {
unsafe { min16(a, b) }
}
#[inline(always)]
fn max(a: __m128i, b: __m128i) -> __m128i {
unsafe { max16(a, b) }
}
#[inline(always)]
fn or(a: __m128i, b: __m128i) -> __m128i {
unsafe { or_si(a, b) }
}
#[inline(always)]
fn loadu(src: &[i16]) -> __m128i {
debug_assert!(src.len() >= Self::LANES);
unsafe { loadu(src.as_ptr().cast::<u8>()) }
}
#[inline(always)]
fn storeu(v: __m128i, 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: __m128i, 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: __m128i) -> __m128i {
unsafe { slli_si::<N>(v) }
}
#[inline(always)]
fn srli<const N: i32>(v: __m128i) -> __m128i {
unsafe { srli_si::<N>(v) }
}
#[inline(always)]
fn slli_one_lane(v: __m128i) -> __m128i {
unsafe { slli_si::<2>(v) }
}
#[inline(always)]
fn srli_top_lane(v: __m128i) -> __m128i {
unsafe { srli_si::<14>(v) }
}
#[inline(always)]
fn horizontal_max(v: __m128i) -> i16 {
let mut lanes = [0i16; 8];
Self::storeu(v, &mut lanes);
lanes.iter().fold(0i16, |acc, &x| acc.max(x))
}
#[inline(always)]
fn prefix_max(v: __m128i, penalties: &[__m128i], masks: &[__m128i]) -> __m128i {
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
}
}
pub(crate) struct Sse41I32;
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn add32(a: __m128i, b: __m128i) -> __m128i {
_mm_add_epi32(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn sub32(a: __m128i, b: __m128i) -> __m128i {
_mm_sub_epi32(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn saturation_target32(a: __m128i) -> __m128i {
_mm_add_epi32(_mm_srli_epi32::<31>(a), _mm_set1_epi32(i32::MAX))
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn adds32(a: __m128i, b: __m128i) -> __m128i {
let r = _mm_add_epi32(a, b);
let sat = saturation_target32(a);
let overflow = _mm_and_si128(_mm_xor_si128(a, r), _mm_xor_si128(b, r));
let overflow_mask = _mm_srai_epi32::<31>(overflow);
_mm_blendv_epi8(r, sat, overflow_mask)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn subs32(a: __m128i, b: __m128i) -> __m128i {
let r = _mm_sub_epi32(a, b);
let sat = saturation_target32(a);
let overflow = _mm_and_si128(_mm_xor_si128(a, b), _mm_xor_si128(a, r));
let overflow_mask = _mm_srai_epi32::<31>(overflow);
_mm_blendv_epi8(r, sat, overflow_mask)
}
#[allow(dead_code)]
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn min32(a: __m128i, b: __m128i) -> __m128i {
_mm_min_epi32(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn max32(a: __m128i, b: __m128i) -> __m128i {
_mm_max_epi32(a, b)
}
#[target_feature(enable = "sse4.1")]
#[inline]
unsafe fn set1_32(value: i32) -> __m128i {
_mm_set1_epi32(value)
}
impl Simd for Sse41I32 {
type Elem = i32;
type Vec = __m128i;
const LANES: usize = 4;
const LOG_LANES: u32 = 2;
const LSS: i32 = size_of::<i32>() as i32; const RSS: i32 = 16 - size_of::<i32>() as i32; const NEG_INF: i32 = NEG_INF_I32;
#[inline(always)]
fn splat(value: i32) -> __m128i {
unsafe { set1_32(value) }
}
#[inline(always)]
fn add(a: __m128i, b: __m128i) -> __m128i {
unsafe { add32(a, b) }
}
#[inline(always)]
fn sub(a: __m128i, b: __m128i) -> __m128i {
unsafe { sub32(a, b) }
}
#[inline(always)]
fn adds(a: __m128i, b: __m128i) -> __m128i {
unsafe { adds32(a, b) }
}
#[inline(always)]
fn subs(a: __m128i, b: __m128i) -> __m128i {
unsafe { subs32(a, b) }
}
#[inline(always)]
fn min(a: __m128i, b: __m128i) -> __m128i {
unsafe { min32(a, b) }
}
#[inline(always)]
fn max(a: __m128i, b: __m128i) -> __m128i {
unsafe { max32(a, b) }
}
#[inline(always)]
fn or(a: __m128i, b: __m128i) -> __m128i {
unsafe { or_si(a, b) }
}
#[inline(always)]
fn loadu(src: &[i32]) -> __m128i {
debug_assert!(src.len() >= Self::LANES);
unsafe { loadu(src.as_ptr().cast::<u8>()) }
}
#[inline(always)]
fn storeu(v: __m128i, 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: __m128i, 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: __m128i) -> __m128i {
unsafe { slli_si::<N>(v) }
}
#[inline(always)]
fn srli<const N: i32>(v: __m128i) -> __m128i {
unsafe { srli_si::<N>(v) }
}
#[inline(always)]
fn slli_one_lane(v: __m128i) -> __m128i {
unsafe { slli_si::<4>(v) }
}
#[inline(always)]
fn srli_top_lane(v: __m128i) -> __m128i {
unsafe { srli_si::<12>(v) }
}
#[inline(always)]
fn horizontal_max(v: __m128i) -> i32 {
let mut lanes = [0i32; 4];
Self::storeu(v, &mut lanes);
lanes.iter().fold(0i32, |acc, &x| acc.max(x))
}
#[inline(always)]
fn prefix_max(v: __m128i, penalties: &[__m128i], masks: &[__m128i]) -> __m128i {
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
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::align::simd::profile::{build_masks, build_penalties};
fn unpack16(v: __m128i) -> [i16; 8] {
let mut out = [0i16; 8];
Sse41I16::storeu(v, &mut out);
out
}
fn unpack32(v: __m128i) -> [i32; 4] {
let mut out = [0i32; 4];
Sse41I32::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; 8], nbytes: usize) -> [i16; 8] {
let sh = nbytes / 2;
let mut out = [0i16; 8];
for (i, &x) in a.iter().enumerate() {
if i + sh < 8 {
out[i + sh] = x;
}
}
out
}
fn shift_right_i16(a: &[i16; 8], nbytes: usize) -> [i16; 8] {
let sh = nbytes / 2;
let mut out = [0i16; 8];
for (i, &x) in a.iter().enumerate() {
if i >= sh {
out[i - sh] = x;
}
}
out
}
fn shift_left_i32(a: &[i32; 4], nbytes: usize) -> [i32; 4] {
let sh = nbytes / 4;
let mut out = [0i32; 4];
for (i, &x) in a.iter().enumerate() {
if i + sh < 4 {
out[i + sh] = x;
}
}
out
}
fn shift_right_i32(a: &[i32; 4], nbytes: usize) -> [i32; 4] {
let sh = nbytes / 4;
let mut out = [0i32; 4];
for (i, &x) in a.iter().enumerate() {
if i >= sh {
out[i - sh] = x;
}
}
out
}
#[test]
fn i16_ops_match_scalar_reference() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let a = [3i16, -2, 5, 1, 0, 7, -4, 2];
let b = [1i16, 1, -3, 4, -5, 2, 6, -1];
let va = Sse41I16::loadu(&a);
let vb = Sse41I16::loadu(&b);
assert_eq!(unpack16(Sse41I16::splat(-9)), [-9i16; 8]);
let mut exp_add = [0i16; 8];
let mut exp_sub = [0i16; 8];
let mut exp_min = [0i16; 8];
let mut exp_max = [0i16; 8];
let mut exp_or = [0i16; 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!(unpack16(Sse41I16::add(va, vb)), exp_add);
assert_eq!(unpack16(Sse41I16::sub(va, vb)), exp_sub);
assert_eq!(unpack16(Sse41I16::min(va, vb)), exp_min);
assert_eq!(unpack16(Sse41I16::max(va, vb)), exp_max);
assert_eq!(unpack16(Sse41I16::or(va, vb)), exp_or);
}
#[test]
fn i16_add_sub_are_non_saturating() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let hi = Sse41I16::splat(i16::MAX);
let lo = Sse41I16::splat(i16::MIN);
let one = Sse41I16::splat(1);
assert_eq!(unpack16(Sse41I16::add(hi, one)), [i16::MIN; 8]);
assert_eq!(unpack16(Sse41I16::sub(lo, one)), [i16::MAX; 8]);
}
#[test]
fn i16_adds_subs_saturate_at_bounds() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let hi = Sse41I16::splat(i16::MAX);
let lo = Sse41I16::splat(i16::MIN);
let one = Sse41I16::splat(1);
assert_eq!(unpack16(Sse41I16::adds(hi, one)), [i16::MAX; 8]);
assert_eq!(unpack16(Sse41I16::subs(lo, one)), [i16::MIN; 8]);
let a = [
3i16,
i16::MAX,
-2,
i16::MIN,
0,
i16::MAX - 1,
-100,
i16::MIN + 1,
];
let b = [1i16, 1, -3, -1, -5, 2, -50, -2];
let va = Sse41I16::loadu(&a);
let vb = Sse41I16::loadu(&b);
let mut exp_adds = [0i16; 8];
let mut exp_subs = [0i16; 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!(unpack16(Sse41I16::adds(va, vb)), exp_adds);
assert_eq!(unpack16(Sse41I16::subs(va, vb)), exp_subs);
}
#[test]
fn i16_loadu_storeu_round_trip() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let src = [10i16, 20, 30, 40, 50, 60, 70, 80];
let v = Sse41I16::loadu(&src);
let mut dst = [0i16; 8];
Sse41I16::storeu(v, &mut dst);
assert_eq!(dst, src);
}
#[test]
fn i16_store_widened_i32_sign_extends_all_lanes() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let src = [-5i16, 2, -9, 11, i16::MIN, i16::MAX, 0, -1];
let v = Sse41I16::loadu(&src);
let mut dst = [0i32; 8];
Sse41I16::store_widened_i32(v, &mut dst);
let expected: [i32; 8] = std::array::from_fn(|k| i32::from(src[k]));
assert_eq!(dst, expected);
}
#[test]
fn i32_store_widened_i32_is_a_plain_store() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let src = [-5i32, 123_456, i32::MIN, i32::MAX];
let v = Sse41I32::loadu(&src);
let mut dst = [0i32; 4];
Sse41I32::store_widened_i32(v, &mut dst);
assert_eq!(dst, src);
}
#[test]
fn i16_slli_srli_have_byte_shift_semantics() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let a = [1i16, 2, 3, 4, 5, 6, 7, 8];
let v = Sse41I16::loadu(&a);
assert_eq!(unpack16(Sse41I16::slli::<2>(v)), shift_left_i16(&a, 2));
assert_eq!(unpack16(Sse41I16::slli::<4>(v)), shift_left_i16(&a, 4));
assert_eq!(unpack16(Sse41I16::slli::<8>(v)), shift_left_i16(&a, 8));
assert_eq!(unpack16(Sse41I16::srli::<2>(v)), shift_right_i16(&a, 2));
assert_eq!(unpack16(Sse41I16::srli::<14>(v)), shift_right_i16(&a, 14));
}
#[test]
fn i16_horizontal_max_seeds_at_zero() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
assert_eq!(Sse41I16::horizontal_max(Sse41I16::splat(-5)), 0);
let mixed = Sse41I16::loadu(&[-5i16, -2, -9, -1, -3, -8, -7, -6]);
assert_eq!(Sse41I16::horizontal_max(mixed), 0);
let positive = Sse41I16::loadu(&[-5i16, 2, -9, 11, -3, 8, -7, 6]);
assert_eq!(Sse41I16::horizontal_max(positive), 11);
}
#[test]
fn i16_prefix_max_matches_scalar_reference() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let penalty: i16 = -4;
let penalties = build_penalties::<Sse41I16>(penalty);
let masks = build_masks::<Sse41I16>(Sse41I16::NEG_INF);
for a in [
[3i16, -2, 5, 1, 0, 7, -4, 2],
[0i16, 0, 0, 0, 0, 0, 0, 0],
[8i16, 6, 4, 2, 0, -2, -4, -6],
[-1i16, 9, -3, 2, 12, -8, 4, 5],
[40i16, 1, 2, 3, 4, 5, 6, 7],
[0i16, 36, 1, 2, 3, 4, 5, 6],
] {
let v = Sse41I16::loadu(&a);
let got = unpack16(Sse41I16::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 !is_x86_feature_detected!("sse4.1") {
return;
}
let a = [3i32, -2, 500, -1];
let b = [1i32, 1, -300, 4];
let va = Sse41I32::loadu(&a);
let vb = Sse41I32::loadu(&b);
assert_eq!(unpack32(Sse41I32::splat(-9)), [-9i32; 4]);
let mut exp_add = [0i32; 4];
let mut exp_sub = [0i32; 4];
let mut exp_min = [0i32; 4];
let mut exp_max = [0i32; 4];
let mut exp_or = [0i32; 4];
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(Sse41I32::add(va, vb)), exp_add);
assert_eq!(unpack32(Sse41I32::sub(va, vb)), exp_sub);
assert_eq!(unpack32(Sse41I32::min(va, vb)), exp_min);
assert_eq!(unpack32(Sse41I32::max(va, vb)), exp_max);
assert_eq!(unpack32(Sse41I32::or(va, vb)), exp_or);
}
#[test]
fn i32_add_sub_are_non_saturating() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let hi = Sse41I32::splat(i32::MAX);
let lo = Sse41I32::splat(i32::MIN);
let one = Sse41I32::splat(1);
assert_eq!(unpack32(Sse41I32::add(hi, one)), [i32::MIN; 4]);
assert_eq!(unpack32(Sse41I32::sub(lo, one)), [i32::MAX; 4]);
}
#[test]
fn i32_adds_subs_saturate_at_bounds() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let hi = Sse41I32::splat(i32::MAX);
let lo = Sse41I32::splat(i32::MIN);
let one = Sse41I32::splat(1);
assert_eq!(unpack32(Sse41I32::adds(hi, one)), [i32::MAX; 4]);
assert_eq!(unpack32(Sse41I32::subs(lo, one)), [i32::MIN; 4]);
let neg = Sse41I32::NEG_INF;
let a = [3i32, i32::MAX, neg, -100];
let b = [1i32, 1, -128, -50];
let va = Sse41I32::loadu(&a);
let vb = Sse41I32::loadu(&b);
let mut exp_adds = [0i32; 4];
let mut exp_subs = [0i32; 4];
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(Sse41I32::adds(va, vb)), exp_adds);
assert_eq!(unpack32(Sse41I32::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 = [next(), next(), next(), next()];
let b = [next(), next(), next(), next()];
let va = Sse41I32::loadu(&a);
let vb = Sse41I32::loadu(&b);
let exp_adds: [i32; 4] = std::array::from_fn(|i| a[i].saturating_add(b[i]));
let exp_subs: [i32; 4] = std::array::from_fn(|i| a[i].saturating_sub(b[i]));
assert_eq!(
unpack32(Sse41I32::adds(va, vb)),
exp_adds,
"adds mismatch for {a:?} + {b:?}"
);
assert_eq!(
unpack32(Sse41I32::subs(va, vb)),
exp_subs,
"subs mismatch for {a:?} - {b:?}"
);
}
}
#[test]
fn i32_loadu_storeu_round_trip() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let src = [100i32, 200, 300, 400];
let v = Sse41I32::loadu(&src);
let mut dst = [0i32; 4];
Sse41I32::storeu(v, &mut dst);
assert_eq!(dst, src);
}
#[test]
fn i32_slli_srli_have_byte_shift_semantics() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let a = [11i32, 22, 33, 44];
let v = Sse41I32::loadu(&a);
assert_eq!(unpack32(Sse41I32::slli::<4>(v)), shift_left_i32(&a, 4));
assert_eq!(unpack32(Sse41I32::slli::<8>(v)), shift_left_i32(&a, 8));
assert_eq!(unpack32(Sse41I32::srli::<4>(v)), shift_right_i32(&a, 4));
assert_eq!(unpack32(Sse41I32::srli::<12>(v)), shift_right_i32(&a, 12));
}
#[test]
fn i32_horizontal_max_seeds_at_zero() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
assert_eq!(Sse41I32::horizontal_max(Sse41I32::splat(-5)), 0);
let mixed = Sse41I32::loadu(&[-5i32, -2, -9, -1]);
assert_eq!(Sse41I32::horizontal_max(mixed), 0);
let positive = Sse41I32::loadu(&[-5i32, 42, -9, 11]);
assert_eq!(Sse41I32::horizontal_max(positive), 42);
}
#[test]
fn i32_prefix_max_matches_scalar_reference() {
if !is_x86_feature_detected!("sse4.1") {
return;
}
let penalty: i32 = -6;
let penalties = build_penalties::<Sse41I32>(penalty);
let masks = build_masks::<Sse41I32>(Sse41I32::NEG_INF);
for a in [
[3i32, -2, 5, 1],
[0i32, 0, 0, 0],
[8i32, 4, 0, -4],
[-1i32, 20, -3, 2],
[30i32, 1, 2, 3],
] {
let v = Sse41I32::loadu(&a);
let got = unpack32(Sse41I32::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!(Sse41I16::LANES, 8);
assert_eq!(Sse41I16::LOG_LANES, 3);
assert_eq!(Sse41I16::LSS, 2);
assert_eq!(Sse41I16::RSS, 14);
assert_eq!(Sse41I16::NEG_INF, i16::MIN + 1024);
assert_eq!(Sse41I32::LANES, 4);
assert_eq!(Sse41I32::LOG_LANES, 2);
assert_eq!(Sse41I32::LSS, 4);
assert_eq!(Sse41I32::RSS, 12);
assert_eq!(Sse41I32::NEG_INF, i32::MIN + 1024);
}
}