use core::ops::Mul;
use p3_binary_field::poly_basis::HAS_HARDWARE_CLMUL;
use p3_binary_field::{
BinaryField2, BinaryField4, BinaryField8, BinaryField16, BinaryField32, BinaryField64,
BinaryField128, Gf2, Ghash128, Poly64, TowerLevel,
};
use p3_field::{PackedValue, PrimeCharacteristicRing};
pub trait ButterflyField: TowerLevel {
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self);
}
unsafe trait ByteCoordinates: TowerLevel {}
unsafe impl ByteCoordinates for BinaryField8 {}
unsafe impl ByteCoordinates for BinaryField16 {}
unsafe impl ByteCoordinates for BinaryField32 {}
unsafe impl ByteCoordinates for BinaryField64 {}
unsafe impl ByteCoordinates for BinaryField128 {}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum TwiddleWidth {
Byte(u8),
Word(u16),
DoubleWord(u32),
Wide,
}
const fn classify(bits: u128) -> TwiddleWidth {
if bits <= u8::MAX as u128 {
TwiddleWidth::Byte(bits as u8)
} else if bits <= u16::MAX as u128 {
TwiddleWidth::Word(bits as u16)
} else if bits <= u32::MAX as u128 {
TwiddleWidth::DoubleWord(bits as u32)
} else {
TwiddleWidth::Wide
}
}
#[inline]
pub(crate) fn packed_butterfly<F: TowerLevel, const INVERSE: bool>(
lo: &mut [F],
hi: &mut [F],
t: F,
) {
let (lo, lo_tail) = F::Packing::pack_slice_with_suffix_mut(lo);
let (hi, hi_tail) = F::Packing::pack_slice_with_suffix_mut(hi);
let zero = t.is_zero();
butterfly_values::<_, INVERSE>(lo, hi, t.into(), zero);
butterfly_values::<_, INVERSE>(lo_tail, hi_tail, t, zero);
}
#[inline]
fn butterfly_values<R: PrimeCharacteristicRing + Copy, const INVERSE: bool>(
lo: &mut [R],
hi: &mut [R],
t: R,
zero: bool,
) {
if zero {
for (u, v) in lo.iter_mut().zip(hi) {
*v += *u;
}
} else if INVERSE {
for (u, v) in lo.iter_mut().zip(hi) {
*v += *u;
*u += t * *v;
}
} else {
for (u, v) in lo.iter_mut().zip(hi) {
*u += t * *v;
*v += *u;
}
}
}
#[inline]
fn typed_butterfly<F, S, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: S)
where
F: Copy + PrimeCharacteristicRing + Mul<S, Output = F>,
S: Copy,
{
if INVERSE {
for (u, v) in lo.iter_mut().zip(hi) {
*v += *u;
*u += *v * t;
}
} else {
for (u, v) in lo.iter_mut().zip(hi) {
*u += *v * t;
*v += *u;
}
}
}
#[inline]
fn scale_by_byte<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F, width: TwiddleWidth)
where
F: TowerLevel + Mul<BinaryField8, Output = F>,
{
if let TwiddleWidth::Byte(s) = width {
typed_butterfly::<F, BinaryField8, INVERSE>(lo, hi, BinaryField8::from_repr(s));
} else {
packed_butterfly::<F, INVERSE>(lo, hi, t);
}
}
#[inline]
fn scale_by_word<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F, width: TwiddleWidth)
where
F: TowerLevel + Mul<BinaryField8, Output = F> + Mul<BinaryField16, Output = F>,
{
if let TwiddleWidth::Word(s) = width {
typed_butterfly::<F, BinaryField16, INVERSE>(lo, hi, BinaryField16::from_repr(s));
} else {
scale_by_byte::<F, INVERSE>(lo, hi, t, width);
}
}
#[inline]
fn scale_by_dword<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F, width: TwiddleWidth)
where
F: TowerLevel
+ Mul<BinaryField8, Output = F>
+ Mul<BinaryField16, Output = F>
+ Mul<BinaryField32, Output = F>,
{
if let TwiddleWidth::DoubleWord(s) = width {
typed_butterfly::<F, BinaryField32, INVERSE>(lo, hi, BinaryField32::from_repr(s));
} else {
scale_by_word::<F, INVERSE>(lo, hi, t, width);
}
}
const WIDE_TYPED_PRODUCTS_PAY: bool = !HAS_HARDWARE_CLMUL;
trait SubfieldScaled: ByteCoordinates {
fn scale_butterfly<const INVERSE: bool>(
lo: &mut [Self],
hi: &mut [Self],
t: Self,
width: TwiddleWidth,
);
}
impl SubfieldScaled for BinaryField8 {
#[inline]
fn scale_butterfly<const INVERSE: bool>(
lo: &mut [Self],
hi: &mut [Self],
t: Self,
width: TwiddleWidth,
) {
scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
}
}
impl SubfieldScaled for BinaryField16 {
#[inline]
fn scale_butterfly<const INVERSE: bool>(
lo: &mut [Self],
hi: &mut [Self],
t: Self,
width: TwiddleWidth,
) {
scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
}
}
impl SubfieldScaled for BinaryField32 {
#[inline]
fn scale_butterfly<const INVERSE: bool>(
lo: &mut [Self],
hi: &mut [Self],
t: Self,
width: TwiddleWidth,
) {
scale_by_word::<Self, INVERSE>(lo, hi, t, width);
}
}
impl SubfieldScaled for BinaryField64 {
#[inline]
fn scale_butterfly<const INVERSE: bool>(
lo: &mut [Self],
hi: &mut [Self],
t: Self,
width: TwiddleWidth,
) {
if WIDE_TYPED_PRODUCTS_PAY {
scale_by_dword::<Self, INVERSE>(lo, hi, t, width);
} else {
scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
}
}
}
impl SubfieldScaled for BinaryField128 {
#[inline]
fn scale_butterfly<const INVERSE: bool>(
lo: &mut [Self],
hi: &mut [Self],
t: Self,
width: TwiddleWidth,
) {
if WIDE_TYPED_PRODUCTS_PAY {
scale_by_dword::<Self, INVERSE>(lo, hi, t, width);
} else {
scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
}
}
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
#[inline]
fn subfield_prefix<F: ByteCoordinates, const INVERSE: bool>(
lo: &mut [F],
hi: &mut [F],
width: TwiddleWidth,
) -> usize {
use core::arch::x86_64::__m512i;
use crate::affine;
use crate::lanes::ByteRegister;
const {
assert!(cfg!(target_endian = "little"));
assert!(size_of::<F>() == 1 << (F::LOG_BITS - 3));
assert!(64 % size_of::<F>() == 0);
}
let bytes = size_of_val(lo);
if bytes < <__m512i as ByteRegister>::BYTES {
return 0;
}
let (lo, hi) = unsafe {
(
core::slice::from_raw_parts_mut(lo.as_mut_ptr().cast::<u8>(), bytes),
core::slice::from_raw_parts_mut(hi.as_mut_ptr().cast::<u8>(), bytes),
)
};
let covered = match width {
TwiddleWidth::Byte(t) => {
affine::subfield_butterfly::<__m512i, 1, INVERSE>(lo, hi, &affine::byte_blocks(t))
}
TwiddleWidth::Word(t) if size_of::<F>() >= 2 => {
affine::subfield_butterfly::<__m512i, 2, INVERSE>(lo, hi, &affine::word_blocks(t))
}
TwiddleWidth::DoubleWord(t) if size_of::<F>() >= 4 => {
affine::subfield_butterfly::<__m512i, 4, INVERSE>(lo, hi, &affine::dword_blocks(t))
}
_ => 0,
};
covered / size_of::<F>()
}
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
))]
#[inline]
fn subfield_prefix<F: ByteCoordinates, const INVERSE: bool>(
lo: &mut [F],
hi: &mut [F],
width: TwiddleWidth,
) -> usize {
use core::arch::aarch64::uint8x16_t;
use crate::lanes::ByteRegister;
use crate::neon;
const {
assert!(size_of::<F>() == 1 << (F::LOG_BITS - 3));
assert!(<uint8x16_t as ByteRegister>::BYTES.is_multiple_of(size_of::<F>()));
}
let TwiddleWidth::Byte(t) = width else {
return 0;
};
let bytes = size_of_val(lo);
if bytes < neon::MIN_BYTES {
return 0;
}
let (lo, hi) = unsafe {
(
core::slice::from_raw_parts_mut(lo.as_mut_ptr().cast::<u8>(), bytes),
core::slice::from_raw_parts_mut(hi.as_mut_ptr().cast::<u8>(), bytes),
)
};
let t = BinaryField8::from_repr(t);
let columns: [u8; 8] =
core::array::from_fn(|b| (t * BinaryField8::from_repr(1 << b)).to_repr());
neon::byte_butterfly::<INVERSE>(lo, hi, &columns) / size_of::<F>()
}
#[cfg(not(any(
all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
),
all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
)
)))]
#[inline]
const fn subfield_prefix<F: ByteCoordinates, const INVERSE: bool>(
_lo: &mut [F],
_hi: &mut [F],
_width: TwiddleWidth,
) -> usize {
0
}
#[inline]
fn coordinate_butterfly<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F)
where
F: SubfieldScaled,
F::Repr: Into<u128>,
{
assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
if t.is_zero() {
return packed_butterfly::<F, INVERSE>(lo, hi, t);
}
let width = classify(t.to_repr().into());
let covered = subfield_prefix::<F, INVERSE>(lo, hi, width);
F::scale_butterfly::<INVERSE>(&mut lo[covered..], &mut hi[covered..], t, width);
}
#[inline]
fn plain_butterfly<F: TowerLevel, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F) {
assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
packed_butterfly::<F, INVERSE>(lo, hi, t);
}
impl ButterflyField for BinaryField8 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for BinaryField16 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for BinaryField32 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for BinaryField64 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for BinaryField128 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for Gf2 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
plain_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for BinaryField2 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
plain_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for BinaryField4 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
plain_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for Ghash128 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
plain_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl ButterflyField for Poly64 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
plain_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use p3_binary_field::TowerLevel;
use p3_field::PrimeCharacteristicRing;
use proptest::prelude::*;
use super::{
BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, ButterflyField,
Ghash128, TwiddleWidth, classify,
};
const CORNERS: [u128; 13] = [
0,
1,
0x80,
0xff,
0x100,
0x8000,
0xffff,
0x1_0000,
0x8000_0000,
0xffff_ffff,
0x1_0000_0000,
1 << 127,
0x87,
];
const LENGTHS: core::ops::RangeInclusive<usize> = 0..=70;
fn sample<F: TowerLevel>(len: usize, seed: u64) -> Vec<F> {
(0..len)
.map(|i| {
let bits = seed
.wrapping_mul(0x9e37_79b9_7f4a_7c15)
.wrapping_add(i as u64 + 1)
.wrapping_mul(0xbf58_476d_1ce4_e5b9);
F::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
})
.collect()
}
fn reference<F: TowerLevel>(lo: &mut [F], hi: &mut [F], t: F, inverse: bool) {
for (u, v) in lo.iter_mut().zip(hi) {
if inverse {
*v += *u;
*u += t * *v;
} else {
*u += t * *v;
*v += *u;
}
}
}
fn agrees<F: ButterflyField>(len: usize, t: F) -> Result<(), TestCaseError> {
let (lo, hi) = (sample::<F>(len, 1), sample::<F>(len, 2));
let (mut want_lo, mut want_hi) = (lo.clone(), hi.clone());
reference(&mut want_lo, &mut want_hi, t, false);
let (mut got_lo, mut got_hi) = (lo.clone(), hi.clone());
F::butterfly::<false>(&mut got_lo, &mut got_hi, t);
prop_assert_eq!(&got_lo, &want_lo);
prop_assert_eq!(&got_hi, &want_hi);
F::butterfly::<true>(&mut got_lo, &mut got_hi, t);
prop_assert_eq!(&got_lo, &lo);
prop_assert_eq!(&got_hi, &hi);
let (mut want_lo, mut want_hi) = (lo.clone(), hi.clone());
reference(&mut want_lo, &mut want_hi, t, true);
let (mut got_lo, mut got_hi) = (lo, hi);
F::butterfly::<true>(&mut got_lo, &mut got_hi, t);
prop_assert_eq!(&got_lo, &want_lo);
prop_assert_eq!(&got_hi, &want_hi);
Ok(())
}
fn sweep<F: ButterflyField>(name: &str) {
for &bits in &CORNERS {
let t =
F::from_le_byte_iter(bits.to_le_bytes().into_iter().chain(core::iter::repeat(0)));
for len in LENGTHS {
agrees::<F>(len, t)
.unwrap_or_else(|e| panic!("{name}, twiddle {bits:#x}, len {len}: {e}"));
}
}
}
#[test]
fn every_level_agrees_with_the_element_loop_at_the_corners() {
sweep::<BinaryField8>("BinaryField8");
sweep::<BinaryField16>("BinaryField16");
sweep::<BinaryField32>("BinaryField32");
sweep::<BinaryField64>("BinaryField64");
sweep::<BinaryField128>("BinaryField128");
sweep::<Ghash128>("Ghash128");
}
#[test]
fn one_byte_twiddles_agree_around_every_register_boundary() {
const LENS: [usize; 14] = [0, 1, 15, 16, 17, 31, 32, 33, 255, 256, 257, 511, 512, 513];
for t in [0x01u8, 0x02, 0x03, 0x80, 0xa5, 0xff] {
for len in LENS {
agrees::<BinaryField8>(len, BinaryField8::from_repr(t)).unwrap();
agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16)).unwrap();
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32)).unwrap();
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64)).unwrap();
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128)).unwrap();
}
}
}
#[test]
fn the_classification_follows_the_twiddle_magnitude() {
assert_eq!(classify(0), TwiddleWidth::Byte(0));
assert_eq!(classify(0xff), TwiddleWidth::Byte(0xff));
assert_eq!(classify(0x100), TwiddleWidth::Word(0x100));
assert_eq!(classify(0xffff), TwiddleWidth::Word(0xffff));
assert_eq!(classify(0x1_0000), TwiddleWidth::DoubleWord(0x1_0000));
assert_eq!(classify(0xffff_ffff), TwiddleWidth::DoubleWord(0xffff_ffff));
assert_eq!(classify(0x1_0000_0000), TwiddleWidth::Wide);
assert_eq!(classify(u128::MAX), TwiddleWidth::Wide);
}
#[test]
#[should_panic = "butterfly lengths differ"]
fn a_length_mismatch_is_refused() {
let mut lo = [BinaryField32::ONE; 4];
let mut hi = [BinaryField32::ONE; 3];
BinaryField32::butterfly::<false>(&mut lo, &mut hi, BinaryField32::ONE);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn random_twiddles_agree_with_the_element_loop(
bits in any::<u128>(),
len in 0usize..70,
) {
let bytes = bits.to_le_bytes();
agrees::<BinaryField32>(len, BinaryField32::from_le_byte_iter(bytes.into_iter()))?;
agrees::<BinaryField64>(len, BinaryField64::from_le_byte_iter(bytes.into_iter()))?;
agrees::<BinaryField128>(len, BinaryField128::from_le_byte_iter(bytes.into_iter()))?;
}
#[test]
fn byte_twiddles_agree_with_the_element_loop(t in any::<u8>(), len in 0usize..70) {
agrees::<BinaryField8>(len, BinaryField8::from_repr(t))?;
agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16))?;
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
#[test]
fn byte_twiddles_agree_on_runs_past_the_register_threshold(
t in any::<u8>(),
len in 0usize..600,
) {
agrees::<BinaryField8>(len, BinaryField8::from_repr(t))?;
agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16))?;
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
#[test]
fn word_twiddles_agree_with_the_element_loop(t in 0x100u16.., len in 0usize..70) {
agrees::<BinaryField16>(len, BinaryField16::from_repr(t))?;
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
#[test]
fn double_word_twiddles_agree_with_the_element_loop(
t in 0x1_0000u32..,
len in 0usize..70,
) {
agrees::<BinaryField32>(len, BinaryField32::from_repr(t))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
}
}