@@ -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 > {
@@ -495,6 +462,15 @@ impl Conflicts<'a> {
495
462
relevant_locals,
496
463
matrix : conflicts,
497
464
unify_cache : BitSet :: new_empty ( body. local_decls . len ( ) ) ,
465
+ unified_locals : {
466
+ let mut table = InPlaceUnificationTable :: new ( ) ;
467
+ // Pre-fill table with all locals (this creates N nodes / "connected" components,
468
+ // "graph"-ically speaking).
469
+ for local in 0 ..body. local_decls . len ( ) {
470
+ assert_eq ! ( table. new_key( ( ) ) , UnifyLocal ( Local :: from_usize( local) ) ) ;
471
+ }
472
+ table
473
+ } ,
498
474
} ;
499
475
500
476
let mut live_and_init_locals = Vec :: new ( ) ;
@@ -761,11 +737,31 @@ impl Conflicts<'a> {
761
737
}
762
738
}
763
739
764
- fn contains ( & self , a : Local , b : Local ) -> bool {
765
- self . matrix . contains ( a, b)
740
+ /// Checks whether `a` and `b` may be merged. Returns `false` if there's a conflict.
741
+ fn can_unify ( & mut self , a : Local , b : Local ) -> bool {
742
+ // After some locals have been unified, their conflicts are only tracked in the root key,
743
+ // so look that up.
744
+ let a = self . unified_locals . find ( a) . 0 ;
745
+ let b = self . unified_locals . find ( b) . 0 ;
746
+
747
+ if a == b {
748
+ // Already merged (part of the same connected component).
749
+ return false ;
750
+ }
751
+
752
+ if self . matrix . contains ( a, b) {
753
+ // Conflict (derived via dataflow, intra-statement conflicts, or inherited from another
754
+ // local during unification).
755
+ return false ;
756
+ }
757
+
758
+ true
766
759
}
767
760
768
761
/// Merges the conflicts of `a` and `b`, so that each one inherits all conflicts of the other.
762
+ ///
763
+ /// `can_unify` must have returned `true` for the same locals, or this may panic or lead to
764
+ /// miscompiles.
769
765
///
770
766
/// This is called when the pass makes the decision to unify `a` and `b` (or parts of `a` and
771
767
/// `b`) and is needed to ensure that future unification decisions take potentially newly
@@ -781,13 +777,24 @@ impl Conflicts<'a> {
781
777
/// `_2` with `_0`, which also doesn't have a conflict in the above list. However `_2` is now
782
778
/// `_3`, which does conflict with `_0`.
783
779
fn unify ( & mut self , a : Local , b : Local ) {
784
- // FIXME: This might be somewhat slow. Conflict graphs are undirected, maybe we can use
785
- // something with union-find to speed this up?
786
-
787
780
trace ! ( "unify({:?}, {:?})" , a, b) ;
781
+
782
+ // Get the root local of the connected components. The root local stores the conflicts of
783
+ // all locals in the connected component (and *is stored* as the conflicting local of other
784
+ // locals).
785
+ let a = self . unified_locals . find ( a) . 0 ;
786
+ let b = self . unified_locals . find ( b) . 0 ;
787
+ assert_ne ! ( a, b) ;
788
+
789
+ trace ! ( "roots: a={:?}, b={:?}" , a, b) ;
788
790
trace ! ( "{:?} conflicts: {:?}" , a, self . matrix. iter( a) . format( ", " ) ) ;
789
791
trace ! ( "{:?} conflicts: {:?}" , b, self . matrix. iter( b) . format( ", " ) ) ;
790
792
793
+ self . unified_locals . union ( a, b) ;
794
+
795
+ let root = self . unified_locals . find ( a) . 0 ;
796
+ assert ! ( root == a || root == b) ;
797
+
791
798
// Make all locals that conflict with `a` also conflict with `b`, and vice versa.
792
799
self . unify_cache . clear ( ) ;
793
800
for conflicts_with_a in self . matrix . iter ( a) {
0 commit comments