diff --git a/Cargo.toml b/Cargo.toml index 109666e..31fa9c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ name = "plonk" harness = false [dependencies] +backtrace = { version = "0.3", optional = true } bitvec = "0.18" subtle = "2.3" crossbeam-utils = "0.7" @@ -47,4 +48,5 @@ lazy_static = "1.4.0" static_assertions = "1.1.0" [features] +gadget-traces = ["backtrace"] sanity-checks = [] diff --git a/src/circuit.rs b/src/circuit.rs index 2df473d..562246f 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -1,6 +1,6 @@ //! Traits and structs for implementing circuit components. -use std::fmt; +use std::{fmt, marker::PhantomData}; use crate::{ arithmetic::FieldExt, @@ -136,6 +136,10 @@ impl<'r, C: Chip> Region<'r, C> { /// A particular concrete layout strategy will implement this trait for each chip it /// supports. pub trait Layouter { + /// Represents the type of the "root" of this layouter, so that nested namespaces + /// can minimize indirection. + type Root: Layouter; + /// Provides access to the chip configuration. fn config(&self) -> &C::Config; @@ -155,4 +159,104 @@ pub trait Layouter { A: FnMut(Region<'_, C>) -> Result<(), Error>, N: Fn() -> NR, NR: Into; + + /// Gets the "root" of this assignment, bypassing the namespacing. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + fn get_root(&mut self) -> &mut Self::Root; + + /// Creates a new (sub)namespace and enters into it. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR; + + /// Exits out of the existing namespace. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + fn pop_namespace(&mut self, gadget_name: Option); + + /// Enters into a namespace. + fn namespace(&mut self, name_fn: N) -> NamespacedLayouter<'_, C, Self::Root> + where + NR: Into, + N: FnOnce() -> NR, + { + self.get_root().push_namespace(name_fn); + + NamespacedLayouter(self.get_root(), PhantomData) + } +} + +/// This is a "namespaced" layouter which borrows a `Layouter` (pushing a namespace +/// context) and, when dropped, pops out of the namespace context. +#[derive(Debug)] +pub struct NamespacedLayouter<'a, C: Chip, L: Layouter + 'a>(&'a mut L, PhantomData); + +impl<'a, C: Chip, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, C, L> { + type Root = L::Root; + + fn config(&self) -> &C::Config { + self.0.config() + } + + fn assign_region(&mut self, name: N, assignment: A) -> Result<(), Error> + where + A: FnMut(Region<'_, C>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into, + { + self.0.assign_region(name, assignment) + } + + fn get_root(&mut self) -> &mut Self::Root { + self.0.get_root() + } + + fn push_namespace(&mut self, _name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + panic!("Only the root's push_namespace should be called"); + } + + fn pop_namespace(&mut self, _gadget_name: Option) { + panic!("Only the root's pop_namespace should be called"); + } +} + +impl<'a, C: Chip, L: Layouter + 'a> Drop for NamespacedLayouter<'a, C, L> { + fn drop(&mut self) { + let gadget_name = { + #[cfg(feature = "gadget-traces")] + { + let mut gadget_name = None; + let mut is_second_frame = false; + backtrace::trace(|frame| { + if is_second_frame { + // Resolve this instruction pointer to a symbol name. + backtrace::resolve_frame(frame, |symbol| { + gadget_name = symbol.name().map(|name| format!("{:#}", name)); + }); + + // We are done! + false + } else { + // We want the next frame. + is_second_frame = true; + true + } + }); + gadget_name + } + + #[cfg(not(feature = "gadget-traces"))] + None + }; + + self.get_root().pop_namespace(gadget_name); + } } diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index f1c4a0e..6f1a348 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -100,6 +100,8 @@ impl<'a, C: Chip, CS: Assignment> SingleChip<'a, C, CS> { } impl<'a, C: Chip, CS: Assignment + 'a> Layouter for SingleChip<'a, C, CS> { + type Root = Self; + fn config(&self) -> &C::Config { &self.config } @@ -142,6 +144,22 @@ impl<'a, C: Chip, CS: Assignment + 'a> Layouter for SingleChip<'a, Ok(()) } + + fn get_root(&mut self) -> &mut Self::Root { + self + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + self.cs.push_namespace(name_fn) + } + + fn pop_namespace(&mut self, gadget_name: Option) { + self.cs.pop_namespace(gadget_name) + } } /// The shape of a region. For a region at a certain index, we track diff --git a/src/dev.rs b/src/dev.rs index 18058f7..b28b594 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -213,6 +213,18 @@ impl Assignment for MockProver { self.permutations[permutation].copy(left_column, left_row, right_column, right_row) } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // TODO: Do something with namespaces :) + } + + fn pop_namespace(&mut self, _: Option) { + // TODO: Do something with namespaces :) + } } impl MockProver { diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 4616d9e..876cc00 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -182,6 +182,23 @@ pub trait Assignment { right_column: usize, right_row: usize, ) -> Result<(), Error>; + + /// Creates a new (sub)namespace and enters into it. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + /// + /// [`Layouter::namespace`]: crate::circuit::Layouter#method.namespace + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR; + + /// Exits out of the existing namespace. + /// + /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. + /// + /// [`Layouter::namespace`]: crate::circuit::Layouter#method.namespace + fn pop_namespace(&mut self, gadget_name: Option); } /// This is a trait that circuits provide implementations for so that the diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index 885a3a9..a949a5e 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -128,6 +128,18 @@ impl Assignment for Assembly { self.permutations[permutation].copy(left_column, left_row, right_column, right_row) } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about namespaces in this context. + } + + fn pop_namespace(&mut self, _: Option) { + // Do nothing; we don't care about namespaces in this context. + } } /// Generate a `VerifyingKey` from an instance of `Circuit`. diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index ed230a1..db3ccfb 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -162,6 +162,18 @@ pub fn create_proof, ConcreteCircuit: Circ Ok(()) } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about namespaces in this context. + } + + fn pop_namespace(&mut self, _: Option) { + // Do nothing; we don't care about namespaces in this context. + } } let mut witness = WitnessCollection {