introduce liveness constraints into NLL code

And do a bunch of gratuitious refactoring that I did not bother to
separate into nice commits.
This commit is contained in:
Niko Matsakis 2017-10-24 14:20:47 -04:00
parent 8535a4a32c
commit 7523c7368c
7 changed files with 393 additions and 209 deletions

View file

@ -53,11 +53,19 @@ pub struct IdxSet<T: Idx> {
}
impl<T: Idx> fmt::Debug for IdxSetBuf<T> {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { self.bits.fmt(w) }
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
w.debug_list()
.entries(self.iter())
.finish()
}
}
impl<T: Idx> fmt::Debug for IdxSet<T> {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { self.bits.fmt(w) }
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
w.debug_list()
.entries(self.iter())
.finish()
}
}
impl<T: Idx> IdxSetBuf<T> {

View file

@ -18,6 +18,7 @@ Rust MIR: a lowered representation of Rust. Also: an experiment!
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(conservative_impl_trait)]
#![feature(const_fn)]
#![feature(core_intrinsics)]
#![feature(i128_type)]

View file

@ -0,0 +1,64 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use rustc::mir::Mir;
use rustc::infer::InferCtxt;
use util::liveness::LivenessResult;
use super::ToRegionIndex;
use super::region_infer::RegionInferenceContext;
pub fn generate_constraints<'a, 'gcx, 'tcx>(infcx: &InferCtxt<'a, 'gcx, 'tcx>,
regioncx: &mut RegionInferenceContext,
mir: &Mir<'tcx>,
liveness: &LivenessResult)
{
ConstraintGeneration { infcx, regioncx, mir, liveness }.add_constraints();
}
struct ConstraintGeneration<'constrain, 'gcx: 'tcx, 'tcx: 'constrain> {
infcx: &'constrain InferCtxt<'constrain, 'gcx, 'tcx>,
regioncx: &'constrain mut RegionInferenceContext,
mir: &'constrain Mir<'tcx>,
liveness: &'constrain LivenessResult,
}
impl<'constrain, 'gcx, 'tcx> ConstraintGeneration<'constrain, 'gcx, 'tcx> {
fn add_constraints(&mut self) {
// To start, add the liveness constraints.
self.add_liveness_constraints();
}
/// Liveness constraints:
///
/// > If a variable V is live at point P, then all regions R in the type of V
/// > must include the point P.
fn add_liveness_constraints(&mut self) {
let tcx = self.infcx.tcx;
debug!("add_liveness_constraints()");
for bb in self.mir.basic_blocks().indices() {
debug!("add_liveness_constraints: bb={:?}", bb);
self.liveness.simulate_block(self.mir, bb, |location, live_locals| {
debug!("add_liveness_constraints: location={:?} live_locals={:?}",
location, live_locals);
for live_local in live_locals.iter() {
let live_local_ty = self.mir.local_decls[live_local].ty;
tcx.for_each_free_region(&live_local_ty, |live_region| {
let vid = live_region.to_region_index();
self.regioncx.add_live_point(vid, location);
})
}
});
}
}
}

View file

@ -8,215 +8,115 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use self::infer::InferenceContext;
use rustc::ty::TypeFoldable;
use rustc::ty::subst::{Kind, Substs};
use rustc::ty::{Ty, TyCtxt, ClosureSubsts, RegionVid, RegionKind};
use rustc::mir::{Mir, Location, Rvalue, BasicBlock, Statement, StatementKind};
use rustc::mir::visit::{MutVisitor, Lookup};
use rustc::ty::{self, RegionKind, TyCtxt};
use rustc::mir::{Location, Mir};
use rustc::mir::transform::{MirPass, MirSource};
use rustc::infer::{self as rustc_infer, InferCtxt};
use rustc::util::nodemap::{FxHashMap, FxHashSet};
use rustc_data_structures::indexed_vec::{IndexVec, Idx};
use syntax_pos::DUMMY_SP;
use std::collections::HashMap;
use rustc::infer::InferCtxt;
use rustc::util::nodemap::FxHashMap;
use rustc_data_structures::indexed_vec::Idx;
use std::collections::BTreeSet;
use std::fmt;
use util::liveness;
use util::liveness::{self, LivenessResult};
use util as mir_util;
use self::mir_util::PassWhere;
mod infer;
mod constraint_generation;
#[allow(dead_code)]
struct NLLVisitor<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
lookup_map: HashMap<RegionVid, Lookup>,
regions: IndexVec<RegionIndex, Region>,
#[allow(dead_code)]
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
}
mod region_infer;
use self::region_infer::RegionInferenceContext;
impl<'a, 'gcx, 'tcx> NLLVisitor<'a, 'gcx, 'tcx> {
pub fn new(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>) -> Self {
NLLVisitor {
infcx,
lookup_map: HashMap::new(),
regions: IndexVec::new(),
}
}
pub fn into_results(self) -> (HashMap<RegionVid, Lookup>, IndexVec<RegionIndex, Region>) {
(self.lookup_map, self.regions)
}
fn renumber_regions<T>(&mut self, value: &T) -> T where T: TypeFoldable<'tcx> {
self.infcx.tcx.fold_regions(value, &mut false, |_region, _depth| {
self.regions.push(Region::default());
self.infcx.next_region_var(rustc_infer::MiscVariable(DUMMY_SP))
})
}
fn store_region(&mut self, region: &RegionKind, lookup: Lookup) {
if let RegionKind::ReVar(rid) = *region {
self.lookup_map.entry(rid).or_insert(lookup);
}
}
fn store_ty_regions(&mut self, ty: &Ty<'tcx>, lookup: Lookup) {
for region in ty.regions() {
self.store_region(region, lookup);
}
}
fn store_kind_regions(&mut self, kind: &'tcx Kind, lookup: Lookup) {
if let Some(ty) = kind.as_type() {
self.store_ty_regions(&ty, lookup);
} else if let Some(region) = kind.as_region() {
self.store_region(region, lookup);
}
}
}
impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> {
fn visit_ty(&mut self, ty: &mut Ty<'tcx>, lookup: Lookup) {
let old_ty = *ty;
*ty = self.renumber_regions(&old_ty);
self.store_ty_regions(ty, lookup);
}
fn visit_substs(&mut self, substs: &mut &'tcx Substs<'tcx>, location: Location) {
*substs = self.renumber_regions(&{*substs});
let lookup = Lookup::Loc(location);
for kind in *substs {
self.store_kind_regions(kind, lookup);
}
}
fn visit_rvalue(&mut self, rvalue: &mut Rvalue<'tcx>, location: Location) {
match *rvalue {
Rvalue::Ref(ref mut r, _, _) => {
let old_r = *r;
*r = self.renumber_regions(&old_r);
let lookup = Lookup::Loc(location);
self.store_region(r, lookup);
}
Rvalue::Use(..) |
Rvalue::Repeat(..) |
Rvalue::Len(..) |
Rvalue::Cast(..) |
Rvalue::BinaryOp(..) |
Rvalue::CheckedBinaryOp(..) |
Rvalue::UnaryOp(..) |
Rvalue::Discriminant(..) |
Rvalue::NullaryOp(..) |
Rvalue::Aggregate(..) => {
// These variants don't contain regions.
}
}
self.super_rvalue(rvalue, location);
}
fn visit_closure_substs(&mut self,
substs: &mut ClosureSubsts<'tcx>,
location: Location) {
*substs = self.renumber_regions(substs);
let lookup = Lookup::Loc(location);
for kind in substs.substs {
self.store_kind_regions(kind, lookup);
}
}
fn visit_statement(&mut self,
block: BasicBlock,
statement: &mut Statement<'tcx>,
location: Location) {
if let StatementKind::EndRegion(_) = statement.kind {
statement.kind = StatementKind::Nop;
}
self.super_statement(block, statement, location);
}
}
mod renumber;
// MIR Pass for non-lexical lifetimes
pub struct NLL;
impl MirPass for NLL {
fn run_pass<'a, 'tcx>(&self,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
source: MirSource,
input_mir: &mut Mir<'tcx>) {
fn run_pass<'a, 'tcx>(
&self,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
source: MirSource,
input_mir: &mut Mir<'tcx>,
) {
if !tcx.sess.opts.debugging_opts.nll {
return;
}
tcx.infer_ctxt().enter(|infcx| {
tcx.infer_ctxt().enter(|ref infcx| {
// Clone mir so we can mutate it without disturbing the rest of the compiler
let mir = &mut input_mir.clone();
let mut visitor = NLLVisitor::new(&infcx);
visitor.visit_mir(mir);
// Replace all regions with fresh inference variables.
let num_region_variables = renumber::renumber_mir(infcx, mir);
let liveness = liveness::liveness_of_locals(mir);
// Compute what is live where.
let liveness = &liveness::liveness_of_locals(mir);
let liveness_per_location: FxHashMap<_, _> =
mir
.basic_blocks()
.indices()
.flat_map(|bb| {
let mut results = vec![];
liveness.simulate_block(&mir, bb, |location, local_set| {
results.push((location, local_set.clone()));
});
results
})
.collect();
// Create the region inference context, generate the constraints,
// and then solve them.
let regioncx = &mut RegionInferenceContext::new(num_region_variables);
constraint_generation::generate_constraints(infcx, regioncx, mir, liveness);
regioncx.solve(infcx, mir);
mir_util::dump_mir(infcx.tcx, None, "nll", &0, source, mir, |pass_where, out| {
match pass_where {
// Before the CFG, dump out the values for each region variable.
PassWhere::BeforeCFG => {
for (index, value) in visitor.regions.iter_enumerated() {
writeln!(out, "| R{:03}: {:?}", index.0, value)?;
}
}
// Before each basic block, dump out the values
// that are live on entry to the basic block.
PassWhere::BeforeBlock(bb) => {
let local_set = &liveness.ins[bb];
writeln!(out, " | Variables live on entry to the block {:?}:", bb)?;
for local in local_set.iter() {
writeln!(out, " | - {:?}", local)?;
}
}
PassWhere::InCFG(location) => {
let local_set = &liveness_per_location[&location];
let mut string = String::new();
for local in local_set.iter() {
string.push_str(&format!(", {:?}", local));
}
if !string.is_empty() {
writeln!(out, " | Live variables here: [{}]", &string[2..])?;
} else {
writeln!(out, " | Live variables here: []")?;
}
}
PassWhere::AfterCFG => { }
}
Ok(())
});
let (_lookup_map, regions) = visitor.into_results();
let mut inference_context = InferenceContext::new(regions);
inference_context.solve(&infcx, mir);
// Dump MIR results into a file, if that is enabled.
dump_mir_results(infcx, liveness, source, regioncx, mir);
})
}
}
fn dump_mir_results<'a, 'gcx, 'tcx>(
infcx: &InferCtxt<'a, 'gcx, 'tcx>,
liveness: &LivenessResult,
source: MirSource,
regioncx: &RegionInferenceContext,
mir: &Mir<'tcx>,
) {
if !mir_util::dump_enabled(infcx.tcx, "nll", source) {
return;
}
let liveness_per_location: FxHashMap<_, _> = mir.basic_blocks()
.indices()
.flat_map(|bb| {
let mut results = vec![];
liveness.simulate_block(&mir, bb, |location, local_set| {
results.push((location, local_set.clone()));
});
results
})
.collect();
mir_util::dump_mir(infcx.tcx, None, "nll", &0, source, mir, |pass_where, out| {
match pass_where {
// Before the CFG, dump out the values for each region variable.
PassWhere::BeforeCFG => for region in regioncx.regions() {
writeln!(out, "| {:?}: {:?}", region, regioncx.region_value(region))?;
},
// Before each basic block, dump out the values
// that are live on entry to the basic block.
PassWhere::BeforeBlock(bb) => {
let local_set = &liveness.ins[bb];
writeln!(out, " | Variables live on entry to the block {:?}:", bb)?;
for local in local_set.iter() {
writeln!(out, " | - {:?}", local)?;
}
}
PassWhere::InCFG(location) => {
let local_set = &liveness_per_location[&location];
writeln!(out, " | Live variables here: {:?}", local_set)?;
}
PassWhere::AfterCFG => {}
}
Ok(())
});
}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct Region {
points: FxHashSet<Location>,
points: BTreeSet<Location>,
}
impl fmt::Debug for Region {
@ -235,4 +135,25 @@ impl Region {
}
}
newtype_index!(RegionIndex);
newtype_index!(RegionIndex {
DEBUG_NAME = "R",
});
/// Right now, we piggy back on the `ReVar` to store our NLL inference
/// regions. These are indexed with `RegionIndex`. This method will
/// assert that the region is a `ReVar` and convert the internal index
/// into a `RegionIndex`. This is reasonable because in our MIR we
/// replace all free regions with inference variables.
trait ToRegionIndex {
fn to_region_index(&self) -> RegionIndex;
}
impl ToRegionIndex for RegionKind {
fn to_region_index(&self) -> RegionIndex {
if let &ty::ReVar(vid) = self {
RegionIndex::new(vid.index as usize)
} else {
bug!("region is not an ReVar: {:?}", self)
}
}
}

View file

@ -15,7 +15,7 @@ use rustc::mir::{Location, Mir};
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
use rustc_data_structures::fx::FxHashSet;
pub struct InferenceContext {
pub struct RegionInferenceContext {
definitions: IndexVec<RegionIndex, VarDefinition>,
constraints: IndexVec<ConstraintIndex, Constraint>,
errors: IndexVec<InferenceErrorIndex, InferenceError>,
@ -28,22 +28,13 @@ pub struct InferenceError {
newtype_index!(InferenceErrorIndex);
#[derive(Default)]
struct VarDefinition {
name: (), // FIXME(nashenas88) RegionName
value: Region,
capped: bool,
}
impl VarDefinition {
pub fn new(value: Region) -> Self {
Self {
name: (),
value,
capped: false,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Constraint {
sub: RegionIndex,
@ -53,10 +44,12 @@ pub struct Constraint {
newtype_index!(ConstraintIndex);
impl InferenceContext {
pub fn new(values: IndexVec<RegionIndex, Region>) -> Self {
impl RegionInferenceContext {
pub fn new(num_region_variables: usize) -> Self {
Self {
definitions: values.into_iter().map(VarDefinition::new).collect(),
definitions: (0..num_region_variables)
.map(|_| VarDefinition::default())
.collect(),
constraints: IndexVec::new(),
errors: IndexVec::new(),
}
@ -87,8 +80,14 @@ impl InferenceContext {
self.constraints.push(Constraint { sup, sub, point });
}
#[allow(dead_code)]
pub fn region(&self, v: RegionIndex) -> &Region {
/// Returns an iterator over all the region indices.
pub fn regions(&self) -> impl Iterator<Item = RegionIndex> {
self.definitions.indices()
}
/// Returns the current value for the region `v`. This is only
/// really meaningful after `solve` has executed.
pub fn region_value(&self, v: RegionIndex) -> &Region {
&self.definitions[v].value
}
@ -144,8 +143,7 @@ impl InferenceContext {
}
struct Dfs<'a, 'gcx: 'tcx + 'a, 'tcx: 'a> {
#[allow(dead_code)]
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
#[allow(dead_code)] infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
mir: &'a Mir<'tcx>,
}
@ -183,17 +181,22 @@ impl<'a, 'gcx: 'tcx, 'tcx: 'a> Dfs<'a, 'gcx, 'tcx> {
let block_data = &self.mir[p.block];
let successor_points = if p.statement_index < block_data.statements.len() {
vec![Location {
statement_index: p.statement_index + 1,
..p
}]
vec![
Location {
statement_index: p.statement_index + 1,
..p
},
]
} else {
block_data.terminator()
block_data
.terminator()
.successors()
.iter()
.map(|&basic_block| Location {
statement_index: 0,
block: basic_block,
.map(|&basic_block| {
Location {
statement_index: 0,
block: basic_block,
}
})
.collect::<Vec<_>>()
};

View file

@ -0,0 +1,132 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use rustc::ty::TypeFoldable;
use rustc::ty::subst::{Kind, Substs};
use rustc::ty::{Ty, ClosureSubsts, RegionVid, RegionKind};
use rustc::mir::{Mir, Location, Rvalue, BasicBlock, Statement, StatementKind};
use rustc::mir::visit::{MutVisitor, Lookup};
use rustc::infer::{self as rustc_infer, InferCtxt};
use syntax_pos::DUMMY_SP;
use std::collections::HashMap;
/// Replaces all free regions appearing in the MIR with fresh
/// inference variables, returning the number of variables created.
pub fn renumber_mir<'a, 'gcx, 'tcx>(infcx: &InferCtxt<'a, 'gcx, 'tcx>,
mir: &mut Mir<'tcx>)
-> usize
{
let mut visitor = NLLVisitor::new(infcx);
visitor.visit_mir(mir);
visitor.num_region_variables
}
struct NLLVisitor<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
lookup_map: HashMap<RegionVid, Lookup>,
num_region_variables: usize,
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
}
impl<'a, 'gcx, 'tcx> NLLVisitor<'a, 'gcx, 'tcx> {
pub fn new(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>) -> Self {
NLLVisitor {
infcx,
lookup_map: HashMap::new(),
num_region_variables: 0
}
}
fn renumber_regions<T>(&mut self, value: &T) -> T where T: TypeFoldable<'tcx> {
self.infcx.tcx.fold_regions(value, &mut false, |_region, _depth| {
self.num_region_variables += 1;
self.infcx.next_region_var(rustc_infer::MiscVariable(DUMMY_SP))
})
}
fn store_region(&mut self, region: &RegionKind, lookup: Lookup) {
if let RegionKind::ReVar(rid) = *region {
self.lookup_map.entry(rid).or_insert(lookup);
}
}
fn store_ty_regions(&mut self, ty: &Ty<'tcx>, lookup: Lookup) {
for region in ty.regions() {
self.store_region(region, lookup);
}
}
fn store_kind_regions(&mut self, kind: &'tcx Kind, lookup: Lookup) {
if let Some(ty) = kind.as_type() {
self.store_ty_regions(&ty, lookup);
} else if let Some(region) = kind.as_region() {
self.store_region(region, lookup);
}
}
}
impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> {
fn visit_ty(&mut self, ty: &mut Ty<'tcx>, lookup: Lookup) {
let old_ty = *ty;
*ty = self.renumber_regions(&old_ty);
self.store_ty_regions(ty, lookup);
}
fn visit_substs(&mut self, substs: &mut &'tcx Substs<'tcx>, location: Location) {
*substs = self.renumber_regions(&{*substs});
let lookup = Lookup::Loc(location);
for kind in *substs {
self.store_kind_regions(kind, lookup);
}
}
fn visit_rvalue(&mut self, rvalue: &mut Rvalue<'tcx>, location: Location) {
match *rvalue {
Rvalue::Ref(ref mut r, _, _) => {
let old_r = *r;
*r = self.renumber_regions(&old_r);
let lookup = Lookup::Loc(location);
self.store_region(r, lookup);
}
Rvalue::Use(..) |
Rvalue::Repeat(..) |
Rvalue::Len(..) |
Rvalue::Cast(..) |
Rvalue::BinaryOp(..) |
Rvalue::CheckedBinaryOp(..) |
Rvalue::UnaryOp(..) |
Rvalue::Discriminant(..) |
Rvalue::NullaryOp(..) |
Rvalue::Aggregate(..) => {
// These variants don't contain regions.
}
}
self.super_rvalue(rvalue, location);
}
fn visit_closure_substs(&mut self,
substs: &mut ClosureSubsts<'tcx>,
location: Location) {
*substs = self.renumber_regions(substs);
let lookup = Lookup::Loc(location);
for kind in substs.substs {
self.store_kind_regions(kind, lookup);
}
}
fn visit_statement(&mut self,
block: BasicBlock,
statement: &mut Statement<'tcx>,
location: Location) {
if let StatementKind::EndRegion(_) = statement.kind {
statement.kind = StatementKind::Nop;
}
self.super_statement(block, statement, location);
}
}

View file

@ -0,0 +1,55 @@
// 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.
// Basic test for liveness constraints: the region (`R1`) that appears
// in the type of `p` includes the points after `&v[0]` up to (but not
// including) the call to `use_x`. The `else` branch is not included.
// compile-flags:-Znll -Zverbose
// ^^^^^^^^^ force compiler to dump more region information
#![allow(warnings)]
fn use_x(_: usize) -> bool { true }
fn main() {
let mut v = [1, 2, 3];
let p = &v[0];
if true {
use_x(*p);
} else {
use_x(22);
}
}
// END RUST SOURCE
// START rustc.node12.nll.0.mir
// | R1: {bb1[1], bb2[0], bb2[1]}
// ...
// let _2: &'_#1r usize;
// END rustc.node12.nll.0.mir
// START rustc.node12.nll.0.mir
// bb1: {
// | Live variables here: [_1, _3]
// _2 = &'_#0r _1[_3];
// | Live variables here: [_2]
// switchInt(const true) -> [0u8: bb3, otherwise: bb2];
// }
// END rustc.node12.nll.0.mir
// START rustc.node12.nll.0.mir
// bb2: {
// | Live variables here: [_2]
// StorageLive(_7);
// | Live variables here: [_2]
// _7 = (*_2);
// | Live variables here: [_7]
// _6 = const use_x(_7) -> bb4;
// }
// END rustc.node12.nll.0.mir