use crate::field::{Elem, FieldBuffer, FieldElem};
use crate::kernel::{FieldKernels, KernelDispatch, RawDispatch};
pub struct Coeff<F: FieldKernels> {
prepared: <F as KernelDispatch>::Prepared,
}
impl<F: FieldKernels> Coeff<F> {
#[inline]
#[must_use]
pub fn new(coeff: Elem<F>) -> Self {
Self {
prepared: F::prepare(RawDispatch, coeff),
}
}
#[inline]
#[must_use]
pub fn value(&self) -> Elem<F> {
F::prepared_coeff(RawDispatch, &self.prepared)
}
}
impl<F: FieldKernels> Clone for Coeff<F> {
fn clone(&self) -> Self {
Self {
prepared: self.prepared.clone(),
}
}
}
impl<F: FieldKernels> core::fmt::Debug for Coeff<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Coeff").field(&self.value()).finish()
}
}
mod private {
pub trait Sealed {}
}
#[allow(private_bounds)]
pub trait PreparedCoefficient<F: FieldKernels>: private::Sealed + PreparedRepr<F> {
fn value(&self) -> Elem<F>;
}
pub(crate) trait PreparedRepr<F: FieldKernels>: private::Sealed {
fn repr(&self, _proof: RawDispatch) -> &<F as KernelDispatch>::Prepared;
}
impl<F: FieldKernels> private::Sealed for Coeff<F> {}
impl<F: FieldKernels> PreparedCoefficient<F> for Coeff<F> {
#[inline]
fn value(&self) -> Elem<F> {
Coeff::value(self)
}
}
impl<F: FieldKernels> PreparedRepr<F> for Coeff<F> {
#[inline]
fn repr(&self, _proof: RawDispatch) -> &<F as KernelDispatch>::Prepared {
&self.prepared
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Copy)]
pub struct CoeffRef<'a, F: FieldKernels> {
prepared: &'a <F as KernelDispatch>::Prepared,
field: core::marker::PhantomData<F>,
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> CoeffRef<'_, F> {
#[inline]
#[must_use]
pub fn value(self) -> Elem<F> {
F::prepared_coeff(RawDispatch, self.prepared)
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> private::Sealed for CoeffRef<'_, F> {}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> PreparedCoefficient<F> for CoeffRef<'_, F> {
#[inline]
fn value(&self) -> Elem<F> {
F::prepared_coeff(RawDispatch, self.prepared)
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> PreparedRepr<F> for CoeffRef<'_, F> {
#[inline]
fn repr(&self, _proof: RawDispatch) -> &<F as KernelDispatch>::Prepared {
self.prepared
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> core::fmt::Debug for CoeffRef<'_, F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("CoeffRef").field(&self.value()).finish()
}
}
#[cfg(feature = "alloc")]
pub struct CoeffVec<F: FieldKernels> {
prepared: alloc::boxed::Box<[<F as KernelDispatch>::Prepared]>,
values: alloc::boxed::Box<[Elem<F>]>,
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> CoeffVec<F> {
#[must_use]
pub fn new(coeffs: &[Elem<F>]) -> Self {
Self {
prepared: prepare_all::<F>(coeffs),
values: alloc::boxed::Box::from(coeffs),
}
}
#[inline]
#[must_use]
pub fn as_ref(&self) -> CoeffVecRef<'_, F> {
CoeffVecRef {
prepared: &self.prepared,
values: &self.values,
field: core::marker::PhantomData,
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.prepared.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.prepared.is_empty()
}
#[inline]
#[must_use]
pub fn get(&self, index: usize) -> Option<CoeffRef<'_, F>> {
self.as_ref().get(index)
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn coeffs(&self) -> impl ExactSizeIterator<Item = CoeffRef<'_, F>> {
self.as_ref().into_coeffs()
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn values(&self) -> impl ExactSizeIterator<Item = Elem<F>> + '_ {
self.values.iter().copied()
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> Clone for CoeffVec<F> {
fn clone(&self) -> Self {
Self {
prepared: self.prepared.clone(),
values: self.values.clone(),
}
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> core::fmt::Debug for CoeffVec<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CoeffVec")
.field("len", &self.len())
.finish_non_exhaustive()
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Copy)]
pub struct CoeffVecRef<'a, F: FieldKernels> {
prepared: &'a [<F as KernelDispatch>::Prepared],
values: &'a [Elem<F>],
field: core::marker::PhantomData<F>,
}
#[cfg(feature = "alloc")]
impl<'a, F: FieldKernels> CoeffVecRef<'a, F> {
#[inline]
#[must_use]
pub fn len(self) -> usize {
self.prepared.len()
}
#[inline]
#[must_use]
pub fn is_empty(self) -> bool {
self.prepared.is_empty()
}
#[inline]
#[must_use]
pub fn get(self, index: usize) -> Option<CoeffRef<'a, F>> {
self.prepared.get(index).map(|prepared| CoeffRef {
prepared,
field: core::marker::PhantomData,
})
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn into_coeffs(self) -> impl ExactSizeIterator<Item = CoeffRef<'a, F>> {
self.prepared.iter().map(|prepared| CoeffRef {
prepared,
field: core::marker::PhantomData,
})
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn values(self) -> impl ExactSizeIterator<Item = Elem<F>> + 'a {
self.values.iter().copied()
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> core::fmt::Debug for CoeffVecRef<'_, F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CoeffVecRef")
.field("len", &self.len())
.finish_non_exhaustive()
}
}
#[cfg(feature = "alloc")]
pub struct CoeffMatrix<F: FieldKernels> {
prepared: alloc::boxed::Box<[<F as KernelDispatch>::Prepared]>,
values: alloc::boxed::Box<[Elem<F>]>,
sources: usize,
outputs: usize,
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> CoeffMatrix<F> {
#[must_use]
pub fn from_source_major(sources: usize, outputs: usize, coeffs: &[Elem<F>]) -> Self {
let len = sources
.checked_mul(outputs)
.expect("CoeffMatrix::from_source_major: dimensions overflow");
assert_eq!(
coeffs.len(),
len,
"CoeffMatrix::from_source_major: {} coefficients for {sources}x{outputs} source-major matrix",
coeffs.len()
);
Self {
prepared: prepare_all::<F>(coeffs),
values: alloc::boxed::Box::from(coeffs),
sources,
outputs,
}
}
#[inline]
#[must_use]
pub const fn source_count(&self) -> usize {
self.sources
}
#[inline]
#[must_use]
pub const fn output_count(&self) -> usize {
self.outputs
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.prepared.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.prepared.is_empty()
}
#[inline]
#[must_use]
pub fn get_at(&self, source: usize, output: usize) -> Option<CoeffRef<'_, F>> {
self.source(source)?.get(output)
}
#[must_use]
pub fn source(&self, source: usize) -> Option<CoeffVecRef<'_, F>> {
if source >= self.sources {
return None;
}
let start = source * self.outputs;
let end = start + self.outputs;
Some(CoeffVecRef {
prepared: self.prepared.get(start..end)?,
values: self.values.get(start..end)?,
field: core::marker::PhantomData,
})
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn values(&self) -> impl ExactSizeIterator<Item = Elem<F>> + '_ {
self.values.iter().copied()
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> Clone for CoeffMatrix<F> {
fn clone(&self) -> Self {
Self {
prepared: self.prepared.clone(),
values: self.values.clone(),
sources: self.sources,
outputs: self.outputs,
}
}
}
#[cfg(feature = "alloc")]
impl<F: FieldKernels> core::fmt::Debug for CoeffMatrix<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CoeffMatrix")
.field("sources", &self.sources)
.field("outputs", &self.outputs)
.field("len", &self.len())
.finish_non_exhaustive()
}
}
#[cfg(feature = "alloc")]
fn prepare_all<F: FieldKernels>(
coeffs: &[Elem<F>],
) -> alloc::boxed::Box<[<F as KernelDispatch>::Prepared]> {
coeffs
.iter()
.copied()
.map(|coeff| F::prepare(RawDispatch, coeff))
.collect::<alloc::vec::Vec<_>>()
.into_boxed_slice()
}
#[inline]
fn check_width<F: FieldBuffer>(name: &str, len: usize) {
assert!(
len.is_multiple_of(F::BYTES),
"{name}: buffer of {len} bytes is not a whole number of {} elements",
F::NAME
);
}
#[inline]
fn check_pair<F: FieldBuffer>(
name: &str,
left_name: &str,
left: usize,
right_name: &str,
right: usize,
) {
assert_eq!(
left, right,
"{name}: {left_name} is {left} bytes but {right_name} is {right} bytes"
);
check_width::<F>(name, left);
}
#[inline]
pub fn add_assign<F: FieldKernels>(dst: &mut [u8], src: &[u8]) {
check_pair::<F>("add_assign", "dst", dst.len(), "src", src.len());
F::add_assign(RawDispatch, dst, src);
}
#[inline]
pub fn add_assign_rows<F: FieldKernels>(dst: &mut [u8], row_len: usize, src: &[u8]) {
assert_ne!(row_len, 0, "add_assign_rows: row length must be nonzero");
check_width::<F>("add_assign_rows", row_len);
assert_eq!(
dst.len(),
src.len(),
"add_assign_rows: dst is {} bytes but src is {} bytes",
dst.len(),
src.len(),
);
assert_eq!(
dst.len() % row_len,
0,
"add_assign_rows: partial trailing row",
);
F::add_assign_rows(RawDispatch, dst, src, row_len);
}
#[inline]
pub fn add_gather_offsets<F: FieldKernels>(dst: &mut [u8], region: &[u8], offsets: &[u32]) {
check_width::<F>("add_gather_offsets", dst.len());
let live = dst.len();
for (index, &start) in offsets.iter().enumerate() {
let start = start as usize;
assert!(
start + live <= region.len(),
"add_gather_offsets: offset {index} ({start}) + {live} exceeds region of {} bytes",
region.len(),
);
}
F::add_gather_offsets(RawDispatch, region, dst, offsets);
}
#[inline]
pub fn sub_assign<F: FieldKernels>(dst: &mut [u8], src: &[u8]) {
check_pair::<F>("sub_assign", "dst", dst.len(), "src", src.len());
F::sub_assign(RawDispatch, dst, src);
}
#[inline]
pub fn mul_add<F: FieldKernels>(dst: &mut [u8], coeff: Elem<F>, src: &[u8]) {
check_pair::<F>("mul_add", "dst", dst.len(), "src", src.len());
if coeff.is_zero() {
return;
}
if coeff.is_one() {
F::add_assign(RawDispatch, dst, src);
return;
}
F::mul_add(RawDispatch, dst, &F::prepare(RawDispatch, coeff), src);
}
#[inline]
pub fn mul_add_with<F: FieldKernels>(
dst: &mut [u8],
coeff: &impl PreparedCoefficient<F>,
src: &[u8],
) {
check_pair::<F>("mul_add_with", "dst", dst.len(), "src", src.len());
let value = coeff.value();
if value.is_zero() {
return;
}
if value.is_one() {
F::add_assign(RawDispatch, dst, src);
return;
}
F::mul_add(RawDispatch, dst, coeff.repr(RawDispatch), src);
}
#[inline]
pub fn mul_into<F: FieldKernels>(dst: &mut [u8], coeff: Elem<F>, src: &[u8]) {
check_pair::<F>("mul_into", "dst", dst.len(), "src", src.len());
if coeff.is_zero() {
dst.fill(0);
return;
}
if coeff.is_one() {
dst.copy_from_slice(src);
return;
}
F::mul_into(RawDispatch, dst, &F::prepare(RawDispatch, coeff), src);
}
#[inline]
pub fn mul_into_with<F: FieldKernels>(
dst: &mut [u8],
coeff: &impl PreparedCoefficient<F>,
src: &[u8],
) {
check_pair::<F>("mul_into_with", "dst", dst.len(), "src", src.len());
let value = coeff.value();
if value.is_zero() {
dst.fill(0);
return;
}
if value.is_one() {
dst.copy_from_slice(src);
return;
}
F::mul_into(RawDispatch, dst, coeff.repr(RawDispatch), src);
}
#[inline]
pub fn mul_assign<F: FieldKernels>(dst: &mut [u8], coeff: Elem<F>) {
check_width::<F>("mul_assign", dst.len());
if coeff.is_one() {
return;
}
if coeff.is_zero() {
dst.fill(0);
return;
}
F::mul_assign(RawDispatch, dst, &F::prepare(RawDispatch, coeff));
}
#[inline]
pub fn mul_assign_with<F: FieldKernels>(dst: &mut [u8], coeff: &impl PreparedCoefficient<F>) {
check_width::<F>("mul_assign_with", dst.len());
let value = coeff.value();
if value.is_one() {
return;
}
if value.is_zero() {
dst.fill(0);
return;
}
F::mul_assign(RawDispatch, dst, coeff.repr(RawDispatch));
}
pub fn mul_add_scatter<F: FieldKernels>(
rows: &mut [u8],
row_len: usize,
coeffs: &[Elem<F>],
src: &[u8],
) {
check_pair::<F>("mul_add_scatter", "row_len", row_len, "src", src.len());
let used = coeffs
.len()
.checked_mul(row_len)
.expect("mul_add_scatter: row geometry overflows");
assert!(
rows.len() >= used,
"mul_add_scatter: rows is {} bytes but {} rows of {row_len} bytes need {used}",
rows.len(),
coeffs.len(),
);
if row_len == 0 || coeffs.is_empty() || coeffs.iter().all(|c| c.is_zero()) {
return;
}
F::mul_add_scatter(RawDispatch, &mut rows[..used], row_len, coeffs, src);
}
#[cfg(feature = "alloc")]
pub fn mul_add_scatter_with<F: FieldKernels>(
rows: &mut [u8],
row_len: usize,
coeffs: CoeffVecRef<'_, F>,
src: &[u8],
) {
check_pair::<F>("mul_add_scatter_with", "row_len", row_len, "src", src.len());
let used = coeffs
.len()
.checked_mul(row_len)
.expect("mul_add_scatter_with: row geometry overflows");
assert!(
rows.len() >= used,
"mul_add_scatter_with: rows is {} bytes but {} rows of {row_len} bytes need {used}",
rows.len(),
coeffs.len(),
);
if row_len == 0 || coeffs.values().all(FieldElem::is_zero) {
return;
}
F::mul_add_scatter_plan(
RawDispatch,
&mut rows[..used],
row_len,
coeffs.values,
coeffs.prepared,
src,
);
}
pub fn mul_add_gather<F: FieldKernels>(dst: &mut [u8], coeffs: &[Elem<F>], srcs: &[&[u8]]) {
check_width::<F>("mul_add_gather", dst.len());
assert_eq!(
coeffs.len(),
srcs.len(),
"mul_add_gather: {} coefficients for {} sources",
coeffs.len(),
srcs.len()
);
for &src in srcs {
assert_eq!(
dst.len(),
src.len(),
"mul_add_gather: source is {} bytes, expected {}",
src.len(),
dst.len()
);
}
if dst.is_empty() || srcs.is_empty() || coeffs.iter().all(|c| c.is_zero()) {
return;
}
F::mul_add_gather(RawDispatch, dst, coeffs, srcs);
}
#[cfg(feature = "alloc")]
pub fn mul_add_gather_with<F: FieldKernels>(
dst: &mut [u8],
coeffs: CoeffVecRef<'_, F>,
srcs: &[&[u8]],
) {
check_width::<F>("mul_add_gather_with", dst.len());
assert_eq!(
coeffs.len(),
srcs.len(),
"mul_add_gather_with: {} coefficients for {} sources",
coeffs.len(),
srcs.len(),
);
for (index, &src) in srcs.iter().enumerate() {
assert_eq!(
dst.len(),
src.len(),
"mul_add_gather_with: source {index} is {} bytes, expected {}",
src.len(),
dst.len(),
);
}
if dst.is_empty() || srcs.is_empty() || coeffs.values().all(FieldElem::is_zero) {
return;
}
F::mul_add_gather_plan(RawDispatch, dst, coeffs.values, coeffs.prepared, srcs);
}
pub fn mul_into_gather<F: FieldKernels>(dst: &mut [u8], coeffs: &[Elem<F>], srcs: &[&[u8]]) {
check_width::<F>("mul_into_gather", dst.len());
assert_eq!(
coeffs.len(),
srcs.len(),
"mul_into_gather: {} coefficients for {} sources",
coeffs.len(),
srcs.len()
);
for (index, &src) in srcs.iter().enumerate() {
assert_eq!(
dst.len(),
src.len(),
"mul_into_gather: source {index} is {} bytes, expected {}",
src.len(),
dst.len()
);
}
if dst.is_empty() || srcs.is_empty() || coeffs.iter().all(|coeff| coeff.is_zero()) {
dst.fill(0);
return;
}
if let [coeff] = coeffs {
mul_into::<F>(dst, *coeff, srcs[0]);
return;
}
F::mul_into_gather(RawDispatch, dst, coeffs, srcs);
}
#[cfg(feature = "alloc")]
pub fn mul_into_gather_with<F: FieldKernels>(
dst: &mut [u8],
coeffs: CoeffVecRef<'_, F>,
srcs: &[&[u8]],
) {
check_width::<F>("mul_into_gather_with", dst.len());
assert_eq!(
coeffs.len(),
srcs.len(),
"mul_into_gather_with: {} coefficients for {} sources",
coeffs.len(),
srcs.len(),
);
for (index, &src) in srcs.iter().enumerate() {
assert_eq!(
dst.len(),
src.len(),
"mul_into_gather_with: source {index} is {} bytes, expected {}",
src.len(),
dst.len(),
);
}
if dst.is_empty() || srcs.is_empty() || coeffs.values().all(FieldElem::is_zero) {
dst.fill(0);
return;
}
if coeffs.len() == 1 {
let coeff = coeffs
.get(0)
.expect("single-entry view has one coefficient");
mul_into_with::<F>(dst, &coeff, srcs[0]);
return;
}
F::mul_into_gather_plan(RawDispatch, dst, coeffs.values, coeffs.prepared, srcs);
}
pub fn mul_add_matrix<F: FieldKernels>(
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Elem<F>], &[u8])],
) {
check_width::<F>("mul_add_matrix", row_len);
let used = nrows
.checked_mul(row_len)
.expect("mul_add_matrix: row geometry overflows");
assert!(
rows.len() >= used,
"mul_add_matrix: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
for &(coeffs, src) in terms {
assert_eq!(
coeffs.len(),
nrows,
"mul_add_matrix: term supplies {} coefficients for {nrows} rows",
coeffs.len()
);
assert_eq!(
src.len(),
row_len,
"mul_add_matrix: source is {} bytes, expected {row_len}",
src.len()
);
}
if nrows == 0 || row_len == 0 || terms.is_empty() {
return;
}
F::mul_add_matrix(RawDispatch, &mut rows[..used], row_len, nrows, terms);
}
#[cfg(feature = "alloc")]
pub fn mul_add_matrix_with<F: FieldKernels>(
rows: &mut [u8],
row_len: usize,
coeffs: &CoeffMatrix<F>,
srcs: &[&[u8]],
) {
check_width::<F>("mul_add_matrix_with", row_len);
let nrows = coeffs.output_count();
let used = nrows
.checked_mul(row_len)
.expect("mul_add_matrix_with: row geometry overflows");
assert!(
rows.len() >= used,
"mul_add_matrix_with: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
assert_eq!(
coeffs.source_count(),
srcs.len(),
"mul_add_matrix_with: matrix holds coefficients for {} sources but there are {}",
coeffs.source_count(),
srcs.len(),
);
for (index, &src) in srcs.iter().enumerate() {
assert_eq!(
src.len(),
row_len,
"mul_add_matrix_with: source {index} is {} bytes, expected {row_len}",
src.len(),
);
}
if nrows == 0 || row_len == 0 || srcs.is_empty() {
return;
}
F::mul_add_matrix_plan(
RawDispatch,
&mut rows[..used],
row_len,
nrows,
coeffs.values.as_ref(),
coeffs.prepared.as_ref(),
srcs,
);
}
pub fn mul_into_matrix<F: FieldKernels>(
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Elem<F>], &[u8])],
) {
check_width::<F>("mul_into_matrix", row_len);
let used = nrows
.checked_mul(row_len)
.expect("mul_into_matrix: row geometry overflows");
assert!(
rows.len() >= used,
"mul_into_matrix: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
for &(coeffs, src) in terms {
assert_eq!(
coeffs.len(),
nrows,
"mul_into_matrix: term supplies {} coefficients for {nrows} rows",
coeffs.len()
);
assert_eq!(
src.len(),
row_len,
"mul_into_matrix: source is {} bytes, expected {row_len}",
src.len()
);
}
if nrows == 0 || row_len == 0 {
return;
}
if terms.is_empty() {
rows[..used].fill(0);
return;
}
F::mul_into_matrix(RawDispatch, &mut rows[..used], row_len, nrows, terms);
}
#[cfg(feature = "alloc")]
pub fn mul_into_matrix_with<F: FieldKernels>(
rows: &mut [u8],
row_len: usize,
coeffs: &CoeffMatrix<F>,
srcs: &[&[u8]],
) {
check_width::<F>("mul_into_matrix_with", row_len);
let nrows = coeffs.output_count();
let used = nrows
.checked_mul(row_len)
.expect("mul_into_matrix_with: row geometry overflows");
assert!(
rows.len() >= used,
"mul_into_matrix_with: rows is {} bytes but {nrows} rows of {row_len} bytes need {used}",
rows.len(),
);
assert_eq!(
coeffs.source_count(),
srcs.len(),
"mul_into_matrix_with: matrix holds coefficients for {} sources but there are {}",
coeffs.source_count(),
srcs.len(),
);
for (index, &src) in srcs.iter().enumerate() {
assert_eq!(
src.len(),
row_len,
"mul_into_matrix_with: source {index} is {} bytes, expected {row_len}",
src.len(),
);
}
if nrows == 0 || row_len == 0 {
return;
}
if srcs.is_empty() {
rows[..used].fill(0);
return;
}
F::mul_into_matrix_plan(
RawDispatch,
&mut rows[..used],
row_len,
nrows,
coeffs.values.as_ref(),
coeffs.prepared.as_ref(),
srcs,
);
}
pub fn mul_add_matrix_at<F: FieldKernels>(
dst: &mut [u8],
row_len: usize,
row_starts: &[usize],
terms: &[(&[Elem<F>], &[u8])],
) {
check_width::<F>("mul_add_matrix_at", row_len);
for (j, &start) in row_starts.iter().enumerate() {
let end = start
.checked_add(row_len)
.expect("mul_add_matrix_at: row offset + length overflows");
assert!(
end <= dst.len(),
"mul_add_matrix_at: row {j} spans {start}..{end} but dst is {} bytes",
dst.len(),
);
assert!(
start.is_multiple_of(F::BYTES),
"mul_add_matrix_at: row {j} offset {start} is not a whole number of {} elements",
F::NAME,
);
}
for (a, &sa) in row_starts.iter().enumerate() {
for &sb in &row_starts[a + 1..] {
let (lo, hi) = if sa <= sb { (sa, sb) } else { (sb, sa) };
assert!(
hi - lo >= row_len,
"mul_add_matrix_at: rows at {sa} and {sb} overlap for {row_len}-byte rows",
);
}
}
let nrows = row_starts.len();
for &(coeffs, src) in terms {
assert_eq!(
coeffs.len(),
nrows,
"mul_add_matrix_at: term supplies {} coefficients for {nrows} rows",
coeffs.len(),
);
assert_eq!(
src.len(),
row_len,
"mul_add_matrix_at: source is {} bytes, expected {row_len}",
src.len()
);
}
if nrows == 0 || row_len == 0 || terms.is_empty() {
return;
}
F::mul_add_matrix_at(RawDispatch, dst, row_len, row_starts, terms);
}
pub fn mul_elementwise<F: FieldKernels>(dst: &mut [u8], a: &[u8], b: &[u8]) {
check_pair::<F>("mul_elementwise", "dst", dst.len(), "a", a.len());
check_pair::<F>("mul_elementwise", "dst", dst.len(), "b", b.len());
F::mul_elementwise(RawDispatch, dst, a, b);
}
#[inline]
pub fn mul_elementwise_assign<F: FieldKernels>(dst: &mut [u8], src: &[u8]) {
check_pair::<F>("mul_elementwise_assign", "dst", dst.len(), "src", src.len());
F::mul_elementwise_assign(RawDispatch, dst, src);
}
#[inline]
pub fn add_assign_scalar<F: FieldKernels>(dst: &mut [u8], value: Elem<F>) {
check_width::<F>("add_assign_scalar", dst.len());
F::add_assign_scalar(RawDispatch, dst, &F::prepare(RawDispatch, value));
}
#[inline]
pub fn sub_assign_scalar<F: FieldKernels>(dst: &mut [u8], value: Elem<F>) {
check_width::<F>("sub_assign_scalar", dst.len());
F::sub_assign_scalar(RawDispatch, dst, &F::prepare(RawDispatch, value));
}
pub fn pack<F: FieldBuffer>(dst: &mut [u8], elems: &[Elem<F>]) {
let expected = elems
.len()
.checked_mul(F::BYTES)
.expect("pack: buffer geometry overflows");
assert_eq!(
dst.len(),
expected,
"pack: dst is {} bytes but {} {} elements need {expected}",
dst.len(),
elems.len(),
F::NAME,
);
for (bytes, &elem) in dst.chunks_exact_mut(F::BYTES).zip(elems) {
F::encode(bytes, elem);
}
}
pub fn unpack<F: FieldBuffer>(dst: &mut [Elem<F>], src: &[u8]) {
let expected = dst
.len()
.checked_mul(F::BYTES)
.expect("unpack: buffer geometry overflows");
assert_eq!(
src.len(),
expected,
"unpack: src is {} bytes but dst holds {} {} elements ({expected} bytes)",
src.len(),
dst.len(),
F::NAME,
);
for (elem, bytes) in dst.iter_mut().zip(src.chunks_exact(F::BYTES)) {
*elem = F::decode(bytes);
}
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn pack_to_vec<F: FieldBuffer>(elems: &[Elem<F>]) -> alloc::vec::Vec<u8> {
let len = elems
.len()
.checked_mul(F::BYTES)
.expect("pack_to_vec: buffer geometry overflows");
let mut bytes = alloc::vec![0; len];
pack::<F>(&mut bytes, elems);
bytes
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
use crate::field::poly::AES;
use crate::field::{Binary, Elem as E8, Polynomial, Rijndael16, Tower};
use alloc::vec::Vec;
#[test]
#[allow(clippy::type_complexity)]
fn zero_row_length_is_a_no_op() {
let coeffs = [E8::<Binary<8, Polynomial<AES>>>::from_raw(0x07); 3];
let mut rows = [0xA5u8; 8];
mul_add_scatter::<Binary<8, Polynomial<AES>>>(&mut rows, 0, &coeffs, &[]);
assert_eq!(rows, [0xA5; 8]);
let coeffs_vec = CoeffVec::<Binary<8, Polynomial<AES>>>::new(&coeffs);
mul_add_scatter_with::<Binary<8, Polynomial<AES>>>(&mut rows, 0, coeffs_vec.as_ref(), &[]);
assert_eq!(rows, [0xA5; 8]);
let terms: &[(&[E8<Binary<8, Polynomial<AES>>>], &[u8])] = &[(&coeffs, &[])];
mul_add_matrix::<Binary<8, Polynomial<AES>>>(&mut rows, 0, 3, terms);
assert_eq!(rows, [0xA5; 8]);
mul_into_matrix::<Binary<8, Polynomial<AES>>>(&mut rows, 0, 3, terms);
assert_eq!(rows, [0xA5; 8]);
mul_add_matrix_at::<Binary<8, Polynomial<AES>>>(
&mut rows,
0,
&[1, 4],
&[(&coeffs[..2], &[])],
);
assert_eq!(rows, [0xA5; 8]);
let matrix = CoeffMatrix::<Binary<8, Polynomial<AES>>>::from_source_major(1, 3, &coeffs);
mul_add_matrix_with::<Binary<8, Polynomial<AES>>>(&mut rows, 0, &matrix, &[&[]]);
assert_eq!(rows, [0xA5; 8]);
mul_into_matrix_with::<Binary<8, Polynomial<AES>>>(&mut rows, 0, &matrix, &[&[]]);
assert_eq!(rows, [0xA5; 8]);
let wide = [Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(0x0103); 2];
let mut rows16 = [0x5Au8; 4];
mul_add_scatter::<Binary<16, Tower<Rijndael16>>>(&mut rows16, 0, &wide, &[]);
assert_eq!(rows16, [0x5A; 4]);
}
#[test]
#[should_panic(expected = "mul_add_scatter")]
fn zero_row_length_still_checks_pairing() {
let coeffs = [E8::<Binary<8, Polynomial<AES>>>::from_raw(0x07); 3];
let mut rows = [0u8; 8];
mul_add_scatter::<Binary<8, Polynomial<AES>>>(&mut rows, 0, &coeffs, &[1, 2, 3]);
}
#[test]
#[should_panic(expected = "term supplies")]
fn zero_row_length_still_checks_term_counts() {
let coeffs = [E8::<Binary<8, Polynomial<AES>>>::from_raw(0x07); 2];
let mut rows = [0u8; 8];
mul_add_matrix::<Binary<8, Polynomial<AES>>>(&mut rows, 0, 3, &[(&coeffs, &[])]);
}
#[test]
fn matrix_source_lookup_is_total() {
let matrix = CoeffMatrix::<Binary<8, Polynomial<AES>>>::from_source_major(
3,
2,
&[E8::<Binary<8, Polynomial<AES>>>::from_raw(1); 6],
);
assert!(matrix.source(0).is_some());
assert!(matrix.source(2).is_some());
assert!(matrix.source(3).is_none());
assert!(matrix.source(usize::MAX).is_none());
assert_eq!(matrix.source(1).map(CoeffVecRef::len), Some(2));
let empty = CoeffMatrix::<Binary<8, Polynomial<AES>>>::from_source_major(3, 0, &[]);
assert_eq!(empty.source_count(), 3);
assert_eq!(empty.output_count(), 0);
assert_eq!(empty.source(0).map(CoeffVecRef::len), Some(0));
assert_eq!(empty.source(2).map(CoeffVecRef::len), Some(0));
assert!(empty.source(3).is_none());
assert!(empty.source(usize::MAX).is_none());
assert!(matrix.get_at(0, usize::MAX).is_none());
assert!(matrix.get_at(usize::MAX, 0).is_none());
assert!(matrix.get_at(usize::MAX, usize::MAX).is_none());
}
#[test]
fn coeff_matrix_is_source_major() {
let values: Vec<E8<Binary<8, Polynomial<AES>>>> = (0u8..6)
.map(|i| E8::<Binary<8, Polynomial<AES>>>::from_raw(i * 37))
.collect();
let matrix = CoeffMatrix::<Binary<8, Polynomial<AES>>>::from_source_major(2, 3, &values);
for source in 0..2 {
for output in 0..3 {
let at = matrix
.get_at(source, output)
.map(CoeffRef::value)
.expect("in-bounds coordinate");
assert_eq!(at, values[source * 3 + output]);
}
}
let row_one: Vec<E8<Binary<8, Polynomial<AES>>>> = matrix
.source(1)
.unwrap()
.into_coeffs()
.map(CoeffRef::value)
.collect();
assert_eq!(row_one, values[3..6]);
}
#[test]
fn prepared_matrix_ops_match_one_shot() {
let values: Vec<Elem<Binary<16, Tower<Rijndael16>>>> = (0u16..3 * 4)
.map(|i| Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(i * 511 + 3))
.collect();
let matrix = CoeffMatrix::<Binary<16, Tower<Rijndael16>>>::from_source_major(3, 4, &values);
let srcs: Vec<Vec<u8>> = (0u8..3).map(|s| alloc::vec![s + 1; 8]).collect();
let src_refs: Vec<&[u8]> = srcs.iter().map(Vec::as_slice).collect();
let terms: Vec<(&[Elem<_>], &[u8])> = (0..3)
.map(|s| (&values[s * 4..s * 4 + 4], src_refs[s]))
.collect();
let mut one_shot = [0x11u8; 32];
mul_add_matrix::<Binary<16, Tower<Rijndael16>>>(&mut one_shot, 8, 4, &terms);
let mut prepared = [0x11u8; 32];
mul_add_matrix_with::<Binary<16, Tower<Rijndael16>>>(&mut prepared, 8, &matrix, &src_refs);
assert_eq!(one_shot, prepared);
let mut one_shot = [0x22u8; 32];
mul_into_matrix::<Binary<16, Tower<Rijndael16>>>(&mut one_shot, 8, 4, &terms);
let mut prepared = [0x22u8; 32];
mul_into_matrix_with::<Binary<16, Tower<Rijndael16>>>(&mut prepared, 8, &matrix, &src_refs);
assert_eq!(one_shot, prepared);
}
}