Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 630e17d

Browse files
committed
Limit number of tracked places, and some other perf improvements
1 parent da4a40f commit 630e17d

File tree

2 files changed

+66
-14
lines changed

2 files changed

+66
-14
lines changed

compiler/rustc_mir_dataflow/src/value_analysis.rs

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
4848
use std::fmt::{Debug, Formatter};
4949

50-
use rustc_data_structures::fx::FxHashMap;
50+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
5151
use rustc_index::vec::IndexVec;
5252
use rustc_middle::mir::tcx::PlaceTy;
5353
use rustc_middle::mir::visit::{PlaceContext, Visitor};
@@ -405,12 +405,31 @@ rustc_index::newtype_index!(
405405
);
406406

407407
/// See [`State`].
408-
#[derive(PartialEq, Eq, Clone, Debug)]
408+
#[derive(PartialEq, Eq, Debug)]
409409
enum StateData<V> {
410410
Reachable(IndexVec<ValueIndex, V>),
411411
Unreachable,
412412
}
413413

414+
impl<V: Clone> Clone for StateData<V> {
415+
fn clone(&self) -> Self {
416+
match self {
417+
Self::Reachable(x) => Self::Reachable(x.clone()),
418+
Self::Unreachable => Self::Unreachable,
419+
}
420+
}
421+
422+
fn clone_from(&mut self, source: &Self) {
423+
match (&mut *self, source) {
424+
(Self::Reachable(x), Self::Reachable(y)) => {
425+
// We go through `raw` here, because `IndexVec` currently has a naive `clone_from`.
426+
x.raw.clone_from(&y.raw);
427+
}
428+
_ => *self = source.clone(),
429+
}
430+
}
431+
}
432+
414433
/// The dataflow state for an instance of [`ValueAnalysis`].
415434
///
416435
/// Every instance specifies a lattice that represents the possible values of a single tracked
@@ -421,9 +440,19 @@ enum StateData<V> {
421440
/// reachable state). All operations on unreachable states are ignored.
422441
///
423442
/// Flooding means assigning a value (by default `⊤`) to all tracked projections of a given place.
424-
#[derive(PartialEq, Eq, Clone, Debug)]
443+
#[derive(PartialEq, Eq, Debug)]
425444
pub struct State<V>(StateData<V>);
426445

446+
impl<V: Clone> Clone for State<V> {
447+
fn clone(&self) -> Self {
448+
Self(self.0.clone())
449+
}
450+
451+
fn clone_from(&mut self, source: &Self) {
452+
self.0.clone_from(&source.0);
453+
}
454+
}
455+
427456
impl<V: Clone + HasTop + HasBottom> State<V> {
428457
pub fn is_reachable(&self) -> bool {
429458
matches!(&self.0, StateData::Reachable(_))
@@ -590,6 +619,7 @@ impl Map {
590619
///
591620
/// This is currently the only way to create a [`Map`]. The way in which the tracked places are
592621
/// chosen is an implementation detail and may not be relied upon.
622+
#[instrument(skip_all, level = "debug")]
593623
pub fn from_filter<'tcx>(
594624
tcx: TyCtxt<'tcx>,
595625
body: &Body<'tcx>,
@@ -604,11 +634,12 @@ impl Map {
604634
if tcx.sess.opts.unstable_opts.unsound_mir_opts {
605635
// We might want to add additional limitations. If a struct has 10 boxed fields of
606636
// itself, there will currently be `10.pow(max_derefs)` tracked places.
607-
map.register_with_filter(tcx, body, 2, filter, &[]);
637+
map.register_with_filter(tcx, body, 2, filter, &FxHashSet::default());
608638
} else {
609639
map.register_with_filter(tcx, body, 0, filter, &escaped_places(body));
610640
}
611641

642+
debug!("registered {} places ({} nodes in total)", map.value_count, map.places.len());
612643
map
613644
}
614645

@@ -619,7 +650,7 @@ impl Map {
619650
body: &Body<'tcx>,
620651
max_derefs: u32,
621652
mut filter: impl FnMut(Ty<'tcx>) -> bool,
622-
exclude: &[Place<'tcx>],
653+
exclude: &FxHashSet<Place<'tcx>>,
623654
) {
624655
// This is used to tell whether a type is `!Freeze`.
625656
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
@@ -648,10 +679,10 @@ impl Map {
648679
ty: Ty<'tcx>,
649680
filter: &mut impl FnMut(Ty<'tcx>) -> bool,
650681
param_env: ty::ParamEnv<'tcx>,
651-
exclude: &[Place<'tcx>],
682+
exclude: &FxHashSet<Place<'tcx>>,
652683
) {
653-
// This currently does a linear scan, could be improved.
654684
if exclude.contains(&Place { local, projection: tcx.intern_place_elems(projection) }) {
685+
// This will also exclude all projections of the excluded place.
655686
return;
656687
}
657688

@@ -764,6 +795,10 @@ impl Map {
764795
Ok(())
765796
}
766797

798+
pub fn tracked_places(&self) -> usize {
799+
self.value_count
800+
}
801+
767802
pub fn apply(&self, place: PlaceIndex, elem: TrackElem) -> Option<PlaceIndex> {
768803
self.projections.get(&(place, elem)).copied()
769804
}
@@ -929,20 +964,20 @@ fn iter_fields<'tcx>(
929964
/// Returns all places, that have their reference or address taken.
930965
///
931966
/// This includes shared references.
932-
fn escaped_places<'tcx>(body: &Body<'tcx>) -> Vec<Place<'tcx>> {
967+
fn escaped_places<'tcx>(body: &Body<'tcx>) -> FxHashSet<Place<'tcx>> {
933968
struct Collector<'tcx> {
934-
result: Vec<Place<'tcx>>,
969+
result: FxHashSet<Place<'tcx>>,
935970
}
936971

937972
impl<'tcx> Visitor<'tcx> for Collector<'tcx> {
938973
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
939974
if context.is_borrow() || context.is_address_of() {
940-
self.result.push(*place);
975+
self.result.insert(*place);
941976
}
942977
}
943978
}
944979

945-
let mut collector = Collector { result: Vec::new() };
980+
let mut collector = Collector { result: FxHashSet::default() };
946981
collector.visit_body(body);
947982
collector.result
948983
}

compiler/rustc_mir_transform/src/dataflow_const_prop.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,42 @@ use rustc_span::DUMMY_SP;
1515

1616
use crate::MirPass;
1717

18+
const TRACKING_LIMIT: usize = 1000;
19+
1820
pub struct DataflowConstProp;
1921

2022
impl<'tcx> MirPass<'tcx> for DataflowConstProp {
2123
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
2224
sess.mir_opt_level() >= 1
2325
}
2426

27+
#[instrument(skip_all level = "debug")]
2528
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
2629
// Decide which places to track during the analysis.
2730
let map = Map::from_filter(tcx, body, Ty::is_scalar);
2831

32+
// We want to have a somewhat linear runtime w.r.t. the number of statements/terminators.
33+
// Let's call this number `n`. Dataflow analysis has `O(h*n)` transfer function
34+
// applications, where `h` is the height of the lattice. Because the height of our lattice
35+
// is linear w.r.t. the number of tracked places, this is `O(tracked_places * n)`. However,
36+
// because every transfer function application could traverse the whole map, this becomes
37+
// `O(num_nodes * tracked_places * n)` in terms of time complexity. Since the number of
38+
// map nodes is strongly correlated to the number of tracked places, this becomes more or
39+
// less `O(n)` if we place a constant limit on the number of tracked places.
40+
if map.tracked_places() > TRACKING_LIMIT {
41+
debug!("aborted dataflow const prop due to too many tracked places");
42+
return;
43+
}
44+
2945
// Perform the actual dataflow analysis.
3046
let analysis = ConstAnalysis::new(tcx, body, map);
31-
let results = analysis.wrap().into_engine(tcx, body).iterate_to_fixpoint();
47+
let results = debug_span!("analyze")
48+
.in_scope(|| analysis.wrap().into_engine(tcx, body).iterate_to_fixpoint());
3249

3350
// Collect results and patch the body afterwards.
3451
let mut visitor = CollectAndPatch::new(tcx, &results.analysis.0.map);
35-
results.visit_reachable_with(body, &mut visitor);
36-
visitor.visit_body(body);
52+
debug_span!("collect").in_scope(|| results.visit_reachable_with(body, &mut visitor));
53+
debug_span!("patch").in_scope(|| visitor.visit_body(body));
3754
}
3855
}
3956

0 commit comments

Comments
 (0)