mirror of https://github.com/zcash/halo2.git
commit
236115917d
|
@ -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<usize> for RegionStart {
|
||||
|
|
|
@ -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<F: FieldExt>: fmt::Debug {
|
||||
/// Enables a selector at the given offset.
|
||||
fn enable_selector<'v>(
|
||||
|
@ -75,100 +84,9 @@ pub trait RegionLayouter<F: FieldExt>: fmt::Debug {
|
|||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// A [`Layouter`] for a single-chip circuit.
|
||||
pub struct SingleChipLayouter<'a, F: FieldExt, CS: Assignment<F> + 'a> {
|
||||
cs: &'a mut CS,
|
||||
/// Stores the starting row for each region.
|
||||
regions: Vec<RegionStart>,
|
||||
/// Stores the first empty row for each column.
|
||||
columns: HashMap<Column<Any>, usize>,
|
||||
_marker: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<'a, F: FieldExt, CS: Assignment<F> + '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<F>> SingleChipLayouter<'a, F, CS> {
|
||||
/// Creates a new single-chip layouter.
|
||||
pub fn new(cs: &'a mut CS) -> Result<Self, Error> {
|
||||
let ret = SingleChipLayouter {
|
||||
cs,
|
||||
regions: vec![],
|
||||
columns: HashMap::default(),
|
||||
_marker: PhantomData,
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: FieldExt, CS: Assignment<F> + 'a> Layouter<F> for SingleChipLayouter<'a, F, CS> {
|
||||
type Root = Self;
|
||||
|
||||
fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
|
||||
where
|
||||
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
|
||||
N: Fn() -> NR,
|
||||
NR: Into<String>,
|
||||
{
|
||||
let region_index = self.regions.len();
|
||||
|
||||
// Get shape of the region.
|
||||
let mut shape = RegionShape::new(region_index.into());
|
||||
{
|
||||
let region: &mut dyn RegionLayouter<F> = &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<F> = &mut region;
|
||||
assignment(region.into())
|
||||
}?;
|
||||
self.cs.exit_region();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_root(&mut self) -> &mut Self::Root {
|
||||
self
|
||||
}
|
||||
|
||||
fn push_namespace<NR, N>(&mut self, name_fn: N)
|
||||
where
|
||||
NR: Into<String>,
|
||||
N: FnOnce() -> NR,
|
||||
{
|
||||
self.cs.push_namespace(name_fn)
|
||||
}
|
||||
|
||||
fn pop_namespace(&mut self, gadget_name: Option<String>) {
|
||||
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<Column<Any>>,
|
||||
|
@ -259,104 +177,3 @@ impl<F: FieldExt> RegionLayouter<F> for RegionShape {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleChipLayouterRegion<'r, 'a, F: FieldExt, CS: Assignment<F> + 'a> {
|
||||
layouter: &'r mut SingleChipLayouter<'a, F, CS>,
|
||||
region_index: RegionIndex,
|
||||
}
|
||||
|
||||
impl<'r, 'a, F: FieldExt, CS: Assignment<F> + '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<F> + '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<F> + 'a> RegionLayouter<F>
|
||||
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<Advice>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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<Fixed>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<F> + 'a> {
|
||||
cs: &'a mut CS,
|
||||
/// Stores the starting row for each region.
|
||||
regions: Vec<RegionStart>,
|
||||
/// Stores the first empty row for each column.
|
||||
columns: HashMap<Column<Any>, usize>,
|
||||
_marker: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<'a, F: FieldExt, CS: Assignment<F> + '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<F>> SingleChipLayouter<'a, F, CS> {
|
||||
/// Creates a new single-chip layouter.
|
||||
pub fn new(cs: &'a mut CS) -> Result<Self, Error> {
|
||||
let ret = SingleChipLayouter {
|
||||
cs,
|
||||
regions: vec![],
|
||||
columns: HashMap::default(),
|
||||
_marker: PhantomData,
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: FieldExt, CS: Assignment<F> + 'a> Layouter<F> for SingleChipLayouter<'a, F, CS> {
|
||||
type Root = Self;
|
||||
|
||||
fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
|
||||
where
|
||||
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
|
||||
N: Fn() -> NR,
|
||||
NR: Into<String>,
|
||||
{
|
||||
let region_index = self.regions.len();
|
||||
|
||||
// Get shape of the region.
|
||||
let mut shape = RegionShape::new(region_index.into());
|
||||
{
|
||||
let region: &mut dyn RegionLayouter<F> = &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<F> = &mut region;
|
||||
assignment(region.into())
|
||||
}?;
|
||||
self.cs.exit_region();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_root(&mut self) -> &mut Self::Root {
|
||||
self
|
||||
}
|
||||
|
||||
fn push_namespace<NR, N>(&mut self, name_fn: N)
|
||||
where
|
||||
NR: Into<String>,
|
||||
N: FnOnce() -> NR,
|
||||
{
|
||||
self.cs.push_namespace(name_fn)
|
||||
}
|
||||
|
||||
fn pop_namespace(&mut self, gadget_name: Option<String>) {
|
||||
self.cs.pop_namespace(gadget_name)
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleChipLayouterRegion<'r, 'a, F: FieldExt, CS: Assignment<F> + 'a> {
|
||||
layouter: &'r mut SingleChipLayouter<'a, F, CS>,
|
||||
region_index: RegionIndex,
|
||||
}
|
||||
|
||||
impl<'r, 'a, F: FieldExt, CS: Assignment<F> + '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<F> + '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<F> + 'a> RegionLayouter<F>
|
||||
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<Advice>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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<Fixed>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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(())
|
||||
}
|
||||
}
|
|
@ -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<F> + 'a> {
|
||||
cs: &'a mut CS,
|
||||
/// Stores the starting row for each region.
|
||||
regions: Vec<RegionStart>,
|
||||
_marker: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<'a, F: FieldExt, CS: Assignment<F> + '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<F>> V1<'a, F, CS> {
|
||||
/// Creates a new v1 layouter.
|
||||
pub fn new(cs: &'a mut CS) -> Result<Self, Error> {
|
||||
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<S>(&mut self, mut synthesis: S) -> Result<(), Error>
|
||||
where
|
||||
S: FnMut(V1Pass<F, CS>) -> 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<F> + '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<F> + 'a>(Pass<'p, 'a, F, CS>);
|
||||
|
||||
impl<'p, 'a, F: FieldExt, CS: Assignment<F> + '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<F> + 'a> Layouter<F> for V1Pass<'p, 'a, F, CS> {
|
||||
type Root = Self;
|
||||
|
||||
fn assign_region<A, AR, N, NR>(&mut self, name: N, assignment: A) -> Result<AR, Error>
|
||||
where
|
||||
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
|
||||
N: Fn() -> NR,
|
||||
NR: Into<String>,
|
||||
{
|
||||
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<NR, N>(&mut self, name_fn: N)
|
||||
where
|
||||
NR: Into<String>,
|
||||
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<String>) {
|
||||
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<RegionShape>,
|
||||
}
|
||||
|
||||
impl MeasurementPass {
|
||||
fn new() -> Self {
|
||||
MeasurementPass { regions: vec![] }
|
||||
}
|
||||
|
||||
fn assign_region<F: FieldExt, A, AR>(&mut self, mut assignment: A) -> Result<AR, Error>
|
||||
where
|
||||
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
|
||||
{
|
||||
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<F> = &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<F> + '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<F> + 'a> AssignmentPass<'p, 'a, F, CS> {
|
||||
fn new(layouter: &'p mut V1<'a, F, CS>) -> Self {
|
||||
AssignmentPass {
|
||||
layouter,
|
||||
region_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
|
||||
where
|
||||
A: FnMut(Region<'_, F>) -> Result<AR, Error>,
|
||||
N: Fn() -> NR,
|
||||
NR: Into<String>,
|
||||
{
|
||||
// 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<F> = &mut region;
|
||||
assignment(region.into())
|
||||
}?;
|
||||
self.layouter.cs.exit_region();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
struct V1Region<'r, 'a, F: FieldExt, CS: Assignment<F> + 'a> {
|
||||
layouter: &'r mut V1<'a, F, CS>,
|
||||
region_index: RegionIndex,
|
||||
}
|
||||
|
||||
impl<'r, 'a, F: FieldExt, CS: Assignment<F> + '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<F> + '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<F> + 'a> RegionLayouter<F> 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<Advice>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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<Fixed>,
|
||||
offset: usize,
|
||||
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
|
||||
) -> Result<Cell, Error> {
|
||||
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(())
|
||||
}
|
||||
}
|
|
@ -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<cmp::Ordering> {
|
||||
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<usize>,
|
||||
}
|
||||
|
||||
/// 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<AllocatedRegion>);
|
||||
|
||||
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<usize>,
|
||||
) -> impl Iterator<Item = EmptySpace> + '_ {
|
||||
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<Column<Any>, Allocations>,
|
||||
region_columns: &[Column<Any>],
|
||||
region_length: usize,
|
||||
start: usize,
|
||||
slack: Option<usize>,
|
||||
) -> Option<usize> {
|
||||
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<RegionShape>) -> Vec<(RegionStart, RegionShape)> {
|
||||
// Tracks the empty regions for each column.
|
||||
let mut column_allocations: HashMap<Column<Any>, 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<RegionShape>) -> Vec<RegionStart> {
|
||||
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<_>>(),
|
||||
vec![0.into(), 0.into(), 15.into()]
|
||||
);
|
||||
}
|
|
@ -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<Any>
|
||||
{
|
||||
}
|
||||
|
||||
/// A column with an index and type
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
|
@ -34,6 +37,32 @@ impl<C: ColumnType> Column<C> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<C: ColumnType> Ord for Column<C> {
|
||||
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<C: ColumnType> PartialOrd for Column<C> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
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<Advice> for Any {
|
||||
fn from(_: Advice) -> Any {
|
||||
Any::Advice
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Fixed> for Any {
|
||||
fn from(_: Fixed) -> Any {
|
||||
Any::Fixed
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Instance> for Any {
|
||||
fn from(_: Instance) -> Any {
|
||||
Any::Instance
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Column<Advice>> for Column<Any> {
|
||||
fn from(advice: Column<Advice>) -> Column<Any> {
|
||||
Column {
|
||||
|
|
Loading…
Reference in New Issue