#[cfg(target_arch = "x86")]
use std::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[inline]
#[target_feature(enable = "avx2")]
fn load_i32x4(ptr: *const i32) -> __m128i {
unsafe { _mm_loadu_si128(ptr.cast::<__m128i>()) }
}
#[inline]
#[target_feature(enable = "avx2")]
fn store_i32x4(ptr: *mut i32, v: __m128i) {
unsafe { _mm_storeu_si128(ptr.cast::<__m128i>(), v) };
}
#[inline]
#[target_feature(enable = "avx2")]
fn wht4(
a0: __m128i,
b0: __m128i,
c0: __m128i,
d0: __m128i,
) -> (__m128i, __m128i, __m128i, __m128i) {
let mut a1 = _mm_add_epi32(a0, b0);
let mut d1 = _mm_sub_epi32(d0, c0);
let e1 = _mm_srai_epi32::<1>(_mm_sub_epi32(a1, d1));
let b1 = _mm_sub_epi32(e1, b0);
let c1 = _mm_sub_epi32(e1, c0);
a1 = _mm_sub_epi32(a1, c1);
d1 = _mm_add_epi32(d1, b1);
(a1, c1, d1, b1)
}
#[inline]
#[target_feature(enable = "avx2")]
fn transpose_4x4(
r0: __m128i,
r1: __m128i,
r2: __m128i,
r3: __m128i,
) -> (__m128i, __m128i, __m128i, __m128i) {
let t0 = _mm_unpacklo_epi32(r0, r1);
let t1 = _mm_unpackhi_epi32(r0, r1);
let t2 = _mm_unpacklo_epi32(r2, r3);
let t3 = _mm_unpackhi_epi32(r2, r3);
(
_mm_unpacklo_epi64(t0, t2),
_mm_unpackhi_epi64(t0, t2),
_mm_unpacklo_epi64(t1, t3),
_mm_unpackhi_epi64(t1, t3),
)
}
#[target_feature(enable = "avx2")]
pub(crate) fn fwht_raw_avx2(input: &mut [i32; 16]) {
let a = load_i32x4(input.as_ptr());
let b = load_i32x4(unsafe { input.as_ptr().add(4) });
let c = load_i32x4(unsafe { input.as_ptr().add(8) });
let d = load_i32x4(unsafe { input.as_ptr().add(12) });
let (r0, r1, r2, r3) = wht4(a, b, c, d);
let (r0, r1, r2, r3) = transpose_4x4(r0, r1, r2, r3);
let (o0, o1, o2, o3) = wht4(r0, r1, r2, r3);
store_i32x4(input.as_mut_ptr(), o0);
store_i32x4(unsafe { input.as_mut_ptr().add(4) }, o1);
store_i32x4(unsafe { input.as_mut_ptr().add(8) }, o2);
store_i32x4(unsafe { input.as_mut_ptr().add(12) }, o3);
}
#[cfg(test)]
mod avx2_vs_scalar {
use super::*;
fn fwht_scalar(input: &mut [i32; 16]) {
let mut mid = [0i32; 16];
for i in 0..4 {
let (a0, b0, c0, d0) = (input[i], input[4 + i], input[8 + i], input[12 + i]);
let mut a1 = a0 + b0;
let mut d1 = d0 - c0;
let e1 = (a1 - d1) >> 1;
let b1 = e1 - b0;
let c1 = e1 - c0;
a1 -= c1;
d1 += b1;
mid[i] = a1;
mid[4 + i] = c1;
mid[8 + i] = d1;
mid[12 + i] = b1;
}
for i in 0..4 {
let base = i * 4;
let (a0, b0, c0, d0) = (mid[base], mid[base + 1], mid[base + 2], mid[base + 3]);
let mut a1 = a0 + b0;
let mut d1 = d0 - c0;
let e1 = (a1 - d1) >> 1;
let b1 = e1 - b0;
let c1 = e1 - c0;
a1 -= c1;
d1 += b1;
input[base] = a1;
input[base + 1] = c1;
input[base + 2] = d1;
input[base + 3] = b1;
}
let mut t = [0i32; 16];
for r in 0..4 {
for c in 0..4 {
t[c * 4 + r] = input[r * 4 + c];
}
}
*input = t;
}
fn lcg(state: &mut u32) -> i32 {
*state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((*state >> 16) as i32 & 0xff) - 128
}
#[test]
fn fwht_matches_scalar() {
if !std::is_x86_feature_detected!("avx2") {
return;
}
for seed in [0x1234_5678, 0xdead_beef, 0xabcd_1234] {
let mut state = seed;
let mut input = [0i32; 16];
for v in input.iter_mut() {
*v = lcg(&mut state);
}
let mut s = input;
let mut a = input;
fwht_scalar(&mut s);
unsafe { fwht_raw_avx2(&mut a) };
assert_eq!(s, a, "WHT mismatch seed={seed:#x}");
}
}
}