"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],
// 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};
// 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,
};
/// '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> {
/// 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));
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> {
{
debug!(
"add_regular_live_constraint(live_ty={:?}, location={:?})",
- live_ty,
- location
+ live_ty, location
);
self.infcx
) {
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);
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
}
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
}
}
--- /dev/null
+// 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))
+ }
+}
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;
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>>,
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);
// 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;
/// 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,
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 {
// "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 {
#![feature(inclusive_range_methods)]
#![feature(crate_visibility_modifier)]
#![feature(never_type)]
+#![feature(specialization)]
#![cfg_attr(stage0, feature(try_trait))]
extern crate arena;