Skip to content

Commit 47cb509

Browse files
JakobDegencjgillot
authored andcommitted
Dest prop: Support removing writes when this unblocks optimizations
1 parent dca02d6 commit 47cb509

File tree

4 files changed

+178
-52
lines changed

4 files changed

+178
-52
lines changed

compiler/rustc_mir_transform/src/dest_prop.rs

Lines changed: 92 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,12 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
217217
// This is the set of merges we will apply this round. It is a subset of the candidates.
218218
let mut merges = FxIndexMap::default();
219219

220-
for (src, candidates) in candidates.c.iter() {
221-
if merged_locals.contains(*src) {
220+
for (src, candidates) in candidates.c.drain(..) {
221+
if merged_locals.contains(src) {
222222
continue;
223223
}
224-
let Some(dest) = candidates.iter().find(|dest| !merged_locals.contains(**dest))
224+
let Some(dest) =
225+
candidates.into_iter().find(|(dest, _)| !merged_locals.contains(*dest))
225226
else {
226227
continue;
227228
};
@@ -231,14 +232,15 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
231232
break;
232233
}
233234

234-
// Replace `src` by `dest` everywhere.
235-
merges.insert(*src, *dest);
236-
merged_locals.insert(*src);
237-
merged_locals.insert(*dest);
238-
239235
// Update liveness information based on the merge we just performed.
240236
// Every location where `src` was live, `dest` will be live.
241-
live.union_rows(*src, *dest);
237+
live.union_rows(src, dest.0);
238+
239+
// Replace `src` by `dest` everywhere.
240+
merged_locals.insert(src);
241+
merged_locals.insert(dest.0);
242+
merges.insert(src, dest.clone());
243+
merges.insert(dest.0, dest);
242244
}
243245
trace!(merging = ?merges);
244246

@@ -260,7 +262,7 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
260262
/// frequently. Everything with a `&'alloc` lifetime points into here.
261263
#[derive(Default)]
262264
struct Allocations {
263-
candidates: FxIndexMap<Local, Vec<Local>>,
265+
candidates: FxIndexMap<Local, Vec<(Local, Vec<Location>)>>,
264266
candidates_reverse: FxIndexMap<Local, Vec<Local>>,
265267
write_info: WriteInfo,
266268
// PERF: Do this for `MaybeLiveLocals` allocations too.
@@ -282,7 +284,7 @@ struct Candidates<'alloc> {
282284
///
283285
/// We will still report that we would like to merge `_1` and `_2` in an attempt to allow us to
284286
/// remove that assignment.
285-
c: &'alloc mut FxIndexMap<Local, Vec<Local>>,
287+
c: &'alloc mut FxIndexMap<Local, Vec<(Local, Vec<Location>)>>,
286288
/// A reverse index of the `c` set; if the `c` set contains `a => Place { local: b, proj }`,
287289
/// then this contains `b => a`.
288290
// PERF: Possibly these should be `SmallVec`s?
@@ -297,7 +299,7 @@ struct Candidates<'alloc> {
297299
fn apply_merges<'tcx>(
298300
body: &mut Body<'tcx>,
299301
tcx: TyCtxt<'tcx>,
300-
merges: &FxIndexMap<Local, Local>,
302+
merges: &FxIndexMap<Local, (Local, Vec<Location>)>,
301303
merged_locals: &BitSet<Local>,
302304
) {
303305
let mut merger = Merger { tcx, merges, merged_locals };
@@ -306,18 +308,27 @@ fn apply_merges<'tcx>(
306308

307309
struct Merger<'a, 'tcx> {
308310
tcx: TyCtxt<'tcx>,
309-
merges: &'a FxIndexMap<Local, Local>,
311+
merges: &'a FxIndexMap<Local, (Local, Vec<Location>)>,
310312
merged_locals: &'a BitSet<Local>,
311313
}
312314

315+
impl<'a, 'tcx> Merger<'a, 'tcx> {
316+
fn should_remove_write_at(&self, local: Local, location: Location) -> bool {
317+
let Some((_, to_remove)) = self.merges.get(&local) else {
318+
return false;
319+
};
320+
to_remove.contains(&location)
321+
}
322+
}
323+
313324
impl<'a, 'tcx> MutVisitor<'tcx> for Merger<'a, 'tcx> {
314325
fn tcx(&self) -> TyCtxt<'tcx> {
315326
self.tcx
316327
}
317328

318329
fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _location: Location) {
319330
if let Some(dest) = self.merges.get(local) {
320-
*local = *dest;
331+
*local = dest.0;
321332
}
322333
}
323334

@@ -348,10 +359,27 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Merger<'a, 'tcx> {
348359
_ => {}
349360
}
350361
}
362+
StatementKind::Deinit(place) => {
363+
if self.should_remove_write_at(place.local, location) {
364+
statement.make_nop();
365+
}
366+
}
351367

352368
_ => {}
353369
}
354370
}
371+
372+
fn visit_operand(&mut self, op: &mut Operand<'tcx>, location: Location) {
373+
self.super_operand(op, location);
374+
match op {
375+
Operand::Move(place) => {
376+
if self.should_remove_write_at(place.local, location) {
377+
*op = Operand::Copy(*place);
378+
}
379+
}
380+
_ => (),
381+
}
382+
}
355383
}
356384

357385
//////////////////////////////////////////////////////////
@@ -373,30 +401,35 @@ struct FilterInformation<'a, 'body, 'alloc, 'tcx> {
373401
// through these methods, and not directly.
374402
impl<'alloc> Candidates<'alloc> {
375403
/// Just `Vec::retain`, but the condition is inverted and we add debugging output
376-
fn vec_filter_candidates(
404+
fn vec_modify_candidates(
377405
src: Local,
378-
v: &mut Vec<Local>,
379-
mut f: impl FnMut(Local) -> CandidateFilter,
406+
v: &mut Vec<(Local, Vec<Location>)>,
407+
mut f: impl FnMut(Local) -> CandidateModification,
380408
at: Location,
381409
) {
382-
v.retain(|dest| {
383-
let remove = f(*dest);
384-
if remove == CandidateFilter::Remove {
410+
v.retain_mut(|(dest, remove_writes)| match f(*dest) {
411+
CandidateModification::Remove => {
385412
trace!("eliminating {:?} => {:?} due to conflict at {:?}", src, dest, at);
413+
false
414+
}
415+
CandidateModification::RemoveWrite => {
416+
trace!("marking write for {:?} => {:?} as needing removing at {:?}", src, dest, at);
417+
remove_writes.push(at);
418+
true
386419
}
387-
remove == CandidateFilter::Keep
420+
CandidateModification::Keep => true,
388421
});
389422
}
390423

391424
/// `vec_filter_candidates` but for an `Entry`
392425
fn entry_filter_candidates(
393-
mut entry: IndexOccupiedEntry<'_, Local, Vec<Local>>,
426+
mut entry: IndexOccupiedEntry<'_, Local, Vec<(Local, Vec<Location>)>>,
394427
p: Local,
395-
f: impl FnMut(Local) -> CandidateFilter,
428+
f: impl FnMut(Local) -> CandidateModification,
396429
at: Location,
397430
) {
398431
let candidates = entry.get_mut();
399-
Self::vec_filter_candidates(p, candidates, f, at);
432+
Self::vec_modify_candidates(p, candidates, f, at);
400433
if candidates.len() == 0 {
401434
entry.remove();
402435
}
@@ -406,7 +439,7 @@ impl<'alloc> Candidates<'alloc> {
406439
fn filter_candidates_by(
407440
&mut self,
408441
p: Local,
409-
mut f: impl FnMut(Local) -> CandidateFilter,
442+
mut f: impl FnMut(Local) -> CandidateModification,
410443
at: Location,
411444
) {
412445
// Cover the cases where `p` appears as a `src`
@@ -420,7 +453,8 @@ impl<'alloc> Candidates<'alloc> {
420453
// We use `retain` here to remove the elements from the reverse set if we've removed the
421454
// matching candidate in the forward set.
422455
srcs.retain(|src| {
423-
if f(*src) == CandidateFilter::Keep {
456+
let modification = f(*src);
457+
if modification == CandidateModification::Keep {
424458
return true;
425459
}
426460
let IndexEntry::Occupied(entry) = self.c.entry(*src) else {
@@ -430,18 +464,20 @@ impl<'alloc> Candidates<'alloc> {
430464
entry,
431465
*src,
432466
|dest| {
433-
if dest == p { CandidateFilter::Remove } else { CandidateFilter::Keep }
467+
if dest == p { modification } else { CandidateModification::Keep }
434468
},
435469
at,
436470
);
437-
false
471+
// Remove the src from the reverse set if we removed the candidate pair
472+
modification == CandidateModification::RemoveWrite
438473
});
439474
}
440475
}
441476

442477
#[derive(Copy, Clone, PartialEq, Eq)]
443-
enum CandidateFilter {
478+
enum CandidateModification {
444479
Keep,
480+
RemoveWrite,
445481
Remove,
446482
}
447483

@@ -500,32 +536,37 @@ impl<'a, 'body, 'alloc, 'tcx> FilterInformation<'a, 'body, 'alloc, 'tcx> {
500536

501537
fn apply_conflicts(&mut self) {
502538
let writes = &self.write_info.writes;
503-
for p in writes {
539+
for &(p, is_removable) in writes {
540+
let modification = if is_removable {
541+
CandidateModification::RemoveWrite
542+
} else {
543+
CandidateModification::Remove
544+
};
504545
let other_skip = self.write_info.skip_pair.and_then(|(a, b)| {
505-
if a == *p {
546+
if a == p {
506547
Some(b)
507-
} else if b == *p {
548+
} else if b == p {
508549
Some(a)
509550
} else {
510551
None
511552
}
512553
});
513554
let at = self.points.point_from_location(self.at);
514555
self.candidates.filter_candidates_by(
515-
*p,
556+
p,
516557
|q| {
517558
if Some(q) == other_skip {
518-
return CandidateFilter::Keep;
559+
return CandidateModification::Keep;
519560
}
520561
// It is possible that a local may be live for less than the
521562
// duration of a statement This happens in the case of function
522563
// calls or inline asm. Because of this, we also mark locals as
523564
// conflicting when both of them are written to in the same
524565
// statement.
525-
if self.live.contains(q, at) || writes.contains(&q) {
526-
CandidateFilter::Remove
566+
if self.live.contains(q, at) || writes.iter().any(|&(x, _)| x == q) {
567+
modification
527568
} else {
528-
CandidateFilter::Keep
569+
CandidateModification::Keep
529570
}
530571
},
531572
self.at,
@@ -537,7 +578,9 @@ impl<'a, 'body, 'alloc, 'tcx> FilterInformation<'a, 'body, 'alloc, 'tcx> {
537578
/// Describes where a statement/terminator writes to
538579
#[derive(Default, Debug)]
539580
struct WriteInfo {
540-
writes: Vec<Local>,
581+
/// Which locals are written to. The `bool` is true if the write is "removable," ie if it comes
582+
/// from a `Operand::Move` or `Deinit`.
583+
writes: Vec<(Local, bool)>,
541584
/// If this pair of locals is a candidate pair, completely skip processing it during this
542585
/// statement. All other candidates are unaffected.
543586
skip_pair: Option<(Local, Local)>,
@@ -581,10 +624,11 @@ impl WriteInfo {
581624
| Rvalue::CopyForDeref(_) => (),
582625
}
583626
}
627+
StatementKind::Deinit(p) => {
628+
self.writes.push((p.local, true));
629+
}
584630
// Retags are technically also reads, but reporting them as a write suffices
585-
StatementKind::SetDiscriminant { place, .. }
586-
| StatementKind::Deinit(place)
587-
| StatementKind::Retag(_, place) => {
631+
StatementKind::SetDiscriminant { place, .. } | StatementKind::Retag(_, place) => {
588632
self.add_place(**place);
589633
}
590634
StatementKind::Intrinsic(_)
@@ -669,16 +713,12 @@ impl WriteInfo {
669713
}
670714

671715
fn add_place(&mut self, place: Place<'_>) {
672-
self.writes.push(place.local);
716+
self.writes.push((place.local, false));
673717
}
674718

675719
fn add_operand<'tcx>(&mut self, op: &Operand<'tcx>) {
676720
match op {
677-
// FIXME(JakobDegen): In a previous version, the `Move` case was incorrectly treated as
678-
// being a read only. This was unsound, however we cannot add a regression test because
679-
// it is not possible to set this off with current MIR. Once we have that ability, a
680-
// regression test should be added.
681-
Operand::Move(p) => self.add_place(*p),
721+
Operand::Move(p) => self.writes.push((p.local, true)),
682722
Operand::Copy(_) | Operand::Constant(_) => (),
683723
}
684724
}
@@ -733,7 +773,7 @@ fn places_to_candidate_pair<'tcx>(
733773
fn find_candidates<'alloc, 'tcx>(
734774
body: &Body<'tcx>,
735775
borrowed: &BitSet<Local>,
736-
candidates: &'alloc mut FxIndexMap<Local, Vec<Local>>,
776+
candidates: &'alloc mut FxIndexMap<Local, Vec<(Local, Vec<Location>)>>,
737777
candidates_reverse: &'alloc mut FxIndexMap<Local, Vec<Local>>,
738778
) -> Candidates<'alloc> {
739779
candidates.clear();
@@ -747,16 +787,16 @@ fn find_candidates<'alloc, 'tcx>(
747787
}
748788
// Generate the reverse map
749789
for (src, cands) in candidates.iter() {
750-
for dest in cands.iter().copied() {
751-
candidates_reverse.entry(dest).or_default().push(*src);
790+
for (dest, _) in cands.iter() {
791+
candidates_reverse.entry(*dest).or_default().push(*src);
752792
}
753793
}
754794
Candidates { c: candidates, reverse: candidates_reverse }
755795
}
756796

757797
struct FindAssignments<'a, 'alloc, 'tcx> {
758798
body: &'a Body<'tcx>,
759-
candidates: &'alloc mut FxIndexMap<Local, Vec<Local>>,
799+
candidates: &'alloc mut FxIndexMap<Local, Vec<(Local, Vec<Location>)>>,
760800
borrowed: &'a BitSet<Local>,
761801
}
762802

@@ -793,7 +833,7 @@ impl<'tcx> Visitor<'tcx> for FindAssignments<'_, '_, 'tcx> {
793833
}
794834

795835
// We may insert duplicates here, but that's fine
796-
self.candidates.entry(src).or_default().push(dest);
836+
self.candidates.entry(src).or_default().push((dest, Vec::new()));
797837
}
798838
}
799839
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
- // MIR for `move_simple` before DestinationPropagation
2+
+ // MIR for `move_simple` after DestinationPropagation
3+
4+
fn move_simple(_1: i32) -> () {
5+
debug x => _1;
6+
let mut _0: ();
7+
let _2: ();
8+
let mut _3: i32;
9+
let mut _4: i32;
10+
11+
bb0: {
12+
StorageLive(_2);
13+
- StorageLive(_3);
14+
- _3 = _1;
15+
- StorageLive(_4);
16+
- _4 = _1;
17+
- _2 = use_both(move _3, move _4) -> [return: bb1, unwind unreachable];
18+
+ nop;
19+
+ nop;
20+
+ nop;
21+
+ nop;
22+
+ _2 = use_both(_1, _1) -> [return: bb1, unwind unreachable];
23+
}
24+
25+
bb1: {
26+
- StorageDead(_4);
27+
- StorageDead(_3);
28+
+ nop;
29+
+ nop;
30+
StorageDead(_2);
31+
_0 = const ();
32+
return;
33+
}
34+
}
35+

0 commit comments

Comments
 (0)