diff --git a/autoprecompiles/src/adapter.rs b/autoprecompiles/src/adapter.rs index 351cdf9fbd..dc41c65dbd 100644 --- a/autoprecompiles/src/adapter.rs +++ b/autoprecompiles/src/adapter.rs @@ -6,6 +6,7 @@ use std::{fmt::Display, sync::Arc}; use powdr_number::FieldElement; use serde::{Deserialize, Serialize}; +use crate::EmpiricalConstraints; use crate::{ blocks::{BasicBlock, Instruction, Program}, constraint_optimizer::IsBusStateful, @@ -45,12 +46,19 @@ pub trait PgoAdapter { config: &PowdrConfig, vm_config: AdapterVmConfig, labels: BTreeMap>, + empirical_constraints: EmpiricalConstraints, ) -> Vec> { let filtered_blocks = blocks .into_iter() .filter(|block| !Self::Adapter::should_skip_block(block)) .collect(); - self.create_apcs_with_pgo(filtered_blocks, config, vm_config, labels) + self.create_apcs_with_pgo( + filtered_blocks, + config, + vm_config, + labels, + empirical_constraints, + ) } fn create_apcs_with_pgo( @@ -59,6 +67,7 @@ pub trait PgoAdapter { config: &PowdrConfig, vm_config: AdapterVmConfig, labels: BTreeMap>, + empirical_constraints: EmpiricalConstraints, ) -> Vec>; fn pc_execution_count(&self, _pc: u64) -> Option { diff --git a/autoprecompiles/src/empirical_constraints.rs b/autoprecompiles/src/empirical_constraints.rs index 8a5f8b018c..96ee227b09 100644 --- a/autoprecompiles/src/empirical_constraints.rs +++ b/autoprecompiles/src/empirical_constraints.rs @@ -6,6 +6,13 @@ use std::hash::Hash; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use crate::{ + adapter::Adapter, + blocks::BasicBlock, + expression::{AlgebraicExpression, AlgebraicReference}, + SymbolicConstraint, +}; + /// "Constraints" that were inferred from execution statistics. They hold empirically /// (most of the time), but are not guaranteed to hold in all cases. #[derive(Serialize, Deserialize, Clone, Default, Debug)] @@ -16,6 +23,9 @@ pub struct EmpiricalConstraints { /// For each basic block (identified by its starting PC), the equivalence classes of columns. /// Each equivalence class is a list of (instruction index in block, column index). pub equivalence_classes_by_block: BTreeMap>>, + /// Count of how many times each program counter was executed in the sampled executions. + /// This can be used to set a threshold for applying constraints only to frequently executed PCs. + pub pc_counts: BTreeMap, } /// Debug information mapping AIR ids to program counters and column names. @@ -61,6 +71,32 @@ impl EmpiricalConstraints { }) .or_insert(classes); } + + // Combine pc counts + for (pc, count) in other.pc_counts { + *self.pc_counts.entry(pc).or_insert(0) += count; + } + } + + pub fn apply_pc_threshold(&self, threshold: u64) -> Self { + EmpiricalConstraints { + column_ranges_by_pc: self + .column_ranges_by_pc + .iter() + .filter(|(pc, _)| self.pc_counts.get(pc).cloned().unwrap_or(0) >= threshold) + .map(|(pc, ranges)| (*pc, ranges.clone())) + .collect(), + equivalence_classes_by_block: self + .equivalence_classes_by_block + .iter() + .filter(|(&block_pc, _)| { + // For equivalence classes, we check the pc_counts of the first instruction in the block + self.pc_counts.get(&(block_pc as u32)).cloned().unwrap_or(0) >= threshold + }) + .map(|(block_pc, classes)| (*block_pc, classes.clone())) + .collect(), + pc_counts: self.pc_counts.clone(), + } } } @@ -88,6 +124,107 @@ fn merge_maps(map1: &mut BTreeMap, map2: BTreeMap { + empirical_constraints: EmpiricalConstraints, + algebraic_references: BTreeMap<(usize, usize), AlgebraicReference>, + block: &'a BasicBlock, +} + +impl<'a, A: Adapter> ConstraintGenerator<'a, A> { + pub fn new( + empirical_constraints: &EmpiricalConstraints, + subs: &[Vec], + columns: impl Iterator, + block: &'a BasicBlock, + ) -> Self { + let reverse_subs = subs + .iter() + .enumerate() + .flat_map(|(instr_index, subs)| { + subs.iter() + .enumerate() + .map(move |(col_index, &poly_id)| (poly_id, (instr_index, col_index))) + }) + .collect::>(); + let algebraic_references = columns + .map(|r| (*reverse_subs.get(&r.id).unwrap(), r.clone())) + .collect::>(); + + Self { + empirical_constraints: empirical_constraints + .apply_pc_threshold(EXECUTION_COUNT_THRESHOLD), + algebraic_references, + block, + } + } + + fn get_algebraic_reference(&self, instr_index: usize, col_index: usize) -> AlgebraicReference { + self.algebraic_references + .get(&(instr_index, col_index)) + .cloned() + .unwrap_or_else(|| { + panic!( + "Missing reference for (i: {}, col_index: {}, block_id: {})", + instr_index, col_index, self.block.start_pc + ) + }) + } + + pub fn range_constraints(&self) -> Vec::PowdrField>> { + let mut constraints = Vec::new(); + + for i in 0..self.block.statements.len() { + let pc = (self.block.start_pc + (i * 4) as u64) as u32; + let Some(range_constraints) = self.empirical_constraints.column_ranges_by_pc.get(&pc) + else { + continue; + }; + for (col_index, range) in range_constraints.iter().enumerate() { + // TODO: We could also enforce looser range constraints. + // This is a bit more complicated though, because we'd have to add bus interactions + // to actually enforce them. + if range.0 == range.1 { + let value = A::PowdrField::from(range.0 as u64); + let reference = self.get_algebraic_reference(i, col_index); + let constraint = AlgebraicExpression::Reference(reference) + - AlgebraicExpression::Number(value); + + constraints.push(SymbolicConstraint { expr: constraint }); + } + } + } + + constraints + } + + pub fn equivalence_constraints(&self) -> Vec::PowdrField>> { + let mut constraints = Vec::new(); + + if let Some(equivalence_classes) = self + .empirical_constraints + .equivalence_classes_by_block + .get(&self.block.start_pc) + { + for equivalence_class in equivalence_classes { + let first = equivalence_class.first().unwrap(); + let first_ref = self.get_algebraic_reference(first.0, first.1); + for other in equivalence_class.iter().skip(1) { + let other_ref = self.get_algebraic_reference(other.0, other.1); + let constraint = AlgebraicExpression::Reference(first_ref.clone()) + - AlgebraicExpression::Reference(other_ref.clone()); + constraints.push(SymbolicConstraint { expr: constraint }); + } + } + } + + constraints + } +} + /// Intersects multiple partitions of the same universe into a single partition. /// In other words, two elements are in the same equivalence class in the resulting partition /// if and only if they are in the same equivalence class in all input partitions. diff --git a/autoprecompiles/src/lib.rs b/autoprecompiles/src/lib.rs index 86a18f766d..90faedb71e 100644 --- a/autoprecompiles/src/lib.rs +++ b/autoprecompiles/src/lib.rs @@ -1,6 +1,7 @@ use crate::adapter::{Adapter, AdapterApc, AdapterVmConfig}; use crate::blocks::BasicBlock; use crate::bus_map::{BusMap, BusType}; +use crate::empirical_constraints::{ConstraintGenerator, EmpiricalConstraints}; use crate::evaluation::AirStats; use crate::expression_conversion::algebraic_to_grouped_expression; use crate::symbolic_machine_generator::convert_machine_field_type; @@ -429,6 +430,7 @@ pub fn build( vm_config: AdapterVmConfig, degree_bound: DegreeBound, apc_candidates_dir_path: Option<&Path>, + empirical_constraints: &EmpiricalConstraints, ) -> Result, crate::constraint_optimizer::Error> { let start = std::time::Instant::now(); @@ -438,6 +440,16 @@ pub fn build( &vm_config.bus_map, ); + // Generate constraints for optimistic precompiles. + let constraint_generator = ConstraintGenerator::::new( + empirical_constraints, + &column_allocator.subs, + machine.main_columns(), + &block, + ); + let range_analyzer_constraints = constraint_generator.range_constraints(); + let equivalence_analyzer_constraints = constraint_generator.equivalence_constraints(); + let labels = [("apc_start_pc", block.start_pc.to_string())]; metrics::counter!("before_opt_cols", &labels) .absolute(machine.unique_references().count() as u64); @@ -446,13 +458,44 @@ pub fn build( metrics::counter!("before_opt_interactions", &labels) .absolute(machine.unique_references().count() as u64); + let mut baseline = machine; + + // Optimize once without empirical constraints let (machine, column_allocator) = optimizer::optimize::( - machine, - vm_config.bus_interaction_handler, + baseline.clone(), + vm_config.bus_interaction_handler.clone(), degree_bound, &vm_config.bus_map, column_allocator, - )?; + ) + .unwrap(); + // Get the precompile that is guaranteed to always work + let guaranteed_precompile = machine.render(&vm_config.bus_map); + + let (machine, column_allocator, optimistic_precompile) = + if !range_analyzer_constraints.is_empty() || !equivalence_analyzer_constraints.is_empty() { + // Add empirical constraints + baseline.constraints.extend(range_analyzer_constraints); + baseline + .constraints + .extend(equivalence_analyzer_constraints); + + // Optimize again with empirical constraints + // TODO: Calling optimize twice is needed; otherwise the solver fails. + let (machine, column_allocator) = optimizer::optimize::( + baseline, + vm_config.bus_interaction_handler, + degree_bound, + &vm_config.bus_map, + column_allocator, + ) + .unwrap(); + let optimistic_precompile = machine.render(&vm_config.bus_map); + (machine, column_allocator, Some(optimistic_precompile)) + } else { + // If there are no empirical constraints, we can skip optimizing twice. + (machine, column_allocator, None) + }; // add guards to constraints that are not satisfied by zeroes let (machine, column_allocator) = add_guards(machine, column_allocator); @@ -469,14 +512,27 @@ pub fn build( let apc = Apc::new(block, machine, column_allocator); if let Some(path) = apc_candidates_dir_path { - let ser_path = path - .join(format!("apc_candidate_{}", apc.start_pc())) - .with_extension("cbor"); - std::fs::create_dir_all(path).expect("Failed to create directory for APC candidates"); - let file = - std::fs::File::create(&ser_path).expect("Failed to create file for APC candidate"); - let writer = BufWriter::new(file); - serde_cbor::to_writer(writer, &apc).expect("Failed to write APC candidate to file"); + if let Some(optimistic_precompile) = &optimistic_precompile { + // For debugging purposes, serialize the APC candidate to a file + let ser_path = path + .join(format!("apc_candidate_{}", apc.start_pc())) + .with_extension("cbor"); + std::fs::create_dir_all(path).expect("Failed to create directory for APC candidates"); + let file = + std::fs::File::create(&ser_path).expect("Failed to create file for APC candidate"); + let writer = BufWriter::new(file); + serde_cbor::to_writer(writer, &apc).expect("Failed to write APC candidate to file"); + + let dumb_path = path + .join(format!("apc_candidate_{}_guaranteed.txt", apc.start_pc())) + .with_extension("txt"); + std::fs::write(dumb_path, guaranteed_precompile).unwrap(); + + let ai_path = path + .join(format!("apc_candidate_{}_optimistic.txt", apc.start_pc())) + .with_extension("txt"); + std::fs::write(ai_path, optimistic_precompile).unwrap(); + } } metrics::gauge!("apc_gen_time_ms", &labels).set(start.elapsed().as_millis() as f64); diff --git a/autoprecompiles/src/memory_optimizer.rs b/autoprecompiles/src/memory_optimizer.rs index 1e1e07629e..fc9d32b04e 100644 --- a/autoprecompiles/src/memory_optimizer.rs +++ b/autoprecompiles/src/memory_optimizer.rs @@ -187,6 +187,20 @@ fn redundant_memory_interactions_indices< // In that case, we can replace both bus interactions with equality constraints // between the data that would have been sent and received. if let Some((previous_send, existing_values)) = memory_contents.remove(&addr) { + // TODO: This can happen which optimistic precompiles, because basic block can include + // big-word branching instructions. This hot fix is NOT sounds, because it does not handle + // a situation where we write a (small-word) limb, accesses the large word, and then the + // small-word limb again. + if existing_values.len() != mem_int.data().len() { + log::error!( + "Memory interaction data length mismatch: existing values = {}, new values = {}. Resetting memory.", + existing_values.iter().map(ToString::to_string).join(", "), + mem_int.data().iter().map(ToString::to_string).join(", ") + ); + memory_contents.clear(); + continue; + } + for (existing, new) in existing_values.iter().zip_eq(mem_int.data().iter()) { new_constraints.push(AlgebraicConstraint::assert_zero( existing.clone() - new.clone(), diff --git a/autoprecompiles/src/pgo/cell/mod.rs b/autoprecompiles/src/pgo/cell/mod.rs index 7086d5c35b..fe7ec84d3c 100644 --- a/autoprecompiles/src/pgo/cell/mod.rs +++ b/autoprecompiles/src/pgo/cell/mod.rs @@ -13,7 +13,7 @@ use crate::{ blocks::BasicBlock, evaluation::EvaluationResult, pgo::cell::selection::parallel_fractional_knapsack, - PowdrConfig, + EmpiricalConstraints, PowdrConfig, }; mod selection; @@ -92,6 +92,7 @@ impl + Send + Sync> PgoAdapter for Cel config: &PowdrConfig, vm_config: AdapterVmConfig, labels: BTreeMap>, + empirical_constraints: EmpiricalConstraints, ) -> Vec> { tracing::info!( "Generating autoprecompiles with cell PGO for {} blocks", @@ -131,6 +132,7 @@ impl + Send + Sync> PgoAdapter for Cel vm_config.clone(), config.degree_bound, config.apc_candidates_dir_path.as_deref(), + &empirical_constraints, ) .ok()?; let candidate = C::create( diff --git a/autoprecompiles/src/pgo/instruction.rs b/autoprecompiles/src/pgo/instruction.rs index 42e8594a20..76ec13c980 100644 --- a/autoprecompiles/src/pgo/instruction.rs +++ b/autoprecompiles/src/pgo/instruction.rs @@ -4,7 +4,7 @@ use crate::{ adapter::{Adapter, AdapterApcWithStats, AdapterVmConfig, PgoAdapter}, blocks::BasicBlock, pgo::create_apcs_for_all_blocks, - PowdrConfig, + EmpiricalConstraints, PowdrConfig, }; pub struct InstructionPgo { @@ -30,6 +30,7 @@ impl PgoAdapter for InstructionPgo { config: &PowdrConfig, vm_config: AdapterVmConfig, _labels: BTreeMap>, + empirical_constraints: EmpiricalConstraints, ) -> Vec> { tracing::info!( "Generating autoprecompiles with instruction PGO for {} blocks", @@ -70,7 +71,12 @@ impl PgoAdapter for InstructionPgo { ); } - create_apcs_for_all_blocks::(blocks, config, vm_config) + create_apcs_for_all_blocks::( + blocks, + config, + vm_config, + empirical_constraints, + ) } fn pc_execution_count(&self, pc: u64) -> Option { diff --git a/autoprecompiles/src/pgo/mod.rs b/autoprecompiles/src/pgo/mod.rs index a5a044ec25..cc490e81ab 100644 --- a/autoprecompiles/src/pgo/mod.rs +++ b/autoprecompiles/src/pgo/mod.rs @@ -6,7 +6,7 @@ use strum::{Display, EnumString}; use crate::{ adapter::{Adapter, AdapterApcWithStats, AdapterVmConfig, ApcWithStats}, blocks::BasicBlock, - PowdrConfig, + EmpiricalConstraints, PowdrConfig, }; mod cell; @@ -77,6 +77,7 @@ fn create_apcs_for_all_blocks( blocks: Vec>, config: &PowdrConfig, vm_config: AdapterVmConfig, + empirical_constraints: EmpiricalConstraints, ) -> Vec> { let n_acc = config.autoprecompiles as usize; tracing::info!("Generating {n_acc} autoprecompiles in parallel"); @@ -97,6 +98,7 @@ fn create_apcs_for_all_blocks( vm_config.clone(), config.degree_bound, config.apc_candidates_dir_path.as_deref(), + &empirical_constraints, ) .unwrap() }) diff --git a/autoprecompiles/src/pgo/none.rs b/autoprecompiles/src/pgo/none.rs index 0c66cfcbf5..2e96834a70 100644 --- a/autoprecompiles/src/pgo/none.rs +++ b/autoprecompiles/src/pgo/none.rs @@ -4,7 +4,7 @@ use crate::{ adapter::{Adapter, AdapterApcWithStats, AdapterVmConfig, PgoAdapter}, blocks::BasicBlock, pgo::create_apcs_for_all_blocks, - PowdrConfig, + EmpiricalConstraints, PowdrConfig, }; pub struct NonePgo { @@ -29,6 +29,7 @@ impl PgoAdapter for NonePgo { config: &PowdrConfig, vm_config: AdapterVmConfig, _labels: BTreeMap>, + empirical_constraints: EmpiricalConstraints, ) -> Vec> { // cost = number_of_original_instructions blocks.sort_by(|a, b| b.statements.len().cmp(&a.statements.len())); @@ -42,6 +43,11 @@ impl PgoAdapter for NonePgo { ); } - create_apcs_for_all_blocks::(blocks, config, vm_config) + create_apcs_for_all_blocks::( + blocks, + config, + vm_config, + empirical_constraints, + ) } } diff --git a/cli-openvm/src/main.rs b/cli-openvm/src/main.rs index 053095941b..d0f2097130 100644 --- a/cli-openvm/src/main.rs +++ b/cli-openvm/src/main.rs @@ -3,7 +3,9 @@ use metrics_tracing_context::{MetricsLayer, TracingContextLayer}; use metrics_util::{debugging::DebuggingRecorder, layers::Layer}; use openvm_sdk::StdIn; use openvm_stark_sdk::bench::serialize_metric_snapshot; -use powdr_autoprecompiles::empirical_constraints::EmpiricalConstraintsJson; +use powdr_autoprecompiles::empirical_constraints::{ + EmpiricalConstraints, EmpiricalConstraintsJson, +}; use powdr_autoprecompiles::pgo::{pgo_config, PgoType}; use powdr_autoprecompiles::PowdrConfig; use powdr_openvm::{compile_openvm, default_powdr_openvm_config, CompiledProgram, GuestOptions}; @@ -169,10 +171,19 @@ fn run_command(command: Commands) { let execution_profile = powdr_openvm::execution_profile_from_guest(&guest_program, stdin_from(input)); - maybe_compute_empirical_constraints(&guest_program, &powdr_config, stdin_from(input)); + let empirical_constraints = maybe_compute_empirical_constraints( + &guest_program, + &powdr_config, + stdin_from(input), + ); let pgo_config = pgo_config(pgo, max_columns, execution_profile); - let program = - powdr_openvm::compile_exe(guest_program, powdr_config, pgo_config).unwrap(); + let program = powdr_openvm::compile_exe( + guest_program, + powdr_config, + pgo_config, + empirical_constraints, + ) + .unwrap(); write_program_to_file(program, &format!("{guest}_compiled.cbor")).unwrap(); } @@ -195,13 +206,22 @@ fn run_command(command: Commands) { powdr_config = powdr_config.with_optimistic_precompiles(true); } let guest_program = compile_openvm(&guest, guest_opts.clone()).unwrap(); - maybe_compute_empirical_constraints(&guest_program, &powdr_config, stdin_from(input)); + let empirical_constraints = maybe_compute_empirical_constraints( + &guest_program, + &powdr_config, + stdin_from(input), + ); let execution_profile = powdr_openvm::execution_profile_from_guest(&guest_program, stdin_from(input)); let pgo_config = pgo_config(pgo, max_columns, execution_profile); let compile_and_exec = || { - let program = - powdr_openvm::compile_exe(guest_program, powdr_config, pgo_config).unwrap(); + let program = powdr_openvm::compile_exe( + guest_program, + powdr_config, + pgo_config, + empirical_constraints, + ) + .unwrap(); powdr_openvm::execute(program, stdin_from(input)).unwrap(); }; if let Some(metrics_path) = metrics { @@ -228,21 +248,30 @@ fn run_command(command: Commands) { optimistic_precompiles, } => { let mut powdr_config = default_powdr_openvm_config(autoprecompiles as u64, skip as u64); - if let Some(apc_candidates_dir) = apc_candidates_dir { + if let Some(apc_candidates_dir) = &apc_candidates_dir { powdr_config = powdr_config.with_apc_candidates_dir(apc_candidates_dir); } if optimistic_precompiles { powdr_config = powdr_config.with_optimistic_precompiles(true); } let guest_program = compile_openvm(&guest, guest_opts).unwrap(); - maybe_compute_empirical_constraints(&guest_program, &powdr_config, stdin_from(input)); + let empirical_constraints = maybe_compute_empirical_constraints( + &guest_program, + &powdr_config, + stdin_from(input), + ); let execution_profile = powdr_openvm::execution_profile_from_guest(&guest_program, stdin_from(input)); let pgo_config = pgo_config(pgo, max_columns, execution_profile); let compile_and_prove = || { - let program = - powdr_openvm::compile_exe(guest_program, powdr_config, pgo_config).unwrap(); + let program = powdr_openvm::compile_exe( + guest_program, + powdr_config, + pgo_config, + empirical_constraints, + ) + .unwrap(); powdr_openvm::prove(&program, mock, recursion, stdin_from(input), None).unwrap() }; if let Some(metrics_path) = metrics { @@ -302,9 +331,9 @@ fn maybe_compute_empirical_constraints( guest_program: &OriginalCompiledProgram, powdr_config: &PowdrConfig, stdin: StdIn, -) { +) -> EmpiricalConstraints { if !powdr_config.optimistic_precompiles { - return; + return EmpiricalConstraints::default(); } tracing::warn!( @@ -326,4 +355,5 @@ fn maybe_compute_empirical_constraints( let json = serde_json::to_string_pretty(&export).unwrap(); std::fs::write(path.join("empirical_constraints.json"), json).unwrap(); } + empirical_constraints } diff --git a/openvm/src/customize_exe.rs b/openvm/src/customize_exe.rs index c3b88bacab..70ffea968e 100644 --- a/openvm/src/customize_exe.rs +++ b/openvm/src/customize_exe.rs @@ -28,6 +28,7 @@ use powdr_autoprecompiles::adapter::{ Adapter, AdapterApc, AdapterApcWithStats, AdapterVmConfig, ApcWithStats, PgoAdapter, }; use powdr_autoprecompiles::blocks::{BasicBlock, Instruction}; +use powdr_autoprecompiles::empirical_constraints::EmpiricalConstraints; use powdr_autoprecompiles::evaluation::{evaluate_apc, EvaluationResult}; use powdr_autoprecompiles::expression::try_convert; use powdr_autoprecompiles::pgo::{ApcCandidateJsonExport, Candidate, KnapsackItem}; @@ -110,6 +111,7 @@ pub fn customize<'a, P: PgoAdapter>>( original_program: OriginalCompiledProgram, config: PowdrConfig, pgo: P, + empirical_constraints: EmpiricalConstraints, ) -> CompiledProgram { let original_config = OriginalVmConfig::new(original_program.vm_config.clone()); let airs = original_config.airs(config.degree_bound.identities).expect("Failed to convert the AIR of an OpenVM instruction, even after filtering by the blacklist!"); @@ -171,7 +173,13 @@ pub fn customize<'a, P: PgoAdapter>>( .collect(); let start = std::time::Instant::now(); - let apcs = pgo.filter_blocks_and_create_apcs_with_pgo(blocks, &config, vm_config, labels); + let apcs = pgo.filter_blocks_and_create_apcs_with_pgo( + blocks, + &config, + vm_config, + labels, + empirical_constraints, + ); metrics::gauge!("total_apc_gen_time_ms").set(start.elapsed().as_millis() as f64); let pc_base = exe.program.pc_base; diff --git a/openvm/src/empirical_constraints.rs b/openvm/src/empirical_constraints.rs index ae2ded80e2..0f435e608c 100644 --- a/openvm/src/empirical_constraints.rs +++ b/openvm/src/empirical_constraints.rs @@ -177,6 +177,12 @@ impl ConstraintDetector { } pub fn process_trace(&mut self, trace: Trace, new_debug_info: DebugInfo) { + let pc_counts = trace + .rows_by_pc() + .into_iter() + .map(|(pc, rows)| (pc, rows.len() as u64)) + .collect(); + // Compute empirical constraints from the current trace tracing::info!(" Detecting equivalence classes by block..."); let equivalence_classes_by_block = self.generate_equivalence_classes_by_block(&trace); @@ -185,6 +191,7 @@ impl ConstraintDetector { let new_empirical_constraints = EmpiricalConstraints { column_ranges_by_pc, equivalence_classes_by_block, + pc_counts, }; // Combine the new empirical constraints and debug info with the existing ones diff --git a/openvm/src/lib.rs b/openvm/src/lib.rs index 6a43ce2452..ab211b75f7 100644 --- a/openvm/src/lib.rs +++ b/openvm/src/lib.rs @@ -36,6 +36,7 @@ use openvm_stark_sdk::config::{ use openvm_stark_sdk::openvm_stark_backend::p3_field::PrimeField32; use openvm_stark_sdk::p3_baby_bear::BabyBear; use openvm_transpiler::transpiler::Transpiler; +use powdr_autoprecompiles::empirical_constraints::EmpiricalConstraints; use powdr_autoprecompiles::evaluation::AirStats; use powdr_autoprecompiles::pgo::{CellPgo, InstructionPgo, NonePgo}; use powdr_autoprecompiles::{execution_profile::execution_profile, PowdrConfig}; @@ -529,6 +530,7 @@ pub fn compile_exe( original_program: OriginalCompiledProgram, config: PowdrConfig, pgo_config: PgoConfig, + empirical_constraints: EmpiricalConstraints, ) -> Result> { let compiled = match pgo_config { PgoConfig::Cell(pgo_data, max_total_columns) => { @@ -550,14 +552,21 @@ pub fn compile_exe( pgo_data, max_total_apc_columns, ), + empirical_constraints, ) } PgoConfig::Instruction(pgo_data) => customize( original_program, config, InstructionPgo::with_pgo_data(pgo_data), + empirical_constraints, + ), + PgoConfig::None => customize( + original_program, + config, + NonePgo::default(), + empirical_constraints, ), - PgoConfig::None => customize(original_program, config, NonePgo::default()), }; // Export the compiled program to a PIL file for debugging purposes. export_pil( @@ -878,7 +887,8 @@ mod tests { segment_height: Option, ) -> Result<(), Box> { let guest = compile_openvm(guest, GuestOptions::default()).unwrap(); - let program = compile_exe(guest, config, pgo_config).unwrap(); + let program = + compile_exe(guest, config, pgo_config, EmpiricalConstraints::default()).unwrap(); prove(&program, mock, recursion, stdin, segment_height) } @@ -998,7 +1008,13 @@ mod tests { let pgo_data = execution_profile_from_guest(&guest, stdin.clone()); let config = default_powdr_openvm_config(GUEST_APC, GUEST_SKIP_NO_APC_EXECUTED); - let program = compile_exe(guest, config, PgoConfig::None).unwrap(); + let program = compile_exe( + guest, + config, + PgoConfig::None, + EmpiricalConstraints::default(), + ) + .unwrap(); // Assert that all APCs aren't executed program @@ -1050,7 +1066,13 @@ mod tests { fn matmul_compile() { let guest = compile_openvm("guest-matmul", GuestOptions::default()).unwrap(); let config = default_powdr_openvm_config(1, 0); - assert!(compile_exe(guest, config, PgoConfig::default()).is_ok()); + assert!(compile_exe( + guest, + config, + PgoConfig::default(), + EmpiricalConstraints::default() + ) + .is_ok()); } #[test] @@ -1519,7 +1541,13 @@ mod tests { let is_cell_pgo = matches!(guest.pgo_config, PgoConfig::Cell(_, _)); let max_degree = config.degree_bound.identities; let guest_program = compile_openvm(guest.name, GuestOptions::default()).unwrap(); - let compiled_program = compile_exe(guest_program, config, guest.pgo_config).unwrap(); + let compiled_program = compile_exe( + guest_program, + config, + guest.pgo_config, + EmpiricalConstraints::default(), + ) + .unwrap(); let (powdr_air_metrics, non_powdr_air_metrics) = compiled_program.air_metrics(max_degree); diff --git a/openvm/tests/common/mod.rs b/openvm/tests/common/mod.rs index c1b4f76a97..8d1cc8922a 100644 --- a/openvm/tests/common/mod.rs +++ b/openvm/tests/common/mod.rs @@ -21,6 +21,7 @@ pub mod apc_builder_utils { use openvm_instructions::instruction::Instruction; use openvm_stark_sdk::p3_baby_bear::BabyBear; use powdr_autoprecompiles::blocks::BasicBlock; + use powdr_autoprecompiles::empirical_constraints::EmpiricalConstraints; use powdr_autoprecompiles::evaluation::evaluate_apc; use powdr_autoprecompiles::{build, VmConfig}; use powdr_number::BabyBearField; @@ -60,9 +61,14 @@ pub mod apc_builder_utils { start_pc: 0, }; - let apc = - build::(basic_block.clone(), vm_config, degree_bound, None) - .unwrap(); + let apc = build::( + basic_block.clone(), + vm_config, + degree_bound, + None, + &EmpiricalConstraints::default(), + ) + .unwrap(); let apc = apc.machine(); let evaluation = evaluate_apc(&basic_block.statements, &airs, apc);