diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml index 3328f67e6d..35ecbf4dab 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -63,6 +63,9 @@ maybe-rayon = { version = "0.1.0", default-features = false } plotters = { version = "0.3.0", default-features = false, optional = true } tabbycat = { version = "0.1", features = ["attributes"], optional = true } +# Legacy circuit compatibility +halo2_legacy_pdqsort = { version = "0.1.0", optional = true } + [dev-dependencies] assert_matches = "1.5" criterion = "0.3" diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index eb2b029b61..1d5fceb797 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -13,9 +13,11 @@ pub use value::Value; pub mod floor_planner; pub use floor_planner::single_pass::SimpleFloorPlanner; -pub use floor_planner::single_pass::SimpleTableLayouter; pub mod layouter; +mod table_layouter; + +pub use table_layouter::{SimpleTableLayouter, TableLayouter}; /// A chip implements a set of instructions that can be used by gadgets. /// @@ -314,6 +316,19 @@ impl<'r, F: Field> Region<'r, F> { }) } + /// Returns the value of the instance column's cell at absolute location `row`. + /// + /// This method is only provided for convenience; it does not create any constraints. + /// Callers still need to use [`Self::assign_advice_from_instance`] to constrain the + /// instance values in their circuit. + pub fn instance_value( + &mut self, + instance: Column, + row: usize, + ) -> Result, Error> { + self.region.instance_value(instance, row) + } + /// Assign a fixed value. /// /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. @@ -369,11 +384,11 @@ impl<'r, F: Field> Region<'r, F> { /// A lookup table in the circuit. #[derive(Debug)] pub struct Table<'r, F: Field> { - table: &'r mut dyn layouter::TableLayouter, + table: &'r mut dyn TableLayouter, } -impl<'r, F: Field> From<&'r mut dyn layouter::TableLayouter> for Table<'r, F> { - fn from(table: &'r mut dyn layouter::TableLayouter) -> Self { +impl<'r, F: Field> From<&'r mut dyn TableLayouter> for Table<'r, F> { + fn from(table: &'r mut dyn TableLayouter) -> Self { Table { table } } } diff --git a/halo2_proofs/src/circuit/floor_planner/single_pass.rs b/halo2_proofs/src/circuit/floor_planner/single_pass.rs index f29df086f4..33c09e4c57 100644 --- a/halo2_proofs/src/circuit/floor_planner/single_pass.rs +++ b/halo2_proofs/src/circuit/floor_planner/single_pass.rs @@ -8,6 +8,7 @@ use ff::Field; use crate::{ circuit::{ layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, + table_layouter::{compute_table_lengths, SimpleTableLayouter}, Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ @@ -167,24 +168,7 @@ impl<'a, F: Field, CS: Assignment + 'a + SyncDeps> Layouter // Check that all table columns have the same length `first_unused`, // and all cells up to that length are assigned. - let first_unused = { - match default_and_assigned - .values() - .map(|(_, assigned)| { - if assigned.iter().all(|b| *b) { - Some(assigned.len()) - } else { - None - } - }) - .reduce(|acc, item| match (acc, item) { - (Some(a), Some(b)) if a == b => Some(a), - _ => None, - }) { - Some(Some(len)) => len, - _ => return Err(Error::Synthesis), // TODO better error - } - }; + let first_unused = compute_table_lengths(&default_and_assigned)?; // Record these columns so that we can prevent them from being used again. for column in default_and_assigned.keys() { @@ -346,6 +330,14 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter Ok((cell, value)) } + fn instance_value( + &mut self, + instance: Column, + row: usize, + ) -> Result, Error> { + self.layouter.cs.query_instance(instance, row) + } + fn assign_fixed<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), @@ -384,88 +376,6 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter } } -/// The default value to fill a table column with. -/// -/// - The outer `Option` tracks whether the value in row 0 of the table column has been -/// assigned yet. This will always be `Some` once a valid table has been completely -/// assigned. -/// - The inner `Value` tracks whether the underlying `Assignment` is evaluating -/// witnesses or not. -type DefaultTableValue = Option>>; - -/// A table layouter that can be used to assign values to a table. -pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment + 'a> { - cs: &'a mut CS, - used_columns: &'r [TableColumn], - /// maps from a fixed column to a pair (default value, vector saying which rows are assigned) - pub default_and_assigned: HashMap, Vec)>, -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug for SimpleTableLayouter<'r, 'a, F, CS> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SimpleTableLayouter") - .field("used_columns", &self.used_columns) - .field("default_and_assigned", &self.default_and_assigned) - .finish() - } -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> SimpleTableLayouter<'r, 'a, F, CS> { - /// Returns a new SimpleTableLayouter - pub fn new(cs: &'a mut CS, used_columns: &'r [TableColumn]) -> Self { - SimpleTableLayouter { - cs, - used_columns, - default_and_assigned: HashMap::default(), - } - } -} - -impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter - for SimpleTableLayouter<'r, 'a, F, CS> -{ - fn assign_cell<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: TableColumn, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result<(), Error> { - if self.used_columns.contains(&column) { - return Err(Error::Synthesis); // TODO better error - } - - let entry = self.default_and_assigned.entry(column).or_default(); - - let mut value = Value::unknown(); - self.cs.assign_fixed( - annotation, - column.inner(), - offset, // tables are always assigned starting at row 0 - || { - let res = to(); - value = res; - res - }, - )?; - - match (entry.0.is_none(), offset) { - // Use the value at offset 0 as the default value for this table column. - (true, 0) => entry.0 = Some(value), - // Since there is already an existing default value for this table column, - // the caller should not be attempting to assign another value at offset 0. - (false, 0) => return Err(Error::Synthesis), // TODO better error - _ => (), - } - if entry.1.len() <= offset { - entry.1.resize(offset + 1, false); - } - entry.1[offset] = true; - - Ok(()) - } -} - #[cfg(test)] mod tests { use halo2curves::pasta::vesta; diff --git a/halo2_proofs/src/circuit/floor_planner/v1.rs b/halo2_proofs/src/circuit/floor_planner/v1.rs index 5cdb98247f..fd26e681df 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1.rs @@ -4,8 +4,8 @@ use ff::Field; use crate::{ circuit::{ - floor_planner::single_pass::SimpleTableLayouter, layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, + table_layouter::{compute_table_lengths, SimpleTableLayouter}, Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ @@ -313,24 +313,7 @@ impl<'p, 'a, F: Field, CS: Assignment + SyncDeps> AssignmentPass<'p, 'a, F, C // Check that all table columns have the same length `first_unused`, // and all cells up to that length are assigned. - let first_unused = { - match default_and_assigned - .values() - .map(|(_, assigned)| { - if assigned.iter().all(|b| *b) { - Some(assigned.len()) - } else { - None - } - }) - .reduce(|acc, item| match (acc, item) { - (Some(a), Some(b)) if a == b => Some(a), - _ => None, - }) { - Some(Some(len)) => len, - _ => return Err(Error::Synthesis), // TODO better error - } - }; + let first_unused = compute_table_lengths(&default_and_assigned)?; // Record these columns so that we can prevent them from being used again. for column in default_and_assigned.keys() { @@ -455,6 +438,14 @@ impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Reg Ok((cell, value)) } + fn instance_value( + &mut self, + instance: Column, + row: usize, + ) -> Result, Error> { + self.plan.cs.query_instance(instance, row) + } + fn assign_fixed<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), diff --git a/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs b/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs index f9acd0f57d..71745de245 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs @@ -199,7 +199,7 @@ pub fn slot_in_biggest_advice_first( region_shapes: Vec, ) -> (Vec, CircuitAllocations) { let mut sorted_regions: Vec<_> = region_shapes.into_iter().collect(); - sorted_regions.sort_unstable_by_key(|shape| { + let sort_key = |shape: &RegionShape| { // Count the number of advice columns let advice_cols = shape .columns() @@ -211,7 +211,24 @@ pub fn slot_in_biggest_advice_first( .count(); // Sort by advice area (since this has the most contention). advice_cols * shape.row_count() - }); + }; + + // This used to incorrectly use `sort_unstable_by_key` with non-unique keys, which gave + // output that differed between 32-bit and 64-bit platforms, and potentially between Rust + // versions. + // We now use `sort_by_cached_key` with non-unique keys, and rely on `region_shapes` + // being sorted by region index (which we also rely on below to return `RegionStart`s + // in the correct order). + #[cfg(not(feature = "floor-planner-v1-legacy-pdqsort"))] + sorted_regions.sort_by_cached_key(sort_key); + + // To preserve compatibility, when the "floor-planner-v1-legacy-pdqsort" feature is enabled, + // we use a copy of the pdqsort implementation from the Rust 1.56.1 standard library, fixed + // to its behaviour on 64-bit platforms. + // https://github.com/rust-lang/rust/blob/1.56.1/library/core/src/slice/mod.rs#L2365-L2402 + #[cfg(feature = "floor-planner-v1-legacy-pdqsort")] + halo2_legacy_pdqsort::sort::quicksort(&mut sorted_regions, |a, b| sort_key(a).lt(&sort_key(b))); + sorted_regions.reverse(); // Lay out the sorted regions. diff --git a/halo2_proofs/src/circuit/layouter.rs b/halo2_proofs/src/circuit/layouter.rs index 7a74f7e408..2adc5dacbe 100644 --- a/halo2_proofs/src/circuit/layouter.rs +++ b/halo2_proofs/src/circuit/layouter.rs @@ -6,6 +6,7 @@ use std::fmt; use ff::Field; +pub use super::table_layouter::TableLayouter; use super::{Cell, RegionIndex, Value}; use crate::plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn}; @@ -98,7 +99,8 @@ pub trait RegionLayouter: fmt::Debug + SyncDeps { /// Assign the value of the instance column's cell at absolute location /// `row` to the column `advice` at `offset` within this region. /// - /// Returns the advice cell, and its value if known. + /// Returns the advice cell that has been equality-constrained to the + /// instance cell, and its value if known. fn assign_advice_from_instance<'v>( &mut self, annotation: &'v (dyn Fn() -> String + 'v), @@ -108,7 +110,11 @@ pub trait RegionLayouter: fmt::Debug + SyncDeps { offset: usize, ) -> Result<(Cell, Value), Error>; - /// Assign a fixed value + /// Returns the value of the instance column's cell at absolute location `row`. + fn instance_value(&mut self, instance: Column, row: usize) + -> Result, Error>; + + /// Assigns a fixed value fn assign_fixed<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), @@ -128,24 +134,6 @@ pub trait RegionLayouter: fmt::Debug + SyncDeps { fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error>; } -/// Helper trait for implementing a custom [`Layouter`]. -/// -/// This trait is used for implementing table assignments. -/// -/// [`Layouter`]: super::Layouter -pub trait TableLayouter: fmt::Debug { - /// Assigns a fixed value to a table cell. - /// - /// Returns an error if the table cell has already been assigned to. - fn assign_cell<'v>( - &'v mut self, - annotation: &'v (dyn Fn() -> String + 'v), - column: TableColumn, - offset: usize, - to: &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result<(), Error>; -} - /// 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(Clone, Debug)] @@ -282,6 +270,14 @@ impl RegionLayouter for RegionShape { )) } + fn instance_value( + &mut self, + _instance: Column, + _row: usize, + ) -> Result, Error> { + Ok(Value::unknown()) + } + fn assign_fixed<'v>( &'v mut self, _: &'v (dyn Fn() -> String + 'v), diff --git a/halo2_proofs/src/circuit/table_layouter.rs b/halo2_proofs/src/circuit/table_layouter.rs new file mode 100644 index 0000000000..5efe11735c --- /dev/null +++ b/halo2_proofs/src/circuit/table_layouter.rs @@ -0,0 +1,413 @@ +//! Implementations of common table layouters. + +use std::{ + collections::HashMap, + fmt::{self, Debug}, +}; + +use ff::Field; + +use crate::plonk::{Assigned, Assignment, Error, TableColumn, TableError}; + +use super::Value; + +/// Helper trait for implementing a custom [`Layouter`]. +/// +/// This trait is used for implementing table assignments. +/// +/// [`Layouter`]: super::Layouter +pub trait TableLayouter: std::fmt::Debug { + /// Assigns a fixed value to a table cell. + /// + /// Returns an error if the table cell has already been assigned to. + fn assign_cell<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: TableColumn, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result<(), Error>; +} + +/// The default value to fill a table column with. +/// +/// - The outer `Option` tracks whether the value in row 0 of the table column has been +/// assigned yet. This will always be `Some` once a valid table has been completely +/// assigned. +/// - The inner `Value` tracks whether the underlying `Assignment` is evaluating +/// witnesses or not. +type DefaultTableValue = Option>>; + +/// A table layouter that can be used to assign values to a table. +pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment + 'a> { + cs: &'a mut CS, + used_columns: &'r [TableColumn], + /// maps from a fixed column to a pair (default value, vector saying which rows are assigned) + pub default_and_assigned: HashMap, Vec)>, +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> fmt::Debug for SimpleTableLayouter<'r, 'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SimpleTableLayouter") + .field("used_columns", &self.used_columns) + .field("default_and_assigned", &self.default_and_assigned) + .finish() + } +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> SimpleTableLayouter<'r, 'a, F, CS> { + /// Returns a new SimpleTableLayouter + pub fn new(cs: &'a mut CS, used_columns: &'r [TableColumn]) -> Self { + SimpleTableLayouter { + cs, + used_columns, + default_and_assigned: HashMap::default(), + } + } +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter + for SimpleTableLayouter<'r, 'a, F, CS> +{ + fn assign_cell<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: TableColumn, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result<(), Error> { + if self.used_columns.contains(&column) { + return Err(Error::TableError(TableError::UsedColumn(column))); + } + + let entry = self.default_and_assigned.entry(column).or_default(); + + let mut value = Value::unknown(); + self.cs.assign_fixed( + annotation, + column.inner(), + offset, // tables are always assigned starting at row 0 + || { + let res = to(); + value = res; + res + }, + )?; + + match (entry.0.is_none(), offset) { + // Use the value at offset 0 as the default value for this table column. + (true, 0) => entry.0 = Some(value), + // Since there is already an existing default value for this table column, + // the caller should not be attempting to assign another value at offset 0. + (false, 0) => { + return Err(Error::TableError(TableError::OverwriteDefault( + column, + format!("{:?}", entry.0.unwrap()), + format!("{:?}", value), + ))) + } + _ => (), + } + if entry.1.len() <= offset { + entry.1.resize(offset + 1, false); + } + entry.1[offset] = true; + + Ok(()) + } +} + +pub(crate) fn compute_table_lengths( + default_and_assigned: &HashMap, Vec)>, +) -> Result { + let column_lengths: Result, Error> = default_and_assigned + .iter() + .map(|(col, (default_value, assigned))| { + if default_value.is_none() || assigned.is_empty() { + return Err(Error::TableError(TableError::ColumnNotAssigned(*col))); + } + if assigned.iter().all(|b| *b) { + // All values in the column have been assigned + Ok((col, assigned.len())) + } else { + Err(Error::TableError(TableError::ColumnNotAssigned(*col))) + } + }) + .collect(); + let column_lengths = column_lengths?; + column_lengths + .into_iter() + .try_fold((None, 0), |acc, (col, col_len)| { + if acc.1 == 0 || acc.1 == col_len { + Ok((Some(*col), col_len)) + } else { + let mut cols = [(*col, col_len), (acc.0.unwrap(), acc.1)]; + cols.sort(); + Err(Error::TableError(TableError::UnevenColumnLengths( + cols[0], cols[1], + ))) + } + }) + .map(|col_len| col_len.1) +} + +#[cfg(test)] +mod tests { + use halo2curves::pasta::Fp; + + use crate::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + plonk::{Circuit, ConstraintSystem}, + poly::Rotation, + }; + + use super::*; + + #[test] + fn table_no_default() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + table: TableColumn, + } + + struct FaultyCircuit; + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let table = meta.lookup_table_column(); + + meta.lookup("", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + vec![(a, table)] + }); + + Self::Config { table } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "duplicate assignment", + |mut table| { + table.assign_cell( + || "default", + config.table, + 1, + || Value::known(Fp::zero()), + ) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit, vec![]); + assert_eq!( + format!("{}", prover.unwrap_err()), + "TableColumn { inner: Column { index: 0, column_type: Fixed } } not fully assigned. Help: assign a value at offset 0." + ); + } + + #[test] + fn table_overwrite_default() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + table: TableColumn, + } + + struct FaultyCircuit; + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let table = meta.lookup_table_column(); + + meta.lookup("", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + vec![(a, table)] + }); + + Self::Config { table } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "duplicate assignment", + |mut table| { + table.assign_cell( + || "default", + config.table, + 0, + || Value::known(Fp::zero()), + )?; + table.assign_cell( + || "duplicate", + config.table, + 0, + || Value::known(Fp::zero()), + ) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit, vec![]); + assert_eq!( + format!("{}", prover.unwrap_err()), + "Attempted to overwrite default value Value { inner: Some(Trivial(0x0000000000000000000000000000000000000000000000000000000000000000)) } with Value { inner: Some(Trivial(0x0000000000000000000000000000000000000000000000000000000000000000)) } in TableColumn { inner: Column { index: 0, column_type: Fixed } }" + ); + } + + #[test] + fn table_reuse_column() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + table: TableColumn, + } + + struct FaultyCircuit; + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let table = meta.lookup_table_column(); + + meta.lookup("", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + vec![(a, table)] + }); + + Self::Config { table } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "first assignment", + |mut table| { + table.assign_cell( + || "default", + config.table, + 0, + || Value::known(Fp::zero()), + ) + }, + )?; + + layouter.assign_table( + || "reuse", + |mut table| { + table.assign_cell(|| "reuse", config.table, 1, || Value::known(Fp::zero())) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit, vec![]); + assert_eq!( + format!("{}", prover.unwrap_err()), + "TableColumn { inner: Column { index: 0, column_type: Fixed } } has already been used" + ); + } + + #[test] + fn table_uneven_columns() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + table: (TableColumn, TableColumn), + } + + struct FaultyCircuit; + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let table = (meta.lookup_table_column(), meta.lookup_table_column()); + meta.lookup("", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + + vec![(a.clone(), table.0), (a, table.1)] + }); + + Self::Config { table } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "table with uneven columns", + |mut table| { + table.assign_cell(|| "", config.table.0, 0, || Value::known(Fp::zero()))?; + table.assign_cell(|| "", config.table.0, 1, || Value::known(Fp::zero()))?; + + table.assign_cell(|| "", config.table.1, 0, || Value::known(Fp::zero())) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit, vec![]); + assert_eq!( + format!("{}", prover.unwrap_err()), + "TableColumn { inner: Column { index: 0, column_type: Fixed } } has length 2 while TableColumn { inner: Column { index: 1, column_type: Fixed } } has length 1" + ); + } +} diff --git a/halo2_proofs/src/circuit/value.rs b/halo2_proofs/src/circuit/value.rs index e6ae26cd1b..f3ea6a39ea 100644 --- a/halo2_proofs/src/circuit/value.rs +++ b/halo2_proofs/src/circuit/value.rs @@ -63,6 +63,11 @@ impl Value { } } + /// ONLY FOR INTERNAL CRATE USAGE; DO NOT EXPOSE! + pub(crate) fn into_option(self) -> Option { + self.inner + } + /// Enforces an assertion on the contained value, if known. /// /// The assertion is ignored if `self` is [`Value::unknown()`]. Do not try to enforce diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index bed4828034..ab6c79a795 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -44,6 +44,9 @@ pub use cost::CircuitCost; mod gates; pub use gates::CircuitGates; +mod tfp; +pub use tfp::TracingFloorPlanner; + #[cfg(feature = "dev-graph")] mod graph; diff --git a/halo2_proofs/src/dev/cost.rs b/halo2_proofs/src/dev/cost.rs index f0b7d5640b..5dd2842867 100644 --- a/halo2_proofs/src/dev/cost.rs +++ b/halo2_proofs/src/dev/cost.rs @@ -1,6 +1,7 @@ //! Developer tools for investigating the cost of a circuit. use std::{ + cmp, collections::{HashMap, HashSet}, iter, marker::PhantomData, @@ -11,7 +12,7 @@ use ff::{Field, PrimeField}; use group::prime::PrimeGroup; use crate::{ - circuit::{layouter::SyncDeps, Value}, + circuit::{layouter::RegionColumn, Value}, plonk::{ Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, Fixed, FloorPlanner, Instance, Selector, @@ -20,10 +21,11 @@ use crate::{ }; /// Measures a circuit to determine its costs, and explain what contributes to them. +#[allow(dead_code)] #[derive(Debug)] pub struct CircuitCost> { /// Power-of-2 bound on the number of rows in the circuit. - k: usize, + k: u32, /// Maximum degree of the circuit. max_deg: usize, /// Number of advice columns. @@ -38,25 +40,141 @@ pub struct CircuitCost> { permutation_cols: usize, /// Number of distinct sets of points in the multiopening argument. point_sets: usize, + /// Maximum rows used over all columns + max_rows: usize, + /// Maximum rows used over all advice columns + max_advice_rows: usize, + /// Maximum rows used over all fixed columns + max_fixed_rows: usize, + num_fixed_columns: usize, + num_advice_columns: usize, + num_instance_columns: usize, + num_total_columns: usize, _marker: PhantomData<(G, ConcreteCircuit)>, } -struct Assembly { - selectors: Vec>, +/// Region implementation used by Layout +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) struct LayoutRegion { + /// The name of the region. Not required to be unique. + pub(crate) name: String, + /// The columns used by this region. + pub(crate) columns: HashSet, + /// The row that this region starts on, if known. + pub(crate) offset: Option, + /// The number of rows that this region takes up. + pub(crate) rows: usize, + /// The cells assigned in this region. + pub(crate) cells: Vec<(RegionColumn, usize)>, +} + +/// Cost and graphing layouter +#[derive(Default, Debug)] +pub(crate) struct Layout { + /// k = 1 << n + pub(crate) k: u32, + /// Regions of the layout + pub(crate) regions: Vec, + current_region: Option, + /// Total row count + pub(crate) total_rows: usize, + /// Total advice rows + pub(crate) total_advice_rows: usize, + /// Total fixed rows + pub(crate) total_fixed_rows: usize, + /// Any cells assigned outside of a region. + pub(crate) loose_cells: Vec<(RegionColumn, usize)>, + /// Pairs of cells between which we have equality constraints. + pub(crate) equality: Vec<(Column, usize, Column, usize)>, + /// Selector assignments used for optimization pass + pub(crate) selectors: Vec>, } -impl Assignment for Assembly { - fn enter_region(&mut self, _: N) +impl Layout { + /// Creates a empty layout + pub fn new(k: u32, n: usize, num_selectors: usize) -> Self { + Layout { + k, + regions: vec![], + current_region: None, + total_rows: 0, + total_advice_rows: 0, + total_fixed_rows: 0, + /// Any cells assigned outside of a region. + loose_cells: vec![], + /// Pairs of cells between which we have equality constraints. + equality: vec![], + /// Selector assignments used for optimization pass + selectors: vec![vec![false; n]; num_selectors], + } + } + + /// Update layout metadata + pub fn update(&mut self, column: RegionColumn, row: usize) { + self.total_rows = cmp::max(self.total_rows, row + 1); + + if let RegionColumn::Column(col) = column { + match col.column_type() { + Any::Advice(_) => { + self.total_advice_rows = cmp::max(self.total_advice_rows, row + 1) + } + Any::Fixed => self.total_fixed_rows = cmp::max(self.total_fixed_rows, row + 1), + _ => {} + } + } + + if let Some(region) = self.current_region { + let region = &mut self.regions[region]; + region.columns.insert(column); + + // The region offset is the earliest row assigned to. + let mut offset = region.offset.unwrap_or(row); + if row < offset { + // The first row assigned was not at offset 0 within the region. + region.rows += offset - row; + offset = row; + } + // The number of rows in this region is the gap between the earliest and + // latest rows assigned. + region.rows = cmp::max(region.rows, row - offset + 1); + region.offset = Some(offset); + + region.cells.push((column, row)); + } else { + self.loose_cells.push((column, row)); + } + } +} + +impl Assignment for Layout { + fn enter_region(&mut self, name_fn: N) where NR: Into, N: FnOnce() -> NR, { - // Do nothing; we don't care about regions in this context. + assert!(self.current_region.is_none()); + self.current_region = Some(self.regions.len()); + self.regions.push(LayoutRegion { + name: name_fn().into(), + columns: HashSet::default(), + offset: None, + rows: 0, + cells: vec![], + }) + } + + fn annotate_column(&mut self, _: A, _: Column) + where + A: FnOnce() -> AR, + AR: Into, + { } fn exit_region(&mut self) { - // Do nothing; we don't care about regions in this context. + assert!(self.current_region.is_some()); + self.current_region = None; } fn enable_selector(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error> @@ -64,8 +182,13 @@ impl Assignment for Assembly { A: FnOnce() -> AR, AR: Into, { - self.selectors[selector.0][row] = true; + if let Some(cell) = self.selectors[selector.0].get_mut(row) { + *cell = true; + } else { + return Err(Error::not_enough_rows_available(self.k)); + } + self.update((*selector).into(), row); Ok(()) } @@ -76,8 +199,8 @@ impl Assignment for Assembly { fn assign_advice( &mut self, _: A, - _: Column, - _: usize, + column: Column, + row: usize, _: V, ) -> Result<(), Error> where @@ -86,14 +209,15 @@ impl Assignment for Assembly { A: FnOnce() -> AR, AR: Into, { + self.update(Column::::from(column).into(), row); Ok(()) } fn assign_fixed( &mut self, _: A, - _: Column, - _: usize, + column: Column, + row: usize, _: V, ) -> Result<(), Error> where @@ -102,10 +226,18 @@ impl Assignment for Assembly { A: FnOnce() -> AR, AR: Into, { + self.update(Column::::from(column).into(), row); Ok(()) } - fn copy(&mut self, _: Column, _: usize, _: Column, _: usize) -> Result<(), Error> { + fn copy( + &mut self, + l_col: Column, + l_row: usize, + r_col: Column, + r_row: usize, + ) -> Result<(), crate::plonk::Error> { + self.equality.push((l_col, l_row, r_col, r_row)); Ok(()) } @@ -122,14 +254,6 @@ impl Assignment for Assembly { Value::unknown() } - fn annotate_column(&mut self, _annotation: A, _column: Column) - where - A: FnOnce() -> AR, - AR: Into, - { - // Do nothing - } - fn push_namespace(&mut self, _: N) where NR: Into, @@ -147,24 +271,19 @@ impl> CircuitCost Self { + pub fn measure(k: u32, circuit: &ConcreteCircuit) -> Self { // Collect the layout details. let mut cs = ConstraintSystem::default(); - #[cfg(feature = "circuit-params")] - let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); - #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut cs); - let mut assembly = Assembly { - selectors: vec![vec![false; 1 << k]; cs.num_selectors], - }; + let mut layout = Layout::new(k, 1 << k, cs.num_selectors); ConcreteCircuit::FloorPlanner::synthesize( - &mut assembly, + &mut layout, circuit, config, cs.constants.clone(), ) .unwrap(); - let (cs, _) = cs.compress_selectors(assembly.selectors); + let (cs, _) = cs.compress_selectors(layout.selectors); assert!((1 << k) >= cs.minimum_rows()); @@ -220,6 +339,15 @@ impl> CircuitCost> CircuitCost> CircuitCost From> for usize { + proof.polycomm.len(point, scalar) } } + +#[cfg(test)] +mod tests { + use halo2curves::pasta::{Eq, Fp}; + + use crate::circuit::SimpleFloorPlanner; + + use super::*; + + #[test] + fn circuit_cost_without_permutation() { + const K: u32 = 4; + + struct MyCircuit; + impl Circuit for MyCircuit { + type Config = (); + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(_meta: &mut ConstraintSystem) -> Self::Config {} + + fn synthesize( + &self, + _config: Self::Config, + _layouter: impl crate::circuit::Layouter, + ) -> Result<(), Error> { + Ok(()) + } + } + CircuitCost::::measure(K, &MyCircuit).proof_size(1); + } +} diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index 32f6e56d3c..95951edfb3 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -101,16 +101,17 @@ impl FailureLocation { .iter() .enumerate() .find(|(_, r)| { - if r.rows.is_none() { - return false; + if let Some((start, end)) = r.rows { + // We match the region if any input columns overlap, rather than all of + // them, because matching complex selector columns is hard. As long as + // regions are rectangles, and failures occur due to assignments entirely + // within single regions, "any" will be equivalent to "all". If these + // assumptions change, we'll start getting bug reports from users :) + (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns) + } else { + // Zero-area region + false } - let (start, end) = r.rows.unwrap(); - // We match the region if any input columns overlap, rather than all of - // them, because matching complex selector columns is hard. As long as - // regions are rectangles, and failures occur due to assignments entirely - // within single regions, "any" will be equivalent to "all". If these - // assumptions change, we'll start getting bug reports from users :) - (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns) }) .map(|(r_i, r)| FailureLocation::InRegion { region: (r_i, r.name.clone(), r.annotations.clone()).into(), diff --git a/halo2_proofs/src/dev/graph/layout.rs b/halo2_proofs/src/dev/graph/layout.rs index 2c9cbe2d9c..7d00434aa2 100644 --- a/halo2_proofs/src/dev/graph/layout.rs +++ b/halo2_proofs/src/dev/graph/layout.rs @@ -3,16 +3,13 @@ use plotters::{ coord::Shift, prelude::{DrawingArea, DrawingAreaErrorKind, DrawingBackend}, }; -use std::cmp; use std::collections::HashSet; use std::ops::Range; use crate::{ - circuit::{layouter::RegionColumn, Value}, - plonk::{ - Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, - Fixed, FloorPlanner, Instance, Selector, - }, + circuit::layouter::RegionColumn, + dev::cost::Layout, + plonk::{Any, Circuit, Column, ConstraintSystem, FloorPlanner}, }; /// Graphical renderer for circuit layouts. @@ -324,196 +321,3 @@ impl CircuitLayout { Ok(()) } } - -#[derive(Debug)] -struct Region { - /// The name of the region. Not required to be unique. - name: String, - /// The columns used by this region. - columns: HashSet, - /// The row that this region starts on, if known. - offset: Option, - /// The number of rows that this region takes up. - rows: usize, - /// The cells assigned in this region. We store this as a `Vec` so that if any cells - /// are double-assigned, they will be visibly darker. - cells: Vec<(RegionColumn, usize)>, -} - -#[derive(Default)] -struct Layout { - k: u32, - regions: Vec, - current_region: Option, - total_rows: usize, - /// Any cells assigned outside of a region. We store this as a `Vec` so that if any - /// cells are double-assigned, they will be visibly darker. - loose_cells: Vec<(RegionColumn, usize)>, - /// Pairs of cells between which we have equality constraints. - equality: Vec<(Column, usize, Column, usize)>, - /// Selector assignments used for optimization pass - selectors: Vec>, -} - -impl Layout { - fn new(k: u32, n: usize, num_selectors: usize) -> Self { - Layout { - k, - regions: vec![], - current_region: None, - total_rows: 0, - /// Any cells assigned outside of a region. We store this as a `Vec` so that if any - /// cells are double-assigned, they will be visibly darker. - loose_cells: vec![], - /// Pairs of cells between which we have equality constraints. - equality: vec![], - /// Selector assignments used for optimization pass - selectors: vec![vec![false; n]; num_selectors], - } - } - - fn update(&mut self, column: RegionColumn, row: usize) { - self.total_rows = cmp::max(self.total_rows, row + 1); - - if let Some(region) = self.current_region { - let region = &mut self.regions[region]; - region.columns.insert(column); - - // The region offset is the earliest row assigned to. - let mut offset = region.offset.unwrap_or(row); - if row < offset { - // The first row assigned was not at offset 0 within the region. - region.rows += offset - row; - offset = row; - } - // The number of rows in this region is the gap between the earliest and - // latest rows assigned. - region.rows = cmp::max(region.rows, row - offset + 1); - region.offset = Some(offset); - - region.cells.push((column, row)); - } else { - self.loose_cells.push((column, row)); - } - } -} - -impl Assignment for Layout { - fn enter_region(&mut self, name_fn: N) - where - NR: Into, - N: FnOnce() -> NR, - { - assert!(self.current_region.is_none()); - self.current_region = Some(self.regions.len()); - self.regions.push(Region { - name: name_fn().into(), - columns: HashSet::default(), - offset: None, - rows: 0, - cells: vec![], - }) - } - - fn exit_region(&mut self) { - assert!(self.current_region.is_some()); - self.current_region = None; - } - - fn enable_selector(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error> - where - A: FnOnce() -> AR, - AR: Into, - { - if let Some(cell) = self.selectors[selector.0].get_mut(row) { - *cell = true; - } else { - return Err(Error::not_enough_rows_available(self.k)); - } - - self.update((*selector).into(), row); - Ok(()) - } - - fn query_instance(&self, _: Column, _: usize) -> Result, Error> { - Ok(Value::unknown()) - } - - fn assign_advice( - &mut self, - _: A, - column: Column, - row: usize, - _: V, - ) -> Result<(), Error> - where - V: FnOnce() -> Value, - VR: Into>, - A: FnOnce() -> AR, - AR: Into, - { - self.update(Column::::from(column).into(), row); - Ok(()) - } - - fn assign_fixed( - &mut self, - _: A, - column: Column, - row: usize, - _: V, - ) -> Result<(), Error> - where - V: FnOnce() -> Value, - VR: Into>, - A: FnOnce() -> AR, - AR: Into, - { - self.update(Column::::from(column).into(), row); - Ok(()) - } - - fn copy( - &mut self, - l_col: Column, - l_row: usize, - r_col: Column, - r_row: usize, - ) -> Result<(), crate::plonk::Error> { - self.equality.push((l_col, l_row, r_col, r_row)); - Ok(()) - } - - fn fill_from_row( - &mut self, - _: Column, - _: usize, - _: Value>, - ) -> Result<(), Error> { - Ok(()) - } - - fn get_challenge(&self, _: Challenge) -> Value { - Value::unknown() - } - - fn annotate_column(&mut self, _annotation: A, _column: Column) - where - A: FnOnce() -> AR, - AR: Into, - { - // Do nothing - } - - fn push_namespace(&mut self, _: N) - where - NR: Into, - N: FnOnce() -> NR, - { - // Do nothing; we don't care about namespaces in this context. - } - - fn pop_namespace(&mut self, _: Option) { - // Do nothing; we don't care about namespaces in this context. - } -} diff --git a/halo2_proofs/src/dev/tfp.rs b/halo2_proofs/src/dev/tfp.rs new file mode 100644 index 0000000000..011ba3cac0 --- /dev/null +++ b/halo2_proofs/src/dev/tfp.rs @@ -0,0 +1,508 @@ +use std::{fmt, marker::PhantomData}; + +use ff::Field; +use tracing::{debug, debug_span, span::EnteredSpan}; + +use crate::{ + circuit::{ + layouter::{RegionLayouter, SyncDeps}, + AssignedCell, Cell, Layouter, Region, Table, Value, + }, + plonk::{ + Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, + Fixed, FloorPlanner, Instance, Selector, + }, +}; + +/// A helper type that augments a [`FloorPlanner`] with [`tracing`] spans and events. +/// +/// `TracingFloorPlanner` can be used to instrument your circuit and determine exactly +/// what is happening during a particular run of keygen or proving. This can be useful for +/// identifying unexpected non-determinism or changes to a circuit. +/// +/// # No stability guarantees +/// +/// The `tracing` output is intended for use during circuit development. It should not be +/// considered production-stable, and the precise format or data exposed may change at any +/// time. +/// +/// # Examples +/// +/// ``` +/// use ff::Field; +/// use halo2_proofs::{ +/// circuit::{floor_planner, Layouter, Value}, +/// dev::TracingFloorPlanner, +/// plonk::{Circuit, ConstraintSystem, Error}, +/// }; +/// +/// # struct MyCircuit { +/// # some_witness: Value, +/// # }; +/// # #[derive(Clone)] +/// # struct MyConfig; +/// impl Circuit for MyCircuit { +/// // Wrap `TracingFloorPlanner` around your existing floor planner of choice. +/// //type FloorPlanner = floor_planner::V1; +/// type FloorPlanner = TracingFloorPlanner; +/// +/// // The rest of your `Circuit` implementation is unchanged. +/// type Config = MyConfig; +/// +/// #[cfg(feature = "circuit-params")] +/// type Params = (); +/// +/// fn without_witnesses(&self) -> Self { +/// Self { some_witness: Value::unknown() } +/// } +/// +/// fn configure(meta: &mut ConstraintSystem) -> Self::Config { +/// // .. +/// # todo!() +/// } +/// +/// fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error> { +/// // .. +/// # todo!() +/// } +/// } +/// +/// #[test] +/// fn some_circuit_test() { +/// // At the start of your test, enable tracing. +/// tracing_subscriber::fmt() +/// .with_max_level(tracing::Level::DEBUG) +/// .with_ansi(false) +/// .without_time() +/// .init(); +/// +/// // Now when the rest of the test runs, you will get `tracing` output for every +/// // operation that the circuit performs under the hood! +/// } +/// ``` +#[derive(Debug)] +pub struct TracingFloorPlanner { + _phantom: PhantomData

, +} + +impl FloorPlanner for TracingFloorPlanner

{ + fn synthesize + SyncDeps, C: Circuit>( + cs: &mut CS, + circuit: &C, + config: C::Config, + constants: Vec>, + ) -> Result<(), Error> { + P::synthesize( + &mut TracingAssignment::new(cs), + &TracingCircuit::borrowed(circuit), + config, + constants, + ) + } +} + +/// A helper type that augments a [`Circuit`] with [`tracing`] spans and events. +enum TracingCircuit<'c, F: Field, C: Circuit> { + Borrowed(&'c C, PhantomData), + Owned(C, PhantomData), +} + +impl<'c, F: Field, C: Circuit> TracingCircuit<'c, F, C> { + fn borrowed(circuit: &'c C) -> Self { + Self::Borrowed(circuit, PhantomData) + } + + fn owned(circuit: C) -> Self { + Self::Owned(circuit, PhantomData) + } + + fn inner_ref(&self) -> &C { + match self { + TracingCircuit::Borrowed(circuit, ..) => circuit, + TracingCircuit::Owned(circuit, ..) => circuit, + } + } +} + +impl<'c, F: Field, C: Circuit> Circuit for TracingCircuit<'c, F, C> { + type Config = C::Config; + type FloorPlanner = C::FloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::owned(self.inner_ref().without_witnesses()) + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let _span = debug_span!("configure").entered(); + C::configure(meta) + } + + fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error> { + let _span = debug_span!("synthesize").entered(); + self.inner_ref() + .synthesize(config, TracingLayouter::new(layouter)) + } +} + +/// A helper type that augments a [`Layouter`] with [`tracing`] spans and events. +struct TracingLayouter> { + layouter: L, + namespace_spans: Vec, + _phantom: PhantomData, +} + +impl> TracingLayouter { + fn new(layouter: L) -> Self { + Self { + layouter, + namespace_spans: vec![], + _phantom: PhantomData, + } + } +} + +impl> Layouter for TracingLayouter { + 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 _span = debug_span!("region", name = name().into()).entered(); + self.layouter.assign_region(name, |region| { + let mut region = TracingRegion(region); + let region: &mut dyn RegionLayouter = &mut region; + assignment(region.into()) + }) + } + + fn assign_table(&mut self, name: N, assignment: A) -> Result<(), Error> + where + A: FnMut(Table<'_, F>) -> Result<(), Error>, + N: Fn() -> NR, + NR: Into, + { + let _span = debug_span!("table", name = name().into()).entered(); + self.layouter.assign_table(name, assignment) + } + + fn constrain_instance( + &mut self, + cell: Cell, + column: Column, + row: usize, + ) -> Result<(), Error> { + self.layouter.constrain_instance(cell, column, row) + } + + fn get_challenge(&self, _: Challenge) -> Value { + Value::unknown() + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + let name = name_fn().into(); + self.namespace_spans.push(debug_span!("ns", name).entered()); + self.layouter.push_namespace(|| name); + } + + fn pop_namespace(&mut self, gadget_name: Option) { + self.layouter.pop_namespace(gadget_name); + self.namespace_spans.pop(); + } +} + +fn debug_value_and_return_cell(value: AssignedCell) -> Cell { + if let Some(v) = value.value().into_option() { + debug!(target: "assigned", value = ?v); + } + value.cell() +} + +/// A helper type that augments a [`Region`] with [`tracing`] spans and events. +#[derive(Debug)] +struct TracingRegion<'r, F: Field>(Region<'r, F>); + +impl<'r, F: Field> RegionLayouter for TracingRegion<'r, F> { + fn enable_selector<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + selector: &Selector, + offset: usize, + ) -> Result<(), Error> { + let _guard = debug_span!("enable_selector", name = annotation(), offset = offset).entered(); + debug!(target: "layouter", "Entered"); + self.0.enable_selector(annotation, selector, offset) + } + + fn name_column<'v>( + &'v mut self, + _: &'v (dyn std::ops::Fn() -> std::string::String + 'v), + _: Column, + ) { + } + + fn assign_advice<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result { + let _guard = + debug_span!("assign_advice", name = annotation(), column = ?column, offset = offset) + .entered(); + debug!(target: "layouter", "Entered"); + self.0 + .assign_advice(annotation, column, offset, to) + .map(debug_value_and_return_cell) + } + + fn assign_advice_from_constant<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + constant: Assigned, + ) -> Result { + let _guard = debug_span!("assign_advice_from_constant", + name = annotation(), + column = ?column, + offset = offset, + constant = ?constant, + ) + .entered(); + debug!(target: "layouter", "Entered"); + self.0 + .assign_advice_from_constant(annotation, column, offset, constant) + .map(debug_value_and_return_cell) + } + + fn assign_advice_from_instance<'v>( + &mut self, + annotation: &'v (dyn Fn() -> String + 'v), + instance: Column, + row: usize, + advice: Column, + offset: usize, + ) -> Result<(Cell, Value), Error> { + let _guard = debug_span!("assign_advice_from_instance", + name = annotation(), + instance = ?instance, + row = row, + advice = ?advice, + offset = offset, + ) + .entered(); + debug!(target: "layouter", "Entered"); + self.0 + .assign_advice_from_instance(annotation, instance, row, advice, offset) + .map(|value| { + if let Some(v) = value.value().into_option() { + debug!(target: "assigned", value = ?v); + } + (value.cell(), value.value().cloned()) + }) + } + + fn instance_value( + &mut self, + instance: Column, + row: usize, + ) -> Result, Error> { + self.0.instance_value(instance, row) + } + + fn assign_fixed<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Value> + 'v), + ) -> Result { + let _guard = + debug_span!("assign_fixed", name = annotation(), column = ?column, offset = offset) + .entered(); + debug!(target: "layouter", "Entered"); + self.0 + .assign_fixed(annotation, column, offset, to) + .map(debug_value_and_return_cell) + } + + fn constrain_constant(&mut self, cell: Cell, constant: Assigned) -> Result<(), Error> { + debug!(target: "constrain_constant", cell = ?cell, constant = ?constant); + self.0.constrain_constant(cell, constant) + } + + fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { + debug!(target: "constrain_equal", left = ?left, right = ?right); + self.0.constrain_equal(left, right) + } +} + +/// A helper type that augments an [`Assignment`] with [`tracing`] spans and events. +struct TracingAssignment<'cs, F: Field, CS: Assignment> { + cs: &'cs mut CS, + in_region: bool, + _phantom: PhantomData, +} + +impl<'cs, F: Field, CS: Assignment> TracingAssignment<'cs, F, CS> { + fn new(cs: &'cs mut CS) -> Self { + Self { + cs, + in_region: false, + _phantom: PhantomData, + } + } +} + +impl<'cs, F: Field, CS: Assignment> Assignment for TracingAssignment<'cs, F, CS> { + fn enter_region(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + self.in_region = true; + self.cs.enter_region(name_fn); + } + + fn annotate_column(&mut self, _: A, _: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + } + + fn exit_region(&mut self) { + self.cs.exit_region(); + self.in_region = false; + } + + fn enable_selector( + &mut self, + annotation: A, + selector: &Selector, + row: usize, + ) -> Result<(), Error> + where + A: FnOnce() -> AR, + AR: Into, + { + let annotation = annotation().into(); + if self.in_region { + debug!(target: "position", row = row); + } else { + debug!(target: "enable_selector", name = annotation, row = row); + } + self.cs.enable_selector(|| annotation, selector, row) + } + + fn query_instance(&self, column: Column, row: usize) -> Result, Error> { + let _guard = debug_span!("positioned").entered(); + debug!(target: "query_instance", column = ?column, row = row); + self.cs.query_instance(column, row) + } + + fn assign_advice( + &mut self, + annotation: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + let annotation = annotation().into(); + if self.in_region { + debug!(target: "position", row = row); + } else { + debug!(target: "assign_advice", name = annotation, column = ?column, row = row); + } + self.cs.assign_advice(|| annotation, column, row, to) + } + + fn assign_fixed( + &mut self, + annotation: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + let annotation = annotation().into(); + if self.in_region { + debug!(target: "position", row = row); + } else { + debug!(target: "assign_fixed", name = annotation, column = ?column, row = row); + } + self.cs.assign_fixed(|| annotation, column, row, to) + } + + fn copy( + &mut self, + left_column: Column, + left_row: usize, + right_column: Column, + right_row: usize, + ) -> Result<(), Error> { + let _guard = debug_span!("positioned").entered(); + debug!( + target: "copy", + left_column = ?left_column, + left_row = left_row, + right_column = ?right_column, + right_row = right_row, + ); + self.cs.copy(left_column, left_row, right_column, right_row) + } + + fn fill_from_row( + &mut self, + column: Column, + row: usize, + to: Value>, + ) -> Result<(), Error> { + let _guard = debug_span!("positioned").entered(); + debug!(target: "fill_from_row", column = ?column, row = row); + self.cs.fill_from_row(column, row, to) + } + + fn get_challenge(&self, _: Challenge) -> Value { + Value::unknown() + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // We enter namespace spans in TracingLayouter. + self.cs.push_namespace(name_fn) + } + + fn pop_namespace(&mut self, gadget_name: Option) { + self.cs.pop_namespace(gadget_name); + // We exit namespace spans in TracingLayouter. + } +} diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 3da9829306..86a47d65fc 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -564,7 +564,7 @@ impl InstanceQuery { /// they cannot simultaneously be used as general fixed columns. /// /// [`Layouter::assign_table`]: crate::circuit::Layouter::assign_table -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct TableColumn { /// The fixed column that this table column is stored in. /// diff --git a/halo2_proofs/src/plonk/error.rs b/halo2_proofs/src/plonk/error.rs index 33fdae9083..27ed3e049b 100644 --- a/halo2_proofs/src/plonk/error.rs +++ b/halo2_proofs/src/plonk/error.rs @@ -3,6 +3,7 @@ use std::error; use std::fmt; use std::io; +use super::TableColumn; use super::{Any, Column}; /// This is an error that could occur during proving or circuit synthesis. @@ -37,6 +38,8 @@ pub enum Error { /// The instance sets up a copy constraint involving a column that has not been /// included in the permutation. ColumnNotInPermutation(Column), + /// An error relating to a lookup table. + TableError(TableError), } impl From for Error { @@ -79,6 +82,7 @@ impl fmt::Display for Error { "Column {:?} must be included in the permutation. Help: try applying `meta.enable_equalty` on the column", column ), + Error::TableError(error) => write!(f, "{}", error) } } } @@ -91,3 +95,45 @@ impl error::Error for Error { } } } + +/// This is an error that could occur during table synthesis. +#[derive(Debug)] +pub enum TableError { + /// A `TableColumn` has not been assigned. + ColumnNotAssigned(TableColumn), + /// A Table has columns of uneven lengths. + UnevenColumnLengths((TableColumn, usize), (TableColumn, usize)), + /// Attempt to assign a used `TableColumn` + UsedColumn(TableColumn), + /// Attempt to overwrite a default value + OverwriteDefault(TableColumn, String, String), +} + +impl fmt::Display for TableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TableError::ColumnNotAssigned(col) => { + write!( + f, + "{:?} not fully assigned. Help: assign a value at offset 0.", + col + ) + } + TableError::UnevenColumnLengths((col, col_len), (table, table_len)) => write!( + f, + "{:?} has length {} while {:?} has length {}", + col, col_len, table, table_len + ), + TableError::UsedColumn(col) => { + write!(f, "{:?} has already been used", col) + } + TableError::OverwriteDefault(col, default, val) => { + write!( + f, + "Attempted to overwrite default value {} with {} in {:?}", + default, val, col + ) + } + } + } +}