Skip to content

Commit 3b5275b

Browse files
committed
Collect empirical constraints
1 parent 5ea1cf3 commit 3b5275b

File tree

8 files changed

+505
-26
lines changed

8 files changed

+505
-26
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use std::collections::BTreeMap;
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::{
6+
adapter::Adapter,
7+
blocks::BasicBlock,
8+
expression::{AlgebraicExpression, AlgebraicReference},
9+
SymbolicConstraint,
10+
};
11+
12+
/// "Constraints" that were inferred from execution statistics.
13+
#[derive(Serialize, Deserialize, Clone, Default)]
14+
pub struct EmpiricalConstraints {
15+
/// For each program counter, the range constraints for each column.
16+
/// The range might not hold in 100% of cases.
17+
pub column_ranges_by_pc: BTreeMap<u32, Vec<(u32, u32)>>,
18+
/// For each basic block (identified by its starting PC), the equivalence classes of columns.
19+
/// Each equivalence class is a list of (instruction index in block, column index).
20+
pub equivalence_classes_by_block: BTreeMap<u64, Vec<Vec<(usize, usize)>>>,
21+
}
22+
23+
/// Debug information mapping AIR ids to program counters and column names.
24+
#[derive(Serialize, Deserialize)]
25+
pub struct DebugInfo {
26+
/// Mapping from program counter to AIR id.
27+
pub air_id_by_pc: BTreeMap<u32, usize>,
28+
/// Mapping from AIR id to column names.
29+
pub column_names_by_air_id: BTreeMap<usize, Vec<String>>,
30+
}
31+
32+
#[derive(Serialize, Deserialize)]
33+
pub struct EmpiricalConstraintsJson {
34+
pub empirical_constraints: EmpiricalConstraints,
35+
pub debug_info: DebugInfo,
36+
}
37+
38+
pub fn add_empirical_constraints<A: Adapter>(
39+
empirical_constraints: &EmpiricalConstraints,
40+
subs: &[Vec<u64>],
41+
block: &BasicBlock<A::Instruction>,
42+
columns: impl Iterator<Item = AlgebraicReference>,
43+
) -> (
44+
Vec<SymbolicConstraint<A::PowdrField>>,
45+
Vec<SymbolicConstraint<A::PowdrField>>,
46+
) {
47+
let range_constraints = &empirical_constraints.column_ranges_by_pc;
48+
let equivalence_classes_by_block = &empirical_constraints.equivalence_classes_by_block;
49+
50+
let mut range_analyzer_constraints = Vec::new();
51+
let mut equivalence_analyzer_constraints = Vec::new();
52+
53+
// Mapping (instruction index, column index) -> AlgebraicReference
54+
let reverse_subs = subs
55+
.iter()
56+
.enumerate()
57+
.flat_map(|(instr_index, subs)| {
58+
subs.iter()
59+
.enumerate()
60+
.map(move |(col_index, &poly_id)| (poly_id, (instr_index, col_index)))
61+
})
62+
.collect::<BTreeMap<_, _>>();
63+
let algebraic_references = columns
64+
.map(|r| (reverse_subs.get(&r.id).unwrap().clone(), r.clone()))
65+
.collect::<BTreeMap<_, _>>();
66+
67+
for i in 0..block.statements.len() {
68+
let pc = (block.start_pc + (i * 4) as u64) as u32;
69+
let Some(range_constraints) = range_constraints.get(&pc) else {
70+
continue;
71+
};
72+
for (col_index, range) in range_constraints.iter().enumerate() {
73+
if range.0 == range.1 {
74+
let value = A::PowdrField::from(range.0 as u64);
75+
let Some(reference) = algebraic_references.get(&(i, col_index)).cloned() else {
76+
panic!(
77+
"Missing reference for (i: {}, col_index: {}, block_id: {})",
78+
i, col_index, block.start_pc
79+
);
80+
};
81+
let constraint =
82+
AlgebraicExpression::Reference(reference) - AlgebraicExpression::Number(value);
83+
84+
range_analyzer_constraints.push(SymbolicConstraint { expr: constraint });
85+
}
86+
}
87+
}
88+
89+
if let Some(equivalence_classes) = equivalence_classes_by_block.get(&block.start_pc) {
90+
for equivalence_class in equivalence_classes {
91+
let first = equivalence_class.first().unwrap();
92+
let Some(first_ref) = algebraic_references.get(first).cloned() else {
93+
// TODO: This fails in some blocks. For now, just return no extra constraints.
94+
println!(
95+
"Missing reference for (i: {}, col_index: {}, block_id: {})",
96+
first.0, first.1, block.start_pc
97+
);
98+
return (range_analyzer_constraints, vec![]);
99+
};
100+
for other in equivalence_class.iter().skip(1) {
101+
let Some(other_ref) = algebraic_references.get(other).cloned() else {
102+
// TODO: This fails in some blocks. For now, just return no extra constraints.
103+
println!(
104+
"Missing reference for (i: {}, col_index: {}, block_id: {})",
105+
other.0, other.1, block.start_pc
106+
);
107+
return (range_analyzer_constraints, vec![]);
108+
};
109+
let constraint = AlgebraicExpression::Reference(first_ref.clone())
110+
- AlgebraicExpression::Reference(other_ref.clone());
111+
equivalence_analyzer_constraints.push(SymbolicConstraint { expr: constraint });
112+
}
113+
}
114+
}
115+
116+
(range_analyzer_constraints, equivalence_analyzer_constraints)
117+
}

autoprecompiles/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod adapter;
2626
pub mod blocks;
2727
pub mod bus_map;
2828
pub mod constraint_optimizer;
29+
pub mod empirical_constraints;
2930
pub mod evaluation;
3031
pub mod execution_profile;
3132
pub mod expression;

cli-openvm/src/main.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,14 @@ fn run_command(command: Commands) {
146146
stdin_from(input),
147147
);
148148
let pgo_config = pgo_config(pgo, max_columns, execution_profile);
149-
let program =
150-
powdr_openvm::compile_guest(&guest, guest_opts, powdr_config, pgo_config).unwrap();
149+
let program = powdr_openvm::compile_guest(
150+
&guest,
151+
guest_opts,
152+
powdr_config,
153+
pgo_config,
154+
stdin_from(input),
155+
)
156+
.unwrap();
151157
write_program_to_file(program, &format!("{guest}_compiled.cbor")).unwrap();
152158
}
153159

@@ -172,9 +178,14 @@ fn run_command(command: Commands) {
172178
);
173179
let pgo_config = pgo_config(pgo, max_columns, execution_profile);
174180
let compile_and_exec = || {
175-
let program =
176-
powdr_openvm::compile_guest(&guest, guest_opts, powdr_config, pgo_config)
177-
.unwrap();
181+
let program = powdr_openvm::compile_guest(
182+
&guest,
183+
guest_opts,
184+
powdr_config,
185+
pgo_config,
186+
stdin_from(input),
187+
)
188+
.unwrap();
178189
powdr_openvm::execute(program, stdin_from(input)).unwrap();
179190
};
180191
if let Some(metrics_path) = metrics {
@@ -210,9 +221,14 @@ fn run_command(command: Commands) {
210221
);
211222
let pgo_config = pgo_config(pgo, max_columns, execution_profile);
212223
let compile_and_prove = || {
213-
let program =
214-
powdr_openvm::compile_guest(&guest, guest_opts, powdr_config, pgo_config)
215-
.unwrap();
224+
let program = powdr_openvm::compile_guest(
225+
&guest,
226+
guest_opts,
227+
powdr_config,
228+
pgo_config,
229+
stdin_from(input),
230+
)
231+
.unwrap();
216232
powdr_openvm::prove(&program, mock, recursion, stdin_from(input), None).unwrap()
217233
};
218234
if let Some(metrics_path) = metrics {

openvm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ derive_more = { version = "2.0.1", default-features = false, features = [
6161
"from",
6262
] }
6363
itertools = "0.14.0"
64+
serde_json = "1.0.140"
6465

6566
tracing = "0.1.40"
6667
tracing-subscriber = { version = "0.3.17", features = ["std", "env-filter"] }

openvm/src/customize_exe.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ use crate::memory_bus_interaction::OpenVmMemoryBusInteraction;
1313
use crate::opcode::branch_opcodes_bigint_set;
1414
use crate::powdr_extension::chip::PowdrAir;
1515
use crate::utils::UnsupportedOpenVmReferenceError;
16-
use crate::OriginalCompiledProgram;
16+
use crate::{empirical_constraints::empirical_constraints, OriginalCompiledProgram};
1717
use crate::{CompiledProgram, SpecializedConfig};
1818
use itertools::Itertools;
1919
use openvm_instructions::instruction::Instruction as OpenVmInstruction;
2020
use openvm_instructions::program::{Program as OpenVmProgram, DEFAULT_PC_STEP};
2121
use openvm_instructions::VmOpcode;
22+
use openvm_sdk::StdIn;
2223
use openvm_stark_backend::{
2324
interaction::SymbolicInteraction,
2425
p3_field::{FieldAlgebra, PrimeField32},
@@ -147,6 +148,7 @@ pub fn customize<'a, P: PgoAdapter<Adapter = BabyBearOpenVmApcAdapter<'a>>>(
147148
}: OriginalCompiledProgram,
148149
config: PowdrConfig,
149150
pgo: P,
151+
inputs: StdIn,
150152
) -> CompiledProgram {
151153
let labels = elf.text_labels();
152154
let debug_info = elf.debug_info();
@@ -217,6 +219,17 @@ pub fn customize<'a, P: PgoAdapter<Adapter = BabyBearOpenVmApcAdapter<'a>>>(
217219
})
218220
.collect();
219221

222+
let _empirical_constraints = empirical_constraints(
223+
exe.clone(),
224+
SpecializedConfig::new(
225+
original_config.clone(),
226+
vec![],
227+
config.degree_bound.identities,
228+
),
229+
inputs,
230+
&blocks,
231+
);
232+
220233
let start = std::time::Instant::now();
221234
let apcs = pgo.filter_blocks_and_create_apcs_with_pgo(blocks, &config, vm_config, labels);
222235
metrics::gauge!("total_apc_gen_time_ms").set(start.elapsed().as_millis() as f64);

0 commit comments

Comments
 (0)