-
Notifications
You must be signed in to change notification settings - Fork 13.5k
Convert typeck constraints in location-sensitive polonius #134920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+309
−117
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
eb7da16
move typeck constraints conversion to its own module
lqd 46154b2
localize typeck constraints
lqd 56e7575
move `find_assignments` to its only use site
lqd ff1aaa5
remove empty `util` module
lqd 79d761d
remove `allow_two_phase_borrow`
lqd 9d444c2
remove borrowck duplicate of `std::ops::ControlFlow`
lqd fc7ee23
address review comments
lqd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
241 changes: 241 additions & 0 deletions
241
compiler/rustc_borrowck/src/polonius/typeck_constraints.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
use rustc_data_structures::fx::FxHashSet; | ||
use rustc_middle::mir::{Body, Location, Statement, StatementKind, Terminator, TerminatorKind}; | ||
use rustc_middle::ty::{TyCtxt, TypeVisitable}; | ||
use rustc_mir_dataflow::points::PointIndex; | ||
|
||
use super::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet}; | ||
use crate::constraints::OutlivesConstraint; | ||
use crate::region_infer::values::LivenessValues; | ||
use crate::type_check::Locations; | ||
use crate::universal_regions::UniversalRegions; | ||
|
||
/// Propagate loans throughout the subset graph at a given point (with some subtleties around the | ||
/// location where effects start to be visible). | ||
pub(super) fn convert_typeck_constraints<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
body: &Body<'tcx>, | ||
liveness: &LivenessValues, | ||
outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>, | ||
universal_regions: &UniversalRegions<'tcx>, | ||
localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, | ||
) { | ||
for outlives_constraint in outlives_constraints { | ||
match outlives_constraint.locations { | ||
Locations::All(_) => { | ||
// 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 { | ||
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, | ||
}); | ||
} | ||
} | ||
} | ||
|
||
Locations::Single(location) => { | ||
// This constraint is marked as holding at one location, we localize it to that | ||
// location or its successor, depending on the corresponding MIR | ||
// statement/terminator. Unfortunately, they all show up from typeck as coming "on | ||
// entry", so for now we modify them to take effects that should apply "on exit" | ||
// into account. | ||
// | ||
// FIXME: this approach is subtle, complicated, and hard to test, so we should track | ||
// this information better in MIR typeck instead, for example with a new `Locations` | ||
// variant that contains which node is crossing over between entry and exit. | ||
let point = liveness.point_from_location(location); | ||
let localized_constraint = if let Some(stmt) = | ||
body[location.block].statements.get(location.statement_index) | ||
{ | ||
localize_statement_constraint( | ||
tcx, | ||
body, | ||
stmt, | ||
liveness, | ||
&outlives_constraint, | ||
location, | ||
point, | ||
universal_regions, | ||
) | ||
} else { | ||
assert_eq!(location.statement_index, body[location.block].statements.len()); | ||
let terminator = body[location.block].terminator(); | ||
localize_terminator_constraint( | ||
tcx, | ||
body, | ||
terminator, | ||
liveness, | ||
&outlives_constraint, | ||
point, | ||
universal_regions, | ||
) | ||
}; | ||
localized_outlives_constraints.push(localized_constraint); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// 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>( | ||
tcx: TyCtxt<'tcx>, | ||
body: &Body<'tcx>, | ||
stmt: &Statement<'tcx>, | ||
liveness: &LivenessValues, | ||
outlives_constraint: &OutlivesConstraint<'tcx>, | ||
current_location: Location, | ||
current_point: PointIndex, | ||
universal_regions: &UniversalRegions<'tcx>, | ||
) -> LocalizedOutlivesConstraint { | ||
match &stmt.kind { | ||
StatementKind::Assign(box (lhs, rhs)) => { | ||
// To create localized outlives constraints without midpoints, we rely on the property | ||
// that no input regions from the RHS of the assignment will flow into themselves: they | ||
// should not appear in the output regions in the LHS. We believe this to be true by | ||
// construction of the MIR, via temporaries, and assert it here. | ||
// | ||
// We think we don't need midpoints because: | ||
// - every LHS Place has a unique set of regions that don't appear elsewhere | ||
// - this implies that for them to be part of the RHS, the same Place must be read and | ||
// written | ||
// - and that should be impossible in MIR | ||
// | ||
// When we have a more complete implementation in the future, tested with crater, etc, | ||
// we can relax this to a debug assert instead, or remove it. | ||
assert!( | ||
{ | ||
let mut lhs_regions = FxHashSet::default(); | ||
tcx.for_each_free_region(lhs, |region| { | ||
let region = universal_regions.to_region_vid(region); | ||
lhs_regions.insert(region); | ||
}); | ||
|
||
let mut rhs_regions = FxHashSet::default(); | ||
tcx.for_each_free_region(rhs, |region| { | ||
let region = universal_regions.to_region_vid(region); | ||
rhs_regions.insert(region); | ||
}); | ||
|
||
// The intersection between LHS and RHS regions should be empty. | ||
lhs_regions.is_disjoint(&rhs_regions) | ||
}, | ||
"there should be no common regions between the LHS and RHS of an assignment" | ||
); | ||
|
||
// As mentioned earlier, we should be tracking these better upstream but: we want to | ||
// relate the types on entry to the type of the place on exit. That is, outlives | ||
// constraints on the RHS are on entry, and outlives constraints to/from the LHS are on | ||
// exit (i.e. on entry to the successor location). | ||
let lhs_ty = body.local_decls[lhs.local].ty; | ||
let successor_location = Location { | ||
block: current_location.block, | ||
statement_index: current_location.statement_index + 1, | ||
}; | ||
let successor_point = liveness.point_from_location(successor_location); | ||
compute_constraint_direction( | ||
tcx, | ||
outlives_constraint, | ||
&lhs_ty, | ||
current_point, | ||
successor_point, | ||
universal_regions, | ||
) | ||
} | ||
_ => { | ||
// For the other cases, we localize an outlives constraint to where it arises. | ||
LocalizedOutlivesConstraint { | ||
source: outlives_constraint.sup, | ||
from: current_point, | ||
target: outlives_constraint.sub, | ||
to: current_point, | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// 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>( | ||
tcx: TyCtxt<'tcx>, | ||
body: &Body<'tcx>, | ||
terminator: &Terminator<'tcx>, | ||
liveness: &LivenessValues, | ||
outlives_constraint: &OutlivesConstraint<'tcx>, | ||
current_point: PointIndex, | ||
universal_regions: &UniversalRegions<'tcx>, | ||
) -> LocalizedOutlivesConstraint { | ||
// FIXME: check if other terminators need the same handling as `Call`s, in particular | ||
// Assert/Yield/Drop. A handful of tests are failing with Drop related issues, as well as some | ||
// coroutine tests, and that may be why. | ||
match &terminator.kind { | ||
// FIXME: also handle diverging calls. | ||
TerminatorKind::Call { destination, target: Some(target), .. } => { | ||
// Calls are similar to assignments, and thus follow the same pattern. If there is a | ||
// target for the call we also relate what flows into the destination here to entry to | ||
// that successor. | ||
let destination_ty = destination.ty(&body.local_decls, tcx); | ||
let successor_location = Location { block: *target, statement_index: 0 }; | ||
let successor_point = liveness.point_from_location(successor_location); | ||
compute_constraint_direction( | ||
tcx, | ||
outlives_constraint, | ||
&destination_ty, | ||
current_point, | ||
successor_point, | ||
universal_regions, | ||
) | ||
} | ||
_ => { | ||
// 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, | ||
} | ||
} | ||
} | ||
} | ||
/// 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<TyCtxt<'tcx>>, | ||
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; | ||
} | ||
}); | ||
|
||
LocalizedOutlivesConstraint { | ||
source: outlives_constraint.sup, | ||
from, | ||
target: outlives_constraint.sub, | ||
to, | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is right, but it feels off to me. The loop using
for_each_free_region
feels like the wrong construct to me.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also prefer if this differentiation was done in MIR typeck instead, if that's what you mean. It wasn't trivial in the NLL days, according to Niko and some comments in the crate, but should still be possible I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think given the FIXME further up in the file, we don't need to do anything in this PR, just wanted to point out that it sure does feel weird.