use super::wide::{HIGH_BY_HIGH, HIGH_BY_LOW, LOW_BY_LOW, Lanes64, Wide, fold_cubic};
pub(crate) trait Register128: Lanes64 {
const CHEAP_MULTIPLY: bool;
#[inline(always)]
fn reduce_product(product: Self) -> u64 {
product.reduce_lane().lower()
}
fn lift(value: u64) -> Self;
fn lower(self) -> u64;
fn swap(self) -> Self;
fn load(a: &[u64; 3]) -> (Self, Self);
fn store(pair: Self, last: Self) -> [u64; 3];
fn load_scalar(k: &u64) -> Self;
}
#[inline(always)]
fn reduce<R: Register128>(product: R) -> u64 {
product.reduce_lane().lower()
}
#[inline]
pub(crate) fn poly_mul_64<R: Register128>(a: u64, b: u64) -> u64 {
R::reduce_product(R::lift(a).clmul::<LOW_BY_LOW>(R::lift(b)))
}
#[inline]
pub(crate) fn poly_square_64<R: Register128>(a: u64) -> u64 {
let x = R::lift(a);
reduce(x.clmul::<LOW_BY_LOW>(x))
}
#[inline]
pub(crate) fn poly_dot_64<R: Register128>(pairs: impl Iterator<Item = (u64, u64)>) -> u64 {
let sum = pairs.fold(R::zero(), |sum, (a, b)| {
sum.xor(R::lift(a).clmul::<LOW_BY_LOW>(R::lift(b)))
});
reduce(sum)
}
#[derive(Clone, Copy)]
struct Operand<R> {
pair: R,
tail: R,
sums: R,
}
impl<R: Register128> Operand<R> {
#[inline(always)]
fn new(a: &[u64; 3]) -> Self {
let (pair, last) = R::load(a);
let third = last.unpack_low(last);
let crossed = pair.xor(pair.swap());
Self {
pair,
tail: third.unpack_low(crossed),
sums: pair.xor(third),
}
}
}
#[derive(Clone, Copy)]
struct Unreduced<R>([R; 3]);
impl<R: Register128> Unreduced<R> {
#[inline(always)]
fn zero() -> Self {
Self([R::zero(); 3])
}
#[inline(always)]
fn xor(self, other: Self) -> Self {
Self(core::array::from_fn(|i| self.0[i].xor(other.0[i])))
}
#[inline(always)]
fn reduce(self) -> [u64; 3] {
let [r0, r1, r2] = self.0;
let pair = Wide { even: r0, odd: r1 }.reduce();
let last = r2.reduce_lane();
R::store(pair, last)
}
}
#[inline(always)]
fn karatsuba<R: Register128>(a: &[u64; 3], b: &[u64; 3]) -> Unreduced<R> {
let (a, b) = (Operand::<R>::new(a), Operand::<R>::new(b));
let c0 = a.pair.clmul::<LOW_BY_LOW>(b.pair);
let c1 = a.pair.clmul::<HIGH_BY_HIGH>(b.pair);
let c2 = a.tail.clmul::<LOW_BY_LOW>(b.tail);
let d01 = a.tail.clmul::<HIGH_BY_HIGH>(b.tail);
let d02 = a.sums.clmul::<LOW_BY_LOW>(b.sums);
let d12 = a.sums.clmul::<HIGH_BY_HIGH>(b.sums);
Unreduced(fold_cubic([c0, c1, c2, d01, d02, d12], R::xor, R::xor3))
}
#[inline(always)]
fn schoolbook<R: Register128>(a: &[u64; 3], b: &[u64; 3]) -> Unreduced<R> {
let ((pair_a, last_a), (pair_b, last_b)) = (R::load(a), R::load(b));
let (third_a, third_b) = (last_a.unpack_low(last_a), last_b.unpack_low(last_b));
let swapped = pair_b.swap();
let c00 = pair_a.clmul::<LOW_BY_LOW>(pair_b);
let c11 = pair_a.clmul::<HIGH_BY_HIGH>(pair_b);
let c01 = pair_a.clmul::<LOW_BY_LOW>(swapped);
let c10 = pair_a.clmul::<HIGH_BY_HIGH>(swapped);
let c02 = pair_a.clmul::<LOW_BY_LOW>(third_b);
let c12 = pair_a.clmul::<HIGH_BY_HIGH>(third_b);
let c20 = third_a.clmul::<LOW_BY_LOW>(pair_b);
let c21 = third_a.clmul::<HIGH_BY_HIGH>(pair_b);
let c22 = third_a.clmul::<LOW_BY_LOW>(third_b);
Unreduced([
c00.xor3(c12, c21),
c01.xor3(c10, c12.xor3(c21, c22)),
c02.xor3(c11, c20.xor(c22)),
])
}
#[inline(always)]
fn mul_unreduced<R: Register128>(a: &[u64; 3], b: &[u64; 3]) -> Unreduced<R> {
if R::CHEAP_MULTIPLY {
schoolbook(a, b)
} else {
karatsuba(a, b)
}
}
#[inline]
pub(crate) fn poly_mul_192<R: Register128>(a: &[u64; 3], b: &[u64; 3]) -> [u64; 3] {
mul_unreduced::<R>(a, b).reduce()
}
#[inline]
pub(crate) fn poly_square_192<R: Register128>(a: &[u64; 3]) -> [u64; 3] {
let (pair, third) = R::load(a);
let s0 = pair.clmul::<LOW_BY_LOW>(pair);
let s1 = pair.clmul::<HIGH_BY_HIGH>(pair);
let s2 = third.clmul::<LOW_BY_LOW>(third);
Unreduced([s0, s2, s1.xor(s2)]).reduce()
}
#[inline(always)]
fn mul_by_64_unreduced<R: Register128>(a: &[u64; 3], k: &u64) -> Unreduced<R> {
let (pair, third) = R::load(a);
let scalar = R::load_scalar(k);
Unreduced([
pair.clmul::<LOW_BY_LOW>(scalar),
pair.clmul::<HIGH_BY_LOW>(scalar),
third.clmul::<LOW_BY_LOW>(scalar),
])
}
#[inline]
pub(crate) fn poly_mul_192_by_64<R: Register128>(a: &[u64; 3], k: &u64) -> [u64; 3] {
mul_by_64_unreduced::<R>(a, k).reduce()
}
#[inline]
pub(crate) fn poly_dot_192<'a, R: Register128>(
pairs: impl Iterator<Item = (&'a [u64; 3], &'a [u64; 3])>,
) -> [u64; 3] {
pairs
.fold(Unreduced::<R>::zero(), |sum, (a, b)| {
sum.xor(mul_unreduced(a, b))
})
.reduce()
}
#[inline]
pub(crate) fn poly_dot_192_by_64<'a, R: Register128>(
pairs: impl Iterator<Item = (&'a [u64; 3], &'a u64)>,
) -> [u64; 3] {
pairs
.fold(Unreduced::<R>::zero(), |sum, (a, k)| {
sum.xor(mul_by_64_unreduced(a, k))
})
.reduce()
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{
Register128, karatsuba, poly_dot_64, poly_dot_192, poly_dot_192_by_64, poly_mul_64,
poly_mul_192, poly_mul_192_by_64, poly_square_64, poly_square_192, schoolbook,
};
use crate::clmul::wide::model::Model;
use crate::clmul::{poly_mul_64 as reference_mul_64, poly_mul_192 as reference_mul_192};
impl Register128 for Model<1> {
const CHEAP_MULTIPLY: bool = false;
fn lift(value: u64) -> Self {
Self([[value, 0]])
}
fn lower(self) -> u64 {
self.0[0][0]
}
fn swap(self) -> Self {
Self([[self.0[0][1], self.0[0][0]]])
}
fn load(a: &[u64; 3]) -> (Self, Self) {
(Self([[a[0], a[1]]]), Self([[a[2], u64::MAX]]))
}
fn store(pair: Self, last: Self) -> [u64; 3] {
[pair.0[0][0], pair.0[0][1], last.0[0][0]]
}
fn load_scalar(k: &u64) -> Self {
Self([[*k, u64::MAX]])
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn the_register_kernels_match_the_selected_backend(
a: [u64; 3], b: [u64; 3], c: [u64; 3], k: u64, l: u64,
) {
type M = Model<1>;
prop_assert_eq!(poly_mul_64::<M>(a[0], b[0]), reference_mul_64(a[0], b[0]));
prop_assert_eq!(poly_square_64::<M>(a[0]), reference_mul_64(a[0], a[0]));
prop_assert_eq!(
poly_dot_64::<M>([(a[0], b[0]), (a[1], b[1])].into_iter()),
reference_mul_64(a[0], b[0]) ^ reference_mul_64(a[1], b[1])
);
prop_assert_eq!(karatsuba::<M>(&a, &b).reduce(), reference_mul_192(&a, &b));
prop_assert_eq!(schoolbook::<M>(&a, &b).reduce(), reference_mul_192(&a, &b));
prop_assert_eq!(poly_mul_192::<M>(&a, &b), reference_mul_192(&a, &b));
prop_assert_eq!(poly_square_192::<M>(&a), reference_mul_192(&a, &a));
prop_assert_eq!(poly_mul_192_by_64::<M>(&a, &k), reference_mul_192(&a, &[k, 0, 0]));
let sum = |x: [u64; 3], y: [u64; 3]| core::array::from_fn(|i| x[i] ^ y[i]);
prop_assert_eq!(
poly_dot_192::<M>([(&a, &b), (&b, &c)].into_iter()),
sum(reference_mul_192(&a, &b), reference_mul_192(&b, &c))
);
prop_assert_eq!(
poly_dot_192_by_64::<M>([(&a, &k), (&c, &l)].into_iter()),
sum(reference_mul_192(&a, &[k, 0, 0]), reference_mul_192(&c, &[l, 0, 0]))
);
}
}
}