@@ -166,20 +166,24 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
166
166
let mut replacements = Replacements :: new ( body. local_decls . len ( ) ) ;
167
167
for candidate @ CandidateAssignment { dest, src, loc } in candidates {
168
168
// Merge locals that don't conflict.
169
- if conflicts. contains ( dest. local , src) {
169
+ if ! conflicts. can_unify ( dest. local , src) {
170
170
debug ! ( "at assignment {:?}, conflict {:?} vs. {:?}" , loc, dest. local, src) ;
171
171
continue ;
172
172
}
173
173
174
+ if replacements. for_src ( candidate. src ) . is_some ( ) {
175
+ debug ! ( "src {:?} already has replacement" , candidate. src) ;
176
+ continue ;
177
+ }
178
+
174
179
if !tcx. consider_optimizing ( || {
175
180
format ! ( "DestinationPropagation {:?} {:?}" , source. def_id( ) , candidate)
176
181
} ) {
177
182
break ;
178
183
}
179
184
180
- if replacements. push ( candidate) . is_ok ( ) {
181
- conflicts. unify ( candidate. src , candidate. dest . local ) ;
182
- }
185
+ replacements. push ( candidate) ;
186
+ conflicts. unify ( candidate. src , candidate. dest . local ) ;
183
187
}
184
188
185
189
replacements. flatten ( tcx) ;
@@ -220,61 +224,21 @@ struct Replacements<'tcx> {
220
224
221
225
/// Whose locals' live ranges to kill.
222
226
kill : BitSet < Local > ,
223
-
224
- /// Tracks locals that have already been merged together to prevent cycles.
225
- unified_locals : InPlaceUnificationTable < UnifyLocal > ,
226
227
}
227
228
228
229
impl Replacements < ' tcx > {
229
230
fn new ( locals : usize ) -> Self {
230
- Self {
231
- map : IndexVec :: from_elem_n ( None , locals) ,
232
- kill : BitSet :: new_empty ( locals) ,
233
- unified_locals : {
234
- let mut table = InPlaceUnificationTable :: new ( ) ;
235
- for local in 0 ..locals {
236
- assert_eq ! ( table. new_key( ( ) ) , UnifyLocal ( Local :: from_usize( local) ) ) ;
237
- }
238
- table
239
- } ,
240
- }
231
+ Self { map : IndexVec :: from_elem_n ( None , locals) , kill : BitSet :: new_empty ( locals) }
241
232
}
242
233
243
- fn push ( & mut self , candidate : CandidateAssignment < ' tcx > ) -> Result < ( ) , ( ) > {
244
- if self . unified_locals . unioned ( candidate. src , candidate. dest . local ) {
245
- // Candidate conflicts with previous replacement (ie. could possibly form a cycle and
246
- // hang).
247
-
248
- let replacement = self . map [ candidate. src ] . as_mut ( ) . unwrap ( ) ;
249
-
250
- // If the current replacement is for the same `dest` local, there are 2 or more
251
- // equivalent `src = dest;` assignments. This is fine, the replacer will `nop` out all
252
- // of them.
253
- if replacement. local == candidate. dest . local {
254
- assert_eq ! ( replacement. projection, candidate. dest. projection) ;
255
- }
256
-
257
- // We still return `Err` in any case, as `src` and `dest` do not need to be unified
258
- // *again*.
259
- trace ! ( "push({:?}): already unified" , candidate) ;
260
- return Err ( ( ) ) ;
261
- }
262
-
234
+ fn push ( & mut self , candidate : CandidateAssignment < ' tcx > ) {
235
+ trace ! ( "Replacements::push({:?})" , candidate) ;
263
236
let entry = & mut self . map [ candidate. src ] ;
264
- if entry. is_some ( ) {
265
- // We're already replacing `src` with something else, so this candidate is out.
266
- trace ! ( "push({:?}): src already has replacement" , candidate) ;
267
- return Err ( ( ) ) ;
268
- }
269
-
270
- self . unified_locals . union ( candidate. src , candidate. dest . local ) ;
237
+ assert ! ( entry. is_none( ) ) ;
271
238
272
239
* entry = Some ( candidate. dest ) ;
273
240
self . kill . insert ( candidate. src ) ;
274
241
self . kill . insert ( candidate. dest . local ) ;
275
-
276
- trace ! ( "push({:?}): accepted" , candidate) ;
277
- Ok ( ( ) )
278
242
}
279
243
280
244
/// Applies the stored replacements to all replacements, until no replacements would result in
@@ -410,6 +374,9 @@ struct Conflicts<'a> {
410
374
411
375
/// Preallocated `BitSet` used by `unify`.
412
376
unify_cache : BitSet < Local > ,
377
+
378
+ /// Tracks locals that have been merged together to prevent cycles and propagate conflicts.
379
+ unified_locals : InPlaceUnificationTable < UnifyLocal > ,
413
380
}
414
381
415
382
impl Conflicts < ' a > {
@@ -481,6 +448,15 @@ impl Conflicts<'a> {
481
448
relevant_locals,
482
449
matrix : conflicts,
483
450
unify_cache : BitSet :: new_empty ( body. local_decls . len ( ) ) ,
451
+ unified_locals : {
452
+ let mut table = InPlaceUnificationTable :: new ( ) ;
453
+ // Pre-fill table with all locals (this creates N nodes / "connected" components,
454
+ // "graph"-ically speaking).
455
+ for local in 0 ..body. local_decls . len ( ) {
456
+ assert_eq ! ( table. new_key( ( ) ) , UnifyLocal ( Local :: from_usize( local) ) ) ;
457
+ }
458
+ table
459
+ } ,
484
460
} ;
485
461
486
462
let mut live_and_init_locals = Vec :: new ( ) ;
@@ -747,11 +723,31 @@ impl Conflicts<'a> {
747
723
}
748
724
}
749
725
750
- fn contains ( & self , a : Local , b : Local ) -> bool {
751
- self . matrix . contains ( a, b)
726
+ /// Checks whether `a` and `b` may be merged. Returns `false` if there's a conflict.
727
+ fn can_unify ( & mut self , a : Local , b : Local ) -> bool {
728
+ // After some locals have been unified, their conflicts are only tracked in the root key,
729
+ // so look that up.
730
+ let a = self . unified_locals . find ( a) . 0 ;
731
+ let b = self . unified_locals . find ( b) . 0 ;
732
+
733
+ if a == b {
734
+ // Already merged (part of the same connected component).
735
+ return false ;
736
+ }
737
+
738
+ if self . matrix . contains ( a, b) {
739
+ // Conflict (derived via dataflow, intra-statement conflicts, or inherited from another
740
+ // local during unification).
741
+ return false ;
742
+ }
743
+
744
+ true
752
745
}
753
746
754
747
/// Merges the conflicts of `a` and `b`, so that each one inherits all conflicts of the other.
748
+ ///
749
+ /// `can_unify` must have returned `true` for the same locals, or this may panic or lead to
750
+ /// miscompiles.
755
751
///
756
752
/// This is called when the pass makes the decision to unify `a` and `b` (or parts of `a` and
757
753
/// `b`) and is needed to ensure that future unification decisions take potentially newly
@@ -767,13 +763,24 @@ impl Conflicts<'a> {
767
763
/// `_2` with `_0`, which also doesn't have a conflict in the above list. However `_2` is now
768
764
/// `_3`, which does conflict with `_0`.
769
765
fn unify ( & mut self , a : Local , b : Local ) {
770
- // FIXME: This might be somewhat slow. Conflict graphs are undirected, maybe we can use
771
- // something with union-find to speed this up?
772
-
773
766
trace ! ( "unify({:?}, {:?})" , a, b) ;
767
+
768
+ // Get the root local of the connected components. The root local stores the conflicts of
769
+ // all locals in the connected component (and *is stored* as the conflicting local of other
770
+ // locals).
771
+ let a = self . unified_locals . find ( a) . 0 ;
772
+ let b = self . unified_locals . find ( b) . 0 ;
773
+ assert_ne ! ( a, b) ;
774
+
775
+ trace ! ( "roots: a={:?}, b={:?}" , a, b) ;
774
776
trace ! ( "{:?} conflicts: {:?}" , a, self . matrix. iter( a) . format( ", " ) ) ;
775
777
trace ! ( "{:?} conflicts: {:?}" , b, self . matrix. iter( b) . format( ", " ) ) ;
776
778
779
+ self . unified_locals . union ( a, b) ;
780
+
781
+ let root = self . unified_locals . find ( a) . 0 ;
782
+ assert ! ( root == a || root == b) ;
783
+
777
784
// Make all locals that conflict with `a` also conflict with `b`, and vice versa.
778
785
self . unify_cache . clear ( ) ;
779
786
for conflicts_with_a in self . matrix . iter ( a) {
0 commit comments