use crate::field::gf16::Elem;
use crate::kernel::Matrix;
use crate::kernel::gf16::{factor_tables, mul_add_scalar};
use crate::kernel::tables::{ScaleTable, TowerCoeff};
use super::{
LaneAvx2, NibbleSsse3, TERM_TILE, TableCoefficient, broadcast_words, lane_avx2, nibble_ssse3,
scale_gfni, scale_split_avx2, scale_ssse3, split_source_avx2, swap_mask256,
};
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
const TERM_BLOCK: usize = 16;
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_matrix_gfni(
token: archmage::X64V3GfniCryptoToken,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Elem], &[u8])],
) {
super::check_elements("gf16::mul_add_matrix_gfni", row_len);
let used = nrows
.checked_mul(row_len)
.expect("gf16::mul_add_matrix_gfni: row geometry overflows");
assert!(
rows.len() >= used,
"gf16::mul_add_matrix_gfni: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
for (t, &(coeffs, src)) in terms.iter().enumerate() {
assert_eq!(
coeffs.len(),
nrows,
"gf16::mul_add_matrix_gfni: term {t} coefficients is {} but rows is {nrows}",
coeffs.len(),
);
assert_eq!(
src.len(),
row_len,
"gf16::mul_add_matrix_gfni: term {t} source is {} bytes but row_len is {row_len} bytes",
src.len(),
);
}
if row_len == 0 || nrows == 0 || terms.is_empty() {
return;
}
mul_add_matrix_gfni_with(token, rows, row_len, nrows, terms);
}
#[allow(clippy::used_underscore_binding)]
#[allow(unsafe_code)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_matrix_gfni_with<M: Matrix<Elem> + ?Sized>(
_token: archmage::X64V3GfniCryptoToken,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
) {
super::check_elements("gf16::mul_add_matrix_gfni_with", row_len);
if row_len == 0 || nrows == 0 || terms.len() == 0 {
return;
}
let used = nrows
.checked_mul(row_len)
.expect("gf16::mul_add_matrix_gfni_with: row geometry overflows");
assert!(
rows.len() >= used,
"gf16::mul_add_matrix_gfni_with: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
let nrows = nrows.min(rows.len() / row_len);
let mut span = row_len;
for term in 0..terms.len() {
span = span.min(terms.source(term).len());
}
if nrows == 0 || span == 0 {
return;
}
let base = rows.as_mut_ptr();
let swap = swap_mask256();
let mut j = 0;
while j + 4 <= nrows {
unsafe { matrix_group_gfni::<4, M>(base.add(j * row_len), row_len, span, j, terms, swap) };
j += 4;
}
if j + 2 <= nrows {
unsafe { matrix_group_gfni::<2, M>(base.add(j * row_len), row_len, span, j, terms, swap) };
j += 2;
}
if j < nrows {
unsafe { matrix_group_gfni::<1, M>(base.add(j * row_len), row_len, span, j, terms, swap) };
}
}
#[allow(unsafe_code)]
#[archmage::rite(v3_gfni_crypto)]
unsafe fn matrix_group_gfni<const N: usize, M: Matrix<Elem> + ?Sized>(
base: *mut u8,
row_len: usize,
span: usize,
first: usize,
terms: &M,
swap: __m256i,
) {
let mut rows = [core::ptr::null_mut::<u8>(); N];
for (k, row) in rows.iter_mut().enumerate() {
*row = unsafe { base.add(k * row_len) };
}
for block_start in (0..terms.len()).step_by(TERM_BLOCK) {
let block_len = (terms.len() - block_start).min(TERM_BLOCK);
let mut words = [[(0i16, 0i16); N]; TERM_BLOCK];
for (t, row_words) in words.iter_mut().take(block_len).enumerate() {
for (k, slot) in row_words.iter_mut().enumerate() {
let coeff = *terms.coefficient(block_start + t, first + k);
*slot = broadcast_words(TowerCoeff::new(coeff));
}
}
let mut offset = 0;
while offset + 32 <= span {
unsafe {
let mut acc = [_mm256_setzero_si256(); N];
for (k, a) in acc.iter_mut().enumerate() {
*a = _mm256_loadu_si256(rows[k].add(offset).cast());
}
for (t, row_words) in words.iter().take(block_len).enumerate() {
let src = terms.source(block_start + t);
let x = _mm256_loadu_si256(src.as_ptr().add(offset).cast());
let swapped = _mm256_shuffle_epi8(x, swap);
for (k, a) in acc.iter_mut().enumerate() {
let (same, cross) = row_words[k];
let scaled = scale_gfni(
x,
swapped,
_mm256_set1_epi16(same),
_mm256_set1_epi16(cross),
);
*a = _mm256_xor_si256(*a, scaled);
}
}
for (k, &a) in acc.iter().enumerate() {
_mm256_storeu_si256(rows[k].add(offset).cast(), a);
}
}
offset += 32;
}
if offset < span {
for (k, &row) in rows.iter().enumerate() {
let tail =
unsafe { core::slice::from_raw_parts_mut(row.add(offset), span - offset) };
for term in block_start..block_start + block_len {
let coeff = *terms.coefficient(term, first + k);
mul_add_scalar(tail, coeff, &terms.source(term)[offset..span]);
}
}
}
}
}
#[cfg_attr(not(test), allow(dead_code))]
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_matrix_avx2(
token: archmage::X64V3Token,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Elem], &[u8])],
) {
super::check_elements("gf16::mul_add_matrix_avx2", row_len);
let used = nrows
.checked_mul(row_len)
.expect("gf16::mul_add_matrix_avx2: row geometry overflows");
assert!(
rows.len() >= used,
"gf16::mul_add_matrix_avx2: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
for (t, &(coeffs, src)) in terms.iter().enumerate() {
assert_eq!(
coeffs.len(),
nrows,
"gf16::mul_add_matrix_avx2: term {t} coefficients is {} but rows is {nrows}",
coeffs.len(),
);
assert_eq!(
src.len(),
row_len,
"gf16::mul_add_matrix_avx2: term {t} source is {} bytes but row_len is {row_len} bytes",
src.len(),
);
}
if row_len == 0 || nrows == 0 || terms.is_empty() {
return;
}
mul_add_matrix_avx2_with(token, rows, row_len, nrows, terms);
}
#[allow(clippy::used_underscore_binding)]
#[allow(unsafe_code)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_matrix_avx2_with<C: TableCoefficient, M: Matrix<C> + ?Sized>(
_token: archmage::X64V3Token,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
) {
super::check_elements("gf16::mul_add_matrix_avx2_with", row_len);
if row_len == 0 || nrows == 0 || terms.len() == 0 {
return;
}
let used = nrows
.checked_mul(row_len)
.expect("gf16::mul_add_matrix_avx2_with: row geometry overflows");
assert!(
rows.len() >= used,
"gf16::mul_add_matrix_avx2_with: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
let nrows = nrows.min(rows.len() / row_len);
let mut span = row_len;
for term in 0..terms.len() {
span = span.min(terms.source(term).len());
}
if nrows == 0 || span == 0 {
return;
}
let base = rows.as_mut_ptr();
let lanes = lane_avx2();
let mut j = 0;
while j + 4 <= nrows {
unsafe {
matrix_group_avx2::<4, C, M>(base.add(j * row_len), row_len, span, j, terms, &lanes);
};
j += 4;
}
if j + 2 <= nrows {
unsafe {
matrix_group_avx2::<2, C, M>(base.add(j * row_len), row_len, span, j, terms, &lanes);
};
j += 2;
}
if j < nrows {
unsafe {
matrix_group_avx2::<1, C, M>(base.add(j * row_len), row_len, span, j, terms, &lanes);
};
}
}
#[allow(unsafe_code)]
#[archmage::rite(v3)]
unsafe fn matrix_group_avx2<const N: usize, C: TableCoefficient, M: Matrix<C> + ?Sized>(
base: *mut u8,
row_len: usize,
span: usize,
first: usize,
terms: &M,
lanes: &LaneAvx2,
) {
let vector_len = span & !31;
let mut rows = [core::ptr::null_mut::<u8>(); N];
for (k, row) in rows.iter_mut().enumerate() {
*row = unsafe { base.add(k * row_len) };
}
for block in (0..terms.len()).step_by(TERM_TILE) {
let block_len = (terms.len() - block).min(TERM_TILE);
let factors: [[[&'static ScaleTable; 4]; N]; TERM_TILE] = core::array::from_fn(|t| {
core::array::from_fn(|k| {
let coeff = terms.coefficient(block + t.min(block_len - 1), first + k);
factor_tables(coeff.coefficient())
})
});
let mut offset = 0;
while offset < vector_len {
unsafe {
let mut acc = [_mm256_setzero_si256(); N];
for (k, a) in acc.iter_mut().enumerate() {
*a = _mm256_loadu_si256(rows[k].add(offset).cast());
}
for (t, row_factors) in factors.iter().take(block_len).enumerate() {
let source =
_mm256_loadu_si256(terms.source(block + t).as_ptr().add(offset).cast());
let split = split_source_avx2(source, lanes);
for (a, factor) in acc.iter_mut().zip(row_factors) {
*a = _mm256_xor_si256(*a, scale_split_avx2(&split, factor, lanes.even));
}
}
for (k, &a) in acc.iter().enumerate() {
_mm256_storeu_si256(rows[k].add(offset).cast(), a);
}
}
offset += 32;
}
if vector_len < span {
for (k, &row) in rows.iter().enumerate() {
let tail = unsafe {
core::slice::from_raw_parts_mut(row.add(vector_len), span - vector_len)
};
for term in block..block + block_len {
mul_add_scalar(
tail,
terms.coefficient(term, first + k).coefficient(),
&terms.source(term)[vector_len..],
);
}
}
}
}
}
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_matrix_ssse3(
token: archmage::X64V2Token,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Elem], &[u8])],
) {
super::check_elements("gf16::mul_add_matrix_ssse3", row_len);
let used = nrows
.checked_mul(row_len)
.expect("gf16::mul_add_matrix_ssse3: row geometry overflows");
assert!(
rows.len() >= used,
"gf16::mul_add_matrix_ssse3: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
for (t, &(coeffs, src)) in terms.iter().enumerate() {
assert_eq!(
coeffs.len(),
nrows,
"gf16::mul_add_matrix_ssse3: term {t} coefficients is {} but rows is {nrows}",
coeffs.len(),
);
assert_eq!(
src.len(),
row_len,
"gf16::mul_add_matrix_ssse3: term {t} source is {} bytes but row_len is {row_len} bytes",
src.len(),
);
}
if row_len == 0 || nrows == 0 || terms.is_empty() {
return;
}
mul_add_matrix_ssse3_with(token, rows, row_len, nrows, terms);
}
#[allow(clippy::used_underscore_binding)]
#[allow(unsafe_code)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_matrix_ssse3_with<C: TableCoefficient, M: Matrix<C> + ?Sized>(
_token: archmage::X64V2Token,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
) {
super::check_elements("gf16::mul_add_matrix_ssse3_with", row_len);
if row_len == 0 || nrows == 0 || terms.len() == 0 {
return;
}
let used = nrows
.checked_mul(row_len)
.expect("gf16::mul_add_matrix_ssse3_with: row geometry overflows");
assert!(
rows.len() >= used,
"gf16::mul_add_matrix_ssse3_with: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
let nrows = nrows.min(rows.len() / row_len);
let mut span = row_len;
for term in 0..terms.len() {
span = span.min(terms.source(term).len());
}
if nrows == 0 || span == 0 {
return;
}
unsafe { matrix_rows_ssse3(rows, row_len, span, nrows, terms) };
}
#[allow(unsafe_code)]
#[archmage::rite(v2)]
unsafe fn matrix_rows_ssse3<C: TableCoefficient, M: Matrix<C> + ?Sized>(
rows: &mut [u8],
row_len: usize,
span: usize,
nrows: usize,
terms: &M,
) {
let vector_len = span & !15;
let base = rows.as_mut_ptr();
for group in (0..nrows).step_by(4) {
let row_count = (nrows - group).min(4);
for block in (0..terms.len()).step_by(TERM_TILE) {
let term_count = (terms.len() - block).min(TERM_TILE);
let vectors: [[NibbleSsse3; 4]; TERM_TILE] = core::array::from_fn(|t| {
core::array::from_fn(|r| {
terms
.coefficient(block + t.min(term_count - 1), group + r.min(row_count - 1))
.with_tables(|tables| nibble_ssse3(tables))
})
});
let mut offset = 0;
while offset < vector_len {
unsafe {
let mut acc = [_mm_setzero_si128(); 4];
for (r, slot) in acc.iter_mut().take(row_count).enumerate() {
*slot = _mm_loadu_si128(base.add((group + r) * row_len + offset).cast());
}
for (t, vector) in vectors.iter().take(term_count).enumerate() {
let source =
_mm_loadu_si128(terms.source(block + t).as_ptr().add(offset).cast());
for (value, scale) in acc.iter_mut().zip(vector).take(row_count) {
*value = _mm_xor_si128(*value, scale_ssse3(source, scale));
}
}
for (r, &slot) in acc.iter().take(row_count).enumerate() {
_mm_storeu_si128(base.add((group + r) * row_len + offset).cast(), slot);
}
}
offset += 16;
}
for r in 0..row_count {
let tail = unsafe {
core::slice::from_raw_parts_mut(
base.add((group + r) * row_len + vector_len),
span - vector_len,
)
};
for term in block..block + term_count {
mul_add_scalar(
tail,
terms.coefficient(term, group + r).coefficient(),
&terms.source(term)[vector_len..],
);
}
}
}
}
}