use super::super::RESOLVE_CHUNK;
use super::{bmul_gfni, brem_gfni, factor_word_gfni, wfactor_gfni};
use crate::kernel::Matrix;
use crate::kernel::gf8::Prepared;
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
const MATRIX_PREFETCH_DISTANCE: usize = 1024;
pub(crate) const MATRIX_PREFETCH_MIN: usize = 8192;
#[inline]
#[archmage::rite(v3_gfni_crypto)]
fn fold_term<const NATIVE: bool, const ROWS: usize, const LANES: usize>(
acc: &mut [[__m256i; LANES]; ROWS],
map: &[u64; ROWS],
x: &[__m256i; LANES],
first: bool,
) {
for (slots, &word) in acc.iter_mut().zip(map) {
let factor = wfactor_gfni::<NATIVE>(word);
for (slot, &value) in slots.iter_mut().zip(x) {
let product = bmul_gfni::<NATIVE>(value, factor);
*slot = if first {
product
} else {
_mm256_xor_si256(*slot, product)
};
}
}
}
#[allow(unsafe_code)]
#[archmage::rite(v3_gfni_crypto)]
fn rows_body<
const NATIVE: bool,
const ROWS: usize,
const LANES: usize,
const OVERWRITE: bool,
const PREFETCH: bool,
>(
ptrs: [*mut u8; ROWS],
vector_len: usize,
maps: &[[u64; ROWS]],
srcs: &[&[u8]],
) {
let step = 32 * LANES;
let mut tile = 0;
while tile + step <= vector_len {
let mut acc = [[_mm256_setzero_si256(); LANES]; ROWS];
if !OVERWRITE {
unsafe {
for (&ptr, slots) in ptrs.iter().zip(acc.iter_mut()) {
for (lane, slot) in slots.iter_mut().enumerate() {
*slot = _mm256_loadu_si256(ptr.add(tile + 32 * lane).cast());
}
}
}
}
let load = |src: &[u8]| -> [__m256i; LANES] {
if PREFETCH {
let ahead = src.as_ptr().wrapping_add(tile + MATRIX_PREFETCH_DISTANCE);
for line in (0..step).step_by(64) {
_mm_prefetch::<{ _MM_HINT_T0 }>(ahead.wrapping_add(line).cast());
}
}
unsafe {
let sp = src.as_ptr().add(tile);
core::array::from_fn(|lane| _mm256_loadu_si256(sp.add(32 * lane).cast()))
}
};
let mut first = OVERWRITE && ROWS > 1;
if LANES == 3 {
let (map_pairs, map_rest) = maps.as_chunks::<2>();
let (src_pairs, src_rest) = srcs.as_chunks::<2>();
for ([map0, map1], [src0, src1]) in map_pairs.iter().zip(src_pairs) {
fold_term::<NATIVE, ROWS, LANES>(&mut acc, map0, &load(src0), first);
fold_term::<NATIVE, ROWS, LANES>(&mut acc, map1, &load(src1), false);
first = false;
}
if let ([map], [src]) = (map_rest, src_rest) {
fold_term::<NATIVE, ROWS, LANES>(&mut acc, map, &load(src), first);
}
} else {
for (map, src) in maps.iter().zip(srcs) {
fold_term::<NATIVE, ROWS, LANES>(&mut acc, map, &load(src), first);
first = false;
}
}
unsafe {
for (&ptr, slots) in ptrs.iter().zip(&acc) {
for (lane, &value) in slots.iter().enumerate() {
_mm256_storeu_si256(ptr.add(tile + 32 * lane).cast(), value);
}
}
}
tile += step;
}
while tile + 32 <= vector_len {
let mut acc = [_mm256_setzero_si256(); ROWS];
if !OVERWRITE {
unsafe {
for (&ptr, slot) in ptrs.iter().zip(acc.iter_mut()) {
*slot = _mm256_loadu_si256(ptr.add(tile).cast());
}
}
}
for (map, src) in maps.iter().zip(srcs) {
unsafe {
let x = _mm256_loadu_si256(src.as_ptr().add(tile).cast());
for (slot, &word) in acc.iter_mut().zip(map) {
*slot = _mm256_xor_si256(
*slot,
bmul_gfni::<NATIVE>(x, wfactor_gfni::<NATIVE>(word)),
);
}
}
}
unsafe {
for (&ptr, &value) in ptrs.iter().zip(&acc) {
_mm256_storeu_si256(ptr.add(tile).cast(), value);
}
}
tile += 32;
}
}
#[allow(unsafe_code)]
#[archmage::rite(v3_gfni_crypto)]
fn rows_resolved<
const NATIVE: bool,
M: Matrix<Prepared> + ?Sized,
const ROWS: usize,
const LANES: usize,
const OVERWRITE: bool,
>(
ptrs: [*mut u8; ROWS],
row_len: usize,
g: usize,
terms: &M,
) {
let vector_len = row_len & !31;
let count = terms.len();
let prefetch = vector_len >= MATRIX_PREFETCH_MIN && count <= RESOLVE_CHUNK;
let mut start = 0;
let mut first = true;
loop {
let taken = (count - start).min(RESOLVE_CHUNK);
let mut maps: [core::mem::MaybeUninit<[u64; ROWS]>; RESOLVE_CHUNK] =
unsafe { core::mem::MaybeUninit::uninit().assume_init() };
let mut srcs: [core::mem::MaybeUninit<&[u8]>; RESOLVE_CHUNK] =
unsafe { core::mem::MaybeUninit::uninit().assume_init() };
for offset in 0..taken {
let term = start + offset;
let mut words = [0u64; ROWS];
for (row, word) in words.iter_mut().enumerate() {
*word = factor_word_gfni::<NATIVE>(terms.coefficient(term, g + row));
}
maps[offset].write(words);
let src = terms.source(term);
assert!(
src.len() >= vector_len,
"matrix kernel: term {term} source is shorter than the row span"
);
srcs[offset].write(src);
}
let (maps, srcs): (&[[u64; ROWS]], &[&[u8]]) = unsafe {
(
core::slice::from_raw_parts(maps.as_ptr().cast::<[u64; ROWS]>(), taken),
core::slice::from_raw_parts(srcs.as_ptr().cast::<&[u8]>(), taken),
)
};
if OVERWRITE && first {
if prefetch {
rows_body::<NATIVE, ROWS, LANES, true, true>(ptrs, vector_len, maps, srcs);
} else {
rows_body::<NATIVE, ROWS, LANES, true, false>(ptrs, vector_len, maps, srcs);
}
} else if prefetch {
rows_body::<NATIVE, ROWS, LANES, false, true>(ptrs, vector_len, maps, srcs);
} else {
rows_body::<NATIVE, ROWS, LANES, false, false>(ptrs, vector_len, maps, srcs);
}
first = false;
start += taken;
if start >= count {
break;
}
}
matrix_tail::<NATIVE, M, OVERWRITE>(&ptrs, row_len, g, vector_len, terms);
}
pub(super) const MAX_GROUP_ROWS: usize = 6;
const FEW_TERMS: usize = 2;
const THREE_LANE_MIN_TERMS: usize = 6;
const THREE_LANE_MIN_ROW: usize = 512;
const THREE_LANE_MAX_ROW: usize = 128 * 1024;
const fn three_lanes(nterms: usize, row_len: usize) -> bool {
nterms >= THREE_LANE_MIN_TERMS
&& nterms <= RESOLVE_CHUNK
&& row_len >= THREE_LANE_MIN_ROW
&& row_len <= THREE_LANE_MAX_ROW
}
pub(super) fn group_rows(nrows: usize, done: usize, nterms: usize) -> usize {
let left = nrows - done;
let widest = if nterms <= FEW_TERMS {
4
} else {
MAX_GROUP_ROWS
};
left.div_ceil(left.div_ceil(widest))
}
#[archmage::rite(v3_gfni_crypto)]
pub(super) fn matrix_group<
const NATIVE: bool,
M: Matrix<Prepared> + ?Sized,
const OVERWRITE: bool,
>(
rows: &[*mut u8],
row_len: usize,
g: usize,
terms: &M,
) {
match *rows {
[a] => rows_resolved::<NATIVE, M, 1, 4, OVERWRITE>([a], row_len, g, terms),
[a, b] => rows_resolved::<NATIVE, M, 2, 4, OVERWRITE>([a, b], row_len, g, terms),
[a, b, c] if three_lanes(terms.len(), row_len) => {
rows_resolved::<NATIVE, M, 3, 3, OVERWRITE>([a, b, c], row_len, g, terms);
}
[a, b, c] => rows_resolved::<NATIVE, M, 3, 2, OVERWRITE>([a, b, c], row_len, g, terms),
[a, b, c, d] => {
rows_resolved::<NATIVE, M, 4, 2, OVERWRITE>([a, b, c, d], row_len, g, terms);
}
[a, b, c, d, e] => {
rows_resolved::<NATIVE, M, 5, 2, OVERWRITE>([a, b, c, d, e], row_len, g, terms);
}
[a, b, c, d, e, f] => {
rows_resolved::<NATIVE, M, 6, 2, OVERWRITE>([a, b, c, d, e, f], row_len, g, terms);
}
_ => unreachable!("matrix row group holds one to {MAX_GROUP_ROWS} rows"),
}
}
#[allow(unsafe_code)]
#[archmage::rite(v3_gfni_crypto)]
fn matrix_tail<const NATIVE: bool, M: Matrix<Prepared> + ?Sized, const OVERWRITE: bool>(
ptrs: &[*mut u8],
row_len: usize,
g: usize,
tile: usize,
terms: &M,
) {
if tile == row_len {
return;
}
let remaining = row_len - tile;
for (slot, &row) in ptrs.iter().enumerate() {
let tail = unsafe { core::slice::from_raw_parts_mut(row.add(tile), remaining) };
if OVERWRITE {
tail.fill(0);
}
for term in 0..terms.len() {
let src = terms.source(term);
let coeff = terms.coefficient(term, g + slot);
if !coeff.is_zero() {
brem_gfni::<NATIVE>(tail, coeff, &src[tile..]);
}
}
}
}