use core::arch::aarch64::*;
use crate::field::gf8b::Elem;
use crate::kernel::gf8::{mul_add_nibble, mul_assign_nibble, mul_into_nibble};
use crate::kernel::tables::{ScaleTable, scale_table};
#[inline]
#[target_feature(enable = "neon")]
fn load_tables(table: &ScaleTable) -> (uint8x16_t, uint8x16_t) {
unsafe { (vld1q_u8(table.lo.as_ptr()), vld1q_u8(table.hi.as_ptr())) }
}
#[inline]
#[target_feature(enable = "neon")]
fn scale(lo: uint8x16_t, hi: uint8x16_t, x: uint8x16_t) -> uint8x16_t {
let low = vandq_u8(x, vdupq_n_u8(0x0f));
let high = vshrq_n_u8(x, 4);
veorq_u8(vqtbl1q_u8(lo, low), vqtbl1q_u8(hi, high))
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Skip,
Identity,
Table,
}
#[derive(Clone, Copy)]
struct Scaling {
lo: uint8x16_t,
hi: uint8x16_t,
kind: Kind,
}
impl Scaling {
#[inline]
#[target_feature(enable = "neon")]
fn new(coeff: Elem) -> Self {
let zero = vdupq_n_u8(0);
if coeff == Elem::ZERO {
return Self {
lo: zero,
hi: zero,
kind: Kind::Skip,
};
}
if coeff == Elem::ONE {
return Self {
lo: zero,
hi: zero,
kind: Kind::Identity,
};
}
let (lo, hi) = load_tables(scale_table(coeff));
Self {
lo,
hi,
kind: Kind::Table,
}
}
#[inline]
#[target_feature(enable = "neon")]
fn fold(self, acc: uint8x16_t, x: uint8x16_t) -> uint8x16_t {
match self.kind {
Kind::Skip => acc,
Kind::Identity => veorq_u8(acc, x),
Kind::Table => veorq_u8(acc, scale(self.lo, self.hi, x)),
}
}
}
#[derive(Clone, Copy)]
struct PreparedRow {
ptr: *mut u8,
table: &'static ScaleTable,
scaling: Scaling,
}
impl PreparedRow {
#[inline]
#[target_feature(enable = "neon")]
fn new(ptr: *mut u8, coeff: Elem) -> Self {
Self {
ptr,
table: scale_table(coeff),
scaling: Scaling::new(coeff),
}
}
}
pub fn mul_add_neon(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
debug_assert_eq!(dst.len(), src.len());
unsafe { mul_add_impl(dst, table, src) }
}
#[target_feature(enable = "neon")]
unsafe fn mul_add_impl(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
let len = dst.len().min(src.len());
let (dst_ptr, src_ptr) = (dst.as_mut_ptr(), src.as_ptr());
let (lo, hi) = load_tables(table);
let mut offset = 0;
while offset + 32 <= len {
unsafe {
let x0 = vld1q_u8(src_ptr.add(offset));
let x1 = vld1q_u8(src_ptr.add(offset + 16));
let d0 = vld1q_u8(dst_ptr.add(offset));
let d1 = vld1q_u8(dst_ptr.add(offset + 16));
vst1q_u8(dst_ptr.add(offset), veorq_u8(d0, scale(lo, hi, x0)));
vst1q_u8(dst_ptr.add(offset + 16), veorq_u8(d1, scale(lo, hi, x1)));
}
offset += 32;
}
if offset + 16 <= len {
unsafe {
let x = vld1q_u8(src_ptr.add(offset));
let d = vld1q_u8(dst_ptr.add(offset));
vst1q_u8(dst_ptr.add(offset), veorq_u8(d, scale(lo, hi, x)));
}
offset += 16;
}
mul_add_nibble(&mut dst[offset..len], table, &src[offset..len]);
}
pub fn mul_assign_neon(dst: &mut [u8], table: &ScaleTable) {
unsafe { mul_assign_impl(dst, table) }
}
#[target_feature(enable = "neon")]
unsafe fn mul_assign_impl(dst: &mut [u8], table: &ScaleTable) {
let len = dst.len() & !15;
let dst_ptr = dst.as_mut_ptr();
let (lo, hi) = load_tables(table);
let mut offset = 0;
while offset < len {
unsafe {
let d = vld1q_u8(dst_ptr.add(offset));
vst1q_u8(dst_ptr.add(offset), scale(lo, hi, d));
}
offset += 16;
}
mul_assign_nibble(&mut dst[len..], table);
}
pub fn mul_into_neon(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
debug_assert_eq!(dst.len(), src.len());
unsafe { mul_into_impl(dst, table, src) }
}
#[target_feature(enable = "neon")]
unsafe fn mul_into_impl(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
let len = dst.len().min(src.len());
let (dst_ptr, src_ptr) = (dst.as_mut_ptr(), src.as_ptr());
let (lo, hi) = load_tables(table);
let mut offset = 0;
while offset + 32 <= len {
unsafe {
let x0 = vld1q_u8(src_ptr.add(offset));
let x1 = vld1q_u8(src_ptr.add(offset + 16));
vst1q_u8(dst_ptr.add(offset), scale(lo, hi, x0));
vst1q_u8(dst_ptr.add(offset + 16), scale(lo, hi, x1));
}
offset += 32;
}
if offset + 16 <= len {
unsafe {
let x = vld1q_u8(src_ptr.add(offset));
vst1q_u8(dst_ptr.add(offset), scale(lo, hi, x));
}
offset += 16;
}
mul_into_nibble(&mut dst[offset..len], table, &src[offset..len]);
}
pub fn scatter_neon(rows: &mut [u8], row_len: usize, coeffs: &[Elem], src: &[u8]) {
debug_assert_eq!(row_len, src.len());
debug_assert!(rows.len() >= coeffs.len().saturating_mul(row_len));
if row_len == 0 || coeffs.is_empty() {
return;
}
unsafe { mul_add_scatter_impl(rows, row_len, coeffs, src) }
}
#[target_feature(enable = "neon")]
unsafe fn mul_add_scatter_impl(rows: &mut [u8], row_len: usize, coeffs: &[Elem], src: &[u8]) {
let span = row_len.min(src.len());
let nrows = coeffs.len().min(rows.len() / row_len);
let base = rows.as_mut_ptr();
let mut j = 0;
while j + 4 <= nrows {
let quad = unsafe {
[
PreparedRow::new(base.add(j * row_len), coeffs[j]),
PreparedRow::new(base.add((j + 1) * row_len), coeffs[j + 1]),
PreparedRow::new(base.add((j + 2) * row_len), coeffs[j + 2]),
PreparedRow::new(base.add((j + 3) * row_len), coeffs[j + 3]),
]
};
unsafe { scatter_quad(&quad, src, span) };
j += 4;
}
while j < nrows {
let coeff = coeffs[j];
if coeff != Elem::ZERO {
let row = unsafe { core::slice::from_raw_parts_mut(base.add(j * row_len), span) };
unsafe { mul_add_impl(row, scale_table(coeff), &src[..span]) };
}
j += 1;
}
}
#[target_feature(enable = "neon")]
unsafe fn scatter_quad(plans: &[PreparedRow; 4], src: &[u8], span: usize) {
let src_ptr = src.as_ptr();
let mut offset = 0;
while offset + 16 <= span {
let x = unsafe { vld1q_u8(src_ptr.add(offset)) };
for plan in plans {
if plan.scaling.kind == Kind::Skip {
continue;
}
unsafe {
let dp = plan.ptr.add(offset);
vst1q_u8(dp, plan.scaling.fold(vld1q_u8(dp), x));
}
}
offset += 16;
}
if offset == span {
return;
}
let tail = span - offset;
for plan in plans {
if plan.scaling.kind == Kind::Skip {
continue;
}
let row = unsafe { core::slice::from_raw_parts_mut(plan.ptr.add(offset), tail) };
mul_add_nibble(row, plan.table, &src[offset..span]);
}
}
pub fn matrix_neon(rows: &mut [u8], row_len: usize, nrows: usize, terms: &[(&[Elem], &[u8])]) {
debug_assert!(rows.len() >= nrows.saturating_mul(row_len));
if row_len == 0 || nrows == 0 || terms.is_empty() {
return;
}
unsafe { mul_add_matrix_impl(rows, row_len, nrows, terms) }
}
#[target_feature(enable = "neon")]
unsafe fn mul_add_matrix_impl(
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Elem], &[u8])],
) {
let mut span = row_len;
let mut count = nrows.min(rows.len() / row_len);
for &(coeffs, src) in terms {
debug_assert_eq!(coeffs.len(), nrows);
debug_assert_eq!(src.len(), row_len);
span = span.min(src.len());
count = count.min(coeffs.len());
}
let base = rows.as_mut_ptr();
let mut j = 0;
while j + 4 <= count {
let quad = unsafe {
[
base.add(j * row_len),
base.add((j + 1) * row_len),
base.add((j + 2) * row_len),
base.add((j + 3) * row_len),
]
};
unsafe { matrix_quad(&quad, span, j, terms) };
j += 4;
}
while j < count {
unsafe { matrix_single(base.add(j * row_len), span, j, terms) };
j += 1;
}
}
#[target_feature(enable = "neon")]
unsafe fn matrix_quad(rows: &[*mut u8; 4], span: usize, first: usize, terms: &[(&[Elem], &[u8])]) {
let mut tile = 0;
while tile + 32 <= span {
let (mut a00, mut a01, mut a10, mut a11, mut a20, mut a21, mut a30, mut a31) = unsafe {
(
vld1q_u8(rows[0].add(tile)),
vld1q_u8(rows[0].add(tile + 16)),
vld1q_u8(rows[1].add(tile)),
vld1q_u8(rows[1].add(tile + 16)),
vld1q_u8(rows[2].add(tile)),
vld1q_u8(rows[2].add(tile + 16)),
vld1q_u8(rows[3].add(tile)),
vld1q_u8(rows[3].add(tile + 16)),
)
};
for &(coeffs, src) in terms {
let (x0, x1) = unsafe {
let sp = src.as_ptr().add(tile);
(vld1q_u8(sp), vld1q_u8(sp.add(16)))
};
let s = Scaling::new(coeffs[first]);
a00 = s.fold(a00, x0);
a01 = s.fold(a01, x1);
let s = Scaling::new(coeffs[first + 1]);
a10 = s.fold(a10, x0);
a11 = s.fold(a11, x1);
let s = Scaling::new(coeffs[first + 2]);
a20 = s.fold(a20, x0);
a21 = s.fold(a21, x1);
let s = Scaling::new(coeffs[first + 3]);
a30 = s.fold(a30, x0);
a31 = s.fold(a31, x1);
}
unsafe {
vst1q_u8(rows[0].add(tile), a00);
vst1q_u8(rows[0].add(tile + 16), a01);
vst1q_u8(rows[1].add(tile), a10);
vst1q_u8(rows[1].add(tile + 16), a11);
vst1q_u8(rows[2].add(tile), a20);
vst1q_u8(rows[2].add(tile + 16), a21);
vst1q_u8(rows[3].add(tile), a30);
vst1q_u8(rows[3].add(tile + 16), a31);
}
tile += 32;
}
if tile + 16 <= span {
let (mut a0, mut a1, mut a2, mut a3) = unsafe {
(
vld1q_u8(rows[0].add(tile)),
vld1q_u8(rows[1].add(tile)),
vld1q_u8(rows[2].add(tile)),
vld1q_u8(rows[3].add(tile)),
)
};
for &(coeffs, src) in terms {
let x = unsafe { vld1q_u8(src.as_ptr().add(tile)) };
a0 = Scaling::new(coeffs[first]).fold(a0, x);
a1 = Scaling::new(coeffs[first + 1]).fold(a1, x);
a2 = Scaling::new(coeffs[first + 2]).fold(a2, x);
a3 = Scaling::new(coeffs[first + 3]).fold(a3, x);
}
unsafe {
vst1q_u8(rows[0].add(tile), a0);
vst1q_u8(rows[1].add(tile), a1);
vst1q_u8(rows[2].add(tile), a2);
vst1q_u8(rows[3].add(tile), a3);
}
tile += 16;
}
if tile < span {
let tail = span - tile;
for (slot, &ptr) in rows.iter().enumerate() {
let row = unsafe { core::slice::from_raw_parts_mut(ptr.add(tile), tail) };
for &(coeffs, src) in terms {
let coeff = coeffs[first + slot];
if coeff != Elem::ZERO {
mul_add_nibble(row, scale_table(coeff), &src[tile..span]);
}
}
}
}
}
#[target_feature(enable = "neon")]
unsafe fn matrix_single(ptr: *mut u8, span: usize, index: usize, terms: &[(&[Elem], &[u8])]) {
let mut tile = 0;
while tile + 32 <= span {
let (mut a0, mut a1) = unsafe { (vld1q_u8(ptr.add(tile)), vld1q_u8(ptr.add(tile + 16))) };
for &(coeffs, src) in terms {
let (x0, x1) = unsafe {
let sp = src.as_ptr().add(tile);
(vld1q_u8(sp), vld1q_u8(sp.add(16)))
};
let s = Scaling::new(coeffs[index]);
a0 = s.fold(a0, x0);
a1 = s.fold(a1, x1);
}
unsafe {
vst1q_u8(ptr.add(tile), a0);
vst1q_u8(ptr.add(tile + 16), a1);
}
tile += 32;
}
if tile + 16 <= span {
let mut a = unsafe { vld1q_u8(ptr.add(tile)) };
for &(coeffs, src) in terms {
let x = unsafe { vld1q_u8(src.as_ptr().add(tile)) };
a = Scaling::new(coeffs[index]).fold(a, x);
}
unsafe { vst1q_u8(ptr.add(tile), a) };
tile += 16;
}
if tile < span {
let tail = span - tile;
let row = unsafe { core::slice::from_raw_parts_mut(ptr.add(tile), tail) };
for &(coeffs, src) in terms {
let coeff = coeffs[index];
if coeff != Elem::ZERO {
mul_add_nibble(row, scale_table(coeff), &src[tile..span]);
}
}
}
}
pub fn gather_neon(dst: &mut [u8], coeffs: &[Elem], srcs: &[&[u8]]) {
debug_assert_eq!(coeffs.len(), srcs.len());
unsafe { mul_add_gather_impl(dst, coeffs, srcs) }
}
#[target_feature(enable = "neon")]
unsafe fn mul_add_gather_impl(dst: &mut [u8], coeffs: &[Elem], srcs: &[&[u8]]) {
let len = dst.len() & !31;
let dst_ptr = dst.as_mut_ptr();
let mut offset = 0;
while offset < len {
let (mut acc0, mut acc1) = unsafe {
(
vld1q_u8(dst_ptr.add(offset)),
vld1q_u8(dst_ptr.add(offset + 16)),
)
};
for (&coeff, &src) in coeffs.iter().zip(srcs) {
let scaling = Scaling::new(coeff);
unsafe {
acc0 = scaling.fold(acc0, vld1q_u8(src.as_ptr().add(offset)));
acc1 = scaling.fold(acc1, vld1q_u8(src.as_ptr().add(offset + 16)));
}
}
unsafe {
vst1q_u8(dst_ptr.add(offset), acc0);
vst1q_u8(dst_ptr.add(offset + 16), acc1);
}
offset += 32;
}
for (&coeff, &src) in coeffs.iter().zip(srcs) {
mul_add_nibble(&mut dst[len..], scale_table(coeff), &src[len..]);
}
}
#[inline]
#[target_feature(enable = "neon")]
fn multiply_vectors(mut a: uint8x16_t, mut b: uint8x16_t) -> uint8x16_t {
let one = vdupq_n_u8(1);
let high_bit = vdupq_n_u8(0x80);
let reduction = vdupq_n_u8(0x1b);
let mut product = vdupq_n_u8(0);
for _ in 0..8 {
let active = vceqq_u8(vandq_u8(b, one), one);
product = veorq_u8(product, vandq_u8(a, active));
let reduce = vceqq_u8(vandq_u8(a, high_bit), high_bit);
a = veorq_u8(vshlq_n_u8(a, 1), vandq_u8(reduction, reduce));
b = vshrq_n_u8(b, 1);
}
product
}
#[inline]
#[target_feature(enable = "neon,aes")]
fn multiply8_pmull(a: uint8x8_t, b: uint8x8_t) -> uint8x8_t {
let product = vreinterpretq_u16_p16(vmull_p8(vreinterpret_p8_u8(a), vreinterpret_p8_u8(b)));
let mask = vdupq_n_u16(0x00ff);
let high = vshrq_n_u16(product, 8);
let mut reduced = veorq_u16(vandq_u16(product, mask), high);
reduced = veorq_u16(reduced, vshlq_n_u16(high, 1));
reduced = veorq_u16(reduced, vshlq_n_u16(high, 3));
reduced = veorq_u16(reduced, vshlq_n_u16(high, 4));
let high = vshrq_n_u16(reduced, 8);
reduced = veorq_u16(vandq_u16(reduced, mask), high);
reduced = veorq_u16(reduced, vshlq_n_u16(high, 1));
reduced = veorq_u16(reduced, vshlq_n_u16(high, 3));
reduced = veorq_u16(reduced, vshlq_n_u16(high, 4));
vmovn_u16(reduced)
}
#[inline]
#[target_feature(enable = "neon,aes")]
pub(super) fn multiply_vectors_pmull(a: uint8x16_t, b: uint8x16_t) -> uint8x16_t {
vcombine_u8(
multiply8_pmull(vget_low_u8(a), vget_low_u8(b)),
multiply8_pmull(vget_high_u8(a), vget_high_u8(b)),
)
}
pub fn elementwise_pmull(dst: &mut [u8], a: &[u8], b: &[u8]) {
debug_assert_eq!(dst.len(), a.len());
debug_assert_eq!(dst.len(), b.len());
unsafe { elementwise_pmull_impl(dst, a, b) }
}
#[target_feature(enable = "neon,aes")]
unsafe fn elementwise_pmull_impl(dst: &mut [u8], a: &[u8], b: &[u8]) {
let len = dst.len().min(a.len()).min(b.len()) & !15;
let mut offset = 0;
while offset < len {
unsafe {
let x = vld1q_u8(a.as_ptr().add(offset));
let y = vld1q_u8(b.as_ptr().add(offset));
vst1q_u8(dst.as_mut_ptr().add(offset), multiply_vectors_pmull(x, y));
}
offset += 16;
}
for ((d, &x), &y) in dst[len..].iter_mut().zip(&a[len..]).zip(&b[len..]) {
*d = Elem(x).mul(Elem(y)).0;
}
}
pub fn elementwise_neon(dst: &mut [u8], a: &[u8], b: &[u8]) {
debug_assert_eq!(dst.len(), a.len());
debug_assert_eq!(dst.len(), b.len());
unsafe { elementwise_impl(dst, a, b) }
}
#[target_feature(enable = "neon")]
unsafe fn elementwise_impl(dst: &mut [u8], a: &[u8], b: &[u8]) {
let len = dst.len().min(a.len()).min(b.len()) & !15;
let mut offset = 0;
while offset < len {
unsafe {
let x = vld1q_u8(a.as_ptr().add(offset));
let y = vld1q_u8(b.as_ptr().add(offset));
vst1q_u8(dst.as_mut_ptr().add(offset), multiply_vectors(x, y));
}
offset += 16;
}
for ((d, &x), &y) in dst[len..].iter_mut().zip(&a[len..]).zip(&b[len..]) {
*d = Elem(x).mul(Elem(y)).0;
}
}