rust/src/librustc_mir/util/liveness.rs
2018-12-25 21:08:33 -07:00

377 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Liveness analysis which computes liveness of MIR local variables at the boundary of basic blocks
//!
//! This analysis considers references as being used only at the point of the
//! borrow. This means that this does not track uses because of references that
//! already exist:
//!
//! ```Rust
//! fn foo() {
//! x = 0;
//! // `x` is live here
//! GLOBAL = &x: *const u32;
//! // but not here, even while it can be accessed through `GLOBAL`.
//! foo();
//! x = 1;
//! // `x` is live again here, because it is assigned to `OTHER_GLOBAL`
//! OTHER_GLOBAL = &x: *const u32;
//! // ...
//! }
//! ```
//!
//! This means that users of this analysis still have to check whether
//! pre-existing references can be used to access the value (e.g., at movable
//! generator yield points, all pre-existing references are invalidated, so this
//! doesn't matter).
use rustc::mir::visit::{
PlaceContext, Visitor, MutatingUseContext, NonMutatingUseContext, NonUseContext,
};
use rustc::mir::Local;
use rustc::mir::*;
use rustc::ty::{item_path, TyCtxt};
use rustc_data_structures::bit_set::BitSet;
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
use rustc_data_structures::work_queue::WorkQueue;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use transform::MirSource;
use util::pretty::{dump_enabled, write_basic_block, write_mir_intro};
pub type LiveVarSet<V> = BitSet<V>;
/// This gives the result of the liveness analysis at the boundary of
/// basic blocks.
///
/// The `V` type defines the set of variables that we computed
/// liveness for. This is often `Local`, in which case we computed
/// liveness for all variables -- but it can also be some other type,
/// which indicates a subset of the variables within the graph.
pub struct LivenessResult<V: Idx> {
/// Live variables on exit to each basic block. This is equal to
/// the union of the `ins` for each successor.
pub outs: IndexVec<BasicBlock, LiveVarSet<V>>,
}
/// Defines the mapping to/from the MIR local variables (`Local`) to
/// the "live variable indices" we are using in a particular
/// computation.
pub trait LiveVariableMap {
type LiveVar;
fn from_local(&self, local: Local) -> Option<Self::LiveVar>;
fn from_live_var(&self, local: Self::LiveVar) -> Local;
fn num_variables(&self) -> usize;
}
#[derive(Debug)]
pub struct IdentityMap<'a, 'tcx: 'a> {
mir: &'a Mir<'tcx>,
}
impl<'a, 'tcx> IdentityMap<'a, 'tcx> {
pub fn new(mir: &'a Mir<'tcx>) -> Self {
Self { mir }
}
}
impl<'a, 'tcx> LiveVariableMap for IdentityMap<'a, 'tcx> {
type LiveVar = Local;
fn from_local(&self, local: Local) -> Option<Self::LiveVar> {
Some(local)
}
fn from_live_var(&self, local: Self::LiveVar) -> Local {
local
}
fn num_variables(&self) -> usize {
self.mir.local_decls.len()
}
}
/// Compute which local variables are live within the given function
/// `mir`. The liveness mode `mode` determines what sorts of uses are
/// considered to make a variable live (e.g., do drops count?).
pub fn liveness_of_locals<'tcx, V: Idx>(
mir: &Mir<'tcx>,
map: &impl LiveVariableMap<LiveVar = V>,
) -> LivenessResult<V> {
let num_live_vars = map.num_variables();
let def_use: IndexVec<_, DefsUses<V>> = mir
.basic_blocks()
.iter()
.map(|b| block(map, b, num_live_vars))
.collect();
let mut outs: IndexVec<_, LiveVarSet<V>> = mir
.basic_blocks()
.indices()
.map(|_| LiveVarSet::new_empty(num_live_vars))
.collect();
let mut bits = LiveVarSet::new_empty(num_live_vars);
// queue of things that need to be re-processed, and a set containing
// the things currently in the queue
let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_all(mir.basic_blocks().len());
let predecessors = mir.predecessors();
while let Some(bb) = dirty_queue.pop() {
// bits = use (bits - def)
bits.overwrite(&outs[bb]);
def_use[bb].apply(&mut bits);
// `bits` now contains the live variables on entry. Therefore,
// add `bits` to the `out` set for each predecessor; if those
// bits were not already present, then enqueue the predecessor
// as dirty.
//
// (note that `union` returns true if the `self` set changed)
for &pred_bb in &predecessors[bb] {
if outs[pred_bb].union(&bits) {
dirty_queue.insert(pred_bb);
}
}
}
LivenessResult { outs }
}
#[derive(Eq, PartialEq, Clone)]
pub enum DefUse {
Def,
Use,
Drop,
}
pub fn categorize<'tcx>(context: PlaceContext<'tcx>) -> Option<DefUse> {
match context {
///////////////////////////////////////////////////////////////////////////
// DEFS
PlaceContext::MutatingUse(MutatingUseContext::Store) |
// This is potentially both a def and a use...
PlaceContext::MutatingUse(MutatingUseContext::AsmOutput) |
// We let Call define the result in both the success and
// unwind cases. This is not really correct, however it
// does not seem to be observable due to the way that we
// generate MIR. To do things properly, we would apply
// the def in call only to the input from the success
// path and not the unwind path. -nmatsakis
PlaceContext::MutatingUse(MutatingUseContext::Call) |
// Storage live and storage dead aren't proper defines, but we can ignore
// values that come before them.
PlaceContext::NonUse(NonUseContext::StorageLive) |
PlaceContext::NonUse(NonUseContext::StorageDead) => Some(DefUse::Def),
///////////////////////////////////////////////////////////////////////////
// REGULAR USES
//
// These are uses that occur *outside* of a drop. For the
// purposes of NLL, these are special in that **all** the
// lifetimes appearing in the variable must be live for each regular use.
PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) |
PlaceContext::MutatingUse(MutatingUseContext::Projection) |
// Borrows only consider their local used at the point of the borrow.
// This won't affect the results since we use this analysis for generators
// and we only care about the result at suspension points. Borrows cannot
// cross suspension points so this behavior is unproblematic.
PlaceContext::MutatingUse(MutatingUseContext::Borrow(..)) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow(..)) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow(..)) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow(..)) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) |
PlaceContext::NonUse(NonUseContext::AscribeUserTy) |
PlaceContext::MutatingUse(MutatingUseContext::Retag) =>
Some(DefUse::Use),
///////////////////////////////////////////////////////////////////////////
// DROP USES
//
// These are uses that occur in a DROP (a MIR drop, not a
// call to `std::mem::drop()`). For the purposes of NLL,
// uses in drop are special because `#[may_dangle]`
// attributes can affect whether lifetimes must be live.
PlaceContext::MutatingUse(MutatingUseContext::Drop) =>
Some(DefUse::Drop),
}
}
struct DefsUsesVisitor<'lv, V, M>
where
V: Idx,
M: LiveVariableMap<LiveVar = V> + 'lv,
{
map: &'lv M,
defs_uses: DefsUses<V>,
}
#[derive(Eq, PartialEq, Clone)]
struct DefsUses<V: Idx> {
defs: LiveVarSet<V>,
uses: LiveVarSet<V>,
}
impl<V: Idx> DefsUses<V> {
fn apply(&self, bits: &mut LiveVarSet<V>) -> bool {
bits.subtract(&self.defs) | bits.union(&self.uses)
}
fn add_def(&mut self, index: V) {
// If it was used already in the block, remove that use
// now that we found a definition.
//
// Example:
//
// // Defs = {X}, Uses = {}
// X = 5
// // Defs = {}, Uses = {X}
// use(X)
self.uses.remove(index);
self.defs.insert(index);
}
fn add_use(&mut self, index: V) {
// Inverse of above.
//
// Example:
//
// // Defs = {}, Uses = {X}
// use(X)
// // Defs = {X}, Uses = {}
// X = 5
// // Defs = {}, Uses = {X}
// use(X)
self.defs.remove(index);
self.uses.insert(index);
}
}
impl<'tcx, 'lv, V, M> Visitor<'tcx> for DefsUsesVisitor<'lv, V, M>
where
V: Idx,
M: LiveVariableMap<LiveVar = V>,
{
fn visit_local(&mut self, &local: &Local, context: PlaceContext<'tcx>, _: Location) {
if let Some(v_index) = self.map.from_local(local) {
match categorize(context) {
Some(DefUse::Def) => self.defs_uses.add_def(v_index),
Some(DefUse::Use) | Some(DefUse::Drop) => self.defs_uses.add_use(v_index),
_ => (),
}
}
}
}
fn block<'tcx, V: Idx>(
map: &impl LiveVariableMap<LiveVar = V>,
b: &BasicBlockData<'tcx>,
locals: usize,
) -> DefsUses<V> {
let mut visitor = DefsUsesVisitor {
map,
defs_uses: DefsUses {
defs: LiveVarSet::new_empty(locals),
uses: LiveVarSet::new_empty(locals),
},
};
let dummy_location = Location {
block: BasicBlock::new(0),
statement_index: 0,
};
// Visit the various parts of the basic block in reverse. If we go
// forward, the logic in `add_def` and `add_use` would be wrong.
visitor.visit_terminator(BasicBlock::new(0), b.terminator(), dummy_location);
for statement in b.statements.iter().rev() {
visitor.visit_statement(BasicBlock::new(0), statement, dummy_location);
}
visitor.defs_uses
}
pub fn dump_mir<'a, 'tcx, V: Idx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
pass_name: &str,
source: MirSource,
mir: &Mir<'tcx>,
map: &impl LiveVariableMap<LiveVar = V>,
result: &LivenessResult<V>,
) {
if !dump_enabled(tcx, pass_name, source) {
return;
}
let node_path = item_path::with_forced_impl_filename_line(|| {
// see notes on #41697 below
tcx.item_path_str(source.def_id)
});
dump_matched_mir_node(tcx, pass_name, &node_path, source, mir, map, result);
}
fn dump_matched_mir_node<'a, 'tcx, V: Idx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
pass_name: &str,
node_path: &str,
source: MirSource,
mir: &Mir<'tcx>,
map: &dyn LiveVariableMap<LiveVar = V>,
result: &LivenessResult<V>,
) {
let mut file_path = PathBuf::new();
file_path.push(Path::new(&tcx.sess.opts.debugging_opts.dump_mir_dir));
let item_id = tcx.hir().as_local_node_id(source.def_id).unwrap();
let file_name = format!("rustc.node{}{}-liveness.mir", item_id, pass_name);
file_path.push(&file_name);
let _ = fs::File::create(&file_path).and_then(|mut file| {
writeln!(file, "// MIR local liveness analysis for `{}`", node_path)?;
writeln!(file, "// source = {:?}", source)?;
writeln!(file, "// pass_name = {}", pass_name)?;
writeln!(file, "")?;
write_mir_fn(tcx, source, mir, map, &mut file, result)?;
Ok(())
});
}
pub fn write_mir_fn<'a, 'tcx, V: Idx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
src: MirSource,
mir: &Mir<'tcx>,
map: &dyn LiveVariableMap<LiveVar = V>,
w: &mut dyn Write,
result: &LivenessResult<V>,
) -> io::Result<()> {
write_mir_intro(tcx, src, mir, w)?;
for block in mir.basic_blocks().indices() {
let print = |w: &mut dyn Write, prefix, result: &IndexVec<BasicBlock, LiveVarSet<V>>| {
let live: Vec<String> = result[block]
.iter()
.map(|v| map.from_live_var(v))
.map(|local| format!("{:?}", local))
.collect();
writeln!(w, "{} {{{}}}", prefix, live.join(", "))
};
write_basic_block(tcx, block, mir, &mut |_, _| Ok(()), w)?;
print(w, " ", &result.outs)?;
if block.index() + 1 != mir.basic_blocks().len() {
writeln!(w, "")?;
}
}
writeln!(w, "}}")?;
Ok(())
}