Skip to content

Commit a4f011b

Browse files
committed
Auto merge of #156218 - cjgillot:dest-prop-range, r=<try>
DestinationPropagation: compute liveness as ranges instead of traveling bitsets
2 parents e95e732 + ea42552 commit a4f011b

3 files changed

Lines changed: 120 additions & 35 deletions

File tree

compiler/rustc_index/src/interval.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,38 @@ impl<I: Idx> IntervalSet<I> {
164164
);
165165
}
166166

167+
/// Specialized version of `insert_range` when we know that the inserted point is *after* any
168+
/// contained.
169+
pub fn append_range(&mut self, range: impl RangeBounds<I> + Clone) {
170+
let start = inclusive_start(range.clone());
171+
let Some(end) = inclusive_end(self.domain, range) else {
172+
// empty range
173+
return;
174+
};
175+
if start > end {
176+
return;
177+
}
178+
179+
let start = start.index() as u32;
180+
let end = end.index() as u32;
181+
if let Some((_, last_end)) = self.map.last_mut() {
182+
assert!(*last_end <= start);
183+
// The start is already adjacent to the set.
184+
if start <= *last_end + 1 {
185+
*last_end = end;
186+
} else {
187+
self.map.push((start, end));
188+
}
189+
} else {
190+
self.map.push((start, end));
191+
}
192+
193+
debug_assert!(
194+
self.check_invariants(),
195+
"wrong intervals after append {start:?}..={end:?} to {self:?}"
196+
);
197+
}
198+
167199
pub fn contains(&self, needle: I) -> bool {
168200
let needle = needle.index() as u32;
169201
let Some(last) = self.map.partition_point(|r| r.0 <= needle).checked_sub(1) else {
@@ -379,6 +411,10 @@ impl<R: Idx, C: Step + Idx> SparseIntervalMatrix<R, C> {
379411
self.ensure_row(row).append(point)
380412
}
381413

414+
pub fn append_range(&mut self, row: R, point: impl RangeBounds<C> + Clone) {
415+
self.ensure_row(row).append_range(point)
416+
}
417+
382418
pub fn contains(&self, row: R, point: C) -> bool {
383419
self.row(row).is_some_and(|r| r.contains(point))
384420
}

compiler/rustc_mir_dataflow/src/impls/liveness.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ use crate::{Analysis, Backward, GenKill};
2525
/// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis
2626
pub struct MaybeLiveLocals;
2727

28+
impl MaybeLiveLocals {
29+
pub fn transfer_function<I>(state: &mut I) -> TransferFunction<'_, I> {
30+
TransferFunction(state)
31+
}
32+
}
33+
2834
impl<'tcx> Analysis<'tcx> for MaybeLiveLocals {
2935
type Domain = DenseBitSet<Local>;
3036
type Direction = Backward;
@@ -81,9 +87,12 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals {
8187
}
8288
}
8389

84-
pub struct TransferFunction<'a>(pub &'a mut DenseBitSet<Local>);
90+
pub struct TransferFunction<'a, I>(pub &'a mut I);
8591

86-
impl<'tcx> Visitor<'tcx> for TransferFunction<'_> {
92+
impl<'tcx, I> Visitor<'tcx> for TransferFunction<'_, I>
93+
where
94+
I: GenKill<Local>,
95+
{
8796
fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
8897
if let PlaceContext::MutatingUse(MutatingUseContext::Yield) = context {
8998
// The resume place is evaluated and assigned to only after coroutine resumes, so its
@@ -143,7 +152,7 @@ pub enum DefUse {
143152
}
144153

145154
impl DefUse {
146-
fn apply(state: &mut DenseBitSet<Local>, place: Place<'_>, context: PlaceContext) {
155+
fn apply(state: &mut impl GenKill<Local>, place: Place<'_>, context: PlaceContext) {
147156
match DefUse::for_place(place, context) {
148157
DefUse::Def => state.kill(place.local),
149158
DefUse::Use => state.gen_(place.local),

compiler/rustc_mir_transform/src/dest_prop.rs

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ use rustc_middle::mir::*;
146146
use rustc_middle::ty::TyCtxt;
147147
use rustc_mir_dataflow::impls::{DefUse, MaybeLiveLocals};
148148
use rustc_mir_dataflow::points::DenseLocationMap;
149-
use rustc_mir_dataflow::{Analysis, EntryStates};
149+
use rustc_mir_dataflow::{Analysis, EntryStates, GenKill};
150150
use tracing::{debug, trace};
151151

152152
pub(super) struct DestinationPropagation;
@@ -502,25 +502,64 @@ impl TwoStepIndex {
502502
}
503503

504504
/// Add points depending on the result of the given dataflow analysis.
505+
#[tracing::instrument(level = "trace", skip(elements, body))]
505506
fn save_as_intervals<'tcx>(
506507
elements: &DenseLocationMap,
507508
body: &Body<'tcx>,
508509
relevant: &RelevantLocals,
509510
entry_states: EntryStates<DenseBitSet<Local>>,
510511
) -> SparseIntervalMatrix<RelevantLocal, TwoStepIndex> {
511-
let mut values = SparseIntervalMatrix::new(2 * elements.num_points());
512-
let mut state = MaybeLiveLocals.bottom_value(body);
513-
let reachable_blocks = traversal::reachable_as_bitset(body);
512+
struct GenKillIntervalMatrix<'a> {
513+
values: SparseIntervalMatrix<RelevantLocal, TwoStepIndex>,
514+
relevant: &'a RelevantLocals,
515+
pending: IndexVec<RelevantLocal, Option<TwoStepIndex>>,
516+
current: TwoStepIndex,
517+
}
514518

515-
let two_step_loc = |location, effect| TwoStepIndex::new(elements, location, effect);
516-
let append_at =
517-
|values: &mut SparseIntervalMatrix<_, _>, state: &DenseBitSet<Local>, twostep| {
518-
for (relevant, &original) in relevant.original.iter_enumerated() {
519-
if state.contains(original) {
520-
values.append(relevant, twostep);
519+
impl GenKill<Local> for GenKillIntervalMatrix<'_> {
520+
fn gen_(&mut self, elem: Local) {
521+
let Some(elem) = self.relevant.shrink[elem] else { return };
522+
let _ = self.pending[elem].get_or_insert(self.current);
523+
}
524+
525+
fn kill(&mut self, elem: Local) {
526+
let Some(elem) = self.relevant.shrink[elem] else { return };
527+
if let Some(start) = self.pending[elem].take() {
528+
debug_assert!(start <= self.current);
529+
self.values.append_range(elem, start..self.current);
530+
}
531+
}
532+
}
533+
534+
impl GenKillIntervalMatrix<'_> {
535+
fn insert_single(&mut self, elem: RelevantLocal) {
536+
// If we have a set pending, we will insert it when killing it, so nothing to do.
537+
if self.pending[elem].is_none() {
538+
self.values.append_range(elem, self.current..=self.current);
539+
}
540+
}
541+
542+
fn finish_block(&mut self) {
543+
for (elem, start) in self.pending.iter_enumerated_mut() {
544+
if let Some(start) = start.take() {
545+
debug_assert!(start <= self.current);
546+
self.values.append_range(elem, start..=self.current);
521547
}
522548
}
523-
};
549+
}
550+
}
551+
552+
let mut state = GenKillIntervalMatrix {
553+
values: SparseIntervalMatrix::new(2 * elements.num_points()),
554+
relevant,
555+
pending: IndexVec::from_elem(None, &relevant.original),
556+
// Dummy value.
557+
current: TwoStepIndex::from_u32(0),
558+
};
559+
560+
let reachable_blocks = traversal::reachable_as_bitset(body);
561+
562+
let two_step_loc = |location, effect| TwoStepIndex::new(elements, location, effect);
524563

525564
// Iterate blocks in decreasing order, to visit locations in decreasing order. This
526565
// allows to use the more efficient `append` method to interval sets.
@@ -529,14 +568,17 @@ fn save_as_intervals<'tcx>(
529568
continue;
530569
}
531570

532-
state.clone_from(&entry_states[block]);
533-
534571
let block_data = &body.basic_blocks[block];
535572
let loc = Location { block, statement_index: block_data.statements.len() };
573+
debug_assert!(state.pending.iter().all(Option::is_none));
574+
state.current = two_step_loc(loc, Effect::After);
575+
576+
for local in entry_states[block].iter() {
577+
state.gen_(local)
578+
}
536579

537580
let term = block_data.terminator();
538-
let mut twostep = two_step_loc(loc, Effect::After);
539-
append_at(&mut values, &state, twostep);
581+
540582
// Ensure we have a non-zero live range even for dead stores. This is done by marking all
541583
// the written-to locals as live in the second half of the statement.
542584
// We also ensure that operands read by terminators conflict with writes by that terminator.
@@ -545,25 +587,23 @@ fn save_as_intervals<'tcx>(
545587
if let Some(relevant) = relevant.shrink[place.local] {
546588
match DefUse::for_place(place, ctxt) {
547589
DefUse::Def | DefUse::Use | DefUse::PartialWrite => {
548-
values.insert(relevant, twostep);
590+
state.insert_single(relevant);
549591
}
550592
DefUse::NonUse => {}
551593
}
552594
}
553595
})
554596
.visit_terminator(term, loc);
555597

556-
twostep = TwoStepIndex::from_u32(twostep.as_u32() + 1);
557-
debug_assert_eq!(twostep, two_step_loc(loc, Effect::Before));
558-
MaybeLiveLocals.apply_early_terminator_effect(&mut state, term, loc);
559-
MaybeLiveLocals.apply_primary_terminator_effect(&mut state, term, loc);
560-
append_at(&mut values, &state, twostep);
598+
state.current = state.current + 1;
599+
debug_assert_eq!(state.current, two_step_loc(loc, Effect::Before));
600+
MaybeLiveLocals::transfer_function(&mut state).visit_terminator(term, loc);
561601

562602
for (statement_index, stmt) in block_data.statements.iter().enumerate().rev() {
563603
let loc = Location { block, statement_index };
564-
twostep = TwoStepIndex::from_u32(twostep.as_u32() + 1);
565-
debug_assert_eq!(twostep, two_step_loc(loc, Effect::After));
566-
append_at(&mut values, &state, twostep);
604+
state.current = state.current + 1;
605+
debug_assert_eq!(state.current, two_step_loc(loc, Effect::After));
606+
567607
// Like terminators, ensure we have a non-zero live range even for dead stores.
568608
// Some rvalues interleave reads and writes, for instance `Rvalue::Aggregate`, see
569609
// https://github.com/rust-lang/rust/issues/146383. By precaution, treat statements
@@ -582,26 +622,26 @@ fn save_as_intervals<'tcx>(
582622
if let Some(relevant) = relevant.shrink[place.local] {
583623
match DefUse::for_place(place, ctxt) {
584624
DefUse::Def | DefUse::PartialWrite => {
585-
values.insert(relevant, twostep);
625+
state.insert_single(relevant);
586626
}
587627
DefUse::Use if !is_simple_assignment => {
588-
values.insert(relevant, twostep);
628+
state.insert_single(relevant);
589629
}
590630
DefUse::Use | DefUse::NonUse => {}
591631
}
592632
}
593633
})
594634
.visit_statement(stmt, loc);
595635

596-
twostep = TwoStepIndex::from_u32(twostep.as_u32() + 1);
597-
debug_assert_eq!(twostep, two_step_loc(loc, Effect::Before));
598-
MaybeLiveLocals.apply_early_statement_effect(&mut state, stmt, loc);
599-
MaybeLiveLocals.apply_primary_statement_effect(&mut state, stmt, loc);
600636
// ... but reads from operands are marked as live here so they do not conflict with
601637
// the all the writes we manually marked as live in the second half of the statement.
602-
append_at(&mut values, &state, twostep);
638+
state.current = TwoStepIndex::from_u32(state.current.as_u32() + 1);
639+
debug_assert_eq!(state.current, two_step_loc(loc, Effect::Before));
640+
MaybeLiveLocals::transfer_function(&mut state).visit_statement(stmt, loc);
603641
}
642+
643+
state.finish_block();
604644
}
605645

606-
values
646+
state.values
607647
}

0 commit comments

Comments
 (0)