@@ -68,16 +68,16 @@ impl Function {
68
68
/// The structure of this pass is simple:
69
69
/// Go through each block and re-insert all instructions.
70
70
pub ( crate ) fn constant_fold ( & mut self , use_constraint_info : bool ) {
71
- let mut context = Context :: new ( self , use_constraint_info) ;
72
- context . block_queue . push_back ( self . entry_block ( ) ) ;
71
+ let mut context = Context :: new ( use_constraint_info) ;
72
+ let mut dom = DominatorTree :: with_function ( self ) ;
73
73
74
74
while let Some ( block) = context. block_queue . pop_front ( ) {
75
75
if context. visited_blocks . contains ( & block) {
76
76
continue ;
77
77
}
78
78
79
79
context. visited_blocks . insert ( block) ;
80
- context. fold_constants_in_block ( self , block) ;
80
+ context. fold_constants_in_block ( & mut self . dfg , & mut dom , block) ;
81
81
}
82
82
}
83
83
}
@@ -99,15 +99,56 @@ struct Context {
99
99
/// We also keep track of how a value was simplified to other values per block. That is,
100
100
/// a same ValueId could have been simplified to one value in one block and to another value
101
101
/// in another block.
102
- constraint_simplification_mappings :
103
- HashMap < ValueId , HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > > ,
102
+ constraint_simplification_mappings : ConstraintSimplificationCache ,
104
103
105
104
// Cache of instructions without any side-effects along with their outputs.
106
105
cached_instruction_results : InstructionResultCache ,
106
+ }
107
107
108
- dom : DominatorTree ,
108
+ /// Records a simplified equivalents of an [`Instruction`] in the blocks
109
+ /// where the constraint that advised the simplification has been encountered.
110
+ ///
111
+ /// For more information see [`ConstraintSimplificationCache`].
112
+ #[ derive( Default ) ]
113
+ struct SimplificationCache {
114
+ /// Simplified expressions where we found them.
115
+ ///
116
+ /// It will always have at least one value because `add` is called
117
+ /// after the default is constructed.
118
+ simplifications : HashMap < BasicBlockId , ValueId > ,
109
119
}
110
120
121
+ impl SimplificationCache {
122
+ /// Called with a newly encountered simplification.
123
+ fn add ( & mut self , dfg : & DataFlowGraph , simple : ValueId , block : BasicBlockId ) {
124
+ self . simplifications
125
+ . entry ( block)
126
+ . and_modify ( |existing| {
127
+ // `SimplificationCache` may already hold a simplification in this block
128
+ // so we check whether `simple` is a better simplification than the current one.
129
+ if let Some ( ( _, simpler) ) = simplify ( dfg, * existing, simple) {
130
+ * existing = simpler;
131
+ } ;
132
+ } )
133
+ . or_insert ( simple) ;
134
+ }
135
+
136
+ /// Try to find a simplification in a visible block.
137
+ fn get ( & self , block : BasicBlockId , dom : & DominatorTree ) -> Option < ValueId > {
138
+ // Deterministically walk up the dominator chain until we encounter a block that contains a simplification.
139
+ dom. find_map_dominator ( block, |b| self . simplifications . get ( & b) . cloned ( ) )
140
+ }
141
+ }
142
+
143
+ /// HashMap from `(side_effects_enabled_var, Instruction)` to a simplified expression that it can
144
+ /// be replaced with based on constraints that testify to their equivalence, stored together
145
+ /// with the set of blocks at which this constraint has been observed.
146
+ ///
147
+ /// Only blocks dominated by one in the cache should have access to this information, otherwise
148
+ /// we create a sort of time paradox where we replace an instruction with a constant we believe
149
+ /// it _should_ equal to, without ever actually producing and asserting the value.
150
+ type ConstraintSimplificationCache = HashMap < ValueId , HashMap < ValueId , SimplificationCache > > ;
151
+
111
152
/// HashMap from (Instruction, side_effects_enabled_var) to the results of the instruction.
112
153
/// Stored as a two-level map to avoid cloning Instructions during the `.get` call.
113
154
///
@@ -124,55 +165,55 @@ struct ResultCache {
124
165
}
125
166
126
167
impl Context {
127
- fn new ( function : & Function , use_constraint_info : bool ) -> Self {
168
+ fn new ( use_constraint_info : bool ) -> Self {
128
169
Self {
129
170
use_constraint_info,
130
171
visited_blocks : Default :: default ( ) ,
131
172
block_queue : Default :: default ( ) ,
132
173
constraint_simplification_mappings : Default :: default ( ) ,
133
174
cached_instruction_results : Default :: default ( ) ,
134
- dom : DominatorTree :: with_function ( function) ,
135
175
}
136
176
}
137
177
138
- fn fold_constants_in_block ( & mut self , function : & mut Function , block : BasicBlockId ) {
139
- let instructions = function. dfg [ block] . take_instructions ( ) ;
178
+ fn fold_constants_in_block (
179
+ & mut self ,
180
+ dfg : & mut DataFlowGraph ,
181
+ dom : & mut DominatorTree ,
182
+ block : BasicBlockId ,
183
+ ) {
184
+ let instructions = dfg[ block] . take_instructions ( ) ;
140
185
141
- let mut side_effects_enabled_var =
142
- function . dfg . make_constant ( FieldElement :: one ( ) , Type :: bool ( ) ) ;
186
+ // Default side effect condition variable with an enabled state.
187
+ let mut side_effects_enabled_var = dfg. make_constant ( FieldElement :: one ( ) , Type :: bool ( ) ) ;
143
188
144
189
for instruction_id in instructions {
145
190
self . fold_constants_into_instruction (
146
- & mut function. dfg ,
191
+ dfg,
192
+ dom,
147
193
block,
148
194
instruction_id,
149
195
& mut side_effects_enabled_var,
150
196
) ;
151
197
}
152
- self . block_queue . extend ( function . dfg [ block] . successors ( ) ) ;
198
+ self . block_queue . extend ( dfg[ block] . successors ( ) ) ;
153
199
}
154
200
155
201
fn fold_constants_into_instruction (
156
202
& mut self ,
157
203
dfg : & mut DataFlowGraph ,
204
+ dom : & mut DominatorTree ,
158
205
block : BasicBlockId ,
159
206
id : InstructionId ,
160
207
side_effects_enabled_var : & mut ValueId ,
161
208
) {
162
- let constraint_simplification_mapping =
163
- self . constraint_simplification_mappings . get ( side_effects_enabled_var) ;
164
- let instruction = Self :: resolve_instruction (
165
- id,
166
- block,
167
- dfg,
168
- & mut self . dom ,
169
- constraint_simplification_mapping,
170
- ) ;
209
+ let constraint_simplification_mapping = self . get_constraint_map ( * side_effects_enabled_var) ;
210
+ let instruction =
211
+ Self :: resolve_instruction ( id, block, dfg, dom, constraint_simplification_mapping) ;
171
212
let old_results = dfg. instruction_results ( id) . to_vec ( ) ;
172
213
173
214
// If a copy of this instruction exists earlier in the block, then reuse the previous results.
174
215
if let Some ( cached_results) =
175
- self . get_cached ( dfg, & instruction, * side_effects_enabled_var, block)
216
+ self . get_cached ( dfg, dom , & instruction, * side_effects_enabled_var, block)
176
217
{
177
218
Self :: replace_result_ids ( dfg, & old_results, cached_results) ;
178
219
return ;
@@ -204,7 +245,7 @@ impl Context {
204
245
block : BasicBlockId ,
205
246
dfg : & DataFlowGraph ,
206
247
dom : & mut DominatorTree ,
207
- constraint_simplification_mapping : Option < & HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > > ,
248
+ constraint_simplification_mapping : & HashMap < ValueId , SimplificationCache > ,
208
249
) -> Instruction {
209
250
let instruction = dfg[ instruction_id] . clone ( ) ;
210
251
@@ -214,30 +255,28 @@ impl Context {
214
255
// This allows us to reach a stable final `ValueId` for each instruction input as we add more
215
256
// constraints to the cache.
216
257
fn resolve_cache (
258
+ block : BasicBlockId ,
217
259
dfg : & DataFlowGraph ,
218
260
dom : & mut DominatorTree ,
219
- cache : Option < & HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > > ,
261
+ cache : & HashMap < ValueId , SimplificationCache > ,
220
262
value_id : ValueId ,
221
- block : BasicBlockId ,
222
263
) -> ValueId {
223
264
let resolved_id = dfg. resolve ( value_id) ;
224
- let Some ( cached_values) = cache. and_then ( |cache| cache. get ( & resolved_id) ) else {
225
- return resolved_id;
226
- } ;
227
-
228
- for ( cached_block, cached_value) in cached_values {
229
- // We can only use the simplified value if it was simplified in a block that dominates the current one
230
- if dom. dominates ( * cached_block, block) {
231
- return resolve_cache ( dfg, dom, cache, * cached_value, block) ;
265
+ match cache. get ( & resolved_id) {
266
+ Some ( simplification_cache) => {
267
+ if let Some ( simplified) = simplification_cache. get ( block, dom) {
268
+ resolve_cache ( block, dfg, dom, cache, simplified)
269
+ } else {
270
+ resolved_id
271
+ }
232
272
}
273
+ None => resolved_id,
233
274
}
234
-
235
- resolved_id
236
275
}
237
276
238
277
// Resolve any inputs to ensure that we're comparing like-for-like instructions.
239
278
instruction. map_values ( |value_id| {
240
- resolve_cache ( dfg, dom, constraint_simplification_mapping, value_id, block )
279
+ resolve_cache ( block , dfg, dom, constraint_simplification_mapping, value_id)
241
280
} )
242
281
}
243
282
@@ -288,7 +327,7 @@ impl Context {
288
327
self . get_constraint_map ( side_effects_enabled_var)
289
328
. entry ( complex)
290
329
. or_default ( )
291
- . push ( ( block , simple) ) ;
330
+ . add ( dfg , simple, block ) ;
292
331
}
293
332
}
294
333
}
@@ -312,7 +351,7 @@ impl Context {
312
351
fn get_constraint_map (
313
352
& mut self ,
314
353
side_effects_enabled_var : ValueId ,
315
- ) -> & mut HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > {
354
+ ) -> & mut HashMap < ValueId , SimplificationCache > {
316
355
self . constraint_simplification_mappings . entry ( side_effects_enabled_var) . or_default ( )
317
356
}
318
357
@@ -327,19 +366,21 @@ impl Context {
327
366
}
328
367
}
329
368
369
+ /// Get a cached result if it can be used in this context.
330
370
fn get_cached < ' a > (
331
- & ' a mut self ,
371
+ & self ,
332
372
dfg : & DataFlowGraph ,
373
+ dom : & mut DominatorTree ,
333
374
instruction : & Instruction ,
334
375
side_effects_enabled_var : ValueId ,
335
376
block : BasicBlockId ,
336
- ) -> Option < & ' a [ ValueId ] > {
377
+ ) -> Option < & [ ValueId ] > {
337
378
let results_for_instruction = self . cached_instruction_results . get ( instruction) ?;
338
379
339
380
let predicate = self . use_constraint_info && instruction. requires_acir_gen_predicate ( dfg) ;
340
381
let predicate = predicate. then_some ( side_effects_enabled_var) ;
341
382
342
- results_for_instruction. get ( & predicate) ?. get ( block, & mut self . dom )
383
+ results_for_instruction. get ( & predicate) ?. get ( block, dom)
343
384
}
344
385
}
345
386
0 commit comments