@@ -16,6 +16,10 @@ pub use simulator::CircuitSimulator;
16
16
use transformers:: transform_internal;
17
17
pub use transformers:: { transform, MIN_EXPRESSION_WIDTH } ;
18
18
19
+ /// We need multiple passes to stabilize the output.
20
+ /// The value was determined by running tests.
21
+ const MAX_OPTIMIZER_PASSES : usize = 3 ;
22
+
19
23
/// This module moves and decomposes acir opcodes. The transformation map allows consumers of this module to map
20
24
/// metadata they had about the opcodes to the new opcode structure generated after the transformation.
21
25
#[ derive( Debug ) ]
@@ -28,9 +32,9 @@ impl AcirTransformationMap {
28
32
/// Builds a map from a vector of pointers to the old acir opcodes.
29
33
/// The index of the vector is the new opcode index.
30
34
/// The value of the vector is the old opcode index pointed.
31
- fn new ( acir_opcode_positions : Vec < usize > ) -> Self {
35
+ fn new ( acir_opcode_positions : & [ usize ] ) -> Self {
32
36
let mut old_indices_to_new_indices = HashMap :: with_capacity ( acir_opcode_positions. len ( ) ) ;
33
- for ( new_index, old_index) in acir_opcode_positions. into_iter ( ) . enumerate ( ) {
37
+ for ( new_index, old_index) in acir_opcode_positions. iter ( ) . copied ( ) . enumerate ( ) {
34
38
old_indices_to_new_indices. entry ( old_index) . or_insert_with ( Vec :: new) . push ( new_index) ;
35
39
}
36
40
AcirTransformationMap { old_indices_to_new_indices }
@@ -72,17 +76,42 @@ fn transform_assert_messages<F: Clone>(
72
76
}
73
77
74
78
/// Applies [`ProofSystemCompiler`][crate::ProofSystemCompiler] specific optimizations to a [`Circuit`].
79
+ ///
80
+ /// Runs multiple passes until the output stabilizes.
75
81
pub fn compile < F : AcirField > (
76
82
acir : Circuit < F > ,
77
83
expression_width : ExpressionWidth ,
78
84
) -> ( Circuit < F > , AcirTransformationMap ) {
79
- let ( acir, acir_opcode_positions) = optimize_internal ( acir) ;
85
+ let mut pass = 0 ;
86
+ let mut prev_opcodes_hash = fxhash:: hash64 ( & acir. opcodes ) ;
87
+ let mut prev_acir = acir;
88
+
89
+ // For most test programs it would be enough to only loop `transform_internal`,
90
+ // but some of them don't stabilize unless we also repeat the backend agnostic optimizations.
91
+ let ( mut acir, acir_opcode_positions) = loop {
92
+ let ( acir, acir_opcode_positions) = optimize_internal ( prev_acir) ;
93
+
94
+ // Stop if we have already done at least one transform and an extra optimization changed nothing.
95
+ if pass > 0 && prev_opcodes_hash == fxhash:: hash64 ( & acir. opcodes ) {
96
+ break ( acir, acir_opcode_positions) ;
97
+ }
80
98
81
- let ( mut acir, acir_opcode_positions) =
82
- transform_internal ( acir, expression_width, acir_opcode_positions) ;
99
+ let ( acir, acir_opcode_positions) =
100
+ transform_internal ( acir, expression_width, acir_opcode_positions) ;
101
+
102
+ let opcodes_hash = fxhash:: hash64 ( & acir. opcodes ) ;
103
+
104
+ // Stop if the output hasn't change in this loop or we went too long.
105
+ if pass == MAX_OPTIMIZER_PASSES - 1 || prev_opcodes_hash == opcodes_hash {
106
+ break ( acir, acir_opcode_positions) ;
107
+ }
83
108
84
- let transformation_map = AcirTransformationMap :: new ( acir_opcode_positions) ;
109
+ pass += 1 ;
110
+ prev_acir = acir;
111
+ prev_opcodes_hash = opcodes_hash;
112
+ } ;
85
113
114
+ let transformation_map = AcirTransformationMap :: new ( & acir_opcode_positions) ;
86
115
acir. assert_messages = transform_assert_messages ( acir. assert_messages , & transformation_map) ;
87
116
88
117
( acir, transformation_map)
0 commit comments