use crate::clmul;
use crate::poly_basis::LowStageTwiddles;
pub(crate) const LOW_STAGES: usize = 4;
const LOW_RUN: usize = 1 << LOW_STAGES;
#[inline]
pub(crate) fn forward_low_stages(
values: &mut [u128],
first_run: usize,
twiddles: &LowStageTwiddles,
) {
low::forward(values, first_run, twiddles);
}
#[inline]
pub(crate) fn scale(values: &mut [u128], scalar: u128) {
let packed = if values.len() < wide::WIDTH {
0
} else {
wide::scale(values, scalar)
};
for value in &mut values[packed..] {
*value = clmul::poly_mul_128_batch(*value, scalar);
}
}
#[inline]
pub(crate) fn butterfly_forward(lo: &mut [u128], hi: &mut [u128], scalar: u128) {
assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
let packed = if lo.len() < wide::WIDTH {
0
} else {
wide::butterfly_forward(lo, hi, scalar)
};
for (lo, hi) in lo[packed..].iter_mut().zip(&mut hi[packed..]) {
*lo ^= clmul::poly_mul_128_batch(scalar, *hi);
*hi ^= *lo;
}
}
#[inline]
pub(crate) fn butterfly_inverse(lo: &mut [u128], hi: &mut [u128], scalar: u128) {
assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
let packed = if lo.len() < wide::WIDTH {
0
} else {
wide::butterfly_inverse(lo, hi, scalar)
};
for (lo, hi) in lo[packed..].iter_mut().zip(&mut hi[packed..]) {
*hi ^= *lo;
*lo ^= clmul::poly_mul_128_batch(scalar, *hi);
}
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "vpclmulqdq",
any(target_feature = "avx2", target_feature = "avx512f")
))]
mod wide {
use crate::packed::lanes;
pub(super) use crate::packed::lanes::WIDTH;
use crate::packed::split::SplitScalar;
#[inline(never)]
pub(super) fn scale(values: &mut [u128], scalar: u128) -> usize {
let (blocks, _) = values.as_chunks_mut::<WIDTH>();
let split = SplitScalar::new(scalar);
for block in blocks.iter_mut() {
unsafe {
let v = lanes::load(block.as_ptr());
lanes::store(block.as_mut_ptr(), split.apply(v));
}
}
blocks.len() * WIDTH
}
#[inline(never)]
pub(super) fn butterfly_forward(lo: &mut [u128], hi: &mut [u128], scalar: u128) -> usize {
let (lo, hi) = (lo.as_chunks_mut::<WIDTH>().0, hi.as_chunks_mut::<WIDTH>().0);
let blocks = lo.len();
let split = SplitScalar::new(scalar);
for (lo, hi) in lo.iter_mut().zip(hi.iter_mut()) {
unsafe {
let a = lanes::load(lo.as_ptr());
let b = lanes::load(hi.as_ptr());
let a = lanes::xor(a, split.apply(b));
lanes::store(lo.as_mut_ptr(), a);
lanes::store(hi.as_mut_ptr(), lanes::xor(b, a));
}
}
blocks * WIDTH
}
#[inline(never)]
pub(super) fn butterfly_inverse(lo: &mut [u128], hi: &mut [u128], scalar: u128) -> usize {
let (lo, hi) = (lo.as_chunks_mut::<WIDTH>().0, hi.as_chunks_mut::<WIDTH>().0);
let blocks = lo.len();
let split = SplitScalar::new(scalar);
for (lo, hi) in lo.iter_mut().zip(hi.iter_mut()) {
unsafe {
let a = lanes::load(lo.as_ptr());
let b = lanes::load(hi.as_ptr());
let b = lanes::xor(b, a);
lanes::store(hi.as_mut_ptr(), b);
lanes::store(lo.as_mut_ptr(), lanes::xor(a, split.apply(b)));
}
}
blocks * WIDTH
}
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "vpclmulqdq",
target_feature = "avx512f"
))]
mod low {
use super::LOW_RUN;
use crate::packed::lanes::{self, Reg, WIDTH};
use crate::packed::split::SplitScalar;
use crate::poly_basis::LowStageTwiddles;
const _: () = assert!(LOW_RUN == 4 * WIDTH);
#[inline(always)]
fn broadcast(pair: [u128; 2]) -> [Reg; 2] {
[lanes::broadcast(pair[0]), lanes::broadcast(pair[1])]
}
#[inline(always)]
fn per_lane(pairs: [[u128; 2]; WIDTH]) -> [Reg; 2] {
let values = pairs.map(|pair| pair[0]);
let companions = pairs.map(|pair| pair[1]);
unsafe {
[
lanes::load(values.as_ptr()),
lanes::load(companions.as_ptr()),
]
}
}
#[inline(always)]
fn add(a: [Reg; 2], b: [Reg; 2]) -> [Reg; 2] {
[lanes::xor(a[0], b[0]), lanes::xor(a[1], b[1])]
}
#[inline(always)]
const fn twiddle(pair: [Reg; 2]) -> SplitScalar<Reg> {
SplitScalar::from_parts(pair[0], pair[1])
}
#[inline(always)]
fn butterfly(lo: &mut Reg, hi: &mut Reg, twiddle: SplitScalar<Reg>) {
*lo = lanes::xor(*lo, twiddle.apply(*hi));
*hi = lanes::xor(*hi, *lo);
}
#[inline(always)]
fn lane_stages(
a: &mut Reg,
b: &mut Reg,
twiddle_1: SplitScalar<Reg>,
twiddle_0: SplitScalar<Reg>,
) {
let (mut lo, mut hi) = lanes::interleave(*a, *b, 2);
butterfly(&mut lo, &mut hi, twiddle_1);
let (mut lo, mut hi) = lanes::interleave(lo, hi, 1);
butterfly(&mut lo, &mut hi, twiddle_0);
let (lo, hi) = lanes::interleave(lo, hi, 1);
(*a, *b) = lanes::interleave(lo, hi, 2);
}
#[inline(never)]
pub(super) fn forward(values: &mut [u128], first_run: usize, twiddles: &LowStageTwiddles) {
let (runs, _) = values.as_chunks_mut::<LOW_RUN>();
let span = |block: usize| twiddles.span(block, 0);
let second_block = broadcast(span(1));
let offsets_1 = [
per_lane([span(0), span(0), span(1), span(1)]),
per_lane([span(2), span(2), span(3), span(3)]),
];
let offsets_0 = [
per_lane([span(0), span(1), span(2), span(3)]),
per_lane([span(4), span(5), span(6), span(7)]),
];
let mut run_twiddles = twiddles.run_twiddles(first_run).map(broadcast);
for (index, run) in runs.iter_mut().enumerate() {
if index != 0 {
let step = twiddles.step(first_run + index - 1);
for (twiddle, step) in run_twiddles.iter_mut().zip(step) {
*twiddle = add(*twiddle, broadcast(*step));
}
}
let [t0, t1, t2, t3] = run_twiddles;
unsafe {
let base = run.as_mut_ptr();
let [mut r0, mut r1, mut r2, mut r3] =
core::array::from_fn(|r| lanes::load(base.add(r * WIDTH)));
let twiddle_3 = twiddle(t3);
butterfly(&mut r0, &mut r2, twiddle_3);
butterfly(&mut r1, &mut r3, twiddle_3);
butterfly(&mut r0, &mut r1, twiddle(t2));
butterfly(&mut r2, &mut r3, twiddle(add(t2, second_block)));
lane_stages(
&mut r0,
&mut r1,
twiddle(add(t1, offsets_1[0])),
twiddle(add(t0, offsets_0[0])),
);
lane_stages(
&mut r2,
&mut r3,
twiddle(add(t1, offsets_1[1])),
twiddle(add(t0, offsets_0[1])),
);
for (r, register) in [r0, r1, r2, r3].into_iter().enumerate() {
lanes::store(base.add(r * WIDTH), register);
}
}
}
}
}
#[cfg(not(all(
target_arch = "x86_64",
target_feature = "vpclmulqdq",
target_feature = "avx512f"
)))]
mod low {
use super::{LOW_RUN, LOW_STAGES};
use crate::poly_basis::LowStageTwiddles;
pub(super) fn forward(values: &mut [u128], first_run: usize, twiddles: &LowStageTwiddles) {
let mut run_twiddles = twiddles.run_twiddles(first_run).map(|pair| pair[0]);
let offsets: [u128; 8] = core::array::from_fn(|block| twiddles.span(block, 0)[0]);
for (index, run) in values.as_chunks_mut::<LOW_RUN>().0.iter_mut().enumerate() {
if index != 0 {
let step = twiddles.step(first_run + index - 1);
for (twiddle, step) in run_twiddles.iter_mut().zip(step) {
*twiddle ^= step[0];
}
}
for j in (0..LOW_STAGES).rev() {
let half = 1 << j;
for (block, pair) in run.chunks_exact_mut(2 * half).enumerate() {
let (lo, hi) = pair.split_at_mut(half);
let twiddle = run_twiddles[j] ^ offsets[block];
super::butterfly_forward(lo, hi, twiddle);
}
}
}
}
}
#[cfg(not(all(
target_arch = "x86_64",
target_feature = "vpclmulqdq",
any(target_feature = "avx2", target_feature = "avx512f")
)))]
mod wide {
pub(super) const WIDTH: usize = usize::MAX;
#[inline]
pub(super) const fn scale(_values: &mut [u128], _scalar: u128) -> usize {
0
}
#[inline]
pub(super) const fn butterfly_forward(
_lo: &mut [u128],
_hi: &mut [u128],
_scalar: u128,
) -> usize {
0
}
#[inline]
pub(super) const fn butterfly_inverse(
_lo: &mut [u128],
_hi: &mut [u128],
_scalar: u128,
) -> usize {
0
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use proptest::prelude::*;
use crate::clmul;
const CORNERS: [u128; 7] = [0, 1, u128::MAX, 1 << 127, 1 << 64, (1u128 << 64) - 1, 0x87];
const PACKED_BUILD: bool = cfg!(all(
target_arch = "x86_64",
target_feature = "vpclmulqdq",
any(target_feature = "avx2", target_feature = "avx512f")
));
const WIDEST_LANES: usize = 4;
const SENTINELS: usize = 2 * WIDEST_LANES;
const _: () = assert!(if PACKED_BUILD {
super::wide::WIDTH == 2 || super::wide::WIDTH == WIDEST_LANES
} else {
super::wide::WIDTH == usize::MAX
});
const SENTINEL: u128 = 0x5a5a_5a5a_5a5a_5a5a_5a5a_5a5a_5a5a_5a5a;
fn sample(len: usize) -> Vec<u128> {
(0..len)
.map(|i| (i as u128 + 1).wrapping_mul(0x9e37_79b9_7f4a_7c15_f39c_c060_5ced_c835))
.collect()
}
fn padded(values: &[u128], offset: usize) -> Vec<u128> {
let lead = SENTINELS + offset;
let mut buffer = Vec::with_capacity(lead + values.len() + SENTINELS);
buffer.extend(core::iter::repeat_n(SENTINEL, lead));
buffer.extend_from_slice(values);
buffer.extend(core::iter::repeat_n(SENTINEL, SENTINELS));
buffer
}
fn payload_mut(buffer: &mut [u128], offset: usize, len: usize) -> &mut [u128] {
let start = SENTINELS + offset;
&mut buffer[start..start + len]
}
fn walls_intact(buffer: &[u128], offset: usize, len: usize) -> bool {
let start = SENTINELS + offset;
buffer[..start].iter().all(|&v| v == SENTINEL)
&& buffer[start + len..].iter().all(|&v| v == SENTINEL)
}
fn payload(buffer: &[u128], offset: usize, len: usize) -> Result<&[u128], TestCaseError> {
prop_assert!(walls_intact(buffer, offset, len));
let start = SENTINELS + offset;
Ok(&buffer[start..start + len])
}
fn reference_scale(values: &[u128], scalar: u128) -> Vec<u128> {
values
.iter()
.map(|&v| clmul::poly_mul_128(v, scalar))
.collect()
}
fn reference_forward(lo: &[u128], hi: &[u128], scalar: u128) -> (Vec<u128>, Vec<u128>) {
let mut lo = lo.to_vec();
let mut hi = hi.to_vec();
for (lo, hi) in lo.iter_mut().zip(&mut hi) {
*lo ^= clmul::poly_mul_128(scalar, *hi);
*hi ^= *lo;
}
(lo, hi)
}
fn reference_inverse(lo: &[u128], hi: &[u128], scalar: u128) -> (Vec<u128>, Vec<u128>) {
let mut lo = lo.to_vec();
let mut hi = hi.to_vec();
for (lo, hi) in lo.iter_mut().zip(&mut hi) {
*hi ^= *lo;
*lo ^= clmul::poly_mul_128(scalar, *hi);
}
(lo, hi)
}
fn kernels_agree(values: &[u128], scalar: u128, offset: usize) -> Result<(), TestCaseError> {
let len = values.len();
let other: Vec<u128> = values.iter().rev().copied().collect();
let mut scaled = padded(values, offset);
super::scale(payload_mut(&mut scaled, offset, len), scalar);
prop_assert_eq!(
payload(&scaled, offset, len)?,
&reference_scale(values, scalar)[..]
);
let mut lo = padded(values, offset);
let mut hi = padded(&other, offset);
super::butterfly_forward(
payload_mut(&mut lo, offset, len),
payload_mut(&mut hi, offset, len),
scalar,
);
let (want_lo, want_hi) = reference_forward(values, &other, scalar);
prop_assert_eq!(payload(&lo, offset, len)?, &want_lo[..]);
prop_assert_eq!(payload(&hi, offset, len)?, &want_hi[..]);
super::butterfly_inverse(
payload_mut(&mut lo, offset, len),
payload_mut(&mut hi, offset, len),
scalar,
);
prop_assert_eq!(payload(&lo, offset, len)?, values);
prop_assert_eq!(payload(&hi, offset, len)?, &other[..]);
let mut lo = padded(values, offset);
let mut hi = padded(&other, offset);
super::butterfly_inverse(
payload_mut(&mut lo, offset, len),
payload_mut(&mut hi, offset, len),
scalar,
);
let (want_lo, want_hi) = reference_inverse(values, &other, scalar);
prop_assert_eq!(payload(&lo, offset, len)?, &want_lo[..]);
prop_assert_eq!(payload(&hi, offset, len)?, &want_hi[..]);
Ok(())
}
#[test]
fn the_packed_prefix_and_the_scalar_tail_agree_at_every_short_length() {
for len in 0..=17 {
let values = sample(len);
for scalar in CORNERS {
for offset in 0..4 {
kernels_agree(&values, scalar, offset)
.unwrap_or_else(|e| panic!("len {len}, scalar {scalar:#x}: {e}"));
}
}
}
}
#[test]
fn the_packed_prefix_covers_every_whole_register() {
if !PACKED_BUILD {
return;
}
let width = super::wide::WIDTH;
for len in 0..4 * width {
let want = len - len % width;
let values = sample(len);
let other: Vec<u128> = values.iter().rev().copied().collect();
let mut scaled = padded(&values, 0);
let covered = super::wide::scale(payload_mut(&mut scaled, 0, len), 3);
assert_eq!(covered, want, "scale at len {len}");
assert!(walls_intact(&scaled, 0, len), "scale overran at len {len}");
let mut lo = padded(&values, 0);
let mut hi = padded(&other, 0);
let covered = super::wide::butterfly_forward(
payload_mut(&mut lo, 0, len),
payload_mut(&mut hi, 0, len),
3,
);
assert_eq!(covered, want, "forward butterfly at len {len}");
assert!(walls_intact(&lo, 0, len), "forward overran lo at len {len}");
assert!(walls_intact(&hi, 0, len), "forward overran hi at len {len}");
let covered = super::wide::butterfly_inverse(
payload_mut(&mut lo, 0, len),
payload_mut(&mut hi, 0, len),
3,
);
assert_eq!(covered, want, "inverse butterfly at len {len}");
assert!(walls_intact(&lo, 0, len), "inverse overran lo at len {len}");
assert!(walls_intact(&hi, 0, len), "inverse overran hi at len {len}");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn the_kernels_agree_with_the_scalar_backend(
values in prop::collection::vec(any::<u128>(), 0..40),
scalar in any::<u128>(),
offset in 0usize..4,
) {
kernels_agree(&values, scalar, offset)?;
}
#[test]
fn a_zero_multiplier_degenerates(values in prop::collection::vec(any::<u128>(), 0..40)) {
let mut scaled = values.clone();
super::scale(&mut scaled, 0);
prop_assert!(scaled.iter().all(|&v| v == 0));
let mut lo = values.clone();
let mut hi: Vec<u128> = values.iter().rev().copied().collect();
let expected: Vec<u128> = hi.iter().zip(&lo).map(|(h, l)| h ^ l).collect();
super::butterfly_forward(&mut lo, &mut hi, 0);
prop_assert_eq!(&lo, &values);
prop_assert_eq!(&hi, &expected);
}
}
}