diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index 55bfbed0b393..37f6d47ff849 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -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 diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index dc97c9415670..0beda679e695 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -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], diff --git a/src/librustc_mir/borrow_check/location.rs b/src/librustc_mir/borrow_check/location.rs new file mode 100644 index 000000000000..28da1b2d7335 --- /dev/null +++ b/src/librustc_mir/borrow_check/location.rs @@ -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 or the MIT license +// , 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, +} + +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 { + (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 + } +} diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs index c619f350f58d..3e3f510e308c 100644 --- a/src/librustc_mir/borrow_check/mod.rs +++ b/src/librustc_mir/borrow_check/mod.rs @@ -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, diff --git a/src/librustc_mir/borrow_check/nll/constraint_generation.rs b/src/librustc_mir/borrow_check/nll/constraint_generation.rs index afaedecdf0ab..d34e9434fbf2 100644 --- a/src/librustc_mir/borrow_check/nll/constraint_generation.rs +++ b/src/librustc_mir/borrow_check/nll/constraint_generation.rs @@ -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, + 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, + 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 } } diff --git a/src/librustc_mir/borrow_check/nll/facts.rs b/src/librustc_mir/borrow_check/nll/facts.rs new file mode 100644 index 000000000000..2802aa0dff4e --- /dev/null +++ b/src/librustc_mir/borrow_check/nll/facts.rs @@ -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 or the MIT license +// , 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, + + // `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, + location_table: &LocationTable, + ) -> Result<(), Box> { + 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( + &self, + rows: &Vec, + file_name: &str, + ) -> Result<(), Box> + 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>; +} + +impl FactRow for RegionVid { + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[self]) + } +} + +impl FactRow for (A, B) +where + A: FactCell, + B: FactCell, +{ + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[&self.0, &self.1]) + } +} + +impl FactRow for (A, B, C) +where + A: FactCell, + B: FactCell, + C: FactCell, +{ + fn write( + &self, + out: &mut File, + location_table: &LocationTable, + ) -> Result<(), Box> { + write_row(out, location_table, &[&self.0, &self.1, &self.2]) + } +} + +impl 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> { + 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> { + 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 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)) + } +} diff --git a/src/librustc_mir/borrow_check/nll/mod.rs b/src/librustc_mir/borrow_check/nll/mod.rs index 3ca1bd23e86e..0b1729294d84 100644 --- a/src/librustc_mir/borrow_check/nll/mod.rs +++ b/src/librustc_mir/borrow_check/nll/mod.rs @@ -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>, move_data: &MoveData<'tcx>, - _borrow_set: &BorrowSet<'tcx>, + borrow_set: &BorrowSet<'tcx>, ) -> ( RegionInferenceContext<'tcx>, Option>, @@ -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); diff --git a/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs b/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs index 32728145b29f..9db19085a396 100644 --- a/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs +++ b/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs @@ -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, + 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, + ) { 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) } } diff --git a/src/librustc_mir/borrow_check/nll/type_check/input_output.rs b/src/librustc_mir/borrow_check/nll/type_check/input_output.rs index b1aeae0b76bb..2b1878c33e90 100644 --- a/src/librustc_mir/borrow_check/nll/type_check/input_output.rs +++ b/src/librustc_mir/borrow_check/nll/type_check/input_output.rs @@ -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, diff --git a/src/librustc_mir/borrow_check/nll/type_check/mod.rs b/src/librustc_mir/borrow_check/nll/type_check/mod.rs index a21b9196badb..42a1745addff 100644 --- a/src/librustc_mir/borrow_check/nll/type_check/mod.rs +++ b/src/librustc_mir/borrow_check/nll/type_check/mod.rs @@ -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 { + match self { + Locations::All => None, + Locations::Pair { from_location, .. } => Some(*from_location), + } + } + + pub fn at_location(&self) -> Option { + 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(&mut self, value: &T, location: Location) -> T + fn normalize(&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() + } +} diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs index 95cf3b8ddc6c..2545ba3a94af 100644 --- a/src/librustc_mir/lib.rs +++ b/src/librustc_mir/lib.rs @@ -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; diff --git a/src/librustc_mir/transform/elaborate_drops.rs b/src/librustc_mir/transform/elaborate_drops.rs index 8b771fcf4936..5397d18cdd72 100644 --- a/src/librustc_mir/transform/elaborate_drops.rs +++ b/src/librustc_mir/transform/elaborate_drops.rs @@ -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);