diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 5537d90e297aa..d661c47c87e10 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -154,7 +154,15 @@ pub(crate) fn compute_regions<'tcx>( // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints // and use them to compute loan liveness. let polonius_diagnostics = polonius_context.map(|polonius_context| { - polonius_context.compute_loan_liveness(infcx.tcx, &mut regioncx, body, borrow_set) + // let _timer = std::time::Instant::now(); + let ret = + polonius_context.compute_loan_liveness(infcx.tcx, &mut regioncx, body, borrow_set); + // eprintln!( + // "compute_loan_liveness took: {} ns, {:?}, borrows: {}, localized constraints: {}", + // _timer.elapsed().as_nanos(), + // body.span, borrow_set.len(), ret.localized_outlives_constraints.outlives.len(), + // ); + ret }); // If requested: dump NLL facts, and run legacy polonius analysis. diff --git a/compiler/rustc_borrowck/src/polonius/constraints.rs b/compiler/rustc_borrowck/src/polonius/constraints.rs index 5259575785955..cf0e737146633 100644 --- a/compiler/rustc_borrowck/src/polonius/constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/constraints.rs @@ -1,6 +1,8 @@ use rustc_middle::ty::RegionVid; use rustc_mir_dataflow::points::PointIndex; +use crate::region_infer::values::LivenessValues; + /// 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. @@ -23,6 +25,25 @@ pub(crate) struct LocalizedOutlivesConstraint { pub from: PointIndex, pub target: RegionVid, pub to: PointIndex, + pub tag: &'static str, +} + +impl rustc_mir_dataflow::fmt::DebugWithContext for LocalizedOutlivesConstraint { + fn fmt_with( + &self, + liveness: &LivenessValues, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!( + fmt, + "{}@{:?} -> {}@{:?} ({})", + self.source.as_u32(), + liveness.location_from_point(self.from), + self.target.as_u32(), + liveness.location_from_point(self.to), + self.tag, + ) + } } /// A container of [LocalizedOutlivesConstraint]s that can be turned into a traversable @@ -33,11 +54,11 @@ pub(crate) struct LocalizedOutlivesConstraintSet { } 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); - } + // 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/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs index 62f9ae173474d..5b5558f783a22 100644 --- a/compiler/rustc_borrowck/src/polonius/dump.rs +++ b/compiler/rustc_borrowck/src/polonius/dump.rs @@ -216,7 +216,7 @@ fn emit_polonius_mir<'tcx>( writeln!(out, "| Localized constraints")?; for constraint in &localized_outlives_constraints.outlives { - let LocalizedOutlivesConstraint { source, from, target, to } = constraint; + 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:?}")?; diff --git a/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs b/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs index f1338b3bf1ee5..255c137a2794a 100644 --- a/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs @@ -1,19 +1,20 @@ use std::collections::BTreeMap; use rustc_hir::def_id::DefId; -use rustc_index::bit_set::SparseBitMatrix; -use rustc_middle::mir::{Body, Location}; +// use rustc_index::bit_set::SparseBitMatrix; +// use rustc_middle::mir::{Body, Location}; use rustc_middle::ty::relate::{ self, Relate, RelateResult, TypeRelation, relate_args_with_variances, }; use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeVisitable}; -use rustc_mir_dataflow::points::PointIndex; +// use rustc_mir_dataflow::points::PointIndex; use super::{ - ConstraintDirection, LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet, + ConstraintDirection, + // LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet, PoloniusLivenessContext, }; -use crate::region_infer::values::LivenessValues; +// use crate::region_infer::values::LivenessValues; use crate::universal_regions::UniversalRegions; impl PoloniusLivenessContext { @@ -34,164 +35,188 @@ impl PoloniusLivenessContext { } } -/// 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(super) fn create_liveness_constraints<'tcx>( - body: &Body<'tcx>, - liveness: &LivenessValues, - live_regions: &SparseBitMatrix, - live_region_variances: &BTreeMap, - 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); +// /// 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 fn create_liveness_constraints<'tcx>( +// body: &Body<'tcx>, +// liveness: &LivenessValues, +// live_regions: &SparseBitMatrix, +// live_region_variances: &BTreeMap, +// 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, - live_regions, - live_region_variances, - 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, - live_regions, - live_region_variances, - universal_regions, - localized_outlives_constraints, - ); - } - } - } - } -} +// 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, +// live_regions, +// live_region_variances, +// universal_regions, +// localized_outlives_constraints, +// "orig", +// liveness, +// ); +// } 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, +// live_regions, +// live_region_variances, +// universal_regions, +// localized_outlives_constraints, +// "orig", +// liveness, +// ); +// } +// } +// } +// } +// } -/// 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, - live_regions: &SparseBitMatrix, - live_region_variances: &BTreeMap, - universal_regions: &UniversalRegions<'_>, - localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, -) { - // Universal regions are semantically live at all points. - // 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, - from: current_point, - target: region, - to: next_point, - }); - } +// /// Propagate loans within a region between two points in the CFG, if that region is live at both +// /// the source and target points. +// pub(crate) fn propagate_loans_between_points( +// current_point: PointIndex, +// next_point: PointIndex, +// live_regions: &SparseBitMatrix, +// live_region_variances: &BTreeMap, +// universal_regions: &UniversalRegions<'_>, +// localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, +// tag: &str, +// liveness: &LivenessValues, +// ) { +// // Universal regions are semantically live at all points. +// // 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() { +// let localized_constraint = LocalizedOutlivesConstraint { +// source: region, +// from: current_point, +// target: region, +// to: next_point, +// tag: "L*", +// }; - let Some(next_live_regions) = live_regions.row(next_point) else { - // There are no constraints to add: there are no live regions at the next point. - return; - }; +// localized_outlives_constraints.push(localized_constraint); +// } - for region in next_live_regions.iter() { - // `region` could be live at the current point, and is live at the next point: add a - // constraint between them, according to variance. - if let Some(&direction) = live_region_variances.get(®ion) { - add_liveness_constraint( - region, - current_point, - next_point, - direction, - localized_outlives_constraints, - ); - } else { - // Note: there currently are cases related to promoted and const generics, where we - // don't yet have variance information (possibly about temporary regions created when - // typeck sanitizes the promoteds). Until that is done, we conservatively fallback to - // maximizing reachability by adding a bidirectional edge here. This will not limit - // traversal whatsoever, and thus propagate liveness when needed. - // - // FIXME: add the missing variance information and remove this fallback bidirectional - // edge. - let fallback = ConstraintDirection::Bidirectional; - add_liveness_constraint( - region, - current_point, - next_point, - fallback, - localized_outlives_constraints, - ); - } - } -} +// let Some(next_live_regions) = live_regions.row(next_point) else { +// // There are no constraints to add: there are no live regions at the next point. +// return; +// }; -/// Adds `LocalizedOutlivesConstraint`s between two connected points, according to the given edge -/// direction. -fn add_liveness_constraint( - region: RegionVid, - current_point: PointIndex, - next_point: PointIndex, - direction: ConstraintDirection, - localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, -) { - match direction { - ConstraintDirection::Forward => { - // Covariant cases: loans flow in the regular direction, from the current point to the - // next point. - localized_outlives_constraints.push(LocalizedOutlivesConstraint { - source: region, - from: current_point, - target: region, - to: next_point, - }); - } - ConstraintDirection::Backward => { - // Contravariant cases: loans flow in the inverse direction, from the next point to the - // current point. - localized_outlives_constraints.push(LocalizedOutlivesConstraint { - source: region, - from: next_point, - target: region, - to: current_point, - }); - } - ConstraintDirection::Bidirectional => { - // For invariant cases, loans can flow in both directions: we add both edges. - localized_outlives_constraints.push(LocalizedOutlivesConstraint { - source: region, - from: current_point, - target: region, - to: next_point, - }); - localized_outlives_constraints.push(LocalizedOutlivesConstraint { - source: region, - from: next_point, - target: region, - to: current_point, - }); - } - } -} +// for region in next_live_regions.iter() { +// // `region` could be live at the current point, and is live at the next point: add a +// // constraint between them, according to variance. +// if let Some(&direction) = live_region_variances.get(®ion) { +// add_liveness_constraint( +// region, +// current_point, +// next_point, +// direction, +// localized_outlives_constraints, +// tag, +// liveness, +// ); +// } else { +// // Note: there currently are cases related to promoted and const generics, where we +// // don't yet have variance information (possibly about temporary regions created when +// // typeck sanitizes the promoteds). Until that is done, we conservatively fallback to +// // maximizing reachability by adding a bidirectional edge here. This will not limit +// // traversal whatsoever, and thus propagate liveness when needed. +// // +// // FIXME: add the missing variance information and remove this fallback bidirectional +// // edge. +// let fallback = ConstraintDirection::Bidirectional; +// add_liveness_constraint( +// region, +// current_point, +// next_point, +// fallback, +// localized_outlives_constraints, +// tag, +// liveness, +// ); +// } +// } +// } + +// /// Adds `LocalizedOutlivesConstraint`s between two connected points, according to the given edge +// /// direction. +// fn add_liveness_constraint( +// region: RegionVid, +// current_point: PointIndex, +// next_point: PointIndex, +// direction: ConstraintDirection, +// localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, +// _tag: &str, +// _liveness: &LivenessValues, +// ) { +// match direction { +// ConstraintDirection::Forward => { +// // Covariant cases: loans flow in the regular direction, from the current point to the +// // next point. +// let constraint = LocalizedOutlivesConstraint { +// source: region, +// from: current_point, +// target: region, +// to: next_point, +// tag: "L1", + +// }; +// localized_outlives_constraints.push(constraint); +// } +// ConstraintDirection::Backward => { +// // Contravariant cases: loans flow in the inverse direction, from the next point to the +// // current point. +// let constraint = LocalizedOutlivesConstraint { +// source: region, +// from: next_point, +// target: region, +// to: current_point, +// tag: "L2", +// }; +// localized_outlives_constraints.push(constraint); +// } +// ConstraintDirection::Bidirectional => { +// // For invariant cases, loans can flow in both directions: we add both edges. +// let constraint = LocalizedOutlivesConstraint { +// source: region, +// from: current_point, +// target: region, +// to: next_point, +// tag: "L3a", +// }; +// localized_outlives_constraints.push(constraint); +// let constraint = LocalizedOutlivesConstraint { +// source: region, +// from: next_point, +// target: region, +// to: current_point, +// tag: "L3b", +// }; +// localized_outlives_constraints.push(constraint); +// } +// } +// } /// Extracts variances for regions contained within types. Follows the same structure as /// `rustc_infer`'s `Generalizer`: we try to relate a type with itself to track and extract the diff --git a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs index bdc3047e5ba01..2506eee458cb0 100644 --- a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs +++ b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs @@ -1,30 +1,195 @@ +use std::collections::BTreeMap; + use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; -use rustc_middle::ty::RegionVid; +use rustc_index::bit_set::SparseBitMatrix; +use rustc_middle::mir::{Body, Location}; +use rustc_middle::ty::{RegionVid, TyCtxt}; +// use rustc_mir_dataflow::fmt::DebugWithAdapter; use rustc_mir_dataflow::points::PointIndex; use super::{LiveLoans, LocalizedOutlivesConstraintSet}; -use crate::BorrowSet; use crate::constraints::OutlivesConstraint; +use crate::polonius::ConstraintDirection; +// use crate::polonius::LocalizedOutlivesConstraint; use crate::region_infer::values::LivenessValues; use crate::type_check::Locations; +use crate::universal_regions::UniversalRegions; +use crate::{BorrowSet, RegionInferenceContext}; /// Compute loan reachability to approximately trace loan liveness throughout the CFG, by /// traversing the full graph of constraints that combines: /// - the localized constraints (the physical edges), /// - with the constraints that hold at all points (the logical edges). pub(super) fn compute_loan_liveness<'tcx>( + _tcx: TyCtxt<'tcx>, liveness: &LivenessValues, - outlives_constraints: impl Iterator>, + // outlives_constraints: impl Iterator>, + regioncx: &RegionInferenceContext<'tcx>, borrow_set: &BorrowSet<'tcx>, localized_outlives_constraints: &LocalizedOutlivesConstraintSet, + body: &Body<'tcx>, + universal_regions: &UniversalRegions<'tcx>, + live_regions: &SparseBitMatrix, + live_region_variances: &BTreeMap, ) -> LiveLoans { + // let _timer = std::time::Instant::now(); + let mut live_loans = LiveLoans::new(borrow_set.len()); + // let mut live_loans_orig = LiveLoans::new(borrow_set.len()); + + // We want to traverse a (region, point) node to its successors computed by typeck. + let mut outlives_per_node: FxHashMap>> = + FxHashMap::default(); + for outlives_constraint in regioncx.outlives_constraints() { + match outlives_constraint.locations { + Locations::All(_) => { + // We don't turn constraints holding at all points into physical edges at every + // point in the graph. They are encoded into *traversal* instead: a given node's + // successors will combine these logical edges with the regular, physical, localized + // edges. + continue; + } + + Locations::Single(location) => { + let node = LocalizedNode { + region: outlives_constraint.sup, + point: liveness.point_from_location(location), + }; + outlives_per_node.entry(node).or_default().push(outlives_constraint); + } + } + } + + // eprintln!( + // "compute_loan_liveness - 1ndexing constraints took: {} ns, {:?}, outlives constraints: {}", + // _timer.elapsed().as_nanos(), + // body.span, + // regioncx.outlives_constraints().count(), + // ); + + // let _timer = std::time::Instant::now(); + + let outlives_constraints = regioncx.outlives_constraints(); + // Create the full graph with the physical edges we've localized earlier, and the logical edges // of constraints that hold at all points. let logical_constraints = outlives_constraints.filter(|c| matches!(c.locations, Locations::All(_))); let graph = LocalizedConstraintGraph::new(&localized_outlives_constraints, logical_constraints); + + // eprintln!( + // "compute_loan_liveness - 2ndexing constraints took: {} ns, {:?}, logical constraints: {}, localized constraints: {}", + // _timer.elapsed().as_nanos(), + // body.span, + // graph.logical_edges.len(), + // localized_outlives_constraints.outlives.len(), + // ); + + // let _timer = std::time::Instant::now(); + + // let mut visited = FxHashSet::default(); + // let mut stack = Vec::new(); + + // // Compute reachability per loan by traversing each loan's subgraph starting from where it is + // // introduced. + // for (loan_idx, loan) in borrow_set.iter_enumerated() { + // visited.clear(); + // stack.clear(); + + // let start_node = LocalizedNode { + // region: loan.region, + // point: liveness.point_from_location(loan.reserve_location), + // }; + // stack.push(start_node); + + // while let Some(node) = stack.pop() { + // if !visited.insert(node) { + // continue; + // } + + // // Record the loan as being live on entry to this point if it reaches a live region + // // there. + // // + // // This is an approximation of liveness (which is the thing we want), in that we're + // // using a single notion of reachability to represent what used to be _two_ different + // // transitive closures. It didn't seem impactful when coming up with the single-graph + // // and reachability through space (regions) + time (CFG) concepts, but in practice the + // // combination of time-traveling with kills is more impactful than initially + // // anticipated. + // // + // // Kills should prevent a loan from reaching its successor points in the CFG, but not + // // while time-traveling: we're not actually at that CFG point, but looking for + // // predecessor regions that contain the loan. One of the two TCs we had pushed the + // // transitive subset edges to each point instead of having backward edges, and the + // // problem didn't exist before. In the abstract, naive reachability is not enough to + // // model this, we'd need a slightly different solution. For example, maybe with a + // // two-step traversal: + // // - at each point we first traverse the subgraph (and possibly time-travel) looking for + // // exit nodes while ignoring kills, + // // - and then when we're back at the current point, we continue normally. + // // + // // Another (less annoying) subtlety is that kills and the loan use-map are + // // flow-insensitive. Kills can actually appear in places before a loan is introduced, or + // // at a location that is actually unreachable in the CFG from the introduction point, + // // and these can also be encountered during time-traveling. + // // + // // The simplest change that made sense to "fix" the issues above is taking into + // // account kills that are: + // // - reachable from the introduction point + // // - encountered during forward traversal. Note that this is not transitive like the + // // two-step traversal described above: only kills encountered on exit via a backward + // // edge are ignored. + // // + // // This version of the analysis, however, is enough in practice to pass the tests that + // // we care about and NLLs reject, without regressions on crater, and is an actionable + // // subset of the full analysis. It also naturally points to areas of improvement that we + // // wish to explore later, namely handling kills appropriately during traversal, instead + // // of continuing traversal to all the reachable nodes. + // // + // // FIXME: analyze potential unsoundness, possibly in concert with a borrowck + // // implementation in a-mir-formality, fuzzing, or manually crafting counter-examples. + + // if liveness.is_live_at(node.region, liveness.location_from_point(node.point)) { + // // live_loans.insert(node.point, loan_idx); + // live_loans_orig.insert(node.point, loan_idx); + // } + + // let _location = liveness.location_from_point(node.point); + // for succ in graph.outgoing_edges(node) { + // // eprintln!( + // // "B - region {:?} at {:?} has successor region {:?} at {:?}", + // // node.region, + // // _location, + // // succ.region, + // // liveness.location_from_point(succ.point), + // // ); + + // // if node.region.as_u32() == 17 && succ.region.as_u32() == 5 { + // // eprintln!("B2, node: {node:?}, succ: {succ:?}"); + // // // eprintln!("outlives: {:?}", outlives_per_point.get(&node.point)); + // // for c in &localized_outlives_constraints.outlives { + // // if c.source == node.region && c.target == succ.region { + // // eprintln!("localized outlives constraint: {c:?}"); + // // } + // // } + // // } + + // stack.push(succ); + // } + // } + // } + + // eprintln!( + // "compute_loan_liveness - loan propagat1on took: {} ns, {:?}, logical constraints: {}, localized constraints: {}", + // _timer.elapsed().as_nanos(), + // body.span, + // graph.logical_edges.len(), + // localized_outlives_constraints.outlives.len(), + // ); + + // let _timer = std::time::Instant::now(); + let mut visited = FxHashSet::default(); let mut stack = Vec::new(); @@ -91,12 +256,300 @@ pub(super) fn compute_loan_liveness<'tcx>( live_loans.insert(node.point, loan_idx); } - for succ in graph.outgoing_edges(node) { + // The outgoing edges are: + // - the physical edges present at this node, + // - the materialized logical edges that exist virtually at all points for this node's + // region, localized at this point. + + let location = liveness.location_from_point(node.point); + + // let mut successors = Vec::new(); + + // The physical edges present at this node are: + // + // 1. the typeck edges that flow from region to region *at this point* + for outlives_constraint in outlives_per_node.get(&node).into_iter().flatten() { + let succ = LocalizedNode { region: outlives_constraint.sub, point: node.point }; stack.push(succ); + // successors.push(succ); } + + // 2a. the liveness edges that flow *forward*, from this node's point to its successors + // in the CFG. + // FIXME: this shouldn't need to take a successors vec, and only return an optional + // successor. + fn propagate_loans_forward( + region: RegionVid, + next_point: PointIndex, + live_regions: &SparseBitMatrix, + live_region_variances: &BTreeMap, + universal_regions: &UniversalRegions<'_>, + successors: &mut Vec, + ) { + // 1. Universal regions are semantically live at all points. + if universal_regions.is_universal_region(region) { + let succ = LocalizedNode { region, point: next_point }; + successors.push(succ); + + // FIXME: we shouldn't need to continue the function and should return here; + // universal regions are most likely not even marked live at the next point + } + + // 2. Gather the edges due to explicit region liveness. + let Some(next_live_regions) = live_regions.row(next_point) else { + // There are no constraints to add: there are no live regions at the next point. + return; + }; + + // `region` could be live at the current point, and is live at the next + // point: add a constraint between them, according to variance. + if !next_live_regions.contains(region) { + return; + } + + // Note: there currently are cases related to promoted and const generics, + // where we don't yet have variance information (possibly about temporary + // regions created when typeck sanitizes the promoteds). Until that is done, + // we conservatively fallback to maximizing reachability by adding a + // bidirectional edge here. This will not limit traversal whatsoever, and + // thus propagate liveness when needed. + // + // FIXME: add the missing variance information and remove this fallback + // bidirectional edge. + let direction = live_region_variances + .get(®ion) + .unwrap_or(&ConstraintDirection::Bidirectional); + + match direction { + ConstraintDirection::Forward => { + // Covariant cases: loans flow in the regular direction, from the + // current point to the next point. + let succ = LocalizedNode { region, point: next_point }; + successors.push(succ); + } + ConstraintDirection::Backward => { + // Contravariant cases: loans flow in the inverse direction, but + // we're only interested in forward successors and there + // are none here. + } + ConstraintDirection::Bidirectional => { + // For invariant cases, loans can flow in both directions, but + // here as well, we only want the forward path of the + // bidirectional edge. + let succ = LocalizedNode { region, point: next_point }; + successors.push(succ); + } + } + } + + if body[location.block].statements.get(location.statement_index).is_some() { + // Intra-block edges, straight line constraints from each point to its successor + // within the same block. + let next_location = Location { + block: location.block, + statement_index: location.statement_index + 1, + }; + let next_point = liveness.point_from_location(next_location); + // FIXME: the above should not be needed, the next point in a block should be + // current_point_idx + 1, only an unknown block needs some translation from the + // locationmap + propagate_loans_forward( + node.region, + next_point, + live_regions, + live_region_variances, + universal_regions, + // &mut successors, + &mut stack, + ); + } else { + // Inter-block edges, from the block's terminator to each successor block's entry + // point. + for successor_block in body[location.block].terminator().successors() { + let next_location = Location { block: successor_block, statement_index: 0 }; + let next_point = liveness.point_from_location(next_location); + propagate_loans_forward( + node.region, + next_point, + live_regions, + live_region_variances, + universal_regions, + // &mut successors, + &mut stack, + ); + } + } + + // 2b. the liveness edges that flow *backward*, from this node's point to its predecessors + // in the CFG. + fn propagate_loans_backward( + region: RegionVid, + current_point: PointIndex, + previous_point: PointIndex, + live_regions: &SparseBitMatrix, + live_region_variances: &BTreeMap, + successors: &mut Vec, + ) { + // Since liveness flows into the regions live at the next point, we compute the + // regions live at the current point, and gather the backward edges flowing to the + // predecessor point. + let Some(current_live_regions) = live_regions.row(current_point) else { + // There are no constraints to add: there are no live regions at the current point. + return; + }; + + // `region` could be live at the previous point, and is live at the current + // point: add a constraint between them, according to variance. + if !current_live_regions.contains(region) { + return; + } + + // Note: there currently are cases related to promoted and const generics, + // where we don't yet have variance information (possibly about temporary + // regions created when typeck sanitizes the promoteds). Until that is done, + // we conservatively fallback to maximizing reachability by adding a + // bidirectional edge here. This will not limit traversal whatsoever, and + // thus propagate liveness when needed. + // + // FIXME: add the missing variance information and remove this fallback + // bidirectional edge. + let direction = live_region_variances + .get(®ion) + .unwrap_or(&ConstraintDirection::Bidirectional); + + match direction { + ConstraintDirection::Forward => { + // Covariant cases: loans flow in the regular direction, but we're only + // interested in backward successors and there are none here. + } + ConstraintDirection::Backward => { + // Contravariant cases: loans flow in the inverse direction, from the next + // point to the current point. + let succ = LocalizedNode { region, point: previous_point }; + successors.push(succ); + } + ConstraintDirection::Bidirectional => { + // For invariant cases, loans can flow in both directions, but + // here as well, we only want the backward path of the + // bidirectional edge. + let succ = LocalizedNode { region, point: previous_point }; + successors.push(succ); + } + } + } + + if location.statement_index > 0 { + // Intra-block edges. We want the predecessor point in the same block. + let previous_location = Location { + block: location.block, + statement_index: location.statement_index - 1, + }; + // FIXME: here as well, the previous point within a block is just the index - 1, we don't + // need to go through a Location. + let previous_point = liveness.point_from_location(previous_location); + propagate_loans_backward( + node.region, + node.point, + previous_point, + live_regions, + live_region_variances, + // &mut successors, + &mut stack, + ); + } else { + // Block entry point, we want the inter-block edges. The terminator of the predecessor block. + for &pred in &body.basic_blocks.predecessors()[location.block] { + let previous_location = + Location { block: pred, statement_index: body[pred].statements.len() }; + let previous_point = liveness.point_from_location(previous_location); + propagate_loans_backward( + node.region, + node.point, + previous_point, + live_regions, + live_region_variances, + // &mut successors, + &mut stack, + ); + } + } + + // 3. The logical edges, materialized at this point. + for logical_succ in graph.logical_edges.get(&node.region).into_iter().flatten() { + let succ = LocalizedNode { region: *logical_succ, point: node.point }; + // successors.push(succ); + stack.push(succ); + } + + // --- + + // let mut valid_edges: Vec<_> = graph.outgoing_edges(node).collect(); + // let mut invalid_edges: Vec<_> = successors.iter().copied().collect(); + + // valid_edges.sort(); + // invalid_edges.sort(); + // valid_edges.dedup(); + // invalid_edges.dedup(); + + // fn prepare(it: impl Iterator) -> Vec { + // let mut edges: Vec<_> = it.collect(); + // edges.sort(); + // edges.dedup(); + // edges + // } + + // let _physical_edges = prepare( + // graph.edges.get(&node).into_iter().flat_map(|targets| targets.iter().copied()), + // ); + // let print = |edges: &Vec| { + // edges + // .iter() + // .map(|e| format!( + // "{}@{:?}", + // e.region.as_u32(), + // liveness.location_from_point(e.point), + // )) + // .collect::>() + // }; + + // assert_eq!( + // valid_edges, + // invalid_edges, + // // "edges differed for region {:?} at {:?}, valid: {:?} (physical: {:?}), invalid: {:?}", + // "edges differed for region {:?} at {:?}, valid: {:?}, invalid: {:?}", + // node.region, + // location, + // print(&valid_edges), + // // print(&_physical_edges), + // print(&invalid_edges), + // ); + + // for succ in successors { + // stack.push(succ); + // } } } + // for (point, point_orig) in live_loans.rows().zip(live_loans_orig.rows()) { + // assert_eq!(point, point_orig, "points differ"); + // let loans_new = live_loans.row(point); + // let loans_orig = live_loans_orig.row(point); + // assert_eq!( + // loans_orig, + // loans_new, + // "live loans differ at {:?}", + // liveness.location_from_point(point), + // ); + // } + + // eprintln!( + // "compute_loan_liveness - loan propagat2on took: {} ns, {:?}, logical constraints: {}, localized constraints: {}", + // _timer.elapsed().as_nanos(), + // body.span, + // graph.logical_edges.len(), + // localized_outlives_constraints.outlives.len(), + // ); + live_loans } @@ -104,7 +557,7 @@ pub(super) fn compute_loan_liveness<'tcx>( /// successors during traversal. struct LocalizedConstraintGraph { /// The actual, physical, edges we have recorded for a given node. - edges: FxHashMap>, + // edges: FxHashMap>, /// The logical edges representing the outlives constraints that hold at all points in the CFG, /// which we don't localize to avoid creating a lot of unnecessary edges in the graph. Some CFGs @@ -113,7 +566,7 @@ struct LocalizedConstraintGraph { } /// A node in the graph to be traversed, one of the two vertices of a localized outlives constraint. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Ord, PartialOrd)] struct LocalizedNode { region: RegionVid, point: PointIndex, @@ -122,39 +575,39 @@ struct LocalizedNode { impl LocalizedConstraintGraph { /// Traverses the constraints and returns the indexed graph of edges per node. fn new<'tcx>( - constraints: &LocalizedOutlivesConstraintSet, + _constraints: &LocalizedOutlivesConstraintSet, logical_constraints: impl Iterator>, ) -> Self { - let mut edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default(); - for constraint in &constraints.outlives { - let source = LocalizedNode { region: constraint.source, point: constraint.from }; - let target = LocalizedNode { region: constraint.target, point: constraint.to }; - edges.entry(source).or_default().insert(target); - } + // let edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default(); + // for constraint in &constraints.outlives { + // let source = LocalizedNode { region: constraint.source, point: constraint.from }; + // let target = LocalizedNode { region: constraint.target, point: constraint.to }; + // edges.entry(source).or_default().insert(target); + // } let mut logical_edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default(); for constraint in logical_constraints { logical_edges.entry(constraint.sup).or_default().insert(constraint.sub); } - LocalizedConstraintGraph { edges, logical_edges } + LocalizedConstraintGraph { logical_edges } } - /// Returns the outgoing edges of a given node, not its transitive closure. - fn outgoing_edges(&self, node: LocalizedNode) -> impl Iterator { - // The outgoing edges are: - // - the physical edges present at this node, - // - the materialized logical edges that exist virtually at all points for this node's - // region, localized at this point. - let physical_edges = - self.edges.get(&node).into_iter().flat_map(|targets| targets.iter().copied()); - let materialized_edges = - self.logical_edges.get(&node.region).into_iter().flat_map(move |targets| { - targets - .iter() - .copied() - .map(move |target| LocalizedNode { point: node.point, region: target }) - }); - physical_edges.chain(materialized_edges) - } + // /// Returns the outgoing edges of a given node, not its transitive closure. + // fn outgoing_edges(&self, node: LocalizedNode) -> impl Iterator { + // // The outgoing edges are: + // // - the physical edges present at this node, + // // - the materialized logical edges that exist virtually at all points for this node's + // // region, localized at this point. + // let physical_edges = + // self.edges.get(&node).into_iter().flat_map(|targets| targets.iter().copied()); + // let materialized_edges = + // self.logical_edges.get(&node.region).into_iter().flat_map(move |targets| { + // targets + // .iter() + // .copied() + // .map(move |target| LocalizedNode { point: node.point, region: target }) + // }); + // physical_edges.chain(materialized_edges) + // } } diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index a9092b1981e1d..8a17e8aea4ada 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -48,7 +48,7 @@ mod dump; pub(crate) mod legacy; mod liveness_constraints; mod loan_liveness; -mod typeck_constraints; +// mod typeck_constraints; use std::collections::BTreeMap; @@ -61,9 +61,9 @@ use rustc_mir_dataflow::points::PointIndex; pub(crate) use self::constraints::*; pub(crate) use self::dump::dump_polonius_mir; -use self::liveness_constraints::create_liveness_constraints; +// use self::liveness_constraints::create_liveness_constraints; use self::loan_liveness::compute_loan_liveness; -use self::typeck_constraints::convert_typeck_constraints; +// use self::typeck_constraints::convert_typeck_constraints; use crate::dataflow::BorrowIndex; use crate::{BorrowSet, RegionInferenceContext}; @@ -99,7 +99,7 @@ pub(crate) struct PoloniusContext { /// computed from the [PoloniusContext] when computing NLL regions. pub(crate) struct PoloniusDiagnosticsContext { /// The localized outlives constraints that were computed in the main analysis. - localized_outlives_constraints: LocalizedOutlivesConstraintSet, + pub localized_outlives_constraints: LocalizedOutlivesConstraintSet, /// The liveness data computed during MIR typeck: [PoloniusLivenessContext::boring_nll_locals]. pub(crate) boring_nll_locals: FxHashSet, @@ -160,34 +160,186 @@ impl PoloniusContext { let PoloniusLivenessContext { live_region_variances, boring_nll_locals } = self.liveness_context; - let mut localized_outlives_constraints = LocalizedOutlivesConstraintSet::default(); - convert_typeck_constraints( - tcx, - body, - regioncx.liveness_constraints(), - regioncx.outlives_constraints(), - regioncx.universal_regions(), - &mut localized_outlives_constraints, - ); - - create_liveness_constraints( - body, - regioncx.liveness_constraints(), - &self.live_regions, - &live_region_variances, - regioncx.universal_regions(), - &mut localized_outlives_constraints, - ); - - // Now that we have a complete graph, we can compute reachability to trace the liveness of - // loans for the next step in the chain, the NLL loan scope and active loans computations. - let live_loans = compute_loan_liveness( - regioncx.liveness_constraints(), - regioncx.outlives_constraints(), - borrow_set, - &localized_outlives_constraints, - ); - regioncx.record_live_loans(live_loans); + // use rustc_middle::mir; + // use crate::consumers::OutlivesConstraint; + // use crate::type_check::Locations; + // use rustc_data_structures::fx::FxHashMap; + // let liveness = regioncx.liveness_constraints(); + + // let mut outlives_per_point: FxHashMap>> = + // FxHashMap::default(); + // for outlives_constraint in regioncx.outlives_constraints() { + // match outlives_constraint.locations { + // Locations::All(_) => { + // // We don't turn constraints holding at all points into physical edges at every + // // point in the graph. They are encoded into *traversal* instead: a given node's + // // successors will combine these logical edges with the regular, physical, localized + // // edges. + // continue; + // } + + // Locations::Single(location) => { + // outlives_per_point.entry(location).or_default().push(outlives_constraint) + // } + // } + // } + + // let mut loans: SparseBitMatrix = + // SparseBitMatrix::new(borrow_set.len()); + + // // todo: faut pas boucler sur les blocks mais suivre le cfg bien sur + // 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 = mir::Location { block, statement_index }; + // // let current_point = liveness.point_from_location(current_location); + + // if statement_index < statement_count { + // // stmt + // let stmt = &bb.statements[statement_index]; + // match &stmt.kind { + // mir::StatementKind::Assign(box (_lhs, rhs)) => { + // if let mir::Rvalue::Ref(_, _, place) = rhs { + // use crate::place_ext::PlaceExt; + // if place.ignore_borrow(tcx, body, &borrow_set.locals_state_at_exit) + // { + // } else { + // let loan = borrow_set + // .get_index_of(¤t_location) + // .unwrap_or_else(|| { + // panic!( + // "could not find BorrowIndex for location {current_location:?}" + // ); + // }); + + // // 1. The loan is introduced here + // let data = &borrow_set[loan]; + // eprintln!( + // "introducing loan {:?} in region {:?} at {:?}", + // loan, data.region, current_location + // ); + // loans.insert(data.region, loan); + // } + // } + + // // // Make sure there are no remaining borrows for variables + // // // that are assigned over. + // // self.kill_borrows_on_place(state, *lhs); + // } + + // _ => {} + // } + // } + + // // 2. loans are propagated along the outlives edges present at this point + // // - needs fixpoint in case a later outlives flows into a region earlier in the list + // // - in particular recording target regions as dirty + // if let Some(outlives) = outlives_per_point.get(¤t_location) { + // let mut changed = false; + // for outlives_constraint in outlives { + // // Whatever is in the source region flows into the target region. + // changed |= + // loans.union_rows(outlives_constraint.sup, outlives_constraint.sub); + // eprintln!( + // "loans in region {:?} flow into region {:?} (changed: {}) at {:?}", + // outlives_constraint.sup, + // outlives_constraint.sub, + // changed, + // current_location + // ); + // } + // } + + // // 3. on enregistre les loans qui atteignent des rĂ©gions live ici dans la liste des + // // live loans + + // // 4. todo: continue traversal to the point successors + // // - note that it can be the point predecessors + // // 4b. on doit enlever de la map les rĂ©gions qui sont dead au successeur (pb, faut genre la cloner?) + // if statement_index < statement_count { + // // stmt + // } else { + // // terminator + // // 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 = + // mir::Location { block: successor_block, statement_index: 0 }; + // // let next_point = liveness.point_from_location(next_location); + // } + // } + // } + // } + + let localized_outlives_constraints = LocalizedOutlivesConstraintSet::default(); + + if borrow_set.len() > 0 { + // let _timer = std::time::Instant::now(); + // convert_typeck_constraints( + // tcx, + // body, + // regioncx.liveness_constraints(), + // regioncx.outlives_constraints(), + // regioncx.universal_regions(), + // &mut localized_outlives_constraints, + // ); + + // eprintln!( + // "compute_loan_liveness - typeck constraints took: {} ns, {:?}, outlives constraints: {}, localized constraints: {}", + // _timer.elapsed().as_nanos(), + // body.span, + // regioncx.outlives_constraints().count(), + // localized_outlives_constraints.outlives.len(), + // ); + // let _typeck_edges = localized_outlives_constraints.outlives.len(); + + // let _timer = std::time::Instant::now(); + // create_liveness_constraints( + // body, + // regioncx.liveness_constraints(), + // &self.live_regions, + // &live_region_variances, + // regioncx.universal_regions(), + // &mut localized_outlives_constraints, + // ); + // eprintln!( + // "compute_loan_liveness - liveness constraints took: {} ns, {:?}, cfg statements: {}, localized constraints: {}", + // _timer.elapsed().as_nanos(), + // body.span, + // body.basic_blocks + // .iter() + // .map(|bb| { + // let statement_count = bb.statements.len(); + // statement_count + // }) + // .sum::(), + // localized_outlives_constraints.outlives.len() - _typeck_edges, + // ); + + // let _timer = std::time::Instant::now(); + + // Now that we have a complete graph, we can compute reachability to trace the liveness of + // loans for the next step in the chain, the NLL loan scope and active loans computations. + let live_loans = compute_loan_liveness( + tcx, + regioncx.liveness_constraints(), + ®ioncx, + borrow_set, + &localized_outlives_constraints, + body, + regioncx.universal_regions(), + &self.live_regions, + &live_region_variances, + ); + + regioncx.record_live_loans(live_loans); + // eprintln!( + // "compute_loan_liveness - computing liveness took: {} ns, {:?}", + // _timer.elapsed().as_nanos(), + // body.span, + // ); + } PoloniusDiagnosticsContext { localized_outlives_constraints, boring_nll_locals } } diff --git a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs index e4e52962bf7f9..63169942739e2 100644 --- a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs @@ -1,6 +1,6 @@ use rustc_data_structures::fx::FxHashSet; -use rustc_middle::mir::{Body, Location, Statement, StatementKind, Terminator, TerminatorKind}; -use rustc_middle::ty::{TyCtxt, TypeVisitable}; +use rustc_middle::mir::{Body, Statement, StatementKind, Terminator, TerminatorKind}; +use rustc_middle::ty::TyCtxt; use rustc_mir_dataflow::points::PointIndex; use super::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet}; @@ -56,9 +56,7 @@ pub(super) fn convert_typeck_constraints<'tcx>( let terminator = body[location.block].terminator(); localize_terminator_constraint( tcx, - body, terminator, - liveness, &outlives_constraint, point, universal_regions, @@ -72,7 +70,7 @@ pub(super) fn convert_typeck_constraints<'tcx>( /// For a given outlives constraint arising from a MIR statement, localize the constraint with the /// needed CFG `from`-`to` intra-block nodes. -fn localize_statement_constraint<'tcx>( +pub(crate) fn localize_statement_constraint<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, stmt: &Statement<'tcx>, @@ -140,11 +138,11 @@ fn localize_statement_constraint<'tcx>( /// For a given outlives constraint arising from a MIR terminator, localize the constraint with the /// needed CFG `from`-`to` inter-block nodes. -fn localize_terminator_constraint<'tcx>( +pub(crate) fn localize_terminator_constraint<'tcx>( tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, + // body: &Body<'tcx>, terminator: &Terminator<'tcx>, - liveness: &LivenessValues, + // liveness: &LivenessValues, outlives_constraint: &OutlivesConstraint<'tcx>, current_point: PointIndex, universal_regions: &UniversalRegions<'tcx>, @@ -171,48 +169,54 @@ fn localize_terminator_constraint<'tcx>( ) } _ => { - // Typeck constraints guide loans between regions at the current point, so we do that in - // the general case, and liveness will take care of making them flow to the terminator's - // successors. - LocalizedOutlivesConstraint { - source: outlives_constraint.sup, - from: current_point, - target: outlives_constraint.sub, - to: current_point, - } + // FIXME: check if other terminators need the same assertion as `Call`s, in particular + // Assert/Yield/Drop. } } -} - -/// For a given outlives constraint and CFG edge, returns the localized constraint with the -/// appropriate `from`-`to` direction. This is computed according to whether the constraint flows to -/// or from a free region in the given `value`, some kind of result for an effectful operation, like -/// the LHS of an assignment. -fn compute_constraint_direction<'tcx>( - tcx: TyCtxt<'tcx>, - outlives_constraint: &OutlivesConstraint<'tcx>, - value: &impl TypeVisitable>, - current_point: PointIndex, - successor_point: PointIndex, - universal_regions: &UniversalRegions<'tcx>, -) -> LocalizedOutlivesConstraint { - let mut to = current_point; - let mut from = current_point; - tcx.for_each_free_region(value, |region| { - let region = universal_regions.to_region_vid(region); - if region == outlives_constraint.sub { - // This constraint flows into the result, its effects start becoming visible on exit. - to = successor_point; - } else if region == outlives_constraint.sup { - // This constraint flows from the result, its effects start becoming visible on exit. - from = successor_point; - } - }); + // Typeck constraints guide loans between regions at the current point, so we do that in + // the general case, and liveness will take care of making them flow to the terminator's + // successors. LocalizedOutlivesConstraint { source: outlives_constraint.sup, - from, + from: current_point, target: outlives_constraint.sub, - to, + to: current_point, + tag: "T2", } } + +// /// For a given outlives constraint and CFG edge, returns the localized constraint with the +// /// appropriate `from`-`to` direction. This is computed according to whether the constraint flows to +// /// or from a free region in the given `value`, some kind of result for an effectful operation, like +// /// the LHS of an assignment. +// fn compute_constraint_direction<'tcx>( +// tcx: TyCtxt<'tcx>, +// outlives_constraint: &OutlivesConstraint<'tcx>, +// value: &impl TypeVisitable>, +// current_point: PointIndex, +// successor_point: PointIndex, +// universal_regions: &UniversalRegions<'tcx>, +// ) -> LocalizedOutlivesConstraint { +// use rustc_middle::ty::TypeVisitable; +// let mut to = current_point; +// let mut from = current_point; +// tcx.for_each_free_region(value, |region| { +// let region = universal_regions.to_region_vid(region); +// if region == outlives_constraint.sub { +// // This constraint flows into the result, its effects start becoming visible on exit. +// to = successor_point; +// } else if region == outlives_constraint.sup { +// // This constraint flows from the result, its effects start becoming visible on exit. +// from = successor_point; +// } +// }); + +// LocalizedOutlivesConstraint { +// source: outlives_constraint.sup, +// from, +// target: outlives_constraint.sub, +// to, +// tag: "X", +// } +// } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 9b19961fcee4e..0d11c22c07c98 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3442,13 +3442,13 @@ impl PatchableFunctionEntry { #[derive(Clone, Copy, PartialEq, Hash, Debug, Default)] pub enum Polonius { /// The default value: disabled. - #[default] Off, /// Legacy version, using datalog and the `polonius-engine` crate. Historical value for `-Zpolonius`. Legacy, /// In-tree prototype, extending the NLL infrastructure. + #[default] Next, }