use core::arch::x86_64::*;
pub type Limbs = [__m256i; 5];
const MASK26: i64 = 0x3ff_ffff;
pub struct Mul {
r: Limbs,
s: [__m256i; 4],
}
impl Mul {
#[target_feature(enable = "avx2")]
pub fn splat(p: &[u32; 5]) -> Mul {
Mul::lanes([p, p, p, p])
}
#[target_feature(enable = "avx2")]
pub fn lanes(p: [&[u32; 5]; 4]) -> Mul {
let limb = |j: usize| {
_mm256_setr_epi64x(
p[0][j] as i64,
p[1][j] as i64,
p[2][j] as i64,
p[3][j] as i64,
)
};
let r = [limb(0), limb(1), limb(2), limb(3), limb(4)];
let five = |x: __m256i| _mm256_add_epi64(_mm256_slli_epi64::<2>(x), x);
let s = [five(r[1]), five(r[2]), five(r[3]), five(r[4])];
Mul { r, s }
}
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn load(blocks: &[u8; 64]) -> Limbs {
let (a, b) = unsafe {
(
_mm256_loadu_si256(blocks.as_ptr() as *const __m256i),
_mm256_loadu_si256(blocks.as_ptr().add(32) as *const __m256i),
)
};
let lo = _mm256_unpacklo_epi64(a, b);
let hi = _mm256_unpackhi_epi64(a, b);
let mask = _mm256_set1_epi64x(MASK26);
[
_mm256_and_si256(lo, mask),
_mm256_and_si256(_mm256_srli_epi64::<26>(lo), mask),
_mm256_and_si256(
_mm256_or_si256(_mm256_srli_epi64::<52>(lo), _mm256_slli_epi64::<12>(hi)),
mask,
),
_mm256_and_si256(_mm256_srli_epi64::<14>(hi), mask),
_mm256_or_si256(_mm256_srli_epi64::<40>(hi), _mm256_set1_epi64x(1 << 24)),
]
}
#[inline]
#[target_feature(enable = "avx2")]
fn mul(h: &Limbs, m: &Mul) -> Limbs {
let (r, s) = (&m.r, &m.s);
let p = |a: __m256i, b: __m256i| _mm256_mul_epu32(a, b);
let add = |a: __m256i, b: __m256i| _mm256_add_epi64(a, b);
let d0 = add(
add(
add(p(h[0], r[0]), p(h[1], s[3])),
add(p(h[2], s[2]), p(h[3], s[1])),
),
p(h[4], s[0]),
);
let d1 = add(
add(
add(p(h[0], r[1]), p(h[1], r[0])),
add(p(h[2], s[3]), p(h[3], s[2])),
),
p(h[4], s[1]),
);
let d2 = add(
add(
add(p(h[0], r[2]), p(h[1], r[1])),
add(p(h[2], r[0]), p(h[3], s[3])),
),
p(h[4], s[2]),
);
let d3 = add(
add(
add(p(h[0], r[3]), p(h[1], r[2])),
add(p(h[2], r[1]), p(h[3], r[0])),
),
p(h[4], s[3]),
);
let d4 = add(
add(
add(p(h[0], r[4]), p(h[1], r[3])),
add(p(h[2], r[2]), p(h[3], r[1])),
),
p(h[4], r[0]),
);
carry([d0, d1, d2, d3, d4])
}
#[inline]
#[target_feature(enable = "avx2")]
fn carry(d: [__m256i; 5]) -> Limbs {
let mask = _mm256_set1_epi64x(MASK26);
let [mut d0, mut d1, mut d2, mut d3, mut d4] = d;
let shr = |x: __m256i| _mm256_srli_epi64::<26>(x);
let and = |x: __m256i| _mm256_and_si256(x, mask);
let add = |a: __m256i, b: __m256i| _mm256_add_epi64(a, b);
let c3 = shr(d3);
d3 = and(d3);
d4 = add(d4, c3);
let c0 = shr(d0);
d0 = and(d0);
d1 = add(d1, c0);
let c4 = shr(d4);
d4 = and(d4);
d0 = add(d0, add(c4, _mm256_slli_epi64::<2>(c4)));
let c1 = shr(d1);
d1 = and(d1);
d2 = add(d2, c1);
let c2 = shr(d2);
d2 = and(d2);
d3 = add(d3, c2);
let c0 = shr(d0);
d0 = and(d0);
d1 = add(d1, c0);
let c3 = shr(d3);
d3 = and(d3);
d4 = add(d4, c3);
[d0, d1, d2, d3, d4]
}
#[inline]
#[target_feature(enable = "avx2")]
fn add_limbs(a: &Limbs, b: &Limbs) -> Limbs {
[
_mm256_add_epi64(a[0], b[0]),
_mm256_add_epi64(a[1], b[1]),
_mm256_add_epi64(a[2], b[2]),
_mm256_add_epi64(a[3], b[3]),
_mm256_add_epi64(a[4], b[4]),
]
}
#[inline]
#[target_feature(enable = "avx2")]
pub unsafe fn step(h: &Limbs, r4: &Mul, blocks: &[u8; 64]) -> Limbs {
let m = unsafe { load(blocks) };
add_limbs(&mul(h, r4), &m)
}
#[inline]
#[target_feature(enable = "avx2")]
pub unsafe fn finish(h: &Limbs, last: &Mul) -> [u64; 5] {
let h = mul(h, last);
let mut out = [0u64; 5];
for (o, v) in out.iter_mut().zip(h) {
let x = _mm_add_epi64(_mm256_castsi256_si128(v), _mm256_extracti128_si256::<1>(v));
let x = _mm_add_epi64(x, _mm_unpackhi_epi64(x, x));
*o = _mm_cvtsi128_si64(x) as u64;
}
out
}
#[target_feature(enable = "avx2")]
pub unsafe fn absorb(
acc: &[u32; 5],
r: &[u32; 5],
powers: &[[u32; 5]; 3],
data: &[u8],
) -> [u64; 5] {
debug_assert!(!data.is_empty() && data.len().is_multiple_of(64));
let mut groups = data.chunks_exact(64).flat_map(<&[u8; 64]>::try_from);
let Some(first) = groups.next() else {
return [
acc[0] as u64,
acc[1] as u64,
acc[2] as u64,
acc[3] as u64,
acc[4] as u64,
];
};
let mut h = unsafe { load(first) };
let a = |j: usize| _mm256_setr_epi64x(acc[j] as i64, 0, 0, 0);
h = add_limbs(&h, &[a(0), a(1), a(2), a(3), a(4)]);
let r4 = Mul::splat(&powers[2]);
for g in groups {
h = unsafe { step(&h, &r4, g) };
}
let last = Mul::lanes([&powers[2], &powers[0], &powers[1], r]);
unsafe { finish(&h, &last) }
}