439 lines
16 KiB
Rust
439 lines
16 KiB
Rust
use rustc_data_structures::fx::FxHashMap;
|
|
use rustc_index::bit_set::BitSet;
|
|
use rustc_middle::mir::{self, BasicBlock, Body, Location, Place};
|
|
use rustc_middle::ty::RegionVid;
|
|
use rustc_middle::ty::TyCtxt;
|
|
use rustc_mir_dataflow::impls::{EverInitializedPlaces, MaybeUninitializedPlaces};
|
|
use rustc_mir_dataflow::ResultsVisitable;
|
|
use rustc_mir_dataflow::{self, fmt::DebugWithContext, CallReturnPlaces, GenKill};
|
|
use rustc_mir_dataflow::{Analysis, Direction, Results};
|
|
use std::fmt;
|
|
|
|
use crate::{
|
|
places_conflict, BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, ToRegionVid,
|
|
};
|
|
|
|
/// A tuple with named fields that can hold either the results or the transient state of the
|
|
/// dataflow analyses used by the borrow checker.
|
|
#[derive(Debug)]
|
|
pub struct BorrowckAnalyses<B, U, E> {
|
|
pub borrows: B,
|
|
pub uninits: U,
|
|
pub ever_inits: E,
|
|
}
|
|
|
|
/// The results of the dataflow analyses used by the borrow checker.
|
|
pub type BorrowckResults<'mir, 'tcx> = BorrowckAnalyses<
|
|
Results<'tcx, Borrows<'mir, 'tcx>>,
|
|
Results<'tcx, MaybeUninitializedPlaces<'mir, 'tcx>>,
|
|
Results<'tcx, EverInitializedPlaces<'mir, 'tcx>>,
|
|
>;
|
|
|
|
/// The transient state of the dataflow analyses used by the borrow checker.
|
|
pub type BorrowckFlowState<'mir, 'tcx> =
|
|
<BorrowckResults<'mir, 'tcx> as ResultsVisitable<'tcx>>::FlowState;
|
|
|
|
macro_rules! impl_visitable {
|
|
( $(
|
|
$T:ident { $( $field:ident : $A:ident ),* $(,)? }
|
|
)* ) => { $(
|
|
impl<'tcx, $($A),*, D: Direction> ResultsVisitable<'tcx> for $T<$( Results<'tcx, $A> ),*>
|
|
where
|
|
$( $A: Analysis<'tcx, Direction = D>, )*
|
|
{
|
|
type Direction = D;
|
|
type FlowState = $T<$( $A::Domain ),*>;
|
|
|
|
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
|
|
$T {
|
|
$( $field: self.$field.analysis.bottom_value(body) ),*
|
|
}
|
|
}
|
|
|
|
fn reset_to_block_entry(
|
|
&self,
|
|
state: &mut Self::FlowState,
|
|
block: BasicBlock,
|
|
) {
|
|
$( state.$field.clone_from(&self.$field.entry_set_for_block(block)); )*
|
|
}
|
|
|
|
fn reconstruct_before_statement_effect(
|
|
&self,
|
|
state: &mut Self::FlowState,
|
|
stmt: &mir::Statement<'tcx>,
|
|
loc: Location,
|
|
) {
|
|
$( self.$field.analysis
|
|
.apply_before_statement_effect(&mut state.$field, stmt, loc); )*
|
|
}
|
|
|
|
fn reconstruct_statement_effect(
|
|
&self,
|
|
state: &mut Self::FlowState,
|
|
stmt: &mir::Statement<'tcx>,
|
|
loc: Location,
|
|
) {
|
|
$( self.$field.analysis
|
|
.apply_statement_effect(&mut state.$field, stmt, loc); )*
|
|
}
|
|
|
|
fn reconstruct_before_terminator_effect(
|
|
&self,
|
|
state: &mut Self::FlowState,
|
|
term: &mir::Terminator<'tcx>,
|
|
loc: Location,
|
|
) {
|
|
$( self.$field.analysis
|
|
.apply_before_terminator_effect(&mut state.$field, term, loc); )*
|
|
}
|
|
|
|
fn reconstruct_terminator_effect(
|
|
&self,
|
|
state: &mut Self::FlowState,
|
|
term: &mir::Terminator<'tcx>,
|
|
loc: Location,
|
|
) {
|
|
$( self.$field.analysis
|
|
.apply_terminator_effect(&mut state.$field, term, loc); )*
|
|
}
|
|
}
|
|
)* }
|
|
}
|
|
|
|
impl_visitable! {
|
|
BorrowckAnalyses { borrows: B, uninits: U, ever_inits: E }
|
|
}
|
|
|
|
rustc_index::newtype_index! {
|
|
pub struct BorrowIndex {
|
|
DEBUG_FORMAT = "bw{}"
|
|
}
|
|
}
|
|
|
|
/// `Borrows` stores the data used in the analyses that track the flow
|
|
/// of borrows.
|
|
///
|
|
/// It uniquely identifies every borrow (`Rvalue::Ref`) by a
|
|
/// `BorrowIndex`, and maps each such index to a `BorrowData`
|
|
/// describing the borrow. These indexes are used for representing the
|
|
/// borrows in compact bitvectors.
|
|
pub struct Borrows<'a, 'tcx> {
|
|
tcx: TyCtxt<'tcx>,
|
|
body: &'a Body<'tcx>,
|
|
|
|
borrow_set: &'a BorrowSet<'tcx>,
|
|
borrows_out_of_scope_at_location: FxHashMap<Location, Vec<BorrowIndex>>,
|
|
}
|
|
|
|
struct StackEntry {
|
|
bb: mir::BasicBlock,
|
|
lo: usize,
|
|
hi: usize,
|
|
}
|
|
|
|
struct OutOfScopePrecomputer<'a, 'tcx> {
|
|
visited: BitSet<mir::BasicBlock>,
|
|
visit_stack: Vec<StackEntry>,
|
|
body: &'a Body<'tcx>,
|
|
regioncx: &'a RegionInferenceContext<'tcx>,
|
|
borrows_out_of_scope_at_location: FxHashMap<Location, Vec<BorrowIndex>>,
|
|
}
|
|
|
|
impl<'a, 'tcx> OutOfScopePrecomputer<'a, 'tcx> {
|
|
fn new(body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>) -> Self {
|
|
OutOfScopePrecomputer {
|
|
visited: BitSet::new_empty(body.basic_blocks().len()),
|
|
visit_stack: vec![],
|
|
body,
|
|
regioncx,
|
|
borrows_out_of_scope_at_location: FxHashMap::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {
|
|
fn precompute_borrows_out_of_scope(
|
|
&mut self,
|
|
borrow_index: BorrowIndex,
|
|
borrow_region: RegionVid,
|
|
location: Location,
|
|
) {
|
|
// We visit one BB at a time. The complication is that we may start in the
|
|
// middle of the first BB visited (the one containing `location`), in which
|
|
// case we may have to later on process the first part of that BB if there
|
|
// is a path back to its start.
|
|
|
|
// For visited BBs, we record the index of the first statement processed.
|
|
// (In fully processed BBs this index is 0.) Note also that we add BBs to
|
|
// `visited` once they are added to `stack`, before they are actually
|
|
// processed, because this avoids the need to look them up again on
|
|
// completion.
|
|
self.visited.insert(location.block);
|
|
|
|
let mut first_lo = location.statement_index;
|
|
let first_hi = self.body[location.block].statements.len();
|
|
|
|
self.visit_stack.push(StackEntry { bb: location.block, lo: first_lo, hi: first_hi });
|
|
|
|
while let Some(StackEntry { bb, lo, hi }) = self.visit_stack.pop() {
|
|
// If we process the first part of the first basic block (i.e. we encounter that block
|
|
// for the second time), we no longer have to visit its successors again.
|
|
let mut finished_early = bb == location.block && hi != first_hi;
|
|
for i in lo..=hi {
|
|
let location = Location { block: bb, statement_index: i };
|
|
// If region does not contain a point at the location, then add to list and skip
|
|
// successor locations.
|
|
if !self.regioncx.region_contains(borrow_region, location) {
|
|
debug!("borrow {:?} gets killed at {:?}", borrow_index, location);
|
|
self.borrows_out_of_scope_at_location
|
|
.entry(location)
|
|
.or_default()
|
|
.push(borrow_index);
|
|
finished_early = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !finished_early {
|
|
// Add successor BBs to the work list, if necessary.
|
|
let bb_data = &self.body[bb];
|
|
debug_assert!(hi == bb_data.statements.len());
|
|
for &succ_bb in bb_data.terminator().successors() {
|
|
if !self.visited.insert(succ_bb) {
|
|
if succ_bb == location.block && first_lo > 0 {
|
|
// `succ_bb` has been seen before. If it wasn't
|
|
// fully processed, add its first part to `stack`
|
|
// for processing.
|
|
self.visit_stack.push(StackEntry {
|
|
bb: succ_bb,
|
|
lo: 0,
|
|
hi: first_lo - 1,
|
|
});
|
|
|
|
// And update this entry with 0, to represent the
|
|
// whole BB being processed.
|
|
first_lo = 0;
|
|
}
|
|
} else {
|
|
// succ_bb hasn't been seen before. Add it to
|
|
// `stack` for processing.
|
|
self.visit_stack.push(StackEntry {
|
|
bb: succ_bb,
|
|
lo: 0,
|
|
hi: self.body[succ_bb].statements.len(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.visited.clear();
|
|
}
|
|
}
|
|
|
|
impl<'a, 'tcx> Borrows<'a, 'tcx> {
|
|
crate fn new(
|
|
tcx: TyCtxt<'tcx>,
|
|
body: &'a Body<'tcx>,
|
|
nonlexical_regioncx: &'a RegionInferenceContext<'tcx>,
|
|
borrow_set: &'a BorrowSet<'tcx>,
|
|
) -> Self {
|
|
let mut prec = OutOfScopePrecomputer::new(body, nonlexical_regioncx);
|
|
for (borrow_index, borrow_data) in borrow_set.iter_enumerated() {
|
|
let borrow_region = borrow_data.region.to_region_vid();
|
|
let location = borrow_data.reserve_location;
|
|
|
|
prec.precompute_borrows_out_of_scope(borrow_index, borrow_region, location);
|
|
}
|
|
|
|
Borrows {
|
|
tcx,
|
|
body,
|
|
borrow_set,
|
|
borrows_out_of_scope_at_location: prec.borrows_out_of_scope_at_location,
|
|
}
|
|
}
|
|
|
|
pub fn location(&self, idx: BorrowIndex) -> &Location {
|
|
&self.borrow_set[idx].reserve_location
|
|
}
|
|
|
|
/// Add all borrows to the kill set, if those borrows are out of scope at `location`.
|
|
/// That means they went out of a nonlexical scope
|
|
fn kill_loans_out_of_scope_at_location(
|
|
&self,
|
|
trans: &mut impl GenKill<BorrowIndex>,
|
|
location: Location,
|
|
) {
|
|
// NOTE: The state associated with a given `location`
|
|
// reflects the dataflow on entry to the statement.
|
|
// Iterate over each of the borrows that we've precomputed
|
|
// to have went out of scope at this location and kill them.
|
|
//
|
|
// We are careful always to call this function *before* we
|
|
// set up the gen-bits for the statement or
|
|
// terminator. That way, if the effect of the statement or
|
|
// terminator *does* introduce a new loan of the same
|
|
// region, then setting that gen-bit will override any
|
|
// potential kill introduced here.
|
|
if let Some(indices) = self.borrows_out_of_scope_at_location.get(&location) {
|
|
trans.kill_all(indices.iter().copied());
|
|
}
|
|
}
|
|
|
|
/// Kill any borrows that conflict with `place`.
|
|
fn kill_borrows_on_place(&self, trans: &mut impl GenKill<BorrowIndex>, place: Place<'tcx>) {
|
|
debug!("kill_borrows_on_place: place={:?}", place);
|
|
|
|
let other_borrows_of_local = self
|
|
.borrow_set
|
|
.local_map
|
|
.get(&place.local)
|
|
.into_iter()
|
|
.flat_map(|bs| bs.iter())
|
|
.copied();
|
|
|
|
// If the borrowed place is a local with no projections, all other borrows of this
|
|
// local must conflict. This is purely an optimization so we don't have to call
|
|
// `places_conflict` for every borrow.
|
|
if place.projection.is_empty() {
|
|
if !self.body.local_decls[place.local].is_ref_to_static() {
|
|
trans.kill_all(other_borrows_of_local);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given
|
|
// pair of array indices are unequal, so that when `places_conflict` returns true, we
|
|
// will be assured that two places being compared definitely denotes the same sets of
|
|
// locations.
|
|
let definitely_conflicting_borrows = other_borrows_of_local.filter(|&i| {
|
|
places_conflict(
|
|
self.tcx,
|
|
self.body,
|
|
self.borrow_set[i].borrowed_place,
|
|
place,
|
|
PlaceConflictBias::NoOverlap,
|
|
)
|
|
});
|
|
|
|
trans.kill_all(definitely_conflicting_borrows);
|
|
}
|
|
}
|
|
|
|
impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
|
|
type Domain = BitSet<BorrowIndex>;
|
|
|
|
const NAME: &'static str = "borrows";
|
|
|
|
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
|
// bottom = nothing is reserved or activated yet;
|
|
BitSet::new_empty(self.borrow_set.len() * 2)
|
|
}
|
|
|
|
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
|
|
// no borrows of code region_scopes have been taken prior to
|
|
// function execution, so this method has no effect.
|
|
}
|
|
}
|
|
|
|
impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
|
|
type Idx = BorrowIndex;
|
|
|
|
fn before_statement_effect(
|
|
&self,
|
|
trans: &mut impl GenKill<Self::Idx>,
|
|
_statement: &mir::Statement<'tcx>,
|
|
location: Location,
|
|
) {
|
|
self.kill_loans_out_of_scope_at_location(trans, location);
|
|
}
|
|
|
|
fn statement_effect(
|
|
&self,
|
|
trans: &mut impl GenKill<Self::Idx>,
|
|
stmt: &mir::Statement<'tcx>,
|
|
location: Location,
|
|
) {
|
|
match stmt.kind {
|
|
mir::StatementKind::Assign(box (lhs, ref rhs)) => {
|
|
if let mir::Rvalue::Ref(_, _, place) = *rhs {
|
|
if place.ignore_borrow(
|
|
self.tcx,
|
|
self.body,
|
|
&self.borrow_set.locals_state_at_exit,
|
|
) {
|
|
return;
|
|
}
|
|
let index = self.borrow_set.get_index_of(&location).unwrap_or_else(|| {
|
|
panic!("could not find BorrowIndex for location {:?}", location);
|
|
});
|
|
|
|
trans.gen(index);
|
|
}
|
|
|
|
// Make sure there are no remaining borrows for variables
|
|
// that are assigned over.
|
|
self.kill_borrows_on_place(trans, lhs);
|
|
}
|
|
|
|
mir::StatementKind::StorageDead(local) => {
|
|
// Make sure there are no remaining borrows for locals that
|
|
// are gone out of scope.
|
|
self.kill_borrows_on_place(trans, Place::from(local));
|
|
}
|
|
|
|
mir::StatementKind::FakeRead(..)
|
|
| mir::StatementKind::SetDiscriminant { .. }
|
|
| mir::StatementKind::Deinit(..)
|
|
| mir::StatementKind::StorageLive(..)
|
|
| mir::StatementKind::Retag { .. }
|
|
| mir::StatementKind::AscribeUserType(..)
|
|
| mir::StatementKind::Coverage(..)
|
|
| mir::StatementKind::CopyNonOverlapping(..)
|
|
| mir::StatementKind::Nop => {}
|
|
}
|
|
}
|
|
|
|
fn before_terminator_effect(
|
|
&self,
|
|
trans: &mut impl GenKill<Self::Idx>,
|
|
_terminator: &mir::Terminator<'tcx>,
|
|
location: Location,
|
|
) {
|
|
self.kill_loans_out_of_scope_at_location(trans, location);
|
|
}
|
|
|
|
fn terminator_effect(
|
|
&self,
|
|
trans: &mut impl GenKill<Self::Idx>,
|
|
terminator: &mir::Terminator<'tcx>,
|
|
_location: Location,
|
|
) {
|
|
if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind {
|
|
for op in operands {
|
|
if let mir::InlineAsmOperand::Out { place: Some(place), .. }
|
|
| mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op
|
|
{
|
|
self.kill_borrows_on_place(trans, place);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn call_return_effect(
|
|
&self,
|
|
_trans: &mut impl GenKill<Self::Idx>,
|
|
_block: mir::BasicBlock,
|
|
_return_places: CallReturnPlaces<'_, 'tcx>,
|
|
) {
|
|
}
|
|
}
|
|
|
|
impl DebugWithContext<Borrows<'_, '_>> for BorrowIndex {
|
|
fn fmt_with(&self, ctxt: &Borrows<'_, '_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{:?}", ctxt.location(*self))
|
|
}
|
|
}
|