提交 5757e65f 编写于 作者: F Felix S. Klock II

scaffolding for borrowck on MIR.

emit (via debug!) scary message from `fn borrowck_mir` until basic
prototype is in place.

Gather children of move paths and set their kill bits in
dataflow. (Each node has a link to the child that is first among its
siblings.)

Hooked in libgraphviz based rendering, including of borrowck dataflow
state.

doing this well required some refactoring of the code, so I cleaned it
up more generally (adding comments to explain what its trying to do
and how it is doing it).

Update: this newer version addresses most review comments (at least
the ones that were largely mechanical changes), but I left the more
interesting revisions to separate followup commits (in this same PR).
上级 213d5798
......@@ -97,7 +97,7 @@ DEPS_rustc := syntax fmt_macros flate arena serialize getopts rbml rustc_front\
log graphviz rustc_llvm rustc_back rustc_data_structures\
rustc_const_eval
DEPS_rustc_back := std syntax rustc_llvm rustc_front flate log libc
DEPS_rustc_borrowck := rustc rustc_front log graphviz syntax
DEPS_rustc_borrowck := rustc rustc_front rustc_mir log graphviz syntax
DEPS_rustc_data_structures := std log serialize
DEPS_rustc_driver := arena flate getopts graphviz libc rustc rustc_back rustc_borrowck \
rustc_typeck rustc_mir rustc_resolve log syntax serialize rustc_llvm \
......
......@@ -653,7 +653,7 @@ fn set_bit(words: &mut [usize], bit: usize) -> bool {
let word = bit / usize_bits;
let bit_in_word = bit % usize_bits;
let bit_mask = 1 << bit_in_word;
debug!("word={} bit_in_word={} bit_mask={}", word, bit_in_word, word);
debug!("word={} bit_in_word={} bit_mask={}", word, bit_in_word, bit_mask);
let oldv = words[word];
let newv = oldv | bit_mask;
words[word] = newv;
......
......@@ -499,13 +499,13 @@ pub enum Lvalue<'tcx> {
/// or `*B` or `B[index]`. Note that it is parameterized because it is
/// shared between `Constant` and `Lvalue`. See the aliases
/// `LvalueProjection` etc below.
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub struct Projection<'tcx, B, V> {
pub base: B,
pub elem: ProjectionElem<'tcx, V>,
}
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub enum ProjectionElem<'tcx, V> {
Deref,
Field(Field, Ty<'tcx>),
......@@ -857,7 +857,7 @@ fn fmt_tuple(fmt: &mut Formatter, lvs: &[Operand]) -> fmt::Result {
/// this does not necessarily mean that they are "==" in Rust -- in
/// particular one must be wary of `NaN`!
#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable)]
#[derive(Clone, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub struct Constant<'tcx> {
pub span: Span,
pub ty: Ty<'tcx>,
......@@ -877,7 +877,7 @@ fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
}
}
#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable)]
#[derive(Clone, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub enum Literal<'tcx> {
Item {
def_id: DefId,
......
// 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 std::mem;
/// `BitSlice` provides helper methods for treating a `[usize]`
/// as a bitvector.
pub trait BitSlice {
fn set_bit(&mut self, idx: usize) -> bool;
fn get_bit(&self, idx: usize) -> bool;
}
impl BitSlice for [usize] {
fn set_bit(&mut self, idx: usize) -> bool {
let words = self;
debug!("set_bit: words={} idx={}",
bits_to_string(words, words.len() * mem::size_of::<usize>()), bit_str(idx));
let BitLookup { word, bit_in_word, bit_mask } = bit_lookup(idx);
debug!("word={} bit_in_word={} bit_mask={}", word, bit_in_word, bit_mask);
let oldv = words[word];
let newv = oldv | bit_mask;
words[word] = newv;
oldv != newv
}
fn get_bit(&self, idx: usize) -> bool {
let words = self;
let BitLookup { word, bit_mask, .. } = bit_lookup(idx);
(words[word] & bit_mask) != 0
}
}
struct BitLookup { word: usize, bit_in_word: usize, bit_mask: usize }
#[inline]
fn bit_lookup(bit: usize) -> BitLookup {
let usize_bits = mem::size_of::<usize>() * 8;
let word = bit / usize_bits;
let bit_in_word = bit % usize_bits;
let bit_mask = 1 << bit_in_word;
BitLookup { word: word, bit_in_word: bit_in_word, bit_mask: bit_mask }
}
fn bit_str(bit: usize) -> String {
let byte = bit >> 8;
let lobits = 1 << (bit & 0xFF);
format!("[{}:{}-{:02x}]", bit, byte, lobits)
}
pub fn bits_to_string(words: &[usize], bytes: usize) -> String {
let mut result = String::new();
let mut sep = '[';
// Note: this is a little endian printout of bytes.
let mut i = 0;
for &word in words.iter() {
let mut v = word;
for _ in 0..mem::size_of::<usize>() {
let byte = v & 0xFF;
if i >= bytes {
assert!(byte == 0);
} else {
result.push(sep);
result.push_str(&format!("{:02x}", byte));
}
v >>= 8;
i += 1;
sep = '-';
}
}
result.push(']');
return result
}
// 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.
//! The move-analysis portion of borrowck needs to work in an abstract
//! domain of lifted Lvalues. Most of the Lvalue variants fall into a
//! one-to-one mapping between the concrete and abstract (e.g. a
//! field-deref on a local-variable, `x.field`, has the same meaning
//! in both domains). Indexed-Projections are the exception: `a[x]`
//! needs to be treated as mapping to the same move path as `a[y]` as
//! well as `a[13]`, et cetera.
//!
//! (In theory the analysis could be extended to work with sets of
//! paths, so that `a[0]` and `a[13]` could be kept distinct, while
//! `a[x]` would still overlap them both. But that is not this
//! representation does today.)
use rustc::mir::repr::{Lvalue, LvalueElem};
use rustc::mir::repr::{Operand, Projection, ProjectionElem};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct AbstractOperand;
pub type AbstractProjection<'tcx> =
Projection<'tcx, Lvalue<'tcx>, AbstractOperand>;
pub type AbstractElem<'tcx> =
ProjectionElem<'tcx, AbstractOperand>;
pub trait Lift {
type Abstract;
fn lift(&self) -> Self::Abstract;
}
impl<'tcx> Lift for Operand<'tcx> {
type Abstract = AbstractOperand;
fn lift(&self) -> Self::Abstract { AbstractOperand }
}
impl<'tcx> Lift for LvalueElem<'tcx> {
type Abstract = AbstractElem<'tcx>;
fn lift(&self) -> Self::Abstract {
match *self {
ProjectionElem::Deref =>
ProjectionElem::Deref,
ProjectionElem::Field(ref f, ty) =>
ProjectionElem::Field(f.clone(), ty.clone()),
ProjectionElem::Index(ref i) =>
ProjectionElem::Index(i.lift()),
ProjectionElem::ConstantIndex {offset,min_length,from_end} =>
ProjectionElem::ConstantIndex {
offset: offset,
min_length: min_length,
from_end: from_end
},
ProjectionElem::Downcast(a, u) =>
ProjectionElem::Downcast(a.clone(), u.clone()),
}
}
}
// 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::middle::ty;
use rustc::mir::repr::{self, Mir};
use std::io;
use std::mem;
use std::usize;
use super::MirBorrowckCtxt;
use super::gather_moves::{Location, MoveData, MovePathData, MovePathIndex, 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> {
mbcx: &'c mut MirBorrowckCtxt<'b, 'a, 'tcx>,
changed: bool,
}
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, };
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
let retval = sets.gen_set.set_bit(move_index.idx().unwrap());
assert!(retval);
}
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);
set_children_kill_bits(sets.kill_set,
move_path_index,
path_map,
move_paths);
}
}
}
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] {
let retval = sets.gen_set.set_bit(move_index.idx().unwrap());
assert!(retval);
}
// Note: while below as originally authored could be
// written as an `if let`, it is more future-proof (to MIR
// changes) to use an explicit `match` here.
match *terminator {
None => {}
Some(repr::Terminator::Goto { target: _ }) => {}
Some(repr::Terminator::If { cond: _, targets: _ }) => {}
Some(repr::Terminator::Switch { discr: _, adt_def: _, targets: _ }) => {}
Some(repr::Terminator::SwitchInt { discr: _, switch_ty: _, values: _, targets: _ }) => {}
Some(repr::Terminator::Resume) => {}
Some(repr::Terminator::Return) => {}
Some(repr::Terminator::Drop { value: _, target: _, unwind: _ }) => {
// either kind of Drop completely invalidates the
// state of the referenced memory, effectively
// acting like a MoveOut. Such gen-set additions
// were added by the loop above over the loc_map.
}
Some(repr::Terminator::Call { func: _, args: _, cleanup: _,
ref destination }) => {
// Note: a followup commit refines this to reflect
// that the destination will be initialized if the
// call succeeds (thus killling any MoveOuts for
// that destination).
//
// That is, this code just does the kills
// unconditionally (which I believe this matches
// the behavior of the old borrowck dataflow
// analysis), but this code also is also removed
// and replaced with something flow-dependent in a
// followup commit.
if let Some((ref destination, _)) = *destination {
let move_path_index = rev_lookup.find(destination);
set_children_kill_bits(sets.kill_set,
move_path_index,
path_map,
move_paths);
}
}
}
}
fn set_children_kill_bits(kill_set: &mut [usize],
move_path_index: MovePathIndex,
path_map: &PathMap,
move_paths: &MovePathData) {
assert!(move_path_index.idx().is_some());
// 1. set kill bits for all moves that directly
// influence path for `move_path_index`
for move_index in &path_map[move_path_index] {
kill_set.set_bit(move_index.idx().unwrap());
}
// 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 child_index = move_paths[move_path_index].first_child;
while let Some(_) = child_index.idx() {
set_children_kill_bits(kill_set, child_index, path_map, move_paths);
child_index = move_paths[child_index].next_sibling;
}
}
}
}
impl<'c, 'b: 'c, 'a: 'b, 'tcx: 'a> PropagationContext<'c, 'b, 'a, 'tcx> {
fn reset(&mut self, bits: &mut [usize]) {
let e = if self.mbcx.flow_state.operator.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);
}
}
}
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)
}
}
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(&self) -> 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 denotation.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> {
fn propagate_bits_into_graph_successors_of(&mut self,
in_out: &mut [usize],
changed: &mut bool,
bb: &repr::BasicBlockData) {
let term = if let Some(ref term) = bb.terminator { term } else { return };
match *term {
repr::Terminator::Return |
repr::Terminator::Resume => {}
repr::Terminator::Goto { ref target } |
repr::Terminator::Drop { ref target, value: _, unwind: None } => {
self.propagate_bits_into_entry_set_for(in_out, changed, target);
}
repr::Terminator::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::Terminator::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::Terminator::Switch { ref targets, .. } |
repr::Terminator::SwitchInt { ref targets, .. } => {
for target in targets {
self.propagate_bits_into_entry_set_for(in_out, changed, target);
}
}
repr::Terminator::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 destination)) = *destination {
self.propagate_bits_into_entry_set_for(in_out, changed, destination);
}
}
}
}
fn propagate_bits_into_entry_set_for(&mut self,
in_out: &mut [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;
}
}
}
impl<'tcx> DataflowState<MoveData<'tcx>> {
pub fn new_move_analysis(mir: &Mir<'tcx>, tcx: &ty::TyCtxt<'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(&self) -> 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 }
}
此差异已折叠。
// 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.
//! Hook into libgraphviz for rendering dataflow graphs for MIR.
use rustc::mir::repr::{BasicBlock, Mir};
use dot;
use dot::IntoCow;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use super::MirBorrowckCtxt;
use bitslice::bits_to_string;
use super::gather_moves::MoveOut;
struct Graph<'c, 'b:'c, 'a:'b, 'tcx:'a> { mbcx: &'c MirBorrowckCtxt<'b, 'a, 'tcx>,
context: &'b str }
pub fn print_borrowck_graph_to(mbcx: &MirBorrowckCtxt,
context: &str,
path: &str) -> io::Result<()> {
let g = Graph { mbcx: mbcx, context: context };
let mut v = Vec::new();
try!(dot::render(&g, &mut v));
println!("print_borrowck_graph_to path: {} context: {} node_id: {}",
path, context, mbcx.node_id);
File::create(path).and_then(|mut f| f.write_all(&v))
}
pub type Node = BasicBlock;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Edge { source: BasicBlock, index: usize }
fn outgoing(mir: &Mir, bb: BasicBlock) -> Vec<Edge> {
let succ_len = mir.basic_block_data(bb).terminator().successors().len();
(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> {
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))
.unwrap()
}
fn node_id(&self, n: &Node) -> dot::Id {
dot::Id::new(format!("bb_{}", n.index()))
.unwrap()
}
fn node_label(&self, n: &Node) -> dot::LabelText {
// A standard MIR label, as generated by write_node_label, is
// presented in a single column in a table.
//
// The code below does a bunch of formatting work to format a
// node (i.e. MIR basic-block) label with extra
// dataflow-enriched information. In particular, the goal is
// to add extra columns that present the three dataflow
// bitvectors, and the data those bitvectors represent.
//
// It presents it in the following format (where I am
// presenting the table rendering via ASCII art, one line per
// row of the table, and a chunk size of 3 rather than 5):
//
// ------ ----------------------- ------------ --------------------
// [e1, e3, e4]
// [e8, e9] "= ENTRY:" <ENTRY-BITS>
// ------ ----------------------- ------------ --------------------
// Left
// Most
// Column
// Is
// Just
// Normal
// Series
// Of
// MIR
// Stmts
// ------ ----------------------- ------------ --------------------
// [g1, g4, g5] "= GEN:" <GEN-BITS>
// ------ ----------------------- ------------ --------------------
// "KILL:" <KILL-BITS> "=" [k1, k3, k8]
// [k9]
// ------ ----------------------- ------------ --------------------
//
// (In addition, the added dataflow is rendered with a colored
// background just so it will stand out compared to the
// statements.)
let mut v = Vec::new();
let i = n.index();
let chunk_size = 5;
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<()>
{
// This function may emit a sequence of <tr>'s, but it
// always finishes with an (unfinished)
// <tr><td></td><td>
//
// Thus, after being called, one should finish both the
// pending <td> as well as the <tr> itself.
let mut seen_one = false;
for c in interpreted.chunks(chunk_size) {
if seen_one {
// if not the first row, finish off the previous row
try!(write!(w, "</td><td></td><td></td></tr>"));
}
try!(write!(w, "<tr><td></td><td {bg} {align}>{objs:?}",
bg = BG_FLOWCONTENT,
align = ALIGN_RIGHT,
objs = c));
seen_one = true;
}
if !seen_one {
try!(write!(w, "<tr><td></td><td {bg} {align}>[]",
bg = BG_FLOWCONTENT,
align = ALIGN_RIGHT));
}
Ok(())
}
::rustc_mir::graphviz::write_node_label(
*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));
try!(chunked_present_left(w, &entry[..], chunk_size));
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()))
},
|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));
try!(chunked_present_left(w, &gen[..], chunk_size));
try!(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())));
try!(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())));
// (chunked_present_right)
let mut seen_one = false;
for k in kill.chunks(chunk_size) {
if !seen_one {
// continuation of row; this is fourth <td>
try!(write!(w, "<td {bg}>= {kill:?}</td></tr>",
bg = BG_FLOWCONTENT,
kill=k));
} else {
// new row, with indent of three <td>'s
try!(write!(w, "<tr><td></td><td></td><td></td><td {bg}>{kill:?}</td></tr>",
bg = BG_FLOWCONTENT,
kill=k));
}
seen_one = true;
}
if !seen_one {
try!(write!(w, "<td {bg}>= []</td></tr>",
bg = BG_FLOWCONTENT));
}
Ok(())
})
.unwrap();
dot::LabelText::html(String::from_utf8(v).unwrap())
}
fn node_shape(&self, _n: &Node) -> Option<dot::LabelText> {
Some(dot::LabelText::label("none"))
}
}
impl<'c, 'b:'c, 'a:'b, 'tcx:'a> dot::GraphWalk<'c> for Graph<'c,'b,'a,'tcx> {
type Node = Node;
type Edge = Edge;
fn nodes(&self) -> dot::Nodes<Node> {
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();
// 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).
let mut edges = Vec::with_capacity(blocks.len());
for bb in blocks {
let outgoing = outgoing(mir, bb);
edges.extend(outgoing.into_iter());
}
edges.into_cow()
}
fn source(&self, edge: &Edge) -> Node {
edge.source
}
fn target(&self, edge: &Edge) -> Node {
let mir = self.mbcx.mir;
mir.basic_block_data(edge.source).terminator().successors()[edge.index]
}
}
// 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 borrowck::BorrowckCtxt;
use syntax::ast;
use syntax::codemap::Span;
use rustc_front::hir;
use rustc_front::intravisit::{FnKind};
use rustc::mir::repr::{BasicBlock, BasicBlockData, Mir, Statement, Terminator};
mod abs_domain;
mod dataflow;
mod gather_moves;
mod graphviz;
use self::dataflow::{Dataflow, DataflowState};
use self::gather_moves::{MoveData};
pub fn borrowck_mir<'b, 'a: 'b, 'tcx: 'a>(
bcx: &'b mut BorrowckCtxt<'a, 'tcx>,
fk: FnKind,
_decl: &hir::FnDecl,
mir: &'a Mir<'tcx>,
body: &hir::Block,
_sp: Span,
id: ast::NodeId,
attributes: &[ast::Attribute]) {
match fk {
FnKind::ItemFn(name, _, _, _, _, _, _) |
FnKind::Method(name, _, _, _) => {
debug!("borrowck_mir({}) UNIMPLEMENTED", name);
}
FnKind::Closure(_) => {
debug!("borrowck_mir closure (body.id={}) UNIMPLEMENTED", body.id);
}
}
let mut mbcx = MirBorrowckCtxt {
bcx: bcx,
mir: mir,
node_id: id,
attributes: attributes,
flow_state: DataflowState::new_move_analysis(mir, bcx.tcx),
};
for bb in mir.all_basic_blocks() {
mbcx.process_basic_block(bb);
}
mbcx.dataflow();
debug!("borrowck_mir done");
}
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>>,
}
impl<'b, 'a: 'b, 'tcx: 'a> MirBorrowckCtxt<'b, 'a, 'tcx> {
fn process_basic_block(&mut self, bb: BasicBlock) {
let &BasicBlockData { ref statements, ref terminator, is_cleanup: _ } =
self.mir.basic_block_data(bb);
for stmt in statements {
self.process_statement(bb, stmt);
}
self.process_terminator(bb, terminator);
}
fn process_statement(&mut self, bb: BasicBlock, stmt: &Statement<'tcx>) {
debug!("MirBorrowckCtxt::process_statement({:?}, {:?}", bb, stmt);
}
fn process_terminator(&mut self, bb: BasicBlock, term: &Option<Terminator<'tcx>>) {
debug!("MirBorrowckCtxt::process_terminator({:?}, {:?})", bb, term);
}
}
......@@ -40,6 +40,7 @@
use std::mem;
use std::rc::Rc;
use syntax::ast;
use syntax::attr::AttrMetaMethods;
use syntax::codemap::Span;
use syntax::errors::DiagnosticBuilder;
......@@ -49,12 +50,16 @@
use rustc_front::intravisit::{Visitor, FnKind};
use rustc_front::util as hir_util;
use rustc::mir::mir_map::MirMap;
pub mod check_loans;
pub mod gather_loans;
pub mod move_data;
mod mir;
#[derive(Clone, Copy)]
pub struct LoanDataFlowOperator;
......@@ -66,15 +71,13 @@ fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v FnDecl,
match fk {
FnKind::ItemFn(..) |
FnKind::Method(..) => {
let new_free_region_map = self.tcx.free_region_map(id);
let old_free_region_map =
mem::replace(&mut self.free_region_map, new_free_region_map);
borrowck_fn(self, fk, fd, b, s, id);
self.free_region_map = old_free_region_map;
self.with_temp_region_map(id, |this| {
borrowck_fn(this, fk, fd, b, s, id, fk.attrs())
});
}
FnKind::Closure(..) => {
borrowck_fn(self, fk, fd, b, s, id);
borrowck_fn(self, fk, fd, b, s, id, fk.attrs());
}
}
}
......@@ -98,9 +101,10 @@ fn visit_impl_item(&mut self, ii: &hir::ImplItem) {
}
}
pub fn check_crate(tcx: &TyCtxt) {
pub fn check_crate<'tcx>(tcx: &TyCtxt<'tcx>, mir_map: &MirMap<'tcx>) {
let mut bccx = BorrowckCtxt {
tcx: tcx,
mir_map: Some(mir_map),
free_region_map: FreeRegionMap::new(),
stats: BorrowStats {
loaned_paths_same: 0,
......@@ -159,8 +163,17 @@ fn borrowck_fn(this: &mut BorrowckCtxt,
decl: &hir::FnDecl,
body: &hir::Block,
sp: Span,
id: ast::NodeId) {
id: ast::NodeId,
attributes: &[ast::Attribute]) {
debug!("borrowck_fn(id={})", id);
if attributes.iter().any(|item| item.check_name("rustc_mir_borrowck")) {
let mir = this.mir_map.unwrap().map.get(&id).unwrap();
this.with_temp_region_map(id, |this| {
mir::borrowck_mir(this, fk, decl, mir, body, sp, id, attributes)
});
}
let cfg = cfg::CFG::new(this.tcx, body);
let AnalysisData { all_loans,
loans: loan_dfcx,
......@@ -233,6 +246,7 @@ fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>,
/// the `BorrowckCtxt` itself , e.g. the flowgraph visualizer.
pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>(
tcx: &'a TyCtxt<'tcx>,
mir_map: Option<&'a MirMap<'tcx>>,
fn_parts: FnParts<'a>,
cfg: &cfg::CFG)
-> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'a, 'tcx>)
......@@ -240,6 +254,7 @@ pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>(
let mut bccx = BorrowckCtxt {
tcx: tcx,
mir_map: mir_map,
free_region_map: FreeRegionMap::new(),
stats: BorrowStats {
loaned_paths_same: 0,
......@@ -279,9 +294,13 @@ pub struct BorrowckCtxt<'a, 'tcx: 'a> {
free_region_map: FreeRegionMap,
// Statistics:
stats: BorrowStats
stats: BorrowStats,
// NodeId to MIR mapping (for methods that carry the #[rustc_mir] attribute).
mir_map: Option<&'a MirMap<'tcx>>,
}
#[derive(Clone)]
struct BorrowStats {
loaned_paths_same: usize,
loaned_paths_imm: usize,
......@@ -574,6 +593,15 @@ pub enum MovedValueUseKind {
// Misc
impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
fn with_temp_region_map<F>(&mut self, id: ast::NodeId, f: F)
where F: for <'b> FnOnce(&'b mut BorrowckCtxt<'a, 'tcx>)
{
let new_free_region_map = self.tcx.free_region_map(id);
let old_free_region_map = mem::replace(&mut self.free_region_map, new_free_region_map);
f(self);
self.free_region_map = old_free_region_map;
}
pub fn is_subregion_of(&self, r_sub: ty::Region, r_sup: ty::Region)
-> bool
{
......
......@@ -23,7 +23,7 @@
#![feature(rustc_diagnostic_macros)]
#![feature(rustc_private)]
#![feature(staged_api)]
#![feature(associated_consts)]
#[macro_use] extern crate log;
#[macro_use] extern crate syntax;
......@@ -32,6 +32,7 @@
extern crate graphviz as dot;
extern crate rustc;
extern crate rustc_front;
extern crate rustc_mir;
pub use borrowck::check_crate;
pub use borrowck::build_borrowck_dataflow_data_for_fn;
......@@ -42,6 +43,7 @@
pub mod diagnostics;
mod borrowck;
mod bitslice;
pub mod graphviz;
......
......@@ -880,7 +880,7 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session,
time(time_passes,
"borrow checking",
|| borrowck::check_crate(tcx));
|| borrowck::check_crate(tcx, &mir_map));
// Avoid overwhelming user with errors if type checking failed.
// I'm not sure how helpful this is, to be honest, but it avoids
......
......@@ -56,6 +56,8 @@
use rustc_front::lowering::{lower_crate, LoweringContext};
use rustc_front::print::pprust as pprust_hir;
use rustc::mir::mir_map::MirMap;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum PpSourceMode {
PpmNormal,
......@@ -875,9 +877,10 @@ pub fn pretty_print_input(sess: Session,
&arenas,
&id,
resolve::MakeGlobMap::No,
|tcx, _, _, _| {
|tcx, mir_map, _, _| {
print_flowgraph(variants,
tcx,
mir_map.as_ref(),
code,
mode,
out)
......@@ -911,12 +914,13 @@ pub fn pretty_print_input(sess: Session,
}
}
fn print_flowgraph<W: Write>(variants: Vec<borrowck_dot::Variant>,
tcx: &TyCtxt,
code: blocks::Code,
mode: PpFlowGraphMode,
mut out: W)
-> io::Result<()> {
fn print_flowgraph<'tcx, W: Write>(variants: Vec<borrowck_dot::Variant>,
tcx: &TyCtxt<'tcx>,
mir_map: Option<&MirMap<'tcx>>,
code: blocks::Code,
mode: PpFlowGraphMode,
mut out: W)
-> io::Result<()> {
let cfg = match code {
blocks::BlockCode(block) => cfg::CFG::new(tcx, &block),
blocks::FnLikeCode(fn_like) => cfg::CFG::new(tcx, &fn_like.body()),
......@@ -942,6 +946,7 @@ fn print_flowgraph<W: Write>(variants: Vec<borrowck_dot::Variant>,
blocks::FnLikeCode(fn_like) => {
let (bccx, analysis_data) =
borrowck::build_borrowck_dataflow_data_for_fn(tcx,
mir_map,
fn_like.to_fn_parts(),
&cfg);
......
......@@ -43,16 +43,33 @@ pub fn write_mir_graphviz<'a, 't, W, I>(tcx: &ty::TyCtxt<'t>, iter: I, w: &mut W
Ok(())
}
/// Write a graphviz DOT node for the given basic block.
fn write_node<W: Write>(block: BasicBlock, mir: &Mir, w: &mut W) -> io::Result<()> {
/// Write a graphviz HTML-styled label for the given basic block, with
/// all necessary escaping already performed. (This is suitable for
/// emitting directly, as is done in this module, or for use with the
/// LabelText::HtmlStr from libgraphviz.)
///
/// `init` and `fini` are callbacks for emitting additional rows of
/// data (using HTML enclosed with `<tr>` in the emitted text).
pub fn write_node_label<W: Write, INIT, FINI>(block: BasicBlock,
mir: &Mir,
w: &mut W,
num_cols: u32,
init: INIT,
fini: FINI) -> io::Result<()>
where INIT: Fn(&mut W) -> io::Result<()>,
FINI: Fn(&mut W) -> io::Result<()>
{
let data = mir.basic_block_data(block);
// Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
try!(write!(w, r#" {} [shape="none", label=<"#, node(block)));
try!(write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#));
// Basic block number at the top.
try!(write!(w, r#"<tr><td bgcolor="gray" align="center">{}</td></tr>"#, block.index()));
try!(write!(w, r#"<tr><td {attrs} colspan="{colspan}">{blk}</td></tr>"#,
attrs=r#"bgcolor="gray" align="center""#,
colspan=num_cols,
blk=block.index()));
try!(init(w));
// List of statements in the middle.
if !data.statements.is_empty() {
......@@ -69,8 +86,19 @@ fn write_node<W: Write>(block: BasicBlock, mir: &Mir, w: &mut W) -> io::Result<(
data.terminator().fmt_head(&mut terminator_head).unwrap();
try!(write!(w, r#"<tr><td align="left">{}</td></tr>"#, dot::escape_html(&terminator_head)));
// Close the table, node label, and the node itself.
writeln!(w, "</table>>];")
try!(fini(w));
// Close the table
writeln!(w, "</table>")
}
/// Write a graphviz DOT node for the given basic block.
fn write_node<W: Write>(block: BasicBlock, mir: &Mir, w: &mut W) -> io::Result<()> {
// Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
try!(write!(w, r#" {} [shape="none", label=<"#, node(block)));
try!(write_node_label(block, mir, w, 1, |_| Ok(()), |_| Ok(())));
// Close the node label and the node itself.
writeln!(w, ">];")
}
/// Write graphviz DOT edges with labels between the given basic block and all of its successors.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册