@@ -166,20 +166,24 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
166166 let mut replacements = Replacements :: new ( body. local_decls . len ( ) ) ;
167167 for candidate @ CandidateAssignment { dest, src, loc } in candidates {
168168 // Merge locals that don't conflict.
169- if conflicts. contains ( dest. local , src) {
169+ if ! conflicts. can_unify ( dest. local , src) {
170170 debug ! ( "at assignment {:?}, conflict {:?} vs. {:?}" , loc, dest. local, src) ;
171171 continue ;
172172 }
173173
174+ if replacements. for_src ( candidate. src ) . is_some ( ) {
175+ debug ! ( "src {:?} already has replacement" , candidate. src) ;
176+ continue ;
177+ }
178+
174179 if !tcx. consider_optimizing ( || {
175180 format ! ( "DestinationPropagation {:?} {:?}" , source. def_id( ) , candidate)
176181 } ) {
177182 break ;
178183 }
179184
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 ) ;
183187 }
184188
185189 replacements. flatten ( tcx) ;
@@ -220,61 +224,21 @@ struct Replacements<'tcx> {
220224
221225 /// Whose locals' live ranges to kill.
222226 kill : BitSet < Local > ,
223-
224- /// Tracks locals that have already been merged together to prevent cycles.
225- unified_locals : InPlaceUnificationTable < UnifyLocal > ,
226227}
227228
228229impl Replacements < ' tcx > {
229230 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) }
241232 }
242233
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) ;
263236 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( ) ) ;
271238
272239 * entry = Some ( candidate. dest ) ;
273240 self . kill . insert ( candidate. src ) ;
274241 self . kill . insert ( candidate. dest . local ) ;
275-
276- trace ! ( "push({:?}): accepted" , candidate) ;
277- Ok ( ( ) )
278242 }
279243
280244 /// Applies the stored replacements to all replacements, until no replacements would result in
@@ -410,6 +374,9 @@ struct Conflicts<'a> {
410374
411375 /// Preallocated `BitSet` used by `unify`.
412376 unify_cache : BitSet < Local > ,
377+
378+ /// Tracks locals that have been merged together to prevent cycles and propagate conflicts.
379+ unified_locals : InPlaceUnificationTable < UnifyLocal > ,
413380}
414381
415382impl Conflicts < ' a > {
@@ -495,6 +462,15 @@ impl Conflicts<'a> {
495462 relevant_locals,
496463 matrix : conflicts,
497464 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+ } ,
498474 } ;
499475
500476 let mut live_and_init_locals = Vec :: new ( ) ;
@@ -761,11 +737,31 @@ impl Conflicts<'a> {
761737 }
762738 }
763739
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
766759 }
767760
768761 /// 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.
769765 ///
770766 /// This is called when the pass makes the decision to unify `a` and `b` (or parts of `a` and
771767 /// `b`) and is needed to ensure that future unification decisions take potentially newly
@@ -781,13 +777,24 @@ impl Conflicts<'a> {
781777 /// `_2` with `_0`, which also doesn't have a conflict in the above list. However `_2` is now
782778 /// `_3`, which does conflict with `_0`.
783779 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-
787780 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) ;
788790 trace ! ( "{:?} conflicts: {:?}" , a, self . matrix. iter( a) . format( ", " ) ) ;
789791 trace ! ( "{:?} conflicts: {:?}" , b, self . matrix. iter( b) . format( ", " ) ) ;
790792
793+ self . unified_locals . union ( a, b) ;
794+
795+ let root = self . unified_locals . find ( a) . 0 ;
796+ assert ! ( root == a || root == b) ;
797+
791798 // Make all locals that conflict with `a` also conflict with `b`, and vice versa.
792799 self . unify_cache . clear ( ) ;
793800 for conflicts_with_a in self . matrix . iter ( a) {
0 commit comments