diff --git a/Cargo.toml b/Cargo.toml index 94b549ddee..70786d95b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ serde = { workspace = true, features = ["derive"] } serde_bytes = "0.11.12" serde_json = { workspace = true } serde_repr = "0.1.14" +strum = { version = "0.25", features = ["derive"] } tap = "1.0.1" stable_deref_trait = "1.2.0" thiserror = { workspace = true } diff --git a/src/cli/repl/meta_cmd.rs b/src/cli/repl/meta_cmd.rs index e38fa3b2d0..5e39c132db 100644 --- a/src/cli/repl/meta_cmd.rs +++ b/src/cli/repl/meta_cmd.rs @@ -19,8 +19,9 @@ use crate::{ lem::{ eval::evaluate_with_env_and_cont, multiframe::MultiFrame, - pointers::{Ptr, ZPtr}, - Tag, + pointers::{Ptr, RawPtr, ZPtr}, + store::expect_ptrs, + tag::Tag, }, package::{Package, SymbolRef}, proof::{ @@ -262,13 +263,13 @@ where let (second_io, ..) = repl .eval_expr(second) .with_context(|| "evaluating second arg")?; - let Ptr::Atom(Tag::Expr(ExprTag::Num), secret) = first_io[0] else { + let (Tag::Expr(ExprTag::Num), RawPtr::Atom(secret)) = first_io[0].parts() else { bail!( "Secret must be a number. Got {}", first_io[0].fmt_to_string(&repl.store, &repl.state.borrow()) ) }; - let secret = *repl.store.expect_f(secret); + let secret = *repl.store.expect_f(*secret); repl.hide(secret, second_io[0])?; Ok(()) }, @@ -572,10 +573,10 @@ where .get_result() .expect("evaluation result must have been set"); let (_, comm) = repl.store.car_cdr(result)?; - let Ptr::Atom(Tag::Expr(ExprTag::Comm), hash) = comm else { + let (Tag::Expr(ExprTag::Comm), RawPtr::Atom(hash)) = comm.parts() else { bail!("Second component of a chain must be a commitment") }; - let hash = *repl.store.expect_f(hash); + let hash = *repl.store.expect_f(*hash); // retrieve from store to persist let (secret, fun) = repl .store @@ -778,10 +779,11 @@ where let (fun, rest) = repl.store.car_cdr(ptcl)?; - let (Ptr::Atom(Tag::Expr(ExprTag::Num), rc_idx), _) = repl.store.car_cdr(&rest)? else { + let (car, _) = repl.store.car_cdr(&rest)?; + let (Tag::Expr(ExprTag::Num), RawPtr::Atom(rc_idx)) = car.parts() else { bail!("Reduction count must be a Num") }; - let Some(rc) = repl.store.expect_f(rc_idx).to_u64().map(|u| u as usize) else { + let Some(rc) = repl.store.expect_f(*rc_idx).to_u64().map(|u| u as usize) else { bail!("Invalid value for reduction count") }; Ok((fun, rc)) @@ -824,13 +826,13 @@ where .eval_expr_with_env(apply_call, repl.store.intern_nil()) .with_context(|| "evaluating protocol function call")?; - let Ptr::Tuple2(Tag::Expr(ExprTag::Cons), idx) = &io[0] else { + let (Tag::Expr(ExprTag::Cons), RawPtr::Hash4(idx)) = &io[0].parts() else { bail!( "Protocol function must return a pair. Got {}", io[0].fmt_to_string(&repl.store, &repl.state.borrow()) ) }; - let (pre_verify, post_verify) = repl.store.fetch_2_ptrs(*idx).unwrap(); + let [pre_verify, post_verify] = &expect_ptrs!(repl.store, 2, *idx); if pre_verify.is_nil() { bail!("Pre-verification predicate rejected the input") diff --git a/src/cli/repl/mod.rs b/src/cli/repl/mod.rs index 34840012bd..3681ae1e96 100644 --- a/src/cli/repl/mod.rs +++ b/src/cli/repl/mod.rs @@ -30,9 +30,9 @@ use crate::{ eval::{evaluate_simple_with_env, evaluate_with_env}, interpreter::Frame, multiframe::MultiFrame, - pointers::Ptr, + pointers::{Ptr, RawPtr}, store::Store, - Tag, + tag::Tag, }, parser, proof::{ @@ -457,7 +457,7 @@ where let (expr_io, ..) = self .eval_expr(expr) .with_context(|| "evaluating first arg")?; - let Ptr::Atom(Tag::Expr(ExprTag::Num), hash_idx) = &expr_io[0] else { + let (Tag::Expr(ExprTag::Num), RawPtr::Atom(hash_idx)) = &expr_io[0].parts() else { bail!("hash must be a number") }; Ok(self.store.expect_f(*hash_idx)) diff --git a/src/cli/zstore.rs b/src/cli/zstore.rs index c295a837d7..96a0ebad8d 100644 --- a/src/cli/zstore.rs +++ b/src/cli/zstore.rs @@ -5,8 +5,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use crate::{ field::{FWrap, LurkField}, lem::{ - pointers::{Ptr, ZPtr}, - store::Store, + pointers::{Ptr, RawPtr, ZPtr}, + store::{expect_ptrs, intern_ptrs_hydrated, Store}, }, }; @@ -38,17 +38,18 @@ impl ZDag { if let Some(z_ptr) = cache.get(ptr) { *z_ptr } else { - let z_ptr = match ptr { - Ptr::Atom(tag, idx) => { + let tag = ptr.tag(); + let z_ptr = match ptr.raw() { + RawPtr::Atom(idx) => { let f = store.expect_f(*idx); let z_ptr = ZPtr::from_parts(*tag, *f); self.0.insert(z_ptr, ZPtrType::Atom); z_ptr } - Ptr::Tuple2(tag, idx) => { - let (a, b) = store.expect_2_ptrs(*idx); - let a = self.populate_with(a, store, cache); - let b = self.populate_with(b, store, cache); + RawPtr::Hash4(idx) => { + let [a, b] = expect_ptrs!(store, 2, *idx); + let a = self.populate_with(&a, store, cache); + let b = self.populate_with(&b, store, cache); let z_ptr = ZPtr::from_parts( *tag, store.poseidon_cache.hash4(&[ @@ -61,11 +62,11 @@ impl ZDag { self.0.insert(z_ptr, ZPtrType::Tuple2(a, b)); z_ptr } - Ptr::Tuple3(tag, idx) => { - let (a, b, c) = store.expect_3_ptrs(*idx); - let a = self.populate_with(a, store, cache); - let b = self.populate_with(b, store, cache); - let c = self.populate_with(c, store, cache); + RawPtr::Hash6(idx) => { + let [a, b, c] = expect_ptrs!(store, 3, *idx); + let a = self.populate_with(&a, store, cache); + let b = self.populate_with(&b, store, cache); + let c = self.populate_with(&c, store, cache); let z_ptr = ZPtr::from_parts( *tag, store.poseidon_cache.hash6(&[ @@ -80,12 +81,12 @@ impl ZDag { self.0.insert(z_ptr, ZPtrType::Tuple3(a, b, c)); z_ptr } - Ptr::Tuple4(tag, idx) => { - let (a, b, c, d) = store.expect_4_ptrs(*idx); - let a = self.populate_with(a, store, cache); - let b = self.populate_with(b, store, cache); - let c = self.populate_with(c, store, cache); - let d = self.populate_with(d, store, cache); + RawPtr::Hash8(idx) => { + let [a, b, c, d] = expect_ptrs!(store, 4, *idx); + let a = self.populate_with(&a, store, cache); + let b = self.populate_with(&b, store, cache); + let c = self.populate_with(&c, store, cache); + let d = self.populate_with(&d, store, cache); let z_ptr = ZPtr::from_parts( *tag, store.poseidon_cache.hash8(&[ @@ -126,26 +127,24 @@ impl ZDag { } else { let ptr = match self.get_type(z_ptr) { None => bail!("Couldn't find ZPtr on ZStore"), - Some(ZPtrType::Atom) => { - store.intern_atom_hydrated(*z_ptr.tag(), *z_ptr.value(), *z_ptr) - } + Some(ZPtrType::Atom) => store.intern_atom(*z_ptr.tag(), *z_ptr.value()), Some(ZPtrType::Tuple2(z1, z2)) => { let ptr1 = self.populate_store(z1, store, cache)?; let ptr2 = self.populate_store(z2, store, cache)?; - store.intern_2_ptrs_hydrated(*z_ptr.tag(), ptr1, ptr2, *z_ptr) + intern_ptrs_hydrated!(store, *z_ptr.tag(), *z_ptr, ptr1, ptr2) } Some(ZPtrType::Tuple3(z1, z2, z3)) => { let ptr1 = self.populate_store(z1, store, cache)?; let ptr2 = self.populate_store(z2, store, cache)?; let ptr3 = self.populate_store(z3, store, cache)?; - store.intern_3_ptrs_hydrated(*z_ptr.tag(), ptr1, ptr2, ptr3, *z_ptr) + intern_ptrs_hydrated!(store, *z_ptr.tag(), *z_ptr, ptr1, ptr2, ptr3) } Some(ZPtrType::Tuple4(z1, z2, z3, z4)) => { let ptr1 = self.populate_store(z1, store, cache)?; let ptr2 = self.populate_store(z2, store, cache)?; let ptr3 = self.populate_store(z3, store, cache)?; let ptr4 = self.populate_store(z4, store, cache)?; - store.intern_4_ptrs_hydrated(*z_ptr.tag(), ptr1, ptr2, ptr3, ptr4, *z_ptr) + intern_ptrs_hydrated!(store, *z_ptr.tag(), *z_ptr, ptr1, ptr2, ptr3, ptr4) } }; cache.insert(*z_ptr, ptr); @@ -276,7 +275,11 @@ mod tests { use crate::{ field::LurkField, - lem::{pointers::Ptr, store::Store, Tag}, + lem::{ + pointers::Ptr, + store::{intern_ptrs, Store}, + tag::Tag, + }, tag::{ContTag, ExprTag, Op1, Op2}, }; @@ -297,23 +300,26 @@ mod tests { } else { match rnd % 4 { 0 => store.intern_atom(tag, Fp::from_u64(rnd)), - 1 => store.intern_2_ptrs( + 1 => intern_ptrs!( + store, tag, rng_interner(rng, max_depth - 1, store), - rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store) ), - 2 => store.intern_3_ptrs( + 2 => intern_ptrs!( + store, tag, rng_interner(rng, max_depth - 1, store), rng_interner(rng, max_depth - 1, store), - rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store) ), - 3 => store.intern_4_ptrs( + 3 => intern_ptrs!( + store, tag, rng_interner(rng, max_depth - 1, store), rng_interner(rng, max_depth - 1, store), rng_interner(rng, max_depth - 1, store), - rng_interner(rng, max_depth - 1, store), + rng_interner(rng, max_depth - 1, store) ), _ => unreachable!(), } diff --git a/src/coprocessor/gadgets.rs b/src/coprocessor/gadgets.rs index 473c5fa38a..e983da74ce 100644 --- a/src/coprocessor/gadgets.rs +++ b/src/coprocessor/gadgets.rs @@ -12,7 +12,7 @@ use crate::{ lem::{ circuit::GlobalAllocator, pointers::{Ptr, ZPtr}, - store::Store, + store::{expect_ptrs, Store}, }, tag::{ExprTag, Tag}, }; @@ -178,7 +178,7 @@ pub(crate) fn deconstruct_tuple2>( ) -> Result<(AllocatedPtr, AllocatedPtr), SynthesisError> { let (a, b) = if not_dummy.get_value() == Some(true) { let idx = get_ptr(tuple, store)?.get_index2().expect("invalid Ptr"); - let (a, b) = store.expect_2_ptrs(idx); + let [a, b] = &expect_ptrs!(store, 2, idx); (store.hash_ptr(a), store.hash_ptr(b)) } else { (ZPtr::dummy(), ZPtr::dummy()) @@ -221,7 +221,7 @@ pub(crate) fn deconstruct_tuple3>( ) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedPtr), SynthesisError> { let (a, b, c) = if not_dummy.get_value() == Some(true) { let idx = get_ptr(tuple, store)?.get_index3().expect("invalid Ptr"); - let (a, b, c) = store.expect_3_ptrs(idx); + let [a, b, c] = &expect_ptrs!(store, 3, idx); (store.hash_ptr(a), store.hash_ptr(b), store.hash_ptr(c)) } else { (ZPtr::dummy(), ZPtr::dummy(), ZPtr::dummy()) @@ -275,7 +275,7 @@ pub(crate) fn deconstruct_tuple4>( > { let (a, b, c, d) = if not_dummy.get_value() == Some(true) { let idx = get_ptr(tuple, store)?.get_index4().expect("invalid Ptr"); - let (a, b, c, d) = store.expect_4_ptrs(idx); + let [a, b, c, d] = &expect_ptrs!(store, 4, idx); ( store.hash_ptr(a), store.hash_ptr(b), @@ -538,7 +538,10 @@ mod test { deconstruct_tuple4, }, field::LurkField, - lem::{circuit::GlobalAllocator, store::Store}, + lem::{ + circuit::GlobalAllocator, + store::{intern_ptrs, Store}, + }, }; use super::{a_ptr_as_z_ptr, chain_car_cdr, construct_list, deconstruct_tuple2}; @@ -561,7 +564,7 @@ mod test { &a_nil, ) .unwrap(); - let nil2_ptr = store.intern_2_ptrs(*nil_tag, nil, nil); + let nil2_ptr = intern_ptrs!(store, *nil_tag, nil, nil); let z_nil2_ptr = store.hash_ptr(&nil2_ptr); assert_eq!(a_ptr_as_z_ptr(&nil2), Some(z_nil2_ptr)); @@ -575,7 +578,7 @@ mod test { &a_nil, ) .unwrap(); - let nil3_ptr = store.intern_3_ptrs(*nil_tag, nil, nil, nil); + let nil3_ptr = intern_ptrs!(store, *nil_tag, nil, nil, nil); let z_nil3_ptr = store.hash_ptr(&nil3_ptr); assert_eq!(a_ptr_as_z_ptr(&nil3), Some(z_nil3_ptr)); @@ -590,7 +593,7 @@ mod test { &a_nil, ) .unwrap(); - let nil4_ptr = store.intern_4_ptrs(*nil_tag, nil, nil, nil, nil); + let nil4_ptr = intern_ptrs!(store, *nil_tag, nil, nil, nil, nil); let z_nil4_ptr = store.hash_ptr(&nil4_ptr); assert_eq!(a_ptr_as_z_ptr(&nil4), Some(z_nil4_ptr)); } @@ -624,7 +627,7 @@ mod test { let nil_tag = *nil.tag(); let not_dummy = Boolean::Constant(true); - let tuple2 = store.intern_2_ptrs(nil_tag, nil, nil); + let tuple2 = intern_ptrs!(store, nil_tag, nil, nil); let z_tuple2 = store.hash_ptr(&tuple2); let a_tuple2 = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "tuple2"), || z_tuple2); let (a, b) = deconstruct_tuple2( @@ -637,7 +640,7 @@ mod test { assert_eq!(a_ptr_as_z_ptr(&a), Some(z_nil)); assert_eq!(a_ptr_as_z_ptr(&b), Some(z_nil)); - let tuple3 = store.intern_3_ptrs(nil_tag, nil, nil, nil); + let tuple3 = intern_ptrs!(store, nil_tag, nil, nil, nil); let z_tuple3 = store.hash_ptr(&tuple3); let a_tuple3 = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "tuple3"), || z_tuple3); let (a, b, c) = deconstruct_tuple3( @@ -651,7 +654,7 @@ mod test { assert_eq!(a_ptr_as_z_ptr(&b), Some(z_nil)); assert_eq!(a_ptr_as_z_ptr(&c), Some(z_nil)); - let tuple4 = store.intern_4_ptrs(nil_tag, nil, nil, nil, nil); + let tuple4 = intern_ptrs!(store, nil_tag, nil, nil, nil, nil); let z_tuple4 = store.hash_ptr(&tuple4); let a_tuple4 = AllocatedPtr::alloc_infallible(&mut cs.namespace(|| "tuple4"), || z_tuple4); let (a, b, c, d) = deconstruct_tuple4( diff --git a/src/coprocessor/mod.rs b/src/coprocessor/mod.rs index 8c711370db..a3be4afd8f 100644 --- a/src/coprocessor/mod.rs +++ b/src/coprocessor/mod.rs @@ -113,7 +113,7 @@ pub(crate) mod test { use super::*; use crate::circuit::gadgets::constraints::{alloc_equal, mul}; - use crate::lem::Tag as LEMTag; + use crate::lem::{pointers::RawPtr, tag::Tag as LEMTag}; use crate::tag::{ExprTag, Tag}; use std::marker::PhantomData; @@ -209,11 +209,11 @@ pub(crate) mod test { } fn evaluate(&self, s: &Store, args: &[Ptr], env: &Ptr, cont: &Ptr) -> Vec { - let Ptr::Atom(LEMTag::Expr(ExprTag::Num), a) = &args[0] else { + let (LEMTag::Expr(ExprTag::Num), RawPtr::Atom(a)) = args[0].parts() else { return vec![args[0], *env, s.cont_error()]; }; let a = s.expect_f(*a); - let Ptr::Atom(LEMTag::Expr(ExprTag::Num), b) = &args[1] else { + let (LEMTag::Expr(ExprTag::Num), RawPtr::Atom(b)) = args[1].parts() else { return vec![args[1], *env, s.cont_error()]; }; let b = s.expect_f(*b); diff --git a/src/lem/eval.rs b/src/lem/eval.rs index ce64d6cd89..e15afee04a 100644 --- a/src/lem/eval.rs +++ b/src/lem/eval.rs @@ -21,8 +21,8 @@ use crate::{ use super::{ interpreter::{Frame, Hints}, - pointers::Ptr, - store::Store, + pointers::{Ptr, RawPtr}, + store::{fetch_ptrs, Store}, Ctrl, Func, Op, Tag, Var, }; @@ -40,11 +40,10 @@ fn get_pc>( store: &Store, lang: &Lang, ) -> usize { - match expr { - Ptr::Tuple2(Tag::Expr(Cproc), idx) => { - let (cproc, _) = store - .fetch_2_ptrs(*idx) - .expect("Coprocessor expression is not interned"); + match expr.parts() { + (Tag::Expr(Cproc), RawPtr::Hash4(idx)) => { + let [cproc, _] = + &fetch_ptrs!(store, 2, *idx).expect("Coprocessor expression is not interned"); let cproc_sym = store .fetch_symbol(cproc) .expect("Coprocessor expression is not interned"); diff --git a/src/lem/interpreter.rs b/src/lem/interpreter.rs index 23d2e86317..a656a86122 100644 --- a/src/lem/interpreter.rs +++ b/src/lem/interpreter.rs @@ -2,11 +2,12 @@ use anyhow::{anyhow, bail, Result}; use super::{ path::Path, - pointers::Ptr, + pointers::{Ptr, RawPtr}, slot::{SlotData, Val}, - store::Store, + store::{fetch_ptrs, intern_ptrs, Store}, + tag::Tag, var_map::VarMap, - Block, Ctrl, Func, Op, Tag, Var, + Block, Ctrl, Func, Op, Var, }; use crate::{ @@ -181,16 +182,16 @@ impl Block { bindings.insert_ptr(tgt.clone(), store.zero(*tag)); } Op::Hash3Zeros(tgt, tag) => { - bindings.insert_ptr(tgt.clone(), Ptr::Atom(*tag, store.hash3zeros_idx)); + bindings.insert_ptr(tgt.clone(), Ptr::atom(*tag, store.hash3zeros_idx)); } Op::Hash4Zeros(tgt, tag) => { - bindings.insert_ptr(tgt.clone(), Ptr::Atom(*tag, store.hash4zeros_idx)); + bindings.insert_ptr(tgt.clone(), Ptr::atom(*tag, store.hash4zeros_idx)); } Op::Hash6Zeros(tgt, tag) => { - bindings.insert_ptr(tgt.clone(), Ptr::Atom(*tag, store.hash6zeros_idx)); + bindings.insert_ptr(tgt.clone(), Ptr::atom(*tag, store.hash6zeros_idx)); } Op::Hash8Zeros(tgt, tag) => { - bindings.insert_ptr(tgt.clone(), Ptr::Atom(*tag, store.hash8zeros_idx)); + bindings.insert_ptr(tgt.clone(), Ptr::atom(*tag, store.hash8zeros_idx)); } Op::Lit(tgt, lit) => { bindings.insert_ptr(tgt.clone(), lit.to_ptr(store)); @@ -229,9 +230,9 @@ impl Block { bindings.insert_bool(tgt.clone(), a || b); } Op::Add(tgt, a, b) => { - let a = bindings.get_ptr(a)?; - let b = bindings.get_ptr(b)?; - let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + let a = *bindings.get_ptr(a)?.raw(); + let b = *bindings.get_ptr(b)?.raw(); + let c = if let (RawPtr::Atom(f), RawPtr::Atom(g)) = (a, b) { let (f, g) = (store.expect_f(f), store.expect_f(g)); store.intern_atom(Tag::Expr(Num), *f + *g) } else { @@ -240,9 +241,9 @@ impl Block { bindings.insert_ptr(tgt.clone(), c); } Op::Sub(tgt, a, b) => { - let a = bindings.get_ptr(a)?; - let b = bindings.get_ptr(b)?; - let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + let a = *bindings.get_ptr(a)?.raw(); + let b = *bindings.get_ptr(b)?.raw(); + let c = if let (RawPtr::Atom(f), RawPtr::Atom(g)) = (a, b) { let (f, g) = (store.expect_f(f), store.expect_f(g)); store.intern_atom(Tag::Expr(Num), *f - *g) } else { @@ -251,9 +252,9 @@ impl Block { bindings.insert_ptr(tgt.clone(), c); } Op::Mul(tgt, a, b) => { - let a = bindings.get_ptr(a)?; - let b = bindings.get_ptr(b)?; - let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + let a = *bindings.get_ptr(a)?.raw(); + let b = *bindings.get_ptr(b)?.raw(); + let c = if let (RawPtr::Atom(f), RawPtr::Atom(g)) = (a, b) { let (f, g) = (store.expect_f(f), store.expect_f(g)); store.intern_atom(Tag::Expr(Num), *f * *g) } else { @@ -262,9 +263,9 @@ impl Block { bindings.insert_ptr(tgt.clone(), c); } Op::Div(tgt, a, b) => { - let a = bindings.get_ptr(a)?; - let b = bindings.get_ptr(b)?; - let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + let a = *bindings.get_ptr(a)?.raw(); + let b = *bindings.get_ptr(b)?.raw(); + let c = if let (RawPtr::Atom(f), RawPtr::Atom(g)) = (a, b) { let (f, g) = (store.expect_f(f), store.expect_f(g)); if g == &F::ZERO { bail!("Can't divide by zero") @@ -276,9 +277,9 @@ impl Block { bindings.insert_ptr(tgt.clone(), c); } Op::Lt(tgt, a, b) => { - let a = bindings.get_ptr(a)?; - let b = bindings.get_ptr(b)?; - let c = if let (Ptr::Atom(_, f_idx), Ptr::Atom(_, g_idx)) = (a, b) { + let a = *bindings.get_ptr(a)?.raw(); + let b = *bindings.get_ptr(b)?.raw(); + let c = if let (RawPtr::Atom(f_idx), RawPtr::Atom(g_idx)) = (a, b) { let f = *store.expect_f(f_idx); let g = *store.expect_f(g_idx); let diff = f - g; @@ -301,8 +302,8 @@ impl Block { } Op::Trunc(tgt, a, n) => { assert!(*n <= 64); - let a = bindings.get_ptr(a)?; - let c = if let Ptr::Atom(_, f_idx) = a { + let a = *bindings.get_ptr(a)?.raw(); + let c = if let RawPtr::Atom(f_idx) = a { let f = *store.expect_f(f_idx); hints.bit_decomp.push(Some(SlotData { vals: vec![Val::Num(f_idx)], @@ -315,9 +316,9 @@ impl Block { bindings.insert_ptr(tgt.clone(), c); } Op::DivRem64(tgt, a, b) => { - let a = bindings.get_ptr(a)?; - let b = bindings.get_ptr(b)?; - let (c1, c2) = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + let a = *bindings.get_ptr(a)?.raw(); + let b = *bindings.get_ptr(b)?.raw(); + let (c1, c2) = if let (RawPtr::Atom(f), RawPtr::Atom(g)) = (a, b) { let f = *store.expect_f(f); let g = *store.expect_f(g); if g == F::ZERO { @@ -341,7 +342,7 @@ impl Block { } Op::Cons2(img, tag, preimg) => { let preimg_ptrs = bindings.get_many_ptr(preimg)?; - let tgt_ptr = store.intern_2_ptrs(*tag, preimg_ptrs[0], preimg_ptrs[1]); + let tgt_ptr = intern_ptrs!(store, *tag, preimg_ptrs[0], preimg_ptrs[1]); bindings.insert_ptr(img.clone(), tgt_ptr); let vals = preimg_ptrs.into_iter().map(Val::Pointer).collect(); hints.hash4.push(Some(SlotData { vals })); @@ -349,19 +350,20 @@ impl Block { Op::Cons3(img, tag, preimg) => { let preimg_ptrs = bindings.get_many_ptr(preimg)?; let tgt_ptr = - store.intern_3_ptrs(*tag, preimg_ptrs[0], preimg_ptrs[1], preimg_ptrs[2]); + intern_ptrs!(store, *tag, preimg_ptrs[0], preimg_ptrs[1], preimg_ptrs[2]); bindings.insert_ptr(img.clone(), tgt_ptr); let vals = preimg_ptrs.into_iter().map(Val::Pointer).collect(); hints.hash6.push(Some(SlotData { vals })); } Op::Cons4(img, tag, preimg) => { let preimg_ptrs = bindings.get_many_ptr(preimg)?; - let tgt_ptr = store.intern_4_ptrs( + let tgt_ptr = intern_ptrs!( + store, *tag, preimg_ptrs[0], preimg_ptrs[1], preimg_ptrs[2], - preimg_ptrs[3], + preimg_ptrs[3] ); bindings.insert_ptr(img.clone(), tgt_ptr); let vals = preimg_ptrs.into_iter().map(Val::Pointer).collect(); @@ -372,10 +374,9 @@ impl Block { let Some(idx) = img_ptr.get_index2() else { bail!("{img} isn't a Tree2 pointer"); }; - let Some((a, b)) = store.fetch_2_ptrs(idx) else { + let Some(preimg_ptrs) = fetch_ptrs!(store, 2, idx) else { bail!("Couldn't fetch {img}'s children") }; - let preimg_ptrs = [*a, *b]; for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { bindings.insert_ptr(var.clone(), *ptr); } @@ -387,10 +388,9 @@ impl Block { let Some(idx) = img_ptr.get_index3() else { bail!("{img} isn't a Tree3 pointer"); }; - let Some((a, b, c)) = store.fetch_3_ptrs(idx) else { + let Some(preimg_ptrs) = fetch_ptrs!(store, 3, idx) else { bail!("Couldn't fetch {img}'s children") }; - let preimg_ptrs = [*a, *b, *c]; for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { bindings.insert_ptr(var.clone(), *ptr); } @@ -402,10 +402,9 @@ impl Block { let Some(idx) = img_ptr.get_index4() else { bail!("{img} isn't a Tree4 pointer"); }; - let Some((a, b, c, d)) = store.fetch_4_ptrs(idx) else { + let Some(preimg_ptrs) = fetch_ptrs!(store, 4, idx) else { bail!("Couldn't fetch {img}'s children") }; - let preimg_ptrs = [*a, *b, *c, *d]; for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { bindings.insert_ptr(var.clone(), *ptr); } @@ -414,30 +413,32 @@ impl Block { } Op::Hide(tgt, sec, src) => { let src_ptr = bindings.get_ptr(src)?; - let Ptr::Atom(Tag::Expr(Num), secret_idx) = bindings.get_ptr(sec)? else { + let sec_ptr = bindings.get_ptr(sec)?; + let (Tag::Expr(Num), RawPtr::Atom(secret_idx)) = sec_ptr.parts() else { bail!("{sec} is not a numeric pointer") }; - let secret = *store.expect_f(secret_idx); + let secret = *store.expect_f(*secret_idx); let tgt_ptr = store.hide(secret, src_ptr); - let vals = vec![Val::Num(secret_idx), Val::Pointer(src_ptr)]; + let vals = vec![Val::Num(*secret_idx), Val::Pointer(src_ptr)]; hints.commitment.push(Some(SlotData { vals })); bindings.insert_ptr(tgt.clone(), tgt_ptr); } Op::Open(tgt_secret, tgt_ptr, comm) => { - let Ptr::Atom(Tag::Expr(Comm), hash) = bindings.get_ptr(comm)? else { + let comm_ptr = bindings.get_ptr(comm)?; + let (Tag::Expr(Comm), RawPtr::Atom(hash)) = comm_ptr.parts() else { bail!("{comm} is not a comm pointer") }; - let hash = *store.expect_f(hash); - let Some((secret, ptr)) = store.open(hash).cloned() else { + let hash = *store.expect_f(*hash); + let Some((secret, ptr)) = store.open(hash) else { bail!("No committed data for hash {}", &hash.hex_digits()) }; - bindings.insert_ptr(tgt_ptr.clone(), ptr); + bindings.insert_ptr(tgt_ptr.clone(), *ptr); bindings.insert_ptr( tgt_secret.clone(), - store.intern_atom(Tag::Expr(Num), secret), + store.intern_atom(Tag::Expr(Num), *secret), ); - let secret_idx = store.intern_f(secret).0; - let vals = vec![Val::Num(secret_idx), Val::Pointer(ptr)]; + let secret_idx = store.intern_f(*secret).0; + let vals = vec![Val::Num(secret_idx), Val::Pointer(*ptr)]; hints.commitment.push(Some(SlotData { vals })); } Op::Unit(f) => f(), diff --git a/src/lem/macros.rs b/src/lem/macros.rs index 7e16a6a720..bac1d1e748 100644 --- a/src/lem/macros.rs +++ b/src/lem/macros.rs @@ -30,16 +30,16 @@ macro_rules! lit { #[macro_export] macro_rules! tag { ( Expr::$tag:ident ) => { - $crate::lem::Tag::Expr($crate::tag::ExprTag::$tag) + $crate::lem::tag::Tag::Expr($crate::tag::ExprTag::$tag) }; ( Cont::$tag:ident ) => { - $crate::lem::Tag::Cont($crate::tag::ContTag::$tag) + $crate::lem::tag::Tag::Cont($crate::tag::ContTag::$tag) }; ( Op1::$tag:ident ) => { - $crate::lem::Tag::Op1($crate::tag::Op1::$tag) + $crate::lem::tag::Tag::Op1($crate::tag::Op1::$tag) }; ( Op2::$tag:ident ) => { - $crate::lem::Tag::Op2($crate::tag::Op2::$tag) + $crate::lem::tag::Tag::Op2($crate::tag::Op2::$tag) }; } diff --git a/src/lem/mod.rs b/src/lem/mod.rs index 2634353ff5..f6986c4bb1 100644 --- a/src/lem/mod.rs +++ b/src/lem/mod.rs @@ -68,23 +68,19 @@ mod path; pub mod pointers; mod slot; pub mod store; +pub mod tag; mod var_map; use anyhow::{bail, Result}; use indexmap::IndexMap; -use serde::{Deserialize, Serialize}; use std::sync::Arc; #[cfg(test)] mod tests; -use crate::{ - field::LurkField, - symbol::Symbol, - tag::{ContTag, ExprTag, Op1, Op2, Tag as TagTrait}, -}; +use crate::{field::LurkField, symbol::Symbol}; -use self::{pointers::Ptr, slot::SlotsCounter, store::Store, var_map::VarMap}; +use self::{pointers::Ptr, slot::SlotsCounter, store::Store, tag::Tag, var_map::VarMap}; pub type AString = Arc; @@ -103,74 +99,6 @@ pub struct Func { #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub struct Var(AString); -/// The LEM `Tag` is a wrapper around other types that are used as tags -#[derive(Copy, Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize)] -pub enum Tag { - Expr(ExprTag), - Cont(ContTag), - Op1(Op1), - Op2(Op2), -} - -impl TryFrom for Tag { - type Error = anyhow::Error; - - fn try_from(val: u16) -> Result { - if let Ok(tag) = ExprTag::try_from(val) { - Ok(Tag::Expr(tag)) - } else if let Ok(tag) = ContTag::try_from(val) { - Ok(Tag::Cont(tag)) - } else { - bail!("Invalid u16 for Tag: {val}") - } - } -} - -impl From for u16 { - fn from(val: Tag) -> Self { - match val { - Tag::Expr(tag) => tag.into(), - Tag::Cont(tag) => tag.into(), - Tag::Op1(tag) => tag.into(), - Tag::Op2(tag) => tag.into(), - } - } -} - -impl TagTrait for Tag { - fn from_field(f: &F) -> Option { - Self::try_from(f.to_u16()?).ok() - } - - fn to_field(&self) -> F { - Tag::to_field(self) - } -} - -impl Tag { - #[inline] - pub fn to_field(&self) -> F { - match self { - Tag::Expr(tag) => tag.to_field(), - Tag::Cont(tag) => tag.to_field(), - Tag::Op1(tag) => tag.to_field(), - Tag::Op2(tag) => tag.to_field(), - } - } -} - -impl std::fmt::Display for Tag { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use Tag::{Cont, Expr, Op1, Op2}; - match self { - Expr(tag) => write!(f, "expr.{}", tag), - Cont(tag) => write!(f, "cont.{}", tag), - Op1(tag) => write!(f, "op1.{}", tag), - Op2(tag) => write!(f, "op2.{}", tag), - } - } -} - /// LEM literals #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum Lit { diff --git a/src/lem/pointers.rs b/src/lem/pointers.rs index 241700bc3b..d21432441c 100644 --- a/src/lem/pointers.rs +++ b/src/lem/pointers.rs @@ -7,33 +7,89 @@ use crate::{ use super::Tag; -/// `Ptr` is the main piece of data LEMs operate on. We can think of a pointer -/// as a building block for trees that represent Lurk data. A pointer can be a -/// atom that contains data encoded as an element of a `LurkField` or it can have -/// children. For performance, the children of a pointer are stored on an -/// `IndexSet` and the resulding index is carried by the pointer itself. -/// -/// A pointer also has a tag, which says what kind of data it encodes. On -/// previous implementations, the tag would be used to infer the number of -/// children a pointer has. However, LEMs require extra flexibility because LEM -/// hashing operations can plug any tag to the resulting pointer. Thus, the -/// number of children have to be made explicit as the `Ptr` enum. +/// `RawPtr` is the basic pointer type of the LEM store. An `Atom` points to a field +/// element, and a `HashN` points to `N` children, which are also raw pointers. Thus, +/// they are a building block for graphs that represent Lurk data. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] -pub enum Ptr { - Atom(Tag, usize), - Tuple2(Tag, usize), - Tuple3(Tag, usize), - Tuple4(Tag, usize), +pub enum RawPtr { + Atom(usize), + Hash4(usize), + Hash6(usize), + Hash8(usize), } -impl Ptr { - pub fn tag(&self) -> &Tag { +impl RawPtr { + #[inline] + pub fn is_hash(&self) -> bool { + matches!( + self, + RawPtr::Hash4(..) | RawPtr::Hash6(..) | RawPtr::Hash8(..) + ) + } + + #[inline] + pub fn get_atom(&self) -> Option { + match self { + RawPtr::Atom(x) => Some(*x), + _ => None, + } + } + + #[inline] + pub fn get_hash4(&self) -> Option { + match self { + RawPtr::Hash4(x) => Some(*x), + _ => None, + } + } + + #[inline] + pub fn get_hash6(&self) -> Option { + match self { + RawPtr::Hash6(x) => Some(*x), + _ => None, + } + } + + #[inline] + pub fn get_hash8(&self) -> Option { match self { - Ptr::Atom(tag, _) | Ptr::Tuple2(tag, _) | Ptr::Tuple3(tag, _) | Ptr::Tuple4(tag, _) => { - tag - } + RawPtr::Hash8(x) => Some(*x), + _ => None, } } +} + +/// `Ptr` is a tagged pointer. The tag is there to say what kind of data it encodes. +/// Since tags can be encoded as field elements, they are also able to be expressed +/// as raw pointers. A `Ptr` can thus be seen as a tuple of `RawPtr`s. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct Ptr { + tag: Tag, + raw: RawPtr, +} + +impl Ptr { + #[inline] + pub fn new(tag: Tag, raw: RawPtr) -> Self { + Ptr { tag, raw } + } + + #[inline] + pub fn tag(&self) -> &Tag { + &self.tag + } + + #[inline] + pub fn raw(&self) -> &RawPtr { + &self.raw + } + + #[inline] + pub fn parts(&self) -> (&Tag, &RawPtr) { + let Ptr { tag, raw } = self; + (tag, raw) + } #[inline] pub fn has_tag(&self, tag: &Tag) -> bool { @@ -77,61 +133,50 @@ impl Ptr { #[inline] pub fn cast(self, tag: Tag) -> Self { - match self { - Ptr::Atom(_, x) => Ptr::Atom(tag, x), - Ptr::Tuple2(_, x) => Ptr::Tuple2(tag, x), - Ptr::Tuple3(_, x) => Ptr::Tuple3(tag, x), - Ptr::Tuple4(_, x) => Ptr::Tuple4(tag, x), - } - } - - #[inline] - pub fn is_tuple(&self) -> bool { - matches!(self, Ptr::Tuple2(..) | Ptr::Tuple3(..) | Ptr::Tuple4(..)) + Ptr { tag, raw: self.raw } } #[inline] pub fn get_atom(&self) -> Option { - match self { - Ptr::Atom(_, x) => Some(*x), - _ => None, - } + self.raw().get_atom() } #[inline] pub fn get_index2(&self) -> Option { - match self { - Ptr::Tuple2(_, x) => Some(*x), - _ => None, - } + self.raw().get_hash4() } #[inline] pub fn get_index3(&self) -> Option { - match self { - Ptr::Tuple3(_, x) => Some(*x), - _ => None, - } + self.raw().get_hash6() } #[inline] pub fn get_index4(&self) -> Option { - match self { - Ptr::Tuple4(_, x) => Some(*x), - _ => None, + self.raw().get_hash8() + } + + #[inline] + pub fn atom(tag: Tag, idx: usize) -> Ptr { + Ptr { + tag, + raw: RawPtr::Atom(idx), } } } -/// A `ZPtr` is the result of "hydrating" a `Ptr`. This process is better -/// explained in the store but, in short, we want to know the Poseidon hash of -/// the children of a `Ptr`. +/// A `ZPtr` is the result of "hydrating" a `Ptr`, which is a process of replacing +/// indices by hashes. That is, a `ZPtr` is a content-addressed, tagged, pointer. +/// By analogy, we can view ordinary field elements as hydrated raw pointers. /// -/// `ZPtr`s are used mainly for proofs, but they're also useful when we want -/// to content-address a store. +/// With `ZPtr`s we are able to content-address arbitrary DAGs, and thus be able to +/// represent these data structures as field elements. This is how we can prove facts +/// about data structures only using field elements. `ZPtr`s are also useful when we +/// want to content-address the store. /// -/// An important note is that computing the respective `ZPtr` of a `Ptr` can be -/// expensive because of the Poseidon hashes. That's why we operate on `Ptr`s +/// In principle, `ZPtr`s could be used in place of `Ptr`, but it is important to +/// note that content-addressing can be expensive, especially in the context of +/// interpretation, because of the Poseidon hashes. That's why we operate on `Ptr`s /// when interpreting LEMs and delay the need for `ZPtr`s as much as possible. pub type ZPtr = crate::z_data::z_ptr::ZPtr; diff --git a/src/lem/store.rs b/src/lem/store.rs index acb504d97a..9f732f2efc 100644 --- a/src/lem/store.rs +++ b/src/lem/store.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; use arc_swap::ArcSwap; use bellpepper::util_cs::witness_cs::SizedWitness; use elsa::{ @@ -26,32 +26,24 @@ use crate::{ tag::ExprTag::{Char, Comm, Cons, Cproc, Fun, Key, Nil, Num, Str, Sym, Thunk, U64}, }; -use super::pointers::{Ptr, ZPtr}; +use super::pointers::{Ptr, RawPtr, ZPtr}; /// The `Store` is a crucial part of Lurk's implementation and tries to be a /// vesatile data structure for many parts of Lurk's data pipeline. /// -/// It holds Lurk data structured as trees of `Ptr`s. When a `Ptr` has children, -/// we store them in the `IndexSet`s available: `tuple2`, `tuple3` or `tuple4`. -/// These data structures speed up LEM interpretation because lookups by indices -/// are fast. +/// It holds Lurk data structured as graphs of `RawPtr`s. When a `RawPtr` has +/// children, we store them in its respective `IndexSet`. These data structures +/// speed up LEM interpretation because lookups by indices are fast, and leave +/// all the hashing to be done by the hydration step in multiple threads. /// -/// The `Store` provides an infra to speed up interning strings and symbols. This -/// data is saved in `string_ptr_cache` and `symbol_ptr_cache`. -/// -/// There's also a process that we call "hydration", in which we use Poseidon -/// hashes to compute the (stable) hash of the children of a pointer. These hashes -/// are necessary when we want to create Lurk proofs because the circuit consumes -/// elements of the `LurkField`, not (unstable) indices of `IndexSet`s. -/// -/// Lastly, we have a `HashMap` to hold committed data, which can be retrieved by -/// the resulting commitment hash. +/// The `Store` also provides an infra to speed up interning strings and symbols. +/// This data is saved in `string_ptr_cache` and `symbol_ptr_cache`. #[derive(Debug)] pub struct Store { f_elts: FrozenIndexSet>>, - tuple2: FrozenIndexSet>, - tuple3: FrozenIndexSet>, - tuple4: FrozenIndexSet>, + hash4: FrozenIndexSet>, + hash6: FrozenIndexSet>, + hash8: FrozenIndexSet>, string_ptr_cache: FrozenMap>, symbol_ptr_cache: FrozenMap>, @@ -59,14 +51,14 @@ pub struct Store { ptr_string_cache: FrozenMap, ptr_symbol_cache: FrozenMap>, + comms: FrozenMap, Box<(F, Ptr)>>, // hash -> (secret, src) + pub poseidon_cache: PoseidonCache, pub inverse_poseidon_cache: InversePoseidonCache, - dehydrated: ArcSwap>>, - z_cache: FrozenMap>>, - inverse_z_cache: FrozenMap, Box>, - - comms: FrozenMap, Box<(F, Ptr)>>, // hash -> (secret, src) + dehydrated: ArcSwap>>, + z_cache: FrozenMap>>, + inverse_z_cache: FrozenMap, Box>, // cached indices for the hashes of 3, 4, 6 and 8 padded zeros pub hash3zeros_idx: usize, @@ -83,7 +75,17 @@ impl Default for Store { let hash6zeros = poseidon_cache.hash6(&[F::ZERO; 6]); let hash8zeros = poseidon_cache.hash8(&[F::ZERO; 8]); + // Since tags are used very often, we will allocate them at the beginning + // in order, so that we do not need to use the `f_elts` when we have a tag + // This is similar to the `hashNzeros` optimization let f_elts = FrozenIndexSet::default(); + let mut i = 0; + while let Some(tag) = Tag::pos(i) { + let (j, _) = f_elts.insert_probe(FWrap(tag.to_field()).into()); + // This is to make sure the indices are ordered + assert_eq!(i, j); + i += 1; + } let (hash3zeros_idx, _) = f_elts.insert_probe(FWrap(hash3zeros).into()); let (hash4zeros_idx, _) = f_elts.insert_probe(FWrap(hash4zeros).into()); let (hash6zeros_idx, _) = f_elts.insert_probe(FWrap(hash6zeros).into()); @@ -91,19 +93,19 @@ impl Default for Store { Self { f_elts, - tuple2: Default::default(), - tuple3: Default::default(), - tuple4: Default::default(), + hash4: Default::default(), + hash6: Default::default(), + hash8: Default::default(), string_ptr_cache: Default::default(), symbol_ptr_cache: Default::default(), ptr_string_cache: Default::default(), ptr_symbol_cache: Default::default(), + comms: Default::default(), poseidon_cache, inverse_poseidon_cache: Default::default(), dehydrated: Default::default(), z_cache: Default::default(), inverse_z_cache: Default::default(), - comms: Default::default(), hash3zeros_idx, hash4zeros_idx, hash6zeros_idx, @@ -112,6 +114,44 @@ impl Default for Store { } } +// These are utility macros for store methods on `Ptr`s, especially because +// they contain two const generic variables (more on this later) +macro_rules! count { + () => (0); + ( $_x:tt $($xs:tt)* ) => (1 + crate::lem::store::count!($($xs)*)); +} +pub(crate) use count; + +macro_rules! intern_ptrs { + ($store:expr, $tag:expr, $($ptrs:expr),*) => {{ + const N: usize = crate::lem::store::count!($($ptrs)*); + ($store).intern_ptrs::<{2*N}, N>($tag, [$($ptrs),*]) + }} +} +pub(crate) use intern_ptrs; + +macro_rules! intern_ptrs_hydrated { + ($store:expr, $tag:expr, $z:expr, $($ptrs:expr),*) => {{ + const N: usize = crate::lem::store::count!($($ptrs)*); + ($store).intern_ptrs_hydrated::<{2*N}, N>($tag, [$($ptrs),*], $z) + }} +} +pub(crate) use intern_ptrs_hydrated; + +macro_rules! fetch_ptrs { + ($store:expr, $n:expr, $idx:expr) => {{ + ($store).fetch_ptrs::<{ 2 * $n }, $n>($idx) + }}; +} +pub(crate) use fetch_ptrs; + +macro_rules! expect_ptrs { + ($store:expr, $n:expr, $idx:expr) => {{ + ($store).expect_ptrs::<{ 2 * $n }, $n>($idx) + }}; +} +pub(crate) use expect_ptrs; + impl Store { /// Cost of poseidon hash with arity 3, including the input #[inline] @@ -161,58 +201,57 @@ impl Store { self.expect_f(self.hash8zeros_idx) } + /// Converts array of pointers of size `P` to array of raw pointers of size `N` such that `P = N * 2`. + /// Since the `generic_const_exprs` feature is still unstable, we cannot substitute `N * 2` + /// for generic const `P` and remove it completely, so we must keep it and do a dynamic assertion + /// that it equals `N * 2`. This is not very ergonomic though, since we must add turbofishes + /// like `::<6, 3>` instead of the simpler `::<3>`. #[inline] - pub fn intern_f(&self, f: F) -> (usize, bool) { - self.f_elts.insert_probe(Box::new(FWrap(f))) + pub fn ptrs_to_raw_ptrs(&self, ptrs: &[Ptr; P]) -> [RawPtr; N] { + assert_eq!(P * 2, N); + let mut raw_ptrs = [self.raw_zero(); N]; + for i in 0..P { + raw_ptrs[2 * i] = self.tag(*ptrs[i].tag()); + raw_ptrs[2 * i + 1] = *ptrs[i].raw(); + } + raw_ptrs } - /// Creates an atom `Ptr` which points to a cached element of the finite - /// field `F` - pub fn intern_atom(&self, tag: Tag, f: F) -> Ptr { - let (idx, inserted) = self.intern_f(f); - let ptr = Ptr::Atom(tag, idx); - if inserted { - // this is for `hydrate_z_cache` - self.dehydrated.load().push(Box::new(ptr)); + /// Tries to convert array of raw pointers of size `N` to array of pointers of size `P = N * 2`. + /// It might fail since not all raw pointers represent valid tags. + #[inline] + pub fn raw_ptrs_to_ptrs( + &self, + raw_ptrs: &[RawPtr; N], + ) -> Option<[Ptr; P]> { + assert_eq!(P * 2, N); + let mut ptrs = [self.dummy(); P]; + for i in 0..P { + let tag = self.fetch_tag(&raw_ptrs[2 * i])?; + ptrs[i] = Ptr::new(tag, raw_ptrs[2 * i + 1]) } - ptr + Some(ptrs) } - /// Similar to `intern_atom` but doesn't add the resulting pointer to - /// `dehydrated`. This function is used when converting a `ZStore` to a - /// `Store`. - pub fn intern_atom_hydrated(&self, tag: Tag, f: F, z: ZPtr) -> Ptr { - let ptr = Ptr::Atom(tag, self.intern_f(f).0); - self.z_cache.insert(ptr, Box::new(z)); - self.inverse_z_cache.insert(z, Box::new(ptr)); - ptr + #[inline] + pub fn intern_f(&self, f: F) -> (usize, bool) { + self.f_elts.insert_probe(Box::new(FWrap(f))) } - /// Creates a `Ptr` that's a parent of two children - pub fn intern_2_ptrs(&self, tag: Tag, a: Ptr, b: Ptr) -> Ptr { - let (idx, inserted) = self.tuple2.insert_probe(Box::new((a, b))); - let ptr = Ptr::Tuple2(tag, idx); - if inserted { - // this is for `hydrate_z_cache` - self.dehydrated.load().push(Box::new(ptr)); - } - ptr + /// Creates an atom `RawPtr` which points to a cached element of the finite + /// field `F` + pub fn intern_raw_atom(&self, f: F) -> RawPtr { + let (idx, _) = self.intern_f(f); + RawPtr::Atom(idx) } - /// Similar to `intern_2_ptrs` but doesn't add the resulting pointer to - /// `dehydrated`. This function is used when converting a `ZStore` to a - /// `Store`. - pub fn intern_2_ptrs_hydrated(&self, tag: Tag, a: Ptr, b: Ptr, z: ZPtr) -> Ptr { - let ptr = Ptr::Tuple2(tag, self.tuple2.insert_probe(Box::new((a, b))).0); - self.z_cache.insert(ptr, Box::new(z)); - self.inverse_z_cache.insert(z, Box::new(ptr)); - ptr + pub fn intern_atom(&self, tag: Tag, f: F) -> Ptr { + Ptr::new(tag, self.intern_raw_atom(f)) } - /// Creates a `Ptr` that's a parent of three children - pub fn intern_3_ptrs(&self, tag: Tag, a: Ptr, b: Ptr, c: Ptr) -> Ptr { - let (idx, inserted) = self.tuple3.insert_probe(Box::new((a, b, c))); - let ptr = Ptr::Tuple3(tag, idx); + /// Creates a `RawPtr` that's a parent of `N` children + pub fn intern_raw_ptrs(&self, ptrs: [RawPtr; N]) -> RawPtr { + let (ptr, inserted) = self.intern_raw_ptrs_internal::(ptrs); if inserted { // this is for `hydrate_z_cache` self.dehydrated.load().push(Box::new(ptr)); @@ -220,43 +259,56 @@ impl Store { ptr } - /// Similar to `intern_3_ptrs` but doesn't add the resulting pointer to + /// Similar to `intern_raw_ptrs` but doesn't add the resulting pointer to /// `dehydrated`. This function is used when converting a `ZStore` to a /// `Store`. - pub fn intern_3_ptrs_hydrated(&self, tag: Tag, a: Ptr, b: Ptr, c: Ptr, z: ZPtr) -> Ptr { - let ptr = Ptr::Tuple3(tag, self.tuple3.insert_probe(Box::new((a, b, c))).0); + pub fn intern_raw_ptrs_hydrated( + &self, + ptrs: [RawPtr; N], + z: FWrap, + ) -> RawPtr { + let (ptr, _) = self.intern_raw_ptrs_internal::(ptrs); self.z_cache.insert(ptr, Box::new(z)); self.inverse_z_cache.insert(z, Box::new(ptr)); ptr } - /// Creates a `Ptr` that's a parent of four children - pub fn intern_4_ptrs(&self, tag: Tag, a: Ptr, b: Ptr, c: Ptr, d: Ptr) -> Ptr { - let (idx, inserted) = self.tuple4.insert_probe(Box::new((a, b, c, d))); - let ptr = Ptr::Tuple4(tag, idx); - if inserted { - // this is for `hydrate_z_cache` - self.dehydrated.load().push(Box::new(ptr)); + #[inline] + fn intern_raw_ptrs_internal(&self, ptrs: [RawPtr; N]) -> (RawPtr, bool) { + macro_rules! intern { + ($Hash:ident, $hash:ident, $n:expr) => {{ + let ptrs = unsafe { std::mem::transmute::<&[RawPtr; N], &[RawPtr; $n]>(&ptrs) }; + let (idx, inserted) = self.$hash.insert_probe(Box::new(*ptrs)); + (RawPtr::$Hash(idx), inserted) + }}; + } + match N { + 4 => intern!(Hash4, hash4, 4), + 6 => intern!(Hash6, hash6, 6), + 8 => intern!(Hash8, hash8, 8), + _ => unimplemented!(), } - ptr } - /// Similar to `intern_4_ptrs` but doesn't add the resulting pointer to + /// Creates a `Ptr` that's a parent of `N` children + pub fn intern_ptrs(&self, tag: Tag, ptrs: [Ptr; P]) -> Ptr { + let raw_ptrs = self.ptrs_to_raw_ptrs::(&ptrs); + let payload = self.intern_raw_ptrs::(raw_ptrs); + Ptr::new(tag, payload) + } + + /// Similar to `intern_ptrs` but doesn't add the resulting pointer to /// `dehydrated`. This function is used when converting a `ZStore` to a /// `Store`. - pub fn intern_4_ptrs_hydrated( + pub fn intern_ptrs_hydrated( &self, tag: Tag, - a: Ptr, - b: Ptr, - c: Ptr, - d: Ptr, + ptrs: [Ptr; P], z: ZPtr, ) -> Ptr { - let ptr = Ptr::Tuple4(tag, self.tuple4.insert_probe(Box::new((a, b, c, d))).0); - self.z_cache.insert(ptr, Box::new(z)); - self.inverse_z_cache.insert(z, Box::new(ptr)); - ptr + let raw_ptrs = self.ptrs_to_raw_ptrs::(&ptrs); + let payload = self.intern_raw_ptrs_hydrated::(raw_ptrs, FWrap(*z.value())); + Ptr::new(tag, payload) } #[inline] @@ -265,18 +317,61 @@ impl Store { } #[inline] - pub fn fetch_2_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr)> { - self.tuple2.get_index(idx) + pub fn fetch_raw_ptrs(&self, idx: usize) -> Option<&[RawPtr; N]> { + macro_rules! fetch { + ($hash:ident, $n:expr) => {{ + let ptrs = self.$hash.get_index(idx)?; + let ptrs = unsafe { std::mem::transmute::<&[RawPtr; $n], &[RawPtr; N]>(ptrs) }; + Some(ptrs) + }}; + } + match N { + 4 => fetch!(hash4, 4), + 6 => fetch!(hash6, 6), + 8 => fetch!(hash8, 8), + _ => unimplemented!(), + } } #[inline] - pub fn fetch_3_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr, Ptr)> { - self.tuple3.get_index(idx) + pub fn fetch_ptrs(&self, idx: usize) -> Option<[Ptr; P]> { + assert_eq!(P * 2, N); + let raw_ptrs = self.fetch_raw_ptrs::(idx)?; + self.raw_ptrs_to_ptrs(raw_ptrs) } #[inline] - pub fn fetch_4_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr, Ptr, Ptr)> { - self.tuple4.get_index(idx) + pub fn expect_f(&self, idx: usize) -> &F { + self.fetch_f(idx).expect("Index missing from f_elts") + } + + #[inline] + pub fn expect_raw_ptrs(&self, idx: usize) -> &[RawPtr; N] { + self.fetch_raw_ptrs::(idx) + .expect("Index missing from store") + } + + #[inline] + pub fn expect_ptrs(&self, idx: usize) -> [Ptr; P] { + self.fetch_ptrs::(idx) + .expect("Index missing from store") + } + + #[inline] + pub fn tag(&self, tag: Tag) -> RawPtr { + // Tags are interned in order, so their index is the store index + RawPtr::Atom(tag.index()) + } + + #[inline] + pub fn fetch_tag(&self, ptr: &RawPtr) -> Option { + let idx = ptr.get_atom()?; + Tag::pos(idx) + } + + pub fn raw_to_ptr(&self, tag: &RawPtr, raw: &RawPtr) -> Option { + let tag = self.fetch_tag(tag)?; + Some(Ptr::new(tag, *raw)) } #[inline] @@ -304,37 +399,42 @@ impl Store { self.intern_atom(Tag::Expr(Comm), hash) } + #[inline] + pub fn raw_zero(&self) -> RawPtr { + self.intern_raw_atom(F::ZERO) + } + #[inline] pub fn zero(&self, tag: Tag) -> Ptr { - self.intern_atom(tag, F::ZERO) + Ptr::new(tag, self.raw_zero()) } - pub fn is_zero(&self, ptr: &Ptr) -> bool { + pub fn is_zero(&self, ptr: &RawPtr) -> bool { match ptr { - Ptr::Atom(_, idx) => self.fetch_f(*idx) == Some(&F::ZERO), + RawPtr::Atom(idx) => self.fetch_f(*idx) == Some(&F::ZERO), _ => false, } } #[inline] pub fn dummy(&self) -> Ptr { - self.zero(Tag::Expr(Nil)) + Ptr::new(Tag::Expr(Nil), self.raw_zero()) } - /// Creates an atom pointer from a `ZPtr`, with its tag and hash. Thus hashing + /// Creates an atom pointer from a `ZPtr`, with its hash. Hashing /// such pointer will result on the same original `ZPtr` #[inline] - pub fn opaque(&self, z_ptr: ZPtr) -> Ptr { - let crate::z_data::z_ptr::ZPtr(t, h) = z_ptr; - self.intern_atom(t, h) + pub fn opaque(&self, z: ZPtr) -> Ptr { + self.intern_atom(*z.tag(), *z.value()) } pub fn intern_string(&self, s: &str) -> Ptr { if let Some(ptr) = self.string_ptr_cache.get(s) { *ptr } else { - let ptr = s.chars().rev().fold(self.zero(Tag::Expr(Str)), |acc, c| { - self.intern_2_ptrs(Tag::Expr(Str), self.char(c), acc) + let empty_str = Ptr::new(Tag::Expr(Str), self.raw_zero()); + let ptr = s.chars().rev().fold(empty_str, |acc, c| { + intern_ptrs!(self, Tag::Expr(Str), self.char(c), acc) }); self.string_ptr_cache.insert(s.to_string(), Box::new(ptr)); self.ptr_string_cache.insert(ptr, s.to_string()); @@ -348,9 +448,12 @@ impl Store { } else { let mut string = String::new(); let mut ptr = *ptr; + if *ptr.tag() != Tag::Expr(Str) { + return None; + } loop { - match ptr { - Ptr::Atom(Tag::Expr(Str), idx) => { + match *ptr.raw() { + RawPtr::Atom(idx) => { if self.fetch_f(idx)? == &F::ZERO { self.ptr_string_cache.insert(ptr, string.clone()); return Some(string); @@ -358,13 +461,15 @@ impl Store { return None; } } - Ptr::Tuple2(Tag::Expr(Str), idx) => { - let (car, cdr) = self.fetch_2_ptrs(idx)?; + RawPtr::Hash4(idx) => { + let [car_tag, car, cdr_tag, cdr] = self.fetch_raw_ptrs(idx)?; + assert_eq!(*car_tag, self.tag(Tag::Expr(Char))); + assert_eq!(*cdr_tag, self.tag(Tag::Expr(Str))); match car { - Ptr::Atom(Tag::Expr(Char), idx) => { + RawPtr::Atom(idx) => { let f = self.fetch_f(*idx)?; string.push(f.to_char().expect("malformed char pointer")); - ptr = *cdr + ptr = Ptr::new(Tag::Expr(Str), *cdr) } _ => return None, } @@ -376,9 +481,9 @@ impl Store { } pub fn intern_symbol_path(&self, path: &[String]) -> Ptr { - path.iter().fold(self.zero(Tag::Expr(Sym)), |acc, s| { - let s_ptr = self.intern_string(s); - self.intern_2_ptrs(Tag::Expr(Sym), s_ptr, acc) + let zero_sym = Ptr::new(Tag::Expr(Sym), self.raw_zero()); + path.iter().fold(zero_sym, |acc, s| { + intern_ptrs!(self, Tag::Expr(Sym), self.intern_string(s), acc) }) } @@ -388,9 +493,9 @@ impl Store { } else { let path_ptr = self.intern_symbol_path(sym.path()); let sym_ptr = if sym == &lurk_sym("nil") { - path_ptr.cast(Tag::Expr(Nil)) + Ptr::new(Tag::Expr(Nil), *path_ptr.raw()) } else if sym.is_keyword() { - path_ptr.cast(Tag::Expr(Key)) + Ptr::new(Tag::Expr(Key), *path_ptr.raw()) } else { path_ptr }; @@ -404,11 +509,13 @@ impl Store { fn fetch_symbol_path(&self, mut idx: usize) -> Option> { let mut path = vec![]; loop { - let (car, cdr) = self.fetch_2_ptrs(idx)?; - let string = self.fetch_string(car)?; + let [car_tag, car, cdr_tag, cdr] = self.fetch_raw_ptrs(idx)?; + assert_eq!(*car_tag, self.tag(Tag::Expr(Str))); + assert_eq!(*cdr_tag, self.tag(Tag::Expr(Sym))); + let string = self.fetch_string(&Ptr::new(Tag::Expr(Str), *car))?; path.push(string); match cdr { - Ptr::Atom(Tag::Expr(Sym), idx) => { + RawPtr::Atom(idx) => { if self.fetch_f(*idx)? == &F::ZERO { path.reverse(); return Some(path); @@ -416,7 +523,7 @@ impl Store { return None; } } - Ptr::Tuple2(Tag::Expr(Sym), idx_cdr) => idx = *idx_cdr, + RawPtr::Hash4(idx_cdr) => idx = *idx_cdr, _ => return None, } } @@ -426,8 +533,8 @@ impl Store { if let Some(sym) = self.ptr_symbol_cache.get(ptr) { Some(sym.clone()) } else { - match ptr { - Ptr::Atom(Tag::Expr(Sym), idx) => { + match (ptr.tag(), ptr.raw()) { + (Tag::Expr(Sym), RawPtr::Atom(idx)) => { if self.fetch_f(*idx)? == &F::ZERO { let sym = Symbol::root_sym(); self.ptr_symbol_cache.insert(*ptr, Box::new(sym.clone())); @@ -436,7 +543,7 @@ impl Store { None } } - Ptr::Atom(Tag::Expr(Key), idx) => { + (Tag::Expr(Key), RawPtr::Atom(idx)) => { if self.fetch_f(*idx)? == &F::ZERO { let key = Symbol::root_key(); self.ptr_symbol_cache.insert(*ptr, Box::new(key.clone())); @@ -445,13 +552,13 @@ impl Store { None } } - Ptr::Tuple2(Tag::Expr(Sym | Nil), idx) => { + (Tag::Expr(Sym | Nil), RawPtr::Hash4(idx)) => { let path = self.fetch_symbol_path(*idx)?; let sym = Symbol::sym_from_vec(path); self.ptr_symbol_cache.insert(*ptr, Box::new(sym.clone())); Some(sym) } - Ptr::Tuple2(Tag::Expr(Key), idx) => { + (Tag::Expr(Key), RawPtr::Hash4(idx)) => { let path = self.fetch_symbol_path(*idx)?; let key = Symbol::key_from_vec(path); self.ptr_symbol_cache.insert(*ptr, Box::new(key.clone())); @@ -478,6 +585,26 @@ impl Store { } } + #[inline] + pub fn intern_lurk_symbol(&self, name: &str) -> Ptr { + self.intern_symbol(&lurk_sym(name)) + } + + #[inline] + pub fn intern_nil(&self) -> Ptr { + self.intern_lurk_symbol("nil") + } + + #[inline] + pub fn intern_user_symbol(&self, name: &str) -> Ptr { + self.intern_symbol(&user_sym(name)) + } + + #[inline] + pub fn key(&self, name: &str) -> Ptr { + self.intern_symbol(&Symbol::key(&[name.to_string()])) + } + #[inline] pub fn add_comm(&self, hash: F, secret: F, payload: Ptr) { self.comms @@ -508,49 +635,29 @@ impl Store { self.comms.get(&FWrap(hash)) } - #[inline] - pub fn intern_lurk_symbol(&self, name: &str) -> Ptr { - self.intern_symbol(&lurk_sym(name)) - } - - #[inline] - pub fn intern_nil(&self) -> Ptr { - self.intern_lurk_symbol("nil") - } - - #[inline] - pub fn intern_user_symbol(&self, name: &str) -> Ptr { - self.intern_symbol(&user_sym(name)) - } - - #[inline] - pub fn key(&self, name: &str) -> Ptr { - self.intern_symbol(&Symbol::key(&[name.to_string()])) - } - #[inline] pub fn cons(&self, car: Ptr, cdr: Ptr) -> Ptr { - self.intern_2_ptrs(Tag::Expr(Cons), car, cdr) + intern_ptrs!(self, Tag::Expr(Cons), car, cdr) } #[inline] pub fn intern_fun(&self, arg: Ptr, body: Ptr, env: Ptr) -> Ptr { - self.intern_4_ptrs(Tag::Expr(Fun), arg, body, env, self.dummy()) + intern_ptrs!(self, Tag::Expr(Fun), arg, body, env, self.dummy()) } #[inline] pub fn cont_outermost(&self) -> Ptr { - Ptr::Atom(Tag::Cont(Outermost), self.hash8zeros_idx) + Ptr::new(Tag::Cont(Outermost), RawPtr::Atom(self.hash8zeros_idx)) } #[inline] pub fn cont_error(&self) -> Ptr { - Ptr::Atom(Tag::Cont(ContTag::Error), self.hash8zeros_idx) + Ptr::new(Tag::Cont(ContTag::Error), RawPtr::Atom(self.hash8zeros_idx)) } #[inline] pub fn cont_terminal(&self) -> Ptr { - Ptr::Atom(Tag::Cont(Terminal), self.hash8zeros_idx) + Ptr::new(Tag::Cont(Terminal), RawPtr::Atom(self.hash8zeros_idx)) } pub fn car_cdr(&self, ptr: &Ptr) -> Result<(Ptr, Ptr)> { @@ -560,23 +667,32 @@ impl Store { Ok((nil, nil)) } Tag::Expr(Cons) => { - let Some(idx) = ptr.get_index2() else { + let Some(idx) = ptr.raw().get_hash4() else { bail!("malformed cons pointer") }; - match self.fetch_2_ptrs(idx) { - Some(res) => Ok(*res), + match self.fetch_raw_ptrs(idx) { + Some([car_tag, car, cdr_tag, cdr]) => { + let car_ptr = self.raw_to_ptr(car_tag, car).context("Not a pointer")?; + let cdr_ptr = self.raw_to_ptr(cdr_tag, cdr).context("Not a pointer")?; + Ok((car_ptr, cdr_ptr)) + } None => bail!("car/cdr not found"), } } Tag::Expr(Str) => { - if self.is_zero(ptr) { - Ok((self.intern_nil(), self.zero(Tag::Expr(Str)))) + if self.is_zero(ptr.raw()) { + let empty_str = Ptr::new(Tag::Expr(Str), self.raw_zero()); + Ok((self.intern_nil(), empty_str)) } else { - let Some(idx) = ptr.get_index2() else { + let Some(idx) = ptr.raw().get_hash4() else { bail!("malformed str pointer") }; - match self.fetch_2_ptrs(idx) { - Some(res) => Ok(*res), + match self.fetch_raw_ptrs(idx) { + Some([car_tag, car, cdr_tag, cdr]) => { + let car_ptr = self.raw_to_ptr(car_tag, car).context("Not a pointer")?; + let cdr_ptr = self.raw_to_ptr(cdr_tag, cdr).context("Not a pointer")?; + Ok((car_ptr, cdr_ptr)) + } None => bail!("car/cdr not found"), } } @@ -591,7 +707,7 @@ impl Store { elts.into_iter() .rev() .fold(last.unwrap_or_else(|| self.intern_nil()), |acc, elt| { - self.intern_2_ptrs(Tag::Expr(Cons), elt, acc) + self.cons(elt, acc) }) } @@ -611,20 +727,25 @@ impl Store { /// Fetches a cons list that was interned. If the list is improper, the second /// element of the returned pair will carry the improper terminating value pub fn fetch_list(&self, ptr: &Ptr) -> Option<(Vec, Option)> { - match ptr { - Ptr::Tuple2(Tag::Expr(Nil), _) => Some((vec![], None)), - Ptr::Tuple2(Tag::Expr(Cons), mut idx) => { + if *ptr == self.intern_nil() { + return Some((vec![], None)); + } + match (ptr.tag(), ptr.raw()) { + (Tag::Expr(Nil), _) => panic!("Malformed nil expression"), + (Tag::Expr(Cons), RawPtr::Hash4(mut idx)) => { let mut list = vec![]; let mut last = None; - while let Some((car, cdr)) = self.fetch_2_ptrs(idx) { - list.push(*car); - match cdr.tag() { + while let Some([car_tag, car, cdr_tag, cdr]) = self.fetch_raw_ptrs(idx) { + let car_ptr = self.raw_to_ptr(car_tag, car)?; + let cdr_ptr = self.raw_to_ptr(cdr_tag, cdr)?; + list.push(car_ptr); + match cdr_ptr.tag() { Tag::Expr(Nil) => break, Tag::Expr(Cons) => { - idx = cdr.get_index2()?; + idx = cdr.get_hash4()?; } _ => { - last = Some(*cdr); + last = Some(cdr_ptr); break; } } @@ -693,26 +814,6 @@ impl Store { self.read(State::init_lurk_state().rccell(), input) } - #[inline] - pub fn expect_f(&self, idx: usize) -> &F { - self.fetch_f(idx).expect("Index missing from f_elts") - } - - #[inline] - pub fn expect_2_ptrs(&self, idx: usize) -> &(Ptr, Ptr) { - self.fetch_2_ptrs(idx).expect("Index missing from tuple2") - } - - #[inline] - pub fn expect_3_ptrs(&self, idx: usize) -> &(Ptr, Ptr, Ptr) { - self.fetch_3_ptrs(idx).expect("Index missing from tuple3") - } - - #[inline] - pub fn expect_4_ptrs(&self, idx: usize) -> &(Ptr, Ptr, Ptr, Ptr) { - self.fetch_4_ptrs(idx).expect("Index missing from tuple4") - } - /// Recursively hashes the children of a `Ptr` in order to obtain its /// corresponding `ZPtr`. While traversing a `Ptr` tree, it consults the /// cache of `Ptr`s that have already been hydrated and also populates this @@ -720,110 +821,49 @@ impl Store { /// /// Warning: without cache hits, this function might blow up Rust's recursion /// depth limit. This limitation is circumvented by calling `hydrate_z_cache` - /// beforehand or by using `hash_ptr` instead, which is slightly slower. - fn hash_ptr_unsafe(&self, ptr: &Ptr) -> ZPtr { - match ptr { - Ptr::Atom(tag, idx) => { - if let Some(z_ptr) = self.z_cache.get(ptr) { - *z_ptr + /// beforehand or by using `hash_raw_ptr` instead, which is slightly slower. + fn hash_raw_ptr_unsafe(&self, ptr: &RawPtr) -> FWrap { + macro_rules! hash_raw { + ($hash:ident, $n:expr, $idx:expr) => {{ + if let Some(z) = self.z_cache.get(ptr) { + *z } else { - let z_ptr = ZPtr::from_parts(*tag, *self.expect_f(*idx)); - self.z_cache.insert(*ptr, Box::new(z_ptr)); - self.inverse_z_cache.insert(z_ptr, Box::new(*ptr)); - z_ptr - } - } - Ptr::Tuple2(tag, idx) => { - if let Some(z_ptr) = self.z_cache.get(ptr) { - *z_ptr - } else { - let (a, b) = self.expect_2_ptrs(*idx); - let a = self.hash_ptr_unsafe(a); - let b = self.hash_ptr_unsafe(b); - let z_ptr = ZPtr::from_parts( - *tag, - self.poseidon_cache.hash4(&[ - a.tag_field(), - *a.value(), - b.tag_field(), - *b.value(), - ]), - ); - self.z_cache.insert(*ptr, Box::new(z_ptr)); - self.inverse_z_cache.insert(z_ptr, Box::new(*ptr)); - z_ptr - } - } - Ptr::Tuple3(tag, idx) => { - if let Some(z_ptr) = self.z_cache.get(ptr) { - *z_ptr - } else { - let (a, b, c) = self.expect_3_ptrs(*idx); - let a = self.hash_ptr_unsafe(a); - let b = self.hash_ptr_unsafe(b); - let c = self.hash_ptr_unsafe(c); - let z_ptr = ZPtr::from_parts( - *tag, - self.poseidon_cache.hash6(&[ - a.tag_field(), - *a.value(), - b.tag_field(), - *b.value(), - c.tag_field(), - *c.value(), - ]), - ); - self.z_cache.insert(*ptr, Box::new(z_ptr)); - self.inverse_z_cache.insert(z_ptr, Box::new(*ptr)); - z_ptr - } - } - Ptr::Tuple4(tag, idx) => { - if let Some(z_ptr) = self.z_cache.get(ptr) { - *z_ptr - } else { - let (a, b, c, d) = self.expect_4_ptrs(*idx); - let a = self.hash_ptr_unsafe(a); - let b = self.hash_ptr_unsafe(b); - let c = self.hash_ptr_unsafe(c); - let d = self.hash_ptr_unsafe(d); - let z_ptr = ZPtr::from_parts( - *tag, - self.poseidon_cache.hash8(&[ - a.tag_field(), - *a.value(), - b.tag_field(), - *b.value(), - c.tag_field(), - *c.value(), - d.tag_field(), - *d.value(), - ]), - ); - self.z_cache.insert(*ptr, Box::new(z_ptr)); - self.inverse_z_cache.insert(z_ptr, Box::new(*ptr)); - z_ptr + let children_ptrs = self.expect_raw_ptrs::<$n>($idx); + let mut children_zs = [F::ZERO; $n]; + for (idx, child_ptr) in children_ptrs.iter().enumerate() { + children_zs[idx] = self.hash_raw_ptr_unsafe(child_ptr).0; + } + let z = FWrap(self.poseidon_cache.$hash(&children_zs)); + self.z_cache.insert(*ptr, Box::new(z)); + self.inverse_z_cache.insert(z, Box::new(*ptr)); + z } - } + }}; + } + match ptr { + RawPtr::Atom(idx) => FWrap(*self.expect_f(*idx)), + RawPtr::Hash4(idx) => hash_raw!(hash4, 4, *idx), + RawPtr::Hash6(idx) => hash_raw!(hash6, 6, *idx), + RawPtr::Hash8(idx) => hash_raw!(hash8, 8, *idx), } } /// Hashes pointers in parallel, consuming chunks of length 256, which is a /// reasonably safe limit. The danger of longer chunks is that the rightmost /// pointers are the ones which are more likely to reach the recursion depth - /// limit in `hash_ptr_unsafe`. So we move in smaller chunks from left to - /// right, populating the `z_cache`, which can rescue `hash_ptr_unsafe` from + /// limit in `hash_raw_ptr_unsafe`. So we move in smaller chunks from left to + /// right, populating the `z_cache`, which can rescue `hash_raw_ptr_unsafe` from /// dangerously deep recursions - fn hydrate_z_cache_with_ptrs(&self, ptrs: &[&Ptr]) { + fn hydrate_z_cache_with_ptrs(&self, ptrs: &[&RawPtr]) { ptrs.chunks(256).for_each(|chunk| { chunk.par_iter().for_each(|ptr| { - self.hash_ptr_unsafe(ptr); + self.hash_raw_ptr_unsafe(ptr); }); }); } - /// Hashes enqueued `Ptr` trees from the bottom to the top, avoiding deep - /// recursions in `hash_ptr`. Resets the `dehydrated` queue afterwards. + /// Hashes enqueued `RawPtr` trees from the bottom to the top, avoiding deep + /// recursions in `hash_raw_ptr`. Resets the `dehydrated` queue afterwards. pub fn hydrate_z_cache(&self) { self.hydrate_z_cache_with_ptrs(&self.dehydrated.load().iter().collect::>()); self.dehydrated.swap(Arc::new(FrozenVec::default())); @@ -842,20 +882,20 @@ impl Store { } } - /// Safe version of `hash_ptr_unsafe` that doesn't hit a stack overflow by + /// Safe version of `hash_raw_ptr_unsafe` that doesn't hit a stack overflow by /// precomputing the pointers that need to be hashed in order to hash the /// provided `ptr` - pub fn hash_ptr(&self, ptr: &Ptr) -> ZPtr { + pub fn hash_raw_ptr(&self, ptr: &RawPtr) -> FWrap { if self.is_below_safe_threshold() { - // just run `hash_ptr_unsafe` for extra speed when the dehydrated + // just run `hash_raw_ptr_unsafe` for extra speed when the dehydrated // queue is small enough - return self.hash_ptr_unsafe(ptr); + return self.hash_raw_ptr_unsafe(ptr); } - let mut ptrs: IndexSet<&Ptr> = IndexSet::default(); + let mut ptrs: IndexSet<&RawPtr> = IndexSet::default(); let mut stack = vec![ptr]; macro_rules! feed_loop { ($x:expr) => { - if $x.is_tuple() { + if $x.is_hash() { if self.z_cache.get($x).is_none() { if ptrs.insert($x) { stack.push($x); @@ -866,22 +906,22 @@ impl Store { } while let Some(ptr) = stack.pop() { match ptr { - Ptr::Atom(..) => (), - Ptr::Tuple2(_, idx) => { - let (a, b) = self.expect_2_ptrs(*idx); - for ptr in [a, b] { + RawPtr::Atom(..) => (), + RawPtr::Hash4(idx) => { + let ptrs = self.expect_raw_ptrs::<4>(*idx); + for ptr in ptrs { feed_loop!(ptr) } } - Ptr::Tuple3(_, idx) => { - let (a, b, c) = self.expect_3_ptrs(*idx); - for ptr in [a, b, c] { + RawPtr::Hash6(idx) => { + let ptrs = self.expect_raw_ptrs::<6>(*idx); + for ptr in ptrs { feed_loop!(ptr) } } - Ptr::Tuple4(_, idx) => { - let (a, b, c, d) = self.expect_4_ptrs(*idx); - for ptr in [a, b, c, d] { + RawPtr::Hash8(idx) => { + let ptrs = self.expect_raw_ptrs::<8>(*idx); + for ptr in ptrs { feed_loop!(ptr) } } @@ -889,8 +929,14 @@ impl Store { } ptrs.reverse(); self.hydrate_z_cache_with_ptrs(&ptrs.into_iter().collect::>()); - // Now it's okay to call `hash_ptr_unsafe` - self.hash_ptr_unsafe(ptr) + // Now it's okay to call `hash_raw_ptr_unsafe` + self.hash_raw_ptr_unsafe(ptr) + } + + /// Hydrates a `Ptr`. That is, creates a `ZPtr` with the tag of the pointer + /// and the hash of its raw pointer + pub fn hash_ptr(&self, ptr: &Ptr) -> ZPtr { + ZPtr::from_parts(*ptr.tag(), self.hash_raw_ptr(ptr.raw()).0) } /// Constructs a vector of scalars that correspond to tags and hashes computed @@ -898,28 +944,45 @@ impl Store { pub fn to_scalar_vector(&self, ptrs: &[Ptr]) -> Vec { ptrs.iter() .fold(Vec::with_capacity(2 * ptrs.len()), |mut acc, ptr| { - let z_ptr = self.hash_ptr(ptr); - acc.push(z_ptr.tag_field()); - acc.push(*z_ptr.value()); + let tag = ptr.tag().to_field(); + let payload = self.hash_raw_ptr(ptr.raw()).0; + acc.push(tag); + acc.push(payload); acc }) } + pub fn to_scalar_vector_raw(&self, ptrs: &[RawPtr]) -> Vec { + ptrs.iter().map(|ptr| self.hash_raw_ptr(ptr).0).collect() + } + /// Equality of the content-addressed versions of two pointers + #[inline] + pub fn raw_ptr_eq(&self, a: &RawPtr, b: &RawPtr) -> bool { + self.hash_raw_ptr(a) == self.hash_raw_ptr(b) + } + #[inline] pub fn ptr_eq(&self, a: &Ptr, b: &Ptr) -> bool { self.hash_ptr(a) == self.hash_ptr(b) } - /// Attempts to recover the `Ptr` that corresponds to `z_ptr` from - /// `inverse_z_cache`. If the mapping is not there, returns an atom pointer - /// with the same tag and value + /// Attempts to recover the `RawPtr` that corresponds to a field element `z` + /// from `inverse_z_cache`. If the mapping is not there, returns a raw atom + /// pointer with value #[inline] - pub fn to_ptr(&self, z_ptr: &ZPtr) -> Ptr { + pub fn to_raw_ptr(&self, z: &FWrap) -> RawPtr { self.inverse_z_cache - .get(z_ptr) + .get(z) .cloned() - .unwrap_or_else(|| self.opaque(*z_ptr)) + .unwrap_or_else(|| self.intern_raw_atom(z.0)) + } + + /// Attempts to recover the `Ptr` that corresponds to a `ZPtr`. If the mapping + /// is not there, returns an atom pointer with the same tag and value + #[inline] + pub fn to_ptr(&self, z_ptr: &ZPtr) -> Ptr { + Ptr::new(*z_ptr.tag(), self.to_raw_ptr(&FWrap(*z_ptr.value()))) } } @@ -957,6 +1020,7 @@ impl Ptr { } Char => { if let Some(c) = self + .raw() .get_atom() .map(|idx| store.expect_f(idx)) .and_then(F::to_char) @@ -986,7 +1050,7 @@ impl Ptr { } } Num => { - if let Some(f) = self.get_atom().map(|idx| store.expect_f(idx)) { + if let Some(f) = self.raw().get_atom().map(|idx| store.expect_f(idx)) { if let Some(u) = f.to_u64() { u.to_string() } else { @@ -998,6 +1062,7 @@ impl Ptr { } U64 => { if let Some(u) = self + .raw() .get_atom() .map(|idx| store.expect_f(idx)) .and_then(F::to_u64) @@ -1007,10 +1072,10 @@ impl Ptr { "".into() } } - Fun => match self.get_index4() { + Fun => match self.raw().get_hash8() { None => "".into(), Some(idx) => { - if let Some((vars, body, ..)) = store.fetch_4_ptrs(idx) { + if let Some([vars, body, _, _]) = fetch_ptrs!(store, 4, idx) { match vars.tag() { Tag::Expr(Nil) => { format!("", body.fmt_to_string(store, state)) @@ -1029,10 +1094,10 @@ impl Ptr { } } }, - Thunk => match self.get_index2() { + Thunk => match self.raw().get_hash4() { None => "".into(), Some(idx) => { - if let Some((val, cont)) = store.fetch_2_ptrs(idx) { + if let Some([val, cont]) = fetch_ptrs!(store, 2, idx) { format!( "Thunk{{ value: {} => cont: {} }}", val.fmt_to_string(store, state), @@ -1043,10 +1108,10 @@ impl Ptr { } } }, - Comm => match self.get_atom() { + Comm => match self.raw().get_atom() { Some(idx) => { let f = store.expect_f(idx); - if store.comms.get(&FWrap(*f)).is_some() { + if store.inverse_z_cache.get(&FWrap(*f)).is_some() { format!("(comm 0x{})", f.hex_digits()) } else { format!("", f.hex_digits()) @@ -1054,10 +1119,10 @@ impl Ptr { } None => "".into(), }, - Cproc => match self.get_index2() { + Cproc => match self.raw().get_hash4() { None => "".into(), Some(idx) => { - if let Some((cproc_name, args)) = store.fetch_2_ptrs(idx) { + if let Some([cproc_name, args]) = fetch_ptrs!(store, 2, idx) { format!( "", cproc_name.fmt_to_string(store, state), @@ -1116,10 +1181,10 @@ impl Ptr { store: &Store, state: &State, ) -> String { - match self.get_index4() { + match self.raw().get_hash8() { None => format!(""), Some(idx) => { - if let Some((a, cont, ..)) = store.fetch_4_ptrs(idx) { + if let Some([a, cont, _, _]) = fetch_ptrs!(store, 4, idx) { format!( "{name}{{ {field}: {}, continuation: {} }}", a.fmt_to_string(store, state), @@ -1139,10 +1204,10 @@ impl Ptr { store: &Store, state: &State, ) -> String { - match self.get_index4() { + match self.raw().get_hash8() { None => format!(""), Some(idx) => { - if let Some((a, b, cont, _)) = store.fetch_4_ptrs(idx) { + if let Some([a, b, cont, _]) = fetch_ptrs!(store, 4, idx) { let (fa, fb) = fields; format!( "{name}{{ {fa}: {}, {fb}: {}, continuation: {} }}", @@ -1164,10 +1229,10 @@ impl Ptr { store: &Store, state: &State, ) -> String { - match self.get_index4() { + match self.raw().get_hash8() { None => format!(""), Some(idx) => { - if let Some((a, b, c, cont)) = store.fetch_4_ptrs(idx) { + if let Some([a, b, c, cont]) = fetch_ptrs!(store, 4, idx) { let (fa, fb, fc) = fields; format!( "{name}{{ {fa}: {}, {fb}: {}, {fc}: {}, continuation: {} }}", @@ -1200,7 +1265,7 @@ mod tests { Num, Symbol, }; - use super::{Ptr, Store}; + use super::{Ptr, RawPtr, Store}; #[test] fn test_car_cdr() { @@ -1284,17 +1349,17 @@ mod tests { &store.poseidon_cache.hash3(&[zero; 3]) ); - let ptr2 = store.intern_2_ptrs(zero_tag, foo, foo); + let ptr2 = intern_ptrs!(store, zero_tag, foo, foo); let z_ptr2 = store.hash_ptr(&ptr2); assert_eq!(z_ptr2.tag(), &zero_tag); assert_eq!(z_ptr2.value(), &store.poseidon_cache.hash4(&[zero; 4])); - let ptr3 = store.intern_3_ptrs(zero_tag, foo, foo, foo); + let ptr3 = intern_ptrs!(store, zero_tag, foo, foo, foo); let z_ptr3 = store.hash_ptr(&ptr3); assert_eq!(z_ptr3.tag(), &zero_tag); assert_eq!(z_ptr3.value(), &store.poseidon_cache.hash6(&[zero; 6])); - let ptr4 = store.intern_4_ptrs(zero_tag, foo, foo, foo, foo); + let ptr4 = intern_ptrs!(store, zero_tag, foo, foo, foo, foo); let z_ptr4 = store.hash_ptr(&ptr4); assert_eq!(z_ptr4.tag(), &zero_tag); assert_eq!(z_ptr4.value(), &store.poseidon_cache.hash8(&[zero; 8])); @@ -1332,14 +1397,14 @@ mod tests { let string = String::from_utf8(vec![b'0'; 4096]).unwrap(); let store = Store::::default(); let ptr = store.intern_string(&string); - // `hash_ptr_unsafe` would overflow the stack, whereas `hash_ptr` works - let x = store.hash_ptr(&ptr); + // `hash_raw_ptr_unsafe` would overflow the stack, whereas `hash_raw_ptr` works + let x = store.hash_raw_ptr(ptr.raw()); let store = Store::::default(); let ptr = store.intern_string(&string); store.hydrate_z_cache(); - // but `hash_ptr_unsafe` works just fine after manual hydration - let y = store.hash_ptr_unsafe(&ptr); + // but `hash_raw_ptr_unsafe` works just fine after manual hydration + let y = store.hash_raw_ptr_unsafe(ptr.raw()); // and, of course, those functions result on the same `ZPtr` assert_eq!(x, y); @@ -1394,25 +1459,24 @@ mod tests { // helper function to test syntax interning roundtrip fn fetch_syntax(ptr: Ptr, store: &Store) -> Syntax { - match ptr { - Ptr::Atom(Tag::Expr(ExprTag::Num), idx) => { - Syntax::Num(Pos::No, Num::Scalar(*store.expect_f(idx))) + match ptr.parts() { + (Tag::Expr(ExprTag::Num), RawPtr::Atom(idx)) => { + Syntax::Num(Pos::No, Num::Scalar(*store.expect_f(*idx))) } - Ptr::Atom(Tag::Expr(ExprTag::Char), idx) => { - Syntax::Char(Pos::No, store.expect_f(idx).to_char().unwrap()) + (Tag::Expr(ExprTag::Char), RawPtr::Atom(idx)) => { + Syntax::Char(Pos::No, store.expect_f(*idx).to_char().unwrap()) } - Ptr::Atom(Tag::Expr(ExprTag::U64), idx) => Syntax::UInt( + (Tag::Expr(ExprTag::U64), RawPtr::Atom(idx)) => Syntax::UInt( Pos::No, - crate::UInt::U64(store.expect_f(idx).to_u64_unchecked()), + crate::UInt::U64(store.expect_f(*idx).to_u64_unchecked()), ), - Ptr::Atom(Tag::Expr(ExprTag::Sym | ExprTag::Key), _) - | Ptr::Tuple2(Tag::Expr(ExprTag::Sym | ExprTag::Key), _) => { + (Tag::Expr(ExprTag::Sym | ExprTag::Key), RawPtr::Atom(_) | RawPtr::Hash4(_)) => { Syntax::Symbol(Pos::No, store.fetch_symbol(&ptr).unwrap().into()) } - Ptr::Atom(Tag::Expr(ExprTag::Str), _) | Ptr::Tuple2(Tag::Expr(ExprTag::Str), _) => { + (Tag::Expr(ExprTag::Str), RawPtr::Atom(_) | RawPtr::Hash4(_)) => { Syntax::String(Pos::No, store.fetch_string(&ptr).unwrap()) } - Ptr::Tuple2(Tag::Expr(ExprTag::Cons), _) => { + (Tag::Expr(ExprTag::Cons), RawPtr::Hash4(_)) => { let (elts, last) = store.fetch_list(&ptr).unwrap(); let elts = elts .into_iter() @@ -1424,7 +1488,7 @@ mod tests { Syntax::List(Pos::No, elts) } } - Ptr::Tuple2(Tag::Expr(ExprTag::Nil), _) => { + (Tag::Expr(ExprTag::Nil), RawPtr::Hash4(_)) => { Syntax::Symbol(Pos::No, lurk_sym("nil").into()) } _ => unreachable!(), diff --git a/src/lem/tag.rs b/src/lem/tag.rs new file mode 100644 index 0000000000..673e0d6d41 --- /dev/null +++ b/src/lem/tag.rs @@ -0,0 +1,166 @@ +use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; +use strum::EnumCount; + +use crate::{ + field::LurkField, + tag::{ + ContTag, ExprTag, Op1, Op2, Tag as TagTrait, CONT_TAG_INIT, EXPR_TAG_INIT, OP1_TAG_INIT, + OP2_TAG_INIT, + }, +}; + +/// The LEM `Tag` is a wrapper around other types that are used as tags +#[derive(Copy, Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize)] +pub enum Tag { + Expr(ExprTag), + Cont(ContTag), + Op1(Op1), + Op2(Op2), +} + +impl TryFrom for Tag { + type Error = anyhow::Error; + + fn try_from(val: u16) -> Result { + if let Ok(tag) = ExprTag::try_from(val) { + Ok(Tag::Expr(tag)) + } else if let Ok(tag) = ContTag::try_from(val) { + Ok(Tag::Cont(tag)) + } else if let Ok(tag) = Op1::try_from(val) { + Ok(Tag::Op1(tag)) + } else if let Ok(tag) = Op2::try_from(val) { + Ok(Tag::Op2(tag)) + } else { + bail!("Invalid u16 for Tag: {val}") + } + } +} + +impl From for u16 { + fn from(val: Tag) -> Self { + match val { + Tag::Expr(tag) => tag.into(), + Tag::Cont(tag) => tag.into(), + Tag::Op1(tag) => tag.into(), + Tag::Op2(tag) => tag.into(), + } + } +} + +impl TagTrait for Tag { + fn from_field(f: &F) -> Option { + Self::try_from(f.to_u16()?).ok() + } + + fn to_field(&self) -> F { + Tag::to_field(self) + } +} + +impl Tag { + #[inline] + pub fn to_field(&self) -> F { + match self { + Self::Expr(tag) => tag.to_field(), + Self::Cont(tag) => tag.to_field(), + Self::Op1(tag) => tag.to_field(), + Self::Op2(tag) => tag.to_field(), + } + } + + pub fn pos(i: usize) -> Option { + let mut last = 0; + if (last..last + ExprTag::COUNT).contains(&i) { + let j = i + EXPR_TAG_INIT as usize - last; + let expr_tag = (j as u16).try_into().expect("unreachable"); + return Some(Tag::Expr(expr_tag)); + } + last += ExprTag::COUNT; + if (last..last + ContTag::COUNT).contains(&i) { + let j = i + CONT_TAG_INIT as usize - last; + let cont_tag = (j as u16).try_into().expect("unreachable"); + return Some(Tag::Cont(cont_tag)); + } + last += ContTag::COUNT; + if (last..last + Op1::COUNT).contains(&i) { + let j = i + OP1_TAG_INIT as usize - last; + let op1_tag = (j as u16).try_into().expect("unreachable"); + return Some(Tag::Op1(op1_tag)); + } + last += Op1::COUNT; + if (last..last + Op2::COUNT).contains(&i) { + let j = i + OP2_TAG_INIT as usize - last; + let op2_tag = (j as u16).try_into().expect("unreachable"); + return Some(Tag::Op2(op2_tag)); + } + None + } + + pub fn index(&self) -> usize { + match self { + Self::Expr(tag) => *tag as usize - EXPR_TAG_INIT as usize, + Self::Cont(tag) => *tag as usize - CONT_TAG_INIT as usize + ExprTag::COUNT, + Self::Op1(tag) => { + *tag as usize - OP1_TAG_INIT as usize + ExprTag::COUNT + ContTag::COUNT + } + Self::Op2(tag) => { + *tag as usize - OP2_TAG_INIT as usize + ExprTag::COUNT + ContTag::COUNT + Op1::COUNT + } + } + } +} + +impl std::fmt::Display for Tag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use Tag::{Cont, Expr, Op1, Op2}; + match self { + Expr(tag) => write!(f, "expr.{}", tag), + Cont(tag) => write!(f, "cont.{}", tag), + Op1(tag) => write!(f, "op1.{}", tag), + Op2(tag) => write!(f, "op2.{}", tag), + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use strum::IntoEnumIterator; + + #[test] + fn pos_index_roundtrip() { + for i in 0.. { + if let Some(tag) = Tag::pos(i) { + let j = tag.index(); + assert_eq!(i, j); + } else { + break; + } + } + + for expr_tag in ExprTag::iter() { + let tag = Tag::Expr(expr_tag); + let tag_2 = Tag::pos(tag.index()).unwrap(); + assert_eq!(tag, tag_2); + } + + for cont_tag in ContTag::iter() { + let tag = Tag::Cont(cont_tag); + let tag_2 = Tag::pos(tag.index()).unwrap(); + assert_eq!(tag, tag_2); + } + + for op1_tag in Op1::iter() { + let tag = Tag::Op1(op1_tag); + let tag_2 = Tag::pos(tag.index()).unwrap(); + assert_eq!(tag, tag_2); + } + + for op2_tag in Op2::iter() { + let tag = Tag::Op2(op2_tag); + let tag_2 = Tag::pos(tag.index()).unwrap(); + assert_eq!(tag, tag_2); + } + } +} diff --git a/src/lem/tests/nivc_steps.rs b/src/lem/tests/nivc_steps.rs index 64b3e11ba4..c1843d96c1 100644 --- a/src/lem/tests/nivc_steps.rs +++ b/src/lem/tests/nivc_steps.rs @@ -5,7 +5,7 @@ use crate::{ eval::lang::Lang, lem::{ eval::{evaluate, make_cprocs_funcs_from_lang, make_eval_step_from_config, EvalConfig}, - store::Store, + store::{expect_ptrs, intern_ptrs, Store}, Tag, }, state::user_sym, @@ -78,12 +78,13 @@ fn test_nivc_steps() { let expr = cproc_input.pop().unwrap(); let idx = expr.get_index2().unwrap(); - let (_, args) = store.expect_2_ptrs(idx); + let [_, args] = expect_ptrs!(store, 2, idx); let new_name = user_sym("cproc-dumb-not"); - let new_expr = store.intern_2_ptrs( + let new_expr = intern_ptrs!( + store, Tag::Expr(ExprTag::Cproc), store.intern_symbol(&new_name), - *args, + args ); // `cproc` can't reduce the altered cproc input (with the wrong name) diff --git a/src/proof/tests/nova_tests_lem.rs b/src/proof/tests/nova_tests_lem.rs index 8867f589fd..74499a580a 100644 --- a/src/proof/tests/nova_tests_lem.rs +++ b/src/proof/tests/nova_tests_lem.rs @@ -4,7 +4,10 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use crate::{ eval::lang::{Coproc, Lang}, - lem::{store::Store, Tag}, + lem::{ + store::{intern_ptrs, Store}, + tag::Tag, + }, num::Num, proof::nova::C1LEM, state::user_sym, @@ -3786,7 +3789,7 @@ fn test_prove_call_literal_fun() { let empty_env = s.intern_nil(); let args = s.list(vec![s.intern_user_symbol("x")]); let body = s.read_with_default_state("(+ x 1)").unwrap(); - let fun = s.intern_4_ptrs(Tag::Expr(ExprTag::Fun), args, body, empty_env, s.dummy()); + let fun = intern_ptrs!(s, Tag::Expr(ExprTag::Fun), args, body, empty_env, s.dummy()); let input = s.num_u64(9); let expr = s.list(vec![fun, input]); let res = s.num_u64(10); diff --git a/src/tag.rs b/src/tag.rs index cb2a8c2686..af2bc41673 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -3,6 +3,7 @@ use lurk_macros::TryFromRepr; use proptest_derive::Arbitrary; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::{convert::TryFrom, fmt}; +use strum::{EnumCount, EnumIter}; use crate::field::LurkField; @@ -20,14 +21,25 @@ pub trait Tag: } } +pub(crate) const EXPR_TAG_INIT: u16 = 0b0000_0000_0000_0000; /// A tag for expressions. Note that ExprTag, ContTag, Op1, Op2 all live in the same u16 namespace #[derive( - Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr, TryFromRepr, + Debug, + Copy, + Clone, + PartialEq, + Eq, + Hash, + Serialize_repr, + Deserialize_repr, + TryFromRepr, + EnumCount, + EnumIter, )] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum ExprTag { - Nil = 0b0000_0000_0000_0000, + Nil = EXPR_TAG_INIT, Cons, Sym, Fun, @@ -89,14 +101,25 @@ impl Tag for ExprTag { } } +pub(crate) const CONT_TAG_INIT: u16 = 0b0001_0000_0000_0000; /// A tag for continuations. Note that ExprTag, ContTag, Op1, Op2 all live in the same u16 namespace #[derive( - Serialize_repr, Deserialize_repr, Debug, Copy, Clone, PartialEq, Eq, Hash, TryFromRepr, + Serialize_repr, + Deserialize_repr, + Debug, + Copy, + Clone, + PartialEq, + Eq, + Hash, + TryFromRepr, + EnumCount, + EnumIter, )] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum ContTag { - Outermost = 0b0001_0000_0000_0000, + Outermost = CONT_TAG_INIT, Call0, Call, Call2, @@ -168,6 +191,7 @@ impl fmt::Display for ContTag { } } +pub(crate) const OP1_TAG_INIT: u16 = 0b0010_0000_0000_0000; #[derive( Copy, Clone, @@ -179,11 +203,13 @@ impl fmt::Display for ContTag { Serialize_repr, Deserialize_repr, TryFromRepr, + EnumCount, + EnumIter, )] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum Op1 { - Car = 0b0010_0000_0000_0000, + Car = OP1_TAG_INIT, Cdr, Atom, Emit, @@ -297,6 +323,7 @@ impl fmt::Display for Op1 { } } +pub(crate) const OP2_TAG_INIT: u16 = 0b0011_0000_0000_0000; #[derive( Copy, Clone, @@ -308,11 +335,13 @@ impl fmt::Display for Op1 { Serialize_repr, Deserialize_repr, TryFromRepr, + EnumCount, + EnumIter, )] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum Op2 { - Sum = 0b0011_0000_0000_0000, + Sum = OP2_TAG_INIT, Diff, Product, Quotient,