use core::iter::{Product, Sum};
use core::mem::transmute;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use p3_field::op_assign_macros::{
impl_add_assign, impl_add_base_field, impl_div_methods, impl_mul_base_field, impl_mul_methods,
impl_packed_field_div, impl_packed_value, impl_rng, impl_sub_assign, impl_sub_base_field,
impl_sum_prod_base_field, ring_sum,
};
use p3_field::{
Algebra, Field, PackedField, PackedFieldPow2, PackedValue, PrimeCharacteristicRing,
};
use rand::distr::{Distribution, StandardUniform};
use rand::{Rng, RngExt};
use super::gf64::{self as lanes, Reg, WIDTH_64};
#[cfg(all(
target_arch = "x86_64",
target_feature = "avx512f",
not(all(feature = "wide-poly", target_feature = "avx512bw"))
))]
use super::x86_64::pairs;
use crate::clmul::wide::{Lanes64, Wide};
use crate::gf2::characteristic_two_methods;
use crate::{Gf2, Poly64};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]
#[must_use]
pub struct PackedPoly64([Poly64; WIDTH_64]);
impl PackedPoly64 {
#[inline(always)]
pub(crate) fn to_vector(self) -> Reg {
unsafe { transmute(self) }
}
#[inline(always)]
pub(crate) fn from_vector(vector: Reg) -> Self {
unsafe { transmute(vector) }
}
#[inline]
const fn broadcast(value: Poly64) -> Self {
Self([value; WIDTH_64])
}
}
impl From<Poly64> for PackedPoly64 {
#[inline]
fn from(value: Poly64) -> Self {
Self::broadcast(value)
}
}
impl Add for PackedPoly64 {
type Output = Self;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: Self) -> Self {
Self::from_vector(self.to_vector().xor(rhs.to_vector()))
}
}
impl Sub for PackedPoly64 {
type Output = Self;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, rhs: Self) -> Self {
self + rhs
}
}
impl Neg for PackedPoly64 {
type Output = Self;
#[inline]
fn neg(self) -> Self {
self
}
}
impl Mul for PackedPoly64 {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
Self::from_vector(Wide::mul(self.to_vector(), rhs.to_vector()).reduce())
}
}
impl PrimeCharacteristicRing for PackedPoly64 {
type PrimeSubfield = Gf2;
const ZERO: Self = Self::broadcast(Poly64::ZERO);
const ONE: Self = Self::broadcast(Poly64::ONE);
const TWO: Self = Self::broadcast(Poly64::ZERO);
const NEG_ONE: Self = Self::broadcast(Poly64::ONE);
#[inline]
fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
Self::broadcast(Poly64::from_prime_subfield(f))
}
characteristic_two_methods!();
#[inline]
fn square(&self) -> Self {
let x = self.to_vector();
Self::from_vector(Wide::mul(x, x).reduce())
}
#[inline]
fn bool_check(&self) -> Self {
self.square() + *self
}
#[inline]
fn dot_product<const N: usize>(u: &[Self; N], v: &[Self; N]) -> Self {
#[cfg(all(
target_arch = "x86_64",
target_feature = "avx512f",
not(all(feature = "wide-poly", target_feature = "avx512bw"))
))]
let [sum] = pairs::sum_of_products(
u,
v,
|a, b| {
let join = |x: &[Self; 2]| pairs::join(x[0].to_vector(), x[1].to_vector());
[Wide::mul(join(a), join(b))]
},
|a, b| [Wide::mul(a.to_vector(), b.to_vector())],
);
#[cfg(not(all(
target_arch = "x86_64",
target_feature = "avx512f",
not(all(feature = "wide-poly", target_feature = "avx512bw"))
)))]
let sum = u.iter().zip(v).fold(Wide::zero(), |sum, (a, b)| {
sum.xor(Wide::mul(a.to_vector(), b.to_vector()))
});
Self::from_vector(sum.reduce())
}
}
impl_add_assign!(PackedPoly64);
impl_sub_assign!(PackedPoly64);
impl_mul_methods!(PackedPoly64);
ring_sum!(PackedPoly64);
impl_rng!(PackedPoly64);
impl_add_base_field!(PackedPoly64, Poly64);
impl_sub_base_field!(PackedPoly64, Poly64);
impl_mul_base_field!(PackedPoly64, Poly64);
impl_div_methods!(PackedPoly64, Poly64);
impl_packed_field_div!(PackedPoly64);
impl_sum_prod_base_field!(PackedPoly64, Poly64);
impl Algebra<Poly64> for PackedPoly64 {}
impl From<Gf2> for PackedPoly64 {
#[inline]
fn from(x: Gf2) -> Self {
Self::from_prime_subfield(x)
}
}
impl_add_base_field!(PackedPoly64, Gf2);
impl_sub_base_field!(PackedPoly64, Gf2);
impl_mul_base_field!(PackedPoly64, Gf2);
impl Algebra<Gf2> for PackedPoly64 {}
impl_packed_value!(PackedPoly64, Poly64, WIDTH_64);
unsafe impl PackedField for PackedPoly64 {
type Scalar = Poly64;
}
unsafe impl PackedFieldPow2 for PackedPoly64 {
#[inline]
fn interleave(&self, other: Self, block_len: usize) -> (Self, Self) {
let (a, b) = lanes::interleave_64(self.to_vector(), other.to_vector(), block_len);
(Self::from_vector(a), Self::from_vector(b))
}
}
#[cfg(test)]
mod tests {
use p3_field::{PackedValue, PrimeCharacteristicRing};
use p3_field_testing::test_packed_binary_field;
use proptest::prelude::*;
use crate::packed::gf64::WIDTH_64;
use crate::{PackedPoly64, Poly64};
const SPECIAL: [u64; 5] = [0, u64::MAX, 1 << 63, 0x1b, 0xf << 60];
fn specials() -> PackedPoly64 {
PackedValue::from_fn(|i| Poly64::new(SPECIAL[i % SPECIAL.len()]))
}
fn packed(bits: [u64; WIDTH_64]) -> PackedPoly64 {
PackedValue::from_fn(|i| Poly64::new(bits[i]))
}
fn lanes_agree(
packed: PackedPoly64,
scalar: impl Fn(usize) -> Poly64,
) -> Result<(), TestCaseError> {
for (lane, &value) in packed.as_slice().iter().enumerate() {
prop_assert_eq!(value, scalar(lane), "lane {}", lane);
}
Ok(())
}
#[test]
fn every_lane_matches_the_scalar_field_at_the_corners() {
for (i, &x) in SPECIAL.iter().enumerate() {
for (j, &y) in SPECIAL.iter().enumerate() {
let a: [u64; WIDTH_64] =
core::array::from_fn(|l| if l == 0 { x } else { SPECIAL[(i + l) % 5] });
let b: [u64; WIDTH_64] =
core::array::from_fn(|l| if l == 0 { y } else { SPECIAL[(j + l) % 5] });
let (pa, pb) = (packed(a), packed(b));
let lane = |bits: [u64; WIDTH_64], l: usize| Poly64::new(bits[l]);
lanes_agree(pa * pb, |l| lane(a, l) * lane(b, l)).unwrap();
lanes_agree(pa.square(), |l| lane(a, l).square()).unwrap();
}
}
}
fn check_dot_product<const N: usize>() {
let term = |t: usize, step: usize, offset: usize| {
PackedValue::from_fn(|l| Poly64::new(SPECIAL[(step * t + l + offset) % SPECIAL.len()]))
};
let u: [PackedPoly64; N] = core::array::from_fn(|t| term(t, 1, 0));
let v: [PackedPoly64; N] = core::array::from_fn(|t| term(t, 2, 1));
let expected = |l: usize| {
u.iter()
.zip(&v)
.map(|(a, b)| a.as_slice()[l] * b.as_slice()[l])
.sum()
};
lanes_agree(PackedPoly64::dot_product(&u, &v), expected).unwrap();
}
#[test]
fn every_dot_product_length_matches_the_sum_of_products() {
check_dot_product::<0>();
check_dot_product::<1>();
check_dot_product::<2>();
check_dot_product::<3>();
check_dot_product::<4>();
check_dot_product::<5>();
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(2000))]
#[test]
fn every_lane_matches_the_scalar_field(
a in any::<[u64; WIDTH_64]>(),
b in any::<[u64; WIDTH_64]>(),
c in any::<[u64; WIDTH_64]>(),
) {
let (pa, pb, pc) = (packed(a), packed(b), packed(c));
let lane = |bits: [u64; WIDTH_64], l: usize| Poly64::new(bits[l]);
lanes_agree(pa * pb, |l| lane(a, l) * lane(b, l))?;
lanes_agree(pa.square(), |l| lane(a, l).square())?;
lanes_agree(pa.bool_check(), |l| lane(a, l).bool_check())?;
lanes_agree(PackedPoly64::dot_product(&[pa, pb], &[pb, pc]), |l| {
lane(a, l) * lane(b, l) + lane(b, l) * lane(c, l)
})?;
}
}
test_packed_binary_field!(
crate::PackedPoly64,
&[crate::PackedPoly64::ZERO],
&[crate::PackedPoly64::ONE],
super::specials()
);
}