Implement new dataflow framework and cursor
This commit is contained in:
parent
2fb4c4472e
commit
07c51f605a
265
src/librustc_mir/dataflow/generic/cursor.rs
Normal file
265
src/librustc_mir/dataflow/generic/cursor.rs
Normal file
@ -0,0 +1,265 @@
|
||||
//! Random access inspection of the results of a dataflow analysis.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use rustc::mir::{self, BasicBlock, Location};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
|
||||
use super::{Analysis, Results};
|
||||
|
||||
/// A `ResultsCursor` that borrows the underlying `Results`.
|
||||
pub type ResultsRefCursor<'a, 'mir, 'tcx, A> = ResultsCursor<'mir, 'tcx, A, &'a Results<'tcx, A>>;
|
||||
|
||||
/// Allows random access inspection of the results of a dataflow analysis.
|
||||
///
|
||||
/// This cursor only has linear performance within a basic block when its statements are visited in
|
||||
/// order. In the worst case—when statements are visited in *reverse* order—performance will be
|
||||
/// quadratic in the number of statements in the block. The order in which basic blocks are
|
||||
/// inspected has no impact on performance.
|
||||
///
|
||||
/// A `ResultsCursor` can either own (the default) or borrow the dataflow results it inspects. The
|
||||
/// type of ownership is determined by `R` (see `ResultsRefCursor` above).
|
||||
pub struct ResultsCursor<'mir, 'tcx, A, R = Results<'tcx, A>>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
results: R,
|
||||
state: BitSet<A::Idx>,
|
||||
|
||||
pos: CursorPosition,
|
||||
|
||||
/// When this flag is set, the cursor is pointing at a `Call` terminator whose call return
|
||||
/// effect has been applied to `state`.
|
||||
///
|
||||
/// This flag helps to ensure that multiple calls to `seek_after_assume_call_returns` with the
|
||||
/// same target will result in exactly one invocation of `apply_call_return_effect`. It is
|
||||
/// sufficient to clear this only in `seek_to_block_start`, since seeking away from a
|
||||
/// terminator will always require a cursor reset.
|
||||
call_return_effect_applied: bool,
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
R: Borrow<Results<'tcx, A>>,
|
||||
{
|
||||
/// Returns a new cursor for `results` that points to the start of the `START_BLOCK`.
|
||||
pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self {
|
||||
ResultsCursor {
|
||||
body,
|
||||
pos: CursorPosition::BlockStart(mir::START_BLOCK),
|
||||
state: results.borrow().entry_sets[mir::START_BLOCK].clone(),
|
||||
call_return_effect_applied: false,
|
||||
results,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `Analysis` used to generate the underlying results.
|
||||
pub fn analysis(&self) -> &A {
|
||||
&self.results.borrow().analysis
|
||||
}
|
||||
|
||||
/// Returns the dataflow state at the current location.
|
||||
pub fn get(&self) -> &BitSet<A::Idx> {
|
||||
&self.state
|
||||
}
|
||||
|
||||
/// Resets the cursor to the start of the given basic block.
|
||||
pub fn seek_to_block_start(&mut self, block: BasicBlock) {
|
||||
self.state.overwrite(&self.results.borrow().entry_sets[block]);
|
||||
self.pos = CursorPosition::BlockStart(block);
|
||||
self.call_return_effect_applied = false;
|
||||
}
|
||||
|
||||
/// Advances the cursor to hold all effects up to and including to the "before" effect of the
|
||||
/// statement (or terminator) at the given location.
|
||||
///
|
||||
/// If you wish to observe the full effect of a statement or terminator, not just the "before"
|
||||
/// effect, use `seek_after` or `seek_after_assume_call_returns`.
|
||||
pub fn seek_before(&mut self, target: Location) {
|
||||
assert!(target <= self.body.terminator_loc(target.block));
|
||||
self._seek(target, false);
|
||||
}
|
||||
|
||||
/// Advances the cursor to hold the full effect of all statements (and possibly closing
|
||||
/// terminators) up to and including the `target`.
|
||||
///
|
||||
/// If the `target` is a `Call` terminator, any call return effect for that terminator will
|
||||
/// **not** be observed. Use `seek_after_assume_call_returns` if you wish to observe the call
|
||||
/// return effect.
|
||||
pub fn seek_after(&mut self, target: Location) {
|
||||
assert!(target <= self.body.terminator_loc(target.block));
|
||||
|
||||
// If we have already applied the call return effect, we are currently pointing at a `Call`
|
||||
// terminator. Unconditionally reset the dataflow cursor, since there is no way to "undo"
|
||||
// the call return effect.
|
||||
if self.call_return_effect_applied {
|
||||
self.seek_to_block_start(target.block);
|
||||
}
|
||||
|
||||
self._seek(target, true);
|
||||
}
|
||||
|
||||
/// Advances the cursor to hold all effects up to and including of the statement (or
|
||||
/// terminator) at the given location.
|
||||
///
|
||||
/// If the `target` is a `Call` terminator, any call return effect for that terminator will
|
||||
/// be observed. Use `seek_after` if you do **not** wish to observe the call return effect.
|
||||
pub fn seek_after_assume_call_returns(&mut self, target: Location) {
|
||||
let terminator_loc = self.body.terminator_loc(target.block);
|
||||
assert!(target.statement_index <= terminator_loc.statement_index);
|
||||
|
||||
self._seek(target, true);
|
||||
|
||||
if target != terminator_loc {
|
||||
return;
|
||||
}
|
||||
|
||||
let terminator = self.body.basic_blocks()[target.block].terminator();
|
||||
if let mir::TerminatorKind::Call {
|
||||
destination: Some((return_place, _)), func, args, ..
|
||||
} = &terminator.kind
|
||||
{
|
||||
if !self.call_return_effect_applied {
|
||||
self.call_return_effect_applied = true;
|
||||
self.results.borrow().analysis.apply_call_return_effect(
|
||||
&mut self.state,
|
||||
target.block,
|
||||
func,
|
||||
args,
|
||||
return_place,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _seek(&mut self, target: Location, apply_after_effect_at_target: bool) {
|
||||
use CursorPosition::*;
|
||||
|
||||
match self.pos {
|
||||
// Return early if we are already at the target location.
|
||||
Before(curr) if curr == target && !apply_after_effect_at_target => return,
|
||||
After(curr) if curr == target && apply_after_effect_at_target => return,
|
||||
|
||||
// Otherwise, we must reset to the start of the target block if...
|
||||
|
||||
// we are in a different block entirely.
|
||||
BlockStart(block) | Before(Location { block, .. }) | After(Location { block, .. })
|
||||
if block != target.block =>
|
||||
{
|
||||
self.seek_to_block_start(target.block)
|
||||
}
|
||||
|
||||
// we are in the same block but have advanced past the target statement.
|
||||
Before(curr) | After(curr) if curr.statement_index > target.statement_index => {
|
||||
self.seek_to_block_start(target.block)
|
||||
}
|
||||
|
||||
// we have already applied the entire effect of a statement but only wish to observe
|
||||
// its "before" effect.
|
||||
After(curr)
|
||||
if curr.statement_index == target.statement_index
|
||||
&& !apply_after_effect_at_target =>
|
||||
{
|
||||
self.seek_to_block_start(target.block)
|
||||
}
|
||||
|
||||
// N.B., `call_return_effect_applied` is checked in `seek_after`, not here.
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let analysis = &self.results.borrow().analysis;
|
||||
let block_data = &self.body.basic_blocks()[target.block];
|
||||
|
||||
// At this point, the cursor is in the same block as the target location at an earlier
|
||||
// statement.
|
||||
debug_assert_eq!(target.block, self.pos.block());
|
||||
|
||||
// Find the first statement whose transfer function has not yet been applied.
|
||||
let first_unapplied_statement = match self.pos {
|
||||
BlockStart(_) => 0,
|
||||
After(Location { statement_index, .. }) => statement_index + 1,
|
||||
|
||||
// If we have only applied the "before" effect for the current statement, apply the
|
||||
// remainder before continuing.
|
||||
Before(curr) => {
|
||||
if curr.statement_index == block_data.statements.len() {
|
||||
let terminator = block_data.terminator();
|
||||
analysis.apply_terminator_effect(&mut self.state, terminator, curr);
|
||||
} else {
|
||||
let statement = &block_data.statements[curr.statement_index];
|
||||
analysis.apply_statement_effect(&mut self.state, statement, curr);
|
||||
}
|
||||
|
||||
// If all we needed to do was go from `Before` to `After` in the same statement,
|
||||
// we are now done.
|
||||
if curr.statement_index == target.statement_index {
|
||||
debug_assert!(apply_after_effect_at_target);
|
||||
self.pos = After(target);
|
||||
return;
|
||||
}
|
||||
|
||||
curr.statement_index + 1
|
||||
}
|
||||
};
|
||||
|
||||
// We have now applied all effects prior to `first_unapplied_statement`.
|
||||
|
||||
// Apply the effects of all statements before `target`.
|
||||
let mut location = Location { block: target.block, statement_index: 0 };
|
||||
for statement_index in first_unapplied_statement..target.statement_index {
|
||||
location.statement_index = statement_index;
|
||||
let statement = &block_data.statements[statement_index];
|
||||
analysis.apply_before_statement_effect(&mut self.state, statement, location);
|
||||
analysis.apply_statement_effect(&mut self.state, statement, location);
|
||||
}
|
||||
|
||||
// Apply the effect of the statement (or terminator) at `target`.
|
||||
location.statement_index = target.statement_index;
|
||||
if target.statement_index == block_data.statements.len() {
|
||||
let terminator = &block_data.terminator();
|
||||
analysis.apply_before_terminator_effect(&mut self.state, terminator, location);
|
||||
|
||||
if apply_after_effect_at_target {
|
||||
analysis.apply_terminator_effect(&mut self.state, terminator, location);
|
||||
self.pos = After(target);
|
||||
} else {
|
||||
self.pos = Before(target);
|
||||
}
|
||||
} else {
|
||||
let statement = &block_data.statements[target.statement_index];
|
||||
analysis.apply_before_statement_effect(&mut self.state, statement, location);
|
||||
|
||||
if apply_after_effect_at_target {
|
||||
analysis.apply_statement_effect(&mut self.state, statement, location);
|
||||
self.pos = After(target)
|
||||
} else {
|
||||
self.pos = Before(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum CursorPosition {
|
||||
/// No effects within this block have been applied.
|
||||
BlockStart(BasicBlock),
|
||||
|
||||
/// Only the "before" effect of the statement (or terminator) at this location has been
|
||||
/// applied (along with the effects of all previous statements).
|
||||
Before(Location),
|
||||
|
||||
/// The effects of all statements up to and including the one at this location have been
|
||||
/// applied.
|
||||
After(Location),
|
||||
}
|
||||
|
||||
impl CursorPosition {
|
||||
fn block(&self) -> BasicBlock {
|
||||
match *self {
|
||||
Self::BlockStart(block) => block,
|
||||
Self::Before(loc) | Self::After(loc) => loc.block,
|
||||
}
|
||||
}
|
||||
}
|
421
src/librustc_mir/dataflow/generic/engine.rs
Normal file
421
src/librustc_mir/dataflow/generic/engine.rs
Normal file
@ -0,0 +1,421 @@
|
||||
//! A solver for dataflow problems.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rustc::mir::{self, traversal, BasicBlock, Location};
|
||||
use rustc::ty::TyCtxt;
|
||||
use rustc_data_structures::work_queue::WorkQueue;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use syntax::ast;
|
||||
|
||||
use super::graphviz;
|
||||
use super::{Analysis, GenKillAnalysis, GenKillSet, Results};
|
||||
|
||||
/// A solver for dataflow problems.
|
||||
pub struct Engine<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
bits_per_block: usize,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
def_id: DefId,
|
||||
dead_unwinds: Option<&'a BitSet<BasicBlock>>,
|
||||
entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
|
||||
analysis: A,
|
||||
|
||||
/// Cached, cumulative transfer functions for each block.
|
||||
trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
|
||||
}
|
||||
|
||||
impl<A> Engine<'a, 'tcx, A>
|
||||
where
|
||||
A: GenKillAnalysis<'tcx>,
|
||||
{
|
||||
/// Creates a new `Engine` to solve a gen-kill dataflow problem.
|
||||
pub fn new_gen_kill(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
def_id: DefId,
|
||||
analysis: A,
|
||||
) -> Self {
|
||||
let bits_per_block = analysis.bits_per_block(body);
|
||||
let mut trans_for_block =
|
||||
IndexVec::from_elem(GenKillSet::identity(bits_per_block), body.basic_blocks());
|
||||
|
||||
// Compute cumulative block transfer functions.
|
||||
//
|
||||
// FIXME: we may want to skip this if the MIR is acyclic, since we will never access a
|
||||
// block transfer function more than once.
|
||||
|
||||
for (block, block_data) in body.basic_blocks().iter_enumerated() {
|
||||
let trans = &mut trans_for_block[block];
|
||||
|
||||
for (i, statement) in block_data.statements.iter().enumerate() {
|
||||
let loc = Location { block, statement_index: i };
|
||||
analysis.before_statement_effect(trans, statement, loc);
|
||||
analysis.statement_effect(trans, statement, loc);
|
||||
}
|
||||
|
||||
if let Some(terminator) = &block_data.terminator {
|
||||
let loc = Location { block, statement_index: block_data.statements.len() };
|
||||
analysis.before_terminator_effect(trans, terminator, loc);
|
||||
analysis.terminator_effect(trans, terminator, loc);
|
||||
}
|
||||
}
|
||||
|
||||
Self::new(tcx, body, def_id, analysis, Some(trans_for_block))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Engine<'a, 'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
/// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
|
||||
/// function.
|
||||
///
|
||||
/// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
|
||||
/// better performance.
|
||||
pub fn new_generic(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
def_id: DefId,
|
||||
analysis: A,
|
||||
) -> Self {
|
||||
Self::new(tcx, body, def_id, analysis, None)
|
||||
}
|
||||
|
||||
fn new(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
def_id: DefId,
|
||||
analysis: A,
|
||||
trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
|
||||
) -> Self {
|
||||
let bits_per_block = analysis.bits_per_block(body);
|
||||
|
||||
let bottom_value_set = if A::BOTTOM_VALUE == true {
|
||||
BitSet::new_filled(bits_per_block)
|
||||
} else {
|
||||
BitSet::new_empty(bits_per_block)
|
||||
};
|
||||
|
||||
let mut entry_sets = IndexVec::from_elem(bottom_value_set, body.basic_blocks());
|
||||
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
|
||||
|
||||
Engine {
|
||||
analysis,
|
||||
bits_per_block,
|
||||
tcx,
|
||||
body,
|
||||
def_id,
|
||||
dead_unwinds: None,
|
||||
entry_sets,
|
||||
trans_for_block,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
|
||||
self.dead_unwinds = Some(dead_unwinds);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn iterate_to_fixpoint(mut self) -> Results<'tcx, A> {
|
||||
let mut temp_state = BitSet::new_empty(self.bits_per_block);
|
||||
|
||||
let mut dirty_queue: WorkQueue<BasicBlock> =
|
||||
WorkQueue::with_none(self.body.basic_blocks().len());
|
||||
|
||||
for (bb, _) in traversal::reverse_postorder(self.body) {
|
||||
dirty_queue.insert(bb);
|
||||
}
|
||||
|
||||
// Add blocks that are not reachable from START_BLOCK to the work queue. These blocks will
|
||||
// be processed after the ones added above.
|
||||
for bb in self.body.basic_blocks().indices() {
|
||||
dirty_queue.insert(bb);
|
||||
}
|
||||
|
||||
while let Some(bb) = dirty_queue.pop() {
|
||||
let bb_data = &self.body[bb];
|
||||
let on_entry = &self.entry_sets[bb];
|
||||
|
||||
temp_state.overwrite(on_entry);
|
||||
self.apply_whole_block_effect(&mut temp_state, bb, bb_data);
|
||||
|
||||
self.propagate_bits_into_graph_successors_of(
|
||||
&mut temp_state,
|
||||
(bb, bb_data),
|
||||
&mut dirty_queue,
|
||||
);
|
||||
}
|
||||
|
||||
let Engine { tcx, body, def_id, trans_for_block, entry_sets, analysis, .. } = self;
|
||||
let results = Results { analysis, entry_sets };
|
||||
|
||||
let res = write_graphviz_results(tcx, def_id, body, &results, trans_for_block);
|
||||
if let Err(e) = res {
|
||||
warn!("Failed to write graphviz dataflow results: {}", e);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Applies the cumulative effect of an entire block, excluding the call return effect if one
|
||||
/// exists.
|
||||
fn apply_whole_block_effect(
|
||||
&self,
|
||||
state: &mut BitSet<A::Idx>,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
) {
|
||||
// Use the cached block transfer function if available.
|
||||
if let Some(trans_for_block) = &self.trans_for_block {
|
||||
trans_for_block[block].apply(state);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise apply effects one-by-one.
|
||||
|
||||
for (statement_index, statement) in block_data.statements.iter().enumerate() {
|
||||
let location = Location { block, statement_index };
|
||||
self.analysis.apply_before_statement_effect(state, statement, location);
|
||||
self.analysis.apply_statement_effect(state, statement, location);
|
||||
}
|
||||
|
||||
let terminator = block_data.terminator();
|
||||
let location = Location { block, statement_index: block_data.statements.len() };
|
||||
self.analysis.apply_before_terminator_effect(state, terminator, location);
|
||||
self.analysis.apply_terminator_effect(state, terminator, location);
|
||||
}
|
||||
|
||||
fn propagate_bits_into_graph_successors_of(
|
||||
&mut self,
|
||||
in_out: &mut BitSet<A::Idx>,
|
||||
(bb, bb_data): (BasicBlock, &'a mir::BasicBlockData<'tcx>),
|
||||
dirty_list: &mut WorkQueue<BasicBlock>,
|
||||
) {
|
||||
use mir::TerminatorKind::*;
|
||||
|
||||
match bb_data.terminator().kind {
|
||||
Return | Resume | Abort | GeneratorDrop | Unreachable => {}
|
||||
|
||||
Goto { target }
|
||||
| Assert { target, cleanup: None, .. }
|
||||
| Yield { resume: target, drop: None, .. }
|
||||
| Drop { target, location: _, unwind: None }
|
||||
| DropAndReplace { target, value: _, location: _, unwind: None } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list)
|
||||
}
|
||||
|
||||
Yield { resume: target, drop: Some(drop), .. } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
|
||||
self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
|
||||
}
|
||||
|
||||
Assert { target, cleanup: Some(unwind), .. }
|
||||
| Drop { target, location: _, unwind: Some(unwind) }
|
||||
| DropAndReplace { target, value: _, location: _, unwind: Some(unwind) } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
|
||||
if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
|
||||
self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
|
||||
}
|
||||
}
|
||||
|
||||
SwitchInt { ref targets, .. } => {
|
||||
for target in targets {
|
||||
self.propagate_bits_into_entry_set_for(in_out, *target, dirty_list);
|
||||
}
|
||||
}
|
||||
|
||||
Call { cleanup, ref destination, ref func, ref args, .. } => {
|
||||
if let Some(unwind) = cleanup {
|
||||
if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
|
||||
self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((ref dest_place, dest_bb)) = *destination {
|
||||
// N.B.: This must be done *last*, otherwise the unwind path will see the call
|
||||
// return effect.
|
||||
self.analysis.apply_call_return_effect(in_out, bb, func, args, dest_place);
|
||||
self.propagate_bits_into_entry_set_for(in_out, dest_bb, dirty_list);
|
||||
}
|
||||
}
|
||||
|
||||
FalseEdges { real_target, imaginary_target } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
|
||||
self.propagate_bits_into_entry_set_for(in_out, imaginary_target, dirty_list);
|
||||
}
|
||||
|
||||
FalseUnwind { real_target, unwind } => {
|
||||
self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
|
||||
if let Some(unwind) = unwind {
|
||||
if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
|
||||
self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bits_into_entry_set_for(
|
||||
&mut self,
|
||||
in_out: &BitSet<A::Idx>,
|
||||
bb: BasicBlock,
|
||||
dirty_queue: &mut WorkQueue<BasicBlock>,
|
||||
) {
|
||||
let entry_set = &mut self.entry_sets[bb];
|
||||
let set_changed = self.analysis.join(entry_set, &in_out);
|
||||
if set_changed {
|
||||
dirty_queue.insert(bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Graphviz
|
||||
|
||||
/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
|
||||
/// `rustc_mir` attributes.
|
||||
fn write_graphviz_results<A>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
def_id: DefId,
|
||||
body: &mir::Body<'tcx>,
|
||||
results: &Results<'tcx, A>,
|
||||
block_transfer_functions: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
|
||||
) -> std::io::Result<()>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
let attrs = match RustcMirAttrs::parse(tcx, def_id) {
|
||||
Ok(attrs) => attrs,
|
||||
|
||||
// Invalid `rustc_mir` attrs will be reported using `span_err`.
|
||||
Err(()) => return Ok(()),
|
||||
};
|
||||
|
||||
let path = match attrs.output_path(A::NAME) {
|
||||
Some(path) => path,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let bits_per_block = results.analysis.bits_per_block(body);
|
||||
|
||||
let mut formatter: Box<dyn graphviz::StateFormatter<'tcx, _>> = match attrs.formatter {
|
||||
Some(sym::two_phase) => Box::new(graphviz::TwoPhaseDiff::new(bits_per_block)),
|
||||
Some(sym::gen_kill) => {
|
||||
if let Some(trans_for_block) = block_transfer_functions {
|
||||
Box::new(graphviz::BlockTransferFunc::new(body, trans_for_block))
|
||||
} else {
|
||||
Box::new(graphviz::SimpleDiff::new(bits_per_block))
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the `SimpleDiff` output style.
|
||||
_ => Box::new(graphviz::SimpleDiff::new(bits_per_block)),
|
||||
};
|
||||
|
||||
debug!("printing dataflow results for {:?} to {}", def_id, path.display());
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let graphviz = graphviz::Formatter::new(body, def_id, results, &mut *formatter);
|
||||
dot::render(&graphviz, &mut buf)?;
|
||||
fs::write(&path, buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RustcMirAttrs {
|
||||
basename_and_suffix: Option<PathBuf>,
|
||||
formatter: Option<Symbol>,
|
||||
}
|
||||
|
||||
impl RustcMirAttrs {
|
||||
fn parse(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<Self, ()> {
|
||||
let attrs = tcx.get_attrs(def_id);
|
||||
|
||||
let mut result = Ok(());
|
||||
let mut ret = RustcMirAttrs::default();
|
||||
|
||||
let rustc_mir_attrs = attrs
|
||||
.into_iter()
|
||||
.filter(|attr| attr.check_name(sym::rustc_mir))
|
||||
.flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));
|
||||
|
||||
for attr in rustc_mir_attrs {
|
||||
let attr_result = if attr.check_name(sym::borrowck_graphviz_postflow) {
|
||||
Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| {
|
||||
let path = PathBuf::from(s.to_string());
|
||||
match path.file_name() {
|
||||
Some(_) => Ok(path),
|
||||
None => {
|
||||
tcx.sess.span_err(attr.span(), "path must end in a filename");
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if attr.check_name(sym::borrowck_graphviz_format) {
|
||||
Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s {
|
||||
sym::gen_kill | sym::two_phase => Ok(s),
|
||||
_ => {
|
||||
tcx.sess.span_err(attr.span(), "unknown formatter");
|
||||
Err(())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
result = result.and(attr_result);
|
||||
}
|
||||
|
||||
result.map(|()| ret)
|
||||
}
|
||||
|
||||
fn set_field<T>(
|
||||
field: &mut Option<T>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
attr: &ast::NestedMetaItem,
|
||||
mapper: impl FnOnce(Symbol) -> Result<T, ()>,
|
||||
) -> Result<(), ()> {
|
||||
if field.is_some() {
|
||||
tcx.sess
|
||||
.span_err(attr.span(), &format!("duplicate values for `{}`", attr.name_or_empty()));
|
||||
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if let Some(s) = attr.value_str() {
|
||||
*field = Some(mapper(s)?);
|
||||
Ok(())
|
||||
} else {
|
||||
tcx.sess
|
||||
.span_err(attr.span(), &format!("`{}` requires an argument", attr.name_or_empty()));
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path where dataflow results should be written, or `None`
|
||||
/// `borrowck_graphviz_postflow` was not specified.
|
||||
///
|
||||
/// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
|
||||
///
|
||||
/// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
|
||||
fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
|
||||
let mut ret = self.basename_and_suffix.as_ref().cloned()?;
|
||||
let suffix = ret.file_name().unwrap(); // Checked when parsing attrs
|
||||
|
||||
let mut file_name: OsString = analysis_name.into();
|
||||
file_name.push("_");
|
||||
file_name.push(suffix);
|
||||
ret.set_file_name(file_name);
|
||||
|
||||
Some(ret)
|
||||
}
|
||||
}
|
309
src/librustc_mir/dataflow/generic/mod.rs
Normal file
309
src/librustc_mir/dataflow/generic/mod.rs
Normal file
@ -0,0 +1,309 @@
|
||||
//! A framework for expressing dataflow problems.
|
||||
|
||||
use std::io;
|
||||
|
||||
use rustc::mir::{self, BasicBlock, Location};
|
||||
use rustc_index::bit_set::{BitSet, HybridBitSet};
|
||||
use rustc_index::vec::{Idx, IndexVec};
|
||||
|
||||
use crate::dataflow::BottomValue;
|
||||
|
||||
mod cursor;
|
||||
mod engine;
|
||||
mod graphviz;
|
||||
|
||||
pub use self::cursor::{ResultsCursor, ResultsRefCursor};
|
||||
pub use self::engine::Engine;
|
||||
|
||||
/// A dataflow analysis that has converged to fixpoint.
|
||||
pub struct Results<'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
pub analysis: A,
|
||||
entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
|
||||
}
|
||||
|
||||
impl<A> Results<'tcx, A>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
pub fn into_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
|
||||
ResultsCursor::new(body, self)
|
||||
}
|
||||
|
||||
pub fn on_block_entry(&self, block: BasicBlock) -> &BitSet<A::Idx> {
|
||||
&self.entry_sets[block]
|
||||
}
|
||||
}
|
||||
|
||||
/// Define the domain of a dataflow problem.
|
||||
///
|
||||
/// This trait specifies the lattice on which this analysis operates. For now, this must be a
|
||||
/// powerset of values of type `Idx`. The elements of this lattice are represented with a `BitSet`
|
||||
/// and referred to as the state vector.
|
||||
///
|
||||
/// This trait also defines the initial value for the dataflow state upon entry to the
|
||||
/// `START_BLOCK`, as well as some names used to refer to this analysis when debugging.
|
||||
pub trait AnalysisDomain<'tcx>: BottomValue {
|
||||
/// The type of the elements in the state vector.
|
||||
type Idx: Idx;
|
||||
|
||||
/// A descriptive name for this analysis. Used only for debugging.
|
||||
///
|
||||
/// This name should be brief and contain no spaces, periods or other characters that are not
|
||||
/// suitable as part of a filename.
|
||||
const NAME: &'static str;
|
||||
|
||||
/// The size of the state vector.
|
||||
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize;
|
||||
|
||||
/// Mutates the entry set of the `START_BLOCK` to contain the initial state for dataflow
|
||||
/// analysis.
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>);
|
||||
|
||||
/// Prints an element in the state vector for debugging.
|
||||
fn pretty_print_idx(&self, w: &mut impl io::Write, idx: Self::Idx) -> io::Result<()> {
|
||||
write!(w, "{:?}", idx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define a dataflow problem with an arbitrarily complex transfer function.
|
||||
pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
||||
/// Updates the current dataflow state with the effect of evaluating a statement.
|
||||
fn apply_statement_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
);
|
||||
|
||||
/// Updates the current dataflow state with an effect that occurs immediately *before* the
|
||||
/// given statement.
|
||||
///
|
||||
/// This method is useful if the consumer of the results of this analysis needs only to observe
|
||||
/// *part* of the effect of a statement (e.g. for two-phase borrows). As a general rule,
|
||||
/// analyses should not implement this without implementing `apply_statement_effect`.
|
||||
fn apply_before_statement_effect(
|
||||
&self,
|
||||
_state: &mut BitSet<Self::Idx>,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Updates the current dataflow state with the effect of evaluating a terminator.
|
||||
///
|
||||
/// The effect of a successful return from a `Call` terminator should **not** be accounted for
|
||||
/// in this function. That should go in `apply_call_return_effect`. For example, in the
|
||||
/// `InitializedPlaces` analyses, the return place for a function call is not marked as
|
||||
/// initialized here.
|
||||
fn apply_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
);
|
||||
|
||||
/// Updates the current dataflow state with an effect that occurs immediately *before* the
|
||||
/// given terminator.
|
||||
///
|
||||
/// This method is useful if the consumer of the results of this analysis needs only to observe
|
||||
/// *part* of the effect of a terminator (e.g. for two-phase borrows). As a general rule,
|
||||
/// analyses should not implement this without implementing `apply_terminator_effect`.
|
||||
fn apply_before_terminator_effect(
|
||||
&self,
|
||||
_state: &mut BitSet<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Updates the current dataflow state with the effect of a successful return from a `Call`
|
||||
/// terminator.
|
||||
///
|
||||
/// This is separate from `apply_terminator_effect` to properly track state across unwind
|
||||
/// edges.
|
||||
fn apply_call_return_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
block: BasicBlock,
|
||||
func: &mir::Operand<'tcx>,
|
||||
args: &[mir::Operand<'tcx>],
|
||||
return_place: &mir::Place<'tcx>,
|
||||
);
|
||||
}
|
||||
|
||||
/// Define a gen/kill dataflow problem.
|
||||
///
|
||||
/// Each method in this trait has a corresponding one in `Analysis`. However, these methods only
|
||||
/// allow modification of the dataflow state via "gen" and "kill" operations. By defining transfer
|
||||
/// functions for each statement in this way, the transfer function for an entire basic block can
|
||||
/// be computed efficiently.
|
||||
///
|
||||
/// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`.
|
||||
pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
|
||||
/// See `Analysis::apply_statement_effect`.
|
||||
fn statement_effect(
|
||||
&self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
);
|
||||
|
||||
/// See `Analysis::apply_before_statement_effect`.
|
||||
fn before_statement_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
}
|
||||
|
||||
/// See `Analysis::apply_terminator_effect`.
|
||||
fn terminator_effect(
|
||||
&self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
);
|
||||
|
||||
/// See `Analysis::apply_before_terminator_effect`.
|
||||
fn before_terminator_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
}
|
||||
|
||||
/// See `Analysis::apply_call_return_effect`.
|
||||
fn call_return_effect(
|
||||
&self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
block: BasicBlock,
|
||||
func: &mir::Operand<'tcx>,
|
||||
args: &[mir::Operand<'tcx>],
|
||||
return_place: &mir::Place<'tcx>,
|
||||
);
|
||||
}
|
||||
|
||||
impl<A> Analysis<'tcx> for A
|
||||
where
|
||||
A: GenKillAnalysis<'tcx>,
|
||||
{
|
||||
fn apply_statement_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
self.statement_effect(state, statement, location);
|
||||
}
|
||||
|
||||
fn apply_before_statement_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
self.before_statement_effect(state, statement, location);
|
||||
}
|
||||
|
||||
fn apply_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
self.terminator_effect(state, terminator, location);
|
||||
}
|
||||
|
||||
fn apply_before_terminator_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
self.before_terminator_effect(state, terminator, location);
|
||||
}
|
||||
|
||||
fn apply_call_return_effect(
|
||||
&self,
|
||||
state: &mut BitSet<Self::Idx>,
|
||||
block: BasicBlock,
|
||||
func: &mir::Operand<'tcx>,
|
||||
args: &[mir::Operand<'tcx>],
|
||||
return_place: &mir::Place<'tcx>,
|
||||
) {
|
||||
self.call_return_effect(state, block, func, args, return_place);
|
||||
}
|
||||
}
|
||||
|
||||
/// The legal operations for a transfer function in a gen/kill problem.
|
||||
pub trait GenKill<T>: Sized {
|
||||
/// Inserts `elem` into the `gen` set, removing it the `kill` set if present.
|
||||
fn gen(&mut self, elem: T);
|
||||
|
||||
/// Inserts `elem` into the `kill` set, removing it the `gen` set if present.
|
||||
fn kill(&mut self, elem: T);
|
||||
|
||||
/// Inserts the given elements into the `gen` set, removing them from the `kill` set if present.
|
||||
fn gen_all(&mut self, elems: impl IntoIterator<Item = T>) {
|
||||
for elem in elems {
|
||||
self.gen(elem);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts the given elements into the `kill` set, removing them from the `gen` set if present.
|
||||
fn kill_all(&mut self, elems: impl IntoIterator<Item = T>) {
|
||||
for elem in elems {
|
||||
self.kill(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores a transfer function for a gen/kill problem.
|
||||
#[derive(Clone)]
|
||||
pub struct GenKillSet<T: Idx> {
|
||||
gen: HybridBitSet<T>,
|
||||
kill: HybridBitSet<T>,
|
||||
}
|
||||
|
||||
impl<T: Idx> GenKillSet<T> {
|
||||
/// Creates a new transfer function that will leave the dataflow state unchanged.
|
||||
pub fn identity(universe: usize) -> Self {
|
||||
GenKillSet {
|
||||
gen: HybridBitSet::new_empty(universe),
|
||||
kill: HybridBitSet::new_empty(universe),
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies this transfer function to the given bitset.
|
||||
pub fn apply(&self, state: &mut BitSet<T>) {
|
||||
state.union(&self.gen);
|
||||
state.subtract(&self.kill);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Idx> GenKill<T> for GenKillSet<T> {
|
||||
fn gen(&mut self, elem: T) {
|
||||
self.gen.insert(elem);
|
||||
self.kill.remove(elem);
|
||||
}
|
||||
|
||||
fn kill(&mut self, elem: T) {
|
||||
self.kill.insert(elem);
|
||||
self.gen.remove(elem);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Idx> GenKill<T> for BitSet<T> {
|
||||
fn gen(&mut self, elem: T) {
|
||||
self.insert(elem);
|
||||
}
|
||||
|
||||
fn kill(&mut self, elem: T) {
|
||||
self.remove(elem);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user