2017-12-07 09:00:26 -06:00
|
|
|
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
//! A nice wrapper to consume dataflow results at several CFG
|
|
|
|
//! locations.
|
|
|
|
|
|
|
|
use rustc::mir::{BasicBlock, Location};
|
|
|
|
use rustc_data_structures::indexed_set::{self, IdxSetBuf};
|
|
|
|
use rustc_data_structures::indexed_vec::Idx;
|
|
|
|
|
|
|
|
use dataflow::{BitDenotation, BlockSets, DataflowResults};
|
|
|
|
use dataflow::move_paths::{HasMoveData, MovePathIndex};
|
|
|
|
|
2017-12-01 09:02:15 -06:00
|
|
|
use std::iter;
|
|
|
|
|
2017-12-07 09:00:26 -06:00
|
|
|
/// A trait for "cartesian products" of multiple FlowAtLocation.
|
|
|
|
///
|
|
|
|
/// There's probably a way to auto-impl this, but I think
|
|
|
|
/// it is cleaner to have manual visitor impls.
|
|
|
|
pub trait FlowsAtLocation {
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Reset the state bitvector to represent the entry to block `bb`.
|
2017-12-07 09:00:26 -06:00
|
|
|
fn reset_to_entry_of(&mut self, bb: BasicBlock);
|
|
|
|
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Build gen + kill sets for statement at `loc`.
|
|
|
|
///
|
|
|
|
/// Note that invoking this method alone does not change the
|
|
|
|
/// `curr_state` -- you must invoke `apply_local_effect`
|
|
|
|
/// afterwards.
|
2017-12-07 09:00:26 -06:00
|
|
|
fn reconstruct_statement_effect(&mut self, loc: Location);
|
|
|
|
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Build gen + kill sets for terminator for `loc`.
|
|
|
|
///
|
|
|
|
/// Note that invoking this method alone does not change the
|
|
|
|
/// `curr_state` -- you must invoke `apply_local_effect`
|
|
|
|
/// afterwards.
|
2017-12-07 09:00:26 -06:00
|
|
|
fn reconstruct_terminator_effect(&mut self, loc: Location);
|
|
|
|
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Apply current gen + kill sets to `flow_state`.
|
|
|
|
///
|
|
|
|
/// (`loc` parameters can be ignored if desired by
|
|
|
|
/// client. For the terminator, the `stmt_idx` will be the number
|
|
|
|
/// of statements in the block.)
|
2017-12-07 09:00:26 -06:00
|
|
|
fn apply_local_effect(&mut self, loc: Location);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents the state of dataflow at a particular
|
|
|
|
/// CFG location, both before and after it is
|
|
|
|
/// executed.
|
2017-12-03 11:49:08 -06:00
|
|
|
///
|
|
|
|
/// Data flow results are typically computed only as basic block
|
|
|
|
/// boundaries. A `FlowInProgress` allows you to reconstruct the
|
|
|
|
/// effects at any point in the control-flow graph by starting with
|
|
|
|
/// the state at the start of the basic block (`reset_to_entry_of`)
|
|
|
|
/// and then replaying the effects of statements and terminators
|
|
|
|
/// (e.g. via `reconstruct_statement_effect` and
|
|
|
|
/// `reconstruct_terminator_effect`; don't forget to call
|
|
|
|
/// `apply_local_effect`).
|
2017-12-07 09:00:26 -06:00
|
|
|
pub struct FlowAtLocation<BD>
|
|
|
|
where
|
|
|
|
BD: BitDenotation,
|
|
|
|
{
|
|
|
|
base_results: DataflowResults<BD>,
|
|
|
|
curr_state: IdxSetBuf<BD::Idx>,
|
|
|
|
stmt_gen: IdxSetBuf<BD::Idx>,
|
|
|
|
stmt_kill: IdxSetBuf<BD::Idx>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<BD> FlowAtLocation<BD>
|
|
|
|
where
|
|
|
|
BD: BitDenotation,
|
|
|
|
{
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Iterate over each bit set in the current state.
|
2017-12-07 09:00:26 -06:00
|
|
|
pub fn each_state_bit<F>(&self, f: F)
|
|
|
|
where
|
|
|
|
F: FnMut(BD::Idx),
|
|
|
|
{
|
|
|
|
self.curr_state
|
|
|
|
.each_bit(self.base_results.operator().bits_per_block(), f)
|
|
|
|
}
|
|
|
|
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Iterate over each `gen` bit in the current effect (invoke
|
|
|
|
/// `reconstruct_statement_effect` or
|
|
|
|
/// `reconstruct_terminator_effect` first).
|
2017-12-07 09:00:26 -06:00
|
|
|
pub fn each_gen_bit<F>(&self, f: F)
|
|
|
|
where
|
|
|
|
F: FnMut(BD::Idx),
|
|
|
|
{
|
|
|
|
self.stmt_gen
|
|
|
|
.each_bit(self.base_results.operator().bits_per_block(), f)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new(results: DataflowResults<BD>) -> Self {
|
|
|
|
let bits_per_block = results.sets().bits_per_block();
|
|
|
|
let curr_state = IdxSetBuf::new_empty(bits_per_block);
|
|
|
|
let stmt_gen = IdxSetBuf::new_empty(bits_per_block);
|
|
|
|
let stmt_kill = IdxSetBuf::new_empty(bits_per_block);
|
|
|
|
FlowAtLocation {
|
|
|
|
base_results: results,
|
|
|
|
curr_state: curr_state,
|
|
|
|
stmt_gen: stmt_gen,
|
|
|
|
stmt_kill: stmt_kill,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Access the underlying operator.
|
2017-12-07 09:00:26 -06:00
|
|
|
pub fn operator(&self) -> &BD {
|
|
|
|
self.base_results.operator()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn contains(&self, x: &BD::Idx) -> bool {
|
|
|
|
self.curr_state.contains(x)
|
|
|
|
}
|
|
|
|
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Returns an iterator over the elements present in the current state.
|
2017-12-01 09:02:15 -06:00
|
|
|
pub fn elems_incoming(&self) -> iter::Peekable<indexed_set::Elems<BD::Idx>> {
|
2017-12-07 09:00:26 -06:00
|
|
|
let univ = self.base_results.sets().bits_per_block();
|
2017-12-01 09:02:15 -06:00
|
|
|
self.curr_state.elems(univ).peekable()
|
2017-12-07 09:00:26 -06:00
|
|
|
}
|
|
|
|
|
2017-12-03 11:49:08 -06:00
|
|
|
/// Creates a clone of the current state and applies the local
|
|
|
|
/// effects to the clone (leaving the state of self intact).
|
|
|
|
/// Invokes `f` with an iterator over the resulting state.
|
2017-12-07 09:00:26 -06:00
|
|
|
pub fn with_elems_outgoing<F>(&self, f: F)
|
|
|
|
where
|
|
|
|
F: FnOnce(indexed_set::Elems<BD::Idx>),
|
|
|
|
{
|
|
|
|
let mut curr_state = self.curr_state.clone();
|
|
|
|
curr_state.union(&self.stmt_gen);
|
|
|
|
curr_state.subtract(&self.stmt_kill);
|
|
|
|
let univ = self.base_results.sets().bits_per_block();
|
|
|
|
f(curr_state.elems(univ));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<BD> FlowsAtLocation for FlowAtLocation<BD>
|
|
|
|
where BD: BitDenotation
|
|
|
|
{
|
|
|
|
fn reset_to_entry_of(&mut self, bb: BasicBlock) {
|
|
|
|
(*self.curr_state).clone_from(self.base_results.sets().on_entry_set_for(bb.index()));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn reconstruct_statement_effect(&mut self, loc: Location) {
|
|
|
|
self.stmt_gen.reset_to_empty();
|
|
|
|
self.stmt_kill.reset_to_empty();
|
2017-12-23 16:45:53 -06:00
|
|
|
{
|
|
|
|
let mut sets = BlockSets {
|
|
|
|
on_entry: &mut self.curr_state,
|
|
|
|
gen_set: &mut self.stmt_gen,
|
|
|
|
kill_set: &mut self.stmt_kill,
|
|
|
|
};
|
|
|
|
self.base_results
|
|
|
|
.operator()
|
|
|
|
.before_statement_effect(&mut sets, loc);
|
|
|
|
}
|
|
|
|
self.apply_local_effect(loc);
|
|
|
|
|
2017-12-07 09:00:26 -06:00
|
|
|
let mut sets = BlockSets {
|
New `ActiveBorrows` dataflow for two-phase `&mut`; not yet borrowed-checked.
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.
2017-12-01 05:32:51 -06:00
|
|
|
on_entry: &mut self.curr_state,
|
2017-12-07 09:00:26 -06:00
|
|
|
gen_set: &mut self.stmt_gen,
|
|
|
|
kill_set: &mut self.stmt_kill,
|
|
|
|
};
|
|
|
|
self.base_results
|
|
|
|
.operator()
|
|
|
|
.statement_effect(&mut sets, loc);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn reconstruct_terminator_effect(&mut self, loc: Location) {
|
|
|
|
self.stmt_gen.reset_to_empty();
|
|
|
|
self.stmt_kill.reset_to_empty();
|
2017-12-23 16:45:53 -06:00
|
|
|
{
|
|
|
|
let mut sets = BlockSets {
|
|
|
|
on_entry: &mut self.curr_state,
|
|
|
|
gen_set: &mut self.stmt_gen,
|
|
|
|
kill_set: &mut self.stmt_kill,
|
|
|
|
};
|
|
|
|
self.base_results
|
|
|
|
.operator()
|
|
|
|
.before_terminator_effect(&mut sets, loc);
|
|
|
|
}
|
|
|
|
self.apply_local_effect(loc);
|
|
|
|
|
2017-12-07 09:00:26 -06:00
|
|
|
let mut sets = BlockSets {
|
New `ActiveBorrows` dataflow for two-phase `&mut`; not yet borrowed-checked.
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.
2017-12-01 05:32:51 -06:00
|
|
|
on_entry: &mut self.curr_state,
|
2017-12-07 09:00:26 -06:00
|
|
|
gen_set: &mut self.stmt_gen,
|
|
|
|
kill_set: &mut self.stmt_kill,
|
|
|
|
};
|
|
|
|
self.base_results
|
|
|
|
.operator()
|
|
|
|
.terminator_effect(&mut sets, loc);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn apply_local_effect(&mut self, _loc: Location) {
|
|
|
|
self.curr_state.union(&self.stmt_gen);
|
|
|
|
self.curr_state.subtract(&self.stmt_kill);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl<'tcx, T> FlowAtLocation<T>
|
|
|
|
where
|
|
|
|
T: HasMoveData<'tcx> + BitDenotation<Idx = MovePathIndex>,
|
|
|
|
{
|
|
|
|
pub fn has_any_child_of(&self, mpi: T::Idx) -> Option<T::Idx> {
|
|
|
|
let move_data = self.operator().move_data();
|
|
|
|
|
|
|
|
let mut todo = vec![mpi];
|
|
|
|
let mut push_siblings = false; // don't look at siblings of original `mpi`.
|
|
|
|
while let Some(mpi) = todo.pop() {
|
|
|
|
if self.contains(&mpi) {
|
|
|
|
return Some(mpi);
|
|
|
|
}
|
|
|
|
let move_path = &move_data.move_paths[mpi];
|
|
|
|
if let Some(child) = move_path.first_child {
|
|
|
|
todo.push(child);
|
|
|
|
}
|
|
|
|
if push_siblings {
|
|
|
|
if let Some(sibling) = move_path.next_sibling {
|
|
|
|
todo.push(sibling);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// after we've processed the original `mpi`, we should
|
|
|
|
// always traverse the siblings of any of its
|
|
|
|
// children.
|
|
|
|
push_siblings = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|