From 6d0017f47c0119e0b6d6d671a38d5398a427b8ae Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 21 Jun 2021 19:10:59 +0100 Subject: [PATCH] Introduce a FloorPlanner trait and integrate it into the Circuit trait This is the beginning of the process to enable full floor planning capabilities in `halo2`. For now, all that a floor planner can do is synthesize a circuit, which makes it no more powerful than a layouter, but easier to use (as moving to a multi-pass layouter no longer requires changes to `Circuit::synthesize`). --- benches/plonk.rs | 11 +- examples/circuit-layout.rs | 15 ++- examples/simple-example.rs | 20 ++-- examples/two-chip.rs | 20 ++-- src/circuit.rs | 3 + src/circuit/floor_planner.rs | 6 + .../single_pass.rs | 31 ++++- src/circuit/{layouter => floor_planner}/v1.rs | 107 ++++++++++-------- .../v1/strategy.rs | 0 src/circuit/layouter.rs | 12 +- src/dev.rs | 51 +++++---- src/dev/graph.rs | 4 +- src/dev/graph/layout.rs | 4 +- src/plonk/circuit.rs | 29 ++++- src/plonk/keygen.rs | 8 +- src/plonk/prover.rs | 6 +- tests/plonk_api.rs | 17 ++- 17 files changed, 227 insertions(+), 117 deletions(-) create mode 100644 src/circuit/floor_planner.rs rename src/circuit/{layouter => floor_planner}/single_pass.rs (86%) rename src/circuit/{layouter => floor_planner}/v1.rs (69%) rename src/circuit/{layouter => floor_planner}/v1/strategy.rs (100%) diff --git a/benches/plonk.rs b/benches/plonk.rs index 281e45cb..c27a75d1 100644 --- a/benches/plonk.rs +++ b/benches/plonk.rs @@ -3,8 +3,7 @@ extern crate criterion; extern crate halo2; use halo2::arithmetic::FieldExt; -use halo2::circuit::layouter::SingleChipLayouter; -use halo2::circuit::{Cell, Layouter}; +use halo2::circuit::{Cell, Layouter, SimpleFloorPlanner}; use halo2::pasta::{EqAffine, Fp}; use halo2::plonk::*; use halo2::poly::{commitment::Params, Rotation}; @@ -174,6 +173,11 @@ fn bench_with_k(name: &str, k: u32, c: &mut Criterion) { impl Circuit for MyCircuit { type Config = PLONKConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { a: None, k: self.k } + } fn configure(meta: &mut ConstraintSystem) -> PLONKConfig { let a = meta.advice_column(); @@ -214,10 +218,9 @@ fn bench_with_k(name: &str, k: u32, c: &mut Criterion) { fn synthesize( &self, - cs: &mut impl Assignment, config: PLONKConfig, + mut layouter: impl Layouter, ) -> Result<(), Error> { - let mut layouter = SingleChipLayouter::new(cs)?; let cs = StandardPLONK::new(config); for _ in 0..(1 << (self.k - 1)) { diff --git a/examples/circuit-layout.rs b/examples/circuit-layout.rs index 822d07e3..d54fe0ce 100644 --- a/examples/circuit-layout.rs +++ b/examples/circuit-layout.rs @@ -1,9 +1,9 @@ use halo2::{ arithmetic::FieldExt, - circuit::{layouter::SingleChipLayouter, Cell, Layouter, Region}, + circuit::{Cell, Layouter, Region, SimpleFloorPlanner}, dev::CircuitLayout, pasta::Fp, - plonk::{Advice, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, Permutation}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Permutation}, poly::Rotation, }; use plotters::prelude::*; @@ -222,6 +222,14 @@ fn main() { impl Circuit for MyCircuit { type Config = PLONKConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { + a: None, + lookup_tables: self.lookup_tables.clone(), + } + } fn configure(meta: &mut ConstraintSystem) -> PLONKConfig { let e = meta.advice_column(); @@ -321,10 +329,9 @@ fn main() { fn synthesize( &self, - cs: &mut impl Assignment, config: PLONKConfig, + mut layouter: impl Layouter, ) -> Result<(), Error> { - let mut layouter = SingleChipLayouter::new(cs)?; let cs = StandardPLONK::new(config); let _ = cs.public_input(&mut layouter.namespace(|| "input"), || { diff --git a/examples/simple-example.rs b/examples/simple-example.rs index f8363359..ed3362fb 100644 --- a/examples/simple-example.rs +++ b/examples/simple-example.rs @@ -4,12 +4,9 @@ use std::marker::PhantomData; use halo2::{ arithmetic::FieldExt, - circuit::{layouter::SingleChipLayouter, Cell, Chip, Layouter, Region}, + circuit::{Cell, Chip, Layouter, Region, SimpleFloorPlanner}, dev::VerifyFailure, - plonk::{ - Advice, Assignment, Circuit, Column, ConstraintSystem, Error, Instance, Permutation, - Selector, - }, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Permutation, Selector}, poly::Rotation, }; @@ -282,6 +279,7 @@ impl NumericInstructions for FieldChip { /// In this struct we store the private input variables. We use `Option` because /// they won't have any value during key generation. During proving, if any of these /// were `None` we would get an error. +#[derive(Default)] struct MyCircuit { a: Option, b: Option, @@ -290,6 +288,11 @@ struct MyCircuit { impl Circuit for MyCircuit { // Since we are using a single chip for everything, we can just reuse its config. type Config = FieldConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } fn configure(meta: &mut ConstraintSystem) -> Self::Config { // We create the two advice columns that FieldChip uses for I/O. @@ -301,8 +304,11 @@ impl Circuit for MyCircuit { FieldChip::configure(meta, advice, instance) } - fn synthesize(&self, cs: &mut impl Assignment, config: Self::Config) -> Result<(), Error> { - let mut layouter = SingleChipLayouter::new(cs)?; + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { let field_chip = FieldChip::::construct(config); // Load our private values into the circuit. diff --git a/examples/two-chip.rs b/examples/two-chip.rs index 59b4b98a..030301cb 100644 --- a/examples/two-chip.rs +++ b/examples/two-chip.rs @@ -4,12 +4,9 @@ use std::marker::PhantomData; use halo2::{ arithmetic::FieldExt, - circuit::{layouter::SingleChipLayouter, Cell, Chip, Layouter, Region}, + circuit::{Cell, Chip, Layouter, Region, SimpleFloorPlanner}, dev::VerifyFailure, - plonk::{ - Advice, Assignment, Circuit, Column, ConstraintSystem, Error, Instance, Permutation, - Selector, - }, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Permutation, Selector}, poly::Rotation, }; @@ -553,6 +550,7 @@ impl FieldInstructions for FieldChip { /// In this struct we store the private input variables. We use `Option` because /// they won't have any value during key generation. During proving, if any of these /// were `None` we would get an error. +#[derive(Default)] struct MyCircuit { a: Option, b: Option, @@ -562,6 +560,11 @@ struct MyCircuit { impl Circuit for MyCircuit { // Since we are using a single chip for everything, we can just reuse its config. type Config = FieldConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } fn configure(meta: &mut ConstraintSystem) -> Self::Config { // We create the two advice columns that FieldChip uses for I/O. @@ -573,8 +576,11 @@ impl Circuit for MyCircuit { FieldChip::configure(meta, advice, instance) } - fn synthesize(&self, cs: &mut impl Assignment, config: Self::Config) -> Result<(), Error> { - let mut layouter = SingleChipLayouter::new(cs)?; + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { let field_chip = FieldChip::::construct(config, ()); // Load our private values into the circuit. diff --git a/src/circuit.rs b/src/circuit.rs index 44867490..52cd9a04 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -9,6 +9,9 @@ use crate::{ plonk::{Advice, Any, Assigned, Column, Error, Fixed, Permutation, Selector}, }; +pub mod floor_planner; +pub use floor_planner::single_pass::SimpleFloorPlanner; + pub mod layouter; /// A chip implements a set of instructions that can be used by gadgets. diff --git a/src/circuit/floor_planner.rs b/src/circuit/floor_planner.rs new file mode 100644 index 00000000..1b629034 --- /dev/null +++ b/src/circuit/floor_planner.rs @@ -0,0 +1,6 @@ +//! Implementations of common circuit floor planners. + +pub(super) mod single_pass; + +mod v1; +pub use v1::{V1Pass, V1}; diff --git a/src/circuit/layouter/single_pass.rs b/src/circuit/floor_planner/single_pass.rs similarity index 86% rename from src/circuit/layouter/single_pass.rs rename to src/circuit/floor_planner/single_pass.rs index 8f416426..cb2bdbd9 100644 --- a/src/circuit/layouter/single_pass.rs +++ b/src/circuit/floor_planner/single_pass.rs @@ -5,13 +5,36 @@ use std::marker::PhantomData; use ff::Field; -use super::{RegionLayouter, RegionShape}; -use crate::plonk::Assigned; use crate::{ - circuit::{Cell, Layouter, Region, RegionIndex, RegionStart}, - plonk::{Advice, Any, Assignment, Column, Error, Fixed, Permutation, Selector}, + circuit::{ + layouter::{RegionLayouter, RegionShape}, + Cell, Layouter, Region, RegionIndex, RegionStart, + }, + plonk::{ + Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, + Permutation, Selector, + }, }; +/// A simple [`FloorPlanner`] that performs minimal optimizations. +/// +/// This floor planner is suitable for debugging circuits. It aims to reflect the circuit +/// "business logic" in the circuit layout as closely as possible. It uses a single-pass +/// layouter that does not reorder regions for optimal packing. +#[derive(Debug)] +pub struct SimpleFloorPlanner; + +impl FloorPlanner for SimpleFloorPlanner { + fn synthesize, C: Circuit>( + cs: &mut CS, + circuit: &C, + config: C::Config, + ) -> Result<(), Error> { + let layouter = SingleChipLayouter::new(cs)?; + circuit.synthesize(config, layouter) + } +} + /// A [`Layouter`] for a single-chip circuit. pub struct SingleChipLayouter<'a, F: Field, CS: Assignment + 'a> { cs: &'a mut CS, diff --git a/src/circuit/layouter/v1.rs b/src/circuit/floor_planner/v1.rs similarity index 69% rename from src/circuit/layouter/v1.rs rename to src/circuit/floor_planner/v1.rs index d3582e6e..6028e5aa 100644 --- a/src/circuit/layouter/v1.rs +++ b/src/circuit/floor_planner/v1.rs @@ -3,65 +3,79 @@ use std::marker::PhantomData; use ff::Field; -use super::{RegionLayouter, RegionShape}; -use crate::plonk::Assigned; use crate::{ - circuit::{Cell, Layouter, Region, RegionIndex, RegionStart}, - plonk::{Advice, Assignment, Column, Error, Fixed, Permutation, Selector}, + circuit::{ + layouter::{RegionLayouter, RegionShape}, + Cell, Layouter, Region, RegionIndex, RegionStart, + }, + plonk::{ + Advice, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Permutation, + Selector, + }, }; mod strategy; -/// The version 1 [`Layouter`] provided by `halo2`. +/// The version 1 [`FloorPlanner`] provided by `halo2`. /// -/// It is a dual-pass layouter, that has visibility into the entire `Circuit::synthesize` -/// step. -pub struct V1<'a, F: Field, CS: Assignment + 'a> { +/// - No column optimizations are performed. Circuit configuration is left entirely to the +/// circuit designer. +/// - A dual-pass layouter is used to measures regions prior to assignment. +/// - Regions are measured as rectangles, bounded on the cells they assign. +/// - Regions are layed out using a greedy first-fit strategy, after sorting regions by +/// their "advice area" (number of advice columns * rows). +#[derive(Debug)] +pub struct V1; + +struct V1Plan<'a, F: Field, CS: Assignment + 'a> { cs: &'a mut CS, /// Stores the starting row for each region. regions: Vec, _marker: PhantomData, } -impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1<'a, F, CS> { +impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1Plan<'a, F, CS> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("layouter::V1").finish() + f.debug_struct("floor_planner::V1Plan").finish() } } -impl<'a, F: Field, CS: Assignment> V1<'a, F, CS> { +impl<'a, F: Field, CS: Assignment> V1Plan<'a, F, CS> { /// Creates a new v1 layouter. pub fn new(cs: &'a mut CS) -> Result { - let ret = V1 { + let ret = V1Plan { cs, regions: vec![], _marker: PhantomData, }; Ok(ret) } +} + +impl FloorPlanner for V1 { + fn synthesize, C: Circuit>( + cs: &mut CS, + circuit: &C, + config: C::Config, + ) -> Result<(), Error> { + let mut plan = V1Plan::new(cs)?; - /// 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))?; + circuit + .without_witnesses() + .synthesize(config.clone(), V1Pass::<_, CS>::measure(pass))?; } - self.regions = strategy::slot_in_biggest_advice_first(measure.regions); + plan.regions = strategy::slot_in_biggest_advice_first(measure.regions); // Second pass: assign the regions. - let mut assign = AssignmentPass::new(self); + let mut assign = AssignmentPass::new(&mut plan); { let pass = &mut assign; - synthesis(V1Pass::assign(pass))?; + circuit.synthesize(config, V1Pass::assign(pass))?; } Ok(()) @@ -113,13 +127,13 @@ impl<'p, 'a, F: Field, CS: Assignment + 'a> Layouter for V1Pass<'p, 'a, F, N: FnOnce() -> NR, { if let Pass::Assignment(pass) = &mut self.0 { - pass.layouter.cs.push_namespace(name_fn); + pass.plan.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); + pass.plan.cs.pop_namespace(gadget_name); } } } @@ -156,15 +170,15 @@ impl MeasurementPass { /// Assigns the circuit. #[derive(Debug)] pub struct AssignmentPass<'p, 'a, F: Field, CS: Assignment + 'a> { - layouter: &'p mut V1<'a, F, CS>, + plan: &'p mut V1Plan<'a, F, CS>, /// Counter tracking which region we need to assign next. region_index: usize, } impl<'p, 'a, F: Field, CS: Assignment + 'a> AssignmentPass<'p, 'a, F, CS> { - fn new(layouter: &'p mut V1<'a, F, CS>) -> Self { + fn new(plan: &'p mut V1Plan<'a, F, CS>) -> Self { AssignmentPass { - layouter, + plan, region_index: 0, } } @@ -179,38 +193,35 @@ impl<'p, 'a, F: Field, CS: Assignment + 'a> AssignmentPass<'p, 'a, F, CS> { 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()); + self.plan.cs.enter_region(name); + let mut region = V1Region::new(self.plan, region_index.into()); let result = { let region: &mut dyn RegionLayouter = &mut region; assignment(region.into()) }?; - self.layouter.cs.exit_region(); + self.plan.cs.exit_region(); Ok(result) } } struct V1Region<'r, 'a, F: Field, CS: Assignment + 'a> { - layouter: &'r mut V1<'a, F, CS>, + plan: &'r mut V1Plan<'a, F, CS>, region_index: RegionIndex, } impl<'r, 'a, F: Field, 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("plan", &self.plan) .field("region_index", &self.region_index) .finish() } } impl<'r, 'a, F: Field, 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, - } + fn new(plan: &'r mut V1Plan<'a, F, CS>, region_index: RegionIndex) -> Self { + V1Region { plan, region_index } } } @@ -221,10 +232,10 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r selector: &Selector, offset: usize, ) -> Result<(), Error> { - self.layouter.cs.enable_selector( + self.plan.cs.enable_selector( annotation, selector, - *self.layouter.regions[*self.region_index] + offset, + *self.plan.regions[*self.region_index] + offset, ) } @@ -235,10 +246,10 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r offset: usize, to: &'v mut (dyn FnMut() -> Result, Error> + 'v), ) -> Result { - self.layouter.cs.assign_advice( + self.plan.cs.assign_advice( annotation, column, - *self.layouter.regions[*self.region_index] + offset, + *self.plan.regions[*self.region_index] + offset, to, )?; @@ -256,10 +267,10 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r offset: usize, to: &'v mut (dyn FnMut() -> Result, Error> + 'v), ) -> Result { - self.layouter.cs.assign_fixed( + self.plan.cs.assign_fixed( annotation, column, - *self.layouter.regions[*self.region_index] + offset, + *self.plan.regions[*self.region_index] + offset, to, )?; @@ -276,12 +287,12 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r left: Cell, right: Cell, ) -> Result<(), Error> { - self.layouter.cs.copy( + self.plan.cs.copy( permutation, left.column, - *self.layouter.regions[*left.region_index] + left.row_offset, + *self.plan.regions[*left.region_index] + left.row_offset, right.column, - *self.layouter.regions[*right.region_index] + right.row_offset, + *self.plan.regions[*right.region_index] + right.row_offset, )?; Ok(()) diff --git a/src/circuit/layouter/v1/strategy.rs b/src/circuit/floor_planner/v1/strategy.rs similarity index 100% rename from src/circuit/layouter/v1/strategy.rs rename to src/circuit/floor_planner/v1/strategy.rs diff --git a/src/circuit/layouter.rs b/src/circuit/layouter.rs index d151f4c4..7a922cfd 100644 --- a/src/circuit/layouter.rs +++ b/src/circuit/layouter.rs @@ -10,12 +10,6 @@ use super::{Cell, RegionIndex}; use crate::plonk::Assigned; use crate::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`]. /// /// This trait is used for implementing region assignments: @@ -88,9 +82,9 @@ pub trait RegionLayouter: fmt::Debug { /// the set of columns it uses as well as the number of rows it uses. #[derive(Clone, Debug)] pub struct RegionShape { - region_index: RegionIndex, - columns: HashSet>, - row_count: usize, + pub(super) region_index: RegionIndex, + pub(super) columns: HashSet>, + pub(super) row_count: usize, } impl RegionShape { diff --git a/src/dev.rs b/src/dev.rs index 19c05b86..90708308 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -11,7 +11,7 @@ use crate::{ arithmetic::{FieldExt, Group}, plonk::{ permutation, Advice, Any, Assignment, Circuit, Column, ColumnType, ConstraintSystem, Error, - Expression, Fixed, Permutation, Selector, + Expression, Fixed, FloorPlanner, Permutation, Selector, }, poly::Rotation, }; @@ -192,9 +192,10 @@ impl Region { /// ``` /// use halo2::{ /// arithmetic::FieldExt, +/// circuit::{Layouter, SimpleFloorPlanner}, /// dev::{MockProver, VerifyFailure}, /// pasta::Fp, -/// plonk::{Advice, Assignment, Circuit, Column, ConstraintSystem, Error}, +/// plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, /// poly::Rotation, /// }; /// const K: u32 = 5; @@ -206,7 +207,7 @@ impl Region { /// c: Column, /// } /// -/// #[derive(Clone)] +/// #[derive(Clone, Default)] /// struct MyCircuit { /// a: Option, /// b: Option, @@ -214,6 +215,11 @@ impl Region { /// /// impl Circuit for MyCircuit { /// type Config = MyConfig; +/// type FloorPlanner = SimpleFloorPlanner; +/// +/// fn without_witnesses(&self) -> Self { +/// Self::default() +/// } /// /// fn configure(meta: &mut ConstraintSystem) -> MyConfig { /// let a = meta.advice_column(); @@ -232,17 +238,20 @@ impl Region { /// MyConfig { a, b, c } /// } /// -/// fn synthesize(&self, cs: &mut impl Assignment, config: MyConfig) -> Result<(), Error> { -/// cs.assign_advice(|| "a", config.a, 0, || { -/// self.a.map(|v| F::from_u64(v)).ok_or(Error::SynthesisError) -/// })?; -/// cs.assign_advice(|| "b", config.b, 0, || { -/// self.b.map(|v| F::from_u64(v)).ok_or(Error::SynthesisError) -/// })?; -/// cs.assign_advice(|| "c", config.c, 0, || { -/// self.a -/// .and_then(|a| self.b.map(|b| F::from_u64(a * b))) -/// .ok_or(Error::SynthesisError) +/// fn synthesize(&self, config: MyConfig, mut layouter: impl Layouter) -> Result<(), Error> { +/// layouter.assign_region(|| "Example region", |mut region| { +/// region.assign_advice(|| "a", config.a, 0, || { +/// self.a.map(|v| F::from_u64(v)).ok_or(Error::SynthesisError) +/// })?; +/// region.assign_advice(|| "b", config.b, 0, || { +/// self.b.map(|v| F::from_u64(v)).ok_or(Error::SynthesisError) +/// })?; +/// region.assign_advice(|| "c", config.c, 0, || { +/// self.a +/// .and_then(|a| self.b.map(|b| F::from_u64(a * b))) +/// .ok_or(Error::SynthesisError) +/// })?; +/// Ok(()) /// }) /// } /// } @@ -463,7 +472,7 @@ impl MockProver { permutations, }; - circuit.synthesize(&mut prover, config)?; + ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config)?; Ok(prover) } @@ -709,8 +718,8 @@ mod tests { use super::{MockProver, VerifyFailure}; use crate::{ - circuit::{layouter::SingleChipLayouter, Layouter}, - plonk::{Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Error, Selector}, + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Selector}, poly::Rotation, }; @@ -728,6 +737,7 @@ mod tests { impl Circuit for FaultyCircuit { type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; fn configure(meta: &mut ConstraintSystem) -> Self::Config { let a = meta.advice_column(); @@ -746,12 +756,15 @@ mod tests { FaultyCircuitConfig { a, q } } + fn without_witnesses(&self) -> Self { + Self {} + } + fn synthesize( &self, - cs: &mut impl Assignment, config: Self::Config, + mut layouter: impl Layouter, ) -> Result<(), Error> { - let mut layouter = SingleChipLayouter::new(cs)?; layouter.assign_region( || "Faulty synthesis", |mut region| { diff --git a/src/dev/graph.rs b/src/dev/graph.rs index c1170160..e7fa3a1f 100644 --- a/src/dev/graph.rs +++ b/src/dev/graph.rs @@ -3,7 +3,7 @@ use tabbycat::{AttrList, Edge, GraphBuilder, GraphType, Identity, StmtList}; use crate::plonk::{ Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, - Permutation, Selector, + FloorPlanner, Permutation, Selector, }; pub mod layout; @@ -21,7 +21,7 @@ pub fn circuit_dot_graph>( let mut cs = ConstraintSystem::default(); let config = ConcreteCircuit::configure(&mut cs); let mut graph = Graph::default(); - circuit.synthesize(&mut graph, config).unwrap(); + ConcreteCircuit::FloorPlanner::synthesize(&mut graph, circuit, config).unwrap(); // Construct the node labels. We need to store these, because tabbycat operates on // string references, and we need those references to live long enough. diff --git a/src/dev/graph/layout.rs b/src/dev/graph/layout.rs index 919568ec..15b8ba2f 100644 --- a/src/dev/graph/layout.rs +++ b/src/dev/graph/layout.rs @@ -9,7 +9,7 @@ use std::ops::Range; use crate::plonk::{ Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, - Permutation, Selector, + FloorPlanner, Permutation, Selector, }; /// Graphical renderer for circuit layouts. @@ -75,7 +75,7 @@ impl CircuitLayout { let mut cs = ConstraintSystem::default(); let config = ConcreteCircuit::configure(&mut cs); let mut layout = Layout::default(); - circuit.synthesize(&mut layout, config).unwrap(); + ConcreteCircuit::FloorPlanner::synthesize(&mut layout, circuit, config).unwrap(); // Figure out what order to render the columns in. // TODO: For now, just render them in the order they were configured. diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 050be229..c7173300 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -7,6 +7,7 @@ use std::{ }; use super::{lookup, permutation, Error}; +use crate::circuit::Layouter; use crate::{arithmetic::FieldExt, circuit::Region, poly::Rotation}; /// A column type @@ -509,12 +510,38 @@ pub trait Assignment { fn pop_namespace(&mut self, gadget_name: Option); } +/// A floor planning strategy for a circuit. +/// +/// The floor planner is chip-agnostic and applies its strategy to the circuit it is used +/// within. +pub trait FloorPlanner { + /// Given the provided `cs`, synthesize the given circuit. + /// + /// Internally, a floor planner will perform the following operations: + /// - Instantiate a [`Layouter`] for this floor planner. + /// - Perform any necessary setup or measurement tasks, which may involve one or more + /// calls to `Circuit::default().synthesize(config, &mut layouter)`. + /// - Call `circuit.synthesize(config, &mut layouter)` exactly once. + fn synthesize, C: Circuit>( + cs: &mut CS, + circuit: &C, + config: C::Config, + ) -> Result<(), Error>; +} + /// This is a trait that circuits provide implementations for so that the /// backend prover can ask the circuit to synthesize using some given /// [`ConstraintSystem`] implementation. pub trait Circuit { /// This is a configuration object that stores things like columns. type Config: Clone; + /// The floor planner used for this circuit. This is an associated type of the + /// `Circuit` trait because its behaviour is circuit-critical. + type FloorPlanner: FloorPlanner; + + /// Returns a copy of this circuit with no witness values (i.e. all witnesses set to + /// `None`). For most circuits, this will be equal to `Self::default()`. + fn without_witnesses(&self) -> Self; /// The circuit is given an opportunity to describe the exact gate /// arrangement, column arrangement, etc. @@ -523,7 +550,7 @@ pub trait Circuit { /// Given the provided `cs`, synthesize the circuit. The concrete type of /// the caller will be different depending on the context, and they may or /// may not expect to have a witness present. - fn synthesize(&self, cs: &mut impl Assignment, config: Self::Config) -> Result<(), Error>; + fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error>; } /// Low-degree expression representing an identity that must hold over the committed columns. diff --git a/src/plonk/keygen.rs b/src/plonk/keygen.rs index 647342f5..ffb31603 100644 --- a/src/plonk/keygen.rs +++ b/src/plonk/keygen.rs @@ -2,7 +2,9 @@ use ff::Field; use group::Curve; use super::{ - circuit::{Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Fixed, Selector}, + circuit::{ + Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Fixed, FloorPlanner, Selector, + }, permutation, Assigned, Error, LagrangeCoeff, Permutation, Polynomial, ProvingKey, VerifyingKey, }; use crate::poly::{ @@ -177,7 +179,7 @@ where }; // Synthesize the circuit to obtain URS - circuit.synthesize(&mut assembly, config)?; + ConcreteCircuit::FloorPlanner::synthesize(&mut assembly, circuit, config)?; let fixed = batch_invert_assigned(&assembly.fixed); @@ -228,7 +230,7 @@ where }; // Synthesize the circuit to obtain URS - circuit.synthesize(&mut assembly, config)?; + ConcreteCircuit::FloorPlanner::synthesize(&mut assembly, circuit, config)?; let fixed = batch_invert_assigned(&assembly.fixed); diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index b64d3bc5..0e02974a 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -3,7 +3,9 @@ use group::Curve; use std::iter; use super::{ - circuit::{Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Fixed, Selector}, + circuit::{ + Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Fixed, FloorPlanner, Selector, + }, lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, Permutation, ProvingKey, }; @@ -216,7 +218,7 @@ pub fn create_proof< }; // Synthesize the circuit to obtain the witness and other information. - circuit.synthesize(&mut witness, config.clone())?; + ConcreteCircuit::FloorPlanner::synthesize(&mut witness, circuit, config.clone())?; let advice = batch_invert_assigned(&witness.advice); diff --git a/tests/plonk_api.rs b/tests/plonk_api.rs index 66db1eac..4955ea23 100644 --- a/tests/plonk_api.rs +++ b/tests/plonk_api.rs @@ -3,12 +3,12 @@ use group::Curve; use halo2::arithmetic::FieldExt; -use halo2::circuit::{layouter::SingleChipLayouter, Cell, Layouter}; +use halo2::circuit::{Cell, Layouter, SimpleFloorPlanner}; use halo2::dev::MockProver; use halo2::pasta::{EqAffine, Fp}; use halo2::plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Assignment, Circuit, Column, - ConstraintSystem, Error, Fixed, Permutation, VerifyingKey, + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, ConstraintSystem, + Error, Fixed, Permutation, VerifyingKey, }; use halo2::poly::{ commitment::{Blind, Params}, @@ -264,6 +264,14 @@ fn plonk_api() { impl Circuit for MyCircuit { type Config = PLONKConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { + a: None, + lookup_tables: self.lookup_tables.clone(), + } + } fn configure(meta: &mut ConstraintSystem) -> PLONKConfig { let e = meta.advice_column(); @@ -363,10 +371,9 @@ fn plonk_api() { fn synthesize( &self, - cs: &mut impl Assignment, config: PLONKConfig, + mut layouter: impl Layouter, ) -> Result<(), Error> { - let mut layouter = SingleChipLayouter::new(cs)?; let cs = StandardPLONK::new(config); let _ = cs.public_input(&mut layouter, || Ok(F::one() + F::one()))?;