use core::arch::aarch64::*;
use crate::field::poly::AES;
use crate::field::{Binary, Elem as Gf8Elem, Polynomial};
use crate::kernel::Matrix;
use crate::kernel::gf8::{Coeffs, Prepared, mul_add_nibble, mul_assign_nibble, mul_into_nibble};
use crate::kernel::proven_checks::{check_equal, check_row_span};
use crate::kernel::tables::ScaleTable;
type Elem = Gf8Elem<Binary<8, Polynomial<AES>>>;
#[inline]
#[archmage::rite(neon, import_intrinsics)]
fn load_tables(table: &ScaleTable) -> (uint8x16_t, uint8x16_t) {
(vld1q_u8(&table.lo), vld1q_u8(&table.hi))
}
#[inline]
#[archmage::rite(neon, import_intrinsics)]
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]
#[archmage::rite(neon, import_intrinsics)]
fn new(coeff: Prepared) -> Self {
let zero = vdupq_n_u8(0);
match coeff.table.coeff {
0 => Self {
lo: zero,
hi: zero,
kind: Kind::Skip,
},
1 => Self {
lo: zero,
hi: zero,
kind: Kind::Identity,
},
_ => {
let (lo, hi) = load_tables(coeff.table);
Self {
lo,
hi,
kind: Kind::Table,
}
}
}
}
#[inline]
#[archmage::rite(neon, import_intrinsics)]
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]
#[archmage::rite(neon, import_intrinsics)]
fn new(ptr: *mut u8, coeff: Prepared) -> Self {
Self {
ptr,
table: coeff.table,
scaling: Scaling::new(coeff),
}
}
}
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_add_neon(_token: archmage::NeonToken, dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
check_equal("gf8::mul_add_neon", "dst", dst.len(), "src", src.len());
mul_add_impl(dst, table, src);
}
#[archmage::rite(neon, import_intrinsics)]
fn mul_add_impl(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
let span = dst.len().min(src.len());
let vector_len = span & !15;
let pair_len = vector_len & !31;
let (lo, hi) = load_tables(table);
let (dst_tiles, _) = dst[..pair_len].as_chunks_mut::<32>();
let (src_tiles, _) = src[..pair_len].as_chunks::<32>();
for (d_tile, s_tile) in dst_tiles.iter_mut().zip(src_tiles) {
let (d0, d1) = d_tile.split_at_mut(16);
let (d0, d1): (&mut [u8; 16], &mut [u8; 16]) =
(d0.try_into().unwrap(), d1.try_into().unwrap());
let (s0, s1): (&[u8; 16], &[u8; 16]) = {
let (s0, s1) = s_tile.split_at(16);
(s0.try_into().unwrap(), s1.try_into().unwrap())
};
let x0 = vld1q_u8(s0);
let x1 = vld1q_u8(s1);
let d0v = vld1q_u8(&*d0);
let d1v = vld1q_u8(&*d1);
vst1q_u8(d0, veorq_u8(d0v, scale(lo, hi, x0)));
vst1q_u8(d1, veorq_u8(d1v, scale(lo, hi, x1)));
}
let (dst_lanes, _) = dst[pair_len..vector_len].as_chunks_mut::<16>();
let (src_lanes, _) = src[pair_len..vector_len].as_chunks::<16>();
for (d, s) in dst_lanes.iter_mut().zip(src_lanes) {
let x = vld1q_u8(s);
let d0v = vld1q_u8(&*d);
vst1q_u8(d, veorq_u8(d0v, scale(lo, hi, x)));
}
mul_add_nibble(&mut dst[vector_len..span], table, &src[vector_len..span]);
}
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_assign_neon(_token: archmage::NeonToken, dst: &mut [u8], table: &ScaleTable) {
mul_assign_impl(dst, table);
}
#[archmage::rite(neon, import_intrinsics)]
fn mul_assign_impl(dst: &mut [u8], table: &ScaleTable) {
let (lanes, tail) = dst.as_chunks_mut::<16>();
let (lo, hi) = load_tables(table);
for lane in lanes {
let d = vld1q_u8(&*lane);
vst1q_u8(lane, scale(lo, hi, d));
}
mul_assign_nibble(tail, table);
}
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_into_neon(_token: archmage::NeonToken, dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
check_equal("gf8::mul_into_neon", "dst", dst.len(), "src", src.len());
mul_into_impl(dst, table, src);
}
#[archmage::rite(neon, import_intrinsics)]
fn mul_into_impl(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
let span = dst.len().min(src.len());
let vector_len = span & !15;
let pair_len = vector_len & !31;
let (lo, hi) = load_tables(table);
let (dst_tiles, _) = dst[..pair_len].as_chunks_mut::<32>();
let (src_tiles, _) = src[..pair_len].as_chunks::<32>();
for (d_tile, s_tile) in dst_tiles.iter_mut().zip(src_tiles) {
let (d0, d1) = d_tile.split_at_mut(16);
let (d0, d1): (&mut [u8; 16], &mut [u8; 16]) =
(d0.try_into().unwrap(), d1.try_into().unwrap());
let (s0, s1): (&[u8; 16], &[u8; 16]) = {
let (s0, s1) = s_tile.split_at(16);
(s0.try_into().unwrap(), s1.try_into().unwrap())
};
vst1q_u8(d0, scale(lo, hi, vld1q_u8(s0)));
vst1q_u8(d1, scale(lo, hi, vld1q_u8(s1)));
}
let (dst_lanes, _) = dst[pair_len..vector_len].as_chunks_mut::<16>();
let (src_lanes, _) = src[pair_len..vector_len].as_chunks::<16>();
for (d, s) in dst_lanes.iter_mut().zip(src_lanes) {
vst1q_u8(d, scale(lo, hi, vld1q_u8(s)));
}
mul_into_nibble(&mut dst[vector_len..span], table, &src[vector_len..span]);
}
#[allow(unsafe_code)]
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_add_scatter_neon(
_token: archmage::NeonToken,
rows: &mut [u8],
row_len: usize,
coeffs: &(impl Coeffs + ?Sized),
src: &[u8],
) {
check_equal(
"gf8::mul_add_scatter_neon",
"row_len",
row_len,
"src",
src.len(),
);
check_row_span(
"gf8::mul_add_scatter_neon",
rows.len(),
row_len,
coeffs.count(),
);
if row_len == 0 || coeffs.count() == 0 {
return;
}
unsafe { mul_add_scatter_impl(rows, row_len, coeffs, src) };
}
#[allow(unsafe_code)]
#[archmage::rite(neon)]
unsafe fn mul_add_scatter_impl(
rows: &mut [u8],
row_len: usize,
coeffs: &(impl Coeffs + ?Sized),
src: &[u8],
) {
let span = row_len.min(src.len());
let nrows = coeffs.count().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.resolved(j)),
PreparedRow::new(base.add((j + 1) * row_len), coeffs.resolved(j + 1)),
PreparedRow::new(base.add((j + 2) * row_len), coeffs.resolved(j + 2)),
PreparedRow::new(base.add((j + 3) * row_len), coeffs.resolved(j + 3)),
]
};
unsafe { scatter_quad(&quad, src, span) };
j += 4;
}
while j < nrows {
let coeff = coeffs.resolved(j);
if !coeff.is_zero() {
let row = unsafe { core::slice::from_raw_parts_mut(base.add(j * row_len), span) };
mul_add_impl(row, coeff.table, &src[..span]);
}
j += 1;
}
}
#[allow(unsafe_code)]
#[archmage::rite(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]);
}
}
#[allow(unsafe_code)]
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_add_matrix_neon<M: Matrix<Prepared> + ?Sized>(
_token: archmage::NeonToken,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
) {
check_row_span("gf8::mul_add_matrix_neon", rows.len(), row_len, nrows);
for term in 0..terms.len() {
check_equal(
"gf8::mul_add_matrix_neon",
"term source",
terms.source(term).len(),
"row_len",
row_len,
);
}
if row_len == 0 || nrows == 0 || terms.len() == 0 {
return;
}
unsafe { mul_add_matrix_impl(rows, row_len, nrows, terms) };
}
#[allow(unsafe_code)]
#[archmage::rite(neon)]
unsafe fn mul_add_matrix_impl<M: Matrix<Prepared> + ?Sized>(
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
) {
let mut span = row_len;
let count = nrows.min(rows.len() / row_len);
for term in 0..terms.len() {
span = span.min(terms.source(term).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;
}
}
#[allow(unsafe_code)]
#[archmage::rite(neon)]
unsafe fn matrix_quad<M: Matrix<Prepared> + ?Sized>(
rows: &[*mut u8; 4],
span: usize,
first: usize,
terms: &M,
) {
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 term in 0..terms.len() {
let src = terms.source(term);
let (x0, x1) = unsafe {
let sp = src.as_ptr().add(tile);
(vld1q_u8(sp), vld1q_u8(sp.add(16)))
};
let s = Scaling::new(terms.coefficient(term, first));
a00 = s.fold(a00, x0);
a01 = s.fold(a01, x1);
let s = Scaling::new(terms.coefficient(term, first + 1));
a10 = s.fold(a10, x0);
a11 = s.fold(a11, x1);
let s = Scaling::new(terms.coefficient(term, first + 2));
a20 = s.fold(a20, x0);
a21 = s.fold(a21, x1);
let s = Scaling::new(terms.coefficient(term, 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 term in 0..terms.len() {
let src = terms.source(term);
let x = unsafe { vld1q_u8(src.as_ptr().add(tile)) };
a0 = Scaling::new(terms.coefficient(term, first)).fold(a0, x);
a1 = Scaling::new(terms.coefficient(term, first + 1)).fold(a1, x);
a2 = Scaling::new(terms.coefficient(term, first + 2)).fold(a2, x);
a3 = Scaling::new(terms.coefficient(term, 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 term in 0..terms.len() {
let coeff = terms.coefficient(term, first + slot);
if !coeff.is_zero() {
mul_add_nibble(row, coeff.table, &terms.source(term)[tile..span]);
}
}
}
}
}
#[allow(unsafe_code)]
#[archmage::rite(neon)]
unsafe fn matrix_single<M: Matrix<Prepared> + ?Sized>(
ptr: *mut u8,
span: usize,
index: usize,
terms: &M,
) {
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 term in 0..terms.len() {
let src = terms.source(term);
let (x0, x1) = unsafe {
let sp = src.as_ptr().add(tile);
(vld1q_u8(sp), vld1q_u8(sp.add(16)))
};
let s = Scaling::new(terms.coefficient(term, 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 term in 0..terms.len() {
let src = terms.source(term);
let x = unsafe { vld1q_u8(src.as_ptr().add(tile)) };
a = Scaling::new(terms.coefficient(term, 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 term in 0..terms.len() {
let coeff = terms.coefficient(term, index);
if !coeff.is_zero() {
mul_add_nibble(row, coeff.table, &terms.source(term)[tile..span]);
}
}
}
}
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_add_gather_neon(
_token: archmage::NeonToken,
dst: &mut [u8],
coeffs: &(impl Coeffs + ?Sized),
srcs: &[&[u8]],
) {
check_equal(
"gf8::mul_add_gather_neon",
"coefficients",
coeffs.count(),
"sources",
srcs.len(),
);
for (index, &src) in srcs.iter().enumerate() {
check_equal(
"gf8::mul_add_gather_neon",
"dst",
dst.len(),
format_args!("source {index}"),
src.len(),
);
}
if dst.is_empty() || srcs.is_empty() {
return;
}
mul_add_gather_impl(dst, coeffs, srcs);
}
#[archmage::rite(neon, import_intrinsics)]
fn mul_add_gather_impl(dst: &mut [u8], coeffs: &(impl Coeffs + ?Sized), srcs: &[&[u8]]) {
let count = coeffs.count().min(srcs.len());
let mut span = dst.len();
for &src in &srcs[..count] {
span = span.min(src.len());
}
let pair_len = span & !31;
let vector_len = span & !15;
let (dst_tiles, _) = dst[..pair_len].as_chunks_mut::<32>();
for (t, d_tile) in dst_tiles.iter_mut().enumerate() {
let (d0, d1) = d_tile.split_at_mut(16);
let (d0, d1): (&mut [u8; 16], &mut [u8; 16]) =
(d0.try_into().unwrap(), d1.try_into().unwrap());
let mut acc0 = vld1q_u8(&*d0);
let mut acc1 = vld1q_u8(&*d1);
for k in 0..count {
let plan = Scaling::new(coeffs.resolved(k));
if plan.kind == Kind::Skip {
continue;
}
let s_tile: &[u8; 32] = srcs[k][t * 32..t * 32 + 32].try_into().unwrap();
let (s0, s1): (&[u8; 16], &[u8; 16]) = {
let (s0, s1) = s_tile.split_at(16);
(s0.try_into().unwrap(), s1.try_into().unwrap())
};
acc0 = plan.fold(acc0, vld1q_u8(s0));
acc1 = plan.fold(acc1, vld1q_u8(s1));
}
vst1q_u8(d0, acc0);
vst1q_u8(d1, acc1);
}
if vector_len > pair_len {
let mut acc = vld1q_u8(dst[pair_len..vector_len].first_chunk().unwrap());
for k in 0..count {
let plan = Scaling::new(coeffs.resolved(k));
if plan.kind == Kind::Skip {
continue;
}
acc = plan.fold(
acc,
vld1q_u8(srcs[k][pair_len..vector_len].first_chunk().unwrap()),
);
}
vst1q_u8(dst[pair_len..vector_len].first_chunk_mut().unwrap(), acc);
}
for k in 0..count {
let coeff = coeffs.resolved(k);
if !coeff.is_zero() {
mul_add_nibble(
&mut dst[vector_len..span],
coeff.table,
&srcs[k][vector_len..span],
);
}
}
}
#[inline]
#[archmage::rite(neon, import_intrinsics)]
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]
#[archmage::rite(neon_aes, import_intrinsics)]
fn multiply8_neon_aes(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]
#[archmage::rite(neon_aes, import_intrinsics)]
pub(super) fn multiply_vectors_neon_aes(a: uint8x16_t, b: uint8x16_t) -> uint8x16_t {
vcombine_u8(
multiply8_neon_aes(vget_low_u8(a), vget_low_u8(b)),
multiply8_neon_aes(vget_high_u8(a), vget_high_u8(b)),
)
}
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_elementwise_neon_aes(
_token: archmage::NeonAesToken,
dst: &mut [u8],
a: &[u8],
b: &[u8],
) {
check_equal(
"gf8::mul_elementwise_neon_aes",
"dst",
dst.len(),
"a",
a.len(),
);
check_equal(
"gf8::mul_elementwise_neon_aes",
"dst",
dst.len(),
"b",
b.len(),
);
mul_elementwise_neon_aes_impl(dst, a, b);
}
#[archmage::rite(neon_aes, import_intrinsics)]
fn mul_elementwise_neon_aes_impl(dst: &mut [u8], a: &[u8], b: &[u8]) {
let (dst_lanes, dst_tail) = dst.as_chunks_mut::<16>();
let (a_lanes, a_tail) = a.as_chunks::<16>();
let (b_lanes, b_tail) = b.as_chunks::<16>();
for ((d, x), y) in dst_lanes.iter_mut().zip(a_lanes).zip(b_lanes) {
vst1q_u8(d, multiply_vectors_neon_aes(vld1q_u8(x), vld1q_u8(y)));
}
for ((d, &x), &y) in dst_tail.iter_mut().zip(a_tail).zip(b_tail) {
*d = Elem::from_raw(x).mul(Elem::from_raw(y)).to_raw();
}
}
#[allow(clippy::used_underscore_binding)]
#[archmage::arcane]
pub fn mul_elementwise_neon(_token: archmage::NeonToken, dst: &mut [u8], a: &[u8], b: &[u8]) {
check_equal("gf8::mul_elementwise_neon", "dst", dst.len(), "a", a.len());
check_equal("gf8::mul_elementwise_neon", "dst", dst.len(), "b", b.len());
elementwise_impl(dst, a, b);
}
#[archmage::rite(neon, import_intrinsics)]
fn elementwise_impl(dst: &mut [u8], a: &[u8], b: &[u8]) {
let (dst_lanes, dst_tail) = dst.as_chunks_mut::<16>();
let (a_lanes, a_tail) = a.as_chunks::<16>();
let (b_lanes, b_tail) = b.as_chunks::<16>();
for ((d, x), y) in dst_lanes.iter_mut().zip(a_lanes).zip(b_lanes) {
vst1q_u8(d, multiply_vectors(vld1q_u8(x), vld1q_u8(y)));
}
for ((d, &x), &y) in dst_tail.iter_mut().zip(a_tail).zip(b_tail) {
*d = Elem::from_raw(x).mul(Elem::from_raw(y)).to_raw();
}
}