about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2019-09-29 02:33:15 +0000
committerbors <bors@rust-lang.org>2019-09-29 02:33:15 +0000
commit0bbab7d99dde8620604fb265706dc8bff20345a7 (patch)
tree3a0815f104740464713c6ba68615de8502d89819
parentb61e69433951e31f7bd746e22f516a48ad41623b (diff)
parent0bf1a80b322f285efb9ea45b62e7dc764ebe1954 (diff)
downloadrust-0bbab7d99dde8620604fb265706dc8bff20345a7.tar.gz
rust-0bbab7d99dde8620604fb265706dc8bff20345a7.zip
Auto merge of #64470 - ecstatic-morse:split-promotion-and-validation, r=eddyb,oli-obk
Implement dataflow-based const validation

This PR adds a separate, dataflow-enabled pass that checks the bodies of `const`s, `static`s and `const fn`s for [const safety](https://github.com/rust-rfcs/const-eval/blob/master/const.md). This is based on my work in #63860, which tried to integrate into the existing pass in [`qualify_consts.rs`](https://github.com/rust-lang/rust/blob/master/src/librustc_mir/transform/qualify_consts.rs). However, the resulting pass was even more unwieldy than the original. Unlike its predecessor, this PR is designed to be combined with #63812 to replace the existing pass completely.

The new checker lives in [`librustc_mir/transform/check_consts`](https://github.com/ecstatic-morse/rust/tree/split-promotion-and-validation/src/librustc_mir/transform/check_consts).

[`qualifs.rs`](https://github.com/ecstatic-morse/rust/blob/split-promotion-and-validation/src/librustc_mir/transform/check_consts/qualifs.rs) contains small modifications to the existing `Qualif` trait and its implementors, but is mostly unchanged except for the removal of `IsNotPromotable` and `IsNotImplicitlyPromotable`, which are only necessary for promotion.

[`resolver.rs`](https://github.com/ecstatic-morse/rust/blob/split-promotion-and-validation/src/librustc_mir/transform/check_consts/resolver.rs) contains the dataflow analysis used to propagate qualifs between locals.

Finally, [`validation.rs`](https://github.com/ecstatic-morse/rust/blob/split-promotion-and-validation/src/librustc_mir/transform/check_consts/validation.rs) contains a refactored version of the existing [`Visitor`](https://github.com/rust-lang/rust/blob/ca3766e2e58f462a20922e42c821a37eaf0e13db/src/librustc_mir/transform/qualify_consts.rs#L1024) in `qualfy_consts.rs`. All errors have been associated with a `struct` to make [comparison with the existing pass](https://github.com/ecstatic-morse/rust/blob/1c19f2d540ca0a964900449d79a5d5181b43146d/src/librustc_mir/transform/qualify_consts.rs#L1006) simple.

The existing validation logic in [`qualify_consts`](https://github.com/rust-lang/rust/blob/master/src/librustc_mir/transform/qualify_consts.rs) has been modified to allow it to run in parallel with the new validator. If [`use_new_validator`](https://github.com/rust-lang/rust/pull/64470/files#diff-c2552a106550d05b69d5e07612f0f812R950) is not set, the old validation will be responsible for actually generating the errors, but those errors can be compared with the ones from the new validator.
-rw-r--r--src/librustc/session/config.rs2
-rw-r--r--src/librustc_mir/dataflow/impls/indirect_mutation.rs148
-rw-r--r--src/librustc_mir/dataflow/impls/mod.rs8
-rw-r--r--src/librustc_mir/dataflow/mod.rs1
-rw-r--r--src/librustc_mir/transform/check_consts/mod.rs52
-rw-r--r--src/librustc_mir/transform/check_consts/ops.rs339
-rw-r--r--src/librustc_mir/transform/check_consts/qualifs.rs284
-rw-r--r--src/librustc_mir/transform/check_consts/resolver.rs349
-rw-r--r--src/librustc_mir/transform/check_consts/validation.rs597
-rw-r--r--src/librustc_mir/transform/mod.rs1
-rw-r--r--src/librustc_mir/transform/qualify_consts.rs149
-rw-r--r--src/test/ui/consts/const-eval/const_fn_ptr.stderr144
-rw-r--r--src/test/ui/consts/const-eval/const_fn_ptr_fail.rs1
-rw-r--r--src/test/ui/consts/const-eval/const_fn_ptr_fail.stderr6
-rw-r--r--src/test/ui/consts/const-eval/const_fn_ptr_fail2.rs8
-rw-r--r--src/test/ui/consts/const-eval/const_fn_ptr_fail2.stderr50
-rw-r--r--src/test/ui/consts/const-if.rs5
-rw-r--r--src/test/ui/consts/const-if.stderr15
-rw-r--r--src/test/ui/consts/const-multi-ref.rs11
-rw-r--r--src/test/ui/consts/const-multi-ref.stderr15
-rw-r--r--src/test/ui/consts/miri_unleashed/assoc_const.stderr4
-rw-r--r--src/test/ui/consts/miri_unleashed/enum_discriminants.stderr48
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_const.rs1
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_const.stderr10
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references.rs9
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references.stderr32
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references_ice.rs4
-rw-r--r--src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr6
28 files changed, 2009 insertions, 290 deletions
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index cbb22f1e448..7c97fd11af2 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -1359,6 +1359,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
         "describes how to render the `rendered` field of json diagnostics"),
     unleash_the_miri_inside_of_you: bool = (false, parse_bool, [TRACKED],
         "take the breaks off const evaluation. NOTE: this is unsound"),
+    suppress_const_validation_back_compat_ice: bool = (false, parse_bool, [TRACKED],
+        "silence ICE triggered when the new const validator disagrees with the old"),
     osx_rpath_install_name: bool = (false, parse_bool, [TRACKED],
         "pass `-install_name @rpath/...` to the macOS linker"),
     sanitizer: Option<Sanitizer> = (None, parse_sanitizer, [TRACKED],
diff --git a/src/librustc_mir/dataflow/impls/indirect_mutation.rs b/src/librustc_mir/dataflow/impls/indirect_mutation.rs
new file mode 100644
index 00000000000..7d20248ebd1
--- /dev/null
+++ b/src/librustc_mir/dataflow/impls/indirect_mutation.rs
@@ -0,0 +1,148 @@
+use rustc::mir::visit::Visitor;
+use rustc::mir::{self, Local, Location};
+use rustc::ty::{self, TyCtxt};
+use rustc_data_structures::bit_set::BitSet;
+use syntax_pos::DUMMY_SP;
+
+use crate::dataflow::{self, GenKillSet};
+
+/// Whether a borrow to a `Local` has been created that could allow that `Local` to be mutated
+/// indirectly. This could either be a mutable reference (`&mut`) or a shared borrow if the type of
+/// that `Local` allows interior mutability. Operations that can mutate local's indirectly include:
+/// assignments through a pointer (`*p = 42`), function calls, drop terminators and inline assembly.
+///
+/// If this returns false for a `Local` at a given statement (or terminator), that `Local` could
+/// not possibly have been mutated indirectly prior to that statement.
+#[derive(Copy, Clone)]
+pub struct IndirectlyMutableLocals<'mir, 'tcx> {
+    body: &'mir mir::Body<'tcx>,
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+}
+
+impl<'mir, 'tcx> IndirectlyMutableLocals<'mir, 'tcx> {
+    pub fn new(
+        tcx: TyCtxt<'tcx>,
+        body: &'mir mir::Body<'tcx>,
+        param_env: ty::ParamEnv<'tcx>,
+    ) -> Self {
+        IndirectlyMutableLocals { body, tcx, param_env }
+    }
+
+    fn transfer_function<'a>(
+        &self,
+        trans: &'a mut GenKillSet<Local>,
+    ) -> TransferFunction<'a, 'mir, 'tcx> {
+        TransferFunction {
+            body: self.body,
+            tcx: self.tcx,
+            param_env: self.param_env,
+            trans
+        }
+    }
+}
+
+impl<'mir, 'tcx> dataflow::BitDenotation<'tcx> for IndirectlyMutableLocals<'mir, 'tcx> {
+    type Idx = Local;
+
+    fn name() -> &'static str { "mut_borrowed_locals" }
+
+    fn bits_per_block(&self) -> usize {
+        self.body.local_decls.len()
+    }
+
+    fn start_block_effect(&self, _entry_set: &mut BitSet<Local>) {
+        // Nothing is borrowed on function entry
+    }
+
+    fn statement_effect(
+        &self,
+        trans: &mut GenKillSet<Local>,
+        loc: Location,
+    ) {
+        let stmt = &self.body[loc.block].statements[loc.statement_index];
+        self.transfer_function(trans).visit_statement(stmt, loc);
+    }
+
+    fn terminator_effect(
+        &self,
+        trans: &mut GenKillSet<Local>,
+        loc: Location,
+    ) {
+        let terminator = self.body[loc.block].terminator();
+        self.transfer_function(trans).visit_terminator(terminator, loc);
+    }
+
+    fn propagate_call_return(
+        &self,
+        _in_out: &mut BitSet<Local>,
+        _call_bb: mir::BasicBlock,
+        _dest_bb: mir::BasicBlock,
+        _dest_place: &mir::Place<'tcx>,
+    ) {
+        // Nothing to do when a call returns successfully
+    }
+}
+
+impl<'mir, 'tcx> dataflow::BottomValue for IndirectlyMutableLocals<'mir, 'tcx> {
+    // bottom = unborrowed
+    const BOTTOM_VALUE: bool = false;
+}
+
+/// A `Visitor` that defines the transfer function for `IndirectlyMutableLocals`.
+struct TransferFunction<'a, 'mir, 'tcx> {
+    trans: &'a mut GenKillSet<Local>,
+    body: &'mir mir::Body<'tcx>,
+    tcx: TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+}
+
+impl<'tcx> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx> {
+    fn visit_rvalue(
+        &mut self,
+        rvalue: &mir::Rvalue<'tcx>,
+        location: Location,
+    ) {
+        if let mir::Rvalue::Ref(_, kind, ref borrowed_place) = *rvalue {
+            let is_mut = match kind {
+                mir::BorrowKind::Mut { .. } => true,
+
+                | mir::BorrowKind::Shared
+                | mir::BorrowKind::Shallow
+                | mir::BorrowKind::Unique
+                => {
+                    !borrowed_place
+                        .ty(self.body, self.tcx)
+                        .ty
+                        .is_freeze(self.tcx, self.param_env, DUMMY_SP)
+                }
+            };
+
+            if is_mut {
+                match borrowed_place.base {
+                    mir::PlaceBase::Local(borrowed_local) if !borrowed_place.is_indirect()
+                        => self.trans.gen(borrowed_local),
+
+                    _ => (),
+                }
+            }
+        }
+
+        self.super_rvalue(rvalue, location);
+    }
+
+
+    fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
+        // This method purposely does nothing except call `super_terminator`. It exists solely to
+        // document the subtleties around drop terminators.
+
+        self.super_terminator(terminator, location);
+
+        if let mir::TerminatorKind::Drop { location: _, .. }
+             | mir::TerminatorKind::DropAndReplace { location: _, .. } = &terminator.kind
+        {
+            // Although drop terminators mutably borrow the location being dropped, that borrow
+            // cannot live beyond the drop terminator because the dropped location is invalidated.
+        }
+    }
+}
diff --git a/src/librustc_mir/dataflow/impls/mod.rs b/src/librustc_mir/dataflow/impls/mod.rs
index 69bbe087921..d669c786c09 100644
--- a/src/librustc_mir/dataflow/impls/mod.rs
+++ b/src/librustc_mir/dataflow/impls/mod.rs
@@ -18,13 +18,13 @@ use super::drop_flag_effects_for_function_entry;
 use super::drop_flag_effects_for_location;
 use super::on_lookup_result_bits;
 
-mod storage_liveness;
-
-pub use self::storage_liveness::*;
-
 mod borrowed_locals;
+mod indirect_mutation;
+mod storage_liveness;
 
 pub use self::borrowed_locals::*;
+pub use self::indirect_mutation::IndirectlyMutableLocals;
+pub use self::storage_liveness::*;
 
 pub(super) mod borrows;
 
diff --git a/src/librustc_mir/dataflow/mod.rs b/src/librustc_mir/dataflow/mod.rs
index 5ab4e25b683..47eb47cf664 100644
--- a/src/librustc_mir/dataflow/mod.rs
+++ b/src/librustc_mir/dataflow/mod.rs
@@ -23,6 +23,7 @@ pub use self::impls::DefinitelyInitializedPlaces;
 pub use self::impls::EverInitializedPlaces;
 pub use self::impls::borrows::Borrows;
 pub use self::impls::HaveBeenBorrowedLocals;
+pub use self::impls::IndirectlyMutableLocals;
 pub use self::at_location::{FlowAtLocation, FlowsAtLocation};
 pub(crate) use self::drop_flag_effects::*;
 
diff --git a/src/librustc_mir/transform/check_consts/mod.rs b/src/librustc_mir/transform/check_consts/mod.rs
new file mode 100644
index 00000000000..3a959a86edd
--- /dev/null
+++ b/src/librustc_mir/transform/check_consts/mod.rs
@@ -0,0 +1,52 @@
+//! Check the bodies of `const`s, `static`s and `const fn`s for illegal operations.
+//!
+//! This module will eventually replace the parts of `qualify_consts.rs` that check whether a local
+//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when
+//! it finds operations that are invalid in a certain context.
+
+use rustc::hir::def_id::DefId;
+use rustc::mir;
+use rustc::ty::{self, TyCtxt};
+
+pub use self::qualifs::Qualif;
+
+pub mod ops;
+mod qualifs;
+mod resolver;
+pub mod validation;
+
+/// Information about the item currently being validated, as well as a reference to the global
+/// context.
+pub struct Item<'mir, 'tcx> {
+    body: &'mir mir::Body<'tcx>,
+    tcx: TyCtxt<'tcx>,
+    def_id: DefId,
+    param_env: ty::ParamEnv<'tcx>,
+    mode: validation::Mode,
+}
+
+impl Item<'mir, 'tcx> {
+    pub fn new(
+        tcx: TyCtxt<'tcx>,
+        def_id: DefId,
+        body: &'mir mir::Body<'tcx>,
+    ) -> Self {
+        let param_env = tcx.param_env(def_id);
+        let mode = validation::Mode::for_item(tcx, def_id)
+            .expect("const validation must only be run inside a const context");
+
+        Item {
+            body,
+            tcx,
+            def_id,
+            param_env,
+            mode,
+        }
+    }
+}
+
+
+fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
+    Some(def_id) == tcx.lang_items().panic_fn() ||
+    Some(def_id) == tcx.lang_items().begin_panic_fn()
+}
diff --git a/src/librustc_mir/transform/check_consts/ops.rs b/src/librustc_mir/transform/check_consts/ops.rs
new file mode 100644
index 00000000000..f457b739949
--- /dev/null
+++ b/src/librustc_mir/transform/check_consts/ops.rs
@@ -0,0 +1,339 @@
+//! Concrete error types for all operations which may be invalid in a certain const context.
+
+use rustc::hir::def_id::DefId;
+use rustc::mir::BorrowKind;
+use rustc::session::config::nightly_options;
+use rustc::ty::TyCtxt;
+use syntax::feature_gate::{emit_feature_err, GateIssue};
+use syntax::symbol::sym;
+use syntax_pos::{Span, Symbol};
+
+use super::Item;
+use super::validation::Mode;
+
+/// An operation that is not *always* allowed in a const context.
+pub trait NonConstOp: std::fmt::Debug {
+    /// Whether this operation can be evaluated by miri.
+    const IS_SUPPORTED_IN_MIRI: bool = true;
+
+    /// Returns a boolean indicating whether the feature gate that would allow this operation is
+    /// enabled, or `None` if such a feature gate does not exist.
+    fn feature_gate(_tcx: TyCtxt<'tcx>) -> Option<bool> {
+        None
+    }
+
+    /// Returns `true` if this operation is allowed in the given item.
+    ///
+    /// This check should assume that we are not in a non-const `fn`, where all operations are
+    /// legal.
+    fn is_allowed_in_item(&self, item: &Item<'_, '_>) -> bool {
+        Self::feature_gate(item.tcx).unwrap_or(false)
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        let mut err = struct_span_err!(
+            item.tcx.sess,
+            span,
+            E0019,
+            "{} contains unimplemented expression type",
+            item.mode
+        );
+        if item.tcx.sess.teach(&err.get_code().unwrap()) {
+            err.note("A function call isn't allowed in the const's initialization expression \
+                      because the expression's value must be known at compile-time.");
+            err.note("Remember: you can't use a function call inside a const's initialization \
+                      expression! However, you can use it anywhere else.");
+        }
+        err.emit();
+    }
+}
+
+/// A `Downcast` projection.
+#[derive(Debug)]
+pub struct Downcast;
+impl NonConstOp for Downcast {}
+
+/// A function call where the callee is a pointer.
+#[derive(Debug)]
+pub struct FnCallIndirect;
+impl NonConstOp for FnCallIndirect {
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        let mut err = item.tcx.sess.struct_span_err(
+            span,
+            &format!("function pointers are not allowed in const fn"));
+        err.emit();
+    }
+}
+
+/// A function call where the callee is not marked as `const`.
+#[derive(Debug)]
+pub struct FnCallNonConst(pub DefId);
+impl NonConstOp for FnCallNonConst {
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        let mut err = struct_span_err!(
+            item.tcx.sess,
+            span,
+            E0015,
+            "calls in {}s are limited to constant functions, \
+             tuple structs and tuple variants",
+            item.mode,
+        );
+        err.emit();
+    }
+}
+
+/// A function call where the callee is not a function definition or function pointer, e.g. a
+/// closure.
+///
+/// This can be subdivided in the future to produce a better error message.
+#[derive(Debug)]
+pub struct FnCallOther;
+impl NonConstOp for FnCallOther {
+    const IS_SUPPORTED_IN_MIRI: bool = false;
+}
+
+/// A call to a `#[unstable]` const fn or `#[rustc_const_unstable]` function.
+///
+/// Contains the name of the feature that would allow the use of this function.
+#[derive(Debug)]
+pub struct FnCallUnstable(pub DefId, pub Symbol);
+impl NonConstOp for FnCallUnstable {
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        let FnCallUnstable(def_id, feature) = *self;
+
+        let mut err = item.tcx.sess.struct_span_err(span,
+            &format!("`{}` is not yet stable as a const fn",
+                    item.tcx.def_path_str(def_id)));
+        if nightly_options::is_nightly_build() {
+            help!(&mut err,
+                  "add `#![feature({})]` to the \
+                   crate attributes to enable",
+                  feature);
+        }
+        err.emit();
+    }
+}
+
+#[derive(Debug)]
+pub struct HeapAllocation;
+impl NonConstOp for HeapAllocation {
+    const IS_SUPPORTED_IN_MIRI: bool = false;
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        let mut err = struct_span_err!(item.tcx.sess, span, E0010,
+                                       "allocations are not allowed in {}s", item.mode);
+        err.span_label(span, format!("allocation not allowed in {}s", item.mode));
+        if item.tcx.sess.teach(&err.get_code().unwrap()) {
+            err.note(
+                "The value of statics and constants must be known at compile time, \
+                 and they live for the entire lifetime of a program. Creating a boxed \
+                 value allocates memory on the heap at runtime, and therefore cannot \
+                 be done at compile time."
+            );
+        }
+        err.emit();
+    }
+}
+
+#[derive(Debug)]
+pub struct IfOrMatch;
+impl NonConstOp for IfOrMatch {}
+
+#[derive(Debug)]
+pub struct LiveDrop;
+impl NonConstOp for LiveDrop {
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        struct_span_err!(item.tcx.sess, span, E0493,
+                         "destructors cannot be evaluated at compile-time")
+            .span_label(span, format!("{}s cannot evaluate destructors",
+                                      item.mode))
+            .emit();
+    }
+}
+
+#[derive(Debug)]
+pub struct Loop;
+impl NonConstOp for Loop {}
+
+#[derive(Debug)]
+pub struct MutBorrow(pub BorrowKind);
+impl NonConstOp for MutBorrow {
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        let kind = self.0;
+        if let BorrowKind::Mut { .. } = kind {
+            let mut err = struct_span_err!(item.tcx.sess, span, E0017,
+                                           "references in {}s may only refer \
+                                            to immutable values", item.mode);
+            err.span_label(span, format!("{}s require immutable values",
+                                                item.mode));
+            if item.tcx.sess.teach(&err.get_code().unwrap()) {
+                err.note("References in statics and constants may only refer \
+                          to immutable values.\n\n\
+                          Statics are shared everywhere, and if they refer to \
+                          mutable data one might violate memory safety since \
+                          holding multiple mutable references to shared data \
+                          is not allowed.\n\n\
+                          If you really want global mutable state, try using \
+                          static mut or a global UnsafeCell.");
+            }
+            err.emit();
+        } else {
+            span_err!(item.tcx.sess, span, E0492,
+                      "cannot borrow a constant which may contain \
+                       interior mutability, create a static instead");
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct MutDeref;
+impl NonConstOp for MutDeref {}
+
+#[derive(Debug)]
+pub struct Panic;
+impl NonConstOp for Panic {
+    fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
+        Some(tcx.features().const_panic)
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        emit_feature_err(
+            &item.tcx.sess.parse_sess,
+            sym::const_panic,
+            span,
+            GateIssue::Language,
+            &format!("panicking in {}s is unstable", item.mode),
+        );
+    }
+}
+
+#[derive(Debug)]
+pub struct RawPtrComparison;
+impl NonConstOp for RawPtrComparison {
+    fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
+        Some(tcx.features().const_compare_raw_pointers)
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        emit_feature_err(
+            &item.tcx.sess.parse_sess,
+            sym::const_compare_raw_pointers,
+            span,
+            GateIssue::Language,
+            &format!("comparing raw pointers inside {}", item.mode),
+        );
+    }
+}
+
+#[derive(Debug)]
+pub struct RawPtrDeref;
+impl NonConstOp for RawPtrDeref {
+    fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
+        Some(tcx.features().const_raw_ptr_deref)
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        emit_feature_err(
+            &item.tcx.sess.parse_sess, sym::const_raw_ptr_deref,
+            span, GateIssue::Language,
+            &format!(
+                "dereferencing raw pointers in {}s is unstable",
+                item.mode,
+            ),
+        );
+    }
+}
+
+#[derive(Debug)]
+pub struct RawPtrToIntCast;
+impl NonConstOp for RawPtrToIntCast {
+    fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
+        Some(tcx.features().const_raw_ptr_to_usize_cast)
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        emit_feature_err(
+            &item.tcx.sess.parse_sess, sym::const_raw_ptr_to_usize_cast,
+            span, GateIssue::Language,
+            &format!(
+                "casting pointers to integers in {}s is unstable",
+                item.mode,
+            ),
+        );
+    }
+}
+
+/// An access to a (non-thread-local) `static`.
+#[derive(Debug)]
+pub struct StaticAccess;
+impl NonConstOp for StaticAccess {
+    fn is_allowed_in_item(&self, item: &Item<'_, '_>) -> bool {
+        item.mode.is_static()
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        let mut err = struct_span_err!(item.tcx.sess, span, E0013,
+                                        "{}s cannot refer to statics, use \
+                                        a constant instead", item.mode);
+        if item.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();
+    }
+}
+
+/// An access to a thread-local `static`.
+#[derive(Debug)]
+pub struct ThreadLocalAccess;
+impl NonConstOp for ThreadLocalAccess {
+    const IS_SUPPORTED_IN_MIRI: bool = false;
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        span_err!(item.tcx.sess, span, E0625,
+            "thread-local statics cannot be \
+            accessed at compile-time");
+    }
+}
+
+#[derive(Debug)]
+pub struct Transmute;
+impl NonConstOp for Transmute {
+    fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
+        Some(tcx.features().const_transmute)
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        emit_feature_err(
+            &item.tcx.sess.parse_sess, sym::const_transmute,
+            span, GateIssue::Language,
+            &format!("The use of std::mem::transmute() \
+            is gated in {}s", item.mode));
+    }
+}
+
+#[derive(Debug)]
+pub struct UnionAccess;
+impl NonConstOp for UnionAccess {
+    fn is_allowed_in_item(&self, item: &Item<'_, '_>) -> bool {
+        // Union accesses are stable in all contexts except `const fn`.
+        item.mode != Mode::ConstFn || Self::feature_gate(item.tcx).unwrap()
+    }
+
+    fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
+        Some(tcx.features().const_fn_union)
+    }
+
+    fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
+        emit_feature_err(
+            &item.tcx.sess.parse_sess, sym::const_fn_union,
+            span, GateIssue::Language,
+            "unions in const fn are unstable",
+        );
+    }
+}
diff --git a/src/librustc_mir/transform/check_consts/qualifs.rs b/src/librustc_mir/transform/check_consts/qualifs.rs
new file mode 100644
index 00000000000..d9d0ce18555
--- /dev/null
+++ b/src/librustc_mir/transform/check_consts/qualifs.rs
@@ -0,0 +1,284 @@
+//! A copy of the `Qualif` trait in `qualify_consts.rs` that is suitable for the new validator.
+
+use rustc::mir::*;
+use rustc::mir::interpret::ConstValue;
+use rustc::ty::{self, Ty};
+use rustc_data_structures::bit_set::BitSet;
+use syntax_pos::DUMMY_SP;
+
+use super::Item as ConstCx;
+use super::validation::Mode;
+
+#[derive(Clone, Copy)]
+pub struct QualifSet(u8);
+
+impl QualifSet {
+    fn contains<Q: ?Sized + Qualif>(self) -> bool {
+        self.0 & (1 << Q::IDX) != 0
+    }
+}
+
+/// 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.
+pub trait Qualif {
+    const IDX: usize;
+
+    /// Whether this `Qualif` is cleared when a local is moved from.
+    const IS_CLEARED_ON_MOVE: bool = false;
+
+    /// Return the qualification that is (conservatively) correct for any value
+    /// of the type.
+    fn in_any_value_of_ty(_cx: &ConstCx<'_, 'tcx>, _ty: Ty<'tcx>) -> bool;
+
+    fn in_static(_cx: &ConstCx<'_, 'tcx>, _static: &Static<'tcx>) -> bool {
+        // FIXME(eddyb) should we do anything here for value properties?
+        false
+    }
+
+    fn in_projection_structurally(
+        cx: &ConstCx<'_, 'tcx>,
+        per_local: &BitSet<Local>,
+        place: PlaceRef<'_, 'tcx>,
+    ) -> bool {
+        if let [proj_base @ .., elem] = place.projection {
+            let base_qualif = Self::in_place(cx, per_local, PlaceRef {
+                base: place.base,
+                projection: proj_base,
+            });
+            let qualif = base_qualif && Self::in_any_value_of_ty(
+                cx,
+                Place::ty_from(place.base, proj_base, cx.body, cx.tcx)
+                    .projection_ty(cx.tcx, elem)
+                    .ty,
+            );
+            match elem {
+                ProjectionElem::Deref |
+                ProjectionElem::Subslice { .. } |
+                ProjectionElem::Field(..) |
+                ProjectionElem::ConstantIndex { .. } |
+                ProjectionElem::Downcast(..) => qualif,
+
+                ProjectionElem::Index(local) => qualif || per_local.contains(*local),
+            }
+        } else {
+            bug!("This should be called if projection is not empty");
+        }
+    }
+
+    fn in_projection(
+        cx: &ConstCx<'_, 'tcx>,
+        per_local: &BitSet<Local>,
+        place: PlaceRef<'_, 'tcx>,
+    ) -> bool {
+        Self::in_projection_structurally(cx, per_local, place)
+    }
+
+    fn in_place(
+        cx: &ConstCx<'_, 'tcx>,
+        per_local: &BitSet<Local>,
+        place: PlaceRef<'_, 'tcx>,
+    ) -> bool {
+        match place {
+            PlaceRef {
+                base: PlaceBase::Local(local),
+                projection: [],
+            } => per_local.contains(*local),
+            PlaceRef {
+                base: PlaceBase::Static(box Static {
+                    kind: StaticKind::Promoted(..),
+                    ..
+                }),
+                projection: [],
+            } => bug!("qualifying already promoted MIR"),
+            PlaceRef {
+                base: PlaceBase::Static(static_),
+                projection: [],
+            } => {
+                Self::in_static(cx, static_)
+            },
+            PlaceRef {
+                base: _,
+                projection: [.., _],
+            } => Self::in_projection(cx, per_local, place),
+        }
+    }
+
+    fn in_operand(
+        cx: &ConstCx<'_, 'tcx>,
+        per_local: &BitSet<Local>,
+        operand: &Operand<'tcx>,
+    ) -> bool {
+        match *operand {
+            Operand::Copy(ref place) |
+            Operand::Move(ref place) => Self::in_place(cx, per_local, place.as_ref()),
+
+            Operand::Constant(ref constant) => {
+                if let ConstValue::Unevaluated(def_id, _) = constant.literal.val {
+                    // Don't peek inside trait associated constants.
+                    if cx.tcx.trait_of_item(def_id).is_some() {
+                        Self::in_any_value_of_ty(cx, constant.literal.ty)
+                    } else {
+                        let (bits, _) = cx.tcx.at(constant.span).mir_const_qualif(def_id);
+
+                        let qualif = QualifSet(bits).contains::<Self>();
+
+                        // Just in case the type is more specific than
+                        // the definition, e.g., impl associated const
+                        // with type parameters, take it into account.
+                        qualif && Self::in_any_value_of_ty(cx, constant.literal.ty)
+                    }
+                } else {
+                    false
+                }
+            }
+        }
+    }
+
+    fn in_rvalue_structurally(
+        cx: &ConstCx<'_, 'tcx>,
+        per_local: &BitSet<Local>,
+        rvalue: &Rvalue<'tcx>,
+    ) -> bool {
+        match *rvalue {
+            Rvalue::NullaryOp(..) => false,
+
+            Rvalue::Discriminant(ref place) |
+            Rvalue::Len(ref place) => Self::in_place(cx, per_local, place.as_ref()),
+
+            Rvalue::Use(ref operand) |
+            Rvalue::Repeat(ref operand, _) |
+            Rvalue::UnaryOp(_, ref operand) |
+            Rvalue::Cast(_, ref operand, _) => Self::in_operand(cx, per_local, operand),
+
+            Rvalue::BinaryOp(_, ref lhs, ref rhs) |
+            Rvalue::CheckedBinaryOp(_, ref lhs, ref rhs) => {
+                Self::in_operand(cx, per_local, lhs) || Self::in_operand(cx, per_local, rhs)
+            }
+
+            Rvalue::Ref(_, _, ref place) => {
+                // Special-case reborrows to be more like a copy of the reference.
+                if let box [proj_base @ .., elem] = &place.projection {
+                    if ProjectionElem::Deref == *elem {
+                        let base_ty = Place::ty_from(&place.base, proj_base, cx.body, cx.tcx).ty;
+                        if let ty::Ref(..) = base_ty.kind {
+                            return Self::in_place(cx, per_local, PlaceRef {
+                                base: &place.base,
+                                projection: proj_base,
+                            });
+                        }
+                    }
+                }
+
+                Self::in_place(cx, per_local, place.as_ref())
+            }
+
+            Rvalue::Aggregate(_, ref operands) => {
+                operands.iter().any(|o| Self::in_operand(cx, per_local, o))
+            }
+        }
+    }
+
+    fn in_rvalue(cx: &ConstCx<'_, 'tcx>, per_local: &BitSet<Local>, rvalue: &Rvalue<'tcx>) -> bool {
+        Self::in_rvalue_structurally(cx, per_local, rvalue)
+    }
+
+    fn in_call(
+        cx: &ConstCx<'_, 'tcx>,
+        _per_local: &BitSet<Local>,
+        _callee: &Operand<'tcx>,
+        _args: &[Operand<'tcx>],
+        return_ty: Ty<'tcx>,
+    ) -> bool {
+        // Be conservative about the returned value of a const fn.
+        Self::in_any_value_of_ty(cx, return_ty)
+    }
+}
+
+/// 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.
+pub struct HasMutInterior;
+
+impl Qualif for HasMutInterior {
+    const IDX: usize = 0;
+
+    fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
+        !ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP)
+    }
+
+    fn in_rvalue(cx: &ConstCx<'_, 'tcx>, per_local: &BitSet<Local>, rvalue: &Rvalue<'tcx>) -> bool {
+        match *rvalue {
+            // Returning `true` for `Rvalue::Ref` indicates the borrow isn't
+            // 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.body, cx.tcx).ty;
+
+                if let BorrowKind::Mut { .. } = kind {
+                    // In theory, any zero-sized value could be borrowed
+                    // mutably without consequences.
+                    match ty.kind {
+                        // Inside a `static mut`, &mut [...] is also allowed.
+                        ty::Array(..) | ty::Slice(_) if cx.mode == Mode::StaticMut => {},
+
+                        // FIXME(ecstaticmorse): uncomment the following match arm to stop marking
+                        // `&mut []` as `HasMutInterior`.
+                        /*
+                        ty::Array(_, len) if len.try_eval_usize(cx.tcx, cx.param_env) == Some(0)
+                            => {},
+                        */
+
+                        _ => return true,
+                    }
+                }
+            }
+
+            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.body, cx.tcx);
+                        assert_eq!(Self::in_any_value_of_ty(cx, ty), true);
+                        return true;
+                    }
+                }
+            }
+
+            _ => {}
+        }
+
+        Self::in_rvalue_structurally(cx, per_local, rvalue)
+    }
+}
+
+/// 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.
+pub struct NeedsDrop;
+
+impl Qualif for NeedsDrop {
+    const IDX: usize = 1;
+    const IS_CLEARED_ON_MOVE: bool = true;
+
+    fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
+        ty.needs_drop(cx.tcx, cx.param_env)
+    }
+
+    fn in_rvalue(cx: &ConstCx<'_, 'tcx>, per_local: &BitSet<Local>, rvalue: &Rvalue<'tcx>) -> bool {
+        if let Rvalue::Aggregate(ref kind, _) = *rvalue {
+            if let AggregateKind::Adt(def, ..) = **kind {
+                if def.has_dtor(cx.tcx) {
+                    return true;
+                }
+            }
+        }
+
+        Self::in_rvalue_structurally(cx, per_local, rvalue)
+    }
+}
diff --git a/src/librustc_mir/transform/check_consts/resolver.rs b/src/librustc_mir/transform/check_consts/resolver.rs
new file mode 100644
index 00000000000..2789693ecb6
--- /dev/null
+++ b/src/librustc_mir/transform/check_consts/resolver.rs
@@ -0,0 +1,349 @@
+//! Propagate `Qualif`s between locals and query the results.
+//!
+//! This also contains the dataflow analysis used to track `Qualif`s on complex control-flow
+//! graphs.
+
+use rustc::mir::visit::Visitor;
+use rustc::mir::{self, BasicBlock, Local, Location};
+use rustc_data_structures::bit_set::BitSet;
+
+use std::cell::RefCell;
+use std::marker::PhantomData;
+
+use crate::dataflow::{self as old_dataflow, generic as dataflow};
+use super::{Item, Qualif};
+use self::old_dataflow::IndirectlyMutableLocals;
+
+/// A `Visitor` that propagates qualifs between locals. This defines the transfer function of
+/// `FlowSensitiveAnalysis` as well as the logic underlying `TempPromotionResolver`.
+///
+/// This transfer does nothing when encountering an indirect assignment. Consumers should rely on
+/// the `IndirectlyMutableLocals` dataflow pass to see if a `Local` may have become qualified via
+/// an indirect assignment or function call.
+struct TransferFunction<'a, 'mir, 'tcx, Q> {
+    item: &'a Item<'mir, 'tcx>,
+    qualifs_per_local: &'a mut BitSet<Local>,
+
+    _qualif: PhantomData<Q>,
+}
+
+impl<Q> TransferFunction<'a, 'mir, 'tcx, Q>
+where
+    Q: Qualif,
+{
+    fn new(
+        item: &'a Item<'mir, 'tcx>,
+        qualifs_per_local: &'a mut BitSet<Local>,
+    ) -> Self {
+        TransferFunction {
+            item,
+            qualifs_per_local,
+            _qualif: PhantomData,
+        }
+    }
+
+    fn initialize_state(&mut self) {
+        self.qualifs_per_local.clear();
+
+        for arg in self.item.body.args_iter() {
+            let arg_ty = self.item.body.local_decls[arg].ty;
+            if Q::in_any_value_of_ty(self.item, arg_ty) {
+                self.qualifs_per_local.insert(arg);
+            }
+        }
+    }
+
+    fn assign_qualif_direct(&mut self, place: &mir::Place<'tcx>, value: bool) {
+        debug_assert!(!place.is_indirect());
+
+        match (value, place) {
+            (true, mir::Place { base: mir::PlaceBase::Local(local), .. }) => {
+                self.qualifs_per_local.insert(*local);
+            }
+
+            // For now, we do not clear the qualif if a local is overwritten in full by
+            // an unqualified rvalue (e.g. `y = 5`). This is to be consistent
+            // with aggregates where we overwrite all fields with assignments, which would not
+            // get this feature.
+            (false, mir::Place { base: mir::PlaceBase::Local(_local), projection: box [] }) => {
+                // self.qualifs_per_local.remove(*local);
+            }
+
+            _ => {}
+        }
+    }
+
+    fn apply_call_return_effect(
+        &mut self,
+        _block: BasicBlock,
+        func: &mir::Operand<'tcx>,
+        args: &[mir::Operand<'tcx>],
+        return_place: &mir::Place<'tcx>,
+    ) {
+        let return_ty = return_place.ty(self.item.body, self.item.tcx).ty;
+        let qualif = Q::in_call(self.item, &mut self.qualifs_per_local, func, args, return_ty);
+        if !return_place.is_indirect() {
+            self.assign_qualif_direct(return_place, qualif);
+        }
+    }
+}
+
+impl<Q> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx, Q>
+where
+    Q: Qualif,
+{
+    fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
+        self.super_operand(operand, location);
+
+        if !Q::IS_CLEARED_ON_MOVE {
+            return;
+        }
+
+        // If a local with no projections is moved from (e.g. `x` in `y = x`), record that
+        // it no longer needs to be dropped.
+        if let mir::Operand::Move(mir::Place {
+            base: mir::PlaceBase::Local(local),
+            projection: box [],
+        }) = *operand {
+            self.qualifs_per_local.remove(local);
+        }
+    }
+
+    fn visit_assign(
+        &mut self,
+        place: &mir::Place<'tcx>,
+        rvalue: &mir::Rvalue<'tcx>,
+        location: Location,
+    ) {
+        let qualif = Q::in_rvalue(self.item, self.qualifs_per_local, rvalue);
+        if !place.is_indirect() {
+            self.assign_qualif_direct(place, qualif);
+        }
+
+        // We need to assign qualifs to the left-hand side before visiting `rvalue` since
+        // qualifs can be cleared on move.
+        self.super_assign(place, rvalue, location);
+    }
+
+    fn visit_terminator_kind(&mut self, kind: &mir::TerminatorKind<'tcx>, location: Location) {
+        // The effect of assignment to the return place in `TerminatorKind::Call` is not applied
+        // here; that occurs in `apply_call_return_effect`.
+
+        if let mir::TerminatorKind::DropAndReplace { value, location: dest, .. } = kind {
+            let qualif = Q::in_operand(self.item, self.qualifs_per_local, value);
+            if !dest.is_indirect() {
+                self.assign_qualif_direct(dest, qualif);
+            }
+        }
+
+        // We need to assign qualifs to the dropped location before visiting the operand that
+        // replaces it since qualifs can be cleared on move.
+        self.super_terminator_kind(kind, location);
+    }
+}
+
+/// Types that can compute the qualifs of each local at each location in a `mir::Body`.
+///
+/// Code that wishes to use a `QualifResolver` must call `visit_{statement,terminator}` for each
+/// statement or terminator, processing blocks in reverse post-order beginning from the
+/// `START_BLOCK`. Calling code may optionally call `get` after visiting each statement or
+/// terminator to query the qualification state immediately before that statement or terminator.
+///
+/// These conditions are much more restrictive than woud be required by `FlowSensitiveResolver`
+/// alone. This is to allow a linear, on-demand `TempPromotionResolver` that can operate
+/// efficiently on simple CFGs.
+pub trait QualifResolver<Q> {
+    /// Get the qualifs of each local at the last location visited.
+    ///
+    /// This takes `&mut self` so qualifs can be computed lazily.
+    fn get(&mut self) -> &BitSet<Local>;
+
+    /// A convenience method for `self.get().contains(local)`.
+    fn contains(&mut self, local: Local) -> bool {
+        self.get().contains(local)
+    }
+
+    /// Resets the resolver to the `START_BLOCK`. This allows a resolver to be reused
+    /// for multiple passes over a `mir::Body`.
+    fn reset(&mut self);
+}
+
+pub type IndirectlyMutableResults<'mir, 'tcx> =
+    old_dataflow::DataflowResultsCursor<'mir, 'tcx, IndirectlyMutableLocals<'mir, 'tcx>>;
+
+/// A resolver for qualifs that works on arbitrarily complex CFGs.
+///
+/// As soon as a `Local` becomes writable through a reference (as determined by the
+/// `IndirectlyMutableLocals` dataflow pass), we must assume that it takes on all other qualifs
+/// possible for its type. This is because no effort is made to track qualifs across indirect
+/// assignments (e.g. `*p = x` or calls to opaque functions).
+///
+/// It is possible to be more precise here by waiting until an indirect assignment actually occurs
+/// before marking a borrowed `Local` as qualified.
+pub struct FlowSensitiveResolver<'a, 'mir, 'tcx, Q>
+where
+    Q: Qualif,
+{
+    location: Location,
+    indirectly_mutable_locals: &'a RefCell<IndirectlyMutableResults<'mir, 'tcx>>,
+    cursor: dataflow::ResultsCursor<'mir, 'tcx, FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q>>,
+    qualifs_per_local: BitSet<Local>,
+
+    /// The value of `Q::in_any_value_of_ty` for each local.
+    qualifs_in_any_value_of_ty: BitSet<Local>,
+}
+
+impl<Q> FlowSensitiveResolver<'a, 'mir, 'tcx, Q>
+where
+    Q: Qualif,
+{
+    pub fn new(
+        _: Q,
+        item: &'a Item<'mir, 'tcx>,
+        indirectly_mutable_locals: &'a RefCell<IndirectlyMutableResults<'mir, 'tcx>>,
+        dead_unwinds: &BitSet<BasicBlock>,
+    ) -> Self {
+        let analysis = FlowSensitiveAnalysis {
+            item,
+            _qualif: PhantomData,
+        };
+        let results =
+            dataflow::Engine::new(item.body, dead_unwinds, analysis).iterate_to_fixpoint();
+        let cursor = dataflow::ResultsCursor::new(item.body, results);
+
+        let mut qualifs_in_any_value_of_ty = BitSet::new_empty(item.body.local_decls.len());
+        for (local, decl) in item.body.local_decls.iter_enumerated() {
+            if Q::in_any_value_of_ty(item, decl.ty) {
+                qualifs_in_any_value_of_ty.insert(local);
+            }
+        }
+
+        FlowSensitiveResolver {
+            cursor,
+            indirectly_mutable_locals,
+            qualifs_per_local: BitSet::new_empty(item.body.local_decls.len()),
+            qualifs_in_any_value_of_ty,
+            location: Location { block: mir::START_BLOCK, statement_index: 0 },
+        }
+    }
+}
+
+impl<Q> Visitor<'tcx> for FlowSensitiveResolver<'_, '_, 'tcx, Q>
+where
+    Q: Qualif
+{
+    fn visit_statement(&mut self, _: &mir::Statement<'tcx>, location: Location) {
+        self.location = location;
+    }
+
+    fn visit_terminator(&mut self, _: &mir::Terminator<'tcx>, location: Location) {
+        self.location = location;
+    }
+}
+
+impl<Q> QualifResolver<Q> for FlowSensitiveResolver<'_, '_, '_, Q>
+where
+    Q: Qualif
+{
+    fn get(&mut self) -> &BitSet<Local> {
+        let mut indirectly_mutable_locals = self.indirectly_mutable_locals.borrow_mut();
+
+        indirectly_mutable_locals.seek(self.location);
+        self.cursor.seek_before(self.location);
+
+        self.qualifs_per_local.overwrite(indirectly_mutable_locals.get());
+        self.qualifs_per_local.union(self.cursor.get());
+        self.qualifs_per_local.intersect(&self.qualifs_in_any_value_of_ty);
+        &self.qualifs_per_local
+    }
+
+    fn contains(&mut self, local: Local) -> bool {
+        // No need to update the cursor if we know that `Local` cannot possibly be qualified.
+        if !self.qualifs_in_any_value_of_ty.contains(local) {
+            return false;
+        }
+
+        // Otherwise, return `true` if this local is qualified or was indirectly mutable at any
+        // point before this statement.
+        self.cursor.seek_before(self.location);
+        if self.cursor.get().contains(local) {
+            return true;
+        }
+
+        let mut indirectly_mutable_locals = self.indirectly_mutable_locals.borrow_mut();
+        indirectly_mutable_locals.seek(self.location);
+        indirectly_mutable_locals.get().contains(local)
+    }
+
+    fn reset(&mut self)  {
+        self.location = Location { block: mir::START_BLOCK, statement_index: 0 };
+    }
+}
+
+/// The dataflow analysis used to propagate qualifs on arbitrary CFGs.
+pub(super) struct FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q> {
+    item: &'a Item<'mir, 'tcx>,
+    _qualif: PhantomData<Q>,
+}
+
+impl<'a, 'mir, 'tcx, Q> FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q>
+where
+    Q: Qualif,
+{
+    fn transfer_function(
+        &self,
+        state: &'a mut BitSet<Local>,
+    ) -> TransferFunction<'a, 'mir, 'tcx, Q> {
+        TransferFunction::<Q>::new(self.item, state)
+    }
+}
+
+impl<Q> old_dataflow::BottomValue for FlowSensitiveAnalysis<'_, '_, '_, Q> {
+    const BOTTOM_VALUE: bool = false;
+}
+
+impl<Q> dataflow::Analysis<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
+where
+    Q: Qualif,
+{
+    type Idx = Local;
+
+    const NAME: &'static str = "flow_sensitive_qualif";
+
+    fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
+        body.local_decls.len()
+    }
+
+    fn initialize_start_block(&self, _body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
+        self.transfer_function(state).initialize_state();
+    }
+
+    fn apply_statement_effect(
+        &self,
+        state: &mut BitSet<Self::Idx>,
+        statement: &mir::Statement<'tcx>,
+        location: Location,
+    ) {
+        self.transfer_function(state).visit_statement(statement, location);
+    }
+
+    fn apply_terminator_effect(
+        &self,
+        state: &mut BitSet<Self::Idx>,
+        terminator: &mir::Terminator<'tcx>,
+        location: Location,
+    ) {
+        self.transfer_function(state).visit_terminator(terminator, location);
+    }
+
+    fn apply_call_return_effect(
+        &self,
+        state: &mut BitSet<Self::Idx>,
+        block: BasicBlock,
+        func: &mir::Operand<'tcx>,
+        args: &[mir::Operand<'tcx>],
+        return_place: &mir::Place<'tcx>,
+    ) {
+        self.transfer_function(state).apply_call_return_effect(block, func, args, return_place)
+    }
+}
diff --git a/src/librustc_mir/transform/check_consts/validation.rs b/src/librustc_mir/transform/check_consts/validation.rs
new file mode 100644
index 00000000000..7e876dd1d99
--- /dev/null
+++ b/src/librustc_mir/transform/check_consts/validation.rs
@@ -0,0 +1,597 @@
+//! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
+
+use rustc::hir::{self, def_id::DefId};
+use rustc::mir::visit::{PlaceContext, Visitor, MutatingUseContext, NonMutatingUseContext};
+use rustc::mir::*;
+use rustc::ty::cast::CastTy;
+use rustc::ty::{self, TyCtxt};
+use rustc_data_structures::bit_set::BitSet;
+use rustc_target::spec::abi::Abi;
+use syntax::symbol::sym;
+use syntax_pos::Span;
+
+use std::cell::RefCell;
+use std::fmt;
+use std::ops::Deref;
+
+use crate::dataflow as old_dataflow;
+use super::{Item, Qualif, is_lang_panic_fn};
+use super::resolver::{FlowSensitiveResolver, IndirectlyMutableResults, QualifResolver};
+use super::qualifs::{HasMutInterior, NeedsDrop};
+use super::ops::{self, NonConstOp};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum CheckOpResult {
+    Forbidden,
+    Unleashed,
+    Allowed,
+}
+
+/// What kind of item we are in.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Mode {
+    /// A `static` item.
+    Static,
+    /// A `static mut` item.
+    StaticMut,
+    /// A `const fn` item.
+    ConstFn,
+    /// A `const` item or an anonymous constant (e.g. in array lengths).
+    Const,
+}
+
+impl Mode {
+    /// Returns the validation mode for the item with the given `DefId`, or `None` if this item
+    /// does not require validation (e.g. a non-const `fn`).
+    pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Self> {
+        use hir::BodyOwnerKind as HirKind;
+
+        let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
+
+        let mode = match tcx.hir().body_owner_kind(hir_id) {
+            HirKind::Closure => return None,
+
+            HirKind::Fn if tcx.is_const_fn(def_id) => Mode::ConstFn,
+            HirKind::Fn => return None,
+
+            HirKind::Const => Mode::Const,
+
+            HirKind::Static(hir::MutImmutable) => Mode::Static,
+            HirKind::Static(hir::MutMutable) => Mode::StaticMut,
+        };
+
+        Some(mode)
+    }
+
+    pub fn is_static(self) -> bool {
+        match self {
+            Mode::Static | Mode::StaticMut => true,
+            Mode::ConstFn | Mode::Const => false,
+        }
+    }
+
+    /// Returns `true` if the value returned by this item must be `Sync`.
+    ///
+    /// This returns false for `StaticMut` since all accesses to one are `unsafe` anyway.
+    pub fn requires_sync(self) -> bool {
+        match self {
+            Mode::Static => true,
+            Mode::ConstFn | Mode::Const |  Mode::StaticMut => false,
+        }
+    }
+}
+
+impl fmt::Display for Mode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match *self {
+            Mode::Const => write!(f, "constant"),
+            Mode::Static | Mode::StaticMut => write!(f, "static"),
+            Mode::ConstFn => write!(f, "constant function"),
+        }
+    }
+}
+
+pub struct Qualifs<'a, 'mir, 'tcx> {
+    has_mut_interior: FlowSensitiveResolver<'a, 'mir, 'tcx, HasMutInterior>,
+    needs_drop: FlowSensitiveResolver<'a, 'mir, 'tcx, NeedsDrop>,
+}
+
+pub struct Validator<'a, 'mir, 'tcx> {
+    item: &'a Item<'mir, 'tcx>,
+    qualifs: Qualifs<'a, 'mir, 'tcx>,
+
+    /// The span of the current statement.
+    span: Span,
+
+    /// True if the local was assigned the result of an illegal borrow (`ops::MutBorrow`).
+    ///
+    /// This is used to hide errors from {re,}borrowing the newly-assigned local, instead pointing
+    /// the user to the place where the illegal borrow occurred. This set is only populated once an
+    /// error has been emitted, so it will never cause an erroneous `mir::Body` to pass validation.
+    ///
+    /// FIXME(ecstaticmorse): assert at the end of checking that if `tcx.has_errors() == false`,
+    /// this set is empty. Note that if we start removing locals from
+    /// `derived_from_illegal_borrow`, just checking at the end won't be enough.
+    derived_from_illegal_borrow: BitSet<Local>,
+
+    errors: Vec<(Span, String)>,
+
+    /// Whether to actually emit errors or just store them in `errors`.
+    pub(crate) suppress_errors: bool,
+}
+
+impl Deref for Validator<'_, 'mir, 'tcx> {
+    type Target = Item<'mir, 'tcx>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.item
+    }
+}
+
+pub fn compute_indirectly_mutable_locals<'mir, 'tcx>(
+    item: &Item<'mir, 'tcx>,
+) -> RefCell<IndirectlyMutableResults<'mir, 'tcx>> {
+    let dead_unwinds = BitSet::new_empty(item.body.basic_blocks().len());
+
+    let indirectly_mutable_locals = old_dataflow::do_dataflow(
+        item.tcx,
+        item.body,
+        item.def_id,
+        &[],
+        &dead_unwinds,
+        old_dataflow::IndirectlyMutableLocals::new(item.tcx, item.body, item.param_env),
+        |_, local| old_dataflow::DebugFormatted::new(&local),
+    );
+
+    let indirectly_mutable_locals = old_dataflow::DataflowResultsCursor::new(
+        indirectly_mutable_locals,
+        item.body,
+    );
+
+    RefCell::new(indirectly_mutable_locals)
+}
+
+impl Validator<'a, 'mir, 'tcx> {
+    pub fn new(
+        item: &'a Item<'mir, 'tcx>,
+        indirectly_mutable_locals: &'a RefCell<IndirectlyMutableResults<'mir, 'tcx>>,
+    ) -> Self {
+        let dead_unwinds = BitSet::new_empty(item.body.basic_blocks().len());
+
+        let needs_drop = FlowSensitiveResolver::new(
+            NeedsDrop,
+            item,
+            indirectly_mutable_locals,
+            &dead_unwinds,
+        );
+
+        let has_mut_interior = FlowSensitiveResolver::new(
+            HasMutInterior,
+            item,
+            indirectly_mutable_locals,
+            &dead_unwinds,
+        );
+
+        let qualifs = Qualifs {
+            needs_drop,
+            has_mut_interior,
+        };
+
+        Validator {
+            span: item.body.span,
+            item,
+            qualifs,
+            errors: vec![],
+            derived_from_illegal_borrow: BitSet::new_empty(item.body.local_decls.len()),
+            suppress_errors: false,
+        }
+    }
+
+    /// Resets the `QualifResolver`s used by this `Validator` and returns them so they can be
+    /// reused.
+    pub fn into_qualifs(mut self) -> Qualifs<'a, 'mir, 'tcx> {
+        self.qualifs.needs_drop.reset();
+        self.qualifs.has_mut_interior.reset();
+        self.qualifs
+    }
+
+    pub fn take_errors(&mut self) -> Vec<(Span, String)> {
+        std::mem::replace(&mut self.errors, vec![])
+    }
+
+    /// Emits an error at the given `span` if an expression cannot be evaluated in the current
+    /// context. Returns `Forbidden` if an error was emitted.
+    pub fn check_op_spanned<O>(&mut self, op: O, span: Span) -> CheckOpResult
+    where
+        O: NonConstOp + fmt::Debug
+    {
+        trace!("check_op: op={:?}", op);
+
+        if op.is_allowed_in_item(self) {
+            return CheckOpResult::Allowed;
+        }
+
+        // If an operation is supported in miri (and is not already controlled by a feature gate) it
+        // can be turned on with `-Zunleash-the-miri-inside-of-you`.
+        let is_unleashable = O::IS_SUPPORTED_IN_MIRI
+            && O::feature_gate(self.tcx).is_none();
+
+        if is_unleashable && self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
+            self.tcx.sess.span_warn(span, "skipping const checks");
+            return CheckOpResult::Unleashed;
+        }
+
+        if !self.suppress_errors {
+            op.emit_error(self, span);
+        }
+
+        self.errors.push((span, format!("{:?}", op)));
+        CheckOpResult::Forbidden
+    }
+
+    /// Emits an error if an expression cannot be evaluated in the current context.
+    pub fn check_op(&mut self, op: impl NonConstOp + fmt::Debug) -> CheckOpResult {
+        let span = self.span;
+        self.check_op_spanned(op, span)
+    }
+}
+
+impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
+    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
+        trace!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location);
+
+        // Check nested operands and places.
+        if let Rvalue::Ref(_, kind, ref place) = *rvalue {
+            // Special-case reborrows to be more like a copy of a reference.
+            let mut reborrow_place = None;
+            if let box [proj_base @ .., elem] = &place.projection {
+                if *elem == ProjectionElem::Deref {
+                    let base_ty = Place::ty_from(&place.base, proj_base, self.body, self.tcx).ty;
+                    if let ty::Ref(..) = base_ty.kind {
+                        reborrow_place = Some(proj_base);
+                    }
+                }
+            }
+
+            if let Some(proj) = reborrow_place {
+                let ctx = match kind {
+                    BorrowKind::Shared => PlaceContext::NonMutatingUse(
+                        NonMutatingUseContext::SharedBorrow,
+                    ),
+                    BorrowKind::Shallow => PlaceContext::NonMutatingUse(
+                        NonMutatingUseContext::ShallowBorrow,
+                    ),
+                    BorrowKind::Unique => PlaceContext::NonMutatingUse(
+                        NonMutatingUseContext::UniqueBorrow,
+                    ),
+                    BorrowKind::Mut { .. } => PlaceContext::MutatingUse(
+                        MutatingUseContext::Borrow,
+                    ),
+                };
+                self.visit_place_base(&place.base, ctx, location);
+                self.visit_projection(&place.base, proj, ctx, location);
+            } else {
+                self.super_rvalue(rvalue, location);
+            }
+        } else {
+            self.super_rvalue(rvalue, location);
+        }
+
+        match *rvalue {
+            Rvalue::Use(_) |
+            Rvalue::Repeat(..) |
+            Rvalue::UnaryOp(UnOp::Neg, _) |
+            Rvalue::UnaryOp(UnOp::Not, _) |
+            Rvalue::NullaryOp(NullOp::SizeOf, _) |
+            Rvalue::CheckedBinaryOp(..) |
+            Rvalue::Cast(CastKind::Pointer(_), ..) |
+            Rvalue::Discriminant(..) |
+            Rvalue::Len(_) |
+            Rvalue::Ref(..) |
+            Rvalue::Aggregate(..) => {}
+
+            Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => {
+                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");
+
+                if let (CastTy::Ptr(_), CastTy::Int(_))
+                     | (CastTy::FnPtr,  CastTy::Int(_)) = (cast_in, cast_out) {
+                    self.check_op(ops::RawPtrToIntCast);
+                }
+            }
+
+            Rvalue::BinaryOp(op, ref lhs, _) => {
+                if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind {
+                    assert!(op == BinOp::Eq || op == BinOp::Ne ||
+                            op == BinOp::Le || op == BinOp::Lt ||
+                            op == BinOp::Ge || op == BinOp::Gt ||
+                            op == BinOp::Offset);
+
+
+                    self.check_op(ops::RawPtrComparison);
+                }
+            }
+
+            Rvalue::NullaryOp(NullOp::Box, _) => {
+                self.check_op(ops::HeapAllocation);
+            }
+        }
+    }
+
+    fn visit_place_base(
+        &mut self,
+        place_base: &PlaceBase<'tcx>,
+        context: PlaceContext,
+        location: Location,
+    ) {
+        trace!(
+            "visit_place_base: place_base={:?} context={:?} location={:?}",
+            place_base,
+            context,
+            location,
+        );
+        self.super_place_base(place_base, context, location);
+
+        match place_base {
+            PlaceBase::Local(_) => {}
+            PlaceBase::Static(box Static{ kind: StaticKind::Promoted(_, _), .. }) => {
+                bug!("Promotion must be run after const validation");
+            }
+
+            PlaceBase::Static(box Static{ kind: StaticKind::Static, def_id, .. }) => {
+                let is_thread_local = self.tcx.has_attr(*def_id, sym::thread_local);
+                if is_thread_local {
+                    self.check_op(ops::ThreadLocalAccess);
+                } else 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",
+                    );
+                } else {
+                    self.check_op(ops::StaticAccess);
+                }
+            }
+        }
+    }
+
+    fn visit_assign(&mut self, dest: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
+        trace!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location);
+
+        // Error on mutable borrows or shared borrows of values with interior mutability.
+        //
+        // This replicates the logic at the start of `assign` in the old const checker.  Note that
+        // it depends on `HasMutInterior` being set for mutable borrows as well as values with
+        // interior mutability.
+        if let Rvalue::Ref(_, kind, ref borrowed_place) = *rvalue {
+            let rvalue_has_mut_interior = HasMutInterior::in_rvalue(
+                &self.item,
+                self.qualifs.has_mut_interior.get(),
+                rvalue,
+            );
+
+            if rvalue_has_mut_interior {
+                let is_derived_from_illegal_borrow = match *borrowed_place {
+                    // If an unprojected local was borrowed and its value was the result of an
+                    // illegal borrow, suppress this error and mark the result of this borrow as
+                    // illegal as well.
+                    Place { base: PlaceBase::Local(borrowed_local), projection: box [] }
+                        if self.derived_from_illegal_borrow.contains(borrowed_local) => true,
+
+                    // Otherwise proceed normally: check the legality of a mutable borrow in this
+                    // context.
+                    _ => self.check_op(ops::MutBorrow(kind)) == CheckOpResult::Forbidden,
+                };
+
+                // When the target of the assignment is a local with no projections, mark it as
+                // derived from an illegal borrow if necessary.
+                //
+                // FIXME: should we also clear `derived_from_illegal_borrow` when a local is
+                // assigned a new value?
+                if is_derived_from_illegal_borrow {
+                    if let Place { base: PlaceBase::Local(dest), projection: box [] } = *dest {
+                        self.derived_from_illegal_borrow.insert(dest);
+                    }
+                }
+            }
+        }
+
+        self.super_assign(dest, rvalue, location);
+    }
+
+    fn visit_projection(
+        &mut self,
+        place_base: &PlaceBase<'tcx>,
+        proj: &[PlaceElem<'tcx>],
+        context: PlaceContext,
+        location: Location,
+    ) {
+        trace!(
+            "visit_place_projection: proj={:?} context={:?} location={:?}",
+            proj,
+            context,
+            location,
+        );
+        self.super_projection(place_base, proj, context, location);
+
+        let (elem, proj_base) = match proj.split_last() {
+            Some(x) => x,
+            None => return,
+        };
+
+        match elem {
+            ProjectionElem::Deref => {
+                if context.is_mutating_use() {
+                    self.check_op(ops::MutDeref);
+                }
+
+                let base_ty = Place::ty_from(place_base, proj_base, self.body, self.tcx).ty;
+                if let ty::RawPtr(_) = base_ty.kind {
+                    self.check_op(ops::RawPtrDeref);
+                }
+            }
+
+            ProjectionElem::ConstantIndex {..} |
+            ProjectionElem::Subslice {..} |
+            ProjectionElem::Field(..) |
+            ProjectionElem::Index(_) => {
+                let base_ty = Place::ty_from(place_base, proj_base, self.body, self.tcx).ty;
+                match base_ty.ty_adt_def() {
+                    Some(def) if def.is_union() => {
+                        self.check_op(ops::UnionAccess);
+                    }
+
+                    _ => {}
+                }
+            }
+
+            ProjectionElem::Downcast(..) => {
+                self.check_op(ops::Downcast);
+            }
+        }
+    }
+
+
+    fn visit_source_info(&mut self, source_info: &SourceInfo) {
+        trace!("visit_source_info: source_info={:?}", source_info);
+        self.span = source_info.span;
+    }
+
+    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
+        trace!("visit_statement: statement={:?} location={:?}", statement, location);
+
+        self.qualifs.needs_drop.visit_statement(statement, location);
+        self.qualifs.has_mut_interior.visit_statement(statement, location);
+        debug!("needs_drop: {:?}", self.qualifs.needs_drop.get());
+        debug!("has_mut_interior: {:?}", self.qualifs.has_mut_interior.get());
+
+        match statement.kind {
+            StatementKind::Assign(..) => {
+                self.super_statement(statement, location);
+            }
+            StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _) => {
+                self.check_op(ops::IfOrMatch);
+            }
+            // FIXME(eddyb) should these really do nothing?
+            StatementKind::FakeRead(..) |
+            StatementKind::SetDiscriminant { .. } |
+            StatementKind::StorageLive(_) |
+            StatementKind::StorageDead(_) |
+            StatementKind::InlineAsm {..} |
+            StatementKind::Retag { .. } |
+            StatementKind::AscribeUserType(..) |
+            StatementKind::Nop => {}
+        }
+    }
+
+    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+        trace!("visit_terminator: terminator={:?} location={:?}", terminator, location);
+
+        self.qualifs.needs_drop.visit_terminator(terminator, location);
+        self.qualifs.has_mut_interior.visit_terminator(terminator, location);
+        debug!("needs_drop: {:?}", self.qualifs.needs_drop.get());
+        debug!("has_mut_interior: {:?}", self.qualifs.has_mut_interior.get());
+
+        self.super_terminator(terminator, location);
+    }
+
+    fn visit_terminator_kind(&mut self, kind: &TerminatorKind<'tcx>, location: Location) {
+        trace!("visit_terminator_kind: kind={:?} location={:?}", kind, location);
+        self.super_terminator_kind(kind, location);
+
+        match kind {
+            TerminatorKind::Call { func, .. } => {
+                let fn_ty = func.ty(self.body, self.tcx);
+
+                let def_id = match fn_ty.kind {
+                    ty::FnDef(def_id, _) => def_id,
+
+                    ty::FnPtr(_) => {
+                        self.check_op(ops::FnCallIndirect);
+                        return;
+                    }
+                    _ => {
+                        self.check_op(ops::FnCallOther);
+                        return;
+                    }
+                };
+
+                // At this point, we are calling a function whose `DefId` is known...
+
+                if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = self.tcx.fn_sig(def_id).abi() {
+                    assert!(!self.tcx.is_const_fn(def_id));
+
+                    if self.tcx.item_name(def_id) == sym::transmute {
+                        self.check_op(ops::Transmute);
+                        return;
+                    }
+
+                    // To preserve the current semantics, we return early, allowing all
+                    // intrinsics (except `transmute`) to pass unchecked to miri.
+                    //
+                    // FIXME: We should keep a whitelist of allowed intrinsics (or at least a
+                    // blacklist of unimplemented ones) and fail here instead.
+                    return;
+                }
+
+                if self.tcx.is_const_fn(def_id) {
+                    return;
+                }
+
+                if is_lang_panic_fn(self.tcx, def_id) {
+                    self.check_op(ops::Panic);
+                } else if let Some(feature) = self.tcx.is_unstable_const_fn(def_id) {
+                    // Exempt unstable const fns inside of macros with
+                    // `#[allow_internal_unstable]`.
+                    if !self.span.allows_unstable(feature) {
+                        self.check_op(ops::FnCallUnstable(def_id, feature));
+                    }
+                } else {
+                    self.check_op(ops::FnCallNonConst(def_id));
+                }
+
+            }
+
+            // Forbid all `Drop` terminators unless the place being dropped is a local with no
+            // projections that cannot be `NeedsDrop`.
+            | TerminatorKind::Drop { location: dropped_place, .. }
+            | TerminatorKind::DropAndReplace { location: dropped_place, .. }
+            => {
+                let mut err_span = self.span;
+
+                // Check to see if the type of this place can ever have a drop impl. If not, this
+                // `Drop` terminator is frivolous.
+                let ty_needs_drop = dropped_place
+                    .ty(self.body, self.tcx)
+                    .ty
+                    .needs_drop(self.tcx, self.param_env);
+
+                if !ty_needs_drop {
+                    return;
+                }
+
+                let needs_drop = if let Place {
+                    base: PlaceBase::Local(local),
+                    projection: box [],
+                } = *dropped_place {
+                    // Use the span where the local was declared as the span of the drop error.
+                    err_span = self.body.local_decls[local].source_info.span;
+                    self.qualifs.needs_drop.contains(local)
+                } else {
+                    true
+                };
+
+                if needs_drop {
+                    self.check_op_spanned(ops::LiveDrop, err_span);
+                }
+            }
+
+            _ => {}
+        }
+    }
+}
diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs
index 0da1f3a1aff..5037c791cb6 100644
--- a/src/librustc_mir/transform/mod.rs
+++ b/src/librustc_mir/transform/mod.rs
@@ -15,6 +15,7 @@ use syntax_pos::Span;
 pub mod add_retag;
 pub mod add_moves_for_packed_drops;
 pub mod cleanup_post_borrowck;
+pub mod check_consts;
 pub mod check_unsafety;
 pub mod simplify_branches;
 pub mod simplify;
diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs
index 7423a668f94..387540655f0 100644
--- a/src/librustc_mir/transform/qualify_consts.rs
+++ b/src/librustc_mir/transform/qualify_consts.rs
@@ -34,6 +34,7 @@ use std::usize;
 use rustc::hir::HirId;
 use crate::transform::{MirPass, MirSource};
 use super::promote_consts::{self, Candidate, TempState};
+use crate::transform::check_consts::ops::{self, NonConstOp};
 
 /// What kind of item we are in.
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -673,12 +674,18 @@ struct Checker<'a, 'tcx> {
 
     temp_promotion_state: IndexVec<Local, TempState>,
     promotion_candidates: Vec<Candidate>,
+
+    /// If `true`, do not emit errors to the user, merely collect them in `errors`.
+    suppress_errors: bool,
+    errors: Vec<(Span, String)>,
 }
 
 macro_rules! unleash_miri {
     ($this:expr) => {{
         if $this.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
-            $this.tcx.sess.span_warn($this.span, "skipping const checks");
+            if $this.mode.requires_const_checking() && !$this.suppress_errors {
+                $this.tcx.sess.span_warn($this.span, "skipping const checks");
+            }
             return;
         }
     }}
@@ -734,16 +741,19 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
             def_id,
             rpo,
             temp_promotion_state: temps,
-            promotion_candidates: vec![]
+            promotion_candidates: vec![],
+            errors: vec![],
+            suppress_errors: false,
         }
     }
 
     // FIXME(eddyb) we could split the errors into meaningful
     // categories, but enabling full miri would make that
     // slightly pointless (even with feature-gating).
-    fn not_const(&mut self) {
+    fn not_const(&mut self, op: impl NonConstOp) {
         unleash_miri!(self);
-        if self.mode.requires_const_checking() {
+        if self.mode.requires_const_checking() && !self.suppress_errors {
+            self.record_error(op);
             let mut err = struct_span_err!(
                 self.tcx.sess,
                 self.span,
@@ -761,6 +771,14 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
         }
     }
 
+    fn record_error(&mut self, op: impl NonConstOp) {
+        self.record_error_spanned(op, self.span);
+    }
+
+    fn record_error_spanned(&mut self, op: impl NonConstOp, span: Span) {
+        self.errors.push((span, format!("{:?}", op)));
+    }
+
     /// Assigns an rvalue/call qualification to the given destination.
     fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location: Location) {
         trace!("assign: {:?} <- {:?}", dest, source);
@@ -779,8 +797,10 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
                     qualifs[HasMutInterior] = false;
                     qualifs[IsNotPromotable] = true;
 
-                    if self.mode.requires_const_checking() {
+                    debug!("suppress_errors: {}", self.suppress_errors);
+                    if self.mode.requires_const_checking() && !self.suppress_errors {
                         if !self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
+                            self.record_error(ops::MutBorrow(kind));
                             if let BorrowKind::Mut { .. } = kind {
                                 let mut err = struct_span_err!(self.tcx.sess,  self.span, E0017,
                                                                "references in {}s may only refer \
@@ -925,8 +945,24 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
 
     /// Check a whole const, static initializer or const fn.
     fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
+        use crate::transform::check_consts as new_checker;
+
         debug!("const-checking {} {:?}", self.mode, self.def_id);
 
+        // FIXME: Also use the new validator when features that require it (e.g. `const_if`) are
+        // enabled.
+        let use_new_validator = self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you;
+        if use_new_validator {
+            debug!("Using dataflow-based const validator");
+        }
+
+        let item = new_checker::Item::new(self.tcx, self.def_id, self.body);
+        let mut_borrowed_locals = new_checker::validation::compute_indirectly_mutable_locals(&item);
+        let mut validator = new_checker::validation::Validator::new(&item, &mut_borrowed_locals);
+
+        validator.suppress_errors = !use_new_validator;
+        self.suppress_errors = use_new_validator;
+
         let body = self.body;
 
         let mut seen_blocks = BitSet::new_empty(body.basic_blocks().len());
@@ -935,6 +971,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
             seen_blocks.insert(bb.index());
 
             self.visit_basic_block_data(bb, &body[bb]);
+            validator.visit_basic_block_data(bb, &body[bb]);
 
             let target = match body[bb].terminator().kind {
                 TerminatorKind::Goto { target } |
@@ -970,12 +1007,42 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
                     bb = target;
                 }
                 _ => {
-                    self.not_const();
+                    self.not_const(ops::Loop);
+                    validator.check_op(ops::Loop);
                     break;
                 }
             }
         }
 
+        // The new validation pass should agree with the old when running on simple const bodies
+        // (e.g. no `if` or `loop`).
+        if !use_new_validator {
+            let mut new_errors = validator.take_errors();
+
+            // FIXME: each checker sometimes emits the same error with the same span twice in a row.
+            self.errors.dedup();
+            new_errors.dedup();
+
+            if self.errors != new_errors {
+                error!("old validator: {:?}", self.errors);
+                error!("new validator: {:?}", new_errors);
+
+                // ICE on nightly if the validators do not emit exactly the same errors.
+                // Users can supress this panic with an unstable compiler flag (hopefully after
+                // filing an issue).
+                let opts = &self.tcx.sess.opts;
+                let trigger_ice = opts.unstable_features.is_nightly_build()
+                    && !opts.debugging_opts.suppress_const_validation_back_compat_ice;
+
+                if trigger_ice {
+                    span_bug!(
+                        body.span,
+                        "{}",
+                        VALIDATOR_MISMATCH_ERR,
+                    );
+                }
+            }
+        }
 
         // Collect all the temps we need to promote.
         let mut promoted_temps = BitSet::new_empty(self.temp_promotion_state.len());
@@ -1041,7 +1108,8 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                         .get_attrs(*def_id)
                         .iter()
                         .any(|attr| attr.check_name(sym::thread_local)) {
-                    if self.mode.requires_const_checking() {
+                    if self.mode.requires_const_checking() && !self.suppress_errors {
+                        self.record_error(ops::ThreadLocalAccess);
                         span_err!(self.tcx.sess, self.span, E0625,
                                     "thread-local statics cannot be \
                                     accessed at compile-time");
@@ -1051,7 +1119,10 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
 
                 // 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() {
+                    if self.mode == Mode::Static
+                        && context.is_mutating_use()
+                        && !self.suppress_errors
+                    {
                         // 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
@@ -1065,7 +1136,8 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                 }
                 unleash_miri!(self);
 
-                if self.mode.requires_const_checking() {
+                if self.mode.requires_const_checking() && !self.suppress_errors {
+                    self.record_error(ops::StaticAccess);
                     let mut err = struct_span_err!(self.tcx.sess, self.span, E0013,
                                                     "{}s cannot refer to statics, use \
                                                     a constant instead", self.mode);
@@ -1102,14 +1174,16 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                 ProjectionElem::Deref => {
                     if context.is_mutating_use() {
                         // `not_const` errors out in const contexts
-                        self.not_const()
+                        self.not_const(ops::MutDeref)
                     }
                     let base_ty = Place::ty_from(place_base, proj_base, self.body, self.tcx).ty;
                     match self.mode {
-                        Mode::NonConstFn => {},
+                        Mode::NonConstFn => {}
+                        _ if self.suppress_errors => {}
                         _ => {
                             if let ty::RawPtr(_) = base_ty.kind {
                                 if !self.tcx.features().const_raw_ptr_deref {
+                                    self.record_error(ops::RawPtrDeref);
                                     emit_feature_err(
                                         &self.tcx.sess.parse_sess, sym::const_raw_ptr_deref,
                                         self.span, GateIssue::Language,
@@ -1133,7 +1207,10 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                         if def.is_union() {
                             match self.mode {
                                 Mode::ConstFn => {
-                                    if !self.tcx.features().const_fn_union {
+                                    if !self.tcx.features().const_fn_union
+                                        && !self.suppress_errors
+                                    {
+                                        self.record_error(ops::UnionAccess);
                                         emit_feature_err(
                                             &self.tcx.sess.parse_sess, sym::const_fn_union,
                                             self.span, GateIssue::Language,
@@ -1153,7 +1230,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                 }
 
                 ProjectionElem::Downcast(..) => {
-                    self.not_const()
+                    self.not_const(ops::Downcast)
                 }
             }
         }
@@ -1239,9 +1316,12 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                     (CastTy::Ptr(_), CastTy::Int(_)) |
                     (CastTy::FnPtr, CastTy::Int(_)) if self.mode != Mode::NonConstFn => {
                         unleash_miri!(self);
-                        if !self.tcx.features().const_raw_ptr_to_usize_cast {
+                        if !self.tcx.features().const_raw_ptr_to_usize_cast
+                            && !self.suppress_errors
+                        {
                             // in const fn and constants require the feature gate
                             // FIXME: make it unsafe inside const fn and constants
+                            self.record_error(ops::RawPtrToIntCast);
                             emit_feature_err(
                                 &self.tcx.sess.parse_sess, sym::const_raw_ptr_to_usize_cast,
                                 self.span, GateIssue::Language,
@@ -1265,8 +1345,10 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
 
                     unleash_miri!(self);
                     if self.mode.requires_const_checking() &&
-                        !self.tcx.features().const_compare_raw_pointers
+                        !self.tcx.features().const_compare_raw_pointers &&
+                        !self.suppress_errors
                     {
+                        self.record_error(ops::RawPtrComparison);
                         // require the feature gate inside constants and const fn
                         // FIXME: make it unsafe to use these operations
                         emit_feature_err(
@@ -1282,7 +1364,8 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
 
             Rvalue::NullaryOp(NullOp::Box, _) => {
                 unleash_miri!(self);
-                if self.mode.requires_const_checking() {
+                if self.mode.requires_const_checking() && !self.suppress_errors {
+                    self.record_error(ops::HeapAllocation);
                     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));
@@ -1327,9 +1410,12 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                                 // special intrinsic that can be called diretly without an intrinsic
                                 // feature gate needs a language feature gate
                                 "transmute" => {
-                                    if self.mode.requires_const_checking() {
+                                    if self.mode.requires_const_checking()
+                                        && !self.suppress_errors
+                                    {
                                         // const eval transmute calls only with the feature gate
                                         if !self.tcx.features().const_transmute {
+                                            self.record_error(ops::Transmute);
                                             emit_feature_err(
                                                 &self.tcx.sess.parse_sess, sym::const_transmute,
                                                 self.span, GateIssue::Language,
@@ -1357,7 +1443,10 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                                     .opts
                                     .debugging_opts
                                     .unleash_the_miri_inside_of_you;
-                                if self.tcx.is_const_fn(def_id) || unleash_miri {
+                                if self.tcx.is_const_fn(def_id)
+                                    || unleash_miri
+                                    || self.suppress_errors
+                                {
                                     // stable const fns or unstable const fns
                                     // with their feature gate active
                                     // FIXME(eddyb) move stability checks from `is_const_fn` here.
@@ -1368,6 +1457,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                                     // since the macro is marked with the attribute.
                                     if !self.tcx.features().const_panic {
                                         // Don't allow panics in constants without the feature gate.
+                                        self.record_error(ops::Panic);
                                         emit_feature_err(
                                             &self.tcx.sess.parse_sess,
                                             sym::const_panic,
@@ -1382,6 +1472,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                                     // functions without the feature gate active in this crate in
                                     // order to report a better error message than the one below.
                                     if !self.span.allows_unstable(feature) {
+                                        self.record_error(ops::FnCallUnstable(def_id, feature));
                                         let mut err = self.tcx.sess.struct_span_err(self.span,
                                             &format!("`{}` is not yet stable as a const fn",
                                                     self.tcx.def_path_str(def_id)));
@@ -1394,6 +1485,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                                         err.emit();
                                     }
                                 } else {
+                                    self.record_error(ops::FnCallNonConst(def_id));
                                     let mut err = struct_span_err!(
                                         self.tcx.sess,
                                         self.span,
@@ -1409,13 +1501,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                     }
                 }
                 ty::FnPtr(_) => {
-                    let unleash_miri = self
-                        .tcx
-                        .sess
-                        .opts
-                        .debugging_opts
-                        .unleash_the_miri_inside_of_you;
-                    if self.mode.requires_const_checking() && !unleash_miri {
+                    unleash_miri!(self);
+                    if self.mode.requires_const_checking() && !self.suppress_errors {
+                        self.record_error(ops::FnCallIndirect);
                         let mut err = self.tcx.sess.struct_span_err(
                             self.span,
                             "function pointers are not allowed in const fn"
@@ -1424,7 +1512,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                     }
                 }
                 _ => {
-                    self.not_const();
+                    self.not_const(ops::FnCallOther);
                 }
             }
 
@@ -1482,7 +1570,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
             }
 
             // Deny *any* live drops anywhere other than functions.
-            if self.mode.requires_const_checking() {
+            if self.mode.requires_const_checking() && !self.suppress_errors {
                 unleash_miri!(self);
                 // HACK(eddyb): emulate a bit of dataflow analysis,
                 // conservatively, that drop elaboration will do.
@@ -1503,6 +1591,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                     // Double-check the type being dropped, to minimize false positives.
                     let ty = place.ty(self.body, self.tcx).ty;
                     if ty.needs_drop(self.tcx, self.param_env) {
+                        self.record_error_spanned(ops::LiveDrop, span);
                         struct_span_err!(self.tcx.sess, span, E0493,
                                          "destructors cannot be evaluated at compile-time")
                             .span_label(span, format!("{}s cannot evaluate destructors",
@@ -1547,7 +1636,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
                 self.super_statement(statement, location);
             }
             StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _) => {
-                self.not_const();
+                self.not_const(ops::IfOrMatch);
             }
             // FIXME(eddyb) should these really do nothing?
             StatementKind::FakeRead(..) |
@@ -1775,3 +1864,7 @@ fn args_required_const(tcx: TyCtxt<'_>, def_id: DefId) -> Option<FxHashSet<usize
     }
     Some(ret)
 }
+
+const VALIDATOR_MISMATCH_ERR: &str =
+    r"Disagreement between legacy and dataflow-based const validators.
+    After filing an issue, use `-Zsuppress-const-validation-back-compat-ice` to compile your code.";
diff --git a/src/test/ui/consts/const-eval/const_fn_ptr.stderr b/src/test/ui/consts/const-eval/const_fn_ptr.stderr
index 41452ee59eb..2fbb1932244 100644
--- a/src/test/ui/consts/const-eval/const_fn_ptr.stderr
+++ b/src/test/ui/consts/const-eval/const_fn_ptr.stderr
@@ -1,146 +1,20 @@
 warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:25:5
+  --> $DIR/const_fn_ptr.rs:12:5
    |
-LL |     assert_eq!(Y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:25:5
-   |
-LL |     assert_eq!(Y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:25:5
-   |
-LL |     assert_eq!(Y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:27:5
-   |
-LL |     assert_eq!(y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:27:5
-   |
-LL |     assert_eq!(y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:27:5
-   |
-LL |     assert_eq!(y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:29:5
-   |
-LL |     assert_eq!(y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:29:5
-   |
-LL |     assert_eq!(y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
+LL |     X(x)
+   |     ^^^^
 
 warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:29:5
-   |
-LL |     assert_eq!(y, 4);
-   |     ^^^^^^^^^^^^^^^^^
+  --> $DIR/const_fn_ptr.rs:16:5
    |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
+LL |     X_const(x)
+   |     ^^^^^^^^^^
 
 warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:32:5
-   |
-LL |     assert_eq!(Z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:32:5
-   |
-LL |     assert_eq!(Z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:32:5
-   |
-LL |     assert_eq!(Z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:34:5
-   |
-LL |     assert_eq!(z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:34:5
-   |
-LL |     assert_eq!(z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:34:5
-   |
-LL |     assert_eq!(z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:36:5
-   |
-LL |     assert_eq!(z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:36:5
-   |
-LL |     assert_eq!(z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr.rs:36:5
-   |
-LL |     assert_eq!(z, 4);
-   |     ^^^^^^^^^^^^^^^^^
+  --> $DIR/const_fn_ptr.rs:20:5
    |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
+LL |     x(y)
+   |     ^^^^
 
 warning: constant `X_const` should have an upper case name
   --> $DIR/const_fn_ptr.rs:9:7
diff --git a/src/test/ui/consts/const-eval/const_fn_ptr_fail.rs b/src/test/ui/consts/const-eval/const_fn_ptr_fail.rs
index 14bd6558e7f..90d3cba07a5 100644
--- a/src/test/ui/consts/const-eval/const_fn_ptr_fail.rs
+++ b/src/test/ui/consts/const-eval/const_fn_ptr_fail.rs
@@ -8,6 +8,7 @@ const X: fn(usize) -> usize = double;
 
 const fn bar(x: usize) -> usize {
     X(x) // FIXME: this should error someday
+    //~^ WARN: skipping const checks
 }
 
 fn main() {}
diff --git a/src/test/ui/consts/const-eval/const_fn_ptr_fail.stderr b/src/test/ui/consts/const-eval/const_fn_ptr_fail.stderr
new file mode 100644
index 00000000000..e80f363ff8b
--- /dev/null
+++ b/src/test/ui/consts/const-eval/const_fn_ptr_fail.stderr
@@ -0,0 +1,6 @@
+warning: skipping const checks
+  --> $DIR/const_fn_ptr_fail.rs:10:5
+   |
+LL |     X(x) // FIXME: this should error someday
+   |     ^^^^
+
diff --git a/src/test/ui/consts/const-eval/const_fn_ptr_fail2.rs b/src/test/ui/consts/const-eval/const_fn_ptr_fail2.rs
index 74c60f9a2a5..b300119509c 100644
--- a/src/test/ui/consts/const-eval/const_fn_ptr_fail2.rs
+++ b/src/test/ui/consts/const-eval/const_fn_ptr_fail2.rs
@@ -6,7 +6,7 @@ fn double(x: usize) -> usize { x * 2 }
 const X: fn(usize) -> usize = double;
 
 const fn bar(x: fn(usize) -> usize, y: usize) -> usize {
-    x(y)
+    x(y) //~ WARN skipping const checks
 }
 
 const Y: usize = bar(X, 2); // FIXME: should fail to typeck someday
@@ -15,12 +15,6 @@ const Z: usize = bar(double, 2); // FIXME: should fail to typeck someday
 fn main() {
     assert_eq!(Y, 4);
     //~^ ERROR evaluation of constant expression failed
-    //~^^ WARN skipping const checks
-    //~^^^ WARN skipping const checks
-    //~^^^^ WARN skipping const checks
     assert_eq!(Z, 4);
     //~^ ERROR evaluation of constant expression failed
-    //~^^ WARN skipping const checks
-    //~^^^ WARN skipping const checks
-    //~^^^^ WARN skipping const checks
 }
diff --git a/src/test/ui/consts/const-eval/const_fn_ptr_fail2.stderr b/src/test/ui/consts/const-eval/const_fn_ptr_fail2.stderr
index 611cc5313c0..9d74d3b0bf2 100644
--- a/src/test/ui/consts/const-eval/const_fn_ptr_fail2.stderr
+++ b/src/test/ui/consts/const-eval/const_fn_ptr_fail2.stderr
@@ -1,50 +1,8 @@
 warning: skipping const checks
-  --> $DIR/const_fn_ptr_fail2.rs:16:5
-   |
-LL |     assert_eq!(Y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr_fail2.rs:16:5
-   |
-LL |     assert_eq!(Y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr_fail2.rs:16:5
-   |
-LL |     assert_eq!(Y, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr_fail2.rs:21:5
-   |
-LL |     assert_eq!(Z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr_fail2.rs:21:5
-   |
-LL |     assert_eq!(Z, 4);
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/const_fn_ptr_fail2.rs:21:5
-   |
-LL |     assert_eq!(Z, 4);
-   |     ^^^^^^^^^^^^^^^^^
+  --> $DIR/const_fn_ptr_fail2.rs:9:5
    |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
+LL |     x(y)
+   |     ^^^^
 
 error[E0080]: evaluation of constant expression failed
   --> $DIR/const_fn_ptr_fail2.rs:16:5
@@ -57,7 +15,7 @@ LL |     assert_eq!(Y, 4);
    = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
 
 error[E0080]: evaluation of constant expression failed
-  --> $DIR/const_fn_ptr_fail2.rs:21:5
+  --> $DIR/const_fn_ptr_fail2.rs:18:5
    |
 LL |     assert_eq!(Z, 4);
    |     ^^^^^^^^^^^-^^^^^
diff --git a/src/test/ui/consts/const-if.rs b/src/test/ui/consts/const-if.rs
new file mode 100644
index 00000000000..9bb5bcc499e
--- /dev/null
+++ b/src/test/ui/consts/const-if.rs
@@ -0,0 +1,5 @@
+const _X: i32 = if true { 5 } else { 6 };
+//~^ ERROR constant contains unimplemented expression type
+//~| ERROR constant contains unimplemented expression type
+
+fn main() {}
diff --git a/src/test/ui/consts/const-if.stderr b/src/test/ui/consts/const-if.stderr
new file mode 100644
index 00000000000..655fcdae587
--- /dev/null
+++ b/src/test/ui/consts/const-if.stderr
@@ -0,0 +1,15 @@
+error[E0019]: constant contains unimplemented expression type
+  --> $DIR/const-if.rs:1:20
+   |
+LL | const _X: i32 = if true { 5 } else { 6 };
+   |                    ^^^^
+
+error[E0019]: constant contains unimplemented expression type
+  --> $DIR/const-if.rs:1:17
+   |
+LL | const _X: i32 = if true { 5 } else { 6 };
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0019`.
diff --git a/src/test/ui/consts/const-multi-ref.rs b/src/test/ui/consts/const-multi-ref.rs
new file mode 100644
index 00000000000..498e99e668b
--- /dev/null
+++ b/src/test/ui/consts/const-multi-ref.rs
@@ -0,0 +1,11 @@
+const _X: i32 = {
+    let mut a = 5;
+    let p = &mut a;      //~ ERROR references in constants may only refer to immutable values
+
+    let reborrow = {p};  //~ ERROR references in constants may only refer to immutable values
+    let pp = &reborrow;
+    let ppp = &pp;
+    ***ppp
+};
+
+fn main() {}
diff --git a/src/test/ui/consts/const-multi-ref.stderr b/src/test/ui/consts/const-multi-ref.stderr
new file mode 100644
index 00000000000..9e525ef9aac
--- /dev/null
+++ b/src/test/ui/consts/const-multi-ref.stderr
@@ -0,0 +1,15 @@
+error[E0017]: references in constants may only refer to immutable values
+  --> $DIR/const-multi-ref.rs:3:13
+   |
+LL |     let p = &mut a;
+   |             ^^^^^^ constants require immutable values
+
+error[E0017]: references in constants may only refer to immutable values
+  --> $DIR/const-multi-ref.rs:5:21
+   |
+LL |     let reborrow = {p};
+   |                     ^ constants require immutable values
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0017`.
diff --git a/src/test/ui/consts/miri_unleashed/assoc_const.stderr b/src/test/ui/consts/miri_unleashed/assoc_const.stderr
index e814303923e..6a6cb343f17 100644
--- a/src/test/ui/consts/miri_unleashed/assoc_const.stderr
+++ b/src/test/ui/consts/miri_unleashed/assoc_const.stderr
@@ -1,8 +1,8 @@
 warning: skipping const checks
-  --> $DIR/assoc_const.rs:12:31
+  --> $DIR/assoc_const.rs:12:20
    |
 LL |     const F: u32 = (U::X, 42).1;
-   |                               ^
+   |                    ^^^^^^^^^^
 
 error[E0080]: erroneous constant used
   --> $DIR/assoc_const.rs:29:13
diff --git a/src/test/ui/consts/miri_unleashed/enum_discriminants.stderr b/src/test/ui/consts/miri_unleashed/enum_discriminants.stderr
index 8ca81ad22b7..df366ba22e4 100644
--- a/src/test/ui/consts/miri_unleashed/enum_discriminants.stderr
+++ b/src/test/ui/consts/miri_unleashed/enum_discriminants.stderr
@@ -22,51 +22,3 @@ warning: skipping const checks
 LL |     if let E1::V2 { .. } = (E1::V1 { f: true }) {
    |            ^^^^^^^^^^^^^
 
-warning: skipping const checks
-  --> $DIR/enum_discriminants.rs:108:5
-   |
-LL |     assert_eq!(OVERFLOW, 0);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/enum_discriminants.rs:108:5
-   |
-LL |     assert_eq!(OVERFLOW, 0);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/enum_discriminants.rs:108:5
-   |
-LL |     assert_eq!(OVERFLOW, 0);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/enum_discriminants.rs:109:5
-   |
-LL |     assert_eq!(MORE_OVERFLOW, 0);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/enum_discriminants.rs:109:5
-   |
-LL |     assert_eq!(MORE_OVERFLOW, 0);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
-warning: skipping const checks
-  --> $DIR/enum_discriminants.rs:109:5
-   |
-LL |     assert_eq!(MORE_OVERFLOW, 0);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this warning originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
-
diff --git a/src/test/ui/consts/miri_unleashed/mutable_const.rs b/src/test/ui/consts/miri_unleashed/mutable_const.rs
index b476e04529a..44b40849467 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_const.rs
+++ b/src/test/ui/consts/miri_unleashed/mutable_const.rs
@@ -7,6 +7,7 @@ use std::cell::UnsafeCell;
 
 // make sure we do not just intern this as mutable
 const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
+//~^ WARN: skipping const checks
 
 const MUTATING_BEHIND_RAW: () = {
     // Test that `MUTABLE_BEHIND_RAW` is actually immutable, by doing this at const time.
diff --git a/src/test/ui/consts/miri_unleashed/mutable_const.stderr b/src/test/ui/consts/miri_unleashed/mutable_const.stderr
index 507d4823a11..757f0ffb59f 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_const.stderr
+++ b/src/test/ui/consts/miri_unleashed/mutable_const.stderr
@@ -1,11 +1,17 @@
 warning: skipping const checks
-  --> $DIR/mutable_const.rs:14:9
+  --> $DIR/mutable_const.rs:9:38
+   |
+LL | const MUTABLE_BEHIND_RAW: *mut i32 = &UnsafeCell::new(42) as *const _ as *mut _;
+   |                                      ^^^^^^^^^^^^^^^^^^^^
+
+warning: skipping const checks
+  --> $DIR/mutable_const.rs:15:9
    |
 LL |         *MUTABLE_BEHIND_RAW = 99
    |         ^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: any use of this value will cause an error
-  --> $DIR/mutable_const.rs:14:9
+  --> $DIR/mutable_const.rs:15:9
    |
 LL | / const MUTATING_BEHIND_RAW: () = {
 LL | |     // Test that `MUTABLE_BEHIND_RAW` is actually immutable, by doing this at const time.
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references.rs b/src/test/ui/consts/miri_unleashed/mutable_references.rs
index 5f9888053a1..59dafcbf4d5 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_references.rs
+++ b/src/test/ui/consts/miri_unleashed/mutable_references.rs
@@ -6,12 +6,15 @@ use std::cell::UnsafeCell;
 // a test demonstrating what things we could allow with a smarter const qualification
 
 static FOO: &&mut u32 = &&mut 42;
+//~^ WARN: skipping const checks
 
 static BAR: &mut () = &mut ();
+//~^ WARN: skipping const checks
 
 struct Foo<T>(T);
 
 static BOO: &mut Foo<()> = &mut Foo(());
+//~^ WARN: skipping const checks
 
 struct Meh {
     x: &'static UnsafeCell<i32>,
@@ -21,15 +24,15 @@ unsafe impl Sync for Meh {}
 
 static MEH: Meh = Meh {
     x: &UnsafeCell::new(42),
+    //~^ WARN: skipping const checks
 };
 
 static OH_YES: &mut i32 = &mut 42;
+//~^ WARN: skipping const checks
 
 fn main() {
     unsafe {
-        *MEH.x.get() = 99; //~ WARN skipping const checks
-        //~^ WARN skipping const checks
+        *MEH.x.get() = 99;
     }
     *OH_YES = 99; //~ ERROR cannot assign to `*OH_YES`, as `OH_YES` is an immutable static item
-    //~^ WARN skipping const checks
 }
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references.stderr b/src/test/ui/consts/miri_unleashed/mutable_references.stderr
index b870aca640a..e8a867307ce 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_references.stderr
+++ b/src/test/ui/consts/miri_unleashed/mutable_references.stderr
@@ -1,23 +1,35 @@
 warning: skipping const checks
-  --> $DIR/mutable_references.rs:30:10
+  --> $DIR/mutable_references.rs:8:26
    |
-LL |         *MEH.x.get() = 99;
-   |          ^^^^^
+LL | static FOO: &&mut u32 = &&mut 42;
+   |                          ^^^^^^^
 
 warning: skipping const checks
-  --> $DIR/mutable_references.rs:30:9
+  --> $DIR/mutable_references.rs:11:23
    |
-LL |         *MEH.x.get() = 99;
-   |         ^^^^^^^^^^^^^^^^^
+LL | static BAR: &mut () = &mut ();
+   |                       ^^^^^^^
 
 warning: skipping const checks
-  --> $DIR/mutable_references.rs:33:5
+  --> $DIR/mutable_references.rs:16:28
    |
-LL |     *OH_YES = 99;
-   |     ^^^^^^^^^^^^
+LL | static BOO: &mut Foo<()> = &mut Foo(());
+   |                            ^^^^^^^^^^^^
+
+warning: skipping const checks
+  --> $DIR/mutable_references.rs:26:8
+   |
+LL |     x: &UnsafeCell::new(42),
+   |        ^^^^^^^^^^^^^^^^^^^^
+
+warning: skipping const checks
+  --> $DIR/mutable_references.rs:30:27
+   |
+LL | static OH_YES: &mut i32 = &mut 42;
+   |                           ^^^^^^^
 
 error[E0594]: cannot assign to `*OH_YES`, as `OH_YES` is an immutable static item
-  --> $DIR/mutable_references.rs:33:5
+  --> $DIR/mutable_references.rs:37:5
    |
 LL |     *OH_YES = 99;
    |     ^^^^^^^^^^^^ cannot assign
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references_ice.rs b/src/test/ui/consts/miri_unleashed/mutable_references_ice.rs
index 4fcd89a74db..635cad81c97 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_references_ice.rs
+++ b/src/test/ui/consts/miri_unleashed/mutable_references_ice.rs
@@ -19,11 +19,11 @@ unsafe impl Sync for Meh {}
 
 // the following will never be ok!
 const MUH: Meh = Meh {
-    x: &UnsafeCell::new(42),
+    x: &UnsafeCell::new(42), //~ WARN: skipping const checks
 };
 
 fn main() {
     unsafe {
-        *MUH.x.get() = 99; //~ WARN skipping const checks
+        *MUH.x.get() = 99;
     }
 }
diff --git a/src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr b/src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr
index 28cf3537d60..c148842bcbc 100644
--- a/src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr
+++ b/src/test/ui/consts/miri_unleashed/mutable_references_ice.stderr
@@ -1,8 +1,8 @@
 warning: skipping const checks
-  --> $DIR/mutable_references_ice.rs:27:9
+  --> $DIR/mutable_references_ice.rs:22:8
    |
-LL |         *MUH.x.get() = 99;
-   |         ^^^^^^^^^^^^^^^^^
+LL |     x: &UnsafeCell::new(42),
+   |        ^^^^^^^^^^^^^^^^^^^^
 
 thread 'rustc' panicked at 'assertion failed: `(left != right)`
   left: `Const`,