use core::ops::Mul;
use p3_binary_field::poly_basis::HAS_HARDWARE_CLMUL;
use p3_binary_field::{
BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, TowerLevel,
};
use p3_field::PrimeCharacteristicRing;
use super::packed_butterfly;
#[cfg(any(
test,
all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
)
))]
mod gfni;
#[cfg(any(
test,
all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
),
all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
)
))]
mod lanes;
#[cfg(test)]
mod model;
#[cfg(any(
test,
all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
)
))]
mod neon;
pub(super) 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)]
pub(super) enum TwiddleWidth {
Byte(u8),
Word(u16),
DoubleWord(u32),
Wide,
}
impl TwiddleWidth {
pub(super) const fn of(bits: u128) -> Self {
if bits <= u8::MAX as u128 {
Self::Byte(bits as u8)
} else if bits <= u16::MAX as u128 {
Self::Word(bits as u16)
} else if bits <= u32::MAX as u128 {
Self::DoubleWord(bits as u32)
} else {
Self::Wide
}
}
}
const WIDE_TYPED_PRODUCTS_PAY: bool = !HAS_HARDWARE_CLMUL;
#[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 by_byte<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F, width: TwiddleWidth)
where
F: TowerLevel + Mul<BinaryField8, Output = F>,
{
match width {
TwiddleWidth::Byte(s) => {
typed_butterfly::<F, _, INVERSE>(lo, hi, BinaryField8::from_repr(s));
}
_ => packed_butterfly::<F, INVERSE>(lo, hi, t),
}
}
#[inline]
fn 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>,
{
match width {
TwiddleWidth::Word(s) => {
typed_butterfly::<F, _, INVERSE>(lo, hi, BinaryField16::from_repr(s));
}
_ => by_byte::<F, INVERSE>(lo, hi, t, width),
}
}
#[inline]
fn by_double_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>
+ Mul<BinaryField32, Output = F>,
{
match width {
TwiddleWidth::DoubleWord(s) => {
typed_butterfly::<F, _, INVERSE>(lo, hi, BinaryField32::from_repr(s));
}
_ => by_word::<F, INVERSE>(lo, hi, t, width),
}
}
pub(super) 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,
) {
packed_butterfly::<Self, INVERSE>(lo, hi, t);
}
}
impl SubfieldScaled for BinaryField16 {
#[inline]
fn scale_butterfly<const INVERSE: bool>(
lo: &mut [Self],
hi: &mut [Self],
t: Self,
width: TwiddleWidth,
) {
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,
) {
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 {
by_double_word::<Self, INVERSE>(lo, hi, t, width);
} else {
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 {
by_double_word::<Self, INVERSE>(lo, hi, t, width);
} else {
by_byte::<Self, INVERSE>(lo, hi, t, width);
}
}
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
pub(super) const fn byte_map_twiddle_bits(row_bytes: usize) -> usize {
use core::arch::x86_64::__m512i;
use lanes::ByteRegister;
if row_bytes.is_multiple_of(<__m512i as ByteRegister>::BYTES) {
32
} else {
8
}
}
#[cfg(not(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
)))]
pub(super) const fn byte_map_twiddle_bits(_row_bytes: usize) -> usize {
8
}
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
#[inline]
fn register_prefix<F: ByteCoordinates, const INVERSE: bool>(
lo: &mut [F],
hi: &mut [F],
width: TwiddleWidth,
) -> usize {
use core::arch::x86_64::__m512i;
use 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) = as_bytes(lo, hi);
let covered = match width {
TwiddleWidth::Byte(t) => {
gfni::subfield_butterfly::<__m512i, 1, INVERSE>(lo, hi, &gfni::byte_blocks(t))
}
TwiddleWidth::Word(t) if size_of::<F>() >= 2 => {
gfni::subfield_butterfly::<__m512i, 2, INVERSE>(lo, hi, &gfni::word_blocks(t))
}
TwiddleWidth::DoubleWord(t) if size_of::<F>() >= 4 => {
gfni::subfield_butterfly::<__m512i, 4, INVERSE>(lo, hi, &gfni::dword_blocks(t))
}
_ => 0,
};
covered / size_of::<F>()
}
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
))]
#[inline]
fn register_prefix<F: ByteCoordinates, const INVERSE: bool>(
lo: &mut [F],
hi: &mut [F],
width: TwiddleWidth,
) -> usize {
use core::arch::aarch64::uint8x16_t;
use lanes::ByteRegister;
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) = as_bytes(lo, hi);
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 register_prefix<F: ByteCoordinates, const INVERSE: bool>(
_lo: &mut [F],
_hi: &mut [F],
_width: TwiddleWidth,
) -> usize {
0
}
#[cfg(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]
fn as_bytes<'a, F: ByteCoordinates>(
lo: &'a mut [F],
hi: &'a mut [F],
) -> (&'a mut [u8], &'a mut [u8]) {
let bytes = size_of_val(lo);
debug_assert_eq!(bytes, size_of_val(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),
)
}
}
#[inline]
pub(super) 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 = TwiddleWidth::of(t.to_repr().into());
let covered = register_prefix::<F, INVERSE>(lo, hi, width);
F::scale_butterfly::<INVERSE>(&mut lo[covered..], &mut hi[covered..], t, width);
}
#[cfg(test)]
mod tests {
use super::{TwiddleWidth, byte_map_twiddle_bits};
#[test]
fn the_classification_follows_the_twiddle_magnitude() {
assert_eq!(TwiddleWidth::of(0), TwiddleWidth::Byte(0));
assert_eq!(TwiddleWidth::of(0xff), TwiddleWidth::Byte(0xff));
assert_eq!(TwiddleWidth::of(0x100), TwiddleWidth::Word(0x100));
assert_eq!(TwiddleWidth::of(0xffff), TwiddleWidth::Word(0xffff));
assert_eq!(
TwiddleWidth::of(0x1_0000),
TwiddleWidth::DoubleWord(0x1_0000)
);
assert_eq!(
TwiddleWidth::of(0xffff_ffff),
TwiddleWidth::DoubleWord(0xffff_ffff)
);
assert_eq!(TwiddleWidth::of(0x1_0000_0000), TwiddleWidth::Wide);
assert_eq!(TwiddleWidth::of(u128::MAX), TwiddleWidth::Wide);
}
#[test]
fn a_row_short_of_whole_registers_counts_only_one_byte() {
for row_bytes in [16, 32, 48, 80] {
assert_eq!(byte_map_twiddle_bits(row_bytes), 8, "row_bytes={row_bytes}");
}
}
}