From c3364a23d2a0ec8557f52fcf5a705503619274c3 Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Mon, 15 Jan 2024 14:41:12 -0500 Subject: [PATCH] perf: Don't track specific live points for promoteds We don't query this information out of the promoted (it's basically a single "unit" regardless of the complexity within it) and this saves on re-initializing the SparseIntervalMatrix's backing IndexVec with mostly empty rows for all of the leading regions in the function. Typical promoteds will only contain a few regions that need up be uplifted, while the parent function can have thousands. For a simple function repeating println!("Hello world"); 50,000 times this reduces compile times from 90 to 15 seconds in debug mode. The previous implementations re-initialization led to an overall roughly n^2 runtime as each promoted initialized slots for ~n regions, now we scale closer to linearly (5000 hello worlds takes 1.1 seconds). --- .../rustc_borrowck/src/region_infer/values.rs | 89 +++++++++++++++---- compiler/rustc_borrowck/src/type_check/mod.rs | 28 +++--- 2 files changed, 87 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 01f7bfcadb6f5..e147f62011db9 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -1,5 +1,6 @@ #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxIndexSet; use rustc_index::bit_set::SparseBitMatrix; use rustc_index::interval::IntervalSet; @@ -41,8 +42,15 @@ pub(crate) struct LivenessValues { /// The map from locations to points. elements: Rc, + /// Which regions are live. This is exclusive with the fine-grained tracking in `points`, and + /// currently only used for validating promoteds (which don't care about more precise tracking). + live_regions: Option>, + /// For each region: the points where it is live. - points: SparseIntervalMatrix, + /// + /// This is not initialized for promoteds, because we don't care *where* within a promoted a + /// region is live, only that it is. + points: Option>, /// When using `-Zpolonius=next`, for each point: the loans flowing into the live regions at /// that point. @@ -71,24 +79,52 @@ impl LiveLoans { impl LivenessValues { /// Create an empty map of regions to locations where they're live. - pub(crate) fn new(elements: Rc) -> Self { + pub(crate) fn with_specific_points(elements: Rc) -> Self { LivenessValues { - points: SparseIntervalMatrix::new(elements.num_points()), + live_regions: None, + points: Some(SparseIntervalMatrix::new(elements.num_points())), + elements, + loans: None, + } + } + + /// Create an empty map of regions to locations where they're live. + /// + /// Unlike `with_specific_points`, does not track exact locations where something is live, only + /// which regions are live. + pub(crate) fn without_specific_points(elements: Rc) -> Self { + LivenessValues { + live_regions: Some(Default::default()), + points: None, elements, loans: None, } } /// Iterate through each region that has a value in this set. - pub(crate) fn regions(&self) -> impl Iterator { - self.points.rows() + pub(crate) fn regions(&self) -> impl Iterator + '_ { + self.points.as_ref().expect("use with_specific_points").rows() + } + + /// Iterate through each region that has a value in this set. + // We are passing query instability implications to the caller. + #[rustc_lint_query_instability] + #[allow(rustc::potential_query_instability)] + pub(crate) fn live_regions_unordered(&self) -> impl Iterator + '_ { + self.live_regions.as_ref().unwrap().iter().copied() } /// Records `region` as being live at the given `location`. pub(crate) fn add_location(&mut self, region: RegionVid, location: Location) { - debug!("LivenessValues::add_location(region={:?}, location={:?})", region, location); let point = self.elements.point_from_location(location); - self.points.insert(region, point); + debug!("LivenessValues::add_location(region={:?}, location={:?})", region, location); + if let Some(points) = &mut self.points { + points.insert(region, point); + } else { + if self.elements.point_in_range(point) { + self.live_regions.as_mut().unwrap().insert(region); + } + } // When available, record the loans flowing into this region as live at the given point. if let Some(loans) = self.loans.as_mut() { @@ -101,7 +137,13 @@ impl LivenessValues { /// Records `region` as being live at all the given `points`. pub(crate) fn add_points(&mut self, region: RegionVid, points: &IntervalSet) { debug!("LivenessValues::add_points(region={:?}, points={:?})", region, points); - self.points.union_row(region, points); + if let Some(this) = &mut self.points { + this.union_row(region, points); + } else { + if points.iter().any(|point| self.elements.point_in_range(point)) { + self.live_regions.as_mut().unwrap().insert(region); + } + } // When available, record the loans flowing into this region as live at the given points. if let Some(loans) = self.loans.as_mut() { @@ -117,23 +159,33 @@ impl LivenessValues { /// Records `region` as being live at all the control-flow points. pub(crate) fn add_all_points(&mut self, region: RegionVid) { - self.points.insert_all_into_row(region); + if let Some(points) = &mut self.points { + points.insert_all_into_row(region); + } else { + self.live_regions.as_mut().unwrap().insert(region); + } } /// Returns whether `region` is marked live at the given `location`. pub(crate) fn is_live_at(&self, region: RegionVid, location: Location) -> bool { let point = self.elements.point_from_location(location); - self.points.row(region).is_some_and(|r| r.contains(point)) - } - - /// Returns whether `region` is marked live at any location. - pub(crate) fn is_live_anywhere(&self, region: RegionVid) -> bool { - self.live_points(region).next().is_some() + if let Some(points) = &self.points { + points.row(region).is_some_and(|r| r.contains(point)) + } else { + unreachable!( + "Should be using LivenessValues::with_specific_points to ask whether live at a location" + ) + } } /// Returns an iterator of all the points where `region` is live. fn live_points(&self, region: RegionVid) -> impl Iterator + '_ { - self.points + let Some(points) = &self.points else { + unreachable!( + "Should be using LivenessValues::with_specific_points to ask whether live at a location" + ) + }; + points .row(region) .into_iter() .flat_map(|set| set.iter()) @@ -288,7 +340,10 @@ impl RegionValues { /// elements for the region `from` from `values` and add them to /// the region `to` in `self`. pub(crate) fn merge_liveness(&mut self, to: N, from: RegionVid, values: &LivenessValues) { - if let Some(set) = values.points.row(from) { + let Some(value_points) = &values.points else { + panic!("LivenessValues must track specific points for use in merge_liveness"); + }; + if let Some(set) = value_points.row(from) { self.points.union_row(to, set); } } diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 48444a6b6f70e..cf28e62177fb4 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -141,7 +141,7 @@ pub(crate) fn type_check<'mir, 'tcx>( let mut constraints = MirTypeckRegionConstraints { placeholder_indices: PlaceholderIndices::default(), placeholder_index_to_region: IndexVec::default(), - liveness_constraints: LivenessValues::new(elements.clone()), + liveness_constraints: LivenessValues::with_specific_points(elements.clone()), outlives_constraints: OutlivesConstraintSet::default(), member_constraints: MemberConstraintSet::default(), type_tests: Vec::default(), @@ -555,7 +555,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { let all_facts = &mut None; let mut constraints = Default::default(); let mut liveness_constraints = - LivenessValues::new(Rc::new(DenseLocationMap::new(promoted_body))); + LivenessValues::without_specific_points(Rc::new(DenseLocationMap::new(promoted_body))); // Don't try to add borrow_region facts for the promoted MIR let mut swap_constraints = |this: &mut Self| { @@ -594,17 +594,19 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { } self.cx.borrowck_context.constraints.outlives_constraints.push(constraint) } - for region in liveness_constraints.regions() { - // If the region is live at at least one location in the promoted MIR, - // then add a liveness constraint to the main MIR for this region - // at the location provided as an argument to this method - if liveness_constraints.is_live_anywhere(region) { - self.cx - .borrowck_context - .constraints - .liveness_constraints - .add_location(region, location); - } + // If the region is live at at least one location in the promoted MIR, + // then add a liveness constraint to the main MIR for this region + // at the location provided as an argument to this method + // + // add_location doesn't care about ordering so not a problem for the live regions to be + // unordered. + #[allow(rustc::potential_query_instability)] + for region in liveness_constraints.live_regions_unordered() { + self.cx + .borrowck_context + .constraints + .liveness_constraints + .add_location(region, location); } }