Skip to content

Commit b4970e4

Browse files
committed
Precompute full conflicts matrix.
1 parent 011314d commit b4970e4

File tree

2 files changed

+92
-156
lines changed

2 files changed

+92
-156
lines changed

compiler/rustc_index/src/bit_set.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,6 +1538,28 @@ impl<R: Idx, C: Idx> BitMatrix<R, C> {
15381538
changed != 0
15391539
}
15401540

1541+
/// Adds the bits from col `read` to the bits from col `write`, and
1542+
/// returns `true` if anything changed.
1543+
pub fn union_cols(&mut self, read: C, write: C) -> bool {
1544+
assert!(read.index() < self.num_columns && write.index() < self.num_columns);
1545+
1546+
let words_per_row = num_words(self.num_columns);
1547+
let (read_index, read_mask) = word_index_and_mask(read);
1548+
let (write_index, write_mask) = word_index_and_mask(write);
1549+
1550+
let words = &mut self.words[..];
1551+
let mut changed = 0;
1552+
for row in 0..self.num_rows {
1553+
let write_word = words[row * words_per_row + write_index];
1554+
let read_word = (words[row * words_per_row + read_index] & read_mask) != 0;
1555+
let new_word = if read_word { write_word | write_mask } else { write_word };
1556+
words[row * words_per_row + write_index] = new_word;
1557+
// See `bitwise` for the rationale.
1558+
changed |= write_word ^ new_word;
1559+
}
1560+
changed != 0
1561+
}
1562+
15411563
/// Adds the bits from `with` to the bits from row `write`, and
15421564
/// returns `true` if anything changed.
15431565
pub fn union_row_with(&mut self, with: &DenseBitSet<C>, write: R) -> bool {

compiler/rustc_mir_transform/src/dest_prop.rs

Lines changed: 70 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@
131131
//! [attempt 2]: https://github.com/rust-lang/rust/pull/71003
132132
//! [attempt 3]: https://github.com/rust-lang/rust/pull/72632
133133
134-
use rustc_data_structures::fx::{FxIndexMap, IndexEntry, IndexOccupiedEntry};
135-
use rustc_index::bit_set::DenseBitSet;
134+
use rustc_data_structures::fx::FxIndexMap;
135+
use rustc_index::bit_set::{BitMatrix, DenseBitSet};
136136
use rustc_index::interval::SparseIntervalMatrix;
137137
use rustc_middle::bug;
138138
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
@@ -153,11 +153,12 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
153153
sess.mir_opt_level() >= 2
154154
}
155155

156+
#[tracing::instrument(level = "trace", skip(self, tcx, body))]
156157
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
157158
let def_id = body.source.def_id();
158159
let mut candidates = Candidates::default();
159160
let mut write_info = WriteInfo::default();
160-
trace!(func = ?tcx.def_path_str(def_id));
161+
trace!(?def_id);
161162

162163
let borrowed = rustc_mir_dataflow::impls::borrowed_locals(body);
163164

@@ -185,40 +186,33 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
185186
trace!(?candidates);
186187
dest_prop_mir_dump(tcx, body, &points, &live, round_count);
187188

188-
FilterInformation::filter_liveness(
189-
&mut candidates,
190-
&points,
191-
&live,
192-
&mut write_info,
193-
body,
194-
);
195-
196-
// Because we only filter once per round, it is unsound to use a local for more than
197-
// one merge operation within a single round of optimizations. We store here which ones
198-
// we have already used.
199-
let mut merged_locals: DenseBitSet<Local> =
200-
DenseBitSet::new_empty(body.local_decls.len());
189+
let mut filter =
190+
FilterInformation::filter_liveness(&points, &live, &mut write_info, body);
201191

202192
// This is the set of merges we will apply this round. It is a subset of the candidates.
203193
let mut merges = FxIndexMap::default();
194+
// Record which locals have been merged to handle storage statements.
195+
let mut merged_locals: DenseBitSet<Local> =
196+
DenseBitSet::new_empty(body.local_decls.len());
204197

205-
for (src, candidates) in candidates.c.iter() {
206-
if merged_locals.contains(*src) {
207-
continue;
208-
}
209-
let Some(dest) = candidates.iter().find(|dest| !merged_locals.contains(**dest))
210-
else {
211-
continue;
212-
};
198+
for (&src, candidates) in candidates.c.iter() {
199+
let Some(dest) = filter.find_non_conflicting(src, candidates) else { continue };
200+
// `dest` may already be replaced with another destination. This is not an issue.
201+
// This just appears as locals changing names between rounds. This will be resolved
202+
// in a next round.
213203

214204
// Replace `src` by `dest` everywhere.
215-
merges.insert(*src, *dest);
216-
merged_locals.insert(*src);
217-
merged_locals.insert(*dest);
205+
merges.insert(src, dest);
206+
merged_locals.insert(src);
207+
merged_locals.insert(dest);
218208

219209
// Update liveness information based on the merge we just performed.
220210
// Every location where `src` was live, `dest` will be live.
221-
live.union_rows(*src, *dest);
211+
live.union_rows(src, dest);
212+
213+
// Update conflict information based on the merge we just performed.
214+
// Everything that conflicted with `src` now conflicts with `dest`.
215+
filter.record_merge(src, dest);
222216
}
223217
trace!(merging = ?merges);
224218

@@ -255,11 +249,6 @@ struct Candidates {
255249
/// We will still report that we would like to merge `_1` and `_2` in an attempt to allow us to
256250
/// remove that assignment.
257251
c: FxIndexMap<Local, Vec<Local>>,
258-
259-
/// A reverse index of the `c` set; if the `c` set contains `a => Place { local: b, proj }`,
260-
/// then this contains `b => a`.
261-
// PERF: Possibly these should be `SmallVec`s?
262-
reverse: FxIndexMap<Local, Vec<Local>>,
263252
}
264253

265254
//////////////////////////////////////////////////////////
@@ -335,8 +324,7 @@ impl<'tcx> MutVisitor<'tcx> for Merger<'tcx> {
335324
struct FilterInformation<'a, 'tcx> {
336325
body: &'a Body<'tcx>,
337326
points: &'a DenseLocationMap,
338-
live: &'a SparseIntervalMatrix<Local, PointIndex>,
339-
candidates: &'a mut Candidates,
327+
conflicts: BitMatrix<Local, Local>,
340328
write_info: &'a mut WriteInfo,
341329
at: Location,
342330
}
@@ -350,94 +338,14 @@ impl Candidates {
350338
/// This is responsible for enforcing the first and third bullet point.
351339
fn reset_and_find<'tcx>(&mut self, body: &Body<'tcx>, borrowed: &DenseBitSet<Local>) {
352340
self.c.clear();
353-
self.reverse.clear();
354341
let mut visitor = FindAssignments { body, candidates: &mut self.c, borrowed };
355342
visitor.visit_body(body);
356343
// Deduplicate candidates.
357344
for (_, cands) in self.c.iter_mut() {
358345
cands.sort();
359346
cands.dedup();
360347
}
361-
// Generate the reverse map.
362-
for (src, cands) in self.c.iter() {
363-
for dest in cands.iter().copied() {
364-
self.reverse.entry(dest).or_default().push(*src);
365-
}
366-
}
367348
}
368-
369-
/// Just `Vec::retain`, but the condition is inverted and we add debugging output
370-
fn vec_filter_candidates(
371-
src: Local,
372-
v: &mut Vec<Local>,
373-
mut f: impl FnMut(Local) -> CandidateFilter,
374-
at: Location,
375-
) {
376-
v.retain(|dest| {
377-
let remove = f(*dest);
378-
if remove == CandidateFilter::Remove {
379-
trace!("eliminating {:?} => {:?} due to conflict at {:?}", src, dest, at);
380-
}
381-
remove == CandidateFilter::Keep
382-
});
383-
}
384-
385-
/// `vec_filter_candidates` but for an `Entry`
386-
fn entry_filter_candidates(
387-
mut entry: IndexOccupiedEntry<'_, Local, Vec<Local>>,
388-
p: Local,
389-
f: impl FnMut(Local) -> CandidateFilter,
390-
at: Location,
391-
) {
392-
let candidates = entry.get_mut();
393-
Self::vec_filter_candidates(p, candidates, f, at);
394-
if candidates.len() == 0 {
395-
// FIXME(#120456) - is `swap_remove` correct?
396-
entry.swap_remove();
397-
}
398-
}
399-
400-
/// For all candidates `(p, q)` or `(q, p)` removes the candidate if `f(q)` says to do so
401-
fn filter_candidates_by(
402-
&mut self,
403-
p: Local,
404-
mut f: impl FnMut(Local) -> CandidateFilter,
405-
at: Location,
406-
) {
407-
// Cover the cases where `p` appears as a `src`
408-
if let IndexEntry::Occupied(entry) = self.c.entry(p) {
409-
Self::entry_filter_candidates(entry, p, &mut f, at);
410-
}
411-
// And the cases where `p` appears as a `dest`
412-
let Some(srcs) = self.reverse.get_mut(&p) else {
413-
return;
414-
};
415-
// We use `retain` here to remove the elements from the reverse set if we've removed the
416-
// matching candidate in the forward set.
417-
srcs.retain(|src| {
418-
if f(*src) == CandidateFilter::Keep {
419-
return true;
420-
}
421-
let IndexEntry::Occupied(entry) = self.c.entry(*src) else {
422-
return false;
423-
};
424-
Self::entry_filter_candidates(
425-
entry,
426-
*src,
427-
|dest| {
428-
if dest == p { CandidateFilter::Remove } else { CandidateFilter::Keep }
429-
},
430-
at,
431-
);
432-
false
433-
});
434-
}
435-
}
436-
437-
#[derive(Copy, Clone, PartialEq, Eq)]
438-
enum CandidateFilter {
439-
Keep,
440-
Remove,
441349
}
442350

443351
impl<'a, 'tcx> FilterInformation<'a, 'tcx> {
@@ -459,74 +367,80 @@ impl<'a, 'tcx> FilterInformation<'a, 'tcx> {
459367
/// statement/terminator to be live. We are additionally conservative by treating all written to
460368
/// locals as also being read from.
461369
fn filter_liveness(
462-
candidates: &mut Candidates,
463-
points: &DenseLocationMap,
370+
points: &'a DenseLocationMap,
464371
live: &SparseIntervalMatrix<Local, PointIndex>,
465-
write_info: &mut WriteInfo,
466-
body: &Body<'tcx>,
467-
) {
372+
write_info: &'a mut WriteInfo,
373+
body: &'a Body<'tcx>,
374+
) -> Self {
375+
let num_locals = body.local_decls.len();
468376
let mut this = FilterInformation {
469377
body,
470378
points,
471-
live,
472-
candidates,
379+
conflicts: BitMatrix::new(num_locals, num_locals),
473380
// We don't actually store anything at this scope, we just keep things here to be able
474381
// to reuse the allocation.
475382
write_info,
476383
// Doesn't matter what we put here, will be overwritten before being used
477384
at: Location::START,
478385
};
479-
this.internal_filter_liveness();
386+
this.internal_filter_liveness(live);
387+
this
480388
}
481389

482-
fn internal_filter_liveness(&mut self) {
390+
fn internal_filter_liveness(&mut self, live: &SparseIntervalMatrix<Local, PointIndex>) {
483391
for (block, data) in traversal::preorder(self.body) {
484392
self.at = Location { block, statement_index: data.statements.len() };
485393
self.write_info.for_terminator(&data.terminator().kind);
486-
self.apply_conflicts();
394+
self.record_conflicts(live);
487395

488396
for (i, statement) in data.statements.iter().enumerate().rev() {
489397
self.at = Location { block, statement_index: i };
490398
self.write_info.for_statement(&statement.kind, self.body);
491-
self.apply_conflicts();
399+
self.record_conflicts(live);
492400
}
493401
}
402+
403+
trace!(?self.conflicts, "initial conflicts");
494404
}
495405

496-
fn apply_conflicts(&mut self) {
406+
fn record_conflicts(&mut self, live: &SparseIntervalMatrix<Local, PointIndex>) {
497407
let writes = &self.write_info.writes;
498-
for p in writes {
499-
let other_skip = self.write_info.skip_pair.and_then(|(a, b)| {
500-
if a == *p {
501-
Some(b)
502-
} else if b == *p {
503-
Some(a)
504-
} else {
505-
None
408+
let skip_pair = self.write_info.skip_pair;
409+
let at = self.points.point_from_location(self.at);
410+
411+
for &p in writes {
412+
for &q in writes {
413+
if p != q && skip_pair != Some((p, q)) && skip_pair != Some((q, p)) {
414+
self.conflicts.insert(p, q);
506415
}
507-
});
508-
let at = self.points.point_from_location(self.at);
509-
self.candidates.filter_candidates_by(
510-
*p,
511-
|q| {
512-
if Some(q) == other_skip {
513-
return CandidateFilter::Keep;
514-
}
515-
// It is possible that a local may be live for less than the
516-
// duration of a statement This happens in the case of function
517-
// calls or inline asm. Because of this, we also mark locals as
518-
// conflicting when both of them are written to in the same
519-
// statement.
520-
if self.live.contains(q, at) || writes.contains(&q) {
521-
CandidateFilter::Remove
522-
} else {
523-
CandidateFilter::Keep
524-
}
525-
},
526-
self.at,
527-
);
416+
}
417+
418+
for q in live.rows() {
419+
if p != q
420+
&& skip_pair != Some((p, q))
421+
&& skip_pair != Some((q, p))
422+
&& live.contains(q, at)
423+
{
424+
self.conflicts.insert(p, q);
425+
}
426+
}
528427
}
529428
}
429+
430+
#[tracing::instrument(level = "trace", skip(self))]
431+
fn record_merge(&mut self, src: Local, dest: Local) {
432+
trace!(?self.conflicts, "pre");
433+
self.conflicts.union_rows(src, dest);
434+
self.conflicts.union_cols(src, dest);
435+
trace!(?self.conflicts, "post");
436+
}
437+
438+
#[tracing::instrument(level = "trace", skip(self), ret)]
439+
fn find_non_conflicting(&self, src: Local, candidates: &Vec<Local>) -> Option<Local> {
440+
candidates.iter().copied().find(|&dest| {
441+
!self.conflicts.contains(src, dest) && !self.conflicts.contains(dest, src)
442+
})
443+
}
530444
}
531445

532446
/// Describes where a statement/terminator writes to

0 commit comments

Comments
 (0)