blob: a86fcb30f4d36ca97bbf7c94fec381eaf69e9834 [file] [log] [blame]
use crate::borrow_check::borrow_set::{BorrowSet, BorrowData};
use crate::borrow_check::place_ext::PlaceExt;
use rustc::mir::{self, Location, Place, PlaceBase, Body};
use rustc::ty::{self, TyCtxt};
use rustc::ty::RegionVid;
use rustc_data_structures::bit_set::BitSet;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
use crate::dataflow::{BitDenotation, BottomValue, GenKillSet};
use crate::borrow_check::nll::region_infer::RegionInferenceContext;
use crate::borrow_check::nll::ToRegionVid;
use crate::borrow_check::places_conflict;
use std::rc::Rc;
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>,
param_env: ty::ParamEnv<'tcx>,
borrow_set: Rc<BorrowSet<'tcx>>,
borrows_out_of_scope_at_location: FxHashMap<Location, Vec<BorrowIndex>>,
/// NLL region inference context with which NLL queries should be resolved
_nonlexical_regioncx: Rc<RegionInferenceContext<'tcx>>,
}
struct StackEntry {
bb: mir::BasicBlock,
lo: usize,
hi: usize,
first_part_only: bool
}
fn precompute_borrows_out_of_scope<'tcx>(
body: &Body<'tcx>,
regioncx: &Rc<RegionInferenceContext<'tcx>>,
borrows_out_of_scope_at_location: &mut FxHashMap<Location, Vec<BorrowIndex>>,
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.
let mut visited = FxHashMap::default();
visited.insert(location.block, location.statement_index);
let mut stack = vec![];
stack.push(StackEntry {
bb: location.block,
lo: location.statement_index,
hi: body[location.block].statements.len(),
first_part_only: false,
});
while let Some(StackEntry { bb, lo, hi, first_part_only }) = stack.pop() {
let mut finished_early = first_part_only;
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 !regioncx.region_contains(borrow_region, location) {
debug!("borrow {:?} gets killed at {:?}", borrow_index, location);
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 = &body[bb];
assert!(hi == bb_data.statements.len());
for &succ_bb in bb_data.terminator.as_ref().unwrap().successors() {
visited.entry(succ_bb)
.and_modify(|lo| {
// `succ_bb` has been seen before. If it wasn't
// fully processed, add its first part to `stack`
// for processing.
if *lo > 0 {
stack.push(StackEntry {
bb: succ_bb,
lo: 0,
hi: *lo - 1,
first_part_only: true,
});
}
// And update this entry with 0, to represent the
// whole BB being processed.
*lo = 0;
})
.or_insert_with(|| {
// succ_bb hasn't been seen before. Add it to
// `stack` for processing.
stack.push(StackEntry {
bb: succ_bb,
lo: 0,
hi: body[succ_bb].statements.len(),
first_part_only: false,
});
// Insert 0 for this BB, to represent the whole BB
// being processed.
0
});
}
}
}
}
impl<'a, 'tcx> Borrows<'a, 'tcx> {
crate fn new(
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
param_env: ty::ParamEnv<'tcx>,
nonlexical_regioncx: Rc<RegionInferenceContext<'tcx>>,
borrow_set: &Rc<BorrowSet<'tcx>>,
) -> Self {
let mut borrows_out_of_scope_at_location = FxHashMap::default();
for (borrow_index, borrow_data) in borrow_set.borrows.iter_enumerated() {
let borrow_region = borrow_data.region.to_region_vid();
let location = borrow_set.borrows[borrow_index].reserve_location;
precompute_borrows_out_of_scope(body, &nonlexical_regioncx,
&mut borrows_out_of_scope_at_location,
borrow_index, borrow_region, location);
}
Borrows {
tcx: tcx,
body: body,
param_env,
borrow_set: borrow_set.clone(),
borrows_out_of_scope_at_location,
_nonlexical_regioncx: nonlexical_regioncx,
}
}
crate fn borrows(&self) -> &IndexVec<BorrowIndex, BorrowData<'tcx>> { &self.borrow_set.borrows }
pub fn location(&self, idx: BorrowIndex) -> &Location {
&self.borrow_set.borrows[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 GenKillSet<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
// termanator. 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);
}
}
/// Kill any borrows that conflict with `place`.
fn kill_borrows_on_place(
&self,
trans: &mut GenKillSet<BorrowIndex>,
place: &Place<'tcx>
) {
debug!("kill_borrows_on_place: place={:?}", place);
if let PlaceBase::Local(local) = place.base {
let other_borrows_of_local = self
.borrow_set
.local_map
.get(&local)
.into_iter()
.flat_map(|bs| bs.into_iter());
// 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() {
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::places_conflict(
self.tcx,
self.param_env,
self.body,
&self.borrow_set.borrows[i].borrowed_place,
place,
places_conflict::PlaceConflictBias::NoOverlap)
});
trans.kill_all(definitely_conflicting_borrows);
}
}
}
impl<'a, 'tcx> BitDenotation<'tcx> for Borrows<'a, 'tcx> {
type Idx = BorrowIndex;
fn name() -> &'static str { "borrows" }
fn bits_per_block(&self) -> usize {
self.borrow_set.borrows.len() * 2
}
fn start_block_effect(&self, _entry_set: &mut BitSet<Self::Idx>) {
// no borrows of code region_scopes have been taken prior to
// function execution, so this method has no effect.
}
fn before_statement_effect(&self,
trans: &mut GenKillSet<Self::Idx>,
location: Location) {
debug!("Borrows::before_statement_effect trans: {:?} location: {:?}",
trans, location);
self.kill_loans_out_of_scope_at_location(trans, location);
}
fn statement_effect(&self,
trans: &mut GenKillSet<Self::Idx>,
location: Location) {
debug!("Borrows::statement_effect: trans={:?} location={:?}",
trans, location);
let block = &self.body.basic_blocks().get(location.block).unwrap_or_else(|| {
panic!("could not find block at location {:?}", location);
});
let stmt = block.statements.get(location.statement_index).unwrap_or_else(|| {
panic!("could not find statement at location {:?}");
});
debug!("Borrows::statement_effect: stmt={:?}", stmt);
match stmt.kind {
mir::StatementKind::Assign(box(ref lhs, ref rhs)) => {
if let mir::Rvalue::Ref(_, _, ref place) = *rhs {
if place.ignore_borrow(
self.tcx,
self.body,
&self.borrow_set.locals_state_at_exit,
) {
return;
}
let index = self.borrow_set.location_map.get(&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::InlineAsm(ref asm) => {
for (output, kind) in asm.outputs.iter().zip(&asm.asm.outputs) {
if !kind.is_indirect && !kind.is_rw {
self.kill_borrows_on_place(trans, output);
}
}
}
mir::StatementKind::FakeRead(..) |
mir::StatementKind::SetDiscriminant { .. } |
mir::StatementKind::StorageLive(..) |
mir::StatementKind::Retag { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => {}
}
}
fn before_terminator_effect(&self,
trans: &mut GenKillSet<Self::Idx>,
location: Location) {
debug!("Borrows::before_terminator_effect: trans={:?} location={:?}",
trans, location);
self.kill_loans_out_of_scope_at_location(trans, location);
}
fn terminator_effect(&self,
_: &mut GenKillSet<Self::Idx>,
_: Location) {}
fn propagate_call_return(
&self,
_in_out: &mut BitSet<BorrowIndex>,
_call_bb: mir::BasicBlock,
_dest_bb: mir::BasicBlock,
_dest_place: &mir::Place<'tcx>,
) {
}
}
impl<'a, 'tcx> BottomValue for Borrows<'a, 'tcx> {
/// bottom = nothing is reserved or activated yet;
const BOTTOM_VALUE: bool = false;
}