Skip to content

Commit 97d49ef

Browse files
Properly inherit conflicts when merging locals
1 parent b7c44d3 commit 97d49ef

File tree

2 files changed

+84
-53
lines changed

2 files changed

+84
-53
lines changed

src/librustc_mir/transform/dest_prop.rs

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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

228229
impl 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

415382
impl Conflicts<'a> {
@@ -481,6 +448,15 @@ impl Conflicts<'a> {
481448
relevant_locals,
482449
matrix: conflicts,
483450
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+
},
484460
};
485461

486462
let mut live_and_init_locals = Vec::new();
@@ -747,11 +723,31 @@ impl Conflicts<'a> {
747723
}
748724
}
749725

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
752745
}
753746

754747
/// 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.
755751
///
756752
/// This is called when the pass makes the decision to unify `a` and `b` (or parts of `a` and
757753
/// `b`) and is needed to ensure that future unification decisions take potentially newly
@@ -767,13 +763,24 @@ impl Conflicts<'a> {
767763
/// `_2` with `_0`, which also doesn't have a conflict in the above list. However `_2` is now
768764
/// `_3`, which does conflict with `_0`.
769765
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-
773766
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);
774776
trace!("{:?} conflicts: {:?}", a, self.matrix.iter(a).format(", "));
775777
trace!("{:?} conflicts: {:?}", b, self.matrix.iter(b).format(", "));
776778

779+
self.unified_locals.union(a, b);
780+
781+
let root = self.unified_locals.find(a).0;
782+
assert!(root == a || root == b);
783+
777784
// Make all locals that conflict with `a` also conflict with `b`, and vice versa.
778785
self.unify_cache.clear();
779786
for conflicts_with_a in self.matrix.iter(a) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// run-pass
2+
3+
// compile-flags: -Zmir-opt-level=2
4+
5+
trait IterExt: Iterator {
6+
fn fold_ex<B, F>(mut self, init: B, mut f: F) -> B
7+
where
8+
Self: Sized,
9+
F: FnMut(B, Self::Item) -> B,
10+
{
11+
let mut accum = init;
12+
while let Some(x) = self.next() {
13+
accum = f(accum, x);
14+
}
15+
accum
16+
}
17+
}
18+
19+
impl<T: Iterator> IterExt for T {}
20+
21+
fn main() {
22+
let test = &["\n"];
23+
test.iter().fold_ex(String::new(), |_, b| b.to_string());
24+
}

0 commit comments

Comments
 (0)