diff --git a/acvm-repo/acvm/src/compiler/mod.rs b/acvm-repo/acvm/src/compiler/mod.rs index daedd77c4a0..ee84f7bf60b 100644 --- a/acvm-repo/acvm/src/compiler/mod.rs +++ b/acvm-repo/acvm/src/compiler/mod.rs @@ -16,10 +16,6 @@ pub use simulator::CircuitSimulator; use transformers::transform_internal; pub use transformers::{transform, MIN_EXPRESSION_WIDTH}; -/// We need multiple passes to stabilize the output. -/// The value was determined by running tests. -const MAX_OPTIMIZER_PASSES: usize = 3; - /// This module moves and decomposes acir opcodes. The transformation map allows consumers of this module to map /// metadata they had about the opcodes to the new opcode structure generated after the transformation. #[derive(Debug)] @@ -84,41 +80,10 @@ pub fn compile( ) -> (Circuit, AcirTransformationMap) { let acir_opcode_positions = (0..acir.opcodes.len()).collect::>(); - if MAX_OPTIMIZER_PASSES == 0 { - return (acir, AcirTransformationMap::new(&acir_opcode_positions)); - } - - let mut pass = 0; - let mut prev_opcodes_hash = fxhash::hash64(&acir.opcodes); - let mut prev_acir = acir; - let mut prev_acir_opcode_positions = acir_opcode_positions; - - // For most test programs it would be enough to only loop `transform_internal`, - // but some of them don't stabilize unless we also repeat the backend agnostic optimizations. - let (mut acir, acir_opcode_positions) = loop { - let (acir, acir_opcode_positions) = - optimize_internal(prev_acir, prev_acir_opcode_positions); - - // Stop if we have already done at least one transform and an extra optimization changed nothing. - if pass > 0 && prev_opcodes_hash == fxhash::hash64(&acir.opcodes) { - break (acir, acir_opcode_positions); - } - - let (acir, acir_opcode_positions) = - transform_internal(acir, expression_width, acir_opcode_positions); - - let opcodes_hash = fxhash::hash64(&acir.opcodes); - - // Stop if the output hasn't change in this loop or we went too long. - if pass == MAX_OPTIMIZER_PASSES - 1 || prev_opcodes_hash == opcodes_hash { - break (acir, acir_opcode_positions); - } + let (acir, acir_opcode_positions) = optimize_internal(acir, acir_opcode_positions); - pass += 1; - prev_acir = acir; - prev_opcodes_hash = opcodes_hash; - prev_acir_opcode_positions = acir_opcode_positions; - }; + let (mut acir, acir_opcode_positions) = + transform_internal(acir, expression_width, acir_opcode_positions); let transformation_map = AcirTransformationMap::new(&acir_opcode_positions); acir.assert_messages = transform_assert_messages(acir.assert_messages, &transformation_map); diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs index a499aec1b30..77a5d2da34e 100644 --- a/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -14,12 +14,17 @@ mod csat; pub(crate) use csat::CSatTransformer; pub use csat::MIN_EXPRESSION_WIDTH; +use tracing::info; use super::{ - optimizers::MergeExpressionsOptimizer, transform_assert_messages, AcirTransformationMap, - MAX_OPTIMIZER_PASSES, + optimizers::{MergeExpressionsOptimizer, RangeOptimizer}, + transform_assert_messages, AcirTransformationMap, }; +/// We need multiple passes to stabilize the output. +/// The value was determined by running tests. +const MAX_TRANSFORMER_PASSES: usize = 3; + /// Applies [`ProofSystemCompiler`][crate::ProofSystemCompiler] specific optimizations to a [`Circuit`]. pub fn transform( acir: Circuit, @@ -50,12 +55,18 @@ pub(super) fn transform_internal( expression_width: ExpressionWidth, mut acir_opcode_positions: Vec, ) -> (Circuit, Vec) { + if acir.opcodes.len() == 1 && matches!(acir.opcodes[0], Opcode::BrilligCall { .. }) { + info!("Program is fully unconstrained, skipping transformation pass"); + return (acir, acir_opcode_positions); + } + // Allow multiple passes until we have stable output. let mut prev_opcodes_hash = fxhash::hash64(&acir.opcodes); // For most test programs it would be enough to loop here, but some of them // don't stabilize unless we also repeat the backend agnostic optimizations. - for _ in 0..MAX_OPTIMIZER_PASSES { + for _ in 0..MAX_TRANSFORMER_PASSES { + info!("Number of opcodes {}", acir.opcodes.len()); let (new_acir, new_acir_opcode_positions) = transform_internal_once(acir, expression_width, acir_opcode_positions); @@ -217,6 +228,12 @@ fn transform_internal_once( ..acir }; + // The `MergeOptimizer` can merge two witnesses which have range opcodes applied to them + // so we run the `RangeOptimizer` afterwards to clear these up. + let range_optimizer = RangeOptimizer::new(acir); + let (acir, new_acir_opcode_positions) = + range_optimizer.replace_redundant_ranges(new_acir_opcode_positions); + (acir, new_acir_opcode_positions) }