Skip to content
Draft
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
23002ae
always add guards
pacheco May 19, 2025
b05703d
apc chip stacking
pacheco May 19, 2025
7a35e47
lower degree bound of optimize step
pacheco May 19, 2025
323e706
group by powers of two
pacheco May 19, 2025
f09fc34
use f64::log
pacheco May 20, 2025
a2d3683
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco May 20, 2025
bdff7c9
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco May 23, 2025
5d060c5
fix for scroll
pacheco May 23, 2025
c6443f7
another unsupported instruction
pacheco May 23, 2025
c950d3f
handle group of 1 precompile
pacheco May 26, 2025
cb4733d
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco May 26, 2025
e28585d
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco May 27, 2025
d142918
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco Jun 2, 2025
df67d47
Merge commit '2ee7358e1f054955f3824de578c89475f6249ace' into apc_stac…
pacheco Jun 4, 2025
920edba
Merge commit '1a11972cc7930fe97a52e9a51ef3a45bcf5b6ec9' into apc_stac…
pacheco Jun 5, 2025
50a1105
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco Jun 5, 2025
dedee09
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco Jun 5, 2025
09b512a
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco Jun 9, 2025
4b3c5d4
test joining constraints
pacheco Jun 2, 2025
a7f5ff1
sort
pacheco Jun 2, 2025
e48bf1b
seems to work!
pacheco Jun 3, 2025
13850f6
seems to work
pacheco Jun 3, 2025
669b974
try handle bus interactions
pacheco Jun 4, 2025
9dcff42
stuff
pacheco Jun 5, 2025
c8918f5
move air_stacking to own file
pacheco Jun 10, 2025
6ff8258
Merge branch 'apc_stacking' into apc_stacking_test2
pacheco Jun 10, 2025
0f5448e
default stacking 1.1
pacheco Jun 10, 2025
b8b0beb
Merge remote-tracking branch 'origin/main' into apc_stacking
pacheco Jun 10, 2025
4de95d2
Merge branch 'apc_stacking' into apc_stacking_test2
pacheco Jun 10, 2025
4dd19ef
print max degree after handling constraints/interactions
pacheco Jun 13, 2025
a7537c2
print degrees before/after optimizer
pacheco Jun 13, 2025
dfb387f
plonk
pacheco Jun 16, 2025
1e83820
chip stacking config
pacheco Jun 16, 2025
f363529
cmdline arg
pacheco Jun 16, 2025
6f6ca78
remove unused
pacheco Jun 16, 2025
05af4ae
cleanup
pacheco Jun 16, 2025
1075ba2
fmt
pacheco Jun 17, 2025
428550b
cleanup
pacheco Jun 17, 2025
2ab7843
Merge remote-tracking branch 'origin/main' into apc_stacking_cleanup
pacheco Jun 18, 2025
5193df6
normalize expr
pacheco Jun 18, 2025
1ddff4f
Merge remote-tracking branch 'origin/main' into apc_stacking_cleanup
pacheco Jun 18, 2025
7ea0748
cleanup
pacheco Jun 18, 2025
4473e9a
witgen cleanup
pacheco Jun 18, 2025
35c8ba0
cleanup
pacheco Jun 18, 2025
c139514
cleanup
pacheco Jun 18, 2025
44c8c58
clenaup
pacheco Jun 18, 2025
a5483e9
cleanup
pacheco Jun 18, 2025
b85b763
Merge remote-tracking branch 'origin/main' into apc_stacking_cleanup
pacheco Jun 18, 2025
c01c5ca
test
pacheco Jun 18, 2025
54dd766
small change
pacheco Jun 18, 2025
3b8dd68
fix
pacheco Jun 18, 2025
3ada389
fmt
pacheco Jun 18, 2025
f176a0f
clippy
pacheco Jun 19, 2025
857997a
comments
pacheco Jun 23, 2025
9dc6760
Merge remote-tracking branch 'origin/main' into apc_stacking_cleanup
pacheco Jun 30, 2025
1905935
Merge remote-tracking branch 'origin/main' into apc_stacking_cleanup
pacheco Jul 2, 2025
be0c6b6
fmt
pacheco Jul 3, 2025
ed27779
Merge remote-tracking branch 'origin/main' into apc_stacking_cleanup
pacheco Jul 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions autoprecompiles/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::bus_map::{BusMap, BusType};
use crate::expression_conversion::algebraic_to_quadratic_symbolic_expression;
use crate::optimizer::simplify_expression;
pub use crate::optimizer::simplify_expression;
use constraint_optimizer::IsBusStateful;
use expression::{AlgebraicExpression, AlgebraicReference};
use itertools::Itertools;
Expand Down Expand Up @@ -36,7 +36,7 @@ pub struct SymbolicInstructionStatement<T> {
pub args: Vec<T>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
pub struct SymbolicConstraint<T> {
pub expr: AlgebraicExpression<T>,
}
Expand Down Expand Up @@ -278,6 +278,9 @@ pub fn build<
vm_config: VmConfig<M, B>,
degree_bound: DegreeBound,
opcode: u32,
// If true, disables the optimization of multipliying only constants when guarding with is_valid.
// Chip stacking needs variables to be guarded too.
strict_is_valid_guards: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some documentation on this parameter?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this?

/// "Guarding" constraints means that the prover can choose to disable them, which is useful for
/// filling up rows until the next power of two. A new boolean variable (`is_valid`) introduced
/// that allows the prover to disable constraints.
enum GuardingMode {
    /// Makes sure that setting all variables to zero satisfies the constraints.
    AllowZeroWitness,
    /// Makes sure that all constraints are satisfied as long as `is_valid` is set to 0.
    /// This mode always increases the constraint degree by one.
    Strict,
}

) -> Result<Apc<T>, crate::constraint_optimizer::Error> {
let (machine, subs) = statements_to_symbolic_machine(
&program,
Expand All @@ -294,7 +297,7 @@ pub fn build<
)?;

// add guards to constraints that are not satisfied by zeroes
let machine = add_guards(machine, vm_config.bus_map);
let machine = add_guards(machine, vm_config.bus_map, strict_is_valid_guards);

Ok(Apc { machine, subs })
}
Expand Down Expand Up @@ -346,6 +349,7 @@ fn add_guards_constraint<T: FieldElement>(
fn add_guards<T: FieldElement>(
mut machine: SymbolicMachine<T>,
bus_map: BusMap,
is_strict: bool,
) -> SymbolicMachine<T> {
let pre_degree = machine.degree();
let exec_bus_id = bus_map.get_bus_id(&BusType::ExecutionBridge).unwrap();
Expand All @@ -358,11 +362,22 @@ fn add_guards<T: FieldElement>(
id: max_id,
});

machine.constraints = machine
.constraints
.into_iter()
.map(|c| add_guards_constraint(c.expr, &is_valid).into())
.collect();
machine.constraints = if is_strict {
machine
.constraints
.into_iter()
.map(|mut c| {
c.expr = is_valid.clone() * c.expr.clone();
c
})
.collect()
} else {
Comment on lines +366 to +374
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this disable some cheaper stuff that add_guards_constraint does in the non strict case? It seems like is_strict could be pulled into add_guards_constraint

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add_guards_constraint is a recursive fn that ensures the degree doesn't increase... which is not the case here, so opted to be explicit here

machine
.constraints
.into_iter()
.map(|c| add_guards_constraint(c.expr, &is_valid).into())
.collect()
};

let [execution_bus_receive, execution_bus_send] = machine
.bus_interactions
Expand All @@ -389,7 +404,7 @@ fn add_guards<T: FieldElement>(
for b in &mut machine.bus_interactions {
// already handled exec and pc lookup bus types
if b.id != exec_bus_id && b.id != pc_lookup_bus_id {
if !satisfies_zero_witness(&b.mult) {
if is_strict || !satisfies_zero_witness(&b.mult) {
// guard the multiplicity by `is_valid`
b.mult = is_valid.clone() * b.mult.clone();
// TODO this would not have to be cloned if we had *=
Expand All @@ -405,11 +420,13 @@ fn add_guards<T: FieldElement>(

machine.constraints.extend(is_valid_mults);

assert_eq!(
pre_degree,
machine.degree(),
"Degree should not change after adding guards"
);
if !is_strict {
assert_eq!(
pre_degree,
machine.degree(),
"Degree should not change after adding guards"
);
}

// This needs to be added after the assertion above because it's a quadratic constraint
// so it may increase the degree of the machine.
Expand Down
27 changes: 24 additions & 3 deletions cli-openvm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ enum Commands {

#[arg(long)]
input: Option<u32>,

#[arg(long)]
chip_stacking: Option<f32>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a description here, with a note that the feature is experimental?

},

Execute {
Expand All @@ -58,6 +61,9 @@ enum Commands {

#[arg(long)]
input: Option<u32>,

#[arg(long)]
chip_stacking: Option<f32>,
},

Prove {
Expand Down Expand Up @@ -89,6 +95,9 @@ enum Commands {

#[arg(long)]
metrics: Option<PathBuf>,

#[arg(long)]
chip_stacking: Option<f32>,
},
}

Expand All @@ -115,8 +124,12 @@ fn run_command(command: Commands) {
pgo,
max_columns,
input,
chip_stacking,
} => {
let powdr_config = PowdrConfig::new(autoprecompiles as u64, skip as u64);
let mut powdr_config = PowdrConfig::new(autoprecompiles as u64, skip as u64);
if let Some(log) = chip_stacking {
powdr_config = powdr_config.with_chip_stacking(log);
}
let pgo_config = pgo_config(guest.clone(), guest_opts.clone(), pgo, max_columns, input);
let program =
powdr_openvm::compile_guest(&guest, guest_opts, powdr_config, pgo_config).unwrap();
Expand All @@ -130,8 +143,12 @@ fn run_command(command: Commands) {
pgo,
max_columns,
input,
chip_stacking,
} => {
let powdr_config = PowdrConfig::new(autoprecompiles as u64, skip as u64);
let mut powdr_config = PowdrConfig::new(autoprecompiles as u64, skip as u64);
if let Some(log) = chip_stacking {
powdr_config = powdr_config.with_chip_stacking(log);
}
let pgo_config = pgo_config(guest.clone(), guest_opts.clone(), pgo, max_columns, input);
let program =
powdr_openvm::compile_guest(&guest, guest_opts, powdr_config, pgo_config).unwrap();
Expand All @@ -148,8 +165,12 @@ fn run_command(command: Commands) {
max_columns,
input,
metrics,
chip_stacking,
} => {
let powdr_config = PowdrConfig::new(autoprecompiles as u64, skip as u64);
let mut powdr_config = PowdrConfig::new(autoprecompiles as u64, skip as u64);
if let Some(log) = chip_stacking {
powdr_config = powdr_config.with_chip_stacking(log);
}
let pgo_config = pgo_config(guest.clone(), guest_opts.clone(), pgo, max_columns, input);
let program =
powdr_openvm::compile_guest(&guest, guest_opts, powdr_config, pgo_config).unwrap();
Expand Down
193 changes: 193 additions & 0 deletions expression/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ impl<T, R> AlgebraicExpression<T, R> {
}
}

impl<T: Clone + Ord, R: Clone + Ord> AlgebraicExpression<T, R> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the changes to this file could be nice individual PR :)

/// Get a canonical form by expanding the expression and reordering operands.
pub fn normalize(self) -> Self {
normalize(expand(self))
}
}
Comment on lines +133 to +138
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be done by going through quadratic expression? cc @georgwiese

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite. GroupedExpression (formerly QuadraticSymbolicExpression) has a canonical representation for expressions up to degree 1...

We could do this by introducing a CanonicalExpression type which is essentially a BTreeMap<BTreeSet<R>, T>. As long as it implements Add + Sub + Mul + Neg, AlgebraicExpression::to_expression can convert to it (via the ExpressionConvertible trait).

If AlgebraicExpression also implements From<CanonicalExpression>, we could implement the normalization as something like

self.to_expression(
    &|number| CanonicalExpression { coefficients:
        [(BTreeSet::new(), *number)].into_iter().collect()
    }, |reference| CanonicalExpression { coefficients:
        ([*reference].into_iter().collect(), T::one())].into_iter().collect()
    }).into()

Not sure if it's worth it, but seems a bit cleaner.


impl<T, R> ops::Add for AlgebraicExpression<T, R> {
type Output = Self;

Expand Down Expand Up @@ -168,6 +175,192 @@ impl<T, R> From<T> for AlgebraicExpression<T, R> {
}
}

// Helper to flatten Add trees
fn flatten_add<T: Ord + Clone, R: Ord + Clone>(
expr: AlgebraicExpression<T, R>,
acc: &mut Vec<AlgebraicExpression<T, R>>,
) {
match expr {
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Add,
right,
}) => {
flatten_add(*left, acc);
flatten_add(*right, acc);
}
_ => acc.push(normalize(expr)),
}
}

// Helper to flatten Mul trees
fn flatten_mul<T: Ord + Clone, R: Ord + Clone>(
expr: AlgebraicExpression<T, R>,
acc: &mut Vec<AlgebraicExpression<T, R>>,
) {
match expr {
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Mul,
right,
}) => {
flatten_mul(*left, acc);
flatten_mul(*right, acc);
}
_ => acc.push(normalize(expr)),
}
}

fn normalize<T: Ord + Clone, R: Ord + Clone>(
expr: AlgebraicExpression<T, R>,
) -> AlgebraicExpression<T, R> {
match expr {
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Add,
right,
}) => {
let mut terms = Vec::new();
flatten_add(*left, &mut terms);
flatten_add(*right, &mut terms);
terms.sort();
terms.into_iter().reduce(|a, b| a + b).unwrap()
}
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Mul,
right,
}) => {
let mut factors = Vec::new();
flatten_mul(*left, &mut factors);
flatten_mul(*right, &mut factors);
factors.sort();
factors.into_iter().reduce(|a, b| a * b).unwrap()
}
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Sub,
right,
}) => normalize(*left) - normalize(*right),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems sus, why would it be simpler than addition?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its simpler because there's no need to handle commutativity (no need to flatten and reorder)

_ => expr,
}
}

fn expand<T: Ord + Clone, R: Ord + Clone>(
expr: AlgebraicExpression<T, R>,
) -> AlgebraicExpression<T, R> {
match expr {
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Mul,
right,
}) => {
let left = expand(*left);
let right = expand(*right);
match (left, right) {
// (a + b)*c ==> a*c + b*c
(
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left: a,
op: AlgebraicBinaryOperator::Add,
right: b,
}),
c,
) => expand(*a * c.clone()) + expand(*b * c),
// a*(b + c) ==> a*b + a*c
(
a,
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left: b,
op: AlgebraicBinaryOperator::Add,
right: c,
}),
) => expand(a.clone() * *b) + expand(a.clone() * *c),
(left, right) => left * right,
}
}
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Add,
right,
}) => expand(*left) + expand(*right),
AlgebraicExpression::BinaryOperation(AlgebraicBinaryOperation {
left,
op: AlgebraicBinaryOperator::Sub,
right,
}) => expand(*left) - expand(*right),
_ => expr,
}
}

#[cfg(test)]
mod tests {
use super::*;
type Expr = AlgebraicExpression<u32, String>;

fn v(name: &str) -> Expr {
Expr::Reference(name.to_string())
}

#[test]
fn test_normalize() {
let expr1 = normalize(v("c") + v("b") + v("a"));
let expr2 = normalize(v("a") + v("b") + v("c"));
assert_eq!(expr1, expr2);

let expr1 = normalize(v("c") * v("b") * v("a"));
let expr2 = normalize(v("a") * v("b") * v("c"));
assert_eq!(expr1, expr2);

let expr1 = normalize(v("a") + v("b") * v("c"));
let expr2 = normalize(v("c") * v("b") + v("a"));
assert_eq!(expr1, expr2);
}

#[test]
fn test_expand() {
let e1 = expand(v("a") * (v("b") + v("c")));
let e2 = v("a") * v("b") + v("a") * v("c");
assert_eq!(e1, e2);

let e1 = expand((v("a") + v("b")) * v("c"));
let e2 = v("a") * v("c") + v("b") * v("c");
assert_eq!(e1, e2);

// (a + b) * (c + d)
let e1 = expand((v("a") + v("b")) * (v("c") + v("d")));
let e2 = v("a") * v("c") + v("a") * v("d") + v("b") * v("c") + v("b") * v("d");
assert_eq!(normalize(e1), normalize(e2));

// (a + b) * (c + d) * (e + f)
let e1 = expand((v("a") + v("b")) * (v("c") + v("d")) * (v("e") + v("f")));
let e2 = v("a") * v("c") * v("e")
+ v("a") * v("c") * v("f")
+ v("a") * v("d") * v("e")
+ v("a") * v("d") * v("f")
+ v("b") * v("c") * v("e")
+ v("b") * v("c") * v("f")
+ v("b") * v("d") * v("e")
+ v("b") * v("d") * v("f");
assert_eq!(normalize(e1), normalize(e2));

// a*(b + c) + d + b*(a + b)
let expr1 = normalize(expand(
v("a") * (v("b") + v("c")) + v("d") + v("b") * (v("a") + v("b")),
));
// a*b + a*c + (d + b*a + b*b)
let expr2 = normalize(expand(
v("a") * v("b") + v("a") * v("c") + (v("d") + v("b") * v("a") + v("b") * v("b")),
));
// (d + b*a) + b*b + (a*b + a*c)
let expr3 = normalize(expand(
(v("d") + v("b") * v("a")) + v("b") * v("b") + (v("a") * v("b") + v("a") * v("c")),
));
assert_eq!(expr1, expr2);
assert_eq!(expr1, expr3);
}
}

impl<T, R> ExpressionConvertible<T, R> for AlgebraicExpression<T, R> {
fn to_expression<
E: Add<E, Output = E> + Sub<E, Output = E> + Mul<E, Output = E> + Neg<Output = E>,
Expand Down
2 changes: 2 additions & 0 deletions openvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ log = "0.4.17"
serde_cbor = "0.11.2"
struct-reflection = { git = "https://github.com/gzanitti/struct-reflection-rs.git" }

bimap = "0.6.3"

[dev-dependencies]
test-log = { version = "0.2.17", features = ["trace"] }
pretty_assertions = "1.4.0"
Expand Down
Loading
Loading