use std::{fmt, marker::PhantomData}; use ff::Field; use tracing::{debug, debug_span, span::EnteredSpan}; #[cfg(feature = "unstable-dynamic-lookups")] use crate::plonk::TableTag; use crate::{ circuit::{layouter::RegionLayouter, AssignedCell, Cell, Layouter, Region, Table, Value}, plonk::{ Advice, Any, Assigned, Assignment, 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; /// /// 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, 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; 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_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) } #[cfg(feature = "unstable-dynamic-lookups")] fn add_to_lookup(&mut self, table: TableTag, offset: usize) -> Result<(), Error> { debug!(target: "add_to_lookup", table = ?table, offset = ?offset); self.0.add_to_lookup(table, offset) } 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 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) } #[cfg(feature = "unstable-dynamic-lookups")] fn add_to_lookup(&mut self, table: TableTag, row: usize) -> Result<(), Error> { let _guard = debug_span!("positioned").entered(); debug!(target: "add_to_lookup", table = ?table, row = row); self.cs.add_to_lookup(table, 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 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. } }