halo2_proofs/circuit/floor_planner/
single_pass.rs

1use std::cmp;
2use std::collections::HashMap;
3use std::fmt;
4use std::marker::PhantomData;
5
6use ff::Field;
7
8use crate::{
9    circuit::{
10        layouter::{RegionColumn, RegionLayouter, RegionShape, TableLayouter},
11        Cell, Layouter, Region, RegionIndex, RegionStart, Table,
12    },
13    plonk::{
14        Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance,
15        Selector, TableColumn,
16    },
17};
18
19/// A simple [`FloorPlanner`] that performs minimal optimizations.
20///
21/// This floor planner is suitable for debugging circuits. It aims to reflect the circuit
22/// "business logic" in the circuit layout as closely as possible. It uses a single-pass
23/// layouter that does not reorder regions for optimal packing.
24#[derive(Debug)]
25pub struct SimpleFloorPlanner;
26
27impl FloorPlanner for SimpleFloorPlanner {
28    fn synthesize<F: Field, CS: Assignment<F>, C: Circuit<F>>(
29        cs: &mut CS,
30        circuit: &C,
31        config: C::Config,
32        constants: Vec<Column<Fixed>>,
33    ) -> Result<(), Error> {
34        let layouter = SingleChipLayouter::new(cs, constants)?;
35        circuit.synthesize(config, layouter)
36    }
37}
38
39/// A [`Layouter`] for a single-chip circuit.
40pub struct SingleChipLayouter<'a, F: Field, CS: Assignment<F> + 'a> {
41    cs: &'a mut CS,
42    constants: Vec<Column<Fixed>>,
43    /// Stores the starting row for each region.
44    regions: Vec<RegionStart>,
45    /// Stores the first empty row for each column.
46    columns: HashMap<RegionColumn, usize>,
47    /// Stores the table fixed columns.
48    table_columns: Vec<TableColumn>,
49    _marker: PhantomData<F>,
50}
51
52impl<'a, F: Field, CS: Assignment<F> + 'a> fmt::Debug for SingleChipLayouter<'a, F, CS> {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        f.debug_struct("SingleChipLayouter")
55            .field("regions", &self.regions)
56            .field("columns", &self.columns)
57            .finish()
58    }
59}
60
61impl<'a, F: Field, CS: Assignment<F>> SingleChipLayouter<'a, F, CS> {
62    /// Creates a new single-chip layouter.
63    pub fn new(cs: &'a mut CS, constants: Vec<Column<Fixed>>) -> Result<Self, Error> {
64        let ret = SingleChipLayouter {
65            cs,
66            constants,
67            regions: vec![],
68            columns: HashMap::default(),
69            table_columns: vec![],
70            _marker: PhantomData,
71        };
72        Ok(ret)
73    }
74}
75
76impl<'a, F: Field, CS: Assignment<F> + 'a> Layouter<F> for SingleChipLayouter<'a, F, CS> {
77    type Root = Self;
78
79    fn assign_region<A, AR, N, NR>(&mut self, name: N, mut assignment: A) -> Result<AR, Error>
80    where
81        A: FnMut(Region<'_, F>) -> Result<AR, Error>,
82        N: Fn() -> NR,
83        NR: Into<String>,
84    {
85        let region_index = self.regions.len();
86
87        // Get shape of the region.
88        let mut shape = RegionShape::new(region_index.into());
89        {
90            let region: &mut dyn RegionLayouter<F> = &mut shape;
91            assignment(region.into())?;
92        }
93
94        // Lay out this region. We implement the simplest approach here: position the
95        // region starting at the earliest row for which none of the columns are in use.
96        let mut region_start = 0;
97        for column in &shape.columns {
98            region_start = cmp::max(region_start, self.columns.get(column).cloned().unwrap_or(0));
99        }
100        self.regions.push(region_start.into());
101
102        // Update column usage information.
103        for column in shape.columns {
104            self.columns.insert(column, region_start + shape.row_count);
105        }
106
107        // Assign region cells.
108        self.cs.enter_region(name);
109        let mut region = SingleChipLayouterRegion::new(self, region_index.into());
110        let result = {
111            let region: &mut dyn RegionLayouter<F> = &mut region;
112            assignment(region.into())
113        }?;
114        let constants_to_assign = region.constants;
115        self.cs.exit_region();
116
117        // Assign constants. For the simple floor planner, we assign constants in order in
118        // the first `constants` column.
119        if self.constants.is_empty() {
120            if !constants_to_assign.is_empty() {
121                return Err(Error::NotEnoughColumnsForConstants);
122            }
123        } else {
124            let constants_column = self.constants[0];
125            let next_constant_row = self
126                .columns
127                .entry(Column::<Any>::from(constants_column).into())
128                .or_default();
129            for (constant, advice) in constants_to_assign {
130                self.cs.assign_fixed(
131                    || format!("Constant({:?})", constant.evaluate()),
132                    constants_column,
133                    *next_constant_row,
134                    || Ok(constant),
135                )?;
136                self.cs.copy(
137                    constants_column.into(),
138                    *next_constant_row,
139                    advice.column,
140                    *self.regions[*advice.region_index] + advice.row_offset,
141                )?;
142                *next_constant_row += 1;
143            }
144        }
145
146        Ok(result)
147    }
148
149    fn assign_table<A, N, NR>(&mut self, name: N, mut assignment: A) -> Result<(), Error>
150    where
151        A: FnMut(Table<'_, F>) -> Result<(), Error>,
152        N: Fn() -> NR,
153        NR: Into<String>,
154    {
155        // Maintenance hazard: there is near-duplicate code in `v1::AssignmentPass::assign_table`.
156        // Assign table cells.
157        self.cs.enter_region(name);
158        let mut table = SimpleTableLayouter::new(self.cs, &self.table_columns);
159        {
160            let table: &mut dyn TableLayouter<F> = &mut table;
161            assignment(table.into())
162        }?;
163        let default_and_assigned = table.default_and_assigned;
164        self.cs.exit_region();
165
166        // Check that all table columns have the same length `first_unused`,
167        // and all cells up to that length are assigned.
168        let first_unused = {
169            match default_and_assigned
170                .values()
171                .map(|(_, assigned)| {
172                    if assigned.iter().all(|b| *b) {
173                        Some(assigned.len())
174                    } else {
175                        None
176                    }
177                })
178                .reduce(|acc, item| match (acc, item) {
179                    (Some(a), Some(b)) if a == b => Some(a),
180                    _ => None,
181                }) {
182                Some(Some(len)) => len,
183                _ => return Err(Error::Synthesis), // TODO better error
184            }
185        };
186
187        // Record these columns so that we can prevent them from being used again.
188        for column in default_and_assigned.keys() {
189            self.table_columns.push(*column);
190        }
191
192        for (col, (default_val, _)) in default_and_assigned {
193            // default_val must be Some because we must have assigned
194            // at least one cell in each column, and in that case we checked
195            // that all cells up to first_unused were assigned.
196            self.cs
197                .fill_from_row(col.inner(), first_unused, default_val.unwrap())?;
198        }
199
200        Ok(())
201    }
202
203    fn constrain_instance(
204        &mut self,
205        cell: Cell,
206        instance: Column<Instance>,
207        row: usize,
208    ) -> Result<(), Error> {
209        self.cs.copy(
210            cell.column,
211            *self.regions[*cell.region_index] + cell.row_offset,
212            instance.into(),
213            row,
214        )
215    }
216
217    fn get_root(&mut self) -> &mut Self::Root {
218        self
219    }
220
221    fn push_namespace<NR, N>(&mut self, name_fn: N)
222    where
223        NR: Into<String>,
224        N: FnOnce() -> NR,
225    {
226        self.cs.push_namespace(name_fn)
227    }
228
229    fn pop_namespace(&mut self, gadget_name: Option<String>) {
230        self.cs.pop_namespace(gadget_name)
231    }
232}
233
234struct SingleChipLayouterRegion<'r, 'a, F: Field, CS: Assignment<F> + 'a> {
235    layouter: &'r mut SingleChipLayouter<'a, F, CS>,
236    region_index: RegionIndex,
237    /// Stores the constants to be assigned, and the cells to which they are copied.
238    constants: Vec<(Assigned<F>, Cell)>,
239}
240
241impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> fmt::Debug
242    for SingleChipLayouterRegion<'r, 'a, F, CS>
243{
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        f.debug_struct("SingleChipLayouterRegion")
246            .field("layouter", &self.layouter)
247            .field("region_index", &self.region_index)
248            .finish()
249    }
250}
251
252impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> SingleChipLayouterRegion<'r, 'a, F, CS> {
253    fn new(layouter: &'r mut SingleChipLayouter<'a, F, CS>, region_index: RegionIndex) -> Self {
254        SingleChipLayouterRegion {
255            layouter,
256            region_index,
257            constants: vec![],
258        }
259    }
260}
261
262impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
263    for SingleChipLayouterRegion<'r, 'a, F, CS>
264{
265    fn enable_selector<'v>(
266        &'v mut self,
267        annotation: &'v (dyn Fn() -> String + 'v),
268        selector: &Selector,
269        offset: usize,
270    ) -> Result<(), Error> {
271        self.layouter.cs.enable_selector(
272            annotation,
273            selector,
274            *self.layouter.regions[*self.region_index] + offset,
275        )
276    }
277
278    fn assign_advice<'v>(
279        &'v mut self,
280        annotation: &'v (dyn Fn() -> String + 'v),
281        column: Column<Advice>,
282        offset: usize,
283        to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
284    ) -> Result<Cell, Error> {
285        self.layouter.cs.assign_advice(
286            annotation,
287            column,
288            *self.layouter.regions[*self.region_index] + offset,
289            to,
290        )?;
291
292        Ok(Cell {
293            region_index: self.region_index,
294            row_offset: offset,
295            column: column.into(),
296        })
297    }
298
299    fn assign_advice_from_constant<'v>(
300        &'v mut self,
301        annotation: &'v (dyn Fn() -> String + 'v),
302        column: Column<Advice>,
303        offset: usize,
304        constant: Assigned<F>,
305    ) -> Result<Cell, Error> {
306        let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?;
307        self.constrain_constant(advice, constant)?;
308
309        Ok(advice)
310    }
311
312    fn assign_advice_from_instance<'v>(
313        &mut self,
314        annotation: &'v (dyn Fn() -> String + 'v),
315        instance: Column<Instance>,
316        row: usize,
317        advice: Column<Advice>,
318        offset: usize,
319    ) -> Result<(Cell, Option<F>), Error> {
320        let value = self.layouter.cs.query_instance(instance, row)?;
321
322        let cell = self.assign_advice(annotation, advice, offset, &mut || {
323            value.ok_or(Error::Synthesis).map(|v| v.into())
324        })?;
325
326        self.layouter.cs.copy(
327            cell.column,
328            *self.layouter.regions[*cell.region_index] + cell.row_offset,
329            instance.into(),
330            row,
331        )?;
332
333        Ok((cell, value))
334    }
335
336    fn assign_fixed<'v>(
337        &'v mut self,
338        annotation: &'v (dyn Fn() -> String + 'v),
339        column: Column<Fixed>,
340        offset: usize,
341        to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
342    ) -> Result<Cell, Error> {
343        self.layouter.cs.assign_fixed(
344            annotation,
345            column,
346            *self.layouter.regions[*self.region_index] + offset,
347            to,
348        )?;
349
350        Ok(Cell {
351            region_index: self.region_index,
352            row_offset: offset,
353            column: column.into(),
354        })
355    }
356
357    fn constrain_constant(&mut self, cell: Cell, constant: Assigned<F>) -> Result<(), Error> {
358        self.constants.push((constant, cell));
359        Ok(())
360    }
361
362    fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> {
363        self.layouter.cs.copy(
364            left.column,
365            *self.layouter.regions[*left.region_index] + left.row_offset,
366            right.column,
367            *self.layouter.regions[*right.region_index] + right.row_offset,
368        )?;
369
370        Ok(())
371    }
372}
373
374/// The default value to fill a table column with.
375///
376/// - The outer `Option` tracks whether the value in row 0 of the table column has been
377///   assigned yet. This will always be `Some` once a valid table has been completely
378///   assigned.
379/// - The inner `Option` tracks whether the underlying `Assignment` is evaluating
380///   witnesses or not.
381type DefaultTableValue<F> = Option<Option<Assigned<F>>>;
382
383pub(crate) struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment<F> + 'a> {
384    cs: &'a mut CS,
385    used_columns: &'r [TableColumn],
386    // maps from a fixed column to a pair (default value, vector saying which rows are assigned)
387    pub(crate) default_and_assigned: HashMap<TableColumn, (DefaultTableValue<F>, Vec<bool>)>,
388}
389
390impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> fmt::Debug for SimpleTableLayouter<'r, 'a, F, CS> {
391    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392        f.debug_struct("SimpleTableLayouter")
393            .field("used_columns", &self.used_columns)
394            .field("default_and_assigned", &self.default_and_assigned)
395            .finish()
396    }
397}
398
399impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> SimpleTableLayouter<'r, 'a, F, CS> {
400    pub(crate) fn new(cs: &'a mut CS, used_columns: &'r [TableColumn]) -> Self {
401        SimpleTableLayouter {
402            cs,
403            used_columns,
404            default_and_assigned: HashMap::default(),
405        }
406    }
407}
408
409impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> TableLayouter<F>
410    for SimpleTableLayouter<'r, 'a, F, CS>
411{
412    fn assign_cell<'v>(
413        &'v mut self,
414        annotation: &'v (dyn Fn() -> String + 'v),
415        column: TableColumn,
416        offset: usize,
417        to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
418    ) -> Result<(), Error> {
419        if self.used_columns.contains(&column) {
420            return Err(Error::Synthesis); // TODO better error
421        }
422
423        let entry = self.default_and_assigned.entry(column).or_default();
424
425        let mut value = None;
426        self.cs.assign_fixed(
427            annotation,
428            column.inner(),
429            offset, // tables are always assigned starting at row 0
430            || {
431                let res = to();
432                value = res.as_ref().ok().cloned();
433                res
434            },
435        )?;
436
437        match (entry.0.is_none(), offset) {
438            // Use the value at offset 0 as the default value for this table column.
439            (true, 0) => entry.0 = Some(value),
440            // Since there is already an existing default value for this table column,
441            // the caller should not be attempting to assign another value at offset 0.
442            (false, 0) => return Err(Error::Synthesis), // TODO better error
443            _ => (),
444        }
445        if entry.1.len() <= offset {
446            entry.1.resize(offset + 1, false);
447        }
448        entry.1[offset] = true;
449
450        Ok(())
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use pasta_curves::vesta;
457
458    use super::SimpleFloorPlanner;
459    use crate::{
460        dev::MockProver,
461        plonk::{Advice, Circuit, Column, Error},
462    };
463
464    #[test]
465    fn not_enough_columns_for_constants() {
466        struct MyCircuit {}
467
468        impl Circuit<vesta::Scalar> for MyCircuit {
469            type Config = Column<Advice>;
470            type FloorPlanner = SimpleFloorPlanner;
471
472            fn without_witnesses(&self) -> Self {
473                MyCircuit {}
474            }
475
476            fn configure(meta: &mut crate::plonk::ConstraintSystem<vesta::Scalar>) -> Self::Config {
477                meta.advice_column()
478            }
479
480            fn synthesize(
481                &self,
482                config: Self::Config,
483                mut layouter: impl crate::circuit::Layouter<vesta::Scalar>,
484            ) -> Result<(), crate::plonk::Error> {
485                layouter.assign_region(
486                    || "assign constant",
487                    |mut region| {
488                        region.assign_advice_from_constant(
489                            || "one",
490                            config,
491                            0,
492                            vesta::Scalar::one(),
493                        )
494                    },
495                )?;
496
497                Ok(())
498            }
499        }
500
501        let circuit = MyCircuit {};
502        assert!(matches!(
503            MockProver::run(3, &circuit, vec![]).unwrap_err(),
504            Error::NotEnoughColumnsForConstants,
505        ));
506    }
507}