Revised mir-dataflow.
Incorporates many fixes contributed by arielb1. ---- revise borrowck::mir::dataflow code to allow varying domain for bitvectors. This particular code implements the `BitDenotation` trait for three analyses: * `MovingOutStatements`, which, like `borrowck::move_data`, maps each bit-index to a move instruction, and a 1 means "the effect of this move reaches this point" (and the assigned l-value, if a scoped declaration, is still in scope). * `MaybeInitializedLvals`, which maps each bit-index to an l-value. A 1 means "there exists a control flow path to this point that initializes the associated l-value." * `MaybeUninitializedLvals`, which maps each bit-index to an l-value A 1 means "there exists a control flow path to this point that de-initializes the associated l-value." ---- Revised `graphviz` dataflow-rendering support in `borrowck::mir`. One big difference is that this code is now parameterized over the `BitDenotation`, so that it can be used to render dataflow results independent of how the dataflow bitvectors are interpreted; see where reference to `MoveOut` is replaced by the type parameter `D`. ---- Factor out routine to query subattributes in `#[rustc_mir(..)]`. (Later commits build upon this for some unit testing and instrumentation.) ---- thread through a tcx so that I can query types of lvalues as part of analysis. ---- Revised `BitDenotation::Ctxt`, allowing variation beyond `MoveData`. The main motivation is to ease threading through a `TyCtxt`. (In hindsight it might have been better to instead attach the `TyCtxt` to each of the different dataflow implementations, but that would require e.g. switching away from having a `Default` impl, so I am leaving that experiment for another time.)
This commit is contained in:
parent
6c72c5fa88
commit
c73f3517a2
@ -226,7 +226,8 @@ pub struct UpvarDecl {
|
||||
/// list of the `Mir`.
|
||||
///
|
||||
/// (We use a `u32` internally just to save memory.)
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
|
||||
RustcEncodable, RustcDecodable)]
|
||||
pub struct BasicBlock(u32);
|
||||
|
||||
impl BasicBlock {
|
||||
|
@ -18,7 +18,7 @@ use ty::TyCtxt;
|
||||
use syntax::ast::NodeId;
|
||||
|
||||
/// Where a specific Mir comes from.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum MirSource {
|
||||
/// Functions and methods.
|
||||
Fn(NodeId),
|
||||
|
@ -1,629 +0,0 @@
|
||||
// Copyright 2012-2016 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.
|
||||
|
||||
use syntax::attr::AttrMetaMethods;
|
||||
|
||||
use rustc::ty::TyCtxt;
|
||||
use rustc::mir::repr::{self, Mir};
|
||||
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::usize;
|
||||
|
||||
use super::MirBorrowckCtxt;
|
||||
use super::gather_moves::{Location, MoveData, MovePathData, MovePathIndex, MoveOutIndex, PathMap};
|
||||
use super::graphviz;
|
||||
use bitslice::BitSlice; // adds set_bit/get_bit to &[usize] bitvector rep.
|
||||
|
||||
pub trait Dataflow {
|
||||
fn dataflow(&mut self);
|
||||
}
|
||||
|
||||
impl<'b, 'a: 'b, 'tcx: 'a> Dataflow for MirBorrowckCtxt<'b, 'a, 'tcx> {
|
||||
fn dataflow(&mut self) {
|
||||
self.build_gen_and_kill_sets();
|
||||
self.pre_dataflow_instrumentation().unwrap();
|
||||
self.propagate();
|
||||
self.post_dataflow_instrumentation().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct PropagationContext<'c, 'b: 'c, 'a: 'b, 'tcx: 'a, OnReturn>
|
||||
where OnReturn: Fn(&MoveData, &mut [usize], &repr::Lvalue)
|
||||
{
|
||||
mbcx: &'c mut MirBorrowckCtxt<'b, 'a, 'tcx>,
|
||||
changed: bool,
|
||||
on_return: OnReturn
|
||||
}
|
||||
|
||||
impl<'b, 'a: 'b, 'tcx: 'a> MirBorrowckCtxt<'b, 'a, 'tcx> {
|
||||
fn propagate(&mut self) {
|
||||
let mut temp = vec![0; self.flow_state.sets.words_per_block];
|
||||
let mut propcx = PropagationContext {
|
||||
mbcx: &mut *self,
|
||||
changed: true,
|
||||
on_return: |move_data, in_out, dest_lval| {
|
||||
let move_path_index = move_data.rev_lookup.find(dest_lval);
|
||||
on_all_children_bits(in_out,
|
||||
&move_data.path_map,
|
||||
&move_data.move_paths,
|
||||
move_path_index,
|
||||
&|in_out, mpi| {
|
||||
in_out.clear_bit(mpi.idx());
|
||||
});
|
||||
},
|
||||
};
|
||||
while propcx.changed {
|
||||
propcx.changed = false;
|
||||
propcx.reset(&mut temp);
|
||||
propcx.walk_cfg(&mut temp);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_gen_and_kill_sets(&mut self) {
|
||||
// First we need to build the gen- and kill-sets. The
|
||||
// gather_moves information provides a high-level mapping from
|
||||
// mir-locations to the MoveOuts (and those correspond
|
||||
// directly to gen-sets here). But we still need to figure out
|
||||
// the kill-sets.
|
||||
|
||||
let move_data = &self.flow_state.operator;
|
||||
let move_paths = &move_data.move_paths;
|
||||
let loc_map = &move_data.loc_map;
|
||||
let path_map = &move_data.path_map;
|
||||
let rev_lookup = &move_data.rev_lookup;
|
||||
|
||||
for bb in self.mir.all_basic_blocks() {
|
||||
let &repr::BasicBlockData { ref statements,
|
||||
ref terminator,
|
||||
is_cleanup: _ } =
|
||||
self.mir.basic_block_data(bb);
|
||||
|
||||
let mut sets = self.flow_state.sets.for_block(bb.index());
|
||||
for (j, stmt) in statements.iter().enumerate() {
|
||||
let loc = Location { block: bb, index: j };
|
||||
debug!("stmt {:?} at loc {:?} moves out of move_indexes {:?}",
|
||||
stmt, loc, &loc_map[loc]);
|
||||
for move_index in &loc_map[loc] {
|
||||
// Every path deinitialized by a *particular move*
|
||||
// has corresponding bit, "gen'ed" (i.e. set)
|
||||
// here, in dataflow vector
|
||||
zero_to_one(&mut sets.gen_set, *move_index);
|
||||
}
|
||||
match stmt.kind {
|
||||
repr::StatementKind::Assign(ref lvalue, _) => {
|
||||
// assigning into this `lvalue` kills all
|
||||
// MoveOuts from it, and *also* all MoveOuts
|
||||
// for children and associated fragment sets.
|
||||
let move_path_index = rev_lookup.find(lvalue);
|
||||
|
||||
on_all_children_bits(sets.kill_set,
|
||||
path_map,
|
||||
move_paths,
|
||||
move_path_index,
|
||||
&|kill_set, mpi| {
|
||||
kill_set.set_bit(mpi.idx());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let loc = Location { block: bb, index: statements.len() };
|
||||
debug!("terminator {:?} at loc {:?} moves out of move_indexes {:?}",
|
||||
terminator, loc, &loc_map[loc]);
|
||||
for move_index in &loc_map[loc] {
|
||||
zero_to_one(&mut sets.gen_set, *move_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn zero_to_one(gen_set: &mut [usize], move_index: MoveOutIndex) {
|
||||
let retval = gen_set.set_bit(move_index.idx());
|
||||
assert!(retval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_all_children_bits<Each>(set: &mut [usize],
|
||||
path_map: &PathMap,
|
||||
move_paths: &MovePathData,
|
||||
move_path_index: MovePathIndex,
|
||||
each_child: &Each)
|
||||
where Each: Fn(&mut [usize], MoveOutIndex)
|
||||
{
|
||||
// 1. invoke `each_child` callback for all moves that directly
|
||||
// influence path for `move_path_index`
|
||||
for move_index in &path_map[move_path_index] {
|
||||
each_child(set, *move_index);
|
||||
}
|
||||
|
||||
// 2. for each child of the path (that is named in this
|
||||
// function), recur.
|
||||
//
|
||||
// (Unnamed children are irrelevant to dataflow; by
|
||||
// definition they have no associated moves.)
|
||||
let mut next_child_index = move_paths[move_path_index].first_child;
|
||||
while let Some(child_index) = next_child_index {
|
||||
on_all_children_bits(set, path_map, move_paths, child_index, each_child);
|
||||
next_child_index = move_paths[child_index].next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c, 'b: 'c, 'a: 'b, 'tcx: 'a, OnReturn> PropagationContext<'c, 'b, 'a, 'tcx, OnReturn>
|
||||
where OnReturn: Fn(&MoveData, &mut [usize], &repr::Lvalue)
|
||||
{
|
||||
fn reset(&mut self, bits: &mut [usize]) {
|
||||
let e = if MoveData::initial_value() {usize::MAX} else {0};
|
||||
for b in bits {
|
||||
*b = e;
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_cfg(&mut self, in_out: &mut [usize]) {
|
||||
let &mut MirBorrowckCtxt { ref mir, ref mut flow_state, .. } = self.mbcx;
|
||||
for (idx, bb) in mir.basic_blocks.iter().enumerate() {
|
||||
{
|
||||
let sets = flow_state.sets.for_block(idx);
|
||||
debug_assert!(in_out.len() == sets.on_entry.len());
|
||||
in_out.clone_from_slice(sets.on_entry);
|
||||
bitwise(in_out, sets.gen_set, &Union);
|
||||
bitwise(in_out, sets.kill_set, &Subtract);
|
||||
}
|
||||
flow_state.propagate_bits_into_graph_successors_of(in_out,
|
||||
&mut self.changed,
|
||||
bb,
|
||||
&self.on_return);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, 'a: 'b, 'tcx: 'a> MirBorrowckCtxt<'b, 'a, 'tcx> {
|
||||
fn pre_dataflow_instrumentation(&self) -> io::Result<()> {
|
||||
self.if_attr_meta_name_found(
|
||||
"borrowck_graphviz_preflow",
|
||||
|this, path: &str| {
|
||||
graphviz::print_borrowck_graph_to(this, "preflow", path)
|
||||
})
|
||||
}
|
||||
|
||||
fn post_dataflow_instrumentation(&self) -> io::Result<()> {
|
||||
self.if_attr_meta_name_found(
|
||||
"borrowck_graphviz_postflow",
|
||||
|this, path: &str| {
|
||||
graphviz::print_borrowck_graph_to(this, "postflow", path)
|
||||
})
|
||||
}
|
||||
|
||||
fn if_attr_meta_name_found<F>(&self,
|
||||
name: &str,
|
||||
callback: F) -> io::Result<()>
|
||||
where F: for <'aa, 'bb> FnOnce(&'aa Self, &'bb str) -> io::Result<()>
|
||||
{
|
||||
for attr in self.attributes {
|
||||
if attr.check_name("rustc_mir") {
|
||||
let items = attr.meta_item_list();
|
||||
for item in items.iter().flat_map(|l| l.iter()) {
|
||||
if item.check_name(name) {
|
||||
if let Some(s) = item.value_str() {
|
||||
return callback(self, &s);
|
||||
} else {
|
||||
self.bcx.tcx.sess.span_err(
|
||||
item.span,
|
||||
&format!("{} attribute requires a path", item.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps each block to a set of bits
|
||||
#[derive(Clone, Debug)]
|
||||
struct Bits {
|
||||
bits: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Bits {
|
||||
fn new(init_word: usize, num_words: usize) -> Self {
|
||||
Bits { bits: vec![init_word; num_words] }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataflowState<O: BitDenotation>
|
||||
{
|
||||
/// All the sets for the analysis. (Factored into its
|
||||
/// own structure so that we can borrow it mutably
|
||||
/// on its own separate from other fields.)
|
||||
pub sets: AllSets,
|
||||
|
||||
/// operator used to initialize, combine, and interpret bits.
|
||||
operator: O,
|
||||
}
|
||||
|
||||
pub struct AllSets {
|
||||
/// Analysis bitwidth for each block.
|
||||
bits_per_block: usize,
|
||||
|
||||
/// Number of words associated with each block entry
|
||||
/// equal to bits_per_block / usize::BITS, rounded up.
|
||||
words_per_block: usize,
|
||||
|
||||
/// For each block, bits generated by executing the statements in
|
||||
/// the block. (For comparison, the Terminator for each block is
|
||||
/// handled in a flow-specific manner during propagation.)
|
||||
gen_sets: Bits,
|
||||
|
||||
/// For each block, bits killed by executing the statements in the
|
||||
/// block. (For comparison, the Terminator for each block is
|
||||
/// handled in a flow-specific manner during propagation.)
|
||||
kill_sets: Bits,
|
||||
|
||||
/// For each block, bits valid on entry to the block.
|
||||
on_entry_sets: Bits,
|
||||
}
|
||||
|
||||
pub struct BlockSets<'a> {
|
||||
on_entry: &'a mut [usize],
|
||||
gen_set: &'a mut [usize],
|
||||
kill_set: &'a mut [usize],
|
||||
}
|
||||
|
||||
impl AllSets {
|
||||
pub fn bits_per_block(&self) -> usize { self.bits_per_block }
|
||||
pub fn bytes_per_block(&self) -> usize { (self.bits_per_block + 7) / 8 }
|
||||
pub fn for_block(&mut self, block_idx: usize) -> BlockSets {
|
||||
let offset = self.words_per_block * block_idx;
|
||||
let range = offset..(offset + self.words_per_block);
|
||||
BlockSets {
|
||||
on_entry: &mut self.on_entry_sets.bits[range.clone()],
|
||||
gen_set: &mut self.gen_sets.bits[range.clone()],
|
||||
kill_set: &mut self.kill_sets.bits[range],
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_set_for<'a>(&self, sets: &'a Bits, block_idx: usize) -> &'a [usize] {
|
||||
let offset = self.words_per_block * block_idx;
|
||||
&sets.bits[offset..(offset + self.words_per_block)]
|
||||
}
|
||||
pub fn gen_set_for(&self, block_idx: usize) -> &[usize] {
|
||||
self.lookup_set_for(&self.gen_sets, block_idx)
|
||||
}
|
||||
pub fn kill_set_for(&self, block_idx: usize) -> &[usize] {
|
||||
self.lookup_set_for(&self.kill_sets, block_idx)
|
||||
}
|
||||
pub fn on_entry_set_for(&self, block_idx: usize) -> &[usize] {
|
||||
self.lookup_set_for(&self.on_entry_sets, block_idx)
|
||||
}
|
||||
pub fn on_exit_set_for(&self, block_idx: usize) -> Vec<usize> {
|
||||
let mut set: Vec<_> = self.on_entry_set_for(block_idx).iter()
|
||||
.map(|x|*x)
|
||||
.collect();
|
||||
bitwise(&mut set[..], self.gen_set_for(block_idx), &Union);
|
||||
bitwise(&mut set[..], self.kill_set_for(block_idx), &Subtract);
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: BitDenotation> DataflowState<O> {
|
||||
fn each_bit<F>(&self, words: &[usize], mut f: F)
|
||||
where F: FnMut(usize) {
|
||||
//! Helper for iterating over the bits in a bitvector.
|
||||
|
||||
for (word_index, &word) in words.iter().enumerate() {
|
||||
if word != 0 {
|
||||
let usize_bits: usize = mem::size_of::<usize>();
|
||||
let base_index = word_index * usize_bits;
|
||||
for offset in 0..usize_bits {
|
||||
let bit = 1 << offset;
|
||||
if (word & bit) != 0 {
|
||||
// NB: we round up the total number of bits
|
||||
// that we store in any given bit set so that
|
||||
// it is an even multiple of usize::BITS. This
|
||||
// means that there may be some stray bits at
|
||||
// the end that do not correspond to any
|
||||
// actual value; that's why we first check
|
||||
// that we are in range of bits_per_block.
|
||||
let bit_index = base_index + offset as usize;
|
||||
if bit_index >= self.sets.bits_per_block() {
|
||||
return;
|
||||
} else {
|
||||
f(bit_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interpret_set(&self, words: &[usize]) -> Vec<&O::Bit> {
|
||||
let mut v = Vec::new();
|
||||
self.each_bit(words, |i| {
|
||||
v.push(self.operator.interpret(i));
|
||||
});
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BitwiseOperator {
|
||||
/// Joins two predecessor bits together, typically either `|` or `&`
|
||||
fn join(&self, pred1: usize, pred2: usize) -> usize;
|
||||
}
|
||||
|
||||
/// Parameterization for the precise form of data flow that is used.
|
||||
pub trait DataflowOperator : BitwiseOperator {
|
||||
/// Specifies the initial value for each bit in the `on_entry` set
|
||||
fn initial_value() -> bool;
|
||||
}
|
||||
|
||||
pub trait BitDenotation: DataflowOperator {
|
||||
/// Specifies what is represented by each bit in the dataflow bitvector.
|
||||
type Bit;
|
||||
/// Size of each bivector allocated for each block in the analysis.
|
||||
fn bits_per_block(&self) -> usize;
|
||||
/// Provides the meaning of each entry in the dataflow bitvector.
|
||||
/// (Mostly intended for use for better debug instrumentation.)
|
||||
fn interpret(&self, idx: usize) -> &Self::Bit;
|
||||
}
|
||||
|
||||
impl<D: BitDenotation> DataflowState<D> {
|
||||
pub fn new(mir: &Mir, denotation: D) -> Self {
|
||||
let bits_per_block = denotation.bits_per_block();
|
||||
let usize_bits = mem::size_of::<usize>() * 8;
|
||||
let words_per_block = (bits_per_block + usize_bits - 1) / usize_bits;
|
||||
let num_blocks = mir.basic_blocks.len();
|
||||
let num_words = num_blocks * words_per_block;
|
||||
|
||||
let entry = if D::initial_value() { usize::MAX } else {0};
|
||||
|
||||
let zeroes = Bits::new(0, num_words);
|
||||
let on_entry = Bits::new(entry, num_words);
|
||||
|
||||
DataflowState {
|
||||
sets: AllSets {
|
||||
bits_per_block: bits_per_block,
|
||||
words_per_block: words_per_block,
|
||||
gen_sets: zeroes.clone(),
|
||||
kill_sets: zeroes,
|
||||
on_entry_sets: on_entry,
|
||||
},
|
||||
operator: denotation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: BitDenotation> DataflowState<D> {
|
||||
/// Propagates the bits of `in_out` into all the successors of `bb`,
|
||||
/// using bitwise operator denoted by `self.operator`.
|
||||
///
|
||||
/// For most blocks, this is entirely uniform. However, for blocks
|
||||
/// that end with a call terminator, the effect of the call on the
|
||||
/// dataflow state may depend on whether the call returned
|
||||
/// successfully or unwound. To reflect this, the `on_return`
|
||||
/// callback mutates `in_out` when propagating `in_out` via a call
|
||||
/// terminator; such mutation is performed *last*, to ensure its
|
||||
/// side-effects do not leak elsewhere (e.g. into unwind target).
|
||||
fn propagate_bits_into_graph_successors_of<OnReturn>(
|
||||
&mut self,
|
||||
in_out: &mut [usize],
|
||||
changed: &mut bool,
|
||||
bb: &repr::BasicBlockData,
|
||||
on_return: OnReturn) where OnReturn: Fn(&D, &mut [usize], &repr::Lvalue)
|
||||
{
|
||||
match bb.terminator().kind {
|
||||
repr::TerminatorKind::Return |
|
||||
repr::TerminatorKind::Resume => {}
|
||||
repr::TerminatorKind::Goto { ref target } |
|
||||
repr::TerminatorKind::Drop { ref target, value: _, unwind: None } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, target);
|
||||
}
|
||||
repr::TerminatorKind::Drop { ref target, value: _, unwind: Some(ref unwind) } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, target);
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, unwind);
|
||||
}
|
||||
repr::TerminatorKind::If { ref targets, .. } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, &targets.0);
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, &targets.1);
|
||||
}
|
||||
repr::TerminatorKind::Switch { ref targets, .. } |
|
||||
repr::TerminatorKind::SwitchInt { ref targets, .. } => {
|
||||
for target in targets {
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, target);
|
||||
}
|
||||
}
|
||||
repr::TerminatorKind::Call { ref cleanup, ref destination, func: _, args: _ } => {
|
||||
if let Some(ref unwind) = *cleanup {
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, unwind);
|
||||
}
|
||||
if let Some((ref dest_lval, ref dest_bb)) = *destination {
|
||||
// N.B.: This must be done *last*, after all other
|
||||
// propagation, as documented in comment above.
|
||||
on_return(&self.operator, in_out, dest_lval);
|
||||
self.propagate_bits_into_entry_set_for(in_out, changed, dest_bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bits_into_entry_set_for(&mut self,
|
||||
in_out: &[usize],
|
||||
changed: &mut bool,
|
||||
bb: &repr::BasicBlock) {
|
||||
let entry_set = self.sets.for_block(bb.index()).on_entry;
|
||||
let set_changed = bitwise(entry_set, in_out, &self.operator);
|
||||
if set_changed {
|
||||
*changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dataflow analyses are built upon some interpretation of the
|
||||
// bitvectors attached to each basic block, represented via a
|
||||
// zero-sized structure.
|
||||
//
|
||||
// Note on PhantomData: Each interpretation will need to instantiate
|
||||
// the `Bit` and `Ctxt` associated types, and in this case, those
|
||||
// associated types need an associated lifetime `'tcx`. The
|
||||
// interpretive structures are zero-sized, so they all need to carry a
|
||||
// `PhantomData` representing how the structures relate to the `'tcx`
|
||||
// lifetime.
|
||||
//
|
||||
// But, since all of the uses of `'tcx` are solely via instances of
|
||||
// `Ctxt` that are passed into the `BitDenotation` methods, we can
|
||||
// consistently use a `PhantomData` that is just a function over a
|
||||
// `&Ctxt` (== `&MoveData<'tcx>).
|
||||
|
||||
/// `MaybeInitializedLvals` tracks all l-values that might be
|
||||
/// initialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // maybe-init:
|
||||
/// // {}
|
||||
/// let a = S; let b = S; let c; let d; // {a, b}
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // { b}
|
||||
/// b = S; // { b}
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // {a}
|
||||
/// d = S; // {a, d}
|
||||
///
|
||||
/// } // {a, b, d}
|
||||
///
|
||||
/// c = S; // {a, b, c, d}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether an l-value *must* be initialized at a
|
||||
/// particular control-flow point, one can take the set-difference
|
||||
/// between this data and the data from `MaybeUninitializedLvals` at the
|
||||
/// corresponding control-flow point.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-intersection
|
||||
/// between this data and `MaybeUninitializedLvals` yields the set of
|
||||
/// l-values that would require a dynamic drop-flag at that statement.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MaybeInitializedLvals<'tcx> {
|
||||
// See "Note on PhantomData" above.
|
||||
phantom: PhantomData<for <'a> Fn(&'a MoveData<'tcx>)>,
|
||||
}
|
||||
|
||||
/// `MaybeUninitializedLvals` tracks all l-values that might be
|
||||
/// uninitialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // maybe-uninit:
|
||||
/// // {a, b, c, d}
|
||||
/// let a = S; let b = S; let c; let d; // { c, d}
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // {a, c, d}
|
||||
/// b = S; // {a, c, d}
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // { b, c, d}
|
||||
/// d = S; // { b, c }
|
||||
///
|
||||
/// } // {a, b, c, d}
|
||||
///
|
||||
/// c = S; // {a, b, d}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether an l-value *must* be uninitialized at a
|
||||
/// particular control-flow point, one can take the set-difference
|
||||
/// between this data and the data from `MaybeInitializedLvals` at the
|
||||
/// corresponding control-flow point.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-intersection
|
||||
/// between this data and `MaybeInitializedLvals` yields the set of
|
||||
/// l-values that would require a dynamic drop-flag at that statement.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MaybeUninitializedLvals<'tcx> {
|
||||
// See "Note on PhantomData" above.
|
||||
phantom: PhantomData<for <'a> Fn(&'a MoveData<'tcx>)>,
|
||||
}
|
||||
|
||||
/// `MovingOutStatements` tracks the statements that perform moves out
|
||||
/// of particular l-values. More precisely, it tracks whether the
|
||||
/// *effect* of such moves (namely, the uninitialization of the
|
||||
/// l-value in question) can reach some point in the control-flow of
|
||||
/// the function, or if that effect is "killed" by some intervening
|
||||
/// operation reinitializing that l-value.
|
||||
///
|
||||
/// The resulting dataflow is a more enriched version of
|
||||
/// `MaybeUninitializedLvals`. Both structures on their own only tell
|
||||
/// you if an l-value *might* be uninitialized at a given point in the
|
||||
/// control flow. But `MovingOutStatements` also includes the added
|
||||
/// data of *which* particular statement causing the deinitialization
|
||||
/// that the borrow checker's error meessage may need to report.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MovingOutStatements<'tcx> {
|
||||
// See "Note on PhantomData" above.
|
||||
phantom: PhantomData<for <'a> Fn(&'a MoveData<'tcx>)>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DataflowState<MoveData<'tcx>> {
|
||||
pub fn new_move_analysis(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Self {
|
||||
let move_data = MoveData::gather_moves(mir, tcx);
|
||||
DataflowState::new(mir, move_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> BitwiseOperator for MoveData<'tcx> {
|
||||
#[inline]
|
||||
fn join(&self, pred1: usize, pred2: usize) -> usize {
|
||||
pred1 | pred2 // moves from both preds are in scope
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> DataflowOperator for MoveData<'tcx> {
|
||||
#[inline]
|
||||
fn initial_value() -> bool {
|
||||
false // no loans in scope by default
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bitwise<Op:BitwiseOperator>(out_vec: &mut [usize],
|
||||
in_vec: &[usize],
|
||||
op: &Op) -> bool {
|
||||
assert_eq!(out_vec.len(), in_vec.len());
|
||||
let mut changed = false;
|
||||
for (out_elt, in_elt) in out_vec.iter_mut().zip(in_vec) {
|
||||
let old_val = *out_elt;
|
||||
let new_val = op.join(old_val, *in_elt);
|
||||
*out_elt = new_val;
|
||||
changed |= old_val != new_val;
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
struct Union;
|
||||
impl BitwiseOperator for Union {
|
||||
fn join(&self, a: usize, b: usize) -> usize { a | b }
|
||||
}
|
||||
struct Subtract;
|
||||
impl BitwiseOperator for Subtract {
|
||||
fn join(&self, a: usize, b: usize) -> usize { a & !b }
|
||||
}
|
@ -10,30 +10,58 @@
|
||||
|
||||
//! Hook into libgraphviz for rendering dataflow graphs for MIR.
|
||||
|
||||
use syntax::ast::NodeId;
|
||||
use rustc::mir::repr::{BasicBlock, Mir};
|
||||
|
||||
use dot;
|
||||
use dot::IntoCow;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
|
||||
use super::MirBorrowckCtxt;
|
||||
use super::super::MirBorrowckCtxtPreDataflow;
|
||||
use bitslice::bits_to_string;
|
||||
use super::gather_moves::MoveOut;
|
||||
use super::{BitDenotation, DataflowState};
|
||||
use super::{HasMoveData};
|
||||
|
||||
struct Graph<'c, 'b:'c, 'a:'b, 'tcx:'a> { mbcx: &'c MirBorrowckCtxt<'b, 'a, 'tcx>,
|
||||
context: &'b str }
|
||||
pub trait MirWithFlowState<'tcx> {
|
||||
type BD: BitDenotation;
|
||||
fn node_id(&self) -> NodeId;
|
||||
fn mir(&self) -> &Mir<'tcx>;
|
||||
fn analysis_ctxt(&self) -> &<Self::BD as BitDenotation>::Ctxt;
|
||||
fn flow_state(&self) -> &DataflowState<Self::BD>;
|
||||
}
|
||||
|
||||
pub fn print_borrowck_graph_to(mbcx: &MirBorrowckCtxt,
|
||||
context: &str,
|
||||
path: &str) -> io::Result<()> {
|
||||
let g = Graph { mbcx: mbcx, context: context };
|
||||
impl<'a, 'tcx: 'a, BD> MirWithFlowState<'tcx> for MirBorrowckCtxtPreDataflow<'a, 'tcx, BD>
|
||||
where 'a, 'tcx: 'a, BD: BitDenotation, BD::Ctxt: HasMoveData<'tcx>
|
||||
{
|
||||
type BD = BD;
|
||||
fn node_id(&self) -> NodeId { self.node_id }
|
||||
fn mir(&self) -> &Mir<'tcx> { self.flow_state.mir() }
|
||||
fn analysis_ctxt(&self) -> &BD::Ctxt { &self.flow_state.ctxt }
|
||||
fn flow_state(&self) -> &DataflowState<Self::BD> { &self.flow_state.flow_state }
|
||||
}
|
||||
|
||||
struct Graph<'a, 'tcx, MWF:'a> where MWF: MirWithFlowState<'tcx>,
|
||||
{
|
||||
mbcx: &'a MWF,
|
||||
phantom: PhantomData<&'tcx ()>
|
||||
}
|
||||
|
||||
pub fn print_borrowck_graph_to<'a, 'tcx, BD>(
|
||||
mbcx: &MirBorrowckCtxtPreDataflow<'a, 'tcx, BD>,
|
||||
path: &Path) -> io::Result<()> where BD: BitDenotation,
|
||||
BD::Bit: Debug, BD::Ctxt: HasMoveData<'tcx>
|
||||
{
|
||||
let g = Graph { mbcx: mbcx, phantom: PhantomData };
|
||||
let mut v = Vec::new();
|
||||
dot::render(&g, &mut v)?;
|
||||
println!("print_borrowck_graph_to path: {} context: {} node_id: {}",
|
||||
path, context, mbcx.node_id);
|
||||
debug!("print_borrowck_graph_to path: {} node_id: {}",
|
||||
path.display(), mbcx.node_id);
|
||||
File::create(path).and_then(|mut f| f.write_all(&v))
|
||||
}
|
||||
|
||||
@ -47,13 +75,14 @@ fn outgoing(mir: &Mir, bb: BasicBlock) -> Vec<Edge> {
|
||||
(0..succ_len).map(|index| Edge { source: bb, index: index}).collect()
|
||||
}
|
||||
|
||||
impl<'c, 'b:'c, 'a:'b, 'tcx:'a> dot::Labeller<'c> for Graph<'c,'b,'a,'tcx> {
|
||||
impl<'a, 'tcx, MWF> dot::Labeller<'a> for Graph<'a, 'tcx, MWF>
|
||||
where MWF: MirWithFlowState<'tcx>, <MWF::BD as BitDenotation>::Bit: Debug
|
||||
{
|
||||
type Node = Node;
|
||||
type Edge = Edge;
|
||||
fn graph_id(&self) -> dot::Id {
|
||||
dot::Id::new(format!("graph_for_node_{}_{}",
|
||||
self.mbcx.node_id,
|
||||
self.context))
|
||||
dot::Id::new(format!("graph_for_node_{}",
|
||||
self.mbcx.node_id()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@ -106,10 +135,10 @@ impl<'c, 'b:'c, 'a:'b, 'tcx:'a> dot::Labeller<'c> for Graph<'c,'b,'a,'tcx> {
|
||||
const BG_FLOWCONTENT: &'static str = r#"bgcolor="pink""#;
|
||||
const ALIGN_RIGHT: &'static str = r#"align="right""#;
|
||||
const FACE_MONOSPACE: &'static str = r#"FACE="Courier""#;
|
||||
fn chunked_present_left<W:io::Write>(w: &mut W,
|
||||
interpreted: &[&MoveOut],
|
||||
chunk_size: usize)
|
||||
-> io::Result<()>
|
||||
fn chunked_present_left<D: Debug, W:io::Write>(w: &mut W,
|
||||
interpreted: &[&D],
|
||||
chunk_size: usize)
|
||||
-> io::Result<()>
|
||||
{
|
||||
// This function may emit a sequence of <tr>'s, but it
|
||||
// always finishes with an (unfinished)
|
||||
@ -137,40 +166,55 @@ impl<'c, 'b:'c, 'a:'b, 'tcx:'a> dot::Labeller<'c> for Graph<'c,'b,'a,'tcx> {
|
||||
Ok(())
|
||||
}
|
||||
::rustc_mir::graphviz::write_node_label(
|
||||
*n, self.mbcx.mir, &mut v, 4,
|
||||
*n, self.mbcx.mir(), &mut v, 4,
|
||||
|w| {
|
||||
let flow = &self.mbcx.flow_state;
|
||||
let entry = flow.interpret_set(flow.sets.on_entry_set_for(i));
|
||||
chunked_present_left(w, &entry[..], chunk_size)?;
|
||||
let ctxt = self.mbcx.analysis_ctxt();
|
||||
let flow = self.mbcx.flow_state();
|
||||
let entry_interp = flow.interpret_set(ctxt, flow.sets.on_entry_set_for(i));
|
||||
chunked_present_left(w, &entry_interp[..], chunk_size)?;
|
||||
let bits_per_block = flow.sets.bits_per_block();
|
||||
let entry = flow.sets.on_entry_set_for(i);
|
||||
debug!("entry set for i={i} bits_per_block: {bpb} entry: {e:?} interp: {ei:?}",
|
||||
i=i, e=entry, bpb=bits_per_block, ei=entry_interp);
|
||||
write!(w, "= ENTRY:</td><td {bg}><FONT {face}>{entrybits:?}</FONT></td>\
|
||||
<td></td></tr>",
|
||||
bg = BG_FLOWCONTENT,
|
||||
face = FACE_MONOSPACE,
|
||||
entrybits=bits_to_string(flow.sets.on_entry_set_for(i),
|
||||
flow.sets.bytes_per_block()))
|
||||
entrybits=bits_to_string(entry, bits_per_block))
|
||||
},
|
||||
|w| {
|
||||
let flow = &self.mbcx.flow_state;
|
||||
let gen = flow.interpret_set( flow.sets.gen_set_for(i));
|
||||
let kill = flow.interpret_set(flow.sets.kill_set_for(i));
|
||||
chunked_present_left(w, &gen[..], chunk_size)?;
|
||||
write!(w, " = GEN:</td><td {bg}><FONT {face}>{genbits:?}</FONT></td>\
|
||||
<td></td></tr>",
|
||||
bg = BG_FLOWCONTENT,
|
||||
face = FACE_MONOSPACE,
|
||||
genbits=bits_to_string( flow.sets.gen_set_for(i),
|
||||
flow.sets.bytes_per_block()))?;
|
||||
write!(w, "<tr><td></td><td {bg} {align}>KILL:</td>\
|
||||
<td {bg}><FONT {face}>{killbits:?}</FONT></td>",
|
||||
bg = BG_FLOWCONTENT,
|
||||
align = ALIGN_RIGHT,
|
||||
face = FACE_MONOSPACE,
|
||||
killbits=bits_to_string(flow.sets.kill_set_for(i),
|
||||
flow.sets.bytes_per_block()))?;
|
||||
let ctxt = self.mbcx.analysis_ctxt();
|
||||
let flow = self.mbcx.flow_state();
|
||||
let gen_interp = flow.interpret_set(ctxt, flow.sets.gen_set_for(i));
|
||||
let kill_interp = flow.interpret_set(ctxt, flow.sets.kill_set_for(i));
|
||||
chunked_present_left(w, &gen_interp[..], chunk_size)?;
|
||||
let bits_per_block = flow.sets.bits_per_block();
|
||||
{
|
||||
let gen = flow.sets.gen_set_for(i);
|
||||
debug!("gen set for i={i} bits_per_block: {bpb} gen: {g:?} interp: {gi:?}",
|
||||
i=i, g=gen, bpb=bits_per_block, gi=gen_interp);
|
||||
write!(w, " = GEN:</td><td {bg}><FONT {face}>{genbits:?}</FONT></td>\
|
||||
<td></td></tr>",
|
||||
bg = BG_FLOWCONTENT,
|
||||
face = FACE_MONOSPACE,
|
||||
genbits=bits_to_string(gen, bits_per_block))?;
|
||||
}
|
||||
|
||||
{
|
||||
let kill = flow.sets.kill_set_for(i);
|
||||
debug!("kill set for i={i} bits_per_block: {bpb} kill: {k:?} interp: {ki:?}",
|
||||
i=i, k=kill, bpb=bits_per_block, ki=kill_interp);
|
||||
write!(w, "<tr><td></td><td {bg} {align}>KILL:</td>\
|
||||
<td {bg}><FONT {face}>{killbits:?}</FONT></td>",
|
||||
bg = BG_FLOWCONTENT,
|
||||
align = ALIGN_RIGHT,
|
||||
face = FACE_MONOSPACE,
|
||||
killbits=bits_to_string(kill, bits_per_block))?;
|
||||
}
|
||||
|
||||
// (chunked_present_right)
|
||||
let mut seen_one = false;
|
||||
for k in kill.chunks(chunk_size) {
|
||||
for k in kill_interp.chunks(chunk_size) {
|
||||
if !seen_one {
|
||||
// continuation of row; this is fourth <td>
|
||||
write!(w, "<td {bg}>= {kill:?}</td></tr>",
|
||||
@ -200,16 +244,18 @@ impl<'c, 'b:'c, 'a:'b, 'tcx:'a> dot::Labeller<'c> for Graph<'c,'b,'a,'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c, 'b:'c, 'a:'b, 'tcx:'a> dot::GraphWalk<'c> for Graph<'c,'b,'a,'tcx> {
|
||||
impl<'a, 'tcx, MWF> dot::GraphWalk<'a> for Graph<'a, 'tcx, MWF>
|
||||
where MWF: MirWithFlowState<'tcx>
|
||||
{
|
||||
type Node = Node;
|
||||
type Edge = Edge;
|
||||
fn nodes(&self) -> dot::Nodes<Node> {
|
||||
self.mbcx.mir.all_basic_blocks().into_cow()
|
||||
self.mbcx.mir().all_basic_blocks().into_cow()
|
||||
}
|
||||
|
||||
fn edges(&self) -> dot::Edges<Edge> {
|
||||
let mir = self.mbcx.mir;
|
||||
let blocks = self.mbcx.mir.all_basic_blocks();
|
||||
let mir = self.mbcx.mir();
|
||||
let blocks = mir.all_basic_blocks();
|
||||
// base initial capacity on assumption every block has at
|
||||
// least one outgoing edge (Which should be true for all
|
||||
// blocks but one, the exit-block).
|
||||
@ -226,7 +272,7 @@ impl<'c, 'b:'c, 'a:'b, 'tcx:'a> dot::GraphWalk<'c> for Graph<'c,'b,'a,'tcx> {
|
||||
}
|
||||
|
||||
fn target(&self, edge: &Edge) -> Node {
|
||||
let mir = self.mbcx.mir;
|
||||
let mir = self.mbcx.mir();
|
||||
mir.basic_block_data(edge.source).terminator().successors()[edge.index]
|
||||
}
|
||||
}
|
1024
src/librustc_borrowck/borrowck/mir/dataflow/mod.rs
Normal file
1024
src/librustc_borrowck/borrowck/mir/dataflow/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,6 @@ use std::fmt;
|
||||
use std::iter;
|
||||
use std::ops::Index;
|
||||
|
||||
use super::dataflow::BitDenotation;
|
||||
use super::abs_domain::{AbstractElem, Lift};
|
||||
|
||||
// This submodule holds some newtype'd Index wrappers that are using
|
||||
@ -32,7 +31,7 @@ mod indexes {
|
||||
|
||||
macro_rules! new_index {
|
||||
($Index:ident) => {
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct $Index(NonZero<usize>);
|
||||
|
||||
impl $Index {
|
||||
@ -56,6 +55,12 @@ mod indexes {
|
||||
pub use self::indexes::MovePathIndex;
|
||||
pub use self::indexes::MoveOutIndex;
|
||||
|
||||
impl self::indexes::MoveOutIndex {
|
||||
pub fn move_path_index(&self, move_data: &MoveData) -> MovePathIndex {
|
||||
move_data.moves[self.idx()].path
|
||||
}
|
||||
}
|
||||
|
||||
/// `MovePath` is a canonicalized representation of a path that is
|
||||
/// moved or assigned to.
|
||||
///
|
||||
@ -125,6 +130,7 @@ impl<'tcx> fmt::Debug for MovePath<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MoveData<'tcx> {
|
||||
pub move_paths: MovePathData<'tcx>,
|
||||
pub moves: Vec<MoveOut>,
|
||||
@ -133,6 +139,7 @@ pub struct MoveData<'tcx> {
|
||||
pub rev_lookup: MovePathLookup<'tcx>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocMap {
|
||||
/// Location-indexed (BasicBlock for outer index, index within BB
|
||||
/// for inner index) map to list of MoveOutIndex's.
|
||||
@ -153,6 +160,7 @@ impl Index<Location> for LocMap {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathMap {
|
||||
/// Path-indexed map to list of MoveOutIndex's.
|
||||
///
|
||||
@ -187,7 +195,7 @@ impl fmt::Debug for MoveOut {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Location {
|
||||
/// block where action is located
|
||||
pub block: BasicBlock,
|
||||
@ -202,10 +210,15 @@ impl fmt::Debug for Location {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MovePathData<'tcx> {
|
||||
move_paths: Vec<MovePath<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'tcx> MovePathData<'tcx> {
|
||||
pub fn len(&self) -> usize { self.move_paths.len() }
|
||||
}
|
||||
|
||||
impl<'tcx> Index<MovePathIndex> for MovePathData<'tcx> {
|
||||
type Output = MovePath<'tcx>;
|
||||
fn index(&self, i: MovePathIndex) -> &MovePath<'tcx> {
|
||||
@ -224,6 +237,7 @@ struct MovePathDataBuilder<'a, 'tcx: 'a> {
|
||||
}
|
||||
|
||||
/// Tables mapping from an l-value to its MovePathIndex.
|
||||
#[derive(Debug)]
|
||||
pub struct MovePathLookup<'tcx> {
|
||||
vars: MovePathInverseMap,
|
||||
temps: MovePathInverseMap,
|
||||
@ -272,6 +286,7 @@ impl<T:Clone> FillTo for Vec<T> {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum LookupKind { Generate, Reuse }
|
||||
#[derive(Clone, Debug)]
|
||||
struct Lookup<T>(LookupKind, T);
|
||||
|
||||
impl Lookup<MovePathIndex> {
|
||||
@ -425,6 +440,8 @@ impl<'a, 'tcx> MovePathDataBuilder<'a, 'tcx> {
|
||||
}
|
||||
|
||||
fn move_path_for(&mut self, lval: &Lvalue<'tcx>) -> MovePathIndex {
|
||||
debug!("move_path_for({:?})", lval);
|
||||
|
||||
let lookup: Lookup<MovePathIndex> = self.lookup(lval);
|
||||
|
||||
// `lookup` is either the previously assigned index or a
|
||||
@ -547,7 +564,7 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
|
||||
debug_assert!(loc_map_bb.len() == len + 1);
|
||||
|
||||
let mut bb_ctxt = BlockContext {
|
||||
tcx: tcx,
|
||||
_tcx: tcx,
|
||||
moves: &mut moves,
|
||||
builder: builder,
|
||||
path_map: &mut path_map,
|
||||
@ -608,23 +625,17 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
|
||||
// example where I am seeing this arise is
|
||||
// `TargetDataLayout::parse(&Session)` in
|
||||
// `rustc::ty::layout`.
|
||||
debug!("encountered Rvalue::Slice as RHS of Assign, source: {:?} \n{}",
|
||||
source, {
|
||||
let mut out = Vec::new();
|
||||
{
|
||||
use std::io::Write;
|
||||
use rustc_mir::pretty::write_mir_named;
|
||||
let mut w: &mut Write = &mut out;
|
||||
write_mir_named(tcx, "boo_attempt_move_out_of_slice", mir, &mut w, None).unwrap();
|
||||
}
|
||||
String::from_utf8(out).unwrap()
|
||||
});
|
||||
//
|
||||
// this should be removed soon.
|
||||
debug!("encountered Rvalue::Slice as RHS of Assign, source: {:?}",
|
||||
source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("gather_moves({:?})", bb_data.terminator());
|
||||
match bb_data.terminator().kind {
|
||||
TerminatorKind::Goto { target: _ } | TerminatorKind::Resume => { }
|
||||
|
||||
@ -642,7 +653,6 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
|
||||
TerminatorKind::If { ref cond, targets: _ } => {
|
||||
let source = Location { block: bb,
|
||||
index: bb_data.statements.len() };
|
||||
debug!("gather_moves If on_operand {:?} {:?}", cond, source);
|
||||
bb_ctxt.on_operand(SK::If, cond, source);
|
||||
}
|
||||
|
||||
@ -658,10 +668,8 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
|
||||
TerminatorKind::Drop { value: ref lval, target: _, unwind: _ } => {
|
||||
let source = Location { block: bb,
|
||||
index: bb_data.statements.len() };
|
||||
debug!("gather_moves Drop on_move_out_lval {:?} {:?}", lval, source);
|
||||
bb_ctxt.on_move_out_lval(SK::Drop, lval, source);
|
||||
}
|
||||
|
||||
TerminatorKind::Call { ref func, ref args, ref destination, cleanup: _ } => {
|
||||
let source = Location { block: bb,
|
||||
index: bb_data.statements.len() };
|
||||
@ -727,7 +735,7 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
|
||||
}
|
||||
|
||||
struct BlockContext<'b, 'a: 'b, 'tcx: 'a> {
|
||||
tcx: TyCtxt<'b, 'tcx, 'tcx>,
|
||||
_tcx: TyCtxt<'b, 'tcx, 'tcx>,
|
||||
moves: &'b mut Vec<MoveOut>,
|
||||
builder: MovePathDataBuilder<'a, 'tcx>,
|
||||
path_map: &'b mut Vec<Vec<MoveOutIndex>>,
|
||||
@ -739,7 +747,6 @@ impl<'b, 'a: 'b, 'tcx: 'a> BlockContext<'b, 'a, 'tcx> {
|
||||
stmt_kind: StmtKind,
|
||||
lval: &Lvalue<'tcx>,
|
||||
source: Location) {
|
||||
let tcx = self.tcx;
|
||||
let i = source.index;
|
||||
let index = MoveOutIndex::new(self.moves.len());
|
||||
|
||||
@ -774,13 +781,3 @@ impl<'b, 'a: 'b, 'tcx: 'a> BlockContext<'b, 'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> BitDenotation for MoveData<'tcx>{
|
||||
type Bit = MoveOut;
|
||||
fn bits_per_block(&self) -> usize {
|
||||
self.moves.len()
|
||||
}
|
||||
fn interpret(&self, idx: usize) -> &Self::Bit {
|
||||
&self.moves[idx]
|
||||
}
|
||||
}
|
||||
|
@ -10,24 +10,49 @@
|
||||
|
||||
use borrowck::BorrowckCtxt;
|
||||
|
||||
use syntax::ast;
|
||||
use syntax::codemap::Span;
|
||||
use syntax::ast::{self, MetaItem};
|
||||
use syntax::attr::AttrMetaMethods;
|
||||
use syntax::codemap::{Span, DUMMY_SP};
|
||||
use syntax::ptr::P;
|
||||
|
||||
use rustc::hir;
|
||||
use rustc::hir::intravisit::{FnKind};
|
||||
|
||||
use rustc::mir::repr;
|
||||
use rustc::mir::repr::{BasicBlock, BasicBlockData, Mir, Statement, Terminator};
|
||||
use rustc::session::Session;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
|
||||
mod abs_domain;
|
||||
mod dataflow;
|
||||
mod gather_moves;
|
||||
mod graphviz;
|
||||
// mod graphviz;
|
||||
|
||||
use self::dataflow::{Dataflow, DataflowState};
|
||||
use self::gather_moves::{MoveData};
|
||||
use self::dataflow::{BitDenotation};
|
||||
use self::dataflow::{Dataflow, DataflowAnalysis, DataflowResults};
|
||||
use self::dataflow::{HasMoveData};
|
||||
use self::dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
|
||||
use self::gather_moves::{MoveData, MovePathIndex, Location};
|
||||
use self::gather_moves::{MovePathContent};
|
||||
|
||||
pub fn borrowck_mir<'b, 'a: 'b, 'tcx: 'a>(
|
||||
bcx: &'b mut BorrowckCtxt<'a, 'tcx>,
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn has_rustc_mir_with(attrs: &[ast::Attribute], name: &str) -> Option<P<MetaItem>> {
|
||||
for attr in attrs {
|
||||
if attr.check_name("rustc_mir") {
|
||||
let items = attr.meta_item_list();
|
||||
for item in items.iter().flat_map(|l| l.iter()) {
|
||||
if item.check_name(name) {
|
||||
return Some(item.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn borrowck_mir<'a, 'tcx: 'a>(
|
||||
bcx: &mut BorrowckCtxt<'a, 'tcx>,
|
||||
fk: FnKind,
|
||||
_decl: &hir::FnDecl,
|
||||
mir: &'a Mir<'tcx>,
|
||||
@ -45,29 +70,89 @@ pub fn borrowck_mir<'b, 'a: 'b, 'tcx: 'a>(
|
||||
}
|
||||
}
|
||||
|
||||
let tcx = bcx.tcx;
|
||||
|
||||
let move_data = MoveData::gather_moves(mir, tcx);
|
||||
let ctxt = (tcx, mir, move_data);
|
||||
let (ctxt, flow_inits) =
|
||||
do_dataflow(bcx, mir, id, attributes, ctxt, MaybeInitializedLvals::default());
|
||||
let ((_, _, move_data), flow_uninits) =
|
||||
do_dataflow(bcx, mir, id, attributes, ctxt, MaybeUninitializedLvals::default());
|
||||
|
||||
let mut mbcx = MirBorrowckCtxt {
|
||||
flow_state: DataflowState::new_move_analysis(mir, bcx.tcx),
|
||||
bcx: bcx,
|
||||
mir: mir,
|
||||
node_id: id,
|
||||
attributes: attributes,
|
||||
move_data: move_data,
|
||||
flow_inits: flow_inits,
|
||||
flow_uninits: flow_uninits,
|
||||
};
|
||||
|
||||
for bb in mir.all_basic_blocks() {
|
||||
mbcx.process_basic_block(bb);
|
||||
}
|
||||
|
||||
mbcx.dataflow();
|
||||
|
||||
debug!("borrowck_mir done");
|
||||
}
|
||||
|
||||
fn do_dataflow<'a, 'tcx, BD>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
node_id: ast::NodeId,
|
||||
attributes: &[ast::Attribute],
|
||||
ctxt: BD::Ctxt,
|
||||
bd: BD) -> (BD::Ctxt, DataflowResults<BD>)
|
||||
where BD: BitDenotation, BD::Bit: Debug, BD::Ctxt: HasMoveData<'tcx>
|
||||
{
|
||||
use syntax::attr::AttrMetaMethods;
|
||||
|
||||
let name_found = |sess: &Session, attrs: &[ast::Attribute], name| -> Option<String> {
|
||||
if let Some(item) = has_rustc_mir_with(attrs, name) {
|
||||
if let Some(s) = item.value_str() {
|
||||
return Some(s.to_string())
|
||||
} else {
|
||||
sess.span_err(
|
||||
item.span,
|
||||
&format!("{} attribute requires a path", item.name()));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
return None;
|
||||
};
|
||||
|
||||
let print_preflow_to =
|
||||
name_found(tcx.sess, attributes, "borrowck_graphviz_preflow");
|
||||
let print_postflow_to =
|
||||
name_found(tcx.sess, attributes, "borrowck_graphviz_postflow");
|
||||
|
||||
let mut mbcx = MirBorrowckCtxtPreDataflow {
|
||||
node_id: node_id,
|
||||
print_preflow_to: print_preflow_to,
|
||||
print_postflow_to: print_postflow_to,
|
||||
flow_state: DataflowAnalysis::new(tcx, mir, ctxt, bd),
|
||||
};
|
||||
|
||||
mbcx.dataflow();
|
||||
mbcx.flow_state.results()
|
||||
}
|
||||
|
||||
|
||||
pub struct MirBorrowckCtxtPreDataflow<'a, 'tcx: 'a, BD>
|
||||
where BD: BitDenotation, BD::Ctxt: HasMoveData<'tcx>
|
||||
{
|
||||
node_id: ast::NodeId,
|
||||
flow_state: DataflowAnalysis<'a, 'tcx, BD>,
|
||||
print_preflow_to: Option<String>,
|
||||
print_postflow_to: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct MirBorrowckCtxt<'b, 'a: 'b, 'tcx: 'a> {
|
||||
bcx: &'b mut BorrowckCtxt<'a, 'tcx>,
|
||||
mir: &'b Mir<'tcx>,
|
||||
node_id: ast::NodeId,
|
||||
attributes: &'b [ast::Attribute],
|
||||
flow_state: DataflowState<MoveData<'tcx>>,
|
||||
move_data: MoveData<'tcx>,
|
||||
flow_inits: DataflowResults<MaybeInitializedLvals<'a, 'tcx>>,
|
||||
flow_uninits: DataflowResults<MaybeUninitializedLvals<'a, 'tcx>>
|
||||
}
|
||||
|
||||
impl<'b, 'a: 'b, 'tcx: 'a> MirBorrowckCtxt<'b, 'a, 'tcx> {
|
||||
@ -89,3 +174,127 @@ impl<'b, 'a: 'b, 'tcx: 'a> MirBorrowckCtxt<'b, 'a, 'tcx> {
|
||||
debug!("MirBorrowckCtxt::process_terminator({:?}, {:?})", bb, term);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum DropFlagState {
|
||||
Live,
|
||||
Dead
|
||||
}
|
||||
|
||||
fn on_all_children_bits<'a, 'tcx, F>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_path_index: MovePathIndex,
|
||||
mut each_child: F)
|
||||
where F: FnMut(MovePathIndex)
|
||||
{
|
||||
fn is_terminal_path<'a, 'tcx>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
path: MovePathIndex) -> bool
|
||||
{
|
||||
match move_data.move_paths[path].content {
|
||||
MovePathContent::Lvalue(ref lvalue) => {
|
||||
match mir.lvalue_ty(tcx, lvalue).to_ty(tcx).sty {
|
||||
// don't trace paths past arrays, slices, and
|
||||
// pointers. They can only be accessed while
|
||||
// their parents are initialized.
|
||||
//
|
||||
// FIXME: we have to do something for moving
|
||||
// slice patterns.
|
||||
ty::TyArray(..) | ty::TySlice(..) |
|
||||
ty::TyRef(..) | ty::TyRawPtr(..) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
|
||||
fn on_all_children_bits<'a, 'tcx, F>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_path_index: MovePathIndex,
|
||||
each_child: &mut F)
|
||||
where F: FnMut(MovePathIndex)
|
||||
{
|
||||
each_child(move_path_index);
|
||||
|
||||
if is_terminal_path(tcx, mir, move_data, move_path_index) {
|
||||
return
|
||||
}
|
||||
|
||||
let mut next_child_index = move_data.move_paths[move_path_index].first_child;
|
||||
while let Some(child_index) = next_child_index {
|
||||
on_all_children_bits(tcx, mir, move_data, child_index, each_child);
|
||||
next_child_index = move_data.move_paths[child_index].next_sibling;
|
||||
}
|
||||
}
|
||||
on_all_children_bits(tcx, mir, move_data, move_path_index, &mut each_child);
|
||||
}
|
||||
|
||||
fn drop_flag_effects_for_function_entry<'a, 'tcx, F>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
mut callback: F)
|
||||
where F: FnMut(MovePathIndex, DropFlagState)
|
||||
{
|
||||
for i in 0..(mir.arg_decls.len() as u32) {
|
||||
let lvalue = repr::Lvalue::Arg(i);
|
||||
let move_path_index = move_data.rev_lookup.find(&lvalue);
|
||||
on_all_children_bits(tcx, mir, move_data,
|
||||
move_path_index,
|
||||
|moi| callback(moi, DropFlagState::Live));
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_flag_effects_for_location<'a, 'tcx, F>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
loc: Location,
|
||||
mut callback: F)
|
||||
where F: FnMut(MovePathIndex, DropFlagState)
|
||||
{
|
||||
debug!("drop_flag_effects_for_location({:?})", loc);
|
||||
|
||||
// first, move out of the RHS
|
||||
for mi in &move_data.loc_map[loc] {
|
||||
let path = mi.move_path_index(move_data);
|
||||
debug!("moving out of path {:?}", move_data.move_paths[path]);
|
||||
|
||||
// don't move out of non-Copy things
|
||||
if let MovePathContent::Lvalue(ref lvalue) = move_data.move_paths[path].content {
|
||||
let ty = mir.lvalue_ty(tcx, lvalue).to_ty(tcx);
|
||||
let empty_param_env = tcx.empty_parameter_environment();
|
||||
if !ty.moves_by_default(tcx, &empty_param_env, DUMMY_SP) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
on_all_children_bits(tcx, mir, move_data,
|
||||
path,
|
||||
|moi| callback(moi, DropFlagState::Dead))
|
||||
}
|
||||
|
||||
let bb = mir.basic_block_data(loc.block);
|
||||
match bb.statements.get(loc.index) {
|
||||
Some(stmt) => match stmt.kind {
|
||||
repr::StatementKind::Assign(ref lvalue, _) => {
|
||||
debug!("drop_flag_effects: assignment {:?}", stmt);
|
||||
on_all_children_bits(tcx, mir, move_data,
|
||||
move_data.rev_lookup.find(lvalue),
|
||||
|moi| callback(moi, DropFlagState::Live))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
// terminator - no move-ins except for function return edge
|
||||
let term = bb.terminator();
|
||||
debug!("drop_flag_effects: terminator {:?}", term);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user