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::split::{HIGH_BY_HIGH, LOW_BY_LOW, Lanes, fold_shifted};
use crate::gf2::characteristic_two_methods;
use crate::{BinaryField128, Gf2, Ghash128};
const SWAP_QUADWORDS: i32 = 0x4e;
#[cfg(all(
target_feature = "avx2",
target_feature = "vpclmulqdq",
not(target_feature = "avx512f")
))]
pub(crate) mod lanes {
use core::arch::x86_64::{
__m256i, _mm_set_epi64x, _mm256_broadcastsi128_si256, _mm256_clmulepi64_epi128,
_mm256_loadu_si256, _mm256_setzero_si256, _mm256_shuffle_epi32, _mm256_storeu_si256,
_mm256_unpacklo_epi64, _mm256_xor_si256,
};
use p3_field::interleave::interleave_u128;
pub(crate) type Reg = __m256i;
pub(crate) const WIDTH: usize = 2;
#[inline(always)]
pub(crate) fn zero() -> Reg {
unsafe { _mm256_setzero_si256() }
}
#[inline(always)]
pub(crate) fn xor(a: Reg, b: Reg) -> Reg {
unsafe { _mm256_xor_si256(a, b) }
}
#[inline(always)]
pub(crate) fn unpack_low_64(a: Reg, b: Reg) -> Reg {
unsafe { _mm256_unpacklo_epi64(a, b) }
}
#[inline(always)]
pub(crate) fn clmul<const IMM: i32>(a: Reg, b: Reg) -> Reg {
unsafe { _mm256_clmulepi64_epi128::<IMM>(a, b) }
}
#[inline(always)]
pub(crate) fn swap_halves(a: Reg) -> Reg {
unsafe { _mm256_shuffle_epi32::<{ super::SWAP_QUADWORDS }>(a) }
}
#[inline(always)]
pub(crate) fn broadcast(value: u128) -> Reg {
unsafe {
let lane = _mm_set_epi64x((value >> 64) as i64, value as i64);
_mm256_broadcastsi128_si256(lane)
}
}
#[inline(always)]
pub(crate) unsafe fn load(from: *const u128) -> Reg {
unsafe { _mm256_loadu_si256(from.cast()) }
}
#[inline(always)]
pub(crate) unsafe fn store(to: *mut u128, value: Reg) {
unsafe { _mm256_storeu_si256(to.cast(), value) }
}
#[inline(always)]
pub(crate) fn interleave(a: Reg, b: Reg, block_len: usize) -> (Reg, Reg) {
match block_len {
1 => interleave_u128(a, b),
WIDTH => (a, b),
_ => panic!("unsupported block_len"),
}
}
}
#[cfg(all(target_feature = "avx512f", target_feature = "vpclmulqdq"))]
pub(crate) mod lanes {
use core::arch::x86_64::{
__m512i, _mm_set_epi64x, _mm512_broadcast_i32x4, _mm512_clmulepi64_epi128,
_mm512_loadu_si512, _mm512_setzero_si512, _mm512_shuffle_epi32, _mm512_storeu_si512,
_mm512_unpacklo_epi64, _mm512_xor_si512,
};
use p3_field::interleave::{interleave_u128, interleave_u256};
pub(crate) type Reg = __m512i;
pub(crate) const WIDTH: usize = 4;
#[inline(always)]
pub(crate) fn zero() -> Reg {
unsafe { _mm512_setzero_si512() }
}
#[inline(always)]
pub(crate) fn xor(a: Reg, b: Reg) -> Reg {
unsafe { _mm512_xor_si512(a, b) }
}
#[inline(always)]
pub(crate) fn unpack_low_64(a: Reg, b: Reg) -> Reg {
unsafe { _mm512_unpacklo_epi64(a, b) }
}
#[inline(always)]
pub(crate) fn clmul<const IMM: i32>(a: Reg, b: Reg) -> Reg {
unsafe { _mm512_clmulepi64_epi128::<IMM>(a, b) }
}
#[inline(always)]
pub(crate) fn swap_halves(a: Reg) -> Reg {
unsafe { _mm512_shuffle_epi32::<{ super::SWAP_QUADWORDS }>(a) }
}
#[inline(always)]
pub(crate) fn broadcast(value: u128) -> Reg {
unsafe {
let lane = _mm_set_epi64x((value >> 64) as i64, value as i64);
_mm512_broadcast_i32x4(lane)
}
}
#[inline(always)]
pub(crate) unsafe fn load(from: *const u128) -> Reg {
unsafe { _mm512_loadu_si512(from.cast()) }
}
#[inline(always)]
pub(crate) unsafe fn store(to: *mut u128, value: Reg) {
unsafe { _mm512_storeu_si512(to.cast(), value) }
}
#[inline(always)]
pub(crate) fn interleave(a: Reg, b: Reg, block_len: usize) -> (Reg, Reg) {
match block_len {
1 => interleave_u128(a, b),
2 => interleave_u256(a, b),
WIDTH => (a, b),
_ => panic!("unsupported block_len"),
}
}
}
use lanes::WIDTH;
impl Lanes for lanes::Reg {
#[inline(always)]
fn zero() -> Self {
lanes::zero()
}
#[inline(always)]
fn broadcast(value: u128) -> Self {
lanes::broadcast(value)
}
#[inline(always)]
fn xor(self, other: Self) -> Self {
lanes::xor(self, other)
}
#[inline(always)]
fn unpack_low_64(self, other: Self) -> Self {
lanes::unpack_low_64(self, other)
}
#[inline(always)]
fn clmul<const IMM: i32>(self, other: Self) -> Self {
lanes::clmul::<IMM>(self, other)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]
#[must_use]
pub struct PackedGhash128([Ghash128; WIDTH]);
impl PackedGhash128 {
#[inline]
#[must_use]
fn to_vector(self) -> lanes::Reg {
unsafe { transmute(self) }
}
#[inline]
fn from_vector(vector: lanes::Reg) -> Self {
unsafe { transmute(vector) }
}
#[inline]
const fn broadcast(value: Ghash128) -> Self {
Self([value; WIDTH])
}
}
impl From<Ghash128> for PackedGhash128 {
#[inline]
fn from(value: Ghash128) -> Self {
Self::broadcast(value)
}
}
impl Add for PackedGhash128 {
type Output = Self;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: Self) -> Self {
Self::from_vector(lanes::xor(self.to_vector(), rhs.to_vector()))
}
}
impl Sub for PackedGhash128 {
type Output = Self;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, rhs: Self) -> Self {
self + rhs
}
}
impl Neg for PackedGhash128 {
type Output = Self;
#[inline]
fn neg(self) -> Self {
self
}
}
impl Mul for PackedGhash128 {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
let (x, y) = (self.to_vector(), rhs.to_vector());
let low = lanes::clmul::<LOW_BY_LOW>(x, y);
let high = lanes::clmul::<HIGH_BY_HIGH>(x, y);
let mixed_x = lanes::xor(x, lanes::swap_halves(x));
let mixed_y = lanes::xor(y, lanes::swap_halves(y));
let middle = lanes::xor(
lanes::xor(low, high),
lanes::clmul::<LOW_BY_LOW>(mixed_x, mixed_y),
);
Self::from_vector(fold_shifted(low, fold_shifted(middle, high)))
}
}
impl PrimeCharacteristicRing for PackedGhash128 {
type PrimeSubfield = Gf2;
const ZERO: Self = Self::broadcast(Ghash128::ZERO);
const ONE: Self = Self::broadcast(Ghash128::ONE);
const TWO: Self = Self::broadcast(Ghash128::ZERO);
const NEG_ONE: Self = Self::broadcast(Ghash128::ONE);
#[inline]
fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
Self::broadcast(Ghash128::from_prime_subfield(f))
}
characteristic_two_methods!();
#[inline]
fn square(&self) -> Self {
let x = self.to_vector();
let low = lanes::clmul::<LOW_BY_LOW>(x, x);
let high = lanes::clmul::<HIGH_BY_HIGH>(x, x);
Self::from_vector(fold_shifted(low, fold_shifted(lanes::zero(), high)))
}
#[inline]
fn bool_check(&self) -> Self {
self.square() + *self
}
#[inline]
fn dot_product<const N: usize>(u: &[Self; N], v: &[Self; N]) -> Self {
let (mut low, mut high, mut middle) = (lanes::zero(), lanes::zero(), lanes::zero());
for (a, b) in u.iter().zip(v) {
let (x, y) = (a.to_vector(), b.to_vector());
low = lanes::xor(low, lanes::clmul::<LOW_BY_LOW>(x, y));
high = lanes::xor(high, lanes::clmul::<HIGH_BY_HIGH>(x, y));
let mixed_x = lanes::xor(x, lanes::swap_halves(x));
let mixed_y = lanes::xor(y, lanes::swap_halves(y));
middle = lanes::xor(middle, lanes::clmul::<LOW_BY_LOW>(mixed_x, mixed_y));
}
middle = lanes::xor(middle, lanes::xor(low, high));
Self::from_vector(fold_shifted(low, fold_shifted(middle, high)))
}
}
impl_add_assign!(PackedGhash128);
impl_sub_assign!(PackedGhash128);
impl_mul_methods!(PackedGhash128);
ring_sum!(PackedGhash128);
impl_rng!(PackedGhash128);
impl_add_base_field!(PackedGhash128, Ghash128);
impl_sub_base_field!(PackedGhash128, Ghash128);
impl_mul_base_field!(PackedGhash128, Ghash128);
impl_div_methods!(PackedGhash128, Ghash128);
impl_packed_field_div!(PackedGhash128);
impl_sum_prod_base_field!(PackedGhash128, Ghash128);
impl Algebra<Ghash128> for PackedGhash128 {}
impl From<Gf2> for PackedGhash128 {
#[inline]
fn from(x: Gf2) -> Self {
Self::from_prime_subfield(x)
}
}
impl_add_base_field!(PackedGhash128, Gf2);
impl_sub_base_field!(PackedGhash128, Gf2);
impl_mul_base_field!(PackedGhash128, Gf2);
impl Algebra<Gf2> for PackedGhash128 {}
impl From<BinaryField128> for PackedGhash128 {
#[inline]
fn from(x: BinaryField128) -> Self {
Self::broadcast(Ghash128::from(x))
}
}
impl_add_base_field!(PackedGhash128, BinaryField128);
impl_sub_base_field!(PackedGhash128, BinaryField128);
impl_mul_base_field!(PackedGhash128, BinaryField128);
impl Algebra<BinaryField128> for PackedGhash128 {}
impl_packed_value!(PackedGhash128, Ghash128, WIDTH);
unsafe impl PackedField for PackedGhash128 {
type Scalar = Ghash128;
}
unsafe impl PackedFieldPow2 for PackedGhash128 {
#[inline]
fn interleave(&self, other: Self, block_len: usize) -> (Self, Self) {
let (a, b) = lanes::interleave(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 super::lanes::{self, WIDTH};
use crate::packed::split::model::Model;
use crate::packed::split::{
HIGH_BY_HIGH, HIGH_BY_LOW, LOW_BY_HIGH, LOW_BY_LOW, Lanes, SplitScalar, fold_shifted,
};
use crate::{Ghash128, PackedGhash128};
const SPECIAL: [u128; 4] = [0, u128::MAX, 1 << 127, 0x87];
fn specials() -> PackedGhash128 {
PackedValue::from_fn(|i| Ghash128::from_le_bytes(SPECIAL[i].to_le_bytes()))
}
fn lanes_of(register: lanes::Reg) -> [u128; WIDTH] {
let mut out = [0u128; WIDTH];
unsafe { lanes::store(out.as_mut_ptr(), register) };
out
}
fn register_of(values: [u128; WIDTH]) -> lanes::Reg {
unsafe { lanes::load(values.as_ptr()) }
}
fn swapped_halves(values: [u128; WIDTH]) -> [u128; WIDTH] {
core::array::from_fn(|i| values[i].rotate_left(64))
}
fn lanes_conform(
a: [u128; WIDTH],
b: [u128; WIDTH],
scalar: u128,
) -> Result<(), TestCaseError> {
let (x, y) = (register_of(a), register_of(b));
let (mx, my) = (Model(a), Model(b));
prop_assert_eq!(lanes_of(lanes::Reg::zero()), Model::<WIDTH>::zero().0);
prop_assert_eq!(
lanes_of(lanes::Reg::broadcast(scalar)),
Model::<WIDTH>::broadcast(scalar).0
);
prop_assert_eq!(lanes_of(lanes::Reg::tail()), Model::<WIDTH>::tail().0);
prop_assert_eq!(lanes_of(x.xor(y)), mx.xor(my).0);
prop_assert_eq!(lanes_of(x.unpack_low_64(y)), mx.unpack_low_64(my).0);
prop_assert_eq!(
lanes_of(x.clmul::<LOW_BY_LOW>(y)),
mx.clmul::<LOW_BY_LOW>(my).0
);
prop_assert_eq!(
lanes_of(x.clmul::<HIGH_BY_LOW>(y)),
mx.clmul::<HIGH_BY_LOW>(my).0
);
prop_assert_eq!(
lanes_of(x.clmul::<LOW_BY_HIGH>(y)),
mx.clmul::<LOW_BY_HIGH>(my).0
);
prop_assert_eq!(
lanes_of(x.clmul::<HIGH_BY_HIGH>(y)),
mx.clmul::<HIGH_BY_HIGH>(my).0
);
prop_assert_eq!(lanes_of(lanes::swap_halves(x)), swapped_halves(a));
prop_assert_eq!(lanes_of(fold_shifted(x, y)), fold_shifted(mx, my).0);
prop_assert_eq!(
lanes_of(SplitScalar::new(scalar).apply(x)),
SplitScalar::new(scalar).apply(mx).0
);
Ok(())
}
#[test]
fn the_register_matches_the_scalar_model_at_the_corners() {
for (i, &x) in SPECIAL.iter().enumerate() {
for (j, &y) in SPECIAL.iter().enumerate() {
let a = core::array::from_fn(|lane| match lane {
0 => x,
_ => SPECIAL[(i + lane) % SPECIAL.len()],
});
let b = core::array::from_fn(|lane| match lane {
0 => y,
_ => SPECIAL[(j + lane) % SPECIAL.len()],
});
for scalar in SPECIAL {
lanes_conform(a, b, scalar)
.unwrap_or_else(|e| panic!("a {x:#x}, b {y:#x}, scalar {scalar:#x}: {e}"));
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn the_register_matches_the_scalar_model(
a in prop::array::uniform(any::<u128>()),
b in prop::array::uniform(any::<u128>()),
scalar in any::<u128>(),
) {
lanes_conform(a, b, scalar)?;
}
}
#[test]
fn bool_check_matches_the_general_product_in_every_lane() {
for (index, &value) in SPECIAL.iter().enumerate() {
let lanes: [Ghash128; WIDTH] = core::array::from_fn(|lane| {
let pattern = if lane == 0 {
value
} else {
SPECIAL[(index + lane) % SPECIAL.len()]
};
Ghash128::from_le_bytes(pattern.to_le_bytes())
});
let packed: PackedGhash128 = PackedValue::from_fn(|lane| lanes[lane]);
for (lane, &scalar) in lanes.iter().enumerate() {
assert_eq!(
packed.bool_check().as_slice()[lane],
scalar * (scalar - Ghash128::ONE),
"lane {lane} of corner {value:#x}"
);
assert_eq!(packed.bool_check().as_slice()[lane], scalar.bool_check());
}
}
for root in [Ghash128::ZERO, Ghash128::ONE] {
let packed: PackedGhash128 = PackedValue::from_fn(|_| root);
assert!(
packed
.bool_check()
.as_slice()
.iter()
.all(|&value| value == Ghash128::ZERO)
);
}
}
test_packed_binary_field!(
crate::PackedGhash128,
&[crate::PackedGhash128::ZERO],
&[crate::PackedGhash128::ONE],
super::specials()
);
}