From ad93b695d1fc8340d520aea1ad22e1d35930e1e2 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 Nov 2017 18:47:09 -0500 Subject: [PATCH] MIR typeck: refactor to track region constraints --- src/librustc/infer/region_constraints/mod.rs | 8 + src/librustc_mir/transform/type_check.rs | 314 ++++++++++++++----- 2 files changed, 250 insertions(+), 72 deletions(-) diff --git a/src/librustc/infer/region_constraints/mod.rs b/src/librustc/infer/region_constraints/mod.rs index 057f1b35ac10..a9e36a5740fd 100644 --- a/src/librustc/infer/region_constraints/mod.rs +++ b/src/librustc/infer/region_constraints/mod.rs @@ -896,3 +896,11 @@ impl<'a, 'gcx, 'tcx> VerifyBound<'tcx> { } } } + +impl<'tcx> RegionConstraintData<'tcx> { + /// True if this region constraint data contains no constraints. + pub fn is_empty(&self) -> bool { + let RegionConstraintData { constraints, verifys, givens } = self; + constraints.is_empty() && verifys.is_empty() && givens.is_empty() + } +} diff --git a/src/librustc_mir/transform/type_check.rs b/src/librustc_mir/transform/type_check.rs index 6384fc56b300..a3dbcefd0e0c 100644 --- a/src/librustc_mir/transform/type_check.rs +++ b/src/librustc_mir/transform/type_check.rs @@ -11,7 +11,8 @@ //! This pass type-checks the MIR to ensure it is not broken. #![allow(unreachable_code)] -use rustc::infer::{InferCtxt, InferOk, InferResult, UnitResult}; +use rustc::infer::{InferCtxt, InferOk, InferResult, LateBoundRegionConversionTime, + RegionConstraintData, UnitResult}; use rustc::traits::{self, FulfillmentContext}; use rustc::ty::error::TypeError; use rustc::ty::fold::TypeFoldable; @@ -28,6 +29,34 @@ use transform::{MirPass, MirSource}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::indexed_vec::Idx; +/// Type checks the given `mir` in the context of the inference +/// context `infcx`. Returns any region constraints that have yet to +/// be proven. +/// +/// This phase of type-check ought to be infallible -- this is because +/// the original, HIR-based type-check succeeded. So if any errors +/// occur here, we will get a `bug!` reported. +pub fn type_check<'a, 'gcx, 'tcx>( + infcx: &InferCtxt<'a, 'gcx, 'tcx>, + body_id: ast::NodeId, + param_env: ty::ParamEnv<'gcx>, + mir: &Mir<'tcx>, +) -> MirTypeckRegionConstraints<'tcx> { + let mut checker = TypeChecker::new(infcx, body_id, param_env); + let errors_reported = { + let mut verifier = TypeVerifier::new(&mut checker, mir); + verifier.visit_mir(mir); + verifier.errors_reported + }; + + if !errors_reported { + // if verifier failed, don't do further checks to avoid ICEs + checker.typeck_mir(mir); + } + + checker.constraints +} + fn mirbug(tcx: TyCtxt, span: Span, msg: &str) { tcx.sess.diagnostic().span_bug(span, msg); } @@ -128,7 +157,7 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> { } fn sanitize_type(&mut self, parent: &fmt::Debug, ty: Ty<'tcx>) -> Ty<'tcx> { - if ty.needs_infer() || ty.has_escaping_regions() || ty.references_error() { + if ty.has_escaping_regions() || ty.references_error() { span_mirbug_and_err!(self, parent, "bad type {:?}", ty) } else { ty @@ -145,7 +174,9 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> { let sty = self.sanitize_type(lvalue, sty); let ty = self.tcx().type_of(def_id); let ty = self.cx.normalize(&ty, location); - if let Err(terr) = self.cx.eq_types(self.last_span, ty, sty, location) { + if let Err(terr) = self.cx + .eq_types(self.last_span, ty, sty, location.at_self()) + { span_mirbug!( self, lvalue, @@ -267,16 +298,18 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> { ProjectionElem::Field(field, fty) => { let fty = self.sanitize_type(lvalue, fty); match self.field_ty(lvalue, base, field, location) { - Ok(ty) => if let Err(terr) = self.cx.eq_types(span, ty, fty, location) { - span_mirbug!( - self, - lvalue, - "bad field access ({:?}: {:?}): {:?}", - ty, - fty, - terr - ); - }, + Ok(ty) => { + if let Err(terr) = self.cx.eq_types(span, ty, fty, location.at_self()) { + span_mirbug!( + self, + lvalue, + "bad field access ({:?}: {:?}): {:?}", + ty, + fty, + terr + ); + } + } Err(FieldAccessError::OutOfRange { field_count }) => span_mirbug!( self, lvalue, @@ -364,12 +397,61 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> { } } +/// The MIR type checker. Visits the MIR and enforces all the +/// constraints needed for it to be valid and well-typed. Along the +/// way, it accrues region constraints -- these can later be used by +/// NLL region checking. pub struct TypeChecker<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> { infcx: &'a InferCtxt<'a, 'gcx, 'tcx>, param_env: ty::ParamEnv<'gcx>, last_span: Span, body_id: ast::NodeId, reported_errors: FxHashSet<(Ty<'tcx>, Span)>, + constraints: MirTypeckRegionConstraints<'tcx>, +} + +/// A collection of region constraints that must be satisfied for the +/// program to be considered well-typed. +#[derive(Default)] +pub struct MirTypeckRegionConstraints<'tcx> { + /// In general, the type-checker is not responsible for enforcing + /// liveness constraints; this job falls to the region inferencer, + /// which performs a liveness analysis. However, in some limited + /// cases, the MIR type-checker creates temporary regions that do + /// not otherwise appear in the MIR -- in particular, the + /// late-bound regions that it instantiates at call-sites -- and + /// hence it must report on their liveness constraints. + pub liveness_set: Vec<(ty::Region<'tcx>, Location)>, + + /// During the course of type-checking, we will accumulate region + /// constraints due to performing subtyping operations or solving + /// traits. These are accumulated into this vector for later use. + pub outlives_sets: Vec>, +} + +/// Outlives relationships between regions and types created at a +/// particular point within the control-flow graph. +pub struct OutlivesSet<'tcx> { + /// The locations associated with these constraints. + pub locations: Locations, + + /// Constraints generated. In terms of the NLL RFC, when you have + /// a constraint `R1: R2 @ P`, the data in there specifies things + /// like `R1: R2`. + pub data: RegionConstraintData<'tcx>, +} + +#[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, + + /// 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, } impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { @@ -384,6 +466,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { body_id, param_env, reported_errors: FxHashSet(), + constraints: MirTypeckRegionConstraints::default(), } } @@ -391,37 +474,54 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { traits::ObligationCause::misc(span, self.body_id) } - fn fully_perform_op(&self, op: OP) -> Result> + fn fully_perform_op( + &mut self, + locations: Locations, + op: OP, + ) -> Result> where - OP: FnOnce() -> InferResult<'tcx, R>, + OP: FnOnce(&mut Self) -> InferResult<'tcx, R>, { let mut fulfill_cx = FulfillmentContext::new(); - let InferOk { value, obligations } = self.infcx.commit_if_ok(|_| op())?; + let InferOk { value, obligations } = self.infcx.commit_if_ok(|_| op(self))?; fulfill_cx.register_predicate_obligations(self.infcx, obligations); if let Err(e) = fulfill_cx.select_all_or_error(self.infcx) { span_mirbug!(self, "", "errors selecting obligation: {:?}", e); - } // FIXME propagate + } + + let data = self.infcx.take_and_reset_region_constraints(); + if !data.is_empty() { + self.constraints + .outlives_sets + .push(OutlivesSet { locations, data }); + } + Ok(value) } - fn sub_types(&self, sub: Ty<'tcx>, sup: Ty<'tcx>, _at_location: Location) -> UnitResult<'tcx> { - self.fully_perform_op(|| { - self.infcx - .at(&self.misc(self.last_span), self.param_env) + fn sub_types( + &mut self, + sub: Ty<'tcx>, + sup: Ty<'tcx>, + locations: Locations, + ) -> UnitResult<'tcx> { + self.fully_perform_op(locations, |this| { + this.infcx + .at(&this.misc(this.last_span), this.param_env) .sup(sup, sub) }) } fn eq_types( - &self, + &mut self, _span: Span, a: Ty<'tcx>, b: Ty<'tcx>, - _at_location: Location, + locations: Locations, ) -> UnitResult<'tcx> { - self.fully_perform_op(|| { - self.infcx - .at(&self.misc(self.last_span), self.param_env) + self.fully_perform_op(locations, |this| { + this.infcx + .at(&this.misc(this.last_span), this.param_env) .eq(b, a) }) } @@ -437,7 +537,9 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { StatementKind::Assign(ref lv, ref rv) => { let lv_ty = lv.ty(mir, tcx).to_ty(tcx); let rv_ty = rv.ty(mir, tcx); - if let Err(terr) = self.sub_types(rv_ty, lv_ty, location.successor_within_block()) { + if let Err(terr) = + self.sub_types(rv_ty, lv_ty, location.at_successor_within_block()) + { span_mirbug!( self, stmt, @@ -482,7 +584,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } } - fn check_terminator(&mut self, mir: &Mir<'tcx>, term: &Terminator<'tcx>, location: Location) { + fn check_terminator( + &mut self, + mir: &Mir<'tcx>, + term: &Terminator<'tcx>, + term_location: Location, + ) { debug!("check_terminator: {:?}", term); let tcx = self.tcx(); match term.kind { @@ -505,7 +612,11 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { let lv_ty = location.ty(mir, tcx).to_ty(tcx); let rv_ty = value.ty(mir, tcx); - if let Err(terr) = self.sub_types(rv_ty, lv_ty, target.start_location()) { + let locations = Locations { + from_location: term_location, + at_location: target.start_location(), + }; + if let Err(terr) = self.sub_types(rv_ty, lv_ty, locations) { span_mirbug!( self, term, @@ -520,7 +631,11 @@ 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 { - if let Err(terr) = self.sub_types(rv_ty, lv_ty, unwind.start_location()) { + let locations = Locations { + from_location: term_location, + at_location: unwind.start_location(), + }; + if let Err(terr) = self.sub_types(rv_ty, lv_ty, locations) { span_mirbug!( self, term, @@ -538,7 +653,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { .. } => { let discr_ty = discr.ty(mir, tcx); - if let Err(terr) = self.sub_types(discr_ty, switch_ty, location) { + if let Err(terr) = self.sub_types(discr_ty, switch_ty, term_location.at_self()) { span_mirbug!( self, term, @@ -568,14 +683,31 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { return; } }; - let sig = tcx.erase_late_bound_regions(&sig); - let sig = self.normalize(&sig, location); - self.check_call_dest(mir, term, &sig, destination); + let (sig, map) = self.infcx.replace_late_bound_regions_with_fresh_var( + term.source_info.span, + LateBoundRegionConversionTime::FnCall, + &sig, + ); + let sig = self.normalize(&sig, term_location); + self.check_call_dest(mir, term, &sig, destination, term_location); + + // The ordinary liveness rules will ensure that all + // regions in the type of the callee are live here. We + // then further constrain the late-bound regions that + // were instantiated at the call site to be live as + // well. The resulting is that all the input (and + // output) types in the signature must be live, since + // all the inputs that fed into it were live. + for &late_bound_region in map.values() { + self.constraints + .liveness_set + .push((late_bound_region, term_location)); + } if self.is_box_free(func) { - self.check_box_free_inputs(mir, term, &sig, args, location); + self.check_box_free_inputs(mir, term, &sig, args, term_location); } else { - self.check_call_inputs(mir, term, &sig, args, location); + self.check_call_inputs(mir, term, &sig, args, term_location); } } TerminatorKind::Assert { @@ -599,16 +731,18 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { let value_ty = value.ty(mir, tcx); match mir.yield_ty { None => span_mirbug!(self, term, "yield in non-generator"), - Some(ty) => if let Err(terr) = self.sub_types(value_ty, ty, location) { - span_mirbug!( - self, - term, - "type of yield value is {:?}, but the yield type is {:?}: {:?}", - value_ty, - ty, - terr - ); - }, + Some(ty) => { + if let Err(terr) = self.sub_types(value_ty, ty, term_location.at_self()) { + span_mirbug!( + self, + term, + "type of yield value is {:?}, but the yield type is {:?}: {:?}", + value_ty, + ty, + terr + ); + } + } } } } @@ -620,14 +754,17 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { term: &Terminator<'tcx>, sig: &ty::FnSig<'tcx>, destination: &Option<(Lvalue<'tcx>, BasicBlock)>, + term_location: Location, ) { let tcx = self.tcx(); match *destination { Some((ref dest, target_block)) => { let dest_ty = dest.ty(mir, tcx).to_ty(tcx); - if let Err(terr) = - self.sub_types(sig.output(), dest_ty, target_block.start_location()) - { + let locations = Locations { + from_location: term_location, + at_location: target_block.start_location(), + }; + if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations) { span_mirbug!( self, term, @@ -653,7 +790,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { term: &Terminator<'tcx>, sig: &ty::FnSig<'tcx>, args: &[Operand<'tcx>], - location: Location, + term_location: Location, ) { debug!("check_call_inputs({:?}, {:?})", sig, args); if args.len() < sig.inputs().len() || (args.len() > sig.inputs().len() && !sig.variadic) { @@ -661,7 +798,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } for (n, (fn_arg, op_arg)) in sig.inputs().iter().zip(args).enumerate() { let op_arg_ty = op_arg.ty(mir, self.tcx()); - if let Err(terr) = self.sub_types(op_arg_ty, fn_arg, location) { + if let Err(terr) = self.sub_types(op_arg_ty, fn_arg, term_location.at_self()) { span_mirbug!( self, term, @@ -699,7 +836,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { term: &Terminator<'tcx>, sig: &ty::FnSig<'tcx>, args: &[Operand<'tcx>], - location: Location, + term_location: Location, ) { debug!("check_box_free_inputs"); @@ -733,7 +870,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } }; - if let Err(terr) = self.sub_types(arg_ty, pointee_ty, location) { + if let Err(terr) = self.sub_types(arg_ty, pointee_ty, term_location.at_self()) { span_mirbug!( self, term, @@ -834,7 +971,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } } - fn check_local(&mut self, mir: &Mir<'gcx>, local: Local, local_decl: &LocalDecl<'gcx>) { + fn check_local(&mut self, mir: &Mir<'tcx>, local: Local, local_decl: &LocalDecl<'tcx>) { match mir.local_kind(local) { LocalKind::ReturnPointer | LocalKind::Arg => { // return values of normal functions are required to be @@ -850,7 +987,13 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { let span = local_decl.source_info.span; let ty = local_decl.ty; - if !ty.is_sized(self.tcx().global_tcx(), self.param_env, span) { + + // Erase the regions from `ty` to get a global type. The + // `Sized` bound in no way depends on precise regions, so this + // shouldn't affect `is_sized`. + let gcx = self.tcx().global_tcx(); + let erased_ty = gcx.lift(&self.tcx().erase_regions(&ty)).unwrap(); + if !erased_ty.is_sized(gcx, self.param_env, span) { // in current MIR construction, all non-control-flow rvalue // expressions evaluate through `as_temp` or `into` a return // slot or local, so to find all unsized rvalues it is enough @@ -868,7 +1011,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } } - fn typeck_mir(&mut self, mir: &Mir<'gcx>) { + fn typeck_mir(&mut self, mir: &Mir<'tcx>) { self.last_span = mir.span; debug!("run_on_mir: {:?}", mir.span); @@ -894,16 +1037,15 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } } - - fn normalize(&mut self, value: &T, _location: Location) -> T + fn normalize(&mut self, value: &T, location: Location) -> T where T: fmt::Debug + TypeFoldable<'tcx>, { - self.fully_perform_op(|| { - let mut selcx = traits::SelectionContext::new(self.infcx); - let cause = traits::ObligationCause::misc(self.last_span, ast::CRATE_NODE_ID); + self.fully_perform_op(location.at_self(), |this| { + let mut selcx = traits::SelectionContext::new(this.infcx); + let cause = traits::ObligationCause::misc(this.last_span, ast::CRATE_NODE_ID); let traits::Normalized { value, obligations } = - traits::normalize(&mut selcx, self.param_env, cause, value); + traits::normalize(&mut selcx, this.param_env, cause, value); Ok(InferOk { value, obligations }) }).unwrap() } @@ -924,16 +1066,44 @@ impl MirPass for TypeckMir { } let param_env = tcx.param_env(def_id); tcx.infer_ctxt().enter(|infcx| { - let mut checker = TypeChecker::new(&infcx, id, param_env); - { - let mut verifier = TypeVerifier::new(&mut checker, mir); - verifier.visit_mir(mir); - if verifier.errors_reported { - // don't do further checks to avoid ICEs - return; - } - } - checker.typeck_mir(mir); + let _region_constraint_sets = type_check(&infcx, id, param_env, mir); + + // For verification purposes, we just ignore the resulting + // region constraint sets. Not our problem. =) }); } } + +trait AtLocation { + /// Creates a `Locations` where `self` is both the from-location + /// and the at-location. This means that any required region + /// relationships must hold upon entering the statement/terminator + /// indicated by `self`. This is typically used when processing + /// "inputs" to the given location. + fn at_self(self) -> Locations; + + /// Creates a `Locations` where `self` is the from-location and + /// its successor within the block is the at-location. This means + /// that any required region relationships must hold only upon + /// **exiting** the statement/terminator indicated by `self`. This + /// is for example used when you have a `lv = rv` statement: it + /// indicates that the `typeof(rv) <: typeof(lv)` as of the + /// **next** statement. + fn at_successor_within_block(self) -> Locations; +} + +impl AtLocation for Location { + fn at_self(self) -> Locations { + Locations { + from_location: self, + at_location: self, + } + } + + fn at_successor_within_block(self) -> Locations { + Locations { + from_location: self, + at_location: self.successor_within_block(), + } + } +}