@@ -90,12 +90,17 @@ struct Context {
90
90
91
91
/// Contains sets of values which are constrained to be equivalent to each other.
92
92
///
93
- /// The mapping's structure is `side_effects_enabled_var => (constrained_value => simplified_value)`.
93
+ /// The mapping's structure is `side_effects_enabled_var => (constrained_value => [(block, simplified_value)] )`.
94
94
///
95
95
/// We partition the maps of constrained values according to the side-effects flag at the point
96
96
/// at which the values are constrained. This prevents constraints which are only sometimes enforced
97
97
/// being used to modify the rest of the program.
98
- constraint_simplification_mappings : HashMap < ValueId , HashMap < ValueId , ValueId > > ,
98
+ ///
99
+ /// We also keep track of how a value was simplified to other values per block. That is,
100
+ /// a same ValueId could have been simplified to one value in one block and to another value
101
+ /// in another block.
102
+ constraint_simplification_mappings :
103
+ HashMap < ValueId , HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > > ,
99
104
100
105
// Cache of instructions without any side-effects along with their outputs.
101
106
cached_instruction_results : InstructionResultCache ,
@@ -154,8 +159,15 @@ impl Context {
154
159
id : InstructionId ,
155
160
side_effects_enabled_var : & mut ValueId ,
156
161
) {
157
- let constraint_simplification_mapping = self . get_constraint_map ( * side_effects_enabled_var) ;
158
- let instruction = Self :: resolve_instruction ( id, dfg, constraint_simplification_mapping) ;
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
+ ) ;
159
171
let old_results = dfg. instruction_results ( id) . to_vec ( ) ;
160
172
161
173
// If a copy of this instruction exists earlier in the block, then reuse the previous results.
@@ -189,8 +201,10 @@ impl Context {
189
201
/// Fetches an [`Instruction`] by its [`InstructionId`] and fully resolves its inputs.
190
202
fn resolve_instruction (
191
203
instruction_id : InstructionId ,
204
+ block : BasicBlockId ,
192
205
dfg : & DataFlowGraph ,
193
- constraint_simplification_mapping : & HashMap < ValueId , ValueId > ,
206
+ dom : & mut DominatorTree ,
207
+ constraint_simplification_mapping : Option < & HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > > ,
194
208
) -> Instruction {
195
209
let instruction = dfg[ instruction_id] . clone ( ) ;
196
210
@@ -201,19 +215,30 @@ impl Context {
201
215
// constraints to the cache.
202
216
fn resolve_cache (
203
217
dfg : & DataFlowGraph ,
204
- cache : & HashMap < ValueId , ValueId > ,
218
+ dom : & mut DominatorTree ,
219
+ cache : Option < & HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > > ,
205
220
value_id : ValueId ,
221
+ block : BasicBlockId ,
206
222
) -> ValueId {
207
223
let resolved_id = dfg. resolve ( value_id) ;
208
- match cache. get ( & resolved_id) {
209
- Some ( cached_value) => resolve_cache ( dfg, cache, * cached_value) ,
210
- None => resolved_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) ;
232
+ }
211
233
}
234
+
235
+ resolved_id
212
236
}
213
237
214
238
// Resolve any inputs to ensure that we're comparing like-for-like instructions.
215
- instruction
216
- . map_values ( |value_id| resolve_cache ( dfg, constraint_simplification_mapping, value_id) )
239
+ instruction. map_values ( |value_id| {
240
+ resolve_cache ( dfg, dom, constraint_simplification_mapping, value_id, block)
241
+ } )
217
242
}
218
243
219
244
/// Pushes a new [`Instruction`] into the [`DataFlowGraph`] which applies any optimizations
@@ -259,26 +284,11 @@ impl Context {
259
284
// to map from the more complex to the simpler value.
260
285
if let Instruction :: Constrain ( lhs, rhs, _) = instruction {
261
286
// These `ValueId`s should be fully resolved now.
262
- match ( & dfg[ lhs] , & dfg[ rhs] ) {
263
- // Ignore trivial constraints
264
- ( Value :: NumericConstant { .. } , Value :: NumericConstant { .. } ) => ( ) ,
265
-
266
- // Prefer replacing with constants where possible.
267
- ( Value :: NumericConstant { .. } , _) => {
268
- self . get_constraint_map ( side_effects_enabled_var) . insert ( rhs, lhs) ;
269
- }
270
- ( _, Value :: NumericConstant { .. } ) => {
271
- self . get_constraint_map ( side_effects_enabled_var) . insert ( lhs, rhs) ;
272
- }
273
- // Otherwise prefer block parameters over instruction results.
274
- // This is as block parameters are more likely to be a single witness rather than a full expression.
275
- ( Value :: Param { .. } , Value :: Instruction { .. } ) => {
276
- self . get_constraint_map ( side_effects_enabled_var) . insert ( rhs, lhs) ;
277
- }
278
- ( Value :: Instruction { .. } , Value :: Param { .. } ) => {
279
- self . get_constraint_map ( side_effects_enabled_var) . insert ( lhs, rhs) ;
280
- }
281
- ( _, _) => ( ) ,
287
+ if let Some ( ( complex, simple) ) = simplify ( dfg, lhs, rhs) {
288
+ self . get_constraint_map ( side_effects_enabled_var)
289
+ . entry ( complex)
290
+ . or_default ( )
291
+ . push ( ( block, simple) ) ;
282
292
}
283
293
}
284
294
}
@@ -302,7 +312,7 @@ impl Context {
302
312
fn get_constraint_map (
303
313
& mut self ,
304
314
side_effects_enabled_var : ValueId ,
305
- ) -> & mut HashMap < ValueId , ValueId > {
315
+ ) -> & mut HashMap < ValueId , Vec < ( BasicBlockId , ValueId ) > > {
306
316
self . constraint_simplification_mappings . entry ( side_effects_enabled_var) . or_default ( )
307
317
}
308
318
@@ -355,6 +365,25 @@ impl ResultCache {
355
365
}
356
366
}
357
367
368
+ /// Check if one expression is simpler than the other.
369
+ /// Returns `Some((complex, simple))` if a simplification was found, otherwise `None`.
370
+ /// Expects the `ValueId`s to be fully resolved.
371
+ fn simplify ( dfg : & DataFlowGraph , lhs : ValueId , rhs : ValueId ) -> Option < ( ValueId , ValueId ) > {
372
+ match ( & dfg[ lhs] , & dfg[ rhs] ) {
373
+ // Ignore trivial constraints
374
+ ( Value :: NumericConstant { .. } , Value :: NumericConstant { .. } ) => None ,
375
+
376
+ // Prefer replacing with constants where possible.
377
+ ( Value :: NumericConstant { .. } , _) => Some ( ( rhs, lhs) ) ,
378
+ ( _, Value :: NumericConstant { .. } ) => Some ( ( lhs, rhs) ) ,
379
+ // Otherwise prefer block parameters over instruction results.
380
+ // This is as block parameters are more likely to be a single witness rather than a full expression.
381
+ ( Value :: Param { .. } , Value :: Instruction { .. } ) => Some ( ( rhs, lhs) ) ,
382
+ ( Value :: Instruction { .. } , Value :: Param { .. } ) => Some ( ( lhs, rhs) ) ,
383
+ ( _, _) => None ,
384
+ }
385
+ }
386
+
358
387
#[ cfg( test) ]
359
388
mod test {
360
389
use std:: sync:: Arc ;
0 commit comments