diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml index 4853999c..d321b53b 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -55,6 +55,8 @@ maybe-rayon = {version = "0.1.0", default-features = false} # Developer tooling dependencies plotters = { version = "0.3.0", default-features = false, optional = true } +serde = { version = "1", features = ["derive"], optional = true } +serde_json = { version = "1", optional = true } tabbycat = { version = "0.1", features = ["attributes"], optional = true } # Legacy circuit compatibility @@ -78,7 +80,7 @@ getrandom = { version = "0.2", features = ["js"] } [features] default = ["batch", "multicore"] multicore = ["maybe-rayon/threads"] -dev-graph = ["plotters", "tabbycat"] +dev-graph = ["plotters", "serde", "serde_json", "tabbycat"] test-dev-graph = [ "dev-graph", "plotters/bitmap_backend", diff --git a/halo2_proofs/src/circuit/layouter.rs b/halo2_proofs/src/circuit/layouter.rs index 81fa00be..f99f4204 100644 --- a/halo2_proofs/src/circuit/layouter.rs +++ b/halo2_proofs/src/circuit/layouter.rs @@ -122,6 +122,7 @@ pub struct RegionShape { /// The virtual column involved in a region. This includes concrete columns, /// as well as selectors that are not concrete columns at this stage. #[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "dev-graph", derive(serde::Serialize))] pub enum RegionColumn { /// Concrete column Column(Column), diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index a292b0ab..a085731e 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -36,7 +36,10 @@ mod graph; #[cfg(feature = "dev-graph")] #[cfg_attr(docsrs, doc(cfg(feature = "dev-graph")))] -pub use graph::{circuit_dot_graph, layout::CircuitLayout}; +pub use graph::{ + circuit_dot_graph, + layout::{render_to_json, CircuitLayout}, +}; #[derive(Debug)] struct Region { diff --git a/halo2_proofs/src/dev/cost.rs b/halo2_proofs/src/dev/cost.rs index 0c3c7efa..99b86fa2 100644 --- a/halo2_proofs/src/dev/cost.rs +++ b/halo2_proofs/src/dev/cost.rs @@ -11,6 +11,9 @@ use std::{ use ff::{Field, PrimeField}; use group::prime::PrimeGroup; +#[cfg(feature = "dev-graph")] +use serde::ser::SerializeStruct; + use crate::{ circuit::{layouter::RegionColumn, Value}, plonk::{ @@ -54,9 +57,37 @@ pub struct CircuitCost> { _marker: PhantomData<(G, ConcreteCircuit)>, } +#[cfg(feature = "dev-graph")] +impl serde::Serialize for Column { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Column", 2)?; + state.serialize_field( + "kind", + match self.column_type() { + Any::Advice => "advice", + Any::Fixed => "fixed", + Any::Instance => "instance", + }, + )?; + state.serialize_field("index", &self.index())?; + state.end() + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "dev-graph", derive(serde::Serialize))] +pub(crate) struct Cell { + pub(crate) column: RegionColumn, + pub(crate) row: usize, +} + /// Region implementation used by Layout #[allow(dead_code)] #[derive(Debug)] +#[cfg_attr(feature = "dev-graph", derive(serde::Serialize))] pub(crate) struct LayoutRegion { /// The name of the region. Not required to be unique. pub(crate) name: String, @@ -66,8 +97,9 @@ pub(crate) struct LayoutRegion { 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)>, + /// The cells assigned in this region. We store this as a `Vec` to track multiple + /// assignments to a cell. + pub(crate) cells: Vec, } /// Cost and graphing layouter @@ -84,10 +116,11 @@ pub(crate) struct Layout { 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)>, + /// Any cells assigned outside of a region. We store this as a `Vec` to track multiple + /// assignments to a cell. + pub(crate) loose_cells: Vec, /// Pairs of cells between which we have equality constraints. - pub(crate) equality: Vec<(Column, usize, Column, usize)>, + pub(crate) equality: Vec<(Cell, Cell)>, /// Selector assignments used for optimization pass pub(crate) selectors: Vec>, } @@ -139,9 +172,9 @@ impl Layout { region.rows = cmp::max(region.rows, row - offset + 1); region.offset = Some(offset); - region.cells.push((column, row)); + region.cells.push(Cell { column, row }); } else { - self.loose_cells.push((column, row)); + self.loose_cells.push(Cell { column, row }); } } } @@ -228,7 +261,16 @@ impl Assignment for Layout { r_col: Column, r_row: usize, ) -> Result<(), crate::plonk::Error> { - self.equality.push((l_col, l_row, r_col, r_row)); + self.equality.push(( + Cell { + column: l_col.into(), + row: l_row, + }, + Cell { + column: r_col.into(), + row: r_row, + }, + )); Ok(()) } diff --git a/halo2_proofs/src/dev/graph/layout.rs b/halo2_proofs/src/dev/graph/layout.rs index 965e0683..8c6ae5e6 100644 --- a/halo2_proofs/src/dev/graph/layout.rs +++ b/halo2_proofs/src/dev/graph/layout.rs @@ -3,12 +3,13 @@ use plotters::{ coord::Shift, prelude::{DrawingArea, DrawingAreaErrorKind, DrawingBackend}, }; + use std::collections::HashSet; use std::ops::Range; use crate::{ circuit::layouter::RegionColumn, - dev::cost::Layout, + dev::cost::{Cell, Layout, LayoutRegion}, plonk::{Any, Circuit, Column, ConstraintSystem, FloorPlanner}, }; @@ -242,26 +243,26 @@ impl CircuitLayout { // Darken the cells of the region that have been assigned to. for region in layout.regions { - for (column, row) in region.cells { + for Cell { column, row } in region.cells { draw_cell(&root, column_index(&cs, column), row)?; } } // Darken any loose cells that have been assigned to. - for (column, row) in layout.loose_cells { + for Cell { column, row } in layout.loose_cells { draw_cell(&root, column_index(&cs, column), row)?; } // Mark equality-constrained cells. if self.mark_equality_cells { let mut cells = HashSet::new(); - for (l_col, l_row, r_col, r_row) in &layout.equality { - let l_col = column_index(&cs, (*l_col).into()); - let r_col = column_index(&cs, (*r_col).into()); + for (l, r) in &layout.equality { + let l_col = column_index(&cs, l.column); + let r_col = column_index(&cs, r.column); // Deduplicate cells. - cells.insert((l_col, *l_row)); - cells.insert((r_col, *r_row)); + cells.insert((l_col, l.row)); + cells.insert((r_col, r.row)); } for (col, row) in cells { @@ -274,11 +275,11 @@ impl CircuitLayout { // Draw lines between equality-constrained cells. if self.show_equality_constraints { - for (l_col, l_row, r_col, r_row) in &layout.equality { - let l_col = column_index(&cs, (*l_col).into()); - let r_col = column_index(&cs, (*r_col).into()); + for (l, r) in &layout.equality { + let l_col = column_index(&cs, l.column); + let r_col = column_index(&cs, r.column); root.draw(&PathElement::new( - [(l_col, *l_row), (r_col, *r_row)], + [(l_col, l.row), (r_col, r.row)], ShapeStyle::from(&RED), ))?; } @@ -318,3 +319,35 @@ impl CircuitLayout { Ok(()) } } + +/// Renders the given circuit layout to a JSON string. +pub fn render_to_json>( + circuit: &ConcreteCircuit, +) -> Result { + // Collect the layout details. + let mut cs = ConstraintSystem::default(); + let config = ConcreteCircuit::configure(&mut cs); + let mut layout = Layout::default(); + ConcreteCircuit::FloorPlanner::synthesize(&mut layout, circuit, config, cs.constants).unwrap(); + + // Render. + #[derive(serde::Serialize)] + struct Circuit { + num_instance_columns: usize, + num_advice_columns: usize, + num_fixed_columns: usize, + total_rows: usize, + regions: Vec, + loose_cells: Vec, + selectors: Vec>, + } + serde_json::to_string(&Circuit { + num_instance_columns: cs.num_instance_columns, + num_advice_columns: cs.num_advice_columns, + num_fixed_columns: cs.num_fixed_columns, + total_rows: layout.total_rows, + regions: layout.regions, + loose_cells: layout.loose_cells, + selectors: layout.selectors, + }) +} diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index a6494d87..1ce9f9e9 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -253,6 +253,7 @@ impl TryFrom> for Column { /// } /// ``` #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "dev-graph", derive(serde::Serialize))] pub struct Selector(pub(crate) usize, bool); impl Selector {