//! Implementations of common circuit layouters. use std::cmp; use std::collections::{HashMap, HashSet}; use std::fmt; use std::marker::PhantomData; use super::{Cell, Layouter, Region, RegionIndex, RegionStart}; use crate::arithmetic::FieldExt; use crate::plonk::{Advice, Any, Assignment, Column, Error, Fixed, Permutation, Selector}; /// Helper trait for implementing a custom [`Layouter`]. /// /// This trait is used for implementing region assignments: /// /// ```ignore /// impl<'a, F: FieldExt, C: Chip, CS: Assignment + 'a> Layouter for MyLayouter<'a, C, CS> { /// fn assign_region( /// &mut self, /// assignment: impl FnOnce(Region<'_, F, C>) -> Result<(), Error>, /// ) -> Result<(), Error> { /// let region_index = self.regions.len(); /// self.regions.push(self.current_gate); /// /// let mut region = MyRegion::new(self, region_index); /// { /// let region: &mut dyn RegionLayouter = &mut region; /// assignment(region.into())?; /// } /// self.current_gate += region.row_count; /// /// Ok(()) /// } /// } /// ``` /// /// TODO: It would be great if we could constrain the columns in these types to be /// "logical" columns that are guaranteed to correspond to the chip (and have come from /// `Chip::Config`). pub trait RegionLayouter: fmt::Debug { /// Enables a selector at the given offset. fn enable_selector<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), selector: &Selector, offset: usize, ) -> Result<(), Error>; /// Assign an advice column value (witness) fn assign_advice<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, to: &'v mut (dyn FnMut() -> Result + 'v), ) -> Result; /// Assign a fixed value fn assign_fixed<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, to: &'v mut (dyn FnMut() -> Result + 'v), ) -> Result; /// Constraint two cells to have the same value. /// /// Returns an error if either of the cells is not within the given permutation. fn constrain_equal( &mut self, permutation: &Permutation, left: Cell, right: Cell, ) -> Result<(), Error>; } /// A [`Layouter`] for a single-chip circuit. pub struct SingleChipLayouter<'a, F: FieldExt, CS: Assignment + 'a> { cs: &'a mut CS, /// Stores the starting row for each region. regions: Vec, /// Stores the first empty row for each column. columns: HashMap, usize>, _marker: PhantomData, } impl<'a, F: FieldExt, CS: Assignment + 'a> fmt::Debug for SingleChipLayouter<'a, F, CS> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SingleChipLayouter") .field("regions", &self.regions) .field("columns", &self.columns) .finish() } } impl<'a, F: FieldExt, CS: Assignment> SingleChipLayouter<'a, F, CS> { /// Creates a new single-chip layouter. pub fn new(cs: &'a mut CS) -> Result { let ret = SingleChipLayouter { cs, regions: vec![], columns: HashMap::default(), _marker: PhantomData, }; Ok(ret) } } impl<'a, F: FieldExt, CS: Assignment + 'a> Layouter for SingleChipLayouter<'a, F, CS> { type Root = Self; fn assign_region(&mut self, name: N, mut assignment: A) -> Result where A: FnMut(Region<'_, F>) -> Result, N: Fn() -> NR, NR: Into, { let region_index = self.regions.len(); // Get shape of the region. let mut shape = RegionShape::new(region_index.into()); { let region: &mut dyn RegionLayouter = &mut shape; assignment(region.into())?; } // Lay out this region. We implement the simplest approach here: position the // region starting at the earliest row for which none of the columns are in use. let mut region_start = 0; for column in &shape.columns { region_start = cmp::max(region_start, self.columns.get(column).cloned().unwrap_or(0)); } self.regions.push(region_start.into()); // Update column usage information. for column in shape.columns { self.columns.insert(column, region_start + shape.row_count); } self.cs.enter_region(name); let mut region = SingleChipLayouterRegion::new(self, region_index.into()); let result = { let region: &mut dyn RegionLayouter = &mut region; assignment(region.into()) }?; self.cs.exit_region(); Ok(result) } 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 /// the set of columns it uses as well as the number of rows it uses. #[derive(Debug)] pub struct RegionShape { region_index: RegionIndex, columns: HashSet>, row_count: usize, } impl RegionShape { /// Create a new `RegionShape` for a region at `region_index`. pub fn new(region_index: RegionIndex) -> Self { RegionShape { region_index, columns: HashSet::default(), row_count: 0, } } /// Get the `region_index` of a `RegionShape`. pub fn region_index(&self) -> RegionIndex { self.region_index } /// Get a reference to the set of `columns` used in a `RegionShape`. pub fn columns(&self) -> &HashSet> { &self.columns } /// Get the `row_count` of a `RegionShape`. pub fn row_count(&self) -> usize { self.row_count } } impl RegionLayouter for RegionShape { fn enable_selector<'v>( &'v mut self, _: &'v (dyn Fn() -> String + 'v), selector: &Selector, offset: usize, ) -> Result<(), Error> { // Track the selector's fixed column as part of the region's shape. // TODO: Avoid exposing selector internals? self.columns.insert(selector.0.into()); self.row_count = cmp::max(self.row_count, offset + 1); Ok(()) } fn assign_advice<'v>( &'v mut self, _: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, _to: &'v mut (dyn FnMut() -> Result + 'v), ) -> Result { self.columns.insert(column.into()); self.row_count = cmp::max(self.row_count, offset + 1); Ok(Cell { region_index: self.region_index, row_offset: offset, column: column.into(), }) } fn assign_fixed<'v>( &'v mut self, _: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, _to: &'v mut (dyn FnMut() -> Result + 'v), ) -> Result { self.columns.insert(column.into()); self.row_count = cmp::max(self.row_count, offset + 1); Ok(Cell { region_index: self.region_index, row_offset: offset, column: column.into(), }) } fn constrain_equal( &mut self, _permutation: &Permutation, _left: Cell, _right: Cell, ) -> Result<(), Error> { // Equality constraints don't affect the region shape. Ok(()) } } struct SingleChipLayouterRegion<'r, 'a, F: FieldExt, CS: Assignment + 'a> { layouter: &'r mut SingleChipLayouter<'a, F, CS>, region_index: RegionIndex, } impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> fmt::Debug for SingleChipLayouterRegion<'r, 'a, F, CS> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SingleChipLayouterRegion") .field("layouter", &self.layouter) .field("region_index", &self.region_index) .finish() } } impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> SingleChipLayouterRegion<'r, 'a, F, CS> { fn new(layouter: &'r mut SingleChipLayouter<'a, F, CS>, region_index: RegionIndex) -> Self { SingleChipLayouterRegion { layouter, region_index, } } } impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> RegionLayouter for SingleChipLayouterRegion<'r, 'a, F, CS> { fn enable_selector<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), selector: &Selector, offset: usize, ) -> Result<(), Error> { self.layouter.cs.enable_selector( annotation, selector, *self.layouter.regions[*self.region_index] + offset, ) } fn assign_advice<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, to: &'v mut (dyn FnMut() -> Result + 'v), ) -> Result { self.layouter.cs.assign_advice( annotation, column, *self.layouter.regions[*self.region_index] + offset, to, )?; Ok(Cell { region_index: self.region_index, row_offset: offset, column: column.into(), }) } fn assign_fixed<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, to: &'v mut (dyn FnMut() -> Result + 'v), ) -> Result { self.layouter.cs.assign_fixed( annotation, column, *self.layouter.regions[*self.region_index] + offset, to, )?; Ok(Cell { region_index: self.region_index, row_offset: offset, column: column.into(), }) } fn constrain_equal( &mut self, permutation: &Permutation, left: Cell, right: Cell, ) -> Result<(), Error> { self.layouter.cs.copy( permutation, left.column, *self.layouter.regions[*left.region_index] + left.row_offset, right.column, *self.layouter.regions[*right.region_index] + right.row_offset, )?; Ok(()) } }