#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
use super::{combine_halves_avx2, narrow_avx2, normalize_avx2, widen_add_avx2};
use crate::kernel::Matrix;
use crate::kernel::proven_checks::check_equal;
use crate::kernel::residue::PreparedResidue;
use crate::kernel::x86::residue::SourceGroup;
const TILE: usize = 4;
fn check_geometry_matrix(name: &str, rows_bytes: usize, row_len: usize, nrows: usize) {
assert!(
row_len.is_multiple_of(4),
"{name}: row lane width is 4 bytes but the row is {row_len} bytes"
);
let used = nrows
.checked_mul(row_len)
.unwrap_or_else(|| panic!("{name}: row geometry overflows"));
assert!(
rows_bytes >= used,
"{name}: rows is {rows_bytes} bytes but {nrows} rows of {row_len} bytes need {used}"
);
}
fn check_terms<M: Matrix<PreparedResidue> + ?Sized>(name: &str, row_len: usize, terms: &M) {
for term in 0..terms.len() {
check_equal(
name,
"term source",
terms.source(term).len(),
"row_len",
row_len,
);
}
}
#[allow(clippy::too_many_arguments)]
#[archmage::rite(v3, import_intrinsics)]
fn tile_group<M: Matrix<PreparedResidue> + ?Sized>(
running: &mut [__m256i; TILE],
terms: &M,
group: usize,
count: usize,
offset: usize,
norm_mu: __m256i,
p32: __m256i,
barrett_mu: __m256i,
p64: __m256i,
p64_m1: __m256i,
shift: i32,
) {
let mut acc_e = [_mm256_setzero_si256(); TILE];
let mut acc_o = [_mm256_setzero_si256(); TILE];
for t in 0..terms.len() {
let column: &[u8; 32] = terms.source(t)[offset..offset + 32]
.try_into()
.expect("residue delayed: column lies inside its source");
let v = _mm256_loadu_si256(column);
let x = normalize_avx2(v, norm_mu, p32);
let xe = _mm256_mul_epu32(x, _mm256_set1_epi64x(1));
let xo = _mm256_mul_epu32(_mm256_shuffle_epi32(x, 0xB1), _mm256_set1_epi64x(1));
for (slot, (even, odd)) in acc_e
.iter_mut()
.zip(acc_o.iter_mut())
.take(count)
.enumerate()
{
let c = _mm256_set1_epi64x(i64::from(
(terms.coefficient(t, group + slot).word & 0xFFFF_FFFF) as u32,
));
*even = _mm256_add_epi64(*even, _mm256_mul_epu32(xe, c));
*odd = _mm256_add_epi64(*odd, _mm256_mul_epu32(xo, c));
}
}
for (slot, sum) in running.iter_mut().take(count).enumerate() {
let reduced = combine_halves_avx2(
narrow_avx2(acc_e[slot], barrett_mu, p64, shift),
narrow_avx2(acc_o[slot], barrett_mu, p64, shift),
);
*sum = widen_add_avx2(*sum, reduced, p64, p64_m1);
}
}
#[allow(
clippy::too_many_arguments,
clippy::cast_possible_wrap,
clippy::used_underscore_binding
)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_into_matrix_u32_avx2_with<M: Matrix<PreparedResidue> + ?Sized>(
_token: archmage::X64V3Token,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
p: u32,
) {
run_delayed::<M, false>(
"residue::mul_into_matrix_u32_avx2_with",
rows,
row_len,
nrows,
terms,
p,
);
}
#[allow(
clippy::too_many_arguments,
clippy::cast_possible_wrap,
clippy::used_underscore_binding
)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_matrix_u32_avx2_with<M: Matrix<PreparedResidue> + ?Sized>(
_token: archmage::X64V3Token,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
p: u32,
) {
run_delayed::<M, true>(
"residue::mul_add_matrix_u32_avx2_with",
rows,
row_len,
nrows,
terms,
p,
);
}
#[allow(clippy::too_many_arguments, clippy::cast_possible_wrap)]
#[archmage::rite(v3, import_intrinsics)]
fn run_delayed<M: Matrix<PreparedResidue> + ?Sized, const ADD: bool>(
name: &str,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &M,
p: u32,
) {
check_geometry_matrix(name, rows.len(), row_len, nrows);
check_terms(name, row_len, terms);
assert!(
p > 1,
"{name}: modulus {p} is not a delayed-reduction modulus"
);
assert!(p % 2 == 1, "residue delayed: modulus {p} is even");
#[allow(clippy::cast_possible_truncation)]
let k = ((1u128 << 32) / u128::from(p)) as u32;
assert!(
k >= 2,
"{name}: modulus {p} has delay depth {k} but delayed reduction needs at least 2"
);
for t in 0..terms.len() {
for row in 0..nrows {
let c = (terms.coefficient(t, row).word & 0xFFFF_FFFF) as u32;
assert!(
c < p,
"residue delayed: prepared coefficient {c} reaches modulus {p}"
);
}
}
if row_len == 0 || nrows == 0 || terms.len() == 0 {
if !ADD {
let used = nrows.saturating_mul(row_len).min(rows.len());
rows[..used].fill(0);
}
return;
}
let k = (k as usize).min(terms.len()).max(1);
let p64 = _mm256_set1_epi64x(i64::from(p));
let p64_m1 = _mm256_set1_epi64x(i64::from(p.wrapping_sub(1)));
let p32 = _mm256_set1_epi32(p as i32);
#[allow(clippy::cast_possible_truncation)]
let norm_mu = _mm256_set1_epi64x(u64::from(((1u128 << 32) / u128::from(p)) as u32) as i64);
let shift = p.ilog2();
let s = shift + 32;
#[allow(clippy::cast_possible_truncation)]
let barrett_mu = _mm256_set1_epi64x(u64::from(((1u128 << s) / u128::from(p)) as u32) as i64);
let vector_len = row_len & !31;
let modulus = u64::from(p);
let mut group = 0;
while group < nrows {
let count = (nrows - group).min(TILE);
let mut offset = 0;
while offset < vector_len {
let mut running = [_mm256_setzero_si256(); TILE];
if ADD {
for (slot, sum) in running.iter_mut().take(count).enumerate() {
let lane: &[u8; 32] = rows[(group + slot) * row_len + offset..][..32]
.try_into()
.expect("residue delayed: column lies inside its row");
*sum = normalize_avx2(_mm256_loadu_si256(lane), norm_mu, p32);
}
}
let mut base = 0;
while base < terms.len() {
let len = (terms.len() - base).min(k);
let window = SourceGroup { terms, base, len };
tile_group(
&mut running,
&window,
group,
count,
offset,
norm_mu,
p32,
barrett_mu,
p64,
p64_m1,
shift as i32,
);
base += len;
}
for (slot, sum) in running.iter().take(count).enumerate() {
let row = &mut rows[(group + slot) * row_len..(group + slot + 1) * row_len];
let lane: &mut [u8; 32] = (&mut row[offset..offset + 32])
.try_into()
.expect("residue delayed: column lies inside its row");
_mm256_storeu_si256(lane, *sum);
}
offset += 32;
}
for (slot, row) in rows
.chunks_exact_mut(row_len)
.skip(group)
.take(count)
.enumerate()
{
let tail_lanes = row[vector_len..].len() / 4;
let mut lane_sums = [0u64; 7];
for (lane_sum, lane) in lane_sums.iter_mut().zip(row[vector_len..].chunks_exact(4)) {
*lane_sum = if ADD {
u64::from(u32::from_le_bytes(lane.try_into().unwrap())) % modulus
} else {
0
};
}
let mut base = 0;
while base < terms.len() {
let len = (terms.len() - base).min(k);
for t in base..base + len {
let c =
u64::from((terms.coefficient(t, group + slot).word & 0xFFFF_FFFF) as u32);
for (lane_sum, lane) in lane_sums[..tail_lanes]
.iter_mut()
.zip(terms.source(t)[vector_len..].chunks_exact(4))
{
let x = u64::from(u32::from_le_bytes(lane.try_into().unwrap())) % modulus;
*lane_sum += x * c;
}
}
for lane_sum in &mut lane_sums[..tail_lanes] {
*lane_sum %= modulus;
}
base += len;
}
#[allow(clippy::cast_possible_truncation)]
for (lane, lane_sum) in row[vector_len..].chunks_exact_mut(4).zip(lane_sums.iter()) {
lane.copy_from_slice(&(*lane_sum as u32).to_le_bytes());
}
}
group += count;
}
}