From 4f7a27b225a28c3600c788563ed66bf64285a79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 30 Jun 2023 11:55:38 +0000 Subject: [PATCH 1/7] introduce `Polonius` enum for `-Zpolonius` this allows to opt into using the legacy version or the in-tree prototype --- compiler/rustc_borrowck/src/facts.rs | 3 +- compiler/rustc_borrowck/src/nll.rs | 5 ++-- .../rustc_borrowck/src/region_infer/mod.rs | 2 +- compiler/rustc_interface/src/tests.rs | 3 +- compiler/rustc_session/src/config.rs | 29 +++++++++++++++++++ compiler/rustc_session/src/options.rs | 18 +++++++++++- 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_borrowck/src/facts.rs b/compiler/rustc_borrowck/src/facts.rs index 9916ebca32fa9..c54e7070478ca 100644 --- a/compiler/rustc_borrowck/src/facts.rs +++ b/compiler/rustc_borrowck/src/facts.rs @@ -41,7 +41,8 @@ pub(crate) trait AllFactsExt { impl AllFactsExt for AllFacts { /// Return fn enabled(tcx: TyCtxt<'_>) -> bool { - tcx.sess.opts.unstable_opts.nll_facts || tcx.sess.opts.unstable_opts.polonius + tcx.sess.opts.unstable_opts.nll_facts + || tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled() } fn write_to_dir( diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 3f60f5aca71d4..129d0ec8cd786 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -169,10 +169,11 @@ pub(crate) fn compute_regions<'cx, 'tcx>( upvars: &[Upvar<'tcx>], consumer_options: Option, ) -> NllOutput<'tcx> { + let is_polonius_legacy_enabled = infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled(); let polonius_input = consumer_options.map(|c| c.polonius_input()).unwrap_or_default() - || infcx.tcx.sess.opts.unstable_opts.polonius; + || is_polonius_legacy_enabled; let polonius_output = consumer_options.map(|c| c.polonius_output()).unwrap_or_default() - || infcx.tcx.sess.opts.unstable_opts.polonius; + || is_polonius_legacy_enabled; let mut all_facts = (polonius_input || AllFacts::enabled(infcx.tcx)).then_some(AllFacts::default()); diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 852935676b6f3..a65306b034c24 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -683,7 +683,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // In Polonius mode, the errors about missing universal region relations are in the output // and need to be emitted or propagated. Otherwise, we need to check whether the // constraints were too strong, and if so, emit or propagate those errors. - if infcx.tcx.sess.opts.unstable_opts.polonius { + if infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled() { self.check_polonius_subset_errors( outlives_requirements.as_mut(), &mut errors_buffer, diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 2510ce7146022..7799af370089a 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -9,6 +9,7 @@ use rustc_session::config::DebugInfo; use rustc_session::config::Input; use rustc_session::config::InstrumentXRay; use rustc_session::config::LinkSelfContained; +use rustc_session::config::Polonius; use rustc_session::config::TraitSolver; use rustc_session::config::{build_configuration, build_session_options, to_crate_config}; use rustc_session::config::{ @@ -814,7 +815,7 @@ fn test_unstable_options_tracking_hash() { tracked!(panic_abort_tests, true); tracked!(panic_in_drop, PanicStrategy::Abort); tracked!(plt, Some(true)); - tracked!(polonius, true); + tracked!(polonius, Polonius::Legacy); tracked!(precise_enum_drop_elaboration, false); tracked!(print_fuel, Some("abc".to_string())); tracked!(profile, true); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d29ab02c1a6ac..e8ae969861e1e 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3166,6 +3166,7 @@ impl PpMode { /// we have an opt-in scheme here, so one is hopefully forced to think about /// how the hash should be calculated when adding a new command-line argument. pub(crate) mod dep_tracking { + use super::Polonius; use super::{ BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, DebugInfoCompression, ErrorOutputType, InstrumentCoverage, InstrumentXRay, LdImpl, LinkerPluginLto, @@ -3276,6 +3277,7 @@ pub(crate) mod dep_tracking { OomStrategy, LanguageIdentifier, TraitSolver, + Polonius, ); impl DepTrackingHash for (T1, T2) @@ -3414,3 +3416,30 @@ impl DumpMonoStatsFormat { } } } + +/// `-Zpolonius` values, enabling the borrow checker polonius analysis, and which version: legacy, +/// or future prototype. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum Polonius { + /// The default value: disabled. + Off, + + /// Legacy version, using datalog and the `polonius-engine` crate. Historical value for `-Zpolonius`. + Legacy, + + /// In-tree experimentation + Next, +} + +impl Default for Polonius { + fn default() -> Self { + Polonius::Off + } +} + +impl Polonius { + /// Returns whether the legacy version of polonius is enabled + pub fn is_legacy_enabled(&self) -> bool { + matches!(self, Polonius::Legacy) + } +} diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index c1424db600eff..e676307f95e25 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -415,6 +415,7 @@ mod desc { pub const parse_gcc_ld: &str = "one of: no value, `lld`"; pub const parse_link_self_contained: &str = "one of: `y`, `yes`, `on`, `n`, `no`, `off`, or a list of enabled (`+` prefix) and disabled (`-` prefix) \ components: `crto`, `libc`, `unwind`, `linker`, `sanitizers`, `mingw`"; + pub const parse_polonius: &str = "either no value or `legacy` (the default), or `next`"; pub const parse_stack_protector: &str = "one of (`none` (default), `basic`, `strong`, or `all`)"; pub const parse_branch_protection: &str = @@ -472,6 +473,21 @@ mod parse { } } + /// Parses whether polonius is enabled, and if so, which version. + pub(crate) fn parse_polonius(slot: &mut Polonius, v: Option<&str>) -> bool { + match v { + Some("legacy") | None => { + *slot = Polonius::Legacy; + true + } + Some("next") => { + *slot = Polonius::Next; + true + } + _ => false, + } + } + /// Use this for any string option that has a static default. pub(crate) fn parse_string(slot: &mut String, v: Option<&str>) -> bool { match v { @@ -1669,7 +1685,7 @@ options! { "whether to use the PLT when calling into shared libraries; only has effect for PIC code on systems with ELF binaries (default: PLT is disabled if full relro is enabled on x86_64)"), - polonius: bool = (false, parse_bool, [TRACKED], + polonius: Polonius = (Polonius::default(), parse_polonius, [TRACKED], "enable polonius-based borrow-checker (default: no)"), polymorphize: bool = (false, parse_bool, [TRACKED], "perform polymorphization analysis"), From 43cdf39d89a1efdc5f386c9a11d3160f12891207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 23 Jun 2023 23:42:48 +0000 Subject: [PATCH 2/7] rename ancient regioncx --- compiler/rustc_borrowck/src/dataflow.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 4ac633c263fab..4d2f983603aa7 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -242,11 +242,11 @@ impl<'a, 'tcx> Borrows<'a, 'tcx> { pub fn new( tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, - nonlexical_regioncx: &'a RegionInferenceContext<'tcx>, + regioncx: &'a RegionInferenceContext<'tcx>, borrow_set: &'a BorrowSet<'tcx>, ) -> Self { let borrows_out_of_scope_at_location = - calculate_borrows_out_of_scope_at_location(body, nonlexical_regioncx, borrow_set); + calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set); Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location } } From a946fabd48791a769abfa56e2c38f403ac2277fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 30 Jun 2023 12:21:50 +0000 Subject: [PATCH 3/7] document Borrows dataflow analysis --- compiler/rustc_borrowck/src/dataflow.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 4d2f983603aa7..031dbe299d1a5 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -333,6 +333,13 @@ impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> { } } +/// Forward dataflow computation of the set of borrows that are in scope at a particular location. +/// - we gen the introduced loans +/// - we kill loans on locals going out of (regular) scope +/// - we kill the loans going out of their region's NLL scope: in NLL terms, the frontier where a +/// region stops containing the CFG points reachable from the issuing location. +/// - we also kill loans of conflicting places when overwriting a shared path: e.g. borrows of +/// `a.b.c` when `a` is overwritten. impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { type Idx = BorrowIndex; From b01261528655a27c57ff9a229702cba1b660743a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 30 Jun 2023 13:18:11 +0000 Subject: [PATCH 4/7] extend Polonius options helpers --- compiler/rustc_session/src/config.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index e8ae969861e1e..e03b66f293e25 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3427,7 +3427,7 @@ pub enum Polonius { /// Legacy version, using datalog and the `polonius-engine` crate. Historical value for `-Zpolonius`. Legacy, - /// In-tree experimentation + /// In-tree prototype, extending the NLL infrastructure. Next, } @@ -3442,4 +3442,9 @@ impl Polonius { pub fn is_legacy_enabled(&self) -> bool { matches!(self, Polonius::Legacy) } + + /// Returns whether the "next" version of polonius is enabled + pub fn is_next_enabled(&self) -> bool { + matches!(self, Polonius::Next) + } } From af8a318aa1c38cc37a5889a2f50ac09589b0c6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 30 Jun 2023 19:11:09 +0000 Subject: [PATCH 5/7] add note why a function is pub --- compiler/rustc_borrowck/src/dataflow.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 031dbe299d1a5..a615b383fee5b 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -222,6 +222,7 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { } } +// This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. pub fn calculate_borrows_out_of_scope_at_location<'tcx>( body: &Body<'tcx>, regioncx: &RegionInferenceContext<'tcx>, From a7f1f24823dd45064fa1bb729e899d0aad51dbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 23 Jun 2023 23:40:31 +0000 Subject: [PATCH 6/7] allow access to liveness' `PointIndex`es --- compiler/rustc_borrowck/src/region_infer/values.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index d205862cd3fe0..38452df32e9ed 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -176,6 +176,11 @@ impl LivenessValues { pub(crate) fn region_value_str(&self, r: N) -> String { region_value_str(self.get_elements(r).map(RegionElement::Location)) } + + #[inline] + pub(crate) fn point_from_location(&self, location: Location) -> PointIndex { + self.elements.point_from_location(location) + } } /// Maps from `ty::PlaceholderRegion` values that are used in the rest of From 3dcff0051a87dea2a9a319c45c0df57acd29cdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 30 Jun 2023 13:20:38 +0000 Subject: [PATCH 7/7] compute NLL loan scopes with liveness in `-Zpolonius=next` --- compiler/rustc_borrowck/src/dataflow.rs | 184 +++++++++++++++++- compiler/rustc_borrowck/src/nll.rs | 37 ++-- .../rustc_borrowck/src/region_infer/mod.rs | 46 ++++- .../src/type_check/liveness/trace.rs | 82 +++++++- compiler/rustc_borrowck/src/type_check/mod.rs | 18 +- 5 files changed, 339 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index a615b383fee5b..6ea84620bbed8 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -1,6 +1,7 @@ #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::graph::WithSuccessors; use rustc_index::bit_set::BitSet; use rustc_middle::mir::{ self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges, @@ -239,6 +240,162 @@ pub fn calculate_borrows_out_of_scope_at_location<'tcx>( prec.borrows_out_of_scope_at_location } +struct PoloniusOutOfScopePrecomputer<'a, 'tcx> { + visited: BitSet, + visit_stack: Vec, + body: &'a Body<'tcx>, + regioncx: &'a RegionInferenceContext<'tcx>, + + loans_out_of_scope_at_location: FxIndexMap>, +} + +impl<'a, 'tcx> PoloniusOutOfScopePrecomputer<'a, 'tcx> { + fn new(body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>) -> Self { + Self { + visited: BitSet::new_empty(body.basic_blocks.len()), + visit_stack: vec![], + body, + regioncx, + loans_out_of_scope_at_location: FxIndexMap::default(), + } + } +} + +impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { + /// Loans are in scope while they are live: whether they are contained within any live region. + /// In the location-insensitive analysis, a loan will be contained in a region if the issuing + /// region can reach it in the subset graph. So this is a reachability problem. + fn precompute_loans_out_of_scope( + &mut self, + loan_idx: BorrowIndex, + issuing_region: RegionVid, + loan_issued_at: Location, + ) { + let sccs = self.regioncx.constraint_sccs(); + let issuing_region_scc = sccs.scc(issuing_region); + + // We first handle the cases where the loan doesn't go out of scope, depending on the issuing + // region's successors. + for scc in sccs.depth_first_search(issuing_region_scc) { + // 1. Via member constraints + // + // The issuing region can flow into the choice regions, and they are either: + // - placeholders or free regions themselves, + // - or also transitively outlive a free region. + // + // That is to say, if there are member constraints here, the loan escapes the function + // and cannot go out of scope. We can early return. + if self.regioncx.scc_has_member_constraints(scc) { + return; + } + + // 2. Via regions that are live at all points: placeholders and free regions. + // + // If the issuing region outlives such a region, its loan escapes the function and + // cannot go out of scope. We can early return. + if self.regioncx.scc_is_live_at_all_points(scc) { + return; + } + } + + let first_block = loan_issued_at.block; + let first_bb_data = &self.body.basic_blocks[first_block]; + + // The first block we visit is the one where the loan is issued, starting from the statement + // where the loan is issued: at `loan_issued_at`. + let first_lo = loan_issued_at.statement_index; + let first_hi = first_bb_data.statements.len(); + + if let Some(kill_location) = + self.loan_kill_location(loan_idx, loan_issued_at, first_block, first_lo, first_hi) + { + debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location); + self.loans_out_of_scope_at_location.entry(kill_location).or_default().push(loan_idx); + + // The loan dies within the first block, we're done and can early return. + return; + } + + // The loan is not dead. Add successor BBs to the work list, if necessary. + for succ_bb in first_bb_data.terminator().successors() { + if self.visited.insert(succ_bb) { + self.visit_stack.push(succ_bb); + } + } + + // We may end up visiting `first_block` again. This is not an issue: we know at this point + // that the loan is not killed in the `first_lo..=first_hi` range, so checking the + // `0..first_lo` range and the `0..first_hi` range gives the same result. + while let Some(block) = self.visit_stack.pop() { + let bb_data = &self.body[block]; + let num_stmts = bb_data.statements.len(); + if let Some(kill_location) = + self.loan_kill_location(loan_idx, loan_issued_at, block, 0, num_stmts) + { + debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location); + self.loans_out_of_scope_at_location + .entry(kill_location) + .or_default() + .push(loan_idx); + + // The loan dies within this block, so we don't need to visit its successors. + continue; + } + + // Add successor BBs to the work list, if necessary. + for succ_bb in bb_data.terminator().successors() { + if self.visited.insert(succ_bb) { + self.visit_stack.push(succ_bb); + } + } + } + + self.visited.clear(); + assert!(self.visit_stack.is_empty(), "visit stack should be empty"); + } + + /// Returns the lowest statement in `start..=end`, where the loan goes out of scope, if any. + /// This is the statement where the issuing region can't reach any of the regions that are live + /// at this point. + fn loan_kill_location( + &self, + loan_idx: BorrowIndex, + loan_issued_at: Location, + block: BasicBlock, + start: usize, + end: usize, + ) -> Option { + for statement_index in start..=end { + let location = Location { block, statement_index }; + + // Check whether the issuing region can reach local regions that are live at this point: + // - a loan is always live at its issuing location because it can reach the issuing + // region, which is always live at this location. + if location == loan_issued_at { + continue; + } + + // - the loan goes out of scope at `location` if it's not contained within any regions + // live at this point. + // + // FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and `r` is + // live at point `q`, then it's guaranteed that `i` would reach `r` at point `q`. + // Reachability is location-insensitive, and we could take advantage of that, by jumping + // to a further point than just the next statement: we can jump to the furthest point + // within the block where `r` is live. + if self.regioncx.is_loan_live_at(loan_idx, location) { + continue; + } + + // No live region is reachable from the issuing region: the loan is killed at this + // point. + return Some(location); + } + + None + } +} + impl<'a, 'tcx> Borrows<'a, 'tcx> { pub fn new( tcx: TyCtxt<'tcx>, @@ -246,8 +403,33 @@ impl<'a, 'tcx> Borrows<'a, 'tcx> { regioncx: &'a RegionInferenceContext<'tcx>, borrow_set: &'a BorrowSet<'tcx>, ) -> Self { - let borrows_out_of_scope_at_location = + let mut borrows_out_of_scope_at_location = calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set); + + // The in-tree polonius analysis computes loans going out of scope using the set-of-loans + // model, and makes sure they're identical to the existing computation of the set-of-points + // model. + if tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { + let mut polonius_prec = PoloniusOutOfScopePrecomputer::new(body, regioncx); + for (loan_idx, loan_data) in borrow_set.iter_enumerated() { + let issuing_region = loan_data.region; + let issued_location = loan_data.reserve_location; + + polonius_prec.precompute_loans_out_of_scope( + loan_idx, + issuing_region, + issued_location, + ); + } + + assert_eq!( + borrows_out_of_scope_at_location, polonius_prec.loans_out_of_scope_at_location, + "the loans out of scope must be the same as the borrows out of scope" + ); + + borrows_out_of_scope_at_location = polonius_prec.loans_out_of_scope_at_location; + } + Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location } } diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 129d0ec8cd786..0ea4401a87847 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -182,22 +182,26 @@ pub(crate) fn compute_regions<'cx, 'tcx>( let elements = &Rc::new(RegionValueElements::new(&body)); // Run the MIR type-checker. - let MirTypeckResults { constraints, universal_region_relations, opaque_type_values } = - type_check::type_check( - infcx, - param_env, - body, - promoted, - &universal_regions, - location_table, - borrow_set, - &mut all_facts, - flow_inits, - move_data, - elements, - upvars, - polonius_input, - ); + let MirTypeckResults { + constraints, + universal_region_relations, + opaque_type_values, + live_loans, + } = type_check::type_check( + infcx, + param_env, + body, + promoted, + &universal_regions, + location_table, + borrow_set, + &mut all_facts, + flow_inits, + move_data, + elements, + upvars, + polonius_input, + ); if let Some(all_facts) = &mut all_facts { let _prof_timer = infcx.tcx.prof.generic_activity("polonius_fact_generation"); @@ -275,6 +279,7 @@ pub(crate) fn compute_regions<'cx, 'tcx>( type_tests, liveness_constraints, elements, + live_loans, ); // Generate various additional constraints. diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index a65306b034c24..96cbe98c21694 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -7,6 +7,7 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::graph::scc::Sccs; use rustc_errors::Diagnostic; use rustc_hir::def_id::CRATE_DEF_ID; +use rustc_index::bit_set::SparseBitMatrix; use rustc_index::{IndexSlice, IndexVec}; use rustc_infer::infer::outlives::test_type_match; use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound, VerifyIfEq}; @@ -21,6 +22,7 @@ use rustc_middle::traits::ObligationCauseCode; use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_span::Span; +use crate::dataflow::BorrowIndex; use crate::{ constraints::{ graph::NormalConstraintGraph, ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet, @@ -30,8 +32,8 @@ use crate::{ nll::PoloniusOutput, region_infer::reverse_sccs::ReverseSccGraph, region_infer::values::{ - LivenessValues, PlaceholderIndices, RegionElement, RegionValueElements, RegionValues, - ToElementIndex, + LivenessValues, PlaceholderIndices, PointIndex, RegionElement, RegionValueElements, + RegionValues, ToElementIndex, }, type_check::{free_region_relations::UniversalRegionRelations, Locations}, universal_regions::UniversalRegions, @@ -119,6 +121,9 @@ pub struct RegionInferenceContext<'tcx> { /// Information about how the universally quantified regions in /// scope on this function relate to one another. universal_region_relations: Frozen>, + + /// The set of loans that are live at a given point in the CFG, when using `-Zpolonius=next`. + live_loans: SparseBitMatrix, } /// Each time that `apply_member_constraint` is successful, it appends @@ -330,6 +335,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { type_tests: Vec>, liveness_constraints: LivenessValues, elements: &Rc, + live_loans: SparseBitMatrix, ) -> Self { debug!("universal_regions: {:#?}", universal_regions); debug!("outlives constraints: {:#?}", outlives_constraints); @@ -383,6 +389,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { type_tests, universal_regions, universal_region_relations, + live_loans, }; result.init_free_and_bound_regions(); @@ -2279,6 +2286,41 @@ impl<'tcx> RegionInferenceContext<'tcx> { } None } + + /// Access to the SCC constraint graph. + pub(crate) fn constraint_sccs(&self) -> &Sccs { + self.constraint_sccs.as_ref() + } + + /// Returns whether the given SCC has any member constraints. + pub(crate) fn scc_has_member_constraints(&self, scc: ConstraintSccIndex) -> bool { + self.member_constraints.indices(scc).next().is_some() + } + + /// Returns whether the given SCC is live at all points: whether the representative is a + /// placeholder or a free region. + pub(crate) fn scc_is_live_at_all_points(&self, scc: ConstraintSccIndex) -> bool { + // FIXME: there must be a cleaner way to find this information. At least, when + // higher-ranked subtyping is abstracted away from the borrowck main path, we'll only + // need to check whether this is a universal region. + let representative = self.scc_representatives[scc]; + let origin = self.var_infos[representative].origin; + let live_at_all_points = matches!( + origin, + RegionVariableOrigin::Nll( + NllRegionVariableOrigin::Placeholder(_) | NllRegionVariableOrigin::FreeRegion + ) + ); + live_at_all_points + } + + /// Returns whether the `loan_idx` is live at the given `location`: whether its issuing + /// region is contained within the type of a variable that is live at this point. + /// Note: for now, the sets of live loans is only available when using `-Zpolonius=next`. + pub(crate) fn is_loan_live_at(&self, loan_idx: BorrowIndex, location: Location) -> bool { + let point = self.liveness_constraints.point_from_location(location); + self.live_loans.contains(point, loan_idx) + } } impl<'tcx> RegionDefinition<'tcx> { diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 5702d39db32d6..21da05c32dd86 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -1,10 +1,11 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; -use rustc_index::bit_set::HybridBitSet; +use rustc_data_structures::graph::WithSuccessors; +use rustc_index::bit_set::{HybridBitSet, SparseBitMatrix}; use rustc_index::interval::IntervalSet; use rustc_infer::infer::canonical::QueryRegionConstraints; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; -use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; +use rustc_middle::ty::{RegionVid, Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; use rustc_span::DUMMY_SP; use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; @@ -14,6 +15,7 @@ use rustc_mir_dataflow::impls::MaybeInitializedPlaces; use rustc_mir_dataflow::move_paths::{HasMoveData, MoveData, MovePathIndex}; use rustc_mir_dataflow::ResultsCursor; +use crate::dataflow::BorrowIndex; use crate::{ region_infer::values::{self, PointIndex, RegionValueElements}, type_check::liveness::local_use_map::LocalUseMap, @@ -50,6 +52,33 @@ pub(super) fn trace<'mir, 'tcx>( let local_use_map = &LocalUseMap::build(&relevant_live_locals, elements, body); + // When using `-Zpolonius=next`, compute the set of loans that can reach a given region. + let num_loans = typeck.borrowck_context.borrow_set.len(); + let mut inflowing_loans = SparseBitMatrix::new(num_loans); + if typeck.tcx().sess.opts.unstable_opts.polonius.is_next_enabled() { + let borrowck_context = &typeck.borrowck_context; + let borrow_set = &borrowck_context.borrow_set; + let constraint_set = &borrowck_context.constraints.outlives_constraints; + + let num_region_vars = typeck.infcx.num_region_vars(); + let graph = constraint_set.graph(num_region_vars); + let region_graph = + graph.region_graph(&constraint_set, borrowck_context.universal_regions.fr_static); + + // Traverse each issuing region's constraints, and record the loan as flowing into the + // outlived region. + for (loan, issuing_region_data) in borrow_set.iter_enumerated() { + for succ in region_graph.depth_first_search(issuing_region_data.region) { + // We don't need to mention that a loan flows into its issuing region. + if succ == issuing_region_data.region { + continue; + } + + inflowing_loans.insert(succ, loan); + } + } + }; + let cx = LivenessContext { typeck, body, @@ -58,6 +87,7 @@ pub(super) fn trace<'mir, 'tcx>( local_use_map, move_data, drop_data: FxIndexMap::default(), + inflowing_loans, }; let mut results = LivenessResults::new(cx); @@ -95,6 +125,9 @@ struct LivenessContext<'me, 'typeck, 'flow, 'tcx> { /// Index indicating where each variable is assigned, used, or /// dropped. local_use_map: &'me LocalUseMap, + + /// Set of loans that flow into a given region, when using `-Zpolonius=next`. + inflowing_loans: SparseBitMatrix, } struct DropData<'tcx> { @@ -486,7 +519,13 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { ) { debug!("add_use_live_facts_for(value={:?})", value); - Self::make_all_regions_live(self.elements, &mut self.typeck, value, live_at) + Self::make_all_regions_live( + self.elements, + &mut self.typeck, + value, + live_at, + &self.inflowing_loans, + ); } /// Some variable with type `live_ty` is "drop live" at `location` @@ -537,7 +576,13 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { // All things in the `outlives` array may be touched by // the destructor and must be live at this point. for &kind in &drop_data.dropck_result.kinds { - Self::make_all_regions_live(self.elements, &mut self.typeck, kind, live_at); + Self::make_all_regions_live( + self.elements, + &mut self.typeck, + kind, + live_at, + &self.inflowing_loans, + ); polonius::add_drop_of_var_derefs_origin(&mut self.typeck, dropped_local, &kind); } @@ -548,6 +593,7 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { typeck: &mut TypeChecker<'_, 'tcx>, value: impl TypeVisitable>, live_at: &IntervalSet, + inflowing_loans: &SparseBitMatrix, ) { debug!("make_all_regions_live(value={:?})", value); debug!( @@ -556,15 +602,35 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { ); let tcx = typeck.tcx(); + let borrowck_context = &mut typeck.borrowck_context; + + // When using `-Zpolonius=next`, we want to record the loans that flow into this value's + // regions as being live at the given `live_at` points: this will be used to compute the + // location where a loan goes out of scope. + let num_loans = borrowck_context.borrow_set.len(); + let mut value_loans = HybridBitSet::new_empty(num_loans); + tcx.for_each_free_region(&value, |live_region| { - let live_region_vid = - typeck.borrowck_context.universal_regions.to_region_vid(live_region); - typeck - .borrowck_context + let live_region_vid = borrowck_context.universal_regions.to_region_vid(live_region); + + borrowck_context .constraints .liveness_constraints .add_elements(live_region_vid, live_at); + + // There can only be inflowing loans for this region when we are using + // `-Zpolonius=next`. + if let Some(inflowing) = inflowing_loans.row(live_region_vid) { + value_loans.union(inflowing); + } }); + + // Record the loans reaching the value. + if !value_loans.is_empty() { + for point in live_at.iter() { + borrowck_context.live_loans.union_row(point, &value_loans); + } + } } fn compute_drop_data( diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index e7b1a489f5d7c..1ec0e62d16a3b 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -14,6 +14,7 @@ use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; use rustc_hir::lang_items::LangItem; +use rustc_index::bit_set::SparseBitMatrix; use rustc_index::{IndexSlice, IndexVec}; use rustc_infer::infer::canonical::QueryRegionConstraints; use rustc_infer::infer::outlives::env::RegionBoundPairs; @@ -50,6 +51,8 @@ use rustc_mir_dataflow::impls::MaybeInitializedPlaces; use rustc_mir_dataflow::move_paths::MoveData; use rustc_mir_dataflow::ResultsCursor; +use crate::dataflow::BorrowIndex; +use crate::region_infer::values::PointIndex; use crate::session_diagnostics::{MoveUnsized, SimdShuffleLastConst}; use crate::{ borrow_set::BorrowSet, @@ -163,6 +166,9 @@ pub(crate) fn type_check<'mir, 'tcx>( debug!(?normalized_inputs_and_output); + // When using `-Zpolonius=next`, liveness will record the set of live loans per point. + let mut live_loans = SparseBitMatrix::new(borrow_set.len()); + let mut borrowck_context = BorrowCheckContext { universal_regions, location_table, @@ -170,6 +176,7 @@ pub(crate) fn type_check<'mir, 'tcx>( all_facts, constraints: &mut constraints, upvars, + live_loans: &mut live_loans, }; let mut checker = TypeChecker::new( @@ -240,7 +247,7 @@ pub(crate) fn type_check<'mir, 'tcx>( }) .collect(); - MirTypeckResults { constraints, universal_region_relations, opaque_type_values } + MirTypeckResults { constraints, universal_region_relations, opaque_type_values, live_loans } } fn translate_outlives_facts(typeck: &mut TypeChecker<'_, '_>) { @@ -855,12 +862,21 @@ struct BorrowCheckContext<'a, 'tcx> { borrow_set: &'a BorrowSet<'tcx>, pub(crate) constraints: &'a mut MirTypeckRegionConstraints<'tcx>, upvars: &'a [Upvar<'tcx>], + + /// The set of loans that are live at a given point in the CFG, filled in by `liveness::trace`, + /// when using `-Zpolonius=next`. + pub(crate) live_loans: &'a mut SparseBitMatrix, } +/// Holder struct for passing results from MIR typeck to the rest of the non-lexical regions +/// inference computation. pub(crate) struct MirTypeckResults<'tcx> { pub(crate) constraints: MirTypeckRegionConstraints<'tcx>, pub(crate) universal_region_relations: Frozen>, pub(crate) opaque_type_values: FxIndexMap, OpaqueHiddenType<'tcx>>, + + /// The set of loans that are live at a given point in the CFG, when using `-Zpolonius=next`. + pub(crate) live_loans: SparseBitMatrix, } /// A collection of region constraints that must be satisfied for the