use core::arch::aarch64::{
uint8x16_t, uint64x2_t, vdupq_n_u8, vdupq_n_u64, veorq_u8, veorq_u64, vextq_u8, vextq_u64,
vgetq_lane_u64, vmull_high_p64, vmull_p64, vreinterpretq_p64_u8, vreinterpretq_u8_u64,
vreinterpretq_u64_u8,
};
use core::mem::transmute;
#[inline]
pub(super) fn clmul_64x64(a: u64, b: u64) -> u128 {
unsafe { vmull_p64(a, b) }
}
#[inline]
unsafe fn clmul_low(a: uint8x16_t, b: uint8x16_t) -> uint8x16_t {
unsafe {
transmute(vmull_p64(
vgetq_lane_u64::<0>(vreinterpretq_u64_u8(a)),
vgetq_lane_u64::<0>(vreinterpretq_u64_u8(b)),
))
}
}
#[inline]
unsafe fn clmul_high(a: uint8x16_t, b: uint8x16_t) -> uint8x16_t {
unsafe {
transmute(vmull_high_p64(
vreinterpretq_p64_u8(a),
vreinterpretq_p64_u8(b),
))
}
}
#[inline]
unsafe fn fold_high(low: uint8x16_t, high: uint8x16_t) -> uint8x16_t {
unsafe {
let zero = vdupq_n_u8(0);
let tail = vreinterpretq_u8_u64(vdupq_n_u64(super::basis::TAIL_128 as u64));
let folded = clmul_high(high, tail);
let spill = vextq_u8::<8>(folded, zero);
let carried = vextq_u8::<8>(zero, folded);
let reduced = veorq_u8(low, clmul_low(veorq_u8(high, spill), tail));
veorq_u8(reduced, carried)
}
}
#[inline]
pub(crate) fn poly_mul_128(a: u128, b: u128) -> u128 {
const {
assert!(
cfg!(target_endian = "little"),
"the halves of a `u128` are its vector lanes only on little-endian targets"
);
}
unsafe {
let a = transmute::<u128, uint8x16_t>(a);
let b = transmute::<u128, uint8x16_t>(b);
let zero = vdupq_n_u8(0);
let swapped = vextq_u8::<8>(b, b);
let middle = veorq_u8(clmul_low(a, swapped), clmul_high(a, swapped));
let low = veorq_u8(clmul_low(a, b), vextq_u8::<8>(zero, middle));
let high = veorq_u8(clmul_high(a, b), vextq_u8::<8>(middle, zero));
transmute::<uint8x16_t, u128>(fold_high(low, high))
}
}
#[inline]
pub(crate) fn poly_square_128(a: u128) -> u128 {
const {
assert!(
cfg!(target_endian = "little"),
"the halves of a `u128` are its vector lanes only on little-endian targets"
);
}
unsafe {
let a = transmute::<u128, uint8x16_t>(a);
transmute::<uint8x16_t, u128>(fold_high(clmul_low(a, a), clmul_high(a, a)))
}
}
#[inline]
pub(crate) fn poly_dot_128(pairs: impl Iterator<Item = (u128, u128)>) -> u128 {
const {
assert!(cfg!(target_endian = "little"));
}
unsafe {
let zero = vdupq_n_u8(0);
let (mut low, mut high, mut middle) = (zero, zero, zero);
for (a, b) in pairs {
let a = transmute::<u128, uint8x16_t>(a);
let b = transmute::<u128, uint8x16_t>(b);
let swapped = vextq_u8::<8>(b, b);
low = veorq_u8(low, clmul_low(a, b));
high = veorq_u8(high, clmul_high(a, b));
middle = veorq_u8(
middle,
veorq_u8(clmul_low(a, swapped), clmul_high(a, swapped)),
);
}
low = veorq_u8(low, vextq_u8::<8>(zero, middle));
high = veorq_u8(high, vextq_u8::<8>(middle, zero));
transmute::<uint8x16_t, u128>(fold_high(low, high))
}
}
#[inline]
pub(crate) fn poly_mul_128_by_64(a: u128, b: u64) -> u128 {
const {
assert!(cfg!(target_endian = "little"));
}
unsafe {
let a = transmute::<u128, uint8x16_t>(a);
let b = vreinterpretq_u8_u64(vdupq_n_u64(b));
let zero = vdupq_n_u8(0);
let low = clmul_low(a, b);
let middle = clmul_high(a, b);
let tail = vreinterpretq_u8_u64(vdupq_n_u64(super::basis::TAIL_128 as u64));
let folded = veorq_u8(vextq_u8::<8>(zero, middle), clmul_high(middle, tail));
transmute::<uint8x16_t, u128>(veorq_u8(low, folded))
}
}
#[inline]
pub(super) fn poly_mul_128_batch(a: u128, b: u128) -> u128 {
unsafe {
let av: uint64x2_t = core::mem::transmute(a);
let bv: uint64x2_t = core::mem::transmute(b);
let low: uint64x2_t =
core::mem::transmute(vmull_p64(vgetq_lane_u64::<0>(av), vgetq_lane_u64::<0>(bv)));
let high: uint64x2_t =
core::mem::transmute(vmull_p64(vgetq_lane_u64::<1>(av), vgetq_lane_u64::<1>(bv)));
let mid: uint64x2_t = core::mem::transmute(vmull_p64(
vgetq_lane_u64::<0>(av) ^ vgetq_lane_u64::<1>(av),
vgetq_lane_u64::<0>(bv) ^ vgetq_lane_u64::<1>(bv),
));
let middle = veorq_u64(veorq_u64(mid, low), high);
let zero = vdupq_n_u64(0);
let lo = veorq_u64(low, vextq_u64::<1>(zero, middle));
let hi = veorq_u64(high, vextq_u64::<1>(middle, zero));
let f0: uint64x2_t = core::mem::transmute(vmull_p64(vgetq_lane_u64::<0>(hi), 0x87));
let f1: uint64x2_t = core::mem::transmute(vmull_p64(vgetq_lane_u64::<1>(hi), 0x87));
let spill: uint64x2_t = core::mem::transmute(vmull_p64(vgetq_lane_u64::<1>(f1), 0x87));
core::mem::transmute(veorq_u64(
veorq_u64(lo, f0),
veorq_u64(vextq_u64::<1>(zero, f1), spill),
))
}
}