Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.

Commit 6bcb1d0

Browse files
Add debug expr in EVMConstraintBuilder (#1500)
### Description Introduce the `debug_expression` method to the `EVMConstraintBuilder` which allows specifying an expression inside an ExecutionGadget to be evaluated and printed during assignment. This can be very useful for debugging. With this we can print the value of cells, but also of arbitrary expressions. For example, it allows printing the evaluation of all the expressions used in a lookup (previous to the RLC). ### Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ### Contents When calling `cb.debug_expression`, the expression is stored in the `EVMConstraintBuilder` along with a string. Later on, at assignment, whenever a step is assigned to an `ExecutionState` that had some debug expressions specified, those expressions will be evaluated and printed. #### Example usage In the `PushGadget::configure` method I include the following line: ```rust cb.debug_expression("push.code_hash", code_hash.expr()); ``` Then I run the a test that assigns this state (with `--nocapture` to see the stdout): ``` cargo test push_gadget_simple --release --all-features -- --nocapture ``` And I will get the following: ``` Debug expression "push.code_hash"=0x178027364c9ed08d00e38dbf197bbf65e45779c1d1b9dca1a229f7b7360892ce [offset=19, step=Op(PUSH1), expr=Advice { query_index: 591, column_index: 76, rotation: Rotation(0), phase: Phase(1) }] ``` --------- Co-authored-by: Chih Cheng Liang <[email protected]>
1 parent 3c8ce57 commit 6bcb1d0

File tree

4 files changed

+138
-39
lines changed

4 files changed

+138
-39
lines changed

zkevm-circuits/src/evm_circuit/execution.rs

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
constraint_builder::{
1717
BaseConstraintBuilder, ConstrainBuilderCommon, EVMConstraintBuilder,
1818
},
19-
rlc,
19+
evaluate_expression, rlc,
2020
},
2121
witness::{Block, Call, ExecStep, Transaction},
2222
},
@@ -225,6 +225,7 @@ pub struct ExecutionConfig<F> {
225225
step: Step<F>,
226226
pub(crate) height_map: HashMap<ExecutionState, usize>,
227227
stored_expressions_map: HashMap<ExecutionState, Vec<StoredExpression<F>>>,
228+
debug_expressions_map: HashMap<ExecutionState, Vec<(String, Expression<F>)>>,
228229
instrument: Instrument,
229230
// internal state gadgets
230231
begin_tx_gadget: Box<BeginTxGadget<F>>,
@@ -453,6 +454,7 @@ impl<F: Field> ExecutionConfig<F> {
453454
});
454455

455456
let mut stored_expressions_map = HashMap::new();
457+
let mut debug_expressions_map = HashMap::new();
456458

457459
macro_rules! configure_gadget {
458460
() => {
@@ -474,6 +476,7 @@ impl<F: Field> ExecutionConfig<F> {
474476
&step_curr,
475477
&mut height_map,
476478
&mut stored_expressions_map,
479+
&mut debug_expressions_map,
477480
&mut instrument,
478481
))
479482
})()
@@ -579,6 +582,7 @@ impl<F: Field> ExecutionConfig<F> {
579582
step: step_curr,
580583
height_map,
581584
stored_expressions_map,
585+
debug_expressions_map,
582586
instrument,
583587
};
584588

@@ -617,6 +621,7 @@ impl<F: Field> ExecutionConfig<F> {
617621
step_curr: &Step<F>,
618622
height_map: &mut HashMap<ExecutionState, usize>,
619623
stored_expressions_map: &mut HashMap<ExecutionState, Vec<StoredExpression<F>>>,
624+
debug_expressions_map: &mut HashMap<ExecutionState, Vec<(String, Expression<F>)>>,
620625
instrument: &mut Instrument,
621626
) -> G {
622627
// Configure the gadget with the max height first so we can find out the actual
@@ -657,6 +662,7 @@ impl<F: Field> ExecutionConfig<F> {
657662
step_next,
658663
height_map,
659664
stored_expressions_map,
665+
debug_expressions_map,
660666
instrument,
661667
G::NAME,
662668
G::EXECUTION_STATE,
@@ -678,6 +684,7 @@ impl<F: Field> ExecutionConfig<F> {
678684
step_next: &Step<F>,
679685
height_map: &mut HashMap<ExecutionState, usize>,
680686
stored_expressions_map: &mut HashMap<ExecutionState, Vec<StoredExpression<F>>>,
687+
debug_expressions_map: &mut HashMap<ExecutionState, Vec<(String, Expression<F>)>>,
681688
instrument: &mut Instrument,
682689
name: &'static str,
683690
execution_state: ExecutionState,
@@ -695,6 +702,7 @@ impl<F: Field> ExecutionConfig<F> {
695702

696703
instrument.on_gadget_built(execution_state, &cb);
697704

705+
let debug_expressions = cb.debug_expressions.clone();
698706
let (constraints, stored_expressions, _, meta) = cb.build();
699707
debug_assert!(
700708
!height_map.contains_key(&execution_state),
@@ -707,6 +715,7 @@ impl<F: Field> ExecutionConfig<F> {
707715
"execution state already configured"
708716
);
709717
stored_expressions_map.insert(execution_state, stored_expressions);
718+
debug_expressions_map.insert(execution_state, debug_expressions);
710719

711720
// Enforce the logic for this opcode
712721
let sel_step: &dyn Fn(&mut VirtualCells<F>) -> Expression<F> =
@@ -887,6 +896,8 @@ impl<F: Field> ExecutionConfig<F> {
887896
block: &Block<F>,
888897
challenges: &Challenges<Value<F>>,
889898
) -> Result<(), Error> {
899+
// Track number of calls to `layouter.assign_region` as layouter assignment passes.
900+
let mut assign_pass = 0;
890901
layouter.assign_region(
891902
|| "Execution step",
892903
|mut region| {
@@ -940,6 +951,7 @@ impl<F: Field> ExecutionConfig<F> {
940951
height,
941952
next.copied(),
942953
challenges,
954+
assign_pass,
943955
)?;
944956

945957
// q_step logic
@@ -976,6 +988,7 @@ impl<F: Field> ExecutionConfig<F> {
976988
end_block_not_last,
977989
height,
978990
challenges,
991+
assign_pass,
979992
)?;
980993

981994
for row_idx in offset..last_row {
@@ -998,6 +1011,7 @@ impl<F: Field> ExecutionConfig<F> {
9981011
height,
9991012
None,
10001013
challenges,
1014+
assign_pass,
10011015
)?;
10021016
self.assign_q_step(&mut region, offset, height)?;
10031017
// enable q_step_last
@@ -1019,6 +1033,7 @@ impl<F: Field> ExecutionConfig<F> {
10191033
|| Value::known(F::ZERO),
10201034
)?;
10211035

1036+
assign_pass += 1;
10221037
Ok(())
10231038
},
10241039
)
@@ -1070,6 +1085,7 @@ impl<F: Field> ExecutionConfig<F> {
10701085
step: &ExecStep,
10711086
height: usize,
10721087
challenges: &Challenges<Value<F>>,
1088+
assign_pass: usize,
10731089
) -> Result<(), Error> {
10741090
if offset_end <= offset_begin {
10751091
return Ok(());
@@ -1086,7 +1102,16 @@ impl<F: Field> ExecutionConfig<F> {
10861102
1,
10871103
offset_begin,
10881104
);
1089-
self.assign_exec_step_int(region, offset_begin, block, transaction, call, step)?;
1105+
self.assign_exec_step_int(
1106+
region,
1107+
offset_begin,
1108+
block,
1109+
transaction,
1110+
call,
1111+
step,
1112+
false,
1113+
assign_pass,
1114+
)?;
10901115

10911116
region.replicate_assignment_for_range(
10921117
|| format!("repeat {:?} rows", step.execution_state()),
@@ -1109,6 +1134,7 @@ impl<F: Field> ExecutionConfig<F> {
11091134
height: usize,
11101135
next: Option<(&Transaction, &Call, &ExecStep)>,
11111136
challenges: &Challenges<Value<F>>,
1137+
assign_pass: usize,
11121138
) -> Result<(), Error> {
11131139
if !matches!(step.execution_state(), ExecutionState::EndBlock) {
11141140
log::trace!(
@@ -1142,12 +1168,24 @@ impl<F: Field> ExecutionConfig<F> {
11421168
transaction_next,
11431169
call_next,
11441170
step_next,
1171+
true,
1172+
assign_pass,
11451173
)?;
11461174
}
11471175

1148-
self.assign_exec_step_int(region, offset, block, transaction, call, step)
1176+
self.assign_exec_step_int(
1177+
region,
1178+
offset,
1179+
block,
1180+
transaction,
1181+
call,
1182+
step,
1183+
false,
1184+
assign_pass,
1185+
)
11491186
}
11501187

1188+
#[allow(clippy::too_many_arguments)]
11511189
fn assign_exec_step_int(
11521190
&self,
11531191
region: &mut CachedRegion<'_, '_, F>,
@@ -1156,6 +1194,12 @@ impl<F: Field> ExecutionConfig<F> {
11561194
transaction: &Transaction,
11571195
call: &Call,
11581196
step: &ExecStep,
1197+
// Set to true when we're assigning the next step before the current step to have
1198+
// next step assignments for evaluation of the stored expressions in current step that
1199+
// depend on the next step.
1200+
is_next: bool,
1201+
// Layouter assignment pass
1202+
assign_pass: usize,
11591203
) -> Result<(), Error> {
11601204
self.step
11611205
.assign_exec_step(region, offset, block, call, step)?;
@@ -1299,19 +1343,32 @@ impl<F: Field> ExecutionConfig<F> {
12991343

13001344
// Fill in the witness values for stored expressions
13011345
let assigned_stored_expressions = self.assign_stored_expressions(region, offset, step)?;
1346+
// Both `SimpleFloorPlanner` and `V1` do two passes; we only enter here once (on the second
1347+
// pass).
1348+
if !is_next && assign_pass == 1 {
1349+
// We only want to print at the latest possible Phase. Currently halo2 implements 3
1350+
// phases. The `lookup_input` randomness is calculated after SecondPhase, so we print
1351+
// the debug expressions only once when we're at third phase, when `lookup_input` has
1352+
// a `Value::known`. This gets called for every `synthesize` call that the Layouter
1353+
// does.
1354+
region.challenges().lookup_input().assert_if_known(|_| {
1355+
self.print_debug_expressions(region, offset, step);
1356+
true
1357+
});
13021358

1303-
// enable with `RUST_LOG=debug`
1304-
if log::log_enabled!(log::Level::Debug) {
1305-
let is_padding_step = matches!(step.execution_state(), ExecutionState::EndBlock)
1306-
&& step.rw_indices_len() == 0;
1307-
if !is_padding_step {
1308-
// expensive function call
1309-
Self::check_rw_lookup(
1310-
&assigned_stored_expressions,
1311-
step,
1312-
block,
1313-
region.challenges(),
1314-
);
1359+
// enable with `RUST_LOG=debug`
1360+
if log::log_enabled!(log::Level::Debug) {
1361+
let is_padding_step = matches!(step.execution_state(), ExecutionState::EndBlock)
1362+
&& step.rw_indices_len() == 0;
1363+
if !is_padding_step {
1364+
// expensive function call
1365+
Self::check_rw_lookup(
1366+
&assigned_stored_expressions,
1367+
step,
1368+
block,
1369+
region.challenges(),
1370+
);
1371+
}
13151372
}
13161373
}
13171374
Ok(())
@@ -1338,6 +1395,30 @@ impl<F: Field> ExecutionConfig<F> {
13381395
Ok(assigned_stored_expressions)
13391396
}
13401397

1398+
fn print_debug_expressions(
1399+
&self,
1400+
region: &mut CachedRegion<'_, '_, F>,
1401+
offset: usize,
1402+
step: &ExecStep,
1403+
) {
1404+
for (name, expression) in self
1405+
.debug_expressions_map
1406+
.get(&step.execution_state())
1407+
.unwrap_or_else(|| panic!("Execution state unknown: {:?}", step.execution_state()))
1408+
{
1409+
let value = evaluate_expression(expression, region, offset);
1410+
let mut value_string = "unknown".to_string();
1411+
value.assert_if_known(|f| {
1412+
value_string = format!("{:?}", f);
1413+
true
1414+
});
1415+
println!(
1416+
"Debug expression \"{}\"={} [offset={}, step={:?}, expr={:?}]",
1417+
name, value_string, offset, step.exec_state, expression
1418+
);
1419+
}
1420+
}
1421+
13411422
fn check_rw_lookup(
13421423
assigned_stored_expressions: &[(String, F)],
13431424
step: &ExecStep,

zkevm-circuits/src/evm_circuit/execution/begin_tx.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
7878
let call_id = cb.curr.state.rw_counter.clone();
7979

8080
let tx_id = cb.query_cell(); // already constrain `if step_first && tx_id = 1` and `tx_id += 1` at EndTx
81+
82+
cb.debug_expression("tx_id", tx_id.expr());
8183
cb.call_context_lookup_write(
8284
Some(call_id.expr()),
8385
CallContextFieldTag::TxId,
@@ -89,6 +91,7 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
8991
CallContextFieldTag::IsSuccess,
9092
Word::from_lo_unchecked(reversion_info.is_persistent()),
9193
); // rwc_delta += 1
94+
cb.debug_expression(format!("call_id {}", 3), call_id.expr());
9295

9396
let [tx_nonce, tx_gas, tx_is_create, tx_call_data_length, tx_call_data_gas_cost] = [
9497
TxContextFieldTag::Nonce,

zkevm-circuits/src/evm_circuit/util.rs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -180,36 +180,45 @@ impl<F> Hash for StoredExpression<F> {
180180
}
181181
}
182182

183+
/// Evaluate an expression using a `CachedRegion` at `offset`.
184+
pub(crate) fn evaluate_expression<F: Field>(
185+
expr: &Expression<F>,
186+
region: &CachedRegion<'_, '_, F>,
187+
offset: usize,
188+
) -> Value<F> {
189+
expr.evaluate(
190+
&|scalar| Value::known(scalar),
191+
&|_| unimplemented!("selector column"),
192+
&|fixed_query| {
193+
Value::known(region.get_fixed(
194+
offset,
195+
fixed_query.column_index(),
196+
fixed_query.rotation(),
197+
))
198+
},
199+
&|advice_query| {
200+
Value::known(region.get_advice(
201+
offset,
202+
advice_query.column_index(),
203+
advice_query.rotation(),
204+
))
205+
},
206+
&|_| unimplemented!("instance column"),
207+
&|challenge| *region.challenges().indexed()[challenge.index()],
208+
&|a| -a,
209+
&|a, b| a + b,
210+
&|a, b| a * b,
211+
&|a, scalar| a * Value::known(scalar),
212+
)
213+
}
214+
183215
impl<F: Field> StoredExpression<F> {
184216
pub fn assign(
185217
&self,
186218
region: &mut CachedRegion<'_, '_, F>,
187219
offset: usize,
188220
) -> Result<Value<F>, Error> {
189-
let value = self.expr.evaluate(
190-
&|scalar| Value::known(scalar),
191-
&|_| unimplemented!("selector column"),
192-
&|fixed_query| {
193-
Value::known(region.get_fixed(
194-
offset,
195-
fixed_query.column_index(),
196-
fixed_query.rotation(),
197-
))
198-
},
199-
&|advice_query| {
200-
Value::known(region.get_advice(
201-
offset,
202-
advice_query.column_index(),
203-
advice_query.rotation(),
204-
))
205-
},
206-
&|_| unimplemented!("instance column"),
207-
&|challenge| *region.challenges().indexed()[challenge.index()],
208-
&|a| -a,
209-
&|a, b| a + b,
210-
&|a, b| a * b,
211-
&|a, scalar| a * Value::known(scalar),
212-
);
221+
let value = evaluate_expression(&self.expr, region, offset);
213222
self.cell.assign(region, offset, value)?;
214223
Ok(value)
215224
}

zkevm-circuits/src/evm_circuit/util/constraint_builder.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ pub(crate) struct EVMConstraintBuilder<'a, F: Field> {
307307
conditions: Vec<Expression<F>>,
308308
constraints_location: ConstraintLocation,
309309
stored_expressions: Vec<StoredExpression<F>>,
310+
pub(crate) debug_expressions: Vec<(String, Expression<F>)>,
310311

311312
meta: &'a mut ConstraintSystem<F>,
312313
}
@@ -353,6 +354,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> {
353354
constraints_location: ConstraintLocation::Step,
354355
stored_expressions: Vec::new(),
355356
meta,
357+
debug_expressions: Vec::new(),
356358
}
357359
}
358360

@@ -1671,4 +1673,8 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> {
16711673
None => 1.expr(),
16721674
}
16731675
}
1676+
1677+
pub fn debug_expression<S: Into<String>>(&mut self, name: S, expr: Expression<F>) {
1678+
self.debug_expressions.push((name.into(), expr));
1679+
}
16741680
}

0 commit comments

Comments
 (0)