]> git.lizzy.rs Git - rust.git/blobdiff - src/librustc_mir/transform/qualify_consts.rs
Auto merge of #61673 - RalfJung:miri-no-hard-float, r=eddyb,oli-obk
[rust.git] / src / librustc_mir / transform / qualify_consts.rs
index fe94181047fcd3a5b76b3940de3dc4e20dbbb4fc..19bd812ec80c0106ab784b3dc4ab24321ac0eec8 100644 (file)
 /// What kind of item we are in.
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 enum Mode {
-    Const,
+    /// A `static` item.
     Static,
+    /// A `static mut` item.
     StaticMut,
+    /// A `const fn` item.
     ConstFn,
-    Fn
+    /// A `const` item or an anonymous constant (e.g. in array lengths).
+    Const,
+    /// Other type of `fn`.
+    NonConstFn,
+}
+
+impl Mode {
+    /// Determine whether we have to do full const-checking because syntactically, we
+    /// are required to be "const".
+    #[inline]
+    fn requires_const_checking(self) -> bool {
+        self != Mode::NonConstFn
+    }
 }
 
 impl fmt::Display for Mode {
@@ -48,7 +62,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
             Mode::Const => write!(f, "constant"),
             Mode::Static | Mode::StaticMut => write!(f, "static"),
             Mode::ConstFn => write!(f, "constant function"),
-            Mode::Fn => write!(f, "function")
+            Mode::NonConstFn => write!(f, "function")
         }
     }
 }
@@ -113,7 +127,7 @@ struct ConstCx<'a, 'tcx> {
     tcx: TyCtxt<'a, 'tcx, 'tcx>,
     param_env: ty::ParamEnv<'tcx>,
     mode: Mode,
-    mir: &'a Body<'tcx>,
+    body: &'a Body<'tcx>,
 
     per_local: PerQualif<BitSet<Local>>,
 }
@@ -135,6 +149,12 @@ enum ValueSource<'a, 'tcx> {
     },
 }
 
+/// A "qualif"(-ication) is a way to look for something "bad" in the MIR that would disqualify some
+/// code for promotion or prevent it from evaluating at compile time. So `return true` means
+/// "I found something bad, no reason to go on searching". `false` is only returned if we
+/// definitely cannot find anything bad anywhere.
+///
+/// The default implementations proceed structurally.
 trait Qualif {
     const IDX: usize;
 
@@ -166,7 +186,7 @@ fn in_projection_structurally(
         let base_qualif = Self::in_place(cx, &proj.base);
         let qualif = base_qualif && Self::mask_for_ty(
             cx,
-            proj.base.ty(cx.mir, cx.tcx)
+            proj.base.ty(cx.body, cx.tcx)
                 .projection_ty(cx.tcx, &proj.elem)
                 .ty,
         );
@@ -245,7 +265,7 @@ fn in_rvalue_structurally(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool
                 // Special-case reborrows to be more like a copy of the reference.
                 if let Place::Projection(ref proj) = *place {
                     if let ProjectionElem::Deref = proj.elem {
-                        let base_ty = proj.base.ty(cx.mir, cx.tcx).ty;
+                        let base_ty = proj.base.ty(cx.body, cx.tcx).ty;
                         if let ty::Ref(..) = base_ty.sty {
                             return Self::in_place(cx, &proj.base);
                         }
@@ -285,7 +305,11 @@ fn in_value(cx: &ConstCx<'_, 'tcx>, source: ValueSource<'_, 'tcx>) -> bool {
     }
 }
 
-// Constant containing interior mutability (UnsafeCell).
+/// Constant containing interior mutability (`UnsafeCell<T>`).
+/// This must be ruled out to make sure that evaluating the constant at compile-time
+/// and at *any point* during the run-time would produce the same result. In particular,
+/// promotion of temporaries must not change program behavior; if the promoted could be
+/// written to, that would be a problem.
 struct HasMutInterior;
 
 impl Qualif for HasMutInterior {
@@ -301,7 +325,7 @@ fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
             // allowed in constants (and the `Checker` will error), and/or it
             // won't be promoted, due to `&mut ...` or interior mutability.
             Rvalue::Ref(_, kind, ref place) => {
-                let ty = place.ty(cx.mir, cx.tcx).ty;
+                let ty = place.ty(cx.body, cx.tcx).ty;
 
                 if let BorrowKind::Mut { .. } = kind {
                     // In theory, any zero-sized value could be borrowed
@@ -314,10 +338,10 @@ fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
                             _ => return true,
                         }
                     } else if let ty::Array(_, len) = ty.sty {
-                        // FIXME(eddyb) the `cx.mode == Mode::Fn` condition
+                        // FIXME(eddyb) the `cx.mode == Mode::NonConstFn` condition
                         // seems unnecessary, given that this is merely a ZST.
                         match len.assert_usize(cx.tcx) {
-                            Some(0) if cx.mode == Mode::Fn => {},
+                            Some(0) if cx.mode == Mode::NonConstFn => {},
                             _ => return true,
                         }
                     } else {
@@ -329,7 +353,7 @@ fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
             Rvalue::Aggregate(ref kind, _) => {
                 if let AggregateKind::Adt(def, ..) = **kind {
                     if Some(def.did) == cx.tcx.lang_items().unsafe_cell_type() {
-                        let ty = rvalue.ty(cx.mir, cx.tcx);
+                        let ty = rvalue.ty(cx.body, cx.tcx);
                         assert_eq!(Self::in_any_value_of_ty(cx, ty), Some(true));
                         return true;
                     }
@@ -343,7 +367,10 @@ fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
     }
 }
 
-// Constant containing an ADT that implements Drop.
+/// Constant containing an ADT that implements `Drop`.
+/// This must be ruled out (a) because we cannot run `Drop` during compile-time
+/// as that might not be a `const fn`, and (b) because implicit promotion would
+/// remove side-effects that occur as part of dropping that value.
 struct NeedsDrop;
 
 impl Qualif for NeedsDrop {
@@ -366,8 +393,12 @@ fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
     }
 }
 
-// Not promotable at all - non-`const fn` calls, asm!,
-// pointer comparisons, ptr-to-int casts, etc.
+/// Not promotable at all - non-`const fn` calls, `asm!`,
+/// pointer comparisons, ptr-to-int casts, etc.
+/// Inside a const context all constness rules apply, so promotion simply has to follow the regular
+/// constant rules (modulo interior mutability or `Drop` rules which are handled `HasMutInterior`
+/// and `NeedsDrop` respectively). Basically this duplicates the checks that the const-checking
+/// visitor enforces by emitting errors when working in const context.
 struct IsNotPromotable;
 
 impl Qualif for IsNotPromotable {
@@ -398,9 +429,10 @@ fn in_projection(cx: &ConstCx<'_, 'tcx>, proj: &Projection<'tcx>) -> bool {
             ProjectionElem::Index(_) => {}
 
             ProjectionElem::Field(..) => {
-                if cx.mode == Mode::Fn {
-                    let base_ty = proj.base.ty(cx.mir, cx.tcx).ty;
+                if cx.mode == Mode::NonConstFn {
+                    let base_ty = proj.base.ty(cx.body, cx.tcx).ty;
                     if let Some(def) = base_ty.ty_adt_def() {
+                        // No promotion of union field accesses.
                         if def.is_union() {
                             return true;
                         }
@@ -414,8 +446,8 @@ fn in_projection(cx: &ConstCx<'_, 'tcx>, proj: &Projection<'tcx>) -> bool {
 
     fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
         match *rvalue {
-            Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if cx.mode == Mode::Fn => {
-                let operand_ty = operand.ty(cx.mir, cx.tcx);
+            Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if cx.mode == Mode::NonConstFn => {
+                let operand_ty = operand.ty(cx.body, cx.tcx);
                 let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
                 let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
                 match (cast_in, cast_out) {
@@ -428,8 +460,8 @@ fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool {
                 }
             }
 
-            Rvalue::BinaryOp(op, ref lhs, _) if cx.mode == Mode::Fn => {
-                if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(cx.mir, cx.tcx).sty {
+            Rvalue::BinaryOp(op, ref lhs, _) if cx.mode == Mode::NonConstFn => {
+                if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(cx.body, cx.tcx).sty {
                     assert!(op == BinOp::Eq || op == BinOp::Ne ||
                             op == BinOp::Le || op == BinOp::Lt ||
                             op == BinOp::Ge || op == BinOp::Gt ||
@@ -454,7 +486,7 @@ fn in_call(
         args: &[Operand<'tcx>],
         _return_ty: Ty<'tcx>,
     ) -> bool {
-        let fn_ty = callee.ty(cx.mir, cx.tcx);
+        let fn_ty = callee.ty(cx.body, cx.tcx);
         match fn_ty.sty {
             ty::FnDef(def_id, _) => {
                 match cx.tcx.fn_sig(def_id).abi() {
@@ -511,12 +543,9 @@ fn in_call(
 
 /// Refers to temporaries which cannot be promoted *implicitly*.
 /// Explicit promotion happens e.g. for constant arguments declared via `rustc_args_required_const`.
-/// Inside a const context all constness rules
-/// apply, so implicit promotion simply has to follow the regular constant rules (modulo interior
-/// mutability or `Drop` rules which are handled `HasMutInterior` and `NeedsDrop` respectively).
-/// Implicit promotion inside regular functions does not happen if `const fn` calls are involved,
-/// as the call may be perfectly alright at runtime, but fail at compile time e.g. due to addresses
-/// being compared inside the function.
+/// Implicit promotion has almost the same rules, except that disallows `const fn` except for
+/// those marked `#[rustc_promotable]`. This is to avoid changing a legitimate run-time operation
+/// into a failing compile-time operation e.g. due to addresses being compared inside the function.
 struct IsNotImplicitlyPromotable;
 
 impl Qualif for IsNotImplicitlyPromotable {
@@ -528,8 +557,8 @@ fn in_call(
         args: &[Operand<'tcx>],
         _return_ty: Ty<'tcx>,
     ) -> bool {
-        if cx.mode == Mode::Fn {
-            if let ty::FnDef(def_id, _) = callee.ty(cx.mir, cx.tcx).sty {
+        if cx.mode == Mode::NonConstFn {
+            if let ty::FnDef(def_id, _) = callee.ty(cx.body, cx.tcx).sty {
                 // Never promote runtime `const fn` calls of
                 // functions without `#[rustc_promotable]`.
                 if !cx.tcx.is_promotable_const_fn(def_id) {
@@ -589,6 +618,11 @@ fn qualifs_in_value(&self, source: ValueSource<'_, 'tcx>) -> PerQualif<bool> {
     }
 }
 
+/// Checks MIR for being admissible as a compile-time constant, using `ConstCx`
+/// for value qualifications, and accumulates writes of
+/// rvalue/call results to locals, in `local_qualif`.
+/// It also records candidates for promotion in `promotion_candidates`,
+/// both in functions and const/static items.
 struct Checker<'a, 'tcx> {
     cx: ConstCx<'a, 'tcx>,
 
@@ -620,12 +654,12 @@ fn deref(&self) -> &Self::Target {
 impl<'a, 'tcx> Checker<'a, 'tcx> {
     fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>,
            def_id: DefId,
-           mir: &'a Body<'tcx>,
+           body: &'a Body<'tcx>,
            mode: Mode)
            -> Self {
         assert!(def_id.is_local());
-        let mut rpo = traversal::reverse_postorder(mir);
-        let temps = promote_consts::collect_temps(mir, &mut rpo);
+        let mut rpo = traversal::reverse_postorder(body);
+        let temps = promote_consts::collect_temps(body, &mut rpo);
         rpo.reset();
 
         let param_env = tcx.param_env(def_id);
@@ -634,12 +668,12 @@ fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>,
             tcx,
             param_env,
             mode,
-            mir,
-            per_local: PerQualif::new(BitSet::new_empty(mir.local_decls.len())),
+            body,
+            per_local: PerQualif::new(BitSet::new_empty(body.local_decls.len())),
         };
 
-        for (local, decl) in mir.local_decls.iter_enumerated() {
-            if let LocalKind::Arg = mir.local_kind(local) {
+        for (local, decl) in body.local_decls.iter_enumerated() {
+            if let LocalKind::Arg = body.local_kind(local) {
                 let qualifs = cx.qualifs_in_any_value_of_ty(decl.ty);
                 for (per_local, qualif) in &mut cx.per_local.as_mut().zip(qualifs).0 {
                     if *qualif {
@@ -650,7 +684,7 @@ fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>,
             if !temps[local].is_promotable() {
                 cx.per_local[IsNotPromotable].insert(local);
             }
-            if let LocalKind::Var = mir.local_kind(local) {
+            if let LocalKind::Var = body.local_kind(local) {
                 // Sanity check to prevent implicit and explicit promotion of
                 // named locals
                 assert!(cx.per_local[IsNotPromotable].contains(local));
@@ -659,7 +693,7 @@ fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>,
 
         Checker {
             cx,
-            span: mir.span,
+            span: body.span,
             def_id,
             rpo,
             temp_promotion_state: temps,
@@ -672,7 +706,7 @@ fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     // slightly pointless (even with feature-gating).
     fn not_const(&mut self) {
         unleash_miri!(self);
-        if self.mode != Mode::Fn {
+        if self.mode.requires_const_checking() {
             let mut err = struct_span_err!(
                 self.tcx.sess,
                 self.span,
@@ -707,7 +741,7 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
                 qualifs[HasMutInterior] = false;
                 qualifs[IsNotPromotable] = true;
 
-                if self.mode != Mode::Fn {
+                if self.mode.requires_const_checking() {
                     if let BorrowKind::Mut { .. } = kind {
                         let mut err = struct_span_err!(self.tcx.sess,  self.span, E0017,
                                                        "references in {}s may only refer \
@@ -737,7 +771,7 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
 
                 // We might have a candidate for promotion.
                 let candidate = Candidate::Ref(location);
-                // We can only promote interior borrows of promotable temps.
+                // Start by traversing to the "base", with non-deref projections removed.
                 let mut place = place;
                 while let Place::Projection(ref proj) = *place {
                     if proj.elem == ProjectionElem::Deref {
@@ -746,8 +780,12 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
                     place = &proj.base;
                 }
                 debug!("qualify_consts: promotion candidate: place={:?}", place);
+                // We can only promote interior borrows of promotable temps (non-temps
+                // don't get promoted anyway).
+                // (If we bailed out of the loop due to a `Deref` above, we will definitely
+                // not enter the conditional here.)
                 if let Place::Base(PlaceBase::Local(local)) = *place {
-                    if self.mir.local_kind(local) == LocalKind::Temp {
+                    if self.body.local_kind(local) == LocalKind::Temp {
                         debug!("qualify_consts: promotion candidate: local={:?}", local);
                         // The borrowed place doesn't have `HasMutInterior`
                         // (from `in_rvalue`), so we can safely ignore
@@ -756,6 +794,10 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
                         // `HasMutInterior`, from a type that does, e.g.:
                         // `let _: &'static _ = &(Cell::new(1), 2).1;`
                         let mut local_qualifs = self.qualifs_in_local(local);
+                        // Any qualifications, except HasMutInterior (see above), disqualify
+                        // from promotion.
+                        // This is, in particular, the "implicit promotion" version of
+                        // the check making sure that we don't run drop glue during const-eval.
                         local_qualifs[HasMutInterior] = false;
                         if !local_qualifs.0.iter().any(|&qualif| qualif) {
                             debug!("qualify_consts: promotion candidate: {:?}", candidate);
@@ -799,11 +841,11 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
             }
         };
 
-        let kind = self.mir.local_kind(index);
+        let kind = self.body.local_kind(index);
         debug!("store to {:?} {:?}", kind, index);
 
         // Only handle promotable temps in non-const functions.
-        if self.mode == Mode::Fn {
+        if self.mode == Mode::NonConstFn {
             if kind != LocalKind::Temp ||
                !self.temp_promotion_state[index].is_promotable() {
                 return;
@@ -837,16 +879,16 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
     fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
         debug!("const-checking {} {:?}", self.mode, self.def_id);
 
-        let mir = self.mir;
+        let body = self.body;
 
-        let mut seen_blocks = BitSet::new_empty(mir.basic_blocks().len());
+        let mut seen_blocks = BitSet::new_empty(body.basic_blocks().len());
         let mut bb = START_BLOCK;
         loop {
             seen_blocks.insert(bb.index());
 
-            self.visit_basic_block_data(bb, &mir[bb]);
+            self.visit_basic_block_data(bb, &body[bb]);
 
-            let target = match mir[bb].terminator().kind {
+            let target = match body[bb].terminator().kind {
                 TerminatorKind::Goto { target } |
                 TerminatorKind::Drop { target, .. } |
                 TerminatorKind::Assert { target, .. } |
@@ -894,7 +936,7 @@ fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
         for candidate in &self.promotion_candidates {
             match *candidate {
                 Candidate::Ref(Location { block: bb, statement_index: stmt_idx }) => {
-                    match self.mir[bb].statements[stmt_idx].kind {
+                    match self.body[bb].statements[stmt_idx].kind {
                         StatementKind::Assign(
                             _,
                             box Rvalue::Ref(_, _, Place::Base(PlaceBase::Local(index)))
@@ -913,138 +955,143 @@ fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
         // Account for errors in consts by using the
         // conservative type qualification instead.
         if qualifs[IsNotPromotable] {
-            qualifs = self.qualifs_in_any_value_of_ty(mir.return_ty());
+            qualifs = self.qualifs_in_any_value_of_ty(body.return_ty());
         }
 
         (qualifs.encode_to_bits(), self.tcx.arena.alloc(promoted_temps))
     }
 }
 
-/// Checks MIR for const-correctness, using `ConstCx`
-/// for value qualifications, and accumulates writes of
-/// rvalue/call results to locals, in `local_qualif`.
-/// For functions (constant or not), it also records
-/// candidates for promotion in `promotion_candidates`.
 impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
-    fn visit_place(&mut self,
-                    place: &Place<'tcx>,
-                    context: PlaceContext,
-                    location: Location) {
-        debug!("visit_place: place={:?} context={:?} location={:?}", place, context, location);
-        place.iterate(|place_base, place_projections| {
-            match place_base {
-                PlaceBase::Local(_) => {}
-                PlaceBase::Static(box Static{ kind: StaticKind::Promoted(_), .. }) => {
-                    unreachable!()
-                }
-                PlaceBase::Static(box Static{ kind: StaticKind::Static(def_id), .. }) => {
-                    if self.tcx
-                           .get_attrs(*def_id)
-                           .iter()
-                           .any(|attr| attr.check_name(sym::thread_local)) {
-                        if self.mode != Mode::Fn {
-                            span_err!(self.tcx.sess, self.span, E0625,
-                                      "thread-local statics cannot be \
-                                       accessed at compile-time");
-                        }
-                        return;
+    fn visit_place_base(
+        &mut self,
+        place_base: &PlaceBase<'tcx>,
+        context: PlaceContext,
+        location: Location,
+    ) {
+        self.super_place_base(place_base, context, location);
+        match place_base {
+            PlaceBase::Local(_) => {}
+            PlaceBase::Static(box Static{ kind: StaticKind::Promoted(_), .. }) => {
+                unreachable!()
+            }
+            PlaceBase::Static(box Static{ kind: StaticKind::Static(def_id), .. }) => {
+                if self.tcx
+                        .get_attrs(*def_id)
+                        .iter()
+                        .any(|attr| attr.check_name(sym::thread_local)) {
+                    if self.mode.requires_const_checking() {
+                        span_err!(self.tcx.sess, self.span, E0625,
+                                    "thread-local statics cannot be \
+                                    accessed at compile-time");
                     }
+                    return;
+                }
 
-                    // Only allow statics (not consts) to refer to other statics.
-                    if self.mode == Mode::Static || self.mode == Mode::StaticMut {
-                        if self.mode == Mode::Static && context.is_mutating_use() {
-                            // this is not strictly necessary as miri will also bail out
-                            // For interior mutability we can't really catch this statically as that
-                            // goes through raw pointers and intermediate temporaries, so miri has
-                            // to catch this anyway
-                            self.tcx.sess.span_err(
-                                self.span,
-                                "cannot mutate statics in the initializer of another static",
-                            );
-                        }
-                        return;
+                // Only allow statics (not consts) to refer to other statics.
+                if self.mode == Mode::Static || self.mode == Mode::StaticMut {
+                    if self.mode == Mode::Static && context.is_mutating_use() {
+                        // this is not strictly necessary as miri will also bail out
+                        // For interior mutability we can't really catch this statically as that
+                        // goes through raw pointers and intermediate temporaries, so miri has
+                        // to catch this anyway
+                        self.tcx.sess.span_err(
+                            self.span,
+                            "cannot mutate statics in the initializer of another static",
+                        );
                     }
-                    unleash_miri!(self);
+                    return;
+                }
+                unleash_miri!(self);
 
-                    if self.mode != Mode::Fn {
-                        let mut err = struct_span_err!(self.tcx.sess, self.span, E0013,
-                                                       "{}s cannot refer to statics, use \
-                                                        a constant instead", self.mode);
-                        if self.tcx.sess.teach(&err.get_code().unwrap()) {
-                            err.note(
-                                "Static and const variables can refer to other const variables. \
-                                 But a const variable cannot refer to a static variable."
-                            );
-                            err.help(
-                                "To fix this, the value can be extracted as a const and then used."
-                            );
-                        }
-                        err.emit()
+                if self.mode.requires_const_checking() {
+                    let mut err = struct_span_err!(self.tcx.sess, self.span, E0013,
+                                                    "{}s cannot refer to statics, use \
+                                                    a constant instead", self.mode);
+                    if self.tcx.sess.teach(&err.get_code().unwrap()) {
+                        err.note(
+                            "Static and const variables can refer to other const variables. \
+                                But a const variable cannot refer to a static variable."
+                        );
+                        err.help(
+                            "To fix this, the value can be extracted as a const and then used."
+                        );
                     }
+                    err.emit()
                 }
             }
+        }
+    }
 
-            for proj in place_projections {
-                match proj.elem {
-                    ProjectionElem::Deref => {
-                        if context.is_mutating_use() {
-                            // `not_const` errors out in const contexts
-                            self.not_const()
-                        }
-                        let base_ty = proj.base.ty(self.mir, self.tcx).ty;
-                        match self.mode {
-                            Mode::Fn => {},
-                            _ => {
-                                if let ty::RawPtr(_) = base_ty.sty {
-                                    if !self.tcx.features().const_raw_ptr_deref {
-                                        emit_feature_err(
-                                            &self.tcx.sess.parse_sess, sym::const_raw_ptr_deref,
-                                            self.span, GateIssue::Language,
-                                            &format!(
-                                                "dereferencing raw pointers in {}s is unstable",
-                                                self.mode,
-                                            ),
-                                        );
-                                    }
-                                }
+    fn visit_projection(
+        &mut self,
+        proj: &Projection<'tcx>,
+        context: PlaceContext,
+        location: Location,
+    ) {
+        debug!(
+            "visit_place_projection: proj={:?} context={:?} location={:?}",
+            proj, context, location,
+        );
+        self.super_projection(proj, context, location);
+        match proj.elem {
+            ProjectionElem::Deref => {
+                if context.is_mutating_use() {
+                    // `not_const` errors out in const contexts
+                    self.not_const()
+                }
+                let base_ty = proj.base.ty(self.body, self.tcx).ty;
+                match self.mode {
+                    Mode::NonConstFn => {},
+                    _ => {
+                        if let ty::RawPtr(_) = base_ty.sty {
+                            if !self.tcx.features().const_raw_ptr_deref {
+                                emit_feature_err(
+                                    &self.tcx.sess.parse_sess, sym::const_raw_ptr_deref,
+                                    self.span, GateIssue::Language,
+                                    &format!(
+                                        "dereferencing raw pointers in {}s is unstable",
+                                        self.mode,
+                                    ),
+                                );
                             }
                         }
                     }
+                }
+            }
 
-                    ProjectionElem::ConstantIndex {..} |
-                    ProjectionElem::Subslice {..} |
-                    ProjectionElem::Field(..) |
-                    ProjectionElem::Index(_) => {
-                        let base_ty = proj.base.ty(self.mir, self.tcx).ty;
-                        if let Some(def) = base_ty.ty_adt_def() {
-                            if def.is_union() {
-                                match self.mode {
-                                    Mode::ConstFn => {
-                                        if !self.tcx.features().const_fn_union {
-                                            emit_feature_err(
-                                                &self.tcx.sess.parse_sess, sym::const_fn_union,
-                                                self.span, GateIssue::Language,
-                                                "unions in const fn are unstable",
-                                            );
-                                        }
-                                    },
-
-                                    | Mode::Fn
-                                    | Mode::Static
-                                    | Mode::StaticMut
-                                    | Mode::Const
-                                    => {},
+            ProjectionElem::ConstantIndex {..} |
+            ProjectionElem::Subslice {..} |
+            ProjectionElem::Field(..) |
+            ProjectionElem::Index(_) => {
+                let base_ty = proj.base.ty(self.body, self.tcx).ty;
+                if let Some(def) = base_ty.ty_adt_def() {
+                    if def.is_union() {
+                        match self.mode {
+                            Mode::ConstFn => {
+                                if !self.tcx.features().const_fn_union {
+                                    emit_feature_err(
+                                        &self.tcx.sess.parse_sess, sym::const_fn_union,
+                                        self.span, GateIssue::Language,
+                                        "unions in const fn are unstable",
+                                    );
                                 }
-                            }
-                        }
-                    }
+                            },
 
-                    ProjectionElem::Downcast(..) => {
-                        self.not_const()
+                            | Mode::NonConstFn
+                            | Mode::Static
+                            | Mode::StaticMut
+                            | Mode::Const
+                            => {},
+                        }
                     }
                 }
             }
-        });
+
+            ProjectionElem::Downcast(..) => {
+                self.not_const()
+            }
+        }
     }
 
     fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
@@ -1069,17 +1116,17 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
         // Check nested operands and places.
         if let Rvalue::Ref(_, kind, ref place) = *rvalue {
             // Special-case reborrows.
-            let mut is_reborrow = false;
+            let mut reborrow_place = None;
             if let Place::Projection(ref proj) = *place {
                 if let ProjectionElem::Deref = proj.elem {
-                    let base_ty = proj.base.ty(self.mir, self.tcx).ty;
+                    let base_ty = proj.base.ty(self.body, self.tcx).ty;
                     if let ty::Ref(..) = base_ty.sty {
-                        is_reborrow = true;
+                        reborrow_place = Some(&proj.base);
                     }
                 }
             }
 
-            if is_reborrow {
+            if let Some(place) = reborrow_place {
                 let ctx = match kind {
                     BorrowKind::Shared => PlaceContext::NonMutatingUse(
                         NonMutatingUseContext::SharedBorrow,
@@ -1094,7 +1141,7 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
                         MutatingUseContext::Borrow,
                     ),
                 };
-                self.super_place(place, ctx, location);
+                self.visit_place(place, ctx, location);
             } else {
                 self.super_rvalue(rvalue, location);
             }
@@ -1116,12 +1163,12 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
             Rvalue::Aggregate(..) => {}
 
             Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => {
-                let operand_ty = operand.ty(self.mir, self.tcx);
+                let operand_ty = operand.ty(self.body, self.tcx);
                 let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
                 let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
                 match (cast_in, cast_out) {
                     (CastTy::Ptr(_), CastTy::Int(_)) |
-                    (CastTy::FnPtr, CastTy::Int(_)) if self.mode != Mode::Fn => {
+                    (CastTy::FnPtr, CastTy::Int(_)) if self.mode != Mode::NonConstFn => {
                         unleash_miri!(self);
                         if !self.tcx.features().const_raw_ptr_to_usize_cast {
                             // in const fn and constants require the feature gate
@@ -1141,14 +1188,16 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
             }
 
             Rvalue::BinaryOp(op, ref lhs, _) => {
-                if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.mir, self.tcx).sty {
+                if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).sty {
                     assert!(op == BinOp::Eq || op == BinOp::Ne ||
                             op == BinOp::Le || op == BinOp::Lt ||
                             op == BinOp::Ge || op == BinOp::Gt ||
                             op == BinOp::Offset);
 
                     unleash_miri!(self);
-                    if self.mode != Mode::Fn && !self.tcx.features().const_compare_raw_pointers {
+                    if self.mode.requires_const_checking() &&
+                        !self.tcx.features().const_compare_raw_pointers
+                    {
                         // require the feature gate inside constants and const fn
                         // FIXME: make it unsafe to use these operations
                         emit_feature_err(
@@ -1164,7 +1213,7 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
 
             Rvalue::NullaryOp(NullOp::Box, _) => {
                 unleash_miri!(self);
-                if self.mode != Mode::Fn {
+                if self.mode.requires_const_checking() {
                     let mut err = struct_span_err!(self.tcx.sess, self.span, E0010,
                                                    "allocations are not allowed in {}s", self.mode);
                     err.span_label(self.span, format!("allocation not allowed in {}s", self.mode));
@@ -1191,11 +1240,11 @@ fn visit_terminator_kind(&mut self,
                 self.assign(dest, ValueSource::Call {
                     callee: func,
                     args,
-                    return_ty: dest.ty(self.mir, self.tcx).ty,
+                    return_ty: dest.ty(self.body, self.tcx).ty,
                 }, location);
             }
 
-            let fn_ty = func.ty(self.mir, self.tcx);
+            let fn_ty = func.ty(self.body, self.tcx);
             let mut callee_def_id = None;
             let mut is_shuffle = false;
             match fn_ty.sty {
@@ -1209,8 +1258,7 @@ fn visit_terminator_kind(&mut self,
                                 // special intrinsic that can be called diretly without an intrinsic
                                 // feature gate needs a language feature gate
                                 "transmute" => {
-                                    // never promote transmute calls
-                                    if self.mode != Mode::Fn {
+                                    if self.mode.requires_const_checking() {
                                         // const eval transmute calls only with the feature gate
                                         if !self.tcx.features().const_transmute {
                                             emit_feature_err(
@@ -1233,7 +1281,7 @@ fn visit_terminator_kind(&mut self,
                         }
                         _ => {
                             // In normal functions no calls are feature-gated.
-                            if self.mode != Mode::Fn {
+                            if self.mode.requires_const_checking() {
                                 let unleash_miri = self
                                     .tcx
                                     .sess
@@ -1292,7 +1340,7 @@ fn visit_terminator_kind(&mut self,
                     }
                 }
                 ty::FnPtr(_) => {
-                    if self.mode != Mode::Fn {
+                    if self.mode.requires_const_checking() {
                         let mut err = self.tcx.sess.struct_span_err(
                             self.span,
                             &format!("function pointers are not allowed in const fn"));
@@ -1304,7 +1352,9 @@ fn visit_terminator_kind(&mut self,
                 }
             }
 
-            if self.mode == Mode::Fn {
+            // No need to do anything in constants and statics, as everything is "constant" anyway
+            // so promotion would be useless.
+            if self.mode != Mode::Static && self.mode != Mode::Const {
                 let constant_args = callee_def_id.and_then(|id| {
                     args_required_const(self.tcx, id)
                 }).unwrap_or_default();
@@ -1349,13 +1399,13 @@ fn visit_terminator_kind(&mut self,
             self.super_terminator_kind(kind, location);
 
             // Deny *any* live drops anywhere other than functions.
-            if self.mode != Mode::Fn {
+            if self.mode.requires_const_checking() {
                 unleash_miri!(self);
                 // HACK(eddyb): emulate a bit of dataflow analysis,
                 // conservatively, that drop elaboration will do.
                 let needs_drop = if let Place::Base(PlaceBase::Local(local)) = *place {
                     if NeedsDrop::in_local(self, local) {
-                        Some(self.mir.local_decls[local].source_info.span)
+                        Some(self.body.local_decls[local].source_info.span)
                     } else {
                         None
                     }
@@ -1365,7 +1415,7 @@ fn visit_terminator_kind(&mut self,
 
                 if let Some(span) = needs_drop {
                     // Double-check the type being dropped, to minimize false positives.
-                    let ty = place.ty(self.mir, self.tcx).ty;
+                    let ty = place.ty(self.body, self.tcx).ty;
                     if ty.needs_drop(self.tcx, self.param_env) {
                         struct_span_err!(self.tcx.sess, span, E0493,
                                          "destructors cannot be evaluated at compile-time")
@@ -1429,14 +1479,14 @@ fn mir_const_qualif<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     // cannot yet be stolen), because `mir_validated()`, which steals
     // from `mir_const(), forces this query to execute before
     // performing the steal.
-    let mir = &tcx.mir_const(def_id).borrow();
+    let body = &tcx.mir_const(def_id).borrow();
 
-    if mir.return_ty().references_error() {
-        tcx.sess.delay_span_bug(mir.span, "mir_const_qualif: MIR had errors");
+    if body.return_ty().references_error() {
+        tcx.sess.delay_span_bug(body.span, "mir_const_qualif: MIR had errors");
         return (1 << IsNotPromotable::IDX, tcx.arena.alloc(BitSet::new_empty(0)));
     }
 
-    Checker::new(tcx, def_id, mir, Mode::Const).check_const()
+    Checker::new(tcx, def_id, body, Mode::Const).check_const()
 }
 
 pub struct QualifyAndPromoteConstants;
@@ -1445,10 +1495,10 @@ impl MirPass for QualifyAndPromoteConstants {
     fn run_pass<'a, 'tcx>(&self,
                           tcx: TyCtxt<'a, 'tcx, 'tcx>,
                           src: MirSource<'tcx>,
-                          mir: &mut Body<'tcx>) {
+                          body: &mut Body<'tcx>) {
         // There's not really any point in promoting errorful MIR.
-        if mir.return_ty().references_error() {
-            tcx.sess.delay_span_bug(mir.span, "QualifyAndPromoteConstants: MIR had errors");
+        if body.return_ty().references_error() {
+            tcx.sess.delay_span_bug(body.span, "QualifyAndPromoteConstants: MIR had errors");
             return;
         }
 
@@ -1460,12 +1510,12 @@ fn run_pass<'a, 'tcx>(&self,
         let id = tcx.hir().as_local_hir_id(def_id).unwrap();
         let mut const_promoted_temps = None;
         let mode = match tcx.hir().body_owner_kind_by_hir_id(id) {
-            hir::BodyOwnerKind::Closure => Mode::Fn,
+            hir::BodyOwnerKind::Closure => Mode::NonConstFn,
             hir::BodyOwnerKind::Fn => {
                 if tcx.is_const_fn(def_id) {
                     Mode::ConstFn
                 } else {
-                    Mode::Fn
+                    Mode::NonConstFn
                 }
             }
             hir::BodyOwnerKind::Const => {
@@ -1477,18 +1527,18 @@ fn run_pass<'a, 'tcx>(&self,
         };
 
         debug!("run_pass: mode={:?}", mode);
-        if mode == Mode::Fn || mode == Mode::ConstFn {
+        if mode == Mode::NonConstFn || mode == Mode::ConstFn {
             // This is ugly because Checker holds onto mir,
             // which can't be mutated until its scope ends.
             let (temps, candidates) = {
-                let mut checker = Checker::new(tcx, def_id, mir, mode);
+                let mut checker = Checker::new(tcx, def_id, body, mode);
                 if mode == Mode::ConstFn {
                     if tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
                         checker.check_const();
                     } else if tcx.is_min_const_fn(def_id) {
                         // enforce `min_const_fn` for stable const fns
                         use super::qualify_min_const_fn::is_min_const_fn;
-                        if let Err((span, err)) = is_min_const_fn(tcx, def_id, mir) {
+                        if let Err((span, err)) = is_min_const_fn(tcx, def_id, body) {
                             let mut diag = struct_span_err!(
                                 tcx.sess,
                                 span,
@@ -1521,12 +1571,12 @@ fn run_pass<'a, 'tcx>(&self,
             };
 
             // Do the actual promotion, now that we know what's viable.
-            promote_consts::promote_candidates(mir, tcx, temps, candidates);
+            promote_consts::promote_candidates(body, tcx, temps, candidates);
         } else {
-            if !mir.control_flow_destroyed.is_empty() {
-                let mut locals = mir.vars_iter();
+            if !body.control_flow_destroyed.is_empty() {
+                let mut locals = body.vars_iter();
                 if let Some(local) = locals.next() {
-                    let span = mir.local_decls[local].source_info.span;
+                    let span = body.local_decls[local].source_info.span;
                     let mut error = tcx.sess.struct_span_err(
                         span,
                         &format!(
@@ -1535,7 +1585,7 @@ fn run_pass<'a, 'tcx>(&self,
                             mode,
                         ),
                     );
-                    for (span, kind) in mir.control_flow_destroyed.iter() {
+                    for (span, kind) in body.control_flow_destroyed.iter() {
                         error.span_note(
                             *span,
                             &format!("use of {} here does not actually short circuit due to \
@@ -1545,7 +1595,7 @@ fn run_pass<'a, 'tcx>(&self,
                         );
                     }
                     for local in locals {
-                        let span = mir.local_decls[local].source_info.span;
+                        let span = body.local_decls[local].source_info.span;
                         error.span_note(
                             span,
                             "more locals defined here",
@@ -1558,14 +1608,14 @@ fn run_pass<'a, 'tcx>(&self,
                 // Already computed by `mir_const_qualif`.
                 const_promoted_temps.unwrap()
             } else {
-                Checker::new(tcx, def_id, mir, mode).check_const().1
+                Checker::new(tcx, def_id, body, mode).check_const().1
             };
 
             // In `const` and `static` everything without `StorageDead`
             // is `'static`, we don't have to create promoted MIR fragments,
             // just remove `Drop` and `StorageDead` on "promoted" locals.
             debug!("run_pass: promoted_temps={:?}", promoted_temps);
-            for block in mir.basic_blocks_mut() {
+            for block in body.basic_blocks_mut() {
                 block.statements.retain(|statement| {
                     match statement.kind {
                         StatementKind::StorageDead(index) => {
@@ -1600,10 +1650,10 @@ fn run_pass<'a, 'tcx>(&self,
                     return;
                 }
             }
-            let ty = mir.return_ty();
+            let ty = body.return_ty();
             tcx.infer_ctxt().enter(|infcx| {
                 let param_env = ty::ParamEnv::empty();
-                let cause = traits::ObligationCause::new(mir.span, id, traits::SharedStatic);
+                let cause = traits::ObligationCause::new(body.span, id, traits::SharedStatic);
                 let mut fulfillment_cx = traits::FulfillmentContext::new();
                 fulfillment_cx.register_bound(&infcx,
                                               param_env,