use hemera::field::Goldilocks as HG;
use nebu::Goldilocks;
pub const WIDTH: usize = 16;
pub const RATE: usize = 9;
pub const NODE_TAG: u64 = 0x6e6f6465;
pub mod tag {
pub const STEP: u64 = 1;
pub const LEAF: u64 = 2;
pub const STATE: u64 = 3;
pub const PRE: u64 = 4;
pub const CHAIN: u64 = 5;
pub const CTX: u64 = 6;
pub const CAP: u64 = 7;
pub const DECIDE: u64 = 8;
pub const WRAP: u64 = 9;
pub const PUBLIC: u64 = 10;
pub const MSG: u64 = 11;
}
fn to_h(x: Goldilocks) -> HG {
HG::new(x.as_u64())
}
fn from_h(x: HG) -> Goldilocks {
Goldilocks::new(x.as_canonical_u64())
}
pub static COUNT: [core::sync::atomic::AtomicU64; 2] = [
core::sync::atomic::AtomicU64::new(0),
core::sync::atomic::AtomicU64::new(0),
];
pub fn count() -> (u64, u64) {
use core::sync::atomic::Ordering::Relaxed;
(COUNT[0].load(Relaxed), COUNT[1].load(Relaxed))
}
pub fn permute(s: &mut [Goldilocks; WIDTH]) {
COUNT[0].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let mut h: [HG; WIDTH] = core::array::from_fn(|i| to_h(s[i]));
hemera::permutation::permute(&mut h);
*s = core::array::from_fn(|i| from_h(h[i]));
}
pub const BACKEND_MIN: usize = 1 << 12;
pub fn permute_many(states: &mut Goldilocks; WIDTH) {
COUNT[1].fetch_add(states.len() as u64, core::sync::atomic::Ordering::Relaxed);
if states.len() >= BACKEND_MIN {
let mut raw: Vec<[u64; WIDTH]> = states.iter().map(|s| s.map(|x| x.as_u64())).collect();
lens::rspcs::backend::current().permute(&mut raw);
for (s, r) in states.iter_mut().zip(&raw) {
*s = r.map(Goldilocks::new);
}
return;
}
permute_many_local(states);
}
pub fn permute_many_local(states: &mut Goldilocks; WIDTH) {
let mut h: Vec<[HG; WIDTH]> = states
.iter()
.map(|s| core::array::from_fn(|i| to_h(s[i])))
.collect();
hemera::permutation::permute_batch(&mut h);
for (s, hs) in states.iter_mut().zip(h) {
*s = core::array::from_fn(|i| from_h(hs[i]));
}
}
static MEMO: std::sync::Mutex<Option<MemoMap>> = std::sync::Mutex::new(None);
type MemoMap = std::collections::HashMap<[u64; WIDTH], [Goldilocks; WIDTH], MemoHash>;
pub const MEMO_CAP: usize = 1 << 16;
#[derive(Clone, Copy, Default)]
pub struct MemoHash;
impl core::hash::BuildHasher for MemoHash {
type Hasher = MemoHasher;
fn build_hasher(&self) -> MemoHasher {
MemoHasher(0)
}
}
#[derive(Clone, Copy)]
pub struct MemoHasher(u64);
impl core::hash::Hasher for MemoHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for c in bytes.chunks(8) {
let mut b = [0u8; 8];
b[..c.len()].copy_from_slice(c);
self.write_u64(u64::from_le_bytes(b));
}
}
fn write_u64(&mut self, x: u64) {
self.0 = (self.0.rotate_left(5) ^ x).wrapping_mul(0x9e37_79b9_7f4a_7c15);
}
}
fn key(s: &[Goldilocks; WIDTH]) -> [u64; WIDTH] {
core::array::from_fn(|i| s[i].as_u64())
}
pub fn permute_many_remember(states: &mut Goldilocks; WIDTH) {
let inputs: Vec<[u64; WIDTH]> = states.iter().map(key).collect();
permute_many_local(states);
let mut g = MEMO.lock().expect("memo");
let m = g.get_or_insert_with(Default::default);
if m.len() + states.len() > MEMO_CAP {
m.clear();
}
for (k, s) in inputs.into_iter().zip(states.iter()) {
m.insert(k, *s);
}
}
pub fn permute_many_recall(states: &mut Goldilocks; WIDTH) {
let mut miss = Vec::new();
{
let g = MEMO.lock().expect("memo");
match g.as_ref() {
Some(m) if !m.is_empty() => {
for (i, s) in states.iter_mut().enumerate() {
match m.get(&key(s)) {
Some(o) => *s = *o,
None => miss.push(i),
}
}
}
_ => miss.extend(0..states.len()),
}
}
if miss.len() == states.len() {
permute_many_local(states);
return;
}
let mut batch: Vec<[Goldilocks; WIDTH]> = miss.iter().map(|&i| states[i]).collect();
permute_many_local(&mut batch);
for (&i, s) in miss.iter().zip(batch) {
states[i] = s;
}
}
pub fn node_input(l: [Goldilocks; 4], r: [Goldilocks; 4]) -> [Goldilocks; WIDTH] {
let mut s = [Goldilocks::ZERO; WIDTH];
s[..4].copy_from_slice(&l);
s[4..8].copy_from_slice(&r);
s[8] = Goldilocks::new(NODE_TAG);
s
}
pub fn node4_input(c: [[Goldilocks; 4]; 4]) -> [Goldilocks; WIDTH] {
let mut s = [Goldilocks::ZERO; WIDTH];
for (i, d) in c.iter().enumerate() {
s[4 * i..4 * i + 4].copy_from_slice(d);
}
s
}
pub fn children4(
cur: [Goldilocks; 4],
pos: usize,
sibs: [[Goldilocks; 4]; 3],
) -> [[Goldilocks; 4]; 4] {
let mut out = [[Goldilocks::ZERO; 4]; 4];
let mut it = sibs.iter();
for (i, o) in out.iter_mut().enumerate() {
*o = if i == pos {
cur
} else {
*it.next().expect("three siblings")
};
}
out
}
pub fn node_state(l: [Goldilocks; 4], r: [Goldilocks; 4]) -> [Goldilocks; WIDTH] {
let mut s = node_input(l, r);
permute(&mut s);
s
}
pub fn head(s: &[Goldilocks; WIDTH]) -> [Goldilocks; 4] {
[s[0], s[1], s[2], s[3]]
}
#[cfg(test)]
mod tests {
use super::*;
struct Watch(std::thread::ThreadId, core::sync::atomic::AtomicUsize);
impl lens::rspcs::backend::Backend for Watch {
fn name(&self) -> &'static str {
"cpu-watched"
}
fn permute(&self, s: &mut [lens::rspcs::backend::State]) {
if std::thread::current().id() == self.0 {
self.1.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
lens::rspcs::backend::Cpu.permute(s)
}
}
#[test]
fn verifier_permutations_never_reach_the_backend() {
use lens::rspcs::backend::Backend;
let many = |k: u64| -> Vec<[Goldilocks; WIDTH]> {
(0..BACKEND_MIN as u64 * 2)
.map(|j| core::array::from_fn(|i| Goldilocks::new(j * 31 + i as u64 + k)))
.collect()
};
let mut want = many(1);
permute_many_local(&mut want);
let watch = std::sync::Arc::new(Watch(std::thread::current().id(), Default::default()));
let prev = lens::rspcs::backend::install(watch.clone());
let mut a = many(1);
permute_many_remember(&mut a);
let mut b = many(1);
permute_many_recall(&mut b);
let mut c = many(2);
permute_many_recall(&mut c);
let mut d = many(3);
permute_many(&mut d);
lens::rspcs::backend::install(prev);
assert_eq!(a, want);
assert_eq!(b, want);
assert_eq!(
watch.1.load(core::sync::atomic::Ordering::Relaxed),
1,
"only the prover's batch"
);
let _ = watch.name();
}
#[test]
fn batch_agrees_with_single() {
let mut many: Vec<[Goldilocks; WIDTH]> = (0..37u64)
.map(|k| core::array::from_fn(|i| Goldilocks::new(k * 131 + i as u64)))
.collect();
let want: Vec<_> = many
.iter()
.map(|s| {
let mut t = *s;
permute(&mut t);
t
})
.collect();
permute_many(&mut many);
assert_eq!(many, want);
let t = crate::machine::hemera::Tables::default();
let x: [Goldilocks; WIDTH] = core::array::from_fn(|i| Goldilocks::new(i as u64 * 977 + 3));
let mut y = x;
permute(&mut y);
assert_eq!(t.permute(&x), y);
}
}