diff --git a/executor/src/witgen/data_structures/mutable_state.rs b/executor/src/witgen/data_structures/mutable_state.rs index 77a83e3bc1..384329eb32 100644 --- a/executor/src/witgen/data_structures/mutable_state.rs +++ b/executor/src/witgen/data_structures/mutable_state.rs @@ -40,6 +40,15 @@ impl<'a, T: FieldElement, Q: QueryCallback> MutableState<'a, T, Q> { } } + /// Runs the first machine (unless there are no machines) end returns the generated columns. + /// The first machine might call other machines, which is handled automatically. + pub fn run(self) -> HashMap> { + if let Some(first_machine) = self.machines.first() { + first_machine.try_borrow_mut().unwrap().run_timed(&self); + } + self.take_witness_col_values() + } + /// Call the machine responsible for the right-hand-side of an identity given its ID /// and the row pair of the caller. pub fn call(&self, identity_id: u64, caller_rows: &RowPair<'_, 'a, T>) -> EvalResult<'a, T> { @@ -59,7 +68,7 @@ impl<'a, T: FieldElement, Q: QueryCallback> MutableState<'a, T, Q> { } /// Extracts the witness column values from the machines. - pub fn take_witness_col_values(self) -> HashMap> { + fn take_witness_col_values(self) -> HashMap> { // We keep the already processed machines mutably borrowed so that // "later" machines do not try to create new rows in already processed // machines. diff --git a/executor/src/witgen/machines/double_sorted_witness_machine_16.rs b/executor/src/witgen/machines/double_sorted_witness_machine_16.rs index 6e8c9c222a..43c9a37f87 100644 --- a/executor/src/witgen/machines/double_sorted_witness_machine_16.rs +++ b/executor/src/witgen/machines/double_sorted_witness_machine_16.rs @@ -132,6 +132,10 @@ impl<'a, T: FieldElement> DoubleSortedWitnesses16<'a, T> { return None; } + if parts.connections.is_empty() { + return None; + } + if !parts .connections .values() diff --git a/executor/src/witgen/machines/double_sorted_witness_machine_32.rs b/executor/src/witgen/machines/double_sorted_witness_machine_32.rs index c4a8ce8c5e..ac5fa2c0cb 100644 --- a/executor/src/witgen/machines/double_sorted_witness_machine_32.rs +++ b/executor/src/witgen/machines/double_sorted_witness_machine_32.rs @@ -102,6 +102,10 @@ impl<'a, T: FieldElement> DoubleSortedWitnesses32<'a, T> { return None; } + if parts.connections.is_empty() { + return None; + } + if !parts.connections.values().all(|i| i.is_permutation()) { return None; } diff --git a/executor/src/witgen/generator.rs b/executor/src/witgen/machines/dynamic_machine.rs similarity index 90% rename from executor/src/witgen/generator.rs rename to executor/src/witgen/machines/dynamic_machine.rs index 24b8ba0515..7c95996a0a 100644 --- a/executor/src/witgen/generator.rs +++ b/executor/src/witgen/machines/dynamic_machine.rs @@ -2,28 +2,24 @@ use powdr_ast::analyzed::AlgebraicExpression as Expression; use powdr_number::{DegreeType, FieldElement}; use std::collections::{BTreeMap, HashMap}; +use crate::witgen::block_processor::BlockProcessor; use crate::witgen::data_structures::finalizable_data::FinalizableData; +use crate::witgen::data_structures::multiplicity_counter::MultiplicityCounter; use crate::witgen::data_structures::mutable_state::MutableState; -use crate::witgen::machines::profiling::{record_end, record_start}; -use crate::witgen::processor::OuterQuery; -use crate::witgen::EvalValue; - -use super::affine_expression::AlgebraicVariable; -use super::block_processor::BlockProcessor; -use super::data_structures::multiplicity_counter::MultiplicityCounter; -use super::machines::{Machine, MachineParts}; -use super::processor::SolverState; -use super::rows::{Row, RowIndex, RowPair}; -use super::sequence_iterator::{DefaultSequenceIterator, ProcessingSequenceIterator}; -use super::vm_processor::VmProcessor; -use super::{EvalResult, FixedData, QueryCallback}; +use crate::witgen::machines::{Machine, MachineParts}; +use crate::witgen::processor::{OuterQuery, SolverState}; +use crate::witgen::rows::{Row, RowIndex, RowPair}; +use crate::witgen::sequence_iterator::{DefaultSequenceIterator, ProcessingSequenceIterator}; +use crate::witgen::vm_processor::VmProcessor; +use crate::witgen::{AlgebraicVariable, EvalResult, EvalValue, FixedData, QueryCallback}; struct ProcessResult<'a, T: FieldElement> { eval_value: EvalValue, T>, updated_data: SolverState<'a, T>, } -pub struct Generator<'a, T: FieldElement> { +/// A machine is generic and can handle lookups that generate a dynamic number of rows. +pub struct DynamicMachine<'a, T: FieldElement> { fixed_data: &'a FixedData<'a, T>, parts: MachineParts<'a, T>, data: FinalizableData, @@ -34,7 +30,7 @@ pub struct Generator<'a, T: FieldElement> { multiplicity_counter: MultiplicityCounter, } -impl<'a, T: FieldElement> Machine<'a, T> for Generator<'a, T> { +impl<'a, T: FieldElement> Machine<'a, T> for DynamicMachine<'a, T> { fn identity_ids(&self) -> Vec { self.parts.identity_ids() } @@ -43,6 +39,16 @@ impl<'a, T: FieldElement> Machine<'a, T> for Generator<'a, T> { &self.name } + /// Runs the machine without any arguments from the first row. + fn run>(&mut self, mutable_state: &MutableState<'a, T, Q>) { + assert!(self.data.is_empty()); + let first_row = self.compute_partial_first_row(mutable_state); + self.data = self + .process(first_row, 0, mutable_state, None, true) + .updated_data + .block; + } + fn process_plookup<'b, Q: QueryCallback>( &mut self, mutable_state: &MutableState<'a, T, Q>, @@ -110,7 +116,7 @@ impl<'a, T: FieldElement> Machine<'a, T> for Generator<'a, T> { } } -impl<'a, T: FieldElement> Generator<'a, T> { +impl<'a, T: FieldElement> DynamicMachine<'a, T> { pub fn new( name: String, fixed_data: &'a FixedData<'a, T>, @@ -132,18 +138,6 @@ impl<'a, T: FieldElement> Generator<'a, T> { } } - /// Runs the machine without any arguments from the first row. - pub fn run>(&mut self, mutable_state: &MutableState<'a, T, Q>) { - record_start(self.name()); - assert!(self.data.is_empty()); - let first_row = self.compute_partial_first_row(mutable_state); - self.data = self - .process(first_row, 0, mutable_state, None, true) - .updated_data - .block; - record_end(self.name()); - } - fn fill_remaining_rows>(&mut self, mutable_state: &MutableState<'a, T, Q>) { if self.data.len() < self.degree as usize + 1 { assert!(self.latch.is_some()); diff --git a/executor/src/witgen/machines/machine_extractor.rs b/executor/src/witgen/machines/machine_extractor.rs index 373ff62aa8..43e42536c4 100644 --- a/executor/src/witgen/machines/machine_extractor.rs +++ b/executor/src/witgen/machines/machine_extractor.rs @@ -13,11 +13,9 @@ use super::fixed_lookup_machine::FixedLookup; use super::sorted_witness_machine::SortedWitnesses; use super::FixedData; use super::KnownMachine; +use crate::witgen::machines::dynamic_machine::DynamicMachine; use crate::witgen::machines::Connection; -use crate::witgen::{ - generator::Generator, - machines::{write_once_memory::WriteOnceMemory, MachineParts}, -}; +use crate::witgen::machines::{write_once_memory::WriteOnceMemory, MachineParts}; use crate::Identity; use powdr_ast::analyzed::{ @@ -26,11 +24,6 @@ use powdr_ast::analyzed::{ use powdr_ast::parsed::{self, visitor::AllChildren}; use powdr_number::FieldElement; -pub struct ExtractionOutput<'a, T: FieldElement> { - pub machines: Vec>, - pub base_parts: MachineParts<'a, T>, -} - pub struct MachineExtractor<'a, T: FieldElement> { fixed: &'a FixedData<'a, T>, } @@ -40,12 +33,10 @@ impl<'a, T: FieldElement> MachineExtractor<'a, T> { Self { fixed } } - /// Finds machines in the witness columns and identities - /// and returns a list of machines and the identities + /// Finds machines in the witness columns and identities and returns a list of machines and the identities /// that are not "internal" to the machines. - pub fn split_out_machines(&self, identities: Vec<&'a Identity>) -> ExtractionOutput<'a, T> { - let mut machines: Vec> = vec![]; - + /// The first returned machine is the "main machine", i.e. a machine that has no incoming connections. + pub fn split_out_machines(&self, identities: Vec<&'a Identity>) -> Vec> { // Ignore prover functions that reference columns of later stages. let all_witnesses = self.fixed.witness_cols.keys().collect::>(); let current_stage_witnesses = self @@ -68,6 +59,30 @@ impl<'a, T: FieldElement> MachineExtractor<'a, T> { }) .collect::>(); + if self.fixed.stage() > 0 { + // We expect later-stage witness columns to be accumulators for lookup and permutation arguments. + // These don't behave like normal witness columns (e.g. in a block machine), and they might depend + // on witness columns of more than one machine. + // Therefore, we treat everything as one big machine. Also, we remove lookups and permutations, + // as they are assumed to be handled in stage 0. + let polynomial_identities = identities + .into_iter() + .filter(|identity| matches!(identity, Identity::Polynomial(_))) + .collect::>(); + let machine_parts = MachineParts::new( + self.fixed, + Default::default(), + polynomial_identities, + self.fixed.witness_cols.keys().collect::>(), + prover_functions, + ); + + return build_main_machine(self.fixed, machine_parts) + .into_iter() + .collect(); + } + let mut machines: Vec> = vec![]; + let mut publics = PublicsTracker::default(); let mut remaining_witnesses = current_stage_witnesses.clone(); let mut base_identities = identities.clone(); @@ -197,27 +212,33 @@ impl<'a, T: FieldElement> MachineExtractor<'a, T> { .collect::>(); log::trace!( - "\nThe base machine contains the following witnesses:\n{}\n identities:\n{}\n and prover functions:\n{}", - remaining_witnesses - .iter() - .map(|s| self.fixed.column_name(s)) - .sorted() - .format(", "), - base_identities - .iter() - .format("\n"), - base_prover_functions.iter().format("\n") - ); + "\nThe base machine contains the following witnesses:\n{}\n identities:\n{}\n and prover functions:\n{}", + remaining_witnesses + .iter() + .map(|s| self.fixed.column_name(s)) + .sorted() + .format(", "), + base_identities + .iter() + .format("\n"), + base_prover_functions.iter().format("\n") + ); - ExtractionOutput { - machines, - base_parts: MachineParts::new( - self.fixed, - Default::default(), - base_identities, - remaining_witnesses, - base_prover_functions, - ), + let base_parts = MachineParts::new( + self.fixed, + Default::default(), + base_identities, + remaining_witnesses, + base_prover_functions, + ); + + if let Some(main_machine) = build_main_machine(self.fixed, base_parts) { + std::iter::once(main_machine).chain(machines).collect() + } else { + if !machines.is_empty() { + log::error!("No main machine was extracted, but secondary machines were. Does the system have a cycle?"); + } + vec![] } } @@ -358,6 +379,14 @@ impl<'a> PublicsTracker<'a> { } } +fn build_main_machine<'a, T: FieldElement>( + fixed_data: &'a FixedData<'a, T>, + machine_parts: MachineParts<'a, T>, +) -> Option> { + (!machine_parts.witnesses.is_empty()) + .then(|| build_machine(fixed_data, machine_parts, |t| format!("Main machine ({t})"))) +} + fn build_machine<'a, T: FieldElement>( fixed_data: &'a FixedData<'a, T>, machine_parts: MachineParts<'a, T>, @@ -395,7 +424,9 @@ fn build_machine<'a, T: FieldElement>( log::debug!("Detected machine: {machine}"); KnownMachine::BlockMachine(machine) } else { - log::debug!("Detected machine: VM."); + log::debug!("Detected machine: Dynamic machine."); + // If there is a connection to this machine, all connections must have the same latch. + // If there is no connection to this machine, it is the main machine and there is no latch. let latch = machine_parts.connections .values() .fold(None, |existing_latch, identity| { @@ -411,13 +442,12 @@ fn build_machine<'a, T: FieldElement>( } else { Some(current_latch.clone()) } - }) - .unwrap(); - KnownMachine::Vm(Generator::new( - name_with_type("Vm"), + }); + KnownMachine::DynamicMachine(DynamicMachine::new( + name_with_type("Dynamic"), fixed_data, machine_parts.clone(), - Some(latch), + latch, )) } } diff --git a/executor/src/witgen/machines/mod.rs b/executor/src/witgen/machines/mod.rs index 2814c6ecb5..124a34b83a 100644 --- a/executor/src/witgen/machines/mod.rs +++ b/executor/src/witgen/machines/mod.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Display; +use dynamic_machine::DynamicMachine; use powdr_ast::analyzed::{ self, AlgebraicExpression, DegreeRange, PermutationIdentity, PhantomPermutationIdentity, PolyID, }; @@ -19,13 +20,13 @@ use self::profiling::{record_end, record_start}; use self::sorted_witness_machine::SortedWitnesses; use self::write_once_memory::WriteOnceMemory; -use super::generator::Generator; use super::rows::RowPair; use super::{EvalError, EvalResult, FixedData, QueryCallback}; mod block_machine; mod double_sorted_witness_machine_16; mod double_sorted_witness_machine_32; +mod dynamic_machine; mod fixed_lookup_machine; pub mod machine_extractor; pub mod profiling; @@ -35,6 +36,21 @@ mod write_once_memory; /// A machine is a set of witness columns and identities where the columns /// are used on the right-hand-side of lookups. It can process lookups. pub trait Machine<'a, T: FieldElement>: Send + Sync { + /// Runs the machine without any arguments from the first row (. + fn run_timed>(&mut self, mutable_state: &MutableState<'a, T, Q>) { + record_start(self.name()); + self.run(mutable_state); + record_end(self.name()); + } + + /// Runs the machine without any arguments from the first row. + fn run>(&mut self, _mutable_state: &MutableState<'a, T, Q>) { + unimplemented!( + "Running machine {} without a machine call is not supported.", + self.name() + ); + } + /// Like `process_plookup`, but also records the time spent in this machine. fn process_plookup_timed<'b, Q: QueryCallback>( &mut self, @@ -106,11 +122,23 @@ pub enum KnownMachine<'a, T: FieldElement> { DoubleSortedWitnesses32(DoubleSortedWitnesses32<'a, T>), WriteOnceMemory(WriteOnceMemory<'a, T>), BlockMachine(BlockMachine<'a, T>), - Vm(Generator<'a, T>), + DynamicMachine(DynamicMachine<'a, T>), FixedLookup(FixedLookup<'a, T>), } impl<'a, T: FieldElement> Machine<'a, T> for KnownMachine<'a, T> { + fn run>(&mut self, mutable_state: &MutableState<'a, T, Q>) { + match self { + KnownMachine::SortedWitnesses(m) => m.run(mutable_state), + KnownMachine::DoubleSortedWitnesses16(m) => m.run(mutable_state), + KnownMachine::DoubleSortedWitnesses32(m) => m.run(mutable_state), + KnownMachine::WriteOnceMemory(m) => m.run(mutable_state), + KnownMachine::BlockMachine(m) => m.run(mutable_state), + KnownMachine::DynamicMachine(m) => m.run(mutable_state), + KnownMachine::FixedLookup(m) => m.run(mutable_state), + } + } + fn process_plookup<'b, Q: QueryCallback>( &mut self, mutable_state: &'b MutableState<'a, T, Q>, @@ -133,7 +161,9 @@ impl<'a, T: FieldElement> Machine<'a, T> for KnownMachine<'a, T> { KnownMachine::BlockMachine(m) => { m.process_plookup(mutable_state, identity_id, caller_rows) } - KnownMachine::Vm(m) => m.process_plookup(mutable_state, identity_id, caller_rows), + KnownMachine::DynamicMachine(m) => { + m.process_plookup(mutable_state, identity_id, caller_rows) + } KnownMachine::FixedLookup(m) => { m.process_plookup(mutable_state, identity_id, caller_rows) } @@ -147,7 +177,7 @@ impl<'a, T: FieldElement> Machine<'a, T> for KnownMachine<'a, T> { KnownMachine::DoubleSortedWitnesses32(m) => m.name(), KnownMachine::WriteOnceMemory(m) => m.name(), KnownMachine::BlockMachine(m) => m.name(), - KnownMachine::Vm(m) => m.name(), + KnownMachine::DynamicMachine(m) => m.name(), KnownMachine::FixedLookup(m) => m.name(), } } @@ -162,7 +192,7 @@ impl<'a, T: FieldElement> Machine<'a, T> for KnownMachine<'a, T> { KnownMachine::DoubleSortedWitnesses32(m) => m.take_witness_col_values(mutable_state), KnownMachine::WriteOnceMemory(m) => m.take_witness_col_values(mutable_state), KnownMachine::BlockMachine(m) => m.take_witness_col_values(mutable_state), - KnownMachine::Vm(m) => m.take_witness_col_values(mutable_state), + KnownMachine::DynamicMachine(m) => m.take_witness_col_values(mutable_state), KnownMachine::FixedLookup(m) => m.take_witness_col_values(mutable_state), } } @@ -174,7 +204,7 @@ impl<'a, T: FieldElement> Machine<'a, T> for KnownMachine<'a, T> { KnownMachine::DoubleSortedWitnesses32(m) => m.identity_ids(), KnownMachine::WriteOnceMemory(m) => m.identity_ids(), KnownMachine::BlockMachine(m) => m.identity_ids(), - KnownMachine::Vm(m) => m.identity_ids(), + KnownMachine::DynamicMachine(m) => m.identity_ids(), KnownMachine::FixedLookup(m) => m.identity_ids(), } } diff --git a/executor/src/witgen/machines/sorted_witness_machine.rs b/executor/src/witgen/machines/sorted_witness_machine.rs index 82460fbad8..56cb4e577a 100644 --- a/executor/src/witgen/machines/sorted_witness_machine.rs +++ b/executor/src/witgen/machines/sorted_witness_machine.rs @@ -48,6 +48,9 @@ impl<'a, T: FieldElement> SortedWitnesses<'a, T> { if parts.identities.len() != 1 { return None; } + if parts.connections.is_empty() { + return None; + } let degree = parts.common_degree_range().max; diff --git a/executor/src/witgen/machines/write_once_memory.rs b/executor/src/witgen/machines/write_once_memory.rs index 543254e55f..1a8b05a94b 100644 --- a/executor/src/witgen/machines/write_once_memory.rs +++ b/executor/src/witgen/machines/write_once_memory.rs @@ -51,6 +51,10 @@ impl<'a, T: FieldElement> WriteOnceMemory<'a, T> { return None; } + if parts.connections.is_empty() { + return None; + } + if !parts.connections.values().all(|i| i.is_lookup()) { return None; } diff --git a/executor/src/witgen/mod.rs b/executor/src/witgen/mod.rs index 7debbf5794..d92a0b0464 100644 --- a/executor/src/witgen/mod.rs +++ b/executor/src/witgen/mod.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use itertools::Itertools; use machines::machine_extractor::MachineExtractor; -use machines::MachineParts; use powdr_ast::analyzed::{ AlgebraicExpression, AlgebraicReference, Analyzed, DegreeRange, Expression, FunctionValueDefinition, PolyID, PolynomialType, Symbol, SymbolKind, TypedExpression, @@ -15,18 +14,14 @@ use std::iter::once; use crate::constant_evaluator::VariablySizedColumn; use crate::witgen::data_structures::mutable_state::MutableState; -use crate::Identity; use self::data_structures::column_map::{FixedColumnMap, WitnessColumnMap}; pub use self::eval_result::{ Constraint, Constraints, EvalError, EvalResult, EvalStatus, EvalValue, IncompleteCause, }; -use self::generator::Generator; use self::global_constraints::GlobalConstraints; -use self::machines::machine_extractor::ExtractionOutput; use self::machines::profiling::{record_end, record_start, reset_and_print_profile_summary}; -use self::machines::Machine; mod affine_expression; pub(crate) mod analysis; @@ -35,7 +30,6 @@ mod data_structures; mod eval_result; mod expression_evaluator; pub mod fixed_evaluator; -mod generator; mod global_constraints; mod identity_processor; mod machines; @@ -204,55 +198,10 @@ impl<'a, 'b, T: FieldElement> WitnessGenerator<'a, 'b, T> { // These are already captured in the range constraints. let (fixed, retained_identities) = global_constraints::set_global_constraints(fixed, &identities); - let ExtractionOutput { - machines, - base_parts, - } = if self.stage == 0 { - MachineExtractor::new(&fixed).split_out_machines(retained_identities) - } else { - // We expect later-stage witness columns to be accumulators for lookup and permutation arguments. - // These don't behave like normal witness columns (e.g. in a block machine), and they might depend - // on witness columns of more than one machine. - // Therefore, we treat everything as one big machine. Also, we remove lookups and permutations, - // as they are assumed to be handled in stage 0. - let polynomial_identities = identities - .iter() - .filter(|identity| matches!(identity, Identity::Polynomial(_))) - .collect::>(); - ExtractionOutput { - machines: Vec::new(), - base_parts: MachineParts::new( - &fixed, - Default::default(), - polynomial_identities, - fixed.witness_cols.keys().collect::>(), - fixed.analyzed.prover_functions.iter().collect(), - ), - } - }; + let machines = MachineExtractor::new(&fixed).split_out_machines(retained_identities); - let mutable_state = MutableState::new(machines.into_iter(), &self.query_callback); - - let generator = (!base_parts.witnesses.is_empty()).then(|| { - let mut generator = Generator::new( - "Main Machine".to_string(), - &fixed, - base_parts, - // We could set the latch of the main VM here, but then we would have to detect it. - // Instead, the main VM will be computed in one block, directly continuing into the - // infinite loop after the first return. - None, - ); - - generator.run(&mutable_state); - generator - }); - - // Get columns from machines - let mut columns = generator - .map(|mut generator| generator.take_witness_col_values(&mutable_state)) - .unwrap_or_default(); - columns.extend(mutable_state.take_witness_col_values()); + // Run main machine and extract columns from all machines. + let mut columns = MutableState::new(machines.into_iter(), &self.query_callback).run(); Self::range_constraint_multiplicity_witgen(&fixed, &mut columns); @@ -508,6 +457,10 @@ impl<'a, T: FieldElement> FixedData<'a, T> { } } + pub fn stage(&self) -> u8 { + self.stage + } + pub fn global_range_constraints(&self) -> &GlobalConstraints { &self.global_range_constraints }