use core::arch::wasm32::*;
use crate::field::gf8b::{Elem, Gf8B};
use crate::kernel::gf8::mul_add_nibble;
use crate::kernel::tables::{ScaleTable, scale_table};
#[derive(Clone, Copy)]
struct Factors {
lo: v128,
hi: v128,
}
#[inline]
#[target_feature(enable = "simd128")]
fn load_factors(table: &ScaleTable) -> Factors {
unsafe {
Factors {
lo: v128_load(table.lo.as_ptr().cast()),
hi: v128_load(table.hi.as_ptr().cast()),
}
}
}
#[inline]
#[target_feature(enable = "simd128")]
fn scaled(value: v128, factors: Factors) -> v128 {
let low = v128_and(value, u8x16_splat(0x0f));
let high = u8x16_shr(value, 4);
v128_xor(
u8x16_swizzle(factors.lo, low),
u8x16_swizzle(factors.hi, high),
)
}
pub fn mul_add_simd128(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
debug_assert_eq!(dst.len(), src.len());
unsafe { mul_add_impl(dst, table, src) }
}
#[target_feature(enable = "simd128")]
unsafe fn mul_add_impl(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
let len = dst.len().min(src.len()) & !15;
let factors = load_factors(table);
let (dst_ptr, src_ptr) = (dst.as_mut_ptr(), src.as_ptr());
let mut offset = 0;
while offset < len {
unsafe {
let d = v128_load(dst_ptr.add(offset).cast());
let s = v128_load(src_ptr.add(offset).cast());
v128_store(dst_ptr.add(offset).cast(), v128_xor(d, scaled(s, factors)));
}
offset += 16;
}
crate::kernel::gf8::mul_add_nibble(&mut dst[len..], table, &src[len..]);
}
pub fn mul_assign_simd128(dst: &mut [u8], table: &ScaleTable) {
unsafe { mul_assign_impl(dst, table) }
}
#[target_feature(enable = "simd128")]
unsafe fn mul_assign_impl(dst: &mut [u8], table: &ScaleTable) {
let len = dst.len() & !15;
let factors = load_factors(table);
let ptr = dst.as_mut_ptr();
let mut offset = 0;
while offset < len {
unsafe {
let d = v128_load(ptr.add(offset).cast());
v128_store(ptr.add(offset).cast(), scaled(d, factors));
}
offset += 16;
}
crate::kernel::gf8::mul_assign_nibble(&mut dst[len..], table);
}
pub fn mul_into_simd128(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
debug_assert_eq!(dst.len(), src.len());
unsafe { mul_into_impl(dst, table, src) }
}
#[target_feature(enable = "simd128")]
unsafe fn mul_into_impl(dst: &mut [u8], table: &ScaleTable, src: &[u8]) {
let len = dst.len().min(src.len()) & !15;
let factors = load_factors(table);
let (dst_ptr, src_ptr) = (dst.as_mut_ptr(), src.as_ptr());
let mut offset = 0;
while offset < len {
unsafe {
let s = v128_load(src_ptr.add(offset).cast());
v128_store(dst_ptr.add(offset).cast(), scaled(s, factors));
}
offset += 16;
}
crate::kernel::gf8::mul_into_nibble(&mut dst[len..], table, &src[len..]);
}
#[inline]
#[target_feature(enable = "simd128")]
pub(super) fn multiply_vectors(mut a: v128, mut b: v128) -> v128 {
let one = u8x16_splat(1);
let reduction = u8x16_splat(0x1b);
let mut product = u8x16_splat(0);
for _ in 0..8 {
let selected = u8x16_eq(v128_and(b, one), one);
product = v128_xor(product, v128_and(a, selected));
let high = u8x16_eq(u8x16_shr(a, 7), one);
a = v128_xor(u8x16_shl(a, 1), v128_and(reduction, high));
b = u8x16_shr(b, 1);
}
product
}
pub fn elementwise_simd128(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 = "simd128")]
unsafe fn elementwise_impl(dst: &mut [u8], a: &[u8], b: &[u8]) {
let len = dst.len().min(a.len()).min(b.len()) & !15;
let (dst_ptr, a_ptr, b_ptr) = (dst.as_mut_ptr(), a.as_ptr(), b.as_ptr());
let mut offset = 0;
while offset < len {
unsafe {
let x = v128_load(a_ptr.add(offset).cast());
let y = v128_load(b_ptr.add(offset).cast());
v128_store(dst_ptr.add(offset).cast(), multiply_vectors(x, y));
}
offset += 16;
}
crate::kernel::scalar::mul_elementwise::<Gf8B>(&mut dst[len..], &a[len..], &b[len..]);
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Skip,
Identity,
Table,
}
#[derive(Clone, Copy)]
struct Scaling {
factors: Factors,
table: &'static ScaleTable,
kind: Kind,
}
impl Scaling {
#[inline]
#[target_feature(enable = "simd128")]
fn new(coeff: Elem) -> Self {
let table = scale_table(coeff);
let kind = if coeff == Elem::ZERO {
Kind::Skip
} else if coeff == Elem::ONE {
Kind::Identity
} else {
Kind::Table
};
Self {
factors: load_factors(table),
table,
kind,
}
}
#[inline]
#[target_feature(enable = "simd128")]
fn fold(self, acc: v128, x: v128) -> v128 {
match self.kind {
Kind::Skip => acc,
Kind::Identity => v128_xor(acc, x),
Kind::Table => v128_xor(acc, scaled(x, self.factors)),
}
}
}
pub fn scatter_simd128(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 = "simd128")]
unsafe fn mul_add_scatter_impl(rows: &mut [u8], row_len: usize, coeffs: &[Elem], src: &[u8]) {
let span = row_len.min(src.len());
let count = coeffs.len().min(rows.len() / row_len);
let src = &src[..span];
let mut rest = &mut rows[..count * row_len];
let mut j = 0;
while j + 4 <= count {
let (block, tail) = rest.split_at_mut(4 * row_len);
rest = tail;
let (r0, block) = block.split_at_mut(row_len);
let (r1, block) = block.split_at_mut(row_len);
let (r2, r3) = block.split_at_mut(row_len);
let plans = [
Scaling::new(coeffs[j]),
Scaling::new(coeffs[j + 1]),
Scaling::new(coeffs[j + 2]),
Scaling::new(coeffs[j + 3]),
];
unsafe {
scatter_quad(
[
&mut r0[..span],
&mut r1[..span],
&mut r2[..span],
&mut r3[..span],
],
&plans,
src,
);
}
j += 4;
}
while j < count {
let (row, tail) = rest.split_at_mut(row_len);
rest = tail;
let coeff = coeffs[j];
if coeff != Elem::ZERO {
unsafe { mul_add_impl(&mut row[..span], scale_table(coeff), src) };
}
j += 1;
}
}
#[target_feature(enable = "simd128")]
unsafe fn scatter_quad(mut rows: [&mut [u8]; 4], plans: &[Scaling; 4], src: &[u8]) {
let span = src.len();
let src_ptr = src.as_ptr();
let mut offset = 0;
while offset + 16 <= span {
let x = unsafe { v128_load(src_ptr.add(offset).cast()) };
for (row, plan) in rows.iter_mut().zip(plans) {
if plan.kind == Kind::Skip {
continue;
}
unsafe {
let dp = row.as_mut_ptr().add(offset).cast();
v128_store(dp, plan.fold(v128_load(dp), x));
}
}
offset += 16;
}
for (row, plan) in rows.iter_mut().zip(plans) {
if plan.kind == Kind::Skip {
continue;
}
mul_add_nibble(&mut row[offset..], plan.table, &src[offset..]);
}
}
pub fn gather_simd128(dst: &mut [u8], coeffs: &[Elem], srcs: &[&[u8]]) {
debug_assert_eq!(coeffs.len(), srcs.len());
if dst.is_empty() || coeffs.is_empty() {
return;
}
unsafe { mul_add_gather_impl(dst, coeffs, srcs) }
}
#[target_feature(enable = "simd128")]
unsafe fn mul_add_gather_impl(dst: &mut [u8], coeffs: &[Elem], srcs: &[&[u8]]) {
let count = coeffs.len().min(srcs.len());
let mut span = dst.len();
for &src in &srcs[..count] {
span = span.min(src.len());
}
let dst_ptr = dst.as_mut_ptr();
let mut offset = 0;
while offset + 32 <= span {
let (mut a0, mut a1) = unsafe {
let dp = dst_ptr.add(offset);
(v128_load(dp.cast()), v128_load(dp.add(16).cast()))
};
for k in 0..count {
let plan = Scaling::new(coeffs[k]);
if plan.kind == Kind::Skip {
continue;
}
unsafe {
let sp = srcs[k].as_ptr().add(offset);
a0 = plan.fold(a0, v128_load(sp.cast()));
a1 = plan.fold(a1, v128_load(sp.add(16).cast()));
}
}
unsafe {
let dp = dst_ptr.add(offset);
v128_store(dp.cast(), a0);
v128_store(dp.add(16).cast(), a1);
}
offset += 32;
}
if offset + 16 <= span {
let mut a = unsafe { v128_load(dst_ptr.add(offset).cast()) };
for k in 0..count {
let plan = Scaling::new(coeffs[k]);
if plan.kind == Kind::Skip {
continue;
}
unsafe { a = plan.fold(a, v128_load(srcs[k].as_ptr().add(offset).cast())) };
}
unsafe { v128_store(dst_ptr.add(offset).cast(), a) };
offset += 16;
}
for k in 0..count {
let coeff = coeffs[k];
if coeff != Elem::ZERO {
mul_add_nibble(
&mut dst[offset..span],
scale_table(coeff),
&srcs[k][offset..span],
);
}
}
}
pub fn matrix_simd128(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 = "simd128")]
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 mut rest = &mut rows[..count * row_len];
let mut j = 0;
while j + 4 <= count {
let (block, tail) = rest.split_at_mut(4 * row_len);
rest = tail;
let (r0, block) = block.split_at_mut(row_len);
let (r1, block) = block.split_at_mut(row_len);
let (r2, r3) = block.split_at_mut(row_len);
unsafe {
matrix_quad(
[
&mut r0[..span],
&mut r1[..span],
&mut r2[..span],
&mut r3[..span],
],
j,
terms,
);
}
j += 4;
}
while j < count {
let (row, tail) = rest.split_at_mut(row_len);
rest = tail;
for &(coeffs, src) in terms {
let coeff = coeffs[j];
if coeff != Elem::ZERO {
unsafe { mul_add_impl(&mut row[..span], scale_table(coeff), &src[..span]) };
}
}
j += 1;
}
}
#[target_feature(enable = "simd128")]
unsafe fn matrix_quad(mut rows: [&mut [u8]; 4], first: usize, terms: &[(&[Elem], &[u8])]) {
let span = rows[0].len();
let mut offset = 0;
while offset + 16 <= span {
let mut acc = unsafe {
[
v128_load(rows[0].as_ptr().add(offset).cast()),
v128_load(rows[1].as_ptr().add(offset).cast()),
v128_load(rows[2].as_ptr().add(offset).cast()),
v128_load(rows[3].as_ptr().add(offset).cast()),
]
};
for &(coeffs, src) in terms {
let x = unsafe { v128_load(src.as_ptr().add(offset).cast()) };
for (a, &coeff) in acc.iter_mut().zip(&coeffs[first..first + 4]) {
*a = Scaling::new(coeff).fold(*a, x);
}
}
for (row, &a) in rows.iter_mut().zip(&acc) {
unsafe { v128_store(row.as_mut_ptr().add(offset).cast(), a) };
}
offset += 16;
}
for (i, row) in rows.iter_mut().enumerate() {
for &(coeffs, src) in terms {
let coeff = coeffs[first + i];
if coeff != Elem::ZERO {
mul_add_nibble(&mut row[offset..], scale_table(coeff), &src[offset..span]);
}
}
}
}