Skip to content

Commit

Permalink
New ActiveBorrows dataflow for two-phase &mut; not yet borrowed-c…
Browse files Browse the repository at this point in the history
…hecked.

High-level picture: The old `Borrows` analysis is now called
`Reservations` (implemented as a newtype wrapper around `Borrows`);
this continues to compute whether a `Rvalue::Ref` can reach a
statement without an intervening `EndRegion`. In addition, we also
track what `Place` each such `Rvalue::Ref` was immediately assigned
to in a given borrow (yay for MIR-structural properties!).

The new `ActiveBorrows` analysis then tracks the initial use of any of
those assigned `Places` for a given borrow. I.e. a borrow becomes
"active" immediately after it starts being "used" in some way. (This
is conservative in the sense that we will treat a copy `x = y;` as a
use of `y`; in principle one might further delay activation in such
cases.)

The new `ActiveBorrows` analysis needs to take the `Reservations`
results as an initial input, because the reservation state influences
the gen/kill sets for `ActiveBorrows`. In particular, a use of `a`
activates a borrow `a = &b` if and only if there exists a path (in the
control flow graph) from the borrow to that use. So we need to know if
the borrow reaches a given use to know if it really gets a gen-bit or
not.

 * Incorporating the output from one dataflow analysis into the input
   of another required more changes to the infrastructure than I had
   expected, and even after those changes, the resulting code is still
   a bit subtle.

 * In particular, Since we need to know the intrablock reservation
   state, we need to dynamically update a bitvector for the
   reservations as we are also trying to compute the gen/kills
   bitvector for the active borrows.

 * The way I ended up deciding to do this (after also toying with at
   least two other designs) is to put both the reservation state and
   the active borrow state into a single bitvector. That is why we now
   have separate (but related) `BorrowIndex` and
   `ReserveOrActivateIndex`: each borrow index maps to a pair of
   neighboring reservation and activation indexes.

As noted above, these changes are solely adding the active borrows
dataflow analysis (and updating the existing code to cope with the
switch from `Borrows` to `Reservations`). The code to process the
bitvector in the borrow checker currently just skips over all of the
active borrow bits.

But atop this commit, one *can* observe the analysis results by
looking at the graphviz output, e.g. via

```rust
 #[rustc_mir(borrowck_graphviz_preflow="pre_two_phase.dot",
             borrowck_graphviz_postflow="post_two_phase.dot")]
```

Includes doc for `FindPlaceUses`, as well as `Reservations` and
`ActiveBorrows` structs, which are wrappers are the `Borrows` struct
that dictate which flow analysis should be performed.
  • Loading branch information
pnkfelix committed Dec 13, 2017
1 parent ef64ace commit ced5a70
Show file tree
Hide file tree
Showing 7 changed files with 552 additions and 151 deletions.
6 changes: 3 additions & 3 deletions src/librustc_mir/borrow_check/error_reporting.rs
Expand Up @@ -19,7 +19,7 @@ use std::rc::Rc;

use super::{MirBorrowckCtxt, Context};
use super::{InitializationRequiringAction, PrefixSet};
use dataflow::{BorrowData, Borrows, FlowAtLocation, MovingOutStatements};
use dataflow::{ActiveBorrows, BorrowData, FlowAtLocation, MovingOutStatements};
use dataflow::move_paths::MovePathIndex;
use util::borrowck_errors::{BorrowckErrors, Origin};

Expand Down Expand Up @@ -324,10 +324,10 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
_: Context,
borrow: &BorrowData<'tcx>,
drop_span: Span,
borrows: &Borrows<'cx, 'gcx, 'tcx>
borrows: &ActiveBorrows<'cx, 'gcx, 'tcx>
) {
let end_span = borrows.opt_region_end_span(&borrow.region);
let scope_tree = borrows.scope_tree();
let scope_tree = borrows.0.scope_tree();
let root_place = self.prefixes(&borrow.borrowed_place, PrefixSet::All).last().unwrap();

match root_place {
Expand Down
12 changes: 6 additions & 6 deletions src/librustc_mir/borrow_check/flows.rs
Expand Up @@ -17,13 +17,13 @@ use rustc::mir::{BasicBlock, Location};

use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
use dataflow::{EverInitializedLvals, MovingOutStatements};
use dataflow::{Borrows, FlowAtLocation, FlowsAtLocation};
use dataflow::{ActiveBorrows, FlowAtLocation, FlowsAtLocation};
use dataflow::move_paths::HasMoveData;
use std::fmt;

// (forced to be `pub` due to its use as an associated type below.)
pub struct Flows<'b, 'gcx: 'tcx, 'tcx: 'b> {
pub borrows: FlowAtLocation<Borrows<'b, 'gcx, 'tcx>>,
pub(crate) struct Flows<'b, 'gcx: 'tcx, 'tcx: 'b> {
pub borrows: FlowAtLocation<ActiveBorrows<'b, 'gcx, 'tcx>>,
pub inits: FlowAtLocation<MaybeInitializedLvals<'b, 'gcx, 'tcx>>,
pub uninits: FlowAtLocation<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>,
pub move_outs: FlowAtLocation<MovingOutStatements<'b, 'gcx, 'tcx>>,
Expand All @@ -32,7 +32,7 @@ pub struct Flows<'b, 'gcx: 'tcx, 'tcx: 'b> {

impl<'b, 'gcx, 'tcx> Flows<'b, 'gcx, 'tcx> {
pub fn new(
borrows: FlowAtLocation<Borrows<'b, 'gcx, 'tcx>>,
borrows: FlowAtLocation<ActiveBorrows<'b, 'gcx, 'tcx>>,
inits: FlowAtLocation<MaybeInitializedLvals<'b, 'gcx, 'tcx>>,
uninits: FlowAtLocation<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>,
move_outs: FlowAtLocation<MovingOutStatements<'b, 'gcx, 'tcx>>,
Expand Down Expand Up @@ -87,7 +87,7 @@ impl<'b, 'gcx, 'tcx> fmt::Display for Flows<'b, 'gcx, 'tcx> {
s.push_str(", ");
};
saw_one = true;
let borrow_data = &self.borrows.operator().borrows()[borrow];
let borrow_data = &self.borrows.operator().borrows()[borrow.borrow_index()];
s.push_str(&format!("{}", borrow_data));
});
s.push_str("] ");
Expand All @@ -99,7 +99,7 @@ impl<'b, 'gcx, 'tcx> fmt::Display for Flows<'b, 'gcx, 'tcx> {
s.push_str(", ");
};
saw_one = true;
let borrow_data = &self.borrows.operator().borrows()[borrow];
let borrow_data = &self.borrows.operator().borrows()[borrow.borrow_index()];
s.push_str(&format!("{}", borrow_data));
});
s.push_str("] ");
Expand Down
76 changes: 52 additions & 24 deletions src/librustc_mir/borrow_check/mod.rs
Expand Up @@ -30,11 +30,12 @@ use syntax_pos::Span;

use dataflow::{do_dataflow, DebugFormatted};
use dataflow::MoveDataParamEnv;
use dataflow::DataflowResultsConsumer;
use dataflow::{DataflowAnalysis, DataflowResultsConsumer};
use dataflow::{FlowAtLocation, FlowsAtLocation};
use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
use dataflow::{EverInitializedLvals, MovingOutStatements};
use dataflow::{BorrowData, BorrowIndex, Borrows};
use dataflow::{Borrows, BorrowData, ReserveOrActivateIndex};
use dataflow::{ActiveBorrows, Reservations};
use dataflow::move_paths::{IllegalMoveOriginKind, MoveError};
use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex};
use util::borrowck_errors::{BorrowckErrors, Origin};
Expand All @@ -48,6 +49,9 @@ use self::MutateMode::{JustWrite, WriteAndRead};
mod error_reporting;
mod flows;
mod prefixes;

use std::borrow::Cow;

pub(crate) mod nll;

pub fn provide(providers: &mut Providers) {
Expand Down Expand Up @@ -205,23 +209,6 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
};
let flow_inits = flow_inits; // remove mut

let flow_borrows = FlowAtLocation::new(do_dataflow(
tcx,
mir,
id,
&attributes,
&dead_unwinds,
Borrows::new(tcx, mir, opt_regioncx, def_id, body_id),
|bd, i| DebugFormatted::new(bd.location(i)),
));

let mut state = Flows::new(
flow_borrows,
flow_inits,
flow_uninits,
flow_move_outs,
flow_ever_inits,
);
let mut mbcx = MirBorrowckCtxt {
tcx: tcx,
mir: mir,
Expand All @@ -237,6 +224,44 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
storage_dead_or_drop_error_reported_s: FxHashSet(),
};

let borrows = Borrows::new(tcx, mir, opt_regioncx, def_id, body_id);
let flow_reservations = do_dataflow(
tcx,
mir,
id,
&attributes,
&dead_unwinds,
Reservations::new(borrows),
|rs, i| {
// In principle we could make the dataflow ensure that
// only reservation bits show up, and assert so here.
//
// In practice it is easier to be looser; in particular,
// it is okay for the kill-sets to hold activation bits.
DebugFormatted::new(&(i.kind(), rs.location(i)))
});
let flow_active_borrows = {
let reservations_on_entry = flow_reservations.0.sets.entry_set_state();
let reservations = flow_reservations.0.operator;
let a = DataflowAnalysis::new_with_entry_sets(mir,
&dead_unwinds,
Cow::Borrowed(reservations_on_entry),
ActiveBorrows::new(reservations));
let results = a.run(tcx,
id,
&attributes,
|ab, i| DebugFormatted::new(&(i.kind(), ab.location(i))));
FlowAtLocation::new(results)
};

let mut state = Flows::new(
flow_active_borrows,
flow_inits,
flow_uninits,
flow_move_outs,
flow_ever_inits,
);

mbcx.analyze_results(&mut state); // entry point for DataflowResultsConsumer

opt_closure_req
Expand Down Expand Up @@ -504,9 +529,8 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
let data = domain.borrows();
flow_state.borrows.with_elems_outgoing(|borrows| {
for i in borrows {
let borrow = &data[i];
let borrow = &data[i.borrow_index()];
let context = ContextKind::StorageDead.new(loc);

self.check_for_invalidation_at_exit(context, borrow, span, flow_state);
}
});
Expand Down Expand Up @@ -721,7 +745,8 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
WriteKind::StorageDeadOrDrop => {
error_reported = true;
this.report_borrowed_value_does_not_live_long_enough(
context, borrow, place_span.1, flow_state.borrows.operator());
context, borrow, place_span.1,
flow_state.borrows.operator());
}
WriteKind::Mutate => {
error_reported = true;
Expand Down Expand Up @@ -1778,7 +1803,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
flow_state: &Flows<'cx, 'gcx, 'tcx>,
mut op: F,
) where
F: FnMut(&mut Self, BorrowIndex, &BorrowData<'tcx>) -> Control,
F: FnMut(&mut Self, ReserveOrActivateIndex, &BorrowData<'tcx>) -> Control,
{
let (access, place) = access_place;

Expand All @@ -1790,7 +1815,10 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
// check for loan restricting path P being used. Accounts for
// borrows of P, P.a.b, etc.
for i in flow_state.borrows.elems_incoming() {
let borrowed = &data[i];
// FIXME for now, just skip the activation state.
if i.is_activation() { continue }

let borrowed = &data[i.borrow_index()];

if self.places_conflict(&borrowed.borrowed_place, place, access) {
let ctrl = op(self, i, borrowed);
Expand Down
6 changes: 2 additions & 4 deletions src/librustc_mir/dataflow/at_location.rs
Expand Up @@ -121,9 +121,8 @@ impl<BD> FlowsAtLocation for FlowAtLocation<BD>
fn reconstruct_statement_effect(&mut self, loc: Location) {
self.stmt_gen.reset_to_empty();
self.stmt_kill.reset_to_empty();
let mut ignored = IdxSetBuf::new_empty(0);
let mut sets = BlockSets {
on_entry: &mut ignored,
on_entry: &mut self.curr_state,
gen_set: &mut self.stmt_gen,
kill_set: &mut self.stmt_kill,
};
Expand All @@ -135,9 +134,8 @@ impl<BD> FlowsAtLocation for FlowAtLocation<BD>
fn reconstruct_terminator_effect(&mut self, loc: Location) {
self.stmt_gen.reset_to_empty();
self.stmt_kill.reset_to_empty();
let mut ignored = IdxSetBuf::new_empty(0);
let mut sets = BlockSets {
on_entry: &mut ignored,
on_entry: &mut self.curr_state,
gen_set: &mut self.stmt_gen,
kill_set: &mut self.stmt_kill,
};
Expand Down

0 comments on commit ced5a70

Please sign in to comment.