#[cfg(not(miri))]
use core::arch::x86_64::_mm512_ternarylogic_epi64;
use core::arch::x86_64::{
__m128i, __m512i, _mm_cvtsi64_si128, _mm_cvtsi128_si64, _mm512_broadcastq_epi64,
_mm512_castsi512_si128, _mm512_gf2p8affine_epi64_epi8, _mm512_loadu_si512,
_mm512_permutexvar_epi8, _mm512_rol_epi64, _mm512_xor_si512,
};
use crate::clmul::gf64::repeated_square;
#[cfg(not(miri))]
const XOR3: i32 = 0x96;
struct Matrices<const K: usize>([[u64; 8]; 8]);
impl<const K: usize> Matrices<K> {
const NEW: Self = Self::new();
const fn new() -> Self {
let mut columns = [0u64; 64];
let mut c = 0;
while c < 64 {
columns[c] = repeated_square(1 << c, K);
c += 1;
}
let mut matrices = [[0u64; 8]; 8];
let mut t = 0;
while t < 8 {
let mut q = 0;
while q < 8 {
let j = (q + 8 - t) % 8;
let mut block = 0u64;
let mut i = 0;
while i < 8 {
let mut row = 0u64;
let mut s = 0;
while s < 8 {
row |= ((columns[8 * j + s] >> (8 * q + i)) & 1) << s;
s += 1;
}
block |= row << (8 * (7 - i));
i += 1;
}
matrices[t][q] = block;
q += 1;
}
t += 1;
}
Self(matrices)
}
#[inline(always)]
fn register(t: usize) -> __m512i {
let table: &'static [[u64; 8]; 8] = const { &Self::NEW.0 };
unsafe { _mm512_loadu_si512(table[t].as_ptr().cast()) }
}
}
const DIAGONAL: [u8; 64] = {
let mut index = [0u8; 64];
let mut q = 0;
while q < 8 {
index[q] = (9 * q) as u8;
q += 1;
}
index
};
#[inline]
pub(crate) fn square_times<const K: usize>(x: u64) -> u64 {
unsafe {
let product = |t: usize, rotated: __m512i| {
_mm512_gf2p8affine_epi64_epi8::<0>(rotated, Matrices::<K>::register(t))
};
let x: __m128i = _mm_cvtsi64_si128(x as i64);
let b = _mm512_broadcastq_epi64(x);
let terms = [
product(0, b),
product(1, _mm512_rol_epi64::<8>(b)),
product(2, _mm512_rol_epi64::<16>(b)),
product(3, _mm512_rol_epi64::<24>(b)),
product(4, _mm512_rol_epi64::<32>(b)),
product(5, _mm512_rol_epi64::<40>(b)),
product(6, _mm512_rol_epi64::<48>(b)),
product(7, _mm512_rol_epi64::<56>(b)),
];
#[cfg(not(miri))]
let sum = _mm512_ternarylogic_epi64::<XOR3>(
_mm512_ternarylogic_epi64::<XOR3>(terms[0], terms[1], terms[2]),
_mm512_ternarylogic_epi64::<XOR3>(terms[3], terms[4], terms[5]),
_mm512_xor_si512(terms[6], terms[7]),
);
#[cfg(miri)]
let sum = terms[1..]
.iter()
.fold(terms[0], |acc, &term| _mm512_xor_si512(acc, term));
let diagonal = _mm512_loadu_si512(DIAGONAL.as_ptr().cast());
_mm_cvtsi128_si64(_mm512_castsi512_si128(_mm512_permutexvar_epi8(
diagonal, sum,
))) as u64
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::square_times;
use crate::clmul::gf64::repeated_square;
use crate::clmul::poly_square_64;
const CORNERS: [u64; 5] = [0, 1, u64::MAX, 1 << 63, 0xf << 60];
#[test]
fn the_reference_squaring_matches_the_field() {
for x in CORNERS {
assert_eq!(repeated_square(x, 1), poly_square_64(x), "{x:#x}");
}
}
#[test]
fn the_matrix_product_is_exact_on_the_basis() {
for c in 0..64 {
let x = 1u64 << c;
assert_eq!(square_times::<3>(x), repeated_square(x, 3), "x^{c}");
assert_eq!(square_times::<24>(x), repeated_square(x, 24), "x^{c}");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(2000))]
#[test]
fn the_matrix_product_matches_repeated_squaring(x: u64) {
let repeated = |k: usize| (0..k).fold(x, |y, _| poly_square_64(y));
prop_assert_eq!(square_times::<3>(x), repeated(3));
prop_assert_eq!(square_times::<6>(x), repeated(6));
prop_assert_eq!(square_times::<12>(x), repeated(12));
prop_assert_eq!(square_times::<24>(x), repeated(24));
}
}
}