add -Znll-facts switch that dumps facts for new analysis

This commit is contained in:
Niko Matsakis 2018-05-01 10:49:11 -04:00
parent 74bb9171cc
commit eaac10ec0d
7 changed files with 440 additions and 53 deletions

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

@ -8,8 +8,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code)] // TODO -- will be used in a later commit, remove then
use rustc::mir::{BasicBlock, Location, Mir};
use rustc_data_structures::indexed_vec::{Idx, IndexVec};

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

@ -10,32 +10,35 @@
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;
mod facts;
crate mod region_infer;
mod renumber;
mod subtype_constraint_generation;
crate mod type_check;
mod universal_regions;
use self::facts::AllFacts;
use self::region_infer::RegionInferenceContext;
use self::universal_regions::UniversalRegions;
@ -71,11 +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,
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>>,
@ -93,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::{Location, 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 {
@ -88,6 +113,23 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
// "outlives" (`>=`) whereas the region constraints
// talk about `<=`.
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 {

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;