use crate::field::{Elem, FieldElem};
use crate::kernel::FieldKernels;
use super::{check_pair, check_width};
const CHUNK_ELEMS: usize = 64;
const SCRATCH_BYTES: usize = CHUNK_ELEMS * 8;
fn chunk_elems<F: FieldKernels>() -> usize {
assert!(
F::BYTES <= SCRATCH_BYTES,
"element width exceeds the batch scratch"
);
(SCRATCH_BYTES / F::BYTES).min(CHUNK_ELEMS)
}
pub fn inv_into<F: FieldKernels>(dst: &mut [u8], src: &[u8]) {
check_pair::<F>("inv_into", "dst", dst.len(), "src", src.len());
let bytes = F::BYTES;
let mut offset = 0;
while offset < dst.len() {
let len = (dst.len() - offset).min(CHUNK_ELEMS * bytes);
batch_invert_into::<F>(&mut dst[offset..offset + len], &src[offset..offset + len]);
offset += len;
}
}
pub fn inv_assign<F: FieldKernels>(dst: &mut [u8]) {
check_width::<F>("inv_assign", dst.len());
let bytes = F::BYTES;
let mut scratch = [0u8; SCRATCH_BYTES];
let mut offset = 0;
while offset < dst.len() {
let len = (dst.len() - offset).min(chunk_elems::<F>() * bytes);
scratch[..len].copy_from_slice(&dst[offset..offset + len]);
batch_invert_into::<F>(&mut dst[offset..offset + len], &scratch[..len]);
offset += len;
}
}
pub fn div_elementwise<F: FieldKernels>(dst: &mut [u8], a: &[u8], b: &[u8]) {
check_pair::<F>("div_elementwise", "dst", dst.len(), "a", a.len());
check_pair::<F>("div_elementwise", "dst", dst.len(), "b", b.len());
batch_divide_into::<F>(dst, a, b);
}
pub fn div_elementwise_assign<F: FieldKernels>(dst: &mut [u8], src: &[u8]) {
check_pair::<F>("div_elementwise_assign", "dst", dst.len(), "src", src.len());
let bytes = F::BYTES;
let mut num = [0u8; SCRATCH_BYTES];
let mut den = [0u8; SCRATCH_BYTES];
let mut offset = 0;
while offset < dst.len() {
let len = (dst.len() - offset).min(chunk_elems::<F>() * bytes);
num[..len].copy_from_slice(&dst[offset..offset + len]);
den[..len].copy_from_slice(&src[offset..offset + len]);
batch_divide_into::<F>(&mut dst[offset..offset + len], &num[..len], &den[..len]);
offset += len;
}
}
fn batch_invert_into<F: FieldKernels>(dst: &mut [u8], src: &[u8]) {
let bytes = F::BYTES;
let elems = src.len() / bytes;
let mut prefix = [Elem::<F>::ZERO; CHUNK_ELEMS];
let mut acc = Elem::<F>::ONE;
for (slot, lane) in prefix.iter_mut().take(elems).zip(src.chunks_exact(bytes)) {
let value = F::decode(lane);
if !value.is_zero() {
acc = acc.mul(value);
}
*slot = acc;
}
let mut suffix = acc.inv();
for i in (0..elems).rev() {
let value = F::decode(&src[i * bytes..(i + 1) * bytes]);
if value.is_zero() {
F::encode(&mut dst[i * bytes..(i + 1) * bytes], Elem::<F>::ZERO);
} else {
let before = if i == 0 {
Elem::<F>::ONE
} else {
prefix[i - 1]
};
F::encode(&mut dst[i * bytes..(i + 1) * bytes], before.mul(suffix));
suffix = suffix.mul(value);
}
}
}
fn batch_divide_into<F: FieldKernels>(dst: &mut [u8], num: &[u8], den: &[u8]) {
let bytes = F::BYTES;
let elems = dst.len() / bytes;
let mut recip = [0u8; SCRATCH_BYTES];
let mut offset = 0;
while offset < elems {
let count = (elems - offset).min(chunk_elems::<F>());
let (rbuf, _) = recip.split_at_mut(count * bytes);
batch_invert_into::<F>(rbuf, &den[offset * bytes..(offset + count) * bytes]);
for index in 0..count {
let lane = offset + index;
let value = F::decode(&num[lane * bytes..(lane + 1) * bytes]);
let inverse = F::decode(&rbuf[index * bytes..(index + 1) * bytes]);
F::encode(
&mut dst[lane * bytes..(lane + 1) * bytes],
value.mul(inverse),
);
}
offset += count;
}
}