diff --git a/src/circuit.rs b/src/circuit.rs index ce50c82e..e77a0a37 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -59,7 +59,7 @@ impl std::ops::Deref for RegionIndex { } /// Starting row of a region in a layouter -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RegionStart(usize); impl From for RegionStart { diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index 563883a3..bbff5437 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -1,13 +1,20 @@ //! Implementations of common circuit layouters. use std::cmp; -use std::collections::{HashMap, HashSet}; +use std::collections::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}; +use super::{Cell, RegionIndex}; +use crate::{ + arithmetic::FieldExt, + plonk::{Advice, Any, Column, Error, Fixed, Permutation, Selector}, +}; + +mod single_pass; +pub use single_pass::SingleChipLayouter; + +mod v1; +pub use v1::{V1Pass, V1}; /// Helper trait for implementing a custom [`Layouter`]. /// @@ -37,6 +44,8 @@ use crate::plonk::{Advice, Any, Assignment, Column, Error, Fixed, Permutation, S /// 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`). +/// +/// [`Layouter`]: super::Layouter pub trait RegionLayouter: fmt::Debug { /// Enables a selector at the given offset. fn enable_selector<'v>( @@ -75,100 +84,9 @@ pub trait RegionLayouter: fmt::Debug { ) -> 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)] +#[derive(Clone, Debug)] pub struct RegionShape { region_index: RegionIndex, columns: HashSet>, @@ -259,104 +177,3 @@ impl RegionLayouter for RegionShape { 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(()) - } -} diff --git a/src/circuit/layouter/single_pass.rs b/src/circuit/layouter/single_pass.rs new file mode 100644 index 00000000..ac590cd5 --- /dev/null +++ b/src/circuit/layouter/single_pass.rs @@ -0,0 +1,203 @@ +use std::cmp; +use std::collections::HashMap; +use std::fmt; +use std::marker::PhantomData; + +use super::{RegionLayouter, RegionShape}; +use crate::{ + arithmetic::FieldExt, + circuit::{Cell, Layouter, Region, RegionIndex, RegionStart}, + plonk::{Advice, Any, Assignment, Column, Error, Fixed, Permutation, Selector}, +}; + +/// 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) + } +} + +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(()) + } +} diff --git a/src/circuit/layouter/v1.rs b/src/circuit/layouter/v1.rs new file mode 100644 index 00000000..b628a987 --- /dev/null +++ b/src/circuit/layouter/v1.rs @@ -0,0 +1,287 @@ +use std::fmt; +use std::marker::PhantomData; + +use super::{RegionLayouter, RegionShape}; +use crate::{ + arithmetic::FieldExt, + circuit::{Cell, Layouter, Region, RegionIndex, RegionStart}, + plonk::{Advice, Assignment, Column, Error, Fixed, Permutation, Selector}, +}; + +mod strategy; + +/// The version 1 [`Layouter`] provided by `halo2`. +/// +/// It is a dual-pass layouter, that has visibility into the entire `Circuit::synthesize` +/// step. +pub struct V1<'a, F: FieldExt, CS: Assignment + 'a> { + cs: &'a mut CS, + /// Stores the starting row for each region. + regions: Vec, + _marker: PhantomData, +} + +impl<'a, F: FieldExt, CS: Assignment + 'a> fmt::Debug for V1<'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("layouter::V1").finish() + } +} + +impl<'a, F: FieldExt, CS: Assignment> V1<'a, F, CS> { + /// Creates a new v1 layouter. + pub fn new(cs: &'a mut CS) -> Result { + let ret = V1 { + cs, + regions: vec![], + _marker: PhantomData, + }; + Ok(ret) + } + + /// Runs the layouter to synthesize the circuit. + /// + /// Even though `synthesis` has `FnMut` bounds, any value-assignment closures + /// contained within it are guaranteed to be called at most once. + pub fn run(&mut self, mut synthesis: S) -> Result<(), Error> + where + S: FnMut(V1Pass) -> Result<(), Error>, + { + // First pass: measure the regions within the circuit. + let mut measure = MeasurementPass::new(); + { + let pass = &mut measure; + synthesis(V1Pass::measure(pass))?; + } + + self.regions = strategy::slot_in_biggest_advice_first(measure.regions); + + // Second pass: assign the regions. + let mut assign = AssignmentPass::new(self); + { + let pass = &mut assign; + synthesis(V1Pass::assign(pass))?; + } + + Ok(()) + } +} + +#[derive(Debug)] +enum Pass<'p, 'a, F: FieldExt, CS: Assignment + 'a> { + Measurement(&'p mut MeasurementPass), + Assignment(&'p mut AssignmentPass<'p, 'a, F, CS>), +} + +/// A single pass of the [`V1`] layouter. +#[derive(Debug)] +pub struct V1Pass<'p, 'a, F: FieldExt, CS: Assignment + 'a>(Pass<'p, 'a, F, CS>); + +impl<'p, 'a, F: FieldExt, CS: Assignment + 'a> V1Pass<'p, 'a, F, CS> { + fn measure(pass: &'p mut MeasurementPass) -> Self { + V1Pass(Pass::Measurement(pass)) + } + + fn assign(pass: &'p mut AssignmentPass<'p, 'a, F, CS>) -> Self { + V1Pass(Pass::Assignment(pass)) + } +} + +impl<'p, 'a, F: FieldExt, CS: Assignment + 'a> Layouter for V1Pass<'p, 'a, F, CS> { + type Root = Self; + + fn assign_region(&mut self, name: N, assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, + { + match &mut self.0 { + Pass::Measurement(pass) => pass.assign_region(assignment), + Pass::Assignment(pass) => pass.assign_region(name, assignment), + } + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + if let Pass::Assignment(pass) = &mut self.0 { + pass.layouter.cs.push_namespace(name_fn); + } + } + + fn pop_namespace(&mut self, gadget_name: Option) { + if let Pass::Assignment(pass) = &mut self.0 { + pass.layouter.cs.pop_namespace(gadget_name); + } + } +} + +/// Measures the circuit. +#[derive(Debug)] +pub struct MeasurementPass { + regions: Vec, +} + +impl MeasurementPass { + fn new() -> Self { + MeasurementPass { regions: vec![] } + } + + fn assign_region(&mut self, mut assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + { + let region_index = self.regions.len(); + + // Get shape of the region. + let mut shape = RegionShape::new(region_index.into()); + let result = { + let region: &mut dyn RegionLayouter = &mut shape; + assignment(region.into()) + }?; + self.regions.push(shape); + + Ok(result) + } +} + +/// Assigns the circuit. +#[derive(Debug)] +pub struct AssignmentPass<'p, 'a, F: FieldExt, CS: Assignment + 'a> { + layouter: &'p mut V1<'a, F, CS>, + /// Counter tracking which region we need to assign next. + region_index: usize, +} + +impl<'p, 'a, F: FieldExt, CS: Assignment + 'a> AssignmentPass<'p, 'a, F, CS> { + fn new(layouter: &'p mut V1<'a, F, CS>) -> Self { + AssignmentPass { + layouter, + region_index: 0, + } + } + + fn assign_region(&mut self, name: N, mut assignment: A) -> Result + where + A: FnMut(Region<'_, F>) -> Result, + N: Fn() -> NR, + NR: Into, + { + // Get the next region we are assigning. + let region_index = self.region_index; + self.region_index += 1; + + self.layouter.cs.enter_region(name); + let mut region = V1Region::new(self.layouter, region_index.into()); + let result = { + let region: &mut dyn RegionLayouter = &mut region; + assignment(region.into()) + }?; + self.layouter.cs.exit_region(); + + Ok(result) + } +} + +struct V1Region<'r, 'a, F: FieldExt, CS: Assignment + 'a> { + layouter: &'r mut V1<'a, F, CS>, + region_index: RegionIndex, +} + +impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> fmt::Debug for V1Region<'r, 'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("V1Region") + .field("layouter", &self.layouter) + .field("region_index", &self.region_index) + .finish() + } +} + +impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> V1Region<'r, 'a, F, CS> { + fn new(layouter: &'r mut V1<'a, F, CS>, region_index: RegionIndex) -> Self { + V1Region { + layouter, + region_index, + } + } +} + +impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> RegionLayouter for V1Region<'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(()) + } +} diff --git a/src/circuit/layouter/v1/strategy.rs b/src/circuit/layouter/v1/strategy.rs new file mode 100644 index 00000000..753e86e5 --- /dev/null +++ b/src/circuit/layouter/v1/strategy.rs @@ -0,0 +1,229 @@ +use std::{ + cmp, + collections::{BTreeSet, HashMap}, +}; + +use super::RegionShape; +use crate::{ + circuit::RegionStart, + plonk::{Any, Column}, +}; + +/// A region allocated within a column. +#[derive(Clone, Default, Debug, PartialEq, Eq)] +struct AllocatedRegion { + // The starting position of the region. + start: usize, + // The length of the region. + length: usize, +} + +impl Ord for AllocatedRegion { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.start.cmp(&other.start) + } +} + +impl PartialOrd for AllocatedRegion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// An area of empty space within a column. +struct EmptySpace { + // The starting position of the empty space. + start: usize, + // The number of rows of empty space, or `None` if unbounded. + end: Option, +} + +/// Allocated rows within a column. +/// +/// This is a set of [a_start, a_end) pairs representing disjoint allocated intervals. +#[derive(Clone, Default, Debug)] +struct Allocations(BTreeSet); + +impl Allocations { + /// Return all the *unallocated* nonempty intervals intersecting [start, end). + /// + /// `end = None` represents an unbounded end. + fn free_intervals( + &self, + start: usize, + end: Option, + ) -> impl Iterator + '_ { + self.0 + .iter() + .map(Some) + .chain(Some(None)) + .scan(start, move |row, region| { + Some(if let Some(region) = region { + if end.map(|end| region.start >= end).unwrap_or(false) { + None + } else { + let ret = if *row < region.start { + Some(EmptySpace { + start: *row, + end: Some(region.start), + }) + } else { + None + }; + + *row = cmp::max(*row, region.start + region.length); + + ret + } + } else if end.map(|end| *row < end).unwrap_or(true) { + Some(EmptySpace { start: *row, end }) + } else { + None + }) + }) + .flatten() + } +} + +/// - `start` is the current start row of the region (not of this column). +/// - `slack` is the maximum number of rows the start could be moved down, taking into +/// account prior columns. +fn first_fit_region( + column_allocations: &mut HashMap, Allocations>, + region_columns: &[Column], + region_length: usize, + start: usize, + slack: Option, +) -> Option { + let (c, remaining_columns) = match region_columns.split_first() { + Some(cols) => cols, + None => return Some(start), + }; + let end = slack.map(|slack| start + region_length + slack); + + // Iterate over the unallocated non-empty intervals in c that intersect [start, end). + for space in column_allocations + .entry(*c) + .or_default() + .clone() + .free_intervals(start, end) + { + // Do we have enough room for this column of the region in this interval? + let s_slack = space + .end + .map(|end| (end as isize - space.start as isize) - region_length as isize); + if let Some((slack, s_slack)) = slack.zip(s_slack) { + assert!(s_slack <= slack as isize); + } + if s_slack.unwrap_or(0) >= 0 { + let row = first_fit_region( + column_allocations, + remaining_columns, + region_length, + space.start, + s_slack.map(|s| s as usize), + ); + if let Some(row) = row { + if let Some(end) = end { + assert!(row + region_length <= end); + } + column_allocations + .get_mut(c) + .unwrap() + .0 + .insert(AllocatedRegion { + start: row, + length: region_length, + }); + return Some(row); + } + } + } + + // No placement worked; the caller will need to try other possibilities. + None +} + +/// Positions the regions starting at the earliest row for which none of the columns are +/// in use, taking into account gaps between earlier regions. +fn slot_in(region_shapes: Vec) -> Vec<(RegionStart, RegionShape)> { + // Tracks the empty regions for each column. + let mut column_allocations: HashMap, Allocations> = Default::default(); + + region_shapes + .into_iter() + .map(|region| { + // Sort the region's columns to ensure determinism. + // - An unstable sort is fine, because region.columns() returns a set. + // - The sort order relies on Column's Ord implementation! + let mut region_columns: Vec<_> = region.columns().iter().cloned().collect(); + region_columns.sort_unstable(); + + let region_start = first_fit_region( + &mut column_allocations, + ®ion_columns, + region.row_count(), + 0, + None, + ) + .expect("We can always fit a region somewhere"); + + (region_start.into(), region) + }) + .collect() +} + +/// Sorts the regions by advice area and then lays them out with the [`slot_in`] strategy. +pub fn slot_in_biggest_advice_first(region_shapes: Vec) -> Vec { + let mut sorted_regions: Vec<_> = region_shapes.into_iter().collect(); + sorted_regions.sort_unstable_by_key(|shape| { + // Count the number of advice columns + let advice_cols = shape + .columns() + .iter() + .filter(|c| matches!(c.column_type(), Any::Advice)) + .count(); + // Sort by advice area (since this has the most contention). + advice_cols * shape.row_count() + }); + sorted_regions.reverse(); + + // Lay out the sorted regions. + let mut regions = slot_in(sorted_regions); + + // Un-sort the regions so they match the original indexing. + regions.sort_unstable_by_key(|(_, region)| region.region_index().0); + regions.into_iter().map(|(start, _)| start).collect() +} + +#[test] +fn test_slot_in() { + let regions = vec![ + RegionShape { + region_index: 0.into(), + columns: vec![Column::new(0, Any::Advice), Column::new(1, Any::Advice)] + .into_iter() + .collect(), + row_count: 15, + }, + RegionShape { + region_index: 1.into(), + columns: vec![Column::new(2, Any::Advice)].into_iter().collect(), + row_count: 10, + }, + RegionShape { + region_index: 2.into(), + columns: vec![Column::new(2, Any::Advice), Column::new(0, Any::Advice)] + .into_iter() + .collect(), + row_count: 10, + }, + ]; + assert_eq!( + slot_in(regions) + .into_iter() + .map(|(i, _)| i) + .collect::>(), + vec![0.into(), 0.into(), 15.into()] + ); +} diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 1b08e431..5c1aeef8 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -10,7 +10,10 @@ use super::{lookup, permutation, Error}; use crate::{arithmetic::FieldExt, circuit::Region, poly::Rotation}; /// A column type -pub trait ColumnType: 'static + Sized + std::fmt::Debug {} +pub trait ColumnType: + 'static + Sized + Copy + std::fmt::Debug + PartialEq + Eq + Into +{ +} /// A column with an index and type #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] @@ -34,6 +37,32 @@ impl Column { } } +impl Ord for Column { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // This ordering is consensus-critical! The layouters rely on deterministic column + // orderings. + match (self.column_type.into(), other.column_type.into()) { + // Indices are assigned within column types. + (Any::Advice, Any::Advice) + | (Any::Instance, Any::Instance) + | (Any::Fixed, Any::Fixed) => self.index.cmp(&other.index), + // Across column types, sort Advice < Instance < Fixed. + (Any::Advice, Any::Instance) + | (Any::Advice, Any::Fixed) + | (Any::Instance, Any::Fixed) => std::cmp::Ordering::Less, + (Any::Fixed, Any::Instance) + | (Any::Fixed, Any::Advice) + | (Any::Instance, Any::Advice) => std::cmp::Ordering::Greater, + } + } +} + +impl PartialOrd for Column { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + /// An advice column #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct Advice; @@ -62,6 +91,24 @@ impl ColumnType for Fixed {} impl ColumnType for Instance {} impl ColumnType for Any {} +impl From for Any { + fn from(_: Advice) -> Any { + Any::Advice + } +} + +impl From for Any { + fn from(_: Fixed) -> Any { + Any::Fixed + } +} + +impl From for Any { + fn from(_: Instance) -> Any { + Any::Instance + } +} + impl From> for Column { fn from(advice: Column) -> Column { Column {