use std::collections::{BTreeMap, HashSet}; use ast::{ analyzed::{AlgebraicExpression as Expression, AlgebraicReference, Identity, PolyID}, parsed::SelectedExpressions, }; use number::{DegreeType, FieldElement}; use parser_util::lines::indent; use crate::witgen::{query_processor::QueryProcessor, util::try_to_simple_poly, Constraint}; use super::{ affine_expression::AffineExpression, data_structures::{column_map::WitnessColumnMap, finalizable_data::FinalizableData}, identity_processor::IdentityProcessor, rows::{CellValue, Row, RowPair, RowUpdater, UnknownStrategy}, Constraints, EvalError, EvalValue, FixedData, MutableState, QueryCallback, }; type Left<'a, T> = Vec>; /// Data needed to handle an outer query. pub struct OuterQuery<'a, T: FieldElement> { /// A local copy of the left-hand side of the outer query. /// This will be mutated while processing the block. pub left: Left<'a, T>, /// The right-hand side of the outer query. pub right: &'a SelectedExpressions>, } impl<'a, T: FieldElement> OuterQuery<'a, T> { pub fn new(left: Left<'a, T>, right: &'a SelectedExpressions>) -> Self { Self { left, right } } pub fn is_complete(&self) -> bool { self.left.iter().all(|l| l.is_constant()) } } pub struct IdentityResult { /// Whether any progress was made by processing the identity pub progress: bool, /// Whether the identity is complete (i.e. all referenced values are known) pub is_complete: bool, } /// A basic processor that holds a set of rows and knows how to process identities and queries /// on any given row. /// The lifetimes mean the following: /// - `'a`: The duration of the entire witness generation (e.g. references to identities) /// - `'b`: The duration of this machine's call (e.g. the mutable references of the other machines) /// - `'c`: The duration of this Processor's lifetime (e.g. the reference to the identity processor) pub struct Processor<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> { /// The global index of the first row of [Processor::data]. row_offset: u64, /// The rows that are being processed. data: FinalizableData<'a, T>, /// The mutable state mutable_state: &'c mut MutableState<'a, 'b, T, Q>, /// The fixed data (containing information about all columns) fixed_data: &'a FixedData<'a, T>, /// The set of witness columns that are actually part of this machine. witness_cols: &'c HashSet, /// Whether a given witness column is relevant for this machine (faster than doing a contains check on witness_cols) is_relevant_witness: WitnessColumnMap, /// The outer query, if any. If there is none, processing an outer query will fail. outer_query: Option>, inputs: BTreeMap, previously_set_inputs: BTreeMap, } impl<'a, 'b, 'c, T: FieldElement, Q: QueryCallback> Processor<'a, 'b, 'c, T, Q> { pub fn new( row_offset: u64, data: FinalizableData<'a, T>, mutable_state: &'c mut MutableState<'a, 'b, T, Q>, fixed_data: &'a FixedData<'a, T>, witness_cols: &'c HashSet, ) -> Self { let is_relevant_witness = WitnessColumnMap::from( fixed_data .witness_cols .keys() .map(|poly_id| witness_cols.contains(&poly_id)), ); Self { row_offset, data, mutable_state, fixed_data, witness_cols, is_relevant_witness, outer_query: None, inputs: BTreeMap::new(), previously_set_inputs: BTreeMap::new(), } } pub fn with_outer_query(self, outer_query: OuterQuery<'a, T>) -> Processor<'a, 'b, 'c, T, Q> { log::trace!(" Extracting inputs:"); let mut inputs = BTreeMap::new(); for (l, r) in outer_query.left.iter().zip(&outer_query.right.expressions) { if let Some(right_poly) = try_to_simple_poly(r).map(|p| p.poly_id) { if let Some(l) = l.constant_value() { log::trace!(" {} = {}", r, l); inputs.insert(right_poly, l); } } } Processor { outer_query: Some(outer_query), inputs, ..self } } pub fn finshed_outer_query(&self) -> bool { self.outer_query .as_ref() .map(|outer_query| outer_query.is_complete()) .unwrap_or(true) } pub fn finish(self) -> FinalizableData<'a, T> { self.data } pub fn latch_value(&self, row_index: usize) -> Option { let row_pair = RowPair::from_single_row( &self.data[row_index], row_index as u64 + self.row_offset, self.fixed_data, UnknownStrategy::Unknown, ); self.outer_query .as_ref() .and_then(|outer_query| outer_query.right.selector.as_ref()) .and_then(|latch| row_pair.evaluate(latch).ok()) .and_then(|l| l.constant_value()) .map(|l| l.is_one()) } pub fn process_queries(&mut self, row_index: usize) -> Result> { let mut query_processor = QueryProcessor::new(self.fixed_data, self.mutable_state.query_callback); let global_row_index = self.row_offset + row_index as u64; let row_pair = RowPair::new( &self.data[row_index], &self.data[row_index + 1], global_row_index, self.fixed_data, UnknownStrategy::Unknown, ); let mut updates = EvalValue::complete(vec![]); for poly_id in self.fixed_data.witness_cols.keys() { if self.is_relevant_witness[&poly_id] { updates.combine(query_processor.process_query(&row_pair, &poly_id)?); } } Ok(self.apply_updates(row_index, &updates, || "queries".to_string())) } /// Given a row and identity index, computes any updates and applies them. /// @returns the `IdentityResult`. pub fn process_identity( &mut self, row_index: usize, identity: &'a Identity>, unknown_strategy: UnknownStrategy, ) -> Result> { // Create row pair let global_row_index = self.row_offset + row_index as u64; let row_pair = RowPair::new( &self.data[row_index], &self.data[row_index + 1], global_row_index, self.fixed_data, unknown_strategy, ); // Compute updates let mut identity_processor = IdentityProcessor::new(self.fixed_data, self.mutable_state); let updates = identity_processor .process_identity(identity, &row_pair) .map_err(|e| -> EvalError { log::warn!("Error in identity: {identity}"); log::warn!( "Known values in current row (local: {row_index}, global {global_row_index}):\n{}", self.data[row_index].render_values(false, Some(self.witness_cols)), ); if identity.contains_next_ref() { log::warn!( "Known values in next row (local: {}, global {}):\n{}", row_index + 1, global_row_index + 1, self.data[row_index + 1].render_values(false, Some(self.witness_cols)), ); } format!("{identity}:\n{}", indent(&format!("{e}"), " ")).into() })?; if unknown_strategy == UnknownStrategy::Zero { assert!(updates.constraints.is_empty()); return Ok(IdentityResult { progress: false, is_complete: false, }); } Ok(IdentityResult { progress: self.apply_updates(row_index, &updates, || identity.to_string()), is_complete: updates.is_complete(), }) } pub fn process_outer_query( &mut self, row_index: usize, ) -> Result<(bool, Constraints<&'a AlgebraicReference, T>), EvalError> { let OuterQuery { left, right } = self .outer_query .as_mut() .expect("Asked to process outer query, but it was not set!"); let row_pair = RowPair::new( &self.data[row_index], &self.data[row_index + 1], self.row_offset + row_index as u64, self.fixed_data, UnknownStrategy::Unknown, ); let mut identity_processor = IdentityProcessor::new(self.fixed_data, self.mutable_state); let updates = identity_processor .process_link(left, right, &row_pair) .map_err(|e| { log::warn!("Error in outer query: {e}"); log::warn!("Some of the following entries could not be matched:"); for (l, r) in left.iter().zip(right.expressions.iter()) { if let Ok(r) = row_pair.evaluate(r) { log::warn!(" => {} = {}", l, r); } } e })?; let progress = self.apply_updates(row_index, &updates, || "outer query".to_string()); let outer_assignments = updates .constraints .into_iter() .filter(|(poly, _)| !self.witness_cols.contains(&poly.poly_id)) .collect::>(); Ok((progress, outer_assignments)) } /// Sets the inputs to the values given in [VmProcessor::inputs] if they are not already set. /// Typically, inputs will have a constraint of the form: `((1 - instr__reset) * (_input' - _input)) = 0;` /// So, once the value of `_input` is set, this function will do nothing until the next reset instruction. /// However, if `_input` does become unconstrained, we need to undo all changes we've done so far. /// For this reason, we keep track of all changes we've done to inputs in [Processor::previously_set_inputs]. pub fn set_inputs_if_unset(&mut self, row_index: usize) -> bool { let mut input_updates = EvalValue::complete(vec![]); for (poly_id, value) in self.inputs.iter() { match &self.data[row_index][poly_id].value { CellValue::Known(_) => {} CellValue::RangeConstraint(_) | CellValue::Unknown => { input_updates.combine(EvalValue::complete([( &self.fixed_data.witness_cols[poly_id].poly, Constraint::Assignment(*value), )])); } }; } for (poly, _) in &input_updates.constraints { let poly_id = poly.poly_id; if let Some(start_row) = self.previously_set_inputs.remove(&poly_id) { log::trace!( " Resetting previously set inputs for column: {}", self.fixed_data.column_name(&poly_id) ); for row_index in start_row..row_index { self.data[row_index][&poly_id].value = CellValue::Unknown; } } } for (poly, _) in &input_updates.constraints { self.previously_set_inputs.insert(poly.poly_id, row_index); } self.apply_updates(row_index, &input_updates, || "inputs".to_string()) } fn apply_updates( &mut self, row_index: usize, updates: &EvalValue<&'a AlgebraicReference, T>, source_name: impl Fn() -> String, ) -> bool { if updates.constraints.is_empty() { return false; } log::trace!(" Updates from: {}", source_name()); // Build RowUpdater // (a bit complicated, because we need two mutable // references to elements of the same vector) let (current, next) = self.data.mutable_row_pair(row_index); let mut row_updater = RowUpdater::new(current, next, self.row_offset + row_index as u64); for (poly, c) in &updates.constraints { if self.witness_cols.contains(&poly.poly_id) { row_updater.apply_update(poly, c); } else if let Constraint::Assignment(v) = c { let left = &mut self.outer_query.as_mut().unwrap().left; log::trace!(" => {} (outer) = {}", poly, v); for l in left.iter_mut() { l.assign(poly, *v); } }; } true } pub fn len(&self) -> usize { self.data.len() } pub fn finalize_range(&mut self, range: impl Iterator) { self.data.finalize_range(range) } pub fn row(&self, i: usize) -> &Row<'a, T> { &self.data[i] } pub fn has_outer_query(&self) -> bool { self.outer_query.is_some() } /// Sets the ith row, extending the data if necessary. pub fn set_row(&mut self, i: usize, row: Row<'a, T>) { if i < self.data.len() { self.data[i] = row; } else { assert_eq!(i, self.data.len()); self.data.push(row); } } /// Checks whether a given identity is satisfied on a proposed row. pub fn check_row_pair( &mut self, row_index: usize, proposed_row: &Row<'a, T>, identity: &'a Identity>, // This could be computed from the identity, but should be pre-computed for performance reasons. has_next_reference: bool, ) -> bool { let mut identity_processor = IdentityProcessor::new(self.fixed_data, self.mutable_state); let row_pair = match has_next_reference { // Check whether identities with a reference to the next row are satisfied // when applied to the previous row and the proposed row. true => { assert!(row_index > 0); RowPair::new( &self.data[row_index - 1], proposed_row, row_index as DegreeType + self.row_offset - 1, self.fixed_data, UnknownStrategy::Zero, ) } // Check whether identities without a reference to the next row are satisfied // when applied to the proposed row. // Because we never access the next row, we can use [RowPair::from_single_row] here. false => RowPair::from_single_row( proposed_row, row_index as DegreeType + self.row_offset, self.fixed_data, UnknownStrategy::Zero, ), }; if identity_processor .process_identity(identity, &row_pair) .is_err() { log::debug!("Previous {:?}", &self.data[row_index - 1]); log::debug!("Proposed {:?}", proposed_row); log::debug!("Failed on identity: {}", identity); return false; } true } }