@@ -7,6 +7,7 @@ use super::Stats;
7
7
use crate :: dominator_tree:: DominatorTree ;
8
8
use crate :: fx:: { FxHashMap , FxHashSet } ;
9
9
use crate :: hash_map:: Entry as HashEntry ;
10
+ use crate :: inst_predicates:: is_pure_for_egraph;
10
11
use crate :: ir:: { Block , Function , Inst , Value , ValueDef } ;
11
12
use crate :: loop_analysis:: { Loop , LoopAnalysis } ;
12
13
use crate :: scoped_hash_map:: ScopedHashMap ;
@@ -216,46 +217,92 @@ impl<'a> Elaborator<'a> {
216
217
217
218
fn compute_best_values ( & mut self ) {
218
219
let best = & mut self . value_to_best_value ;
219
- for ( value, def) in self . func . dfg . values_and_defs ( ) {
220
- trace ! ( "computing best for value {:?} def {:?}" , value, def) ;
221
- match def {
222
- ValueDef :: Union ( x, y) => {
223
- // Pick the best of the two options based on
224
- // min-cost. This works because each element of `best`
225
- // is a `(cost, value)` tuple; `cost` comes first so
226
- // the natural comparison works based on cost, and
227
- // breaks ties based on value number.
228
- trace ! ( " -> best of {:?} and {:?}" , best[ x] , best[ y] ) ;
229
- best[ value] = std:: cmp:: min ( best[ x] , best[ y] ) ;
230
- trace ! ( " -> {:?}" , best[ value] ) ;
231
- }
232
- ValueDef :: Param ( _, _) => {
233
- best[ value] = BestEntry ( Cost :: zero ( ) , value) ;
220
+
221
+ // Do a fixpoint loop to compute the best value for each eclass.
222
+ //
223
+ // The maximum number of iterations is the length of the longest chain
224
+ // of `vNN -> vMM` edges in the dataflow graph where `NN < MM`, so this
225
+ // is *technically* quadratic, but `cranelift-frontend` won't construct
226
+ // any such edges. NaN canonicalization will introduce some of these
227
+ // edges, but they are chains of only two or three edges. So in
228
+ // practice, we *never* do more than a handful of iterations here unless
229
+ // (a) we parsed the CLIF from text and the text was funkily numbered,
230
+ // which we don't really care about, or (b) the CLIF producer did
231
+ // something weird, in which case it is their responsibility to stop
232
+ // doing that.
233
+ trace ! ( "Entering fixpoint loop to compute the best values for each eclass" ) ;
234
+ let mut keep_going = true ;
235
+ while keep_going {
236
+ keep_going = false ;
237
+ trace ! (
238
+ "fixpoint iteration {}" ,
239
+ self . stats. elaborate_best_cost_fixpoint_iters
240
+ ) ;
241
+ self . stats . elaborate_best_cost_fixpoint_iters += 1 ;
242
+
243
+ for ( value, def) in self . func . dfg . values_and_defs ( ) {
244
+ // If the cost of this value is finite, then we've already found
245
+ // its final cost.
246
+ if best[ value] . 0 . is_finite ( ) {
247
+ continue ;
234
248
}
235
- // If the Inst is inserted into the layout (which is,
236
- // at this point, only the side-effecting skeleton),
237
- // then it must be computed and thus we give it zero
238
- // cost.
239
- ValueDef :: Result ( inst, _) => {
240
- if let Some ( _) = self . func . layout . inst_block ( inst) {
241
- best[ value] = BestEntry ( Cost :: zero ( ) , value) ;
242
- } else {
243
- trace ! ( " -> value {}: result, computing cost" , value) ;
244
- let inst_data = & self . func . dfg . insts [ inst] ;
245
- // N.B.: at this point we know that the opcode is
246
- // pure, so `pure_op_cost`'s precondition is
247
- // satisfied.
248
- let cost = Cost :: of_pure_op (
249
- inst_data. opcode ( ) ,
250
- self . func . dfg . inst_values ( inst) . map ( |value| best[ value] . 0 ) ,
249
+
250
+ trace ! ( "computing best for value {:?} def {:?}" , value, def) ;
251
+ let orig_best_value = best[ value] ;
252
+
253
+ match def {
254
+ ValueDef :: Union ( x, y) => {
255
+ // Pick the best of the two options based on
256
+ // min-cost. This works because each element of `best`
257
+ // is a `(cost, value)` tuple; `cost` comes first so
258
+ // the natural comparison works based on cost, and
259
+ // breaks ties based on value number.
260
+ best[ value] = std:: cmp:: min ( best[ x] , best[ y] ) ;
261
+ trace ! (
262
+ " -> best of union({:?}, {:?}) = {:?}" ,
263
+ best[ x] ,
264
+ best[ y] ,
265
+ best[ value]
251
266
) ;
252
- best[ value] = BestEntry ( cost, value) ;
253
267
}
254
- }
255
- } ;
256
- debug_assert_ne ! ( best[ value] . 0 , Cost :: infinity( ) ) ;
257
- debug_assert_ne ! ( best[ value] . 1 , Value :: reserved_value( ) ) ;
258
- trace ! ( "best for eclass {:?}: {:?}" , value, best[ value] ) ;
268
+ ValueDef :: Param ( _, _) => {
269
+ best[ value] = BestEntry ( Cost :: zero ( ) , value) ;
270
+ }
271
+ // If the Inst is inserted into the layout (which is,
272
+ // at this point, only the side-effecting skeleton),
273
+ // then it must be computed and thus we give it zero
274
+ // cost.
275
+ ValueDef :: Result ( inst, _) => {
276
+ if let Some ( _) = self . func . layout . inst_block ( inst) {
277
+ best[ value] = BestEntry ( Cost :: zero ( ) , value) ;
278
+ } else {
279
+ let inst_data = & self . func . dfg . insts [ inst] ;
280
+ // N.B.: at this point we know that the opcode is
281
+ // pure, so `pure_op_cost`'s precondition is
282
+ // satisfied.
283
+ let cost = Cost :: of_pure_op (
284
+ inst_data. opcode ( ) ,
285
+ self . func . dfg . inst_values ( inst) . map ( |value| best[ value] . 0 ) ,
286
+ ) ;
287
+ best[ value] = BestEntry ( cost, value) ;
288
+ trace ! ( " -> cost of value {} = {:?}" , value, cost) ;
289
+ }
290
+ }
291
+ } ;
292
+
293
+ // Keep on iterating the fixpoint loop while we are finding new
294
+ // best values.
295
+ keep_going |= orig_best_value != best[ value] ;
296
+ }
297
+ }
298
+
299
+ if cfg ! ( any( feature = "trace-log" , debug_assertions) ) {
300
+ trace ! ( "finished fixpoint loop to compute best value for each eclass" ) ;
301
+ for value in self . func . dfg . values ( ) {
302
+ debug_assert_ne ! ( best[ value] . 0 , Cost :: infinity( ) ) ;
303
+ debug_assert_ne ! ( best[ value] . 1 , Value :: reserved_value( ) ) ;
304
+ trace ! ( "-> best for eclass {:?}: {:?}" , value, best[ value] ) ;
305
+ }
259
306
}
260
307
}
261
308
@@ -606,7 +653,13 @@ impl<'a> Elaborator<'a> {
606
653
}
607
654
inst
608
655
} ;
656
+
609
657
// Place the inst just before `before`.
658
+ debug_assert ! (
659
+ is_pure_for_egraph( self . func, inst) ,
660
+ "something has gone very wrong if we are elaborating effectful \
661
+ instructions, they should have remained in the skeleton"
662
+ ) ;
610
663
self . func . layout . insert_inst ( inst, before) ;
611
664
612
665
// Update the inst's arguments.
0 commit comments