Skip to content

Commit

Permalink
Merge branch 'master' into jf/make-for-a-statement
Browse files Browse the repository at this point in the history
  • Loading branch information
jfecher committed Oct 4, 2023
2 parents e2a04db + a3593c0 commit 4ebba50
Show file tree
Hide file tree
Showing 177 changed files with 982 additions and 2,165 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ jobs:
- name: Install Yarn dependencies
uses: ./.github/actions/setup

- name: Install Playwright
uses: ./.github/actions/install-playwright

- name: Install jq
run: sudo apt-get install jq

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ tower = "0.4"
url = "2.2.0"
wasm-bindgen = { version = "=0.2.86", features = ["serde-serialize"] }
wasm-bindgen-test = "0.3.33"
js-sys = "0.3.62"
base64 = "0.21.2"
fxhash = "0.2.1"
acir = { path = "acvm-repo/acir", default-features = false }
Expand Down
215 changes: 7 additions & 208 deletions acvm-repo/acvm/src/compiler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
use acir::{
circuit::{
brillig::BrilligOutputs, directives::Directive, opcodes::UnsupportedMemoryOpcode, Circuit,
Opcode, OpcodeLocation,
},
native_types::{Expression, Witness},
BlackBoxFunc, FieldElement,
circuit::{opcodes::UnsupportedMemoryOpcode, Circuit, Opcode, OpcodeLocation},
BlackBoxFunc,
};
use indexmap::IndexMap;
use thiserror::Error;

use crate::Language;
Expand All @@ -15,8 +10,9 @@ use crate::Language;
mod optimizers;
mod transformers;

use optimizers::{GeneralOptimizer, RangeOptimizer};
use transformers::{CSatTransformer, FallbackTransformer, R1CSTransformer};
pub use optimizers::optimize;
pub use transformers::transform;
use transformers::transform_internal;

#[derive(PartialEq, Eq, Debug, Error)]
pub enum CompileError {
Expand Down Expand Up @@ -77,204 +73,7 @@ pub fn compile(
np_language: Language,
is_opcode_supported: impl Fn(&Opcode) -> bool,
) -> Result<(Circuit, AcirTransformationMap), CompileError> {
// Instantiate the optimizer.
// Currently the optimizer and reducer are one in the same
// for CSAT
let (acir, AcirTransformationMap { acir_opcode_positions }) = optimize(acir);

// Track original acir opcode positions throughout the transformation passes of the compilation
// by applying the modifications done to the circuit opcodes and also to the opcode_positions (delete and insert)
let acir_opcode_positions = acir.opcodes.iter().enumerate().map(|(i, _)| i).collect();

// Fallback transformer pass
let (acir, acir_opcode_positions) =
FallbackTransformer::transform(acir, is_opcode_supported, acir_opcode_positions)?;

// General optimizer pass
let mut opcodes: Vec<Opcode> = Vec::new();
for opcode in acir.opcodes {
match opcode {
Opcode::Arithmetic(arith_expr) => {
opcodes.push(Opcode::Arithmetic(GeneralOptimizer::optimize(arith_expr)));
}
other_opcode => opcodes.push(other_opcode),
};
}
let acir = Circuit { opcodes, ..acir };

// Range optimization pass
let range_optimizer = RangeOptimizer::new(acir);
let (mut acir, acir_opcode_positions) =
range_optimizer.replace_redundant_ranges(acir_opcode_positions);

let mut transformer = match &np_language {
crate::Language::R1CS => {
let transformation_map = AcirTransformationMap { acir_opcode_positions };
acir.assert_messages =
transform_assert_messages(acir.assert_messages, &transformation_map);
let transformer = R1CSTransformer::new(acir);
return Ok((transformer.transform(), transformation_map));
}
crate::Language::PLONKCSat { width } => {
let mut csat = CSatTransformer::new(*width);
for value in acir.circuit_arguments() {
csat.mark_solvable(value);
}
csat
}
};

// TODO: the code below is only for CSAT transformer
// TODO it may be possible to refactor it in a way that we do not need to return early from the r1cs
// TODO or at the very least, we could put all of it inside of CSatOptimizer pass

let mut new_acir_opcode_positions: Vec<usize> = Vec::with_capacity(acir_opcode_positions.len());
// Optimize the arithmetic gates by reducing them into the correct width and
// creating intermediate variables when necessary
let mut transformed_opcodes = Vec::new();

let mut next_witness_index = acir.current_witness_index + 1;
// maps a normalized expression to the intermediate variable which represents the expression, along with its 'norm'
// the 'norm' is simply the value of the first non zero coefficient in the expression, taken from the linear terms, or quadratic terms if there is none.
let mut intermediate_variables: IndexMap<Expression, (FieldElement, Witness)> = IndexMap::new();
for (index, opcode) in acir.opcodes.iter().enumerate() {
match opcode {
Opcode::Arithmetic(arith_expr) => {
let len = intermediate_variables.len();

let arith_expr = transformer.transform(
arith_expr.clone(),
&mut intermediate_variables,
&mut next_witness_index,
);

// Update next_witness counter
next_witness_index += (intermediate_variables.len() - len) as u32;
let mut new_opcodes = Vec::new();
for (g, (norm, w)) in intermediate_variables.iter().skip(len) {
// de-normalize
let mut intermediate_opcode = g * *norm;
// constrain the intermediate opcode to the intermediate variable
intermediate_opcode.linear_combinations.push((-FieldElement::one(), *w));
intermediate_opcode.sort();
new_opcodes.push(intermediate_opcode);
}
new_opcodes.push(arith_expr);
for opcode in new_opcodes {
new_acir_opcode_positions.push(acir_opcode_positions[index]);
transformed_opcodes.push(Opcode::Arithmetic(opcode));
}
}
Opcode::BlackBoxFuncCall(func) => {
match func {
acir::circuit::opcodes::BlackBoxFuncCall::AND { output, .. }
| acir::circuit::opcodes::BlackBoxFuncCall::XOR { output, .. } => {
transformer.mark_solvable(*output);
}
acir::circuit::opcodes::BlackBoxFuncCall::RANGE { .. } => (),
acir::circuit::opcodes::BlackBoxFuncCall::SHA256 { outputs, .. }
| acir::circuit::opcodes::BlackBoxFuncCall::Keccak256 { outputs, .. }
| acir::circuit::opcodes::BlackBoxFuncCall::Keccak256VariableLength {
outputs,
..
}
| acir::circuit::opcodes::BlackBoxFuncCall::RecursiveAggregation {
output_aggregation_object: outputs,
..
}
| acir::circuit::opcodes::BlackBoxFuncCall::Blake2s { outputs, .. } => {
for witness in outputs {
transformer.mark_solvable(*witness);
}
}
acir::circuit::opcodes::BlackBoxFuncCall::FixedBaseScalarMul {
outputs,
..
}
| acir::circuit::opcodes::BlackBoxFuncCall::Pedersen { outputs, .. } => {
transformer.mark_solvable(outputs.0);
transformer.mark_solvable(outputs.1);
}
acir::circuit::opcodes::BlackBoxFuncCall::HashToField128Security {
output,
..
}
| acir::circuit::opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { output, .. }
| acir::circuit::opcodes::BlackBoxFuncCall::EcdsaSecp256r1 { output, .. }
| acir::circuit::opcodes::BlackBoxFuncCall::SchnorrVerify { output, .. } => {
transformer.mark_solvable(*output);
}
}

new_acir_opcode_positions.push(acir_opcode_positions[index]);
transformed_opcodes.push(opcode.clone());
}
Opcode::Directive(directive) => {
match directive {
Directive::Quotient(quotient_directive) => {
transformer.mark_solvable(quotient_directive.q);
transformer.mark_solvable(quotient_directive.r);
}
Directive::ToLeRadix { b, .. } => {
for witness in b {
transformer.mark_solvable(*witness);
}
}
Directive::PermutationSort { bits, .. } => {
for witness in bits {
transformer.mark_solvable(*witness);
}
}
}
new_acir_opcode_positions.push(acir_opcode_positions[index]);
transformed_opcodes.push(opcode.clone());
}
Opcode::MemoryInit { .. } => {
// `MemoryInit` does not write values to the `WitnessMap`
new_acir_opcode_positions.push(acir_opcode_positions[index]);
transformed_opcodes.push(opcode.clone());
}
Opcode::MemoryOp { op, .. } => {
for (_, witness1, witness2) in &op.value.mul_terms {
transformer.mark_solvable(*witness1);
transformer.mark_solvable(*witness2);
}
for (_, witness) in &op.value.linear_combinations {
transformer.mark_solvable(*witness);
}
new_acir_opcode_positions.push(acir_opcode_positions[index]);
transformed_opcodes.push(opcode.clone());
}
Opcode::Brillig(brillig) => {
for output in &brillig.outputs {
match output {
BrilligOutputs::Simple(w) => transformer.mark_solvable(*w),
BrilligOutputs::Array(v) => {
for witness in v {
transformer.mark_solvable(*witness);
}
}
}
}
new_acir_opcode_positions.push(acir_opcode_positions[index]);
transformed_opcodes.push(opcode.clone());
}
}
}

let current_witness_index = next_witness_index - 1;

let transformation_map =
AcirTransformationMap { acir_opcode_positions: new_acir_opcode_positions };

let acir = Circuit {
current_witness_index,
opcodes: transformed_opcodes,
// The optimizer does not add new public inputs
private_parameters: acir.private_parameters,
public_parameters: acir.public_parameters,
return_values: acir.return_values,
assert_messages: transform_assert_messages(acir.assert_messages, &transformation_map),
};

Ok((acir, transformation_map))
transform_internal(acir, np_language, is_opcode_supported, acir_opcode_positions)
}
40 changes: 40 additions & 0 deletions acvm-repo/acvm/src/compiler/optimizers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
use acir::circuit::{Circuit, Opcode};

mod general;
mod redundant_range;
mod unused_memory;

pub(crate) use general::GeneralOptimizer;
pub(crate) use redundant_range::RangeOptimizer;

use self::unused_memory::UnusedMemoryOptimizer;

use super::AcirTransformationMap;

/// Applies [`ProofSystemCompiler`][crate::ProofSystemCompiler] independent optimizations to a [`Circuit`].
pub fn optimize(acir: Circuit) -> (Circuit, AcirTransformationMap) {
// General optimizer pass
let mut opcodes: Vec<Opcode> = Vec::new();
for opcode in acir.opcodes {
match opcode {
Opcode::Arithmetic(arith_expr) => {
opcodes.push(Opcode::Arithmetic(GeneralOptimizer::optimize(arith_expr)));
}
other_opcode => opcodes.push(other_opcode),
};
}
let acir = Circuit { opcodes, ..acir };

// Track original acir opcode positions throughout the transformation passes of the compilation
// by applying the modifications done to the circuit opcodes and also to the opcode_positions (delete and insert)
let acir_opcode_positions = acir.opcodes.iter().enumerate().map(|(i, _)| i).collect();

// Unused memory optimization pass
let memory_optimizer = UnusedMemoryOptimizer::new(acir);
let (acir, acir_opcode_positions) =
memory_optimizer.remove_unused_memory_initializations(acir_opcode_positions);

// Range optimization pass
let range_optimizer = RangeOptimizer::new(acir);
let (acir, acir_opcode_positions) =
range_optimizer.replace_redundant_ranges(acir_opcode_positions);

let transformation_map = AcirTransformationMap { acir_opcode_positions };

(acir, transformation_map)
}
61 changes: 61 additions & 0 deletions acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use acir::circuit::{opcodes::BlockId, Circuit, Opcode};
use std::collections::HashSet;

/// `UnusedMemoryOptimizer` will remove initializations of memory blocks which are unused.
pub(crate) struct UnusedMemoryOptimizer {
unused_memory_initializations: HashSet<BlockId>,
circuit: Circuit,
}

impl UnusedMemoryOptimizer {
/// Creates a new `UnusedMemoryOptimizer ` by collecting unused memory init
/// opcodes from `Circuit`.
pub(crate) fn new(circuit: Circuit) -> Self {
let unused_memory_initializations = Self::collect_unused_memory_initializations(&circuit);
Self { circuit, unused_memory_initializations }
}

/// Creates a set of ids for memory blocks for which no [`Opcode::MemoryOp`]s exist.
///
/// These memory blocks can be safely removed.
fn collect_unused_memory_initializations(circuit: &Circuit) -> HashSet<BlockId> {
let mut unused_memory_initialization = HashSet::new();

for opcode in &circuit.opcodes {
match opcode {
Opcode::MemoryInit { block_id, .. } => {
unused_memory_initialization.insert(*block_id);
}
Opcode::MemoryOp { block_id, .. } => {
unused_memory_initialization.remove(block_id);
}
_ => (),
}
}
unused_memory_initialization
}

/// Returns a `Circuit` where [`Opcode::MemoryInit`]s for unused memory blocks are dropped.
pub(crate) fn remove_unused_memory_initializations(
self,
order_list: Vec<usize>,
) -> (Circuit, Vec<usize>) {
let mut new_order_list = Vec::with_capacity(order_list.len());
let mut optimized_opcodes = Vec::with_capacity(self.circuit.opcodes.len());
for (idx, opcode) in self.circuit.opcodes.iter().enumerate() {
match opcode {
Opcode::MemoryInit { block_id, .. }
if self.unused_memory_initializations.contains(block_id) =>
{
// Drop opcode
}
_ => {
new_order_list.push(order_list[idx]);
optimized_opcodes.push(opcode.clone());
}
}
}

(Circuit { opcodes: optimized_opcodes, ..self.circuit }, new_order_list)
}
}
Loading

0 comments on commit 4ebba50

Please sign in to comment.