Auto merge of #50370 - nikomatsakis:nll-alias-analysis-flat, r=pnkfelix

introduce `-Znll-facts` to dump base-facts for the NLL analysis

r? @pnkfelix
This commit is contained in:
bors 2018-05-05 11:05:23 +00:00
commit 2d847dc90f
12 changed files with 687 additions and 104 deletions

View file

@ -1978,6 +1978,11 @@ impl fmt::Debug for Location {
}
impl Location {
pub const START: Location = Location {
block: START_BLOCK,
statement_index: 0,
};
/// Returns the location immediately after this one within the enclosing block.
///
/// Note that if this location represents a terminator, then the

View file

@ -1250,6 +1250,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"choose which RELRO level to use"),
nll_subminimal_causes: bool = (false, parse_bool, [UNTRACKED],
"when tracking region error causes, accept subminimal results for faster execution."),
nll_facts: bool = (false, parse_bool, [UNTRACKED],
"dump facts from NLL analysis into side files"),
disable_nll_user_type_assert: bool = (false, parse_bool, [UNTRACKED],
"disable user provided type assertion in NLL"),
trans_time_graph: bool = (false, parse_bool, [UNTRACKED],

View file

@ -0,0 +1,123 @@
// 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::{BasicBlock, Location, Mir};
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
/// Maps between a MIR Location, which identifies the a particular
/// statement within a basic block, to a "rich location", which
/// identifies at a finer granularity. In particular, we distinguish
/// the *start* of a statement and the *mid-point*. The mid-point is
/// the point *just* before the statement takes effect; in particular,
/// for an assignment `A = B`, it is the point where B is about to be
/// written into A. This mid-point is a kind of hack to work around
/// our inability to track the position information at sufficient
/// granularity through outlives relations; however, the rich location
/// table serves another purpose: it compresses locations from
/// multiple words into a single u32.
crate struct LocationTable {
num_points: usize,
statements_before_block: IndexVec<BasicBlock, usize>,
}
newtype_index!(LocationIndex { DEBUG_FORMAT = "LocationIndex({})" });
#[derive(Copy, Clone, Debug)]
crate enum RichLocation {
Start(Location),
Mid(Location),
}
impl LocationTable {
crate fn new(mir: &Mir<'_>) -> Self {
let mut num_points = 0;
let statements_before_block = mir.basic_blocks()
.iter()
.map(|block_data| {
let v = num_points;
num_points += (block_data.statements.len() + 1) * 2;
v
})
.collect();
debug!(
"LocationTable(statements_before_block={:#?})",
statements_before_block
);
debug!("LocationTable: num_points={:#?}", num_points);
Self {
num_points,
statements_before_block,
}
}
crate fn all_points(&self) -> impl Iterator<Item = LocationIndex> {
(0..self.num_points).map(LocationIndex::new)
}
crate fn start_index(&self, location: Location) -> LocationIndex {
let Location {
block,
statement_index,
} = location;
let start_index = self.statements_before_block[block];
LocationIndex::new(start_index + statement_index * 2)
}
crate fn mid_index(&self, location: Location) -> LocationIndex {
let Location {
block,
statement_index,
} = location;
let start_index = self.statements_before_block[block];
LocationIndex::new(start_index + statement_index * 2 + 1)
}
crate fn to_location(&self, index: LocationIndex) -> RichLocation {
let point_index = index.index();
// Find the basic block. We have a vector with the
// starting index of the statement in each block. Imagine
// we have statement #22, and we have a vector like:
//
// [0, 10, 20]
//
// In that case, this represents point_index 2 of
// basic block BB2. We know this because BB0 accounts for
// 0..10, BB1 accounts for 11..20, and BB2 accounts for
// 20...
//
// To compute this, we could do a binary search, but
// because I am lazy we instead iterate through to find
// the last point where the "first index" (0, 10, or 20)
// was less than the statement index (22). In our case, this will
// be (BB2, 20).
let (block, &first_index) = self.statements_before_block
.iter_enumerated()
.filter(|(_, first_index)| **first_index <= point_index)
.last()
.unwrap();
let statement_index = (point_index - first_index) / 2;
if index.is_start() {
RichLocation::Start(Location { block, statement_index })
} else {
RichLocation::Mid(Location { block, statement_index })
}
}
}
impl LocationIndex {
fn is_start(&self) -> bool {
// even indices are start points; odd indices are mid points
(self.index() % 2) == 0
}
}

View file

@ -50,12 +50,14 @@ use std::iter;
use self::borrow_set::{BorrowSet, BorrowData};
use self::flows::Flows;
use self::location::LocationTable;
use self::prefixes::PrefixSet;
use self::MutateMode::{JustWrite, WriteAndRead};
crate mod borrow_set;
mod error_reporting;
mod flows;
mod location;
crate mod place_ext;
mod prefixes;
@ -110,6 +112,7 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
let mut mir: Mir<'tcx> = input_mir.clone();
let free_regions = nll::replace_regions_in_mir(infcx, def_id, param_env, &mut mir);
let mir = &mir; // no further changes
let location_table = &LocationTable::new(mir);
let move_data: MoveData<'tcx> = match MoveData::gather_moves(mir, tcx) {
Ok(move_data) => move_data,
@ -199,6 +202,7 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
def_id,
free_regions,
mir,
location_table,
param_env,
&mut flow_inits,
&mdpe.move_data,

View file

@ -8,28 +8,37 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use borrow_check::borrow_set::BorrowSet;
use borrow_check::location::LocationTable;
use borrow_check::nll::facts::AllFacts;
use rustc::hir;
use rustc::mir::{BasicBlock, BasicBlockData, Location, Place, Mir, Rvalue};
use rustc::infer::InferCtxt;
use rustc::mir::visit::TyContext;
use rustc::mir::visit::Visitor;
use rustc::mir::Place::Projection;
use rustc::mir::{Local, PlaceProjection, ProjectionElem};
use rustc::mir::visit::TyContext;
use rustc::infer::InferCtxt;
use rustc::ty::{self, CanonicalTy, ClosureSubsts};
use rustc::ty::subst::Substs;
use rustc::mir::{BasicBlock, BasicBlockData, Location, Mir, Place, Rvalue};
use rustc::mir::{Local, PlaceProjection, ProjectionElem, Statement, Terminator};
use rustc::ty::fold::TypeFoldable;
use rustc::ty::subst::Substs;
use rustc::ty::{self, CanonicalTy, ClosureSubsts};
use super::region_infer::{Cause, RegionInferenceContext};
use super::ToRegionVid;
use super::region_infer::{RegionInferenceContext, Cause};
pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
infcx: &InferCtxt<'cx, 'gcx, 'tcx>,
regioncx: &mut RegionInferenceContext<'tcx>,
all_facts: &mut Option<AllFacts>,
location_table: &LocationTable,
mir: &Mir<'tcx>,
borrow_set: &BorrowSet<'tcx>,
) {
let mut cg = ConstraintGeneration {
borrow_set,
infcx,
regioncx,
location_table,
all_facts,
mir,
};
@ -41,8 +50,11 @@ pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
/// 'cg = the duration of the constraint generation process itself.
struct ConstraintGeneration<'cg, 'cx: 'cg, 'gcx: 'tcx, 'tcx: 'cx> {
infcx: &'cg InferCtxt<'cx, 'gcx, 'tcx>,
all_facts: &'cg mut Option<AllFacts>,
location_table: &'cg LocationTable,
regioncx: &'cg mut RegionInferenceContext<'tcx>,
mir: &'cg Mir<'tcx>,
borrow_set: &'cg BorrowSet<'tcx>,
}
impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx, 'tcx> {
@ -68,12 +80,14 @@ impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx
/// call. Make them live at the location where they appear.
fn visit_ty(&mut self, ty: &ty::Ty<'tcx>, ty_context: TyContext) {
match ty_context {
TyContext::ReturnTy(source_info) |
TyContext::YieldTy(source_info) |
TyContext::LocalDecl { source_info, .. } => {
span_bug!(source_info.span,
"should not be visiting outside of the CFG: {:?}",
ty_context);
TyContext::ReturnTy(source_info)
| TyContext::YieldTy(source_info)
| TyContext::LocalDecl { source_info, .. } => {
span_bug!(
source_info.span,
"should not be visiting outside of the CFG: {:?}",
ty_context
);
}
TyContext::Location(location) => {
self.add_regular_live_constraint(*ty, location, Cause::LiveOther(location));
@ -90,25 +104,117 @@ impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx
self.super_closure_substs(substs);
}
fn visit_statement(
&mut self,
block: BasicBlock,
statement: &Statement<'tcx>,
location: Location,
) {
if let Some(all_facts) = self.all_facts {
all_facts.cfg_edge.push((
self.location_table.start_index(location),
self.location_table.mid_index(location),
));
all_facts.cfg_edge.push((
self.location_table.mid_index(location),
self.location_table
.start_index(location.successor_within_block()),
));
}
self.super_statement(block, statement, location);
}
fn visit_assign(
&mut self,
block: BasicBlock,
place: &Place<'tcx>,
rvalue: &Rvalue<'tcx>,
location: Location,
) {
// When we see `X = ...`, then kill borrows of
// `(*X).foo` and so forth.
if let Some(all_facts) = self.all_facts {
if let Place::Local(temp) = place {
if let Some(borrow_indices) = self.borrow_set.local_map.get(temp) {
for &borrow_index in borrow_indices {
let location_index = self.location_table.mid_index(location);
all_facts.killed.push((borrow_index, location_index));
}
}
}
}
self.super_assign(block, place, rvalue, location);
}
fn visit_terminator(
&mut self,
block: BasicBlock,
terminator: &Terminator<'tcx>,
location: Location,
) {
if let Some(all_facts) = self.all_facts {
all_facts.cfg_edge.push((
self.location_table.start_index(location),
self.location_table.mid_index(location),
));
for successor_block in terminator.successors() {
all_facts.cfg_edge.push((
self.location_table.mid_index(location),
self.location_table
.start_index(successor_block.start_location()),
));
}
}
self.super_terminator(block, terminator, location);
}
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
debug!("visit_rvalue(rvalue={:?}, location={:?})", rvalue, location);
// Look for an rvalue like:
//
// & L
//
// where L is the path that is borrowed. In that case, we have
// to add the reborrow constraints (which don't fall out
// naturally from the type-checker).
if let Rvalue::Ref(region, _bk, ref borrowed_lv) = *rvalue {
self.add_reborrow_constraint(location, region, borrowed_lv);
match rvalue {
Rvalue::Ref(region, _borrow_kind, borrowed_place) => {
// In some cases, e.g. when borrowing from an unsafe
// place, we don't bother to create a loan, since
// there are no conditions to validate.
if let Some(all_facts) = self.all_facts {
if let Some(borrow_index) = self.borrow_set.location_map.get(&location) {
let region_vid = region.to_region_vid();
all_facts.borrow_region.push((
region_vid,
*borrow_index,
self.location_table.mid_index(location),
));
}
}
// Look for an rvalue like:
//
// & L
//
// where L is the path that is borrowed. In that case, we have
// to add the reborrow constraints (which don't fall out
// naturally from the type-checker).
self.add_reborrow_constraint(location, region, borrowed_place);
}
_ => { }
}
self.super_rvalue(rvalue, location);
}
fn visit_user_assert_ty(&mut self, _c_ty: &CanonicalTy<'tcx>,
_local: &Local, _location: Location) { }
fn visit_user_assert_ty(
&mut self,
_c_ty: &CanonicalTy<'tcx>,
_local: &Local,
_location: Location,
) {
}
}
impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
@ -122,8 +228,7 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
{
debug!(
"add_regular_live_constraint(live_ty={:?}, location={:?})",
live_ty,
location
live_ty, location
);
self.infcx
@ -144,8 +249,10 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
) {
let mut borrowed_place = borrowed_place;
debug!("add_reborrow_constraint({:?}, {:?}, {:?})",
location, borrow_region, borrowed_place);
debug!(
"add_reborrow_constraint({:?}, {:?}, {:?})",
location, borrow_region, borrowed_place
);
while let Projection(box PlaceProjection { base, elem }) = borrowed_place {
debug!("add_reborrow_constraint - iteration {:?}", borrowed_place);
@ -165,12 +272,20 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
location.successor_within_block(),
);
if let Some(all_facts) = self.all_facts {
all_facts.outlives.push((
ref_region.to_region_vid(),
borrow_region.to_region_vid(),
self.location_table.mid_index(location),
));
}
match mutbl {
hir::Mutability::MutImmutable => {
// Immutable reference. We don't need the base
// to be valid for the entire lifetime of
// the borrow.
break
break;
}
hir::Mutability::MutMutable => {
// Mutable reference. We *do* need the base
@ -199,19 +314,19 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
}
ty::TyRawPtr(..) => {
// deref of raw pointer, guaranteed to be valid
break
break;
}
ty::TyAdt(def, _) if def.is_box() => {
// deref of `Box`, need the base to be valid - propagate
}
_ => bug!("unexpected deref ty {:?} in {:?}", base_ty, borrowed_place)
_ => bug!("unexpected deref ty {:?} in {:?}", base_ty, borrowed_place),
}
}
ProjectionElem::Field(..) |
ProjectionElem::Downcast(..) |
ProjectionElem::Index(..) |
ProjectionElem::ConstantIndex { .. } |
ProjectionElem::Subslice { .. } => {
ProjectionElem::Field(..)
| ProjectionElem::Downcast(..)
| ProjectionElem::Index(..)
| ProjectionElem::ConstantIndex { .. }
| ProjectionElem::Subslice { .. } => {
// other field access
}
}

View file

@ -0,0 +1,194 @@
// 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 borrow_check::location::{LocationIndex, LocationTable};
use dataflow::indexes::BorrowIndex;
use rustc::ty::RegionVid;
use std::error::Error;
use std::fmt::Debug;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
/// The "facts" which are the basis of the NLL borrow analysis.
#[derive(Default)]
crate struct AllFacts {
// `borrow_region(R, B, P)` -- the region R may refer to data from borrow B
// starting at the point P (this is usually the point *after* a borrow rvalue)
crate borrow_region: Vec<(RegionVid, BorrowIndex, LocationIndex)>,
// universal_region(R) -- this is a "free region" within fn body
crate universal_region: Vec<RegionVid>,
// `cfg_edge(P,Q)` for each edge P -> Q in the control flow
crate cfg_edge: Vec<(LocationIndex, LocationIndex)>,
// `killed(B,P)` when some prefix of the path borrowed at B is assigned at point P
crate killed: Vec<(BorrowIndex, LocationIndex)>,
// `outlives(R1, R2, P)` when we require `R1@P: R2@P`
crate outlives: Vec<(RegionVid, RegionVid, LocationIndex)>,
// `region_live_at(R, P)` when the region R appears in a live variable at P
crate region_live_at: Vec<(RegionVid, LocationIndex)>,
}
impl AllFacts {
crate fn write_to_dir(
&self,
dir: impl AsRef<Path>,
location_table: &LocationTable,
) -> Result<(), Box<dyn Error>> {
let dir: &Path = dir.as_ref();
fs::create_dir_all(dir)?;
let wr = FactWriter { location_table, dir };
macro_rules! write_facts_to_path {
($wr:ident . write_facts_to_path($this:ident . [
$($field:ident,)*
])) => {
$(
$wr.write_facts_to_path(
&$this.$field,
&format!("{}.facts", stringify!($field))
)?;
)*
}
}
write_facts_to_path! {
wr.write_facts_to_path(self.[
borrow_region,
universal_region,
cfg_edge,
killed,
outlives,
region_live_at,
])
}
Ok(())
}
}
struct FactWriter<'w> {
location_table: &'w LocationTable,
dir: &'w Path,
}
impl<'w> FactWriter<'w> {
fn write_facts_to_path<T>(
&self,
rows: &Vec<T>,
file_name: &str,
) -> Result<(), Box<dyn Error>>
where
T: FactRow,
{
let file = &self.dir.join(file_name);
let mut file = File::create(file)?;
for row in rows {
row.write(&mut file, self.location_table)?;
}
Ok(())
}
}
trait FactRow {
fn write(
&self,
out: &mut File,
location_table: &LocationTable,
) -> Result<(), Box<dyn Error>>;
}
impl FactRow for RegionVid {
fn write(
&self,
out: &mut File,
location_table: &LocationTable,
) -> Result<(), Box<dyn Error>> {
write_row(out, location_table, &[self])
}
}
impl<A, B> FactRow for (A, B)
where
A: FactCell,
B: FactCell,
{
fn write(
&self,
out: &mut File,
location_table: &LocationTable,
) -> Result<(), Box<dyn Error>> {
write_row(out, location_table, &[&self.0, &self.1])
}
}
impl<A, B, C> FactRow for (A, B, C)
where
A: FactCell,
B: FactCell,
C: FactCell,
{
fn write(
&self,
out: &mut File,
location_table: &LocationTable,
) -> Result<(), Box<dyn Error>> {
write_row(out, location_table, &[&self.0, &self.1, &self.2])
}
}
impl<A, B, C, D> FactRow for (A, B, C, D)
where
A: FactCell,
B: FactCell,
C: FactCell,
D: FactCell,
{
fn write(
&self,
out: &mut File,
location_table: &LocationTable,
) -> Result<(), Box<dyn Error>> {
write_row(out, location_table, &[&self.0, &self.1, &self.2, &self.3])
}
}
fn write_row(
out: &mut dyn Write,
location_table: &LocationTable,
columns: &[&dyn FactCell],
) -> Result<(), Box<dyn Error>> {
for (index, c) in columns.iter().enumerate() {
let tail = if index == columns.len() - 1 {
"\n"
} else {
"\t"
};
write!(out, "{:?}{}", c.to_string(location_table), tail)?;
}
Ok(())
}
trait FactCell {
fn to_string(&self, location_table: &LocationTable) -> String;
}
impl<A: Debug> FactCell for A {
default fn to_string(&self, _location_table: &LocationTable) -> String {
format!("{:?}", self)
}
}
impl FactCell for LocationIndex {
fn to_string(&self, location_table: &LocationTable) -> String {
format!("{:?}", location_table.to_location(*self))
}
}

View file

@ -9,36 +9,39 @@
// except according to those terms.
use borrow_check::borrow_set::BorrowSet;
use borrow_check::location::LocationTable;
use dataflow::move_paths::MoveData;
use dataflow::FlowAtLocation;
use dataflow::MaybeInitializedPlaces;
use rustc::hir::def_id::DefId;
use rustc::mir::{ClosureRegionRequirements, ClosureOutlivesSubject, Mir};
use rustc::infer::InferCtxt;
use rustc::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, Mir};
use rustc::ty::{self, RegionKind, RegionVid};
use rustc::util::nodemap::FxHashMap;
use std::collections::BTreeSet;
use std::fmt::Debug;
use std::io;
use std::path::PathBuf;
use transform::MirSource;
use util::liveness::{LivenessResults, LocalSet};
use dataflow::FlowAtLocation;
use dataflow::MaybeInitializedPlaces;
use dataflow::move_paths::MoveData;
use self::mir_util::PassWhere;
use util as mir_util;
use util::pretty::{self, ALIGN};
use self::mir_util::PassWhere;
mod constraint_generation;
pub mod explain_borrow;
pub(crate) mod region_infer;
mod facts;
crate mod region_infer;
mod renumber;
mod subtype_constraint_generation;
pub(crate) mod type_check;
crate mod type_check;
mod universal_regions;
use self::facts::AllFacts;
use self::region_infer::RegionInferenceContext;
use self::universal_regions::UniversalRegions;
/// Rewrites the regions in the MIR to use NLL variables, also
/// scraping out the set of universal regions (e.g., region parameters)
/// declared on the function. That set will need to be given to
@ -71,10 +74,11 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
def_id: DefId,
universal_regions: UniversalRegions<'tcx>,
mir: &Mir<'tcx>,
location_table: &LocationTable,
param_env: ty::ParamEnv<'gcx>,
flow_inits: &mut FlowAtLocation<MaybeInitializedPlaces<'cx, 'gcx, 'tcx>>,
move_data: &MoveData<'tcx>,
_borrow_set: &BorrowSet<'tcx>,
borrow_set: &BorrowSet<'tcx>,
) -> (
RegionInferenceContext<'tcx>,
Option<ClosureRegionRequirements<'gcx>>,
@ -92,15 +96,47 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
move_data,
);
let mut all_facts = if infcx.tcx.sess.opts.debugging_opts.nll_facts {
Some(AllFacts::default())
} else {
None
};
if let Some(all_facts) = &mut all_facts {
all_facts
.universal_region
.extend(universal_regions.universal_regions());
}
// Create the region inference context, taking ownership of the region inference
// data that was contained in `infcx`.
let var_origins = infcx.take_region_var_origins();
let mut regioncx = RegionInferenceContext::new(var_origins, universal_regions, mir);
subtype_constraint_generation::generate(&mut regioncx, mir, constraint_sets);
let mut regioncx =
RegionInferenceContext::new(var_origins, universal_regions, mir);
// Generate various constraints.
subtype_constraint_generation::generate(
&mut regioncx,
&mut all_facts,
location_table,
mir,
constraint_sets,
);
constraint_generation::generate_constraints(
infcx,
&mut regioncx,
&mut all_facts,
location_table,
&mir,
borrow_set,
);
// Generate non-subtyping constraints.
constraint_generation::generate_constraints(infcx, &mut regioncx, &mir);
// Dump facts if requested.
if let Some(all_facts) = all_facts {
let def_path = infcx.tcx.hir.def_path(def_id);
let dir_path = PathBuf::from("nll-facts").join(def_path.to_filename_friendly_no_crate());
all_facts.write_to_dir(dir_path, location_table).unwrap();
}
// Solve the region constraints.
let closure_region_requirements = regioncx.solve(infcx, &mir, def_id);

View file

@ -8,14 +8,17 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use rustc::mir::Mir;
use borrow_check::location::LocationTable;
use borrow_check::nll::facts::AllFacts;
use rustc::infer::region_constraints::Constraint;
use rustc::infer::region_constraints::RegionConstraintData;
use rustc::infer::region_constraints::{Verify, VerifyBound};
use rustc::mir::{Location, Mir};
use rustc::ty;
use std::iter;
use syntax::codemap::Span;
use super::region_infer::{TypeTest, RegionInferenceContext, RegionTest};
use super::region_infer::{RegionInferenceContext, RegionTest, TypeTest};
use super::type_check::Locations;
use super::type_check::MirTypeckRegionConstraints;
use super::type_check::OutlivesSet;
@ -27,19 +30,30 @@ use super::type_check::OutlivesSet;
/// them into the NLL `RegionInferenceContext`.
pub(super) fn generate<'tcx>(
regioncx: &mut RegionInferenceContext<'tcx>,
all_facts: &mut Option<AllFacts>,
location_table: &LocationTable,
mir: &Mir<'tcx>,
constraints: &MirTypeckRegionConstraints<'tcx>,
) {
SubtypeConstraintGenerator { regioncx, mir }.generate(constraints);
SubtypeConstraintGenerator {
regioncx,
location_table,
mir,
}.generate(constraints, all_facts);
}
struct SubtypeConstraintGenerator<'cx, 'tcx: 'cx> {
regioncx: &'cx mut RegionInferenceContext<'tcx>,
location_table: &'cx LocationTable,
mir: &'cx Mir<'tcx>,
}
impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
fn generate(&mut self, constraints: &MirTypeckRegionConstraints<'tcx>) {
fn generate(
&mut self,
constraints: &MirTypeckRegionConstraints<'tcx>,
all_facts: &mut Option<AllFacts>,
) {
let MirTypeckRegionConstraints {
liveness_set,
outlives_sets,
@ -57,6 +71,17 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
self.regioncx.add_live_point(region_vid, *location, &cause);
}
if let Some(all_facts) = all_facts {
all_facts
.region_live_at
.extend(liveness_set.into_iter().flat_map(|(region, location, _)| {
let r = self.to_region_vid(region);
let p1 = self.location_table.start_index(*location);
let p2 = self.location_table.mid_index(*location);
iter::once((r, p1)).chain(iter::once((r, p2)))
}));
}
for OutlivesSet { locations, data } in outlives_sets {
debug!("generate: constraints at: {:#?}", locations);
let RegionConstraintData {
@ -65,7 +90,11 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
givens,
} = data;
let span = self.mir.source_info(locations.from_location).span;
let span = self.mir
.source_info(locations.from_location().unwrap_or(Location::START))
.span;
let at_location = locations.at_location().unwrap_or(Location::START);
for constraint in constraints.keys() {
debug!("generate: constraint: {:?}", constraint);
@ -83,8 +112,24 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
// reverse direction, because `regioncx` talks about
// "outlives" (`>=`) whereas the region constraints
// talk about `<=`.
self.regioncx
.add_outlives(span, b_vid, a_vid, locations.at_location);
self.regioncx.add_outlives(span, b_vid, a_vid, at_location);
// In the new analysis, all outlives relations etc
// "take effect" at the mid point of the statement
// that requires them, so ignore the `at_location`.
if let Some(all_facts) = all_facts {
if let Some(from_location) = locations.from_location() {
all_facts.outlives.push((
b_vid,
a_vid,
self.location_table.mid_index(from_location),
));
} else {
for location in self.location_table.all_points() {
all_facts.outlives.push((b_vid, a_vid, location));
}
}
}
}
for verify in verifys {
@ -109,7 +154,7 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
let lower_bound = self.to_region_vid(verify.region);
let point = locations.at_location;
let point = locations.at_location().unwrap_or(Location::START);
let test = self.verify_bound_to_region_test(&verify.bound);
@ -149,14 +194,6 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
}
fn to_region_vid(&self, r: ty::Region<'tcx>) -> ty::RegionVid {
// Every region that we see in the constraints came from the
// MIR or from the parameter environment. If the former, it
// will be a region variable. If the latter, it will be in
// the set of universal regions *somewhere*.
if let ty::ReVar(vid) = r {
*vid
} else {
self.regioncx.to_region_vid(r)
}
self.regioncx.to_region_vid(r)
}
}

View file

@ -29,7 +29,7 @@ use rustc::traits::PredicateObligations;
use rustc_data_structures::indexed_vec::Idx;
use super::{AtLocation, TypeChecker};
use super::{Locations, TypeChecker};
impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
pub(super) fn equate_inputs_and_outputs(
@ -47,26 +47,21 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
} = universal_regions;
let infcx = self.infcx;
let start_position = Location {
block: START_BLOCK,
statement_index: 0,
};
// Equate expected input tys with those in the MIR.
let argument_locals = (1..).map(Local::new);
for (&unnormalized_input_ty, local) in unnormalized_input_tys.iter().zip(argument_locals) {
let input_ty = self.normalize(&unnormalized_input_ty, start_position);
let input_ty = self.normalize(&unnormalized_input_ty, Locations::All);
let mir_input_ty = mir.local_decls[local].ty;
self.equate_normalized_input_or_output(start_position, input_ty, mir_input_ty);
self.equate_normalized_input_or_output(input_ty, mir_input_ty);
}
assert!(
mir.yield_ty.is_some() && universal_regions.yield_ty.is_some() ||
mir.yield_ty.is_none() && universal_regions.yield_ty.is_none()
);
);
if let Some(mir_yield_ty) = mir.yield_ty {
let ur_yield_ty = universal_regions.yield_ty.unwrap();
self.equate_normalized_input_or_output(start_position, ur_yield_ty, mir_yield_ty);
self.equate_normalized_input_or_output(ur_yield_ty, mir_yield_ty);
}
// Return types are a bit more complex. They may contain existential `impl Trait`
@ -75,13 +70,13 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
"equate_inputs_and_outputs: unnormalized_output_ty={:?}",
unnormalized_output_ty
);
let output_ty = self.normalize(&unnormalized_output_ty, start_position);
let output_ty = self.normalize(&unnormalized_output_ty, Locations::All);
debug!(
"equate_inputs_and_outputs: normalized output_ty={:?}",
output_ty
);
let mir_output_ty = mir.local_decls[RETURN_PLACE].ty;
let anon_type_map = self.fully_perform_op(start_position.at_self(), |cx| {
let anon_type_map = self.fully_perform_op(Locations::All, |cx| {
let mut obligations = ObligationAccumulator::default();
let (output_ty, anon_type_map) = obligations.add(infcx.instantiate_anon_types(
@ -112,7 +107,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
let anon_defn_ty = anon_defn_ty.subst(tcx, anon_decl.substs);
let anon_defn_ty = renumber::renumber_regions(
cx.infcx,
TyContext::Location(start_position),
TyContext::Location(Location::START),
&anon_defn_ty,
);
debug!(
@ -134,7 +129,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}).unwrap_or_else(|terr| {
span_mirbug!(
self,
start_position,
Location::START,
"equate_inputs_and_outputs: `{:?}=={:?}` failed with `{:?}`",
output_ty,
mir_output_ty,
@ -148,7 +143,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
// prove that `T: Iterator` where `T` is the type we
// instantiated it with).
if let Some(anon_type_map) = anon_type_map {
self.fully_perform_op(start_position.at_self(), |_cx| {
self.fully_perform_op(Locations::All, |_cx| {
infcx.constrain_anon_types(&anon_type_map, universal_regions);
Ok(InferOk {
value: (),
@ -158,13 +153,13 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
}
fn equate_normalized_input_or_output(&mut self, location: Location, a: Ty<'tcx>, b: Ty<'tcx>) {
fn equate_normalized_input_or_output(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) {
debug!("equate_normalized_input_or_output(a={:?}, b={:?})", a, b);
if let Err(terr) = self.eq_types(a, b, location.at_self()) {
if let Err(terr) = self.eq_types(a, b, Locations::All) {
span_mirbug!(
self,
location,
Location::START,
"equate_normalized_input_or_output: `{:?}=={:?}` failed with `{:?}`",
a,
b,

View file

@ -618,17 +618,72 @@ pub struct OutlivesSet<'tcx> {
pub data: RegionConstraintData<'tcx>,
}
/// The `Locations` type summarizes *where* region constraints are
/// required to hold. Normally, this is at a particular point which
/// created the obligation, but for constraints that the user gave, we
/// want the constraint to hold at all points.
#[derive(Copy, Clone, Debug)]
pub struct Locations {
/// The location in the MIR that generated these constraints.
/// This is intended for error reporting and diagnosis; the
/// constraints may *take effect* at a distinct spot.
pub from_location: Location,
pub enum Locations {
/// Indicates that a type constraint should always be true. This
/// is particularly important in the new borrowck analysis for
/// things like the type of the return slot. Consider this
/// example:
///
/// ```
/// fn foo<'a>(x: &'a u32) -> &'a u32 {
/// let y = 22;
/// return &y; // error
/// }
/// ```
///
/// Here, we wind up with the signature from the return type being
/// something like `&'1 u32` where `'1` is a universal region. But
/// the type of the return slot `_0` is something like `&'2 u32`
/// where `'2` is an existential region variable. The type checker
/// requires that `&'2 u32 = &'1 u32` -- but at what point? In the
/// older NLL analysis, we required this only at the entry point
/// to the function. By the nature of the constraints, this wound
/// up propagating to all points reachable from start (because
/// `'1` -- as a universal region -- is live everywhere). In the
/// newer analysis, though, this doesn't work: `_0` is considered
/// dead at the start (it has no usable value) and hence this type
/// equality is basically a no-op. Then, later on, when we do `_0
/// = &'3 y`, that region `'3` never winds up related to the
/// universal region `'1` and hence no error occurs. Therefore, we
/// use Locations::All instead, which ensures that the `'1` and
/// `'2` are equal everything. We also use this for other
/// user-given type annotations; e.g., if the user wrote `let mut
/// x: &'static u32 = ...`, we would ensure that all values
/// assigned to `x` are of `'static` lifetime.
All,
/// The constraints must be met at this location. In terms of the
/// NLL RFC, when you have a constraint `R1: R2 @ P`, this field
/// is the `P` value.
pub at_location: Location,
Pair {
/// The location in the MIR that generated these constraints.
/// This is intended for error reporting and diagnosis; the
/// constraints may *take effect* at a distinct spot.
from_location: Location,
/// The constraints must be met at this location. In terms of the
/// NLL RFC, when you have a constraint `R1: R2 @ P`, this field
/// is the `P` value.
at_location: Location,
}
}
impl Locations {
pub fn from_location(&self) -> Option<Location> {
match self {
Locations::All => None,
Locations::Pair { from_location, .. } => Some(*from_location),
}
}
pub fn at_location(&self) -> Option<Location> {
match self {
Locations::All => None,
Locations::Pair { at_location, .. } => Some(*at_location),
}
}
}
impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
@ -770,7 +825,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
"check_stmt: user_assert_ty ty={:?} local_ty={:?}",
ty, local_ty
);
if let Err(terr) = self.eq_types(ty, local_ty, location.at_self()) {
if let Err(terr) = self.eq_types(ty, local_ty, Locations::All) {
span_mirbug!(
self,
stmt,
@ -820,7 +875,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
let place_ty = location.ty(mir, tcx).to_ty(tcx);
let rv_ty = value.ty(mir, tcx);
let locations = Locations {
let locations = Locations::Pair {
from_location: term_location,
at_location: target.start_location(),
};
@ -839,7 +894,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
// *both* blocks, so we need to ensure that it holds
// at both locations.
if let Some(unwind) = unwind {
let locations = Locations {
let locations = Locations::Pair {
from_location: term_location,
at_location: unwind.start_location(),
};
@ -971,7 +1026,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
match *destination {
Some((ref dest, target_block)) => {
let dest_ty = dest.ty(mir, tcx).to_ty(tcx);
let locations = Locations {
let locations = Locations::Pair {
from_location: term_location,
at_location: target_block.start_location(),
};
@ -1375,7 +1430,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
};
let operand_ty = operand.ty(mir, tcx);
if let Err(terr) =
self.sub_types(operand_ty, field_ty, location.at_successor_within_block())
self.sub_types(operand_ty, field_ty, location.at_self())
{
span_mirbug!(
self,
@ -1514,12 +1569,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
}
fn normalize<T>(&mut self, value: &T, location: Location) -> T
fn normalize<T>(&mut self, value: &T, location: impl ToLocations) -> T
where
T: fmt::Debug + TypeFoldable<'tcx>,
{
debug!("normalize(value={:?}, location={:?})", value, location);
self.fully_perform_op(location.at_self(), |this| {
self.fully_perform_op(location.to_locations(), |this| {
let Normalized { value, obligations } = this.infcx
.at(&this.misc(this.last_span), this.param_env)
.normalize(value)
@ -1585,16 +1640,32 @@ trait AtLocation {
impl AtLocation for Location {
fn at_self(self) -> Locations {
Locations {
Locations::Pair {
from_location: self,
at_location: self,
}
}
fn at_successor_within_block(self) -> Locations {
Locations {
Locations::Pair {
from_location: self,
at_location: self.successor_within_block(),
}
}
}
trait ToLocations: fmt::Debug + Copy {
fn to_locations(self) -> Locations;
}
impl ToLocations for Locations {
fn to_locations(self) -> Locations {
self
}
}
impl ToLocations for Location {
fn to_locations(self) -> Locations {
self.at_self()
}
}

View file

@ -34,6 +34,7 @@ Rust MIR: a lowered representation of Rust. Also: an experiment!
#![feature(inclusive_range_methods)]
#![feature(crate_visibility_modifier)]
#![feature(never_type)]
#![feature(specialization)]
#![cfg_attr(stage0, feature(try_trait))]
extern crate arena;

View file

@ -550,7 +550,7 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
}
fn drop_flags_on_init(&mut self) {
let loc = Location { block: START_BLOCK, statement_index: 0 };
let loc = Location::START;
let span = self.patch.source_info_for_location(self.mir, loc).span;
let false_ = self.constant_bool(span, false);
for flag in self.drop_flags.values() {
@ -576,7 +576,7 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
}
fn drop_flags_for_args(&mut self) {
let loc = Location { block: START_BLOCK, statement_index: 0 };
let loc = Location::START;
dataflow::drop_flag_effects_for_function_entry(
self.tcx, self.mir, self.env, |path, ds| {
self.set_drop_flag(loc, path, ds);