#![allow(clippy::needless_range_loop)]
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn sum_f64_filtered(values: &[f64], mask: &[bool]) -> f64 {
debug_assert_eq!(values.len(), mask.len());
let len = values.len().min(mask.len());
if len == 0 {
return 0.0;
}
let mut acc0 = _mm256_setzero_pd();
let mut acc1 = _mm256_setzero_pd();
let zero = _mm256_setzero_pd();
let ones_epi64 = _mm256_set1_epi64x(0);
let chunks = len / 8;
let values_ptr = values.as_ptr();
let mask_ptr = mask.as_ptr() as *const u8;
for i in 0..chunks {
let off = i * 8;
let v0 = _mm256_loadu_pd(values_ptr.add(off));
let v1 = _mm256_loadu_pd(values_ptr.add(off + 4));
let m0_bytes = _mm_cvtsi32_si128(std::ptr::read_unaligned(mask_ptr.add(off) as *const i32));
let m1_bytes =
_mm_cvtsi32_si128(std::ptr::read_unaligned(mask_ptr.add(off + 4) as *const i32));
let m0_epi32 = _mm_cvtepu8_epi32(m0_bytes);
let m1_epi32 = _mm_cvtepu8_epi32(m1_bytes);
let m0_epi64 = _mm256_cvtepi32_epi64(m0_epi32);
let m1_epi64 = _mm256_cvtepi32_epi64(m1_epi32);
let eq0 = _mm256_cmpeq_epi64(m0_epi64, ones_epi64);
let eq1 = _mm256_cmpeq_epi64(m1_epi64, ones_epi64);
let blend_mask0 = _mm256_castsi256_pd(_mm256_xor_si256(
eq0,
_mm256_set1_epi64x(-1), ));
let blend_mask1 = _mm256_castsi256_pd(_mm256_xor_si256(
eq1,
_mm256_set1_epi64x(-1),
));
let masked0 = _mm256_blendv_pd(zero, v0, blend_mask0);
let masked1 = _mm256_blendv_pd(zero, v1, blend_mask1);
acc0 = _mm256_add_pd(acc0, masked0);
acc1 = _mm256_add_pd(acc1, masked1);
}
let combined = _mm256_add_pd(acc0, acc1);
let hadd = _mm256_hadd_pd(combined, combined);
let hi128 = _mm256_extractf128_pd(hadd, 1);
let lo128 = _mm256_castpd256_pd128(hadd);
let sum_vec = _mm_add_sd(lo128, hi128);
let mut sum = _mm_cvtsd_f64(sum_vec);
let remainder_start = chunks * 8;
if remainder_start + 4 <= len {
let v = _mm256_loadu_pd(values_ptr.add(remainder_start));
let m_bytes = _mm_cvtsi32_si128(
std::ptr::read_unaligned(mask_ptr.add(remainder_start) as *const i32),
);
let m_epi32 = _mm_cvtepu8_epi32(m_bytes);
let m_epi64 = _mm256_cvtepi32_epi64(m_epi32);
let eq = _mm256_cmpeq_epi64(m_epi64, ones_epi64);
let blend = _mm256_castsi256_pd(_mm256_xor_si256(eq, _mm256_set1_epi64x(-1)));
let masked = _mm256_blendv_pd(zero, v, blend);
let hadd2 = _mm256_hadd_pd(masked, masked);
let hi2 = _mm256_extractf128_pd(hadd2, 1);
let lo2 = _mm256_castpd256_pd128(hadd2);
let s2 = _mm_add_sd(lo2, hi2);
sum += _mm_cvtsd_f64(s2);
for i in (remainder_start + 4)..len {
if mask[i] {
sum += values[i];
}
}
} else {
for i in remainder_start..len {
if mask[i] {
sum += values[i];
}
}
}
sum
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn sum_f64_masked(values: &[f64], mask: &[bool]) -> f64 {
sum_f64_filtered(values, mask)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn sum_f64(values: &[f64]) -> f64 {
let len = values.len();
if len == 0 {
return 0.0;
}
let mut acc0 = _mm256_setzero_pd();
let mut acc1 = _mm256_setzero_pd();
let chunks = len / 8;
let ptr = values.as_ptr();
for i in 0..chunks {
let off = i * 8;
let v0 = _mm256_loadu_pd(ptr.add(off));
let v1 = _mm256_loadu_pd(ptr.add(off + 4));
acc0 = _mm256_add_pd(acc0, v0);
acc1 = _mm256_add_pd(acc1, v1);
}
let combined = _mm256_add_pd(acc0, acc1);
let hadd = _mm256_hadd_pd(combined, combined);
let hi128 = _mm256_extractf128_pd(hadd, 1);
let lo128 = _mm256_castpd256_pd128(hadd);
let sum_vec = _mm_add_sd(lo128, hi128);
let mut sum = _mm_cvtsd_f64(sum_vec);
for i in (chunks * 8)..len {
sum += values[i];
}
sum
}
#[cfg(all(test, target_arch = "x86_64"))]
mod tests {
use super::*;
fn has_avx2() -> bool {
is_x86_feature_detected!("avx2")
}
#[test]
fn test_avx2_sum_f64_filtered_basic() {
if !has_avx2() {
return;
}
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let mask = vec![true, false, true, false, true, false, true, false];
let result = unsafe { sum_f64_filtered(&values, &mask) };
assert!((result - 16.0).abs() < 1e-10, "got {}", result);
}
#[test]
fn test_avx2_sum_f64_filtered_all_true() {
if !has_avx2() {
return;
}
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let mask = vec![true; 8];
let result = unsafe { sum_f64_filtered(&values, &mask) };
assert!((result - 36.0).abs() < 1e-10, "got {}", result);
}
#[test]
fn test_avx2_sum_f64_filtered_all_false() {
if !has_avx2() {
return;
}
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let mask = vec![false; 8];
let result = unsafe { sum_f64_filtered(&values, &mask) };
assert_eq!(result, 0.0);
}
#[test]
fn test_avx2_sum_f64_filtered_empty() {
if !has_avx2() {
return;
}
let result = unsafe { sum_f64_filtered(&[], &[]) };
assert_eq!(result, 0.0);
}
#[test]
fn test_avx2_sum_f64_filtered_remainder() {
if !has_avx2() {
return;
}
let values: Vec<f64> = (1..=7).map(|x| x as f64).collect();
let mask = vec![true, false, true, false, true, false, true];
let result = unsafe { sum_f64_filtered(&values, &mask) };
assert!((result - 16.0).abs() < 1e-10, "got {}", result);
}
#[test]
fn test_avx2_sum_f64_filtered_large() {
if !has_avx2() {
return;
}
let values: Vec<f64> = (1..=1024).map(|x| x as f64).collect();
let mask: Vec<bool> = (0..1024).map(|i| i % 2 == 0).collect();
let result = unsafe { sum_f64_filtered(&values, &mask) };
let expected: f64 = (0..1024).filter(|i| i % 2 == 0).map(|i| (i + 1) as f64).sum();
assert!((result - expected).abs() < 1e-6, "got {} expected {}", result, expected);
}
#[test]
fn test_avx2_matches_scalar() {
if !has_avx2() {
return;
}
let values: Vec<f64> = (0..100).map(|i| (i as f64) * 1.1 + 0.7).collect();
let mask: Vec<bool> = (0..100).map(|i| i % 3 != 0).collect();
let avx2_result = unsafe { sum_f64_filtered(&values, &mask) };
let scalar_result = super::super::scalar::sum_f64_filtered(&values, &mask);
assert!(
(avx2_result - scalar_result).abs() < 1e-10,
"AVX2 {} != scalar {}",
avx2_result,
scalar_result
);
}
#[test]
fn test_avx2_sum_f64_unfiltered() {
if !has_avx2() {
return;
}
let values: Vec<f64> = (1..=100).map(|x| x as f64).collect();
let result = unsafe { sum_f64(&values) };
assert!((result - 5050.0).abs() < 1e-10);
}
}