From b70a9159a629e15bdb1aa104cc195c9d4614da89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 13 Dec 2024 10:34:00 +0000 Subject: [PATCH 1/6] introduce localized outlives constraints these are the basic blocks of the naive polonius constraint graph implementation. --- .../src/polonius/constraints.rs | 45 +++++++++++++++++++ compiler/rustc_borrowck/src/polonius/mod.rs | 3 ++ 2 files changed, 48 insertions(+) create mode 100644 compiler/rustc_borrowck/src/polonius/constraints.rs diff --git a/compiler/rustc_borrowck/src/polonius/constraints.rs b/compiler/rustc_borrowck/src/polonius/constraints.rs new file mode 100644 index 0000000000000..50f59dd0dee67 --- /dev/null +++ b/compiler/rustc_borrowck/src/polonius/constraints.rs @@ -0,0 +1,45 @@ +use rustc_middle::ty::RegionVid; +use rustc_mir_dataflow::points::PointIndex; + +/// A localized outlives constraint reifies the CFG location where the outlives constraint holds, +/// within the origins themselves as if they were different from point to point: from `a: b` +/// outlives constraints to `a@p: b@p`, where `p` is the point in the CFG. +/// +/// This models two sources of constraints: +/// - constraints that traverse the subsets between regions at a given point, `a@p: b@p`. These +/// depend on typeck constraints generated via assignments, calls, etc. (In practice there are +/// subtleties where a statement's effect only starts being visible at the successor point, via +/// the "result" of that statement). +/// - constraints that traverse the CFG via the same region, `a@p: a@q`, where `p` is a predecessor +/// of `q`. These depend on the liveness of the regions at these points, as well as their +/// variance. +/// +/// The `source` origin at `from` flows into the `target` origin at `to`. +/// +/// This dual of NLL's [crate::constraints::OutlivesConstraint] therefore encodes the +/// position-dependent outlives constraints used by Polonius, to model the flow-sensitive loan +/// propagation via reachability within a graph of localized constraints. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub(crate) struct LocalizedOutlivesConstraint { + pub source: RegionVid, + pub from: PointIndex, + pub target: RegionVid, + pub to: PointIndex, +} + +/// A container of [LocalizedOutlivesConstraint]s that can be turned into a traversable +/// `rustc_data_structures` graph. +#[derive(Clone, Default, Debug)] +pub(crate) struct LocalizedOutlivesConstraintSet { + pub outlives: Vec, +} + +impl LocalizedOutlivesConstraintSet { + pub(crate) fn push(&mut self, constraint: LocalizedOutlivesConstraint) { + if constraint.source == constraint.target && constraint.from == constraint.to { + // 'a@p: 'a@p is pretty uninteresting + return; + } + self.outlives.push(constraint); + } +} diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 9c1583f198862..391ef2069f95e 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -1 +1,4 @@ +mod constraints; +pub(crate) use constraints::*; + pub(crate) mod legacy; From a5f05919e0b6d7036a4cd161a02b93150af41758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 13 Dec 2024 10:35:28 +0000 Subject: [PATCH 2/6] add general documentation on the polonius module this describes the rough algorithm using the localized constraint graph --- compiler/rustc_borrowck/src/polonius/mod.rs | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 391ef2069f95e..70f8fdaa63780 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -1,3 +1,38 @@ +//! Polonius analysis and support code: +//! - dedicated constraints +//! - conversion from NLL constraints +//! - debugging utilities +//! - etc. +//! +//! The current implementation models the flow-sensitive borrow-checking concerns as a graph +//! containing both information about regions and information about the control flow. +//! +//! Loan propagation is seen as a reachability problem (with some subtleties) between where the loan +//! is introduced and a given point. +//! +//! Constraints arising from type-checking allow loans to flow from region to region at the same CFG +//! point. Constraints arising from liveness allow loans to flow within from point to point, between +//! live regions at these points. +//! +//! Edges can be bidirectional to encode invariant relationships, and loans can flow "back in time" +//! to traverse these constraints arising earlier in the CFG. +//! +//! When incorporating kills in the traversal, the loans reaching a given point are considered live. +//! +//! After this, the usual NLL process happens. These live loans are fed into a dataflow analysis +//! combining them with the points where loans go out of NLL scope (the frontier where they stop +//! propagating to a live region), to yield the "loans in scope" or "active loans", at a given +//! point. +//! +//! Illegal accesses are still computed by checking whether one of these resulting loans is +//! invalidated. +//! +//! More information on this simple approach can be found in the following links, and in the future +//! in the rustc dev guide: +//! - +//! - +//! + mod constraints; pub(crate) use constraints::*; From e7fb93bf9b4261cd786977f7a41a9ff63ff22a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 13 Dec 2024 15:32:52 +0000 Subject: [PATCH 3/6] set up skeleton for localized constraints conversion --- compiler/rustc_borrowck/src/lib.rs | 1 + compiler/rustc_borrowck/src/nll.rs | 17 +++ compiler/rustc_borrowck/src/polonius/mod.rs | 143 +++++++++++++++++- .../rustc_borrowck/src/region_infer/mod.rs | 4 + 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 63e20b16f7a01..f42945787e1e9 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -202,6 +202,7 @@ fn do_mir_borrowck<'tcx>( polonius_output, opt_closure_req, nll_errors, + localized_outlives_constraints, } = nll::compute_regions( &infcx, free_regions, diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 7656031ed3a8f..66bb62641e622 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -29,6 +29,7 @@ use crate::consumers::ConsumerOptions; use crate::diagnostics::RegionErrors; use crate::facts::{AllFacts, AllFactsExt, RustcFacts}; use crate::location::LocationTable; +use crate::polonius::LocalizedOutlivesConstraintSet; use crate::region_infer::RegionInferenceContext; use crate::type_check::{self, MirTypeckResults}; use crate::universal_regions::UniversalRegions; @@ -45,6 +46,9 @@ pub(crate) struct NllOutput<'tcx> { pub polonius_output: Option>, pub opt_closure_req: Option>, pub nll_errors: RegionErrors<'tcx>, + + /// When using `-Zpolonius=next`: the localized typeck and liveness constraints. + pub localized_outlives_constraints: LocalizedOutlivesConstraintSet, } /// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal @@ -135,6 +139,18 @@ pub(crate) fn compute_regions<'a, 'tcx>( elements, ); + // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives + // constraints. + let mut localized_outlives_constraints = LocalizedOutlivesConstraintSet::default(); + if infcx.tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { + polonius::create_localized_constraints( + &mut regioncx, + infcx.infcx.tcx, + body, + &mut localized_outlives_constraints, + ); + } + // If requested: dump NLL facts, and run legacy polonius analysis. let polonius_output = all_facts.as_ref().and_then(|all_facts| { if infcx.tcx.sess.opts.unstable_opts.nll_facts { @@ -175,6 +191,7 @@ pub(crate) fn compute_regions<'a, 'tcx>( polonius_output, opt_closure_req: closure_region_requirements, nll_errors, + localized_outlives_constraints, } } diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 70f8fdaa63780..03db7ba50edad 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -35,5 +35,146 @@ mod constraints; pub(crate) use constraints::*; - pub(crate) mod legacy; + +use rustc_middle::mir::{Body, Location}; +use rustc_middle::ty::TyCtxt; +use rustc_mir_dataflow::points::PointIndex; + +use crate::RegionInferenceContext; +use crate::constraints::OutlivesConstraint; +use crate::region_infer::values::LivenessValues; +use crate::type_check::Locations; +use crate::universal_regions::UniversalRegions; + +/// When using `-Zpolonius=next`, fills the given constraint set by: +/// - converting NLL typeck constraints to be localized +/// - encoding liveness constraints +pub(crate) fn create_localized_constraints<'tcx>( + regioncx: &mut RegionInferenceContext<'tcx>, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, +) { + if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { + return; + } + + convert_typeck_constraints( + body, + regioncx.liveness_constraints(), + regioncx.outlives_constraints(), + localized_outlives_constraints, + ); + create_liveness_constraints( + body, + regioncx.liveness_constraints(), + regioncx.universal_regions(), + localized_outlives_constraints, + ); + + // FIXME: here, we can trace loan reachability in the constraint graph and record this as loan + // liveness for the next step in the chain, the NLL loan scope and active loans computations. +} + +/// Propagate loans throughout the subset graph at a given point (with some subtleties around the +/// location where effects start to be visible). +fn convert_typeck_constraints<'tcx>( + body: &Body<'tcx>, + liveness: &LivenessValues, + outlives_constraints: impl Iterator>, + localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, +) { + for outlives_constraint in outlives_constraints { + match outlives_constraint.locations { + Locations::All(_) => { + // FIXME: for now, turn logical constraints holding at all points into physical + // edges at every point in the graph. We can encode this into *traversal* instead. + for (block, bb) in body.basic_blocks.iter_enumerated() { + let statement_count = bb.statements.len(); + for statement_index in 0..=statement_count { + let current_location = Location { block, statement_index }; + let current_point = liveness.point_from_location(current_location); + + localized_outlives_constraints.push(LocalizedOutlivesConstraint { + source: outlives_constraint.sup, + from: current_point, + target: outlives_constraint.sub, + to: current_point, + }); + } + } + } + + _ => {} + } + } +} + +/// Propagate loans throughout the CFG: for each statement in the MIR, create localized outlives +/// constraints for loans that are propagated to the next statements. +pub(crate) fn create_liveness_constraints<'tcx>( + body: &Body<'tcx>, + liveness: &LivenessValues, + universal_regions: &UniversalRegions<'tcx>, + localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, +) { + for (block, bb) in body.basic_blocks.iter_enumerated() { + let statement_count = bb.statements.len(); + for statement_index in 0..=statement_count { + let current_location = Location { block, statement_index }; + let current_point = liveness.point_from_location(current_location); + + if statement_index < statement_count { + // Intra-block edges, straight line constraints from each point to its successor + // within the same block. + let next_location = Location { block, statement_index: statement_index + 1 }; + let next_point = liveness.point_from_location(next_location); + propagate_loans_between_points( + current_point, + next_point, + liveness, + universal_regions, + localized_outlives_constraints, + ); + } else { + // Inter-block edges, from the block's terminator to each successor block's entry + // point. + for successor_block in bb.terminator().successors() { + let next_location = Location { block: successor_block, statement_index: 0 }; + let next_point = liveness.point_from_location(next_location); + propagate_loans_between_points( + current_point, + next_point, + liveness, + universal_regions, + localized_outlives_constraints, + ); + } + } + } + } +} + +/// Propagate loans within a region between two points in the CFG, if that region is live at both +/// the source and target points. +fn propagate_loans_between_points( + current_point: PointIndex, + next_point: PointIndex, + _liveness: &LivenessValues, + universal_regions: &UniversalRegions<'_>, + localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, +) { + // Universal regions are semantically live at all points. + // FIXME: We always have universal regions but they're not always (or often) involved in the + // subset graph. So for now, we emit this edge, but we only need to emit edges for universal + // regions that existential regions can actually reach. + for region in universal_regions.universal_regions_iter() { + localized_outlives_constraints.push(LocalizedOutlivesConstraint { + source: region, + from: current_point, + target: region, + to: next_point, + }); + } +} diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 0eecf98a6ede5..f2aa8b8e780b3 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -2224,6 +2224,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn scc_representative(&self, scc: ConstraintSccIndex) -> RegionVid { self.constraint_sccs.annotation(scc).representative } + + pub(crate) fn liveness_constraints(&self) -> &LivenessValues { + &self.liveness_constraints + } } impl<'tcx> RegionDefinition<'tcx> { From ee93ce9c611b673520eedceddb3bd0d13a4fc73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 13 Dec 2024 15:35:28 +0000 Subject: [PATCH 4/6] extract main NLL MIR dump function this will allow calling from polonius MIR --- compiler/rustc_borrowck/src/nll.rs | 80 ++++++++++++++----------- compiler/rustc_middle/src/mir/pretty.rs | 1 + 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 66bb62641e622..ec0dbf6aa77b5 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -232,40 +232,7 @@ pub(super) fn dump_nll_mir<'tcx>( &0, body, |pass_where, out| { - match pass_where { - // Before the CFG, dump out the values for each region variable. - PassWhere::BeforeCFG => { - regioncx.dump_mir(tcx, out)?; - writeln!(out, "|")?; - - if let Some(closure_region_requirements) = closure_region_requirements { - writeln!(out, "| Free Region Constraints")?; - for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| { - writeln!(out, "| {msg}") - })?; - writeln!(out, "|")?; - } - - if borrow_set.len() > 0 { - writeln!(out, "| Borrows")?; - for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() { - writeln!( - out, - "| {:?}: issued at {:?} in {:?}", - borrow_idx, borrow_data.reserve_location, borrow_data.region - )?; - } - writeln!(out, "|")?; - } - } - - PassWhere::BeforeLocation(_) => {} - - PassWhere::AfterTerminator(_) => {} - - PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {} - } - Ok(()) + emit_nll_mir(tcx, regioncx, closure_region_requirements, borrow_set, pass_where, out) }, options, ); @@ -283,6 +250,51 @@ pub(super) fn dump_nll_mir<'tcx>( }; } +/// Produces the actual NLL MIR sections to emit during the dumping process. +pub(crate) fn emit_nll_mir<'tcx>( + tcx: TyCtxt<'tcx>, + regioncx: &RegionInferenceContext<'tcx>, + closure_region_requirements: &Option>, + borrow_set: &BorrowSet<'tcx>, + pass_where: PassWhere, + out: &mut dyn io::Write, +) -> io::Result<()> { + match pass_where { + // Before the CFG, dump out the values for each region variable. + PassWhere::BeforeCFG => { + regioncx.dump_mir(tcx, out)?; + writeln!(out, "|")?; + + if let Some(closure_region_requirements) = closure_region_requirements { + writeln!(out, "| Free Region Constraints")?; + for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| { + writeln!(out, "| {msg}") + })?; + writeln!(out, "|")?; + } + + if borrow_set.len() > 0 { + writeln!(out, "| Borrows")?; + for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() { + writeln!( + out, + "| {:?}: issued at {:?} in {:?}", + borrow_idx, borrow_data.reserve_location, borrow_data.region + )?; + } + writeln!(out, "|")?; + } + } + + PassWhere::BeforeLocation(_) => {} + + PassWhere::AfterTerminator(_) => {} + + PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {} + } + Ok(()) +} + #[allow(rustc::diagnostic_outside_of_impl)] #[allow(rustc::untranslatable_diagnostic)] pub(super) fn dump_annotation<'tcx, 'infcx>( diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 2bfcd0a62274d..bf5045835a012 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -23,6 +23,7 @@ pub(crate) const ALIGN: usize = 40; /// An indication of where we are in the control flow graph. Used for printing /// extra information in `dump_mir` +#[derive(Clone)] pub enum PassWhere { /// We have not started dumping the control flow graph, but we are about to. BeforeCFG, From c75c5176c577b56985ba1e1117d949940710ad0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 13 Dec 2024 15:44:39 +0000 Subject: [PATCH 5/6] introduce beginnings of polonius MIR dump This is mostly for test purposes to show the localized constraints until the MIR debugger is set up. --- compiler/rustc_borrowck/src/lib.rs | 10 ++ compiler/rustc_borrowck/src/polonius/dump.rs | 101 ++++++++++++++++++ compiler/rustc_borrowck/src/polonius/mod.rs | 2 + .../rustc_borrowck/src/region_infer/values.rs | 5 + 4 files changed, 118 insertions(+) create mode 100644 compiler/rustc_borrowck/src/polonius/dump.rs diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index f42945787e1e9..5c8e12a1c69ef 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -316,6 +316,16 @@ fn do_mir_borrowck<'tcx>( mbcx.report_move_errors(); + // If requested, dump polonius MIR. + polonius::dump_polonius_mir( + &infcx, + body, + ®ioncx, + &borrow_set, + &localized_outlives_constraints, + &opt_closure_req, + ); + // For each non-user used mutable variable, check if it's been assigned from // a user-declared local. If so, then put that local into the used_mut set. // Note that this set is expected to be small - only upvars from closures diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs new file mode 100644 index 0000000000000..607e3d92bb1a9 --- /dev/null +++ b/compiler/rustc_borrowck/src/polonius/dump.rs @@ -0,0 +1,101 @@ +use std::io; + +use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options}; +use rustc_middle::mir::{Body, ClosureRegionRequirements, PassWhere}; +use rustc_middle::ty::TyCtxt; +use rustc_session::config::MirIncludeSpans; + +use crate::borrow_set::BorrowSet; +use crate::polonius::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet}; +use crate::{BorrowckInferCtxt, RegionInferenceContext}; + +/// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information. +// Note: this currently duplicates most of NLL MIR, with some additions for the localized outlives +// constraints. This is ok for now as this dump will change in the near future to an HTML file to +// become more useful. +pub(crate) fn dump_polonius_mir<'tcx>( + infcx: &BorrowckInferCtxt<'tcx>, + body: &Body<'tcx>, + regioncx: &RegionInferenceContext<'tcx>, + borrow_set: &BorrowSet<'tcx>, + localized_outlives_constraints: &LocalizedOutlivesConstraintSet, + closure_region_requirements: &Option>, +) { + let tcx = infcx.tcx; + if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { + return; + } + + // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in + // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example, + // they're always disabled in mir-opt tests to make working with blessed dumps easier. + let options = PrettyPrintMirOptions { + include_extra_comments: matches!( + tcx.sess.opts.unstable_opts.mir_include_spans, + MirIncludeSpans::On | MirIncludeSpans::Nll + ), + }; + + dump_mir_with_options( + tcx, + false, + "polonius", + &0, + body, + |pass_where, out| { + emit_polonius_mir( + tcx, + regioncx, + closure_region_requirements, + borrow_set, + localized_outlives_constraints, + pass_where, + out, + ) + }, + options, + ); +} + +/// Produces the actual NLL + Polonius MIR sections to emit during the dumping process. +fn emit_polonius_mir<'tcx>( + tcx: TyCtxt<'tcx>, + regioncx: &RegionInferenceContext<'tcx>, + closure_region_requirements: &Option>, + borrow_set: &BorrowSet<'tcx>, + localized_outlives_constraints: &LocalizedOutlivesConstraintSet, + pass_where: PassWhere, + out: &mut dyn io::Write, +) -> io::Result<()> { + // Emit the regular NLL front-matter + crate::nll::emit_nll_mir( + tcx, + regioncx, + closure_region_requirements, + borrow_set, + pass_where.clone(), + out, + )?; + + let liveness = regioncx.liveness_constraints(); + + // Add localized outlives constraints + match pass_where { + PassWhere::BeforeCFG => { + if localized_outlives_constraints.outlives.len() > 0 { + writeln!(out, "| Localized constraints")?; + + for constraint in &localized_outlives_constraints.outlives { + let LocalizedOutlivesConstraint { source, from, target, to } = constraint; + let from = liveness.location_from_point(*from); + let to = liveness.location_from_point(*to); + writeln!(out, "| {source:?} at {from:?} -> {target:?} at {to:?}")?; + } + writeln!(out, "|")?; + } + } + _ => {} + } + + Ok(()) +} diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 03db7ba50edad..fa7e08d9db345 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -35,6 +35,8 @@ mod constraints; pub(crate) use constraints::*; +mod dump; +pub(crate) use dump::dump_polonius_mir; pub(crate) mod legacy; use rustc_middle::mir::{Body, Location}; diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index a16bce63839b0..0b0757f16ab25 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -199,6 +199,11 @@ impl LivenessValues { self.elements.point_from_location(location) } + #[inline] + pub(crate) fn location_from_point(&self, point: PointIndex) -> Location { + self.elements.to_location(point) + } + /// When using `-Zpolonius=next`, returns whether the `loan_idx` is live at the given `point`. pub(crate) fn is_loan_live_at(&self, loan_idx: BorrowIndex, point: PointIndex) -> bool { self.loans From aeb3d103a6913115996d0ddb9d550016ea403488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Mon, 16 Dec 2024 11:08:00 +0000 Subject: [PATCH 6/6] address review comments - move constraints to an Option - check `-Zpolonius=next` only once - rewrite fixme comments to make the actionable part clear --- compiler/rustc_borrowck/src/lib.rs | 2 +- compiler/rustc_borrowck/src/nll.rs | 17 +++++------ compiler/rustc_borrowck/src/polonius/dump.rs | 7 +++-- compiler/rustc_borrowck/src/polonius/mod.rs | 30 +++++++++----------- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 5c8e12a1c69ef..e6dfe4e0ce212 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -322,7 +322,7 @@ fn do_mir_borrowck<'tcx>( body, ®ioncx, &borrow_set, - &localized_outlives_constraints, + localized_outlives_constraints, &opt_closure_req, ); diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index ec0dbf6aa77b5..abe27555b1864 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -48,7 +48,7 @@ pub(crate) struct NllOutput<'tcx> { pub nll_errors: RegionErrors<'tcx>, /// When using `-Zpolonius=next`: the localized typeck and liveness constraints. - pub localized_outlives_constraints: LocalizedOutlivesConstraintSet, + pub localized_outlives_constraints: Option, } /// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal @@ -141,15 +141,12 @@ pub(crate) fn compute_regions<'a, 'tcx>( // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives // constraints. - let mut localized_outlives_constraints = LocalizedOutlivesConstraintSet::default(); - if infcx.tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { - polonius::create_localized_constraints( - &mut regioncx, - infcx.infcx.tcx, - body, - &mut localized_outlives_constraints, - ); - } + let localized_outlives_constraints = + if infcx.tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { + Some(polonius::create_localized_constraints(&mut regioncx, body)) + } else { + None + }; // If requested: dump NLL facts, and run legacy polonius analysis. let polonius_output = all_facts.as_ref().and_then(|all_facts| { diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs index 607e3d92bb1a9..a6d8014903440 100644 --- a/compiler/rustc_borrowck/src/polonius/dump.rs +++ b/compiler/rustc_borrowck/src/polonius/dump.rs @@ -18,7 +18,7 @@ pub(crate) fn dump_polonius_mir<'tcx>( body: &Body<'tcx>, regioncx: &RegionInferenceContext<'tcx>, borrow_set: &BorrowSet<'tcx>, - localized_outlives_constraints: &LocalizedOutlivesConstraintSet, + localized_outlives_constraints: Option, closure_region_requirements: &Option>, ) { let tcx = infcx.tcx; @@ -26,6 +26,9 @@ pub(crate) fn dump_polonius_mir<'tcx>( return; } + let localized_outlives_constraints = localized_outlives_constraints + .expect("missing localized constraints with `-Zpolonius=next`"); + // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example, // they're always disabled in mir-opt tests to make working with blessed dumps easier. @@ -48,7 +51,7 @@ pub(crate) fn dump_polonius_mir<'tcx>( regioncx, closure_region_requirements, borrow_set, - localized_outlives_constraints, + &localized_outlives_constraints, pass_where, out, ) diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index fa7e08d9db345..eee5e70efe348 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -40,7 +40,6 @@ pub(crate) use dump::dump_polonius_mir; pub(crate) mod legacy; use rustc_middle::mir::{Body, Location}; -use rustc_middle::ty::TyCtxt; use rustc_mir_dataflow::points::PointIndex; use crate::RegionInferenceContext; @@ -49,34 +48,31 @@ use crate::region_infer::values::LivenessValues; use crate::type_check::Locations; use crate::universal_regions::UniversalRegions; -/// When using `-Zpolonius=next`, fills the given constraint set by: +/// Creates a constraint set for `-Zpolonius=next` by: /// - converting NLL typeck constraints to be localized /// - encoding liveness constraints pub(crate) fn create_localized_constraints<'tcx>( regioncx: &mut RegionInferenceContext<'tcx>, - tcx: TyCtxt<'tcx>, body: &Body<'tcx>, - localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, -) { - if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { - return; - } - +) -> LocalizedOutlivesConstraintSet { + let mut localized_outlives_constraints = LocalizedOutlivesConstraintSet::default(); convert_typeck_constraints( body, regioncx.liveness_constraints(), regioncx.outlives_constraints(), - localized_outlives_constraints, + &mut localized_outlives_constraints, ); create_liveness_constraints( body, regioncx.liveness_constraints(), regioncx.universal_regions(), - localized_outlives_constraints, + &mut localized_outlives_constraints, ); // FIXME: here, we can trace loan reachability in the constraint graph and record this as loan // liveness for the next step in the chain, the NLL loan scope and active loans computations. + + localized_outlives_constraints } /// Propagate loans throughout the subset graph at a given point (with some subtleties around the @@ -90,8 +86,9 @@ fn convert_typeck_constraints<'tcx>( for outlives_constraint in outlives_constraints { match outlives_constraint.locations { Locations::All(_) => { - // FIXME: for now, turn logical constraints holding at all points into physical - // edges at every point in the graph. We can encode this into *traversal* instead. + // For now, turn logical constraints holding at all points into physical edges at + // every point in the graph. + // FIXME: encode this into *traversal* instead. for (block, bb) in body.basic_blocks.iter_enumerated() { let statement_count = bb.statements.len(); for statement_index in 0..=statement_count { @@ -168,9 +165,10 @@ fn propagate_loans_between_points( localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, ) { // Universal regions are semantically live at all points. - // FIXME: We always have universal regions but they're not always (or often) involved in the - // subset graph. So for now, we emit this edge, but we only need to emit edges for universal - // regions that existential regions can actually reach. + // Note: we always have universal regions but they're not always (or often) involved in the + // subset graph. For now, we emit all their edges unconditionally, but some of these subgraphs + // will be disconnected from the rest of the graph and thus, unnecessary. + // FIXME: only emit the edges of universal regions that existential regions can reach. for region in universal_regions.universal_regions_iter() { localized_outlives_constraints.push(LocalizedOutlivesConstraint { source: region,