pub(crate) mod fan_paar;
pub(crate) mod tower;
#[allow(unused_imports)]
pub(crate) use tower::{gf16, gf32, gf64};
pub(crate) mod gf2;
pub(crate) mod gf8;
pub(crate) mod goldilocks;
pub(crate) mod mersenne31;
pub(crate) mod prime;
pub(crate) mod quad_mersenne31;
pub(crate) mod scalar;
pub(crate) mod tables;
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
pub(crate) mod aarch64;
#[cfg(all(feature = "simd", target_arch = "wasm32"))]
pub(crate) mod wasm32;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
pub(crate) mod x86;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
pub(crate) mod matrix_provider;
pub(crate) use byte_ops::xor;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
pub(crate) use matrix_provider::{FlatMatrix, Matrix};
#[cfg(test)]
mod tests;
use crate::field::Field;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
use archmage::SimdToken;
#[cfg(feature = "simd")]
use simdispatch::Selection;
mod private {
pub trait Sealed {}
}
impl private::Sealed for crate::field::gf8b::Gf8B {}
impl private::Sealed for crate::field::gf8d::Gf8D {}
impl private::Sealed for crate::field::gf16::Gf16 {}
impl private::Sealed for crate::field::gf32::Gf32 {}
impl private::Sealed for crate::field::gf64::Gf64 {}
impl private::Sealed for crate::field::fan_paar::FanPaar8 {}
impl private::Sealed for crate::field::fan_paar::FanPaar16 {}
impl private::Sealed for crate::field::fan_paar::FanPaar32 {}
impl private::Sealed for crate::field::fan_paar::FanPaar64 {}
impl private::Sealed for crate::field::mersenne31::Mersenne31 {}
impl private::Sealed for crate::field::goldilocks::Goldilocks {}
impl private::Sealed for crate::field::quad_mersenne31::QuadMersenne31 {}
pub use simdispatch::{Backend, ParseBackendError};
pub const FGF_TIERS: &[Backend] = &[
Backend::V3GfniCrypto,
Backend::V3,
Backend::V2,
Backend::NeonAes,
Backend::Neon,
Backend::Wasm128,
Backend::Scalar,
];
pub trait KernelBackend {
fn has_native_mul(self) -> bool;
fn has_blocked_rows(self) -> bool;
}
impl KernelBackend for Backend {
#[inline]
fn has_native_mul(self) -> bool {
matches!(self, Backend::V3GfniCrypto)
}
#[inline]
fn has_blocked_rows(self) -> bool {
matches!(
self,
Backend::V3GfniCrypto | Backend::Neon | Backend::NeonAes
)
}
}
#[inline]
#[must_use]
pub fn backend() -> Backend {
#[cfg(feature = "simd")]
{
BACKEND.backend
}
#[cfg(not(feature = "simd"))]
{
Backend::Scalar
}
}
#[cfg(feature = "simd")]
static BACKEND: std::sync::LazyLock<ResolvedBackend> =
std::sync::LazyLock::new(ResolvedBackend::new);
#[cfg(feature = "simd")]
struct ResolvedBackend {
backend: Backend,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
x86: X86Proof,
}
#[cfg(feature = "simd")]
impl ResolvedBackend {
fn new() -> Self {
let backend = Selection::new("SIMD_BACKEND").supports(FGF_TIERS).resolve();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let x86 = match backend {
Backend::V3GfniCrypto => X86Proof::V3GfniCrypto(
archmage::X64V3GfniCryptoToken::summon()
.expect("selected x86 GFNI backend must have its capability token"),
),
Backend::V3 => X86Proof::V3(
archmage::X64V3Token::summon()
.expect("selected x86 V3 backend must have its capability token"),
),
Backend::V2 => X86Proof::V2(
archmage::X64V2Token::summon()
.expect("selected x86 V2 backend must have its capability token"),
),
_ => X86Proof::Scalar,
};
Self {
backend,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
x86,
}
}
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[derive(Clone, Copy)]
pub(crate) enum X86Proof {
V3GfniCrypto(archmage::X64V3GfniCryptoToken),
V3(archmage::X64V3Token),
V2(archmage::X64V2Token),
Scalar,
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
pub(crate) fn x86_proof() -> X86Proof {
BACKEND.x86
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
pub(crate) fn x86_v3_gfni_token() -> archmage::X64V3GfniCryptoToken {
match x86_proof() {
X86Proof::V3GfniCrypto(token) => token,
_ => unreachable!("GFNI kernel reached without the selected GFNI proof"),
}
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
pub(crate) fn x86_v3_token() -> archmage::X64V3Token {
match x86_proof() {
X86Proof::V3GfniCrypto(token) => token.v3(),
X86Proof::V3(token) => token,
_ => unreachable!("V3 kernel reached without a selected V3 proof"),
}
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
pub(crate) fn x86_v2_token() -> archmage::X64V2Token {
match x86_proof() {
X86Proof::V3GfniCrypto(token) => token.v3().v2(),
X86Proof::V3(token) => token.v2(),
X86Proof::V2(token) => token,
X86Proof::Scalar => unreachable!("V2 kernel reached without a selected V2 proof"),
}
}
#[inline]
#[must_use]
pub fn backend_for<F: FieldKernels>() -> Backend {
F::backend()
}
#[inline]
#[must_use]
pub fn has_vector_elementwise<F: FieldKernels>() -> bool {
F::has_vector_elementwise()
}
#[inline]
#[must_use]
pub fn vector_elementwise_min_bytes<F: FieldKernels>() -> usize {
F::vector_elementwise_min_bytes()
}
#[allow(private_bounds)]
pub trait FieldKernels: Field + private::Sealed + KernelDispatch {
#[inline]
#[must_use]
fn backend() -> Backend {
Backend::Scalar
}
#[inline]
#[must_use]
fn has_vector_elementwise() -> bool {
false
}
#[inline]
#[must_use]
fn vector_elementwise_min_bytes() -> usize {
0
}
}
pub(crate) trait KernelDispatch: Field {
type Prepared: Clone + Send + Sync + core::fmt::Debug;
fn prepare(_proof: RawDispatch, coeff: Self::Elem) -> Self::Prepared;
fn prepared_coeff(_proof: RawDispatch, prepared: &Self::Prepared) -> Self::Elem;
fn add_assign(_proof: RawDispatch, dst: &mut [u8], src: &[u8]);
fn add_gather_offsets(_proof: RawDispatch, region: &[u8], dst: &mut [u8], offsets: &[u32]) {
let live = dst.len();
for &start in offsets {
Self::add_assign(
RawDispatch,
dst,
®ion[start as usize..start as usize + live],
);
}
}
fn sub_assign(_proof: RawDispatch, dst: &mut [u8], src: &[u8]);
fn add_assign_rows(_proof: RawDispatch, dst: &mut [u8], src: &[u8], _row_len: usize) {
Self::add_assign(RawDispatch, dst, src);
}
fn mul_add(_proof: RawDispatch, dst: &mut [u8], coeff: &Self::Prepared, src: &[u8]);
fn mul_into(_proof: RawDispatch, dst: &mut [u8], coeff: &Self::Prepared, src: &[u8]) {
dst.copy_from_slice(src);
Self::mul_assign(RawDispatch, dst, coeff);
}
fn mul_assign(_proof: RawDispatch, dst: &mut [u8], coeff: &Self::Prepared);
fn mul_add_scatter(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
coeffs: &[Self::Elem],
src: &[u8],
);
fn mul_add_gather(_proof: RawDispatch, dst: &mut [u8], coeffs: &[Self::Elem], srcs: &[&[u8]]);
fn mul_into_gather(_proof: RawDispatch, dst: &mut [u8], coeffs: &[Self::Elem], srcs: &[&[u8]]) {
let mut pairs = coeffs.iter().copied().zip(srcs.iter().copied());
let Some((first, src)) = pairs.next() else {
dst.fill(0);
return;
};
Self::mul_into(RawDispatch, dst, &Self::prepare(RawDispatch, first), src);
for (coeff, src) in pairs {
Self::mul_add(RawDispatch, dst, &Self::prepare(RawDispatch, coeff), src);
}
}
fn mul_add_matrix(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Self::Elem], &[u8])],
);
#[cfg(feature = "alloc")]
fn mul_add_scatter_with(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
coeffs: &[Self::Prepared],
src: &[u8],
) {
for (row, coeff) in rows.chunks_exact_mut(row_len).zip(coeffs) {
Self::mul_add(RawDispatch, row, coeff, src);
}
}
#[cfg(feature = "alloc")]
fn mul_add_scatter_plan(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
_values: &[Self::Elem],
coeffs: &[Self::Prepared],
src: &[u8],
) {
Self::mul_add_scatter_with(RawDispatch, rows, row_len, coeffs, src);
}
#[cfg(feature = "alloc")]
fn mul_add_gather_with(
_proof: RawDispatch,
dst: &mut [u8],
coeffs: &[Self::Prepared],
srcs: &[&[u8]],
) {
for (coeff, &src) in coeffs.iter().zip(srcs) {
Self::mul_add(RawDispatch, dst, coeff, src);
}
}
#[cfg(feature = "alloc")]
fn mul_add_gather_plan(
_proof: RawDispatch,
dst: &mut [u8],
_values: &[Self::Elem],
coeffs: &[Self::Prepared],
srcs: &[&[u8]],
) {
Self::mul_add_gather_with(RawDispatch, dst, coeffs, srcs);
}
#[cfg(any(feature = "alloc", test))]
fn mul_into_gather_with(
_proof: RawDispatch,
dst: &mut [u8],
coeffs: &[Self::Prepared],
srcs: &[&[u8]],
) {
let mut pairs = coeffs.iter().zip(srcs.iter().copied());
let Some((first, src)) = pairs.next() else {
dst.fill(0);
return;
};
Self::mul_into(RawDispatch, dst, first, src);
for (coeff, src) in pairs {
Self::mul_add(RawDispatch, dst, coeff, src);
}
}
#[cfg(feature = "alloc")]
fn mul_into_gather_plan(
_proof: RawDispatch,
dst: &mut [u8],
_values: &[Self::Elem],
coeffs: &[Self::Prepared],
srcs: &[&[u8]],
) {
Self::mul_into_gather_with(RawDispatch, dst, coeffs, srcs);
}
#[cfg(test)]
fn mul_add_matrix_with(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Self::Prepared], &[u8])],
) {
for &(coeffs, src) in terms {
for (row, coeff) in rows.chunks_exact_mut(row_len).take(nrows).zip(coeffs) {
Self::mul_add(RawDispatch, row, coeff, src);
}
}
}
#[cfg(feature = "alloc")]
fn mul_add_matrix_plan(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
nrows: usize,
_values: &[Self::Elem],
coeffs: &[Self::Prepared],
srcs: &[&[u8]],
) {
for (term, &src) in srcs.iter().enumerate() {
let start = term * nrows;
for (row, coeff) in rows
.chunks_exact_mut(row_len)
.take(nrows)
.zip(&coeffs[start..start + nrows])
{
Self::mul_add(RawDispatch, row, coeff, src);
}
}
}
fn mul_into_matrix(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
nrows: usize,
terms: &[(&[Self::Elem], &[u8])],
) {
for row in rows.chunks_exact_mut(row_len).take(nrows) {
row.fill(0);
}
Self::mul_add_matrix(RawDispatch, rows, row_len, nrows, terms);
}
#[cfg(feature = "alloc")]
fn mul_into_matrix_plan(
_proof: RawDispatch,
rows: &mut [u8],
row_len: usize,
nrows: usize,
values: &[Self::Elem],
coeffs: &[Self::Prepared],
srcs: &[&[u8]],
) {
for row in rows.chunks_exact_mut(row_len).take(nrows) {
row.fill(0);
}
Self::mul_add_matrix_plan(RawDispatch, rows, row_len, nrows, values, coeffs, srcs);
}
fn mul_add_matrix_at(
_proof: RawDispatch,
dst: &mut [u8],
row_len: usize,
row_starts: &[usize],
terms: &[(&[Self::Elem], &[u8])],
) {
scalar::mul_add_matrix_at::<Self>(dst, row_len, row_starts, terms);
}
fn mul_elementwise(_proof: RawDispatch, dst: &mut [u8], a: &[u8], b: &[u8]);
}
pub(crate) struct RawDispatch;
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
pub(crate) mod proven_checks {
#[inline]
pub(crate) fn check_equal(
name: &str,
left_name: impl core::fmt::Display,
left: usize,
right_name: impl core::fmt::Display,
right: usize,
) {
assert_eq!(
left, right,
"{name}: {left_name} is {left} bytes but {right_name} is {right} bytes"
);
}
#[inline]
pub(crate) fn check_elem_multiple(name: &str, len: usize, elem_bytes: usize) {
assert!(
len.is_multiple_of(elem_bytes),
"{name}: buffer of {len} bytes is not a whole number of {elem_bytes}-byte elements"
);
}
#[inline]
pub(crate) fn check_row_span(name: &str, buffer_len: usize, row_len: usize, count: usize) {
let used = count
.checked_mul(row_len)
.unwrap_or_else(|| panic!("{name}: row geometry overflows"));
assert!(
buffer_len >= used,
"{name}: rows is {buffer_len} bytes but {count} rows of {row_len} bytes need {used}"
);
}
}
pub(crate) fn xor_gather(region: &[u8], dst: &mut [u8], offsets: &[u32]) {
let live = dst.len();
for (index, &start) in offsets.iter().enumerate() {
let start = start as usize;
assert!(
start + live <= region.len(),
"xor_gather: offset {index} ({start}) + {live} exceeds region of {} bytes",
region.len(),
);
}
if offsets.is_empty() {
return;
}
if live <= XOR_INLINE_MAX {
for &start in offsets {
scalar::xor(dst, ®ion[start as usize..start as usize + live]);
}
return;
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
match x86_proof() {
X86Proof::V3GfniCrypto(token) => {
x86::xor_gather_avx2(token.v3(), region, dst, offsets);
}
X86Proof::V3(token) => x86::xor_gather_avx2(token, region, dst, offsets),
X86Proof::V2(token) => {
for &start in offsets {
x86::xor_sse2(
token.v1(),
dst,
®ion[start as usize..start as usize + live],
);
}
}
X86Proof::Scalar => {
for &start in offsets {
scalar::xor(dst, ®ion[start as usize..start as usize + live]);
}
}
}
#[cfg(not(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64"))))]
match backend() {
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
Backend::Neon | Backend::NeonAes => {
for &start in offsets {
aarch64::xor_neon(dst, ®ion[start as usize..start as usize + live]);
}
}
#[cfg(all(feature = "simd", target_arch = "wasm32"))]
Backend::Wasm128 => {
for &start in offsets {
wasm32::xor_simd128(dst, ®ion[start as usize..start as usize + live]);
}
}
_ => {
for &start in offsets {
scalar::xor(dst, ®ion[start as usize..start as usize + live]);
}
}
}
}
const XOR_INLINE_MAX: usize = 31;
pub(crate) mod byte_ops {
use super::*;
pub fn xor(dst: &mut [u8], src: &[u8]) {
assert_eq!(dst.len(), src.len(), "fgf::xor: length mismatch");
if dst.len() <= XOR_INLINE_MAX {
scalar::xor(dst, src);
return;
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
match x86_proof() {
X86Proof::V3GfniCrypto(token) => x86::xor_avx2(token.v3(), dst, src),
X86Proof::V3(token) => x86::xor_avx2(token, dst, src),
X86Proof::V2(token) => x86::xor_sse2(token.v1(), dst, src),
X86Proof::Scalar => scalar::xor(dst, src),
}
#[cfg(not(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64"))))]
match backend() {
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
Backend::Neon | Backend::NeonAes => aarch64::xor_neon(dst, src),
#[cfg(all(feature = "simd", target_arch = "wasm32"))]
Backend::Wasm128 => wasm32::xor_simd128(dst, src),
_ => scalar::xor(dst, src),
}
}
}