]> git.lizzy.rs Git - rust.git/commitdiff
Validate the MIR of all optimizations in the mir-opt directory
authorOliver Scherer <github35764891676564198441@oli-obk.de>
Fri, 14 Aug 2020 16:01:14 +0000 (18:01 +0200)
committerOliver Scherer <github35764891676564198441@oli-obk.de>
Tue, 18 Aug 2020 11:01:00 +0000 (13:01 +0200)
20 files changed:
src/librustc_middle/mir/mod.rs
src/librustc_middle/query/mod.rs
src/librustc_middle/ty/query/mod.rs
src/librustc_mir/borrow_check/mod.rs
src/librustc_mir/interpret/intern.rs
src/librustc_mir/transform/const_prop.rs
src/librustc_mir/transform/generator.rs
src/librustc_mir/transform/mod.rs
src/librustc_mir/transform/validate.rs
src/test/mir-opt/const_prop/checked_add.main.ConstProp.diff
src/test/mir-opt/const_prop/indirect.main.ConstProp.diff
src/test/mir-opt/const_prop/issue_67019.main.ConstProp.diff
src/test/mir-opt/const_prop/mutable_variable_aggregate.main.ConstProp.diff
src/test/mir-opt/const_prop/optimizes_into_variable.main.ConstProp.diff.32bit
src/test/mir-opt/const_prop/optimizes_into_variable.main.ConstProp.diff.64bit
src/test/mir-opt/const_prop/return_place.add.ConstProp.diff
src/test/mir-opt/const_prop/tuple_literal_propagation.main.ConstProp.diff
src/test/mir-opt/fn-ptr-shim.rs
src/test/mir-opt/generator_tiny.main-{{closure}}.generator_resume.0.mir
src/tools/compiletest/src/runtest.rs

index 1faed87f8ff17026327f88f233ff55334b16b83a..22828994934819eb0b60a15b39bd8ba54afc72b6 100644 (file)
@@ -73,15 +73,35 @@ fn local_decls(&self) -> &LocalDecls<'tcx> {
 
 /// The various "big phases" that MIR goes through.
 ///
+/// These phases all describe dialects of MIR. Since all MIR uses the same datastructures, the
+/// dialects forbid certain variants or values in certain phases.
+///
+/// Note: Each phase's validation checks all invariants of the *previous* phases' dialects. A phase
+/// that changes the dialect documents what invariants must be uphelpd *after* that phase finishes.
+///
 /// Warning: ordering of variants is significant.
 #[derive(Copy, Clone, TyEncodable, TyDecodable, Debug, PartialEq, Eq, PartialOrd, Ord)]
 #[derive(HashStable)]
 pub enum MirPhase {
     Build = 0,
+    // FIXME: it's unclear whether we still need this phase (and its corresponding query).
+    // We used to have this for pre-miri MIR based const eval.
     Const = 1,
-    Validated = 2,
-    DropElab = 3,
-    Optimized = 4,
+    /// This phase checks the MIR for promotable elements and takes them out of the main MIR body
+    /// by creating a new MIR body per promoted element. After this phase (and thus the termination
+    /// of the `mir_promoted` query), these promoted elements are available in the `promoted_mir`
+    /// query.
+    ConstPromotion = 2,
+    /// After this phase
+    /// * the only `AggregateKind`s allowed are `Array` and `Generator`,
+    /// * `DropAndReplace` is gone for good
+    /// * `Drop` now uses explicit drop flags visible in the MIR and reaching a `Drop` terminator
+    ///   means that the auto-generated drop glue will be invoked.
+    DropLowering = 3,
+    /// After this phase, generators are explicit state machines (no more `Yield`).
+    /// `AggregateKind::Generator` is gone for good.
+    GeneratorLowering = 4,
+    Optimized = 5,
 }
 
 impl MirPhase {
index d874edf627472362fd7740a067c0c278405a3a60..b22bf5308690fba892aa0048ae5ae16ab878ad13 100644 (file)
@@ -247,7 +247,7 @@ fn describe_as_module(def_id: LocalDefId, tcx: TyCtxt<'_>) -> String {
             desc { |tcx| "elaborating drops for `{}`", tcx.def_path_str(key.did.to_def_id()) }
         }
 
-        query mir_validated(key: ty::WithOptConstParam<LocalDefId>) ->
+        query mir_promoted(key: ty::WithOptConstParam<LocalDefId>) ->
             (
                 &'tcx Steal<mir::Body<'tcx>>,
                 &'tcx Steal<IndexVec<mir::Promoted, mir::Body<'tcx>>>
@@ -281,6 +281,8 @@ fn describe_as_module(def_id: LocalDefId, tcx: TyCtxt<'_>) -> String {
             cache_on_disk_if { key.is_local() }
         }
 
+        /// The `DefId` is the `DefId` of the containing MIR body. Promoteds to not have their own
+        /// `DefId`.
         query promoted_mir(key: DefId) -> &'tcx IndexVec<mir::Promoted, mir::Body<'tcx>> {
             desc { |tcx| "optimizing promoted MIR for `{}`", tcx.def_path_str(key) }
             cache_on_disk_if { key.is_local() }
index f220c4fb0728f0858472875c1836c4526bb2dcec..ee9b203b151804390860599744f6fafdd88d676c 100644 (file)
 /// `DefPathHash` in the current codebase to the corresponding `DefId`, we have
 /// everything we need to re-run the query.
 ///
-/// Take the `mir_validated` query as an example. Like many other queries, it
+/// Take the `mir_promoted` query as an example. Like many other queries, it
 /// just has a single parameter: the `DefId` of the item it will compute the
 /// validated MIR for. Now, when we call `force_from_dep_node()` on a `DepNode`
 /// with kind `MirValidated`, we know that the GUID/fingerprint of the `DepNode`
index f7031b2a598484963d763b63722119aaddcf141d..b2a09f627517f5aa3eaa6d37a18e057e852d35fc 100644 (file)
@@ -106,7 +106,7 @@ fn mir_borrowck<'tcx>(
     tcx: TyCtxt<'tcx>,
     def: ty::WithOptConstParam<LocalDefId>,
 ) -> &'tcx BorrowCheckResult<'tcx> {
-    let (input_body, promoted) = tcx.mir_validated(def);
+    let (input_body, promoted) = tcx.mir_promoted(def);
     debug!("run query mir_borrowck: {}", tcx.def_path_str(def.did.to_def_id()));
 
     let opt_closure_req = tcx.infer_ctxt().enter(|infcx| {
index 9959c38e5c7382ba4b43103d270462a6ee5110b2..6ba5a18da05e993c7e5953db3e120abadc7d501a 100644 (file)
@@ -7,7 +7,8 @@
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_hir as hir;
 use rustc_middle::mir::interpret::InterpResult;
-use rustc_middle::ty::{self, query::TyCtxtAt, Ty};
+use rustc_middle::ty::{self, layout::TyAndLayout, query::TyCtxtAt, Ty};
+use rustc_target::abi::Size;
 
 use rustc_ast::Mutability;
 
@@ -430,3 +431,25 @@ pub fn intern_const_alloc_recursive<M: CompileTimeMachine<'mir, 'tcx>>(
         }
     }
 }
+
+impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
+    /// A helper function that allocates memory for the layout given and gives you access to mutate
+    /// it. Once your own mutation code is done, the backing `Allocation` is removed from the
+    /// current `Memory` and returned.
+    pub(crate) fn with_temp_alloc(
+        &mut self,
+        layout: TyAndLayout<'tcx>,
+        f: impl FnOnce(
+            &mut InterpCx<'mir, 'tcx, M>,
+            MPlaceTy<'tcx, M::PointerTag>,
+        ) -> InterpResult<'tcx, ()>,
+    ) -> InterpResult<'tcx, &'tcx Allocation> {
+        let dest = self.allocate(layout, MemoryKind::Stack);
+        f(self, dest)?;
+        let ptr = dest.ptr.assert_ptr();
+        assert_eq!(ptr.offset, Size::ZERO);
+        let mut alloc = self.memory.alloc_map.remove(&ptr.alloc_id).unwrap().1;
+        alloc.mutability = Mutability::Not;
+        Ok(self.tcx.intern_const_alloc(alloc))
+    }
+}
index be23fe7247e20cfb948ab1922b3cb28df91f5218..40b1bfc7a95973b3d1134b7aa90b2c670c229283 100644 (file)
@@ -14,9 +14,9 @@
     MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
 };
 use rustc_middle::mir::{
-    AggregateKind, AssertKind, BasicBlock, BinOp, Body, ClearCrossCrate, Constant, Local,
-    LocalDecl, LocalKind, Location, Operand, Place, Rvalue, SourceInfo, SourceScope,
-    SourceScopeData, Statement, StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
+    AssertKind, BasicBlock, BinOp, Body, ClearCrossCrate, Constant, Local, LocalDecl, LocalKind,
+    Location, Operand, Place, Rvalue, SourceInfo, SourceScope, SourceScopeData, Statement,
+    StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
 };
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutError, TyAndLayout};
 use rustc_middle::ty::subst::{InternalSubsts, Subst};
@@ -28,9 +28,9 @@
 
 use crate::const_eval::ConstEvalErr;
 use crate::interpret::{
-    self, compile_time_machine, truncate, AllocId, Allocation, Frame, ImmTy, Immediate, InterpCx,
-    LocalState, LocalValue, MemPlace, Memory, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy,
-    Pointer, ScalarMaybeUninit, StackPopCleanup,
+    self, compile_time_machine, truncate, AllocId, Allocation, ConstValue, Frame, ImmTy, Immediate,
+    InterpCx, LocalState, LocalValue, MemPlace, Memory, MemoryKind, OpTy, Operand as InterpOperand,
+    PlaceTy, Pointer, ScalarMaybeUninit, StackPopCleanup,
 };
 use crate::transform::{MirPass, MirSource};
 
@@ -824,19 +824,18 @@ fn replace_with_const(
                     ));
                 }
                 Immediate::ScalarPair(
-                    ScalarMaybeUninit::Scalar(one),
-                    ScalarMaybeUninit::Scalar(two),
+                    ScalarMaybeUninit::Scalar(_),
+                    ScalarMaybeUninit::Scalar(_),
                 ) => {
-                    // Found a value represented as a pair. For now only do cont-prop if type of
-                    // Rvalue is also a pair with two scalars. The more general case is more
-                    // complicated to implement so we'll do it later.
-                    // FIXME: implement the general case stated above ^.
-                    let ty = &value.layout.ty.kind;
+                    // Found a value represented as a pair. For now only do const-prop if the type
+                    // of `rvalue` is also a tuple with two scalars.
+                    // FIXME: enable the general case stated above ^.
+                    let ty = &value.layout.ty;
                     // Only do it for tuples
-                    if let ty::Tuple(substs) = ty {
+                    if let ty::Tuple(substs) = ty.kind {
                         // Only do it if tuple is also a pair with two scalars
                         if substs.len() == 2 {
-                            let opt_ty1_ty2 = self.use_ecx(|this| {
+                            let alloc = self.use_ecx(|this| {
                                 let ty1 = substs[0].expect_ty();
                                 let ty2 = substs[1].expect_ty();
                                 let ty_is_scalar = |ty| {
@@ -844,24 +843,36 @@ fn replace_with_const(
                                         == Some(true)
                                 };
                                 if ty_is_scalar(ty1) && ty_is_scalar(ty2) {
-                                    Ok(Some((ty1, ty2)))
+                                    let alloc = this
+                                        .ecx
+                                        .with_temp_alloc(value.layout, |ecx, dest| {
+                                            ecx.write_immediate_to_mplace(*imm, dest)
+                                        })
+                                        .unwrap();
+                                    Ok(Some(alloc))
                                 } else {
                                     Ok(None)
                                 }
                             });
 
-                            if let Some(Some((ty1, ty2))) = opt_ty1_ty2 {
-                                *rval = Rvalue::Aggregate(
-                                    Box::new(AggregateKind::Tuple),
-                                    vec![
-                                        self.operand_from_scalar(one, ty1, source_info.span),
-                                        self.operand_from_scalar(two, ty2, source_info.span),
-                                    ],
-                                );
+                            if let Some(Some(alloc)) = alloc {
+                                *rval = Rvalue::Use(Operand::Constant(Box::new(Constant {
+                                    span: source_info.span,
+                                    user_ty: None,
+                                    literal: self.ecx.tcx.mk_const(ty::Const {
+                                        ty,
+                                        val: ty::ConstKind::Value(ConstValue::ByRef {
+                                            alloc,
+                                            offset: Size::ZERO,
+                                        }),
+                                    }),
+                                })));
                             }
                         }
                     }
                 }
+                // Scalars or scalar pairs that contain undef values are assumed to not have
+                // successfully evaluated and are thus not propagated.
                 _ => {}
             }
         }
index 8618cc126c563e336919ef366d306752ff1282ee..faf1adf03971bccff880b6508deefc6965027d5f 100644 (file)
@@ -57,6 +57,7 @@
 use crate::transform::simplify;
 use crate::transform::{MirPass, MirSource};
 use crate::util::dump_mir;
+use crate::util::expand_aggregate;
 use crate::util::storage;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_hir as hir;
@@ -66,7 +67,7 @@
 use rustc_index::vec::{Idx, IndexVec};
 use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
 use rustc_middle::mir::*;
-use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::subst::{Subst, SubstsRef};
 use rustc_middle::ty::GeneratorSubsts;
 use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
 use rustc_target::abi::VariantIdx;
@@ -236,10 +237,28 @@ struct TransformVisitor<'tcx> {
 }
 
 impl TransformVisitor<'tcx> {
-    // Make a GeneratorState rvalue
-    fn make_state(&self, idx: VariantIdx, val: Operand<'tcx>) -> Rvalue<'tcx> {
-        let adt = AggregateKind::Adt(self.state_adt_ref, idx, self.state_substs, None, None);
-        Rvalue::Aggregate(box adt, vec![val])
+    // Make a GeneratorState variant assignment. `core::ops::GeneratorState` only has single
+    // element tuple variants, so we can just write to the downcasted first field and then set the
+    // discriminant to the appropriate variant.
+    fn make_state(
+        &self,
+        idx: VariantIdx,
+        val: Operand<'tcx>,
+        source_info: SourceInfo,
+    ) -> impl Iterator<Item = Statement<'tcx>> {
+        let kind = AggregateKind::Adt(self.state_adt_ref, idx, self.state_substs, None, None);
+        assert_eq!(self.state_adt_ref.variants[idx].fields.len(), 1);
+        let ty = self
+            .tcx
+            .type_of(self.state_adt_ref.variants[idx].fields[0].did)
+            .subst(self.tcx, self.state_substs);
+        expand_aggregate(
+            Place::return_place(),
+            std::iter::once((val, ty)),
+            kind,
+            source_info,
+            self.tcx,
+        )
     }
 
     // Create a Place referencing a generator struct field
@@ -325,13 +344,7 @@ fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockDat
         if let Some((state_idx, resume, v, drop)) = ret_val {
             let source_info = data.terminator().source_info;
             // We must assign the value first in case it gets declared dead below
-            data.statements.push(Statement {
-                source_info,
-                kind: StatementKind::Assign(box (
-                    Place::return_place(),
-                    self.make_state(state_idx, v),
-                )),
-            });
+            data.statements.extend(self.make_state(state_idx, v, source_info));
             let state = if let Some((resume, resume_arg)) = resume {
                 // Yield
                 let state = 3 + self.suspension_points.len();
index 4f26f3bb45973b178c36fc8821c9bc24025221b8..3f13dc0476aba28be700fc046ba675f102894925 100644 (file)
@@ -60,7 +60,7 @@ pub(crate) fn provide(providers: &mut Providers) {
         mir_const_qualif_const_arg: |tcx, (did, param_did)| {
             mir_const_qualif(tcx, ty::WithOptConstParam { did, const_param_did: Some(param_did) })
         },
-        mir_validated,
+        mir_promoted,
         mir_drops_elaborated_and_const_checked,
         optimized_mir,
         optimized_mir_of_const_arg,
@@ -189,7 +189,7 @@ pub fn run_passes(
     }
 
     if validate {
-        validate::Validator { when: format!("input to phase {:?}", mir_phase) }
+        validate::Validator { when: format!("input to phase {:?}", mir_phase), mir_phase }
             .run_pass(tcx, source, body);
     }
 
@@ -210,8 +210,11 @@ pub fn run_passes(
         run_hooks(body, index, true);
 
         if validate {
-            validate::Validator { when: format!("after {} in phase {:?}", pass.name(), mir_phase) }
-                .run_pass(tcx, source, body);
+            validate::Validator {
+                when: format!("after {} in phase {:?}", pass.name(), mir_phase),
+                mir_phase,
+            }
+            .run_pass(tcx, source, body);
         }
 
         index += 1;
@@ -226,7 +229,7 @@ pub fn run_passes(
     body.phase = mir_phase;
 
     if mir_phase == MirPhase::Optimized {
-        validate::Validator { when: format!("end of phase {:?}", mir_phase) }
+        validate::Validator { when: format!("end of phase {:?}", mir_phase), mir_phase }
             .run_pass(tcx, source, body);
     }
 }
@@ -240,7 +243,7 @@ fn mir_const_qualif(tcx: TyCtxt<'_>, def: ty::WithOptConstParam<LocalDefId>) ->
     }
 
     // N.B., this `borrow()` is guaranteed to be valid (i.e., the value
-    // cannot yet be stolen), because `mir_validated()`, which steals
+    // cannot yet be stolen), because `mir_promoted()`, which steals
     // from `mir_const(), forces this query to execute before
     // performing the steal.
     let body = &tcx.mir_const(def).borrow();
@@ -311,12 +314,12 @@ fn mir_const<'tcx>(
     tcx.alloc_steal_mir(body)
 }
 
-fn mir_validated(
+fn mir_promoted(
     tcx: TyCtxt<'tcx>,
     def: ty::WithOptConstParam<LocalDefId>,
 ) -> (&'tcx Steal<Body<'tcx>>, &'tcx Steal<IndexVec<Promoted, Body<'tcx>>>) {
     if let Some(def) = def.try_upgrade(tcx) {
-        return tcx.mir_validated(def);
+        return tcx.mir_promoted(def);
     }
 
     // Ensure that we compute the `mir_const_qualif` for constants at
@@ -350,7 +353,7 @@ fn mir_validated(
         &mut body,
         InstanceDef::Item(def.to_global()),
         None,
-        MirPhase::Validated,
+        MirPhase::ConstPromotion,
         &[promote, opt_coverage],
     );
 
@@ -366,7 +369,7 @@ fn mir_drops_elaborated_and_const_checked<'tcx>(
         return tcx.mir_drops_elaborated_and_const_checked(def);
     }
 
-    // (Mir-)Borrowck uses `mir_validated`, so we have to force it to
+    // (Mir-)Borrowck uses `mir_promoted`, so we have to force it to
     // execute before we can steal.
     if let Some(param_did) = def.const_param_did {
         tcx.ensure().mir_borrowck_const_arg((def.did, param_did));
@@ -374,7 +377,7 @@ fn mir_drops_elaborated_and_const_checked<'tcx>(
         tcx.ensure().mir_borrowck(def.did);
     }
 
-    let (body, _) = tcx.mir_validated(def);
+    let (body, _) = tcx.mir_promoted(def);
     let mut body = body.steal();
 
     run_post_borrowck_cleanup_passes(tcx, &mut body, def.did, None);
@@ -419,7 +422,7 @@ fn run_post_borrowck_cleanup_passes<'tcx>(
         body,
         InstanceDef::Item(ty::WithOptConstParam::unknown(def_id.to_def_id())),
         promoted,
-        MirPhase::DropElab,
+        MirPhase::DropLowering,
         &[post_borrowck_cleanup],
     );
 }
@@ -430,16 +433,24 @@ fn run_optimization_passes<'tcx>(
     def_id: LocalDefId,
     promoted: Option<Promoted>,
 ) {
-    let optimizations: &[&dyn MirPass<'tcx>] = &[
+    let mir_opt_level = tcx.sess.opts.debugging_opts.mir_opt_level;
+
+    // Lowering generator control-flow and variables has to happen before we do anything else
+    // to them. We run some optimizations before that, because they may be harder to do on the state
+    // machine than on MIR with async primitives.
+    let optimizations_with_generators: &[&dyn MirPass<'tcx>] = &[
         &unreachable_prop::UnreachablePropagation,
         &uninhabited_enum_branching::UninhabitedEnumBranching,
         &simplify::SimplifyCfg::new("after-uninhabited-enum-branching"),
         &inline::Inline,
-        // Lowering generator control-flow and variables has to happen before we do anything else
-        // to them. We do this inside the "optimizations" block so that it can benefit from
-        // optimizations that run before, that might be harder to do on the state machine than MIR
-        // with async primitives.
         &generator::StateTransform,
+    ];
+
+    // Even if we don't do optimizations, we still have to lower generators for codegen.
+    let no_optimizations_with_generators: &[&dyn MirPass<'tcx>] = &[&generator::StateTransform];
+
+    // The main optimizations that we do on MIR.
+    let optimizations: &[&dyn MirPass<'tcx>] = &[
         &instcombine::InstCombine,
         &match_branches::MatchBranchSimplification,
         &const_prop::ConstProp,
@@ -455,21 +466,38 @@ fn run_optimization_passes<'tcx>(
         &simplify::SimplifyLocals,
     ];
 
+    // Optimizations to run even if mir optimizations have been disabled.
     let no_optimizations: &[&dyn MirPass<'tcx>] = &[
-        // Even if we don't do optimizations, we still have to lower generators for codegen.
-        &generator::StateTransform,
         // FIXME(#70073): This pass is responsible for both optimization as well as some lints.
         &const_prop::ConstProp,
     ];
 
+    // Some cleanup necessary at least for LLVM and potentially other codegen backends.
     let pre_codegen_cleanup: &[&dyn MirPass<'tcx>] = &[
         &add_call_guards::CriticalCallEdges,
         // Dump the end result for testing and debugging purposes.
         &dump_mir::Marker("PreCodegen"),
     ];
 
-    let mir_opt_level = tcx.sess.opts.debugging_opts.mir_opt_level;
+    // End of pass declarations, now actually run the passes.
+    // Generator Lowering
+    #[rustfmt::skip]
+    run_passes(
+        tcx,
+        body,
+        InstanceDef::Item(ty::WithOptConstParam::unknown(def_id.to_def_id())),
+        promoted,
+        MirPhase::GeneratorLowering,
+        &[
+            if mir_opt_level > 0 {
+                optimizations_with_generators
+            } else {
+                no_optimizations_with_generators
+            }
+        ],
+    );
 
+    // Main optimization passes
     #[rustfmt::skip]
     run_passes(
         tcx,
@@ -533,7 +561,7 @@ fn promoted_mir<'tcx>(
     } else {
         tcx.ensure().mir_borrowck(def.did);
     }
-    let (_, promoted) = tcx.mir_validated(def);
+    let (_, promoted) = tcx.mir_promoted(def);
     let mut promoted = promoted.steal();
 
     for (p, mut body) in promoted.iter_enumerated_mut() {
index 9296e2ca7008fdb4840830992c8c052576cf9063..d7c9ecd0655c0c04b83d34a4f7c2fef837ab2747 100644 (file)
@@ -4,8 +4,8 @@
 use rustc_middle::mir::visit::Visitor;
 use rustc_middle::{
     mir::{
-        BasicBlock, Body, Location, Operand, Rvalue, Statement, StatementKind, Terminator,
-        TerminatorKind,
+        AggregateKind, BasicBlock, Body, Location, MirPhase, Operand, Rvalue, Statement,
+        StatementKind, Terminator, TerminatorKind,
     },
     ty::{
         self,
@@ -23,12 +23,19 @@ enum EdgeKind {
 pub struct Validator {
     /// Describes at which point in the pipeline this validation is happening.
     pub when: String,
+    /// The phase for which we are upholding the dialect. If the given phase forbids a specific
+    /// element, this validator will now emit errors if that specific element is encountered.
+    /// Note that phases that change the dialect cause all *following* phases to check the
+    /// invariants of the new dialect. A phase that changes dialects never checks the new invariants
+    /// itself.
+    pub mir_phase: MirPhase,
 }
 
 impl<'tcx> MirPass<'tcx> for Validator {
     fn run_pass(&self, tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut Body<'tcx>) {
         let param_env = tcx.param_env(source.def_id());
-        TypeChecker { when: &self.when, source, body, tcx, param_env }.visit_body(body);
+        let mir_phase = self.mir_phase;
+        TypeChecker { when: &self.when, source, body, tcx, param_env, mir_phase }.visit_body(body);
     }
 }
 
@@ -130,6 +137,7 @@ struct TypeChecker<'a, 'tcx> {
     body: &'a Body<'tcx>,
     tcx: TyCtxt<'tcx>,
     param_env: ParamEnv<'tcx>,
+    mir_phase: MirPhase,
 }
 
 impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
@@ -226,16 +234,16 @@ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
                     self.fail(
                         location,
                         format!(
-                            "encountered `Assign` statement with incompatible types:\n\
+                            "encountered `{:?}` with incompatible types:\n\
                             left-hand side has type: {}\n\
                             right-hand side has type: {}",
-                            left_ty, right_ty,
+                            statement.kind, left_ty, right_ty,
                         ),
                     );
                 }
-                // The sides of an assignment must not alias. Currently this just checks whether the places
-                // are identical.
                 match rvalue {
+                    // The sides of an assignment must not alias. Currently this just checks whether the places
+                    // are identical.
                     Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) => {
                         if dest == src {
                             self.fail(
@@ -244,6 +252,28 @@ fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
                             );
                         }
                     }
+                    // The deaggregator currently does not deaggreagate arrays.
+                    // So for now, we ignore them here.
+                    Rvalue::Aggregate(box AggregateKind::Array { .. }, _) => {}
+                    // All other aggregates must be gone after some phases.
+                    Rvalue::Aggregate(box kind, _) => {
+                        if self.mir_phase > MirPhase::DropLowering
+                            && !matches!(kind, AggregateKind::Generator(..))
+                        {
+                            // Generators persist until the state machine transformation, but all
+                            // other aggregates must have been lowered.
+                            self.fail(
+                                location,
+                                format!("{:?} have been lowered to field assignments", rvalue),
+                            )
+                        } else if self.mir_phase > MirPhase::GeneratorLowering {
+                            // No more aggregates after drop and generator lowering.
+                            self.fail(
+                                location,
+                                format!("{:?} have been lowered to field assignments", rvalue),
+                            )
+                        }
+                    }
                     _ => {}
                 }
             }
@@ -288,6 +318,12 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
                 }
             }
             TerminatorKind::DropAndReplace { target, unwind, .. } => {
+                if self.mir_phase > MirPhase::DropLowering {
+                    self.fail(
+                        location,
+                        "`DropAndReplace` is not permitted to exist after drop elaboration",
+                    );
+                }
                 self.check_edge(location, *target, EdgeKind::Normal);
                 if let Some(unwind) = unwind {
                     self.check_edge(location, *unwind, EdgeKind::Unwind);
@@ -326,6 +362,9 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
                 }
             }
             TerminatorKind::Yield { resume, drop, .. } => {
+                if self.mir_phase > MirPhase::GeneratorLowering {
+                    self.fail(location, "`Yield` should have been replaced by generator lowering");
+                }
                 self.check_edge(location, *resume, EdgeKind::Normal);
                 if let Some(drop) = drop {
                     self.check_edge(location, *drop, EdgeKind::Normal);
index 9510f0dad34e1a89c60696c4ab00d4b9bca6bb7c..86e0d2b0bc8a7318cad903737b5c6058fbe85285 100644 (file)
           StorageLive(_1);                 // scope 0 at $DIR/checked_add.rs:5:9: 5:10
 -         _2 = CheckedAdd(const 1_u32, const 1_u32); // scope 0 at $DIR/checked_add.rs:5:18: 5:23
 -         assert(!move (_2.1: bool), "attempt to compute `{} + {}` which would overflow", const 1_u32, const 1_u32) -> bb1; // scope 0 at $DIR/checked_add.rs:5:18: 5:23
-+         _2 = (const 2_u32, const false); // scope 0 at $DIR/checked_add.rs:5:18: 5:23
++         _2 = const (2_u32, false);       // scope 0 at $DIR/checked_add.rs:5:18: 5:23
++                                          // ty::Const
++                                          // + ty: (u32, bool)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [2, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/checked_add.rs:5:18: 5:23
++                                          // + literal: Const { ty: (u32, bool), val: Value(ByRef { alloc: Allocation { bytes: [2, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
 +         assert(!const false, "attempt to compute `{} + {}` which would overflow", const 1_u32, const 1_u32) -> bb1; // scope 0 at $DIR/checked_add.rs:5:18: 5:23
       }
   
index fde3c1244ea58e5d39f0b1890313e19f8bc6a7f0..978442d65e9979224110494004b72a896a0b5c64 100644 (file)
 -         _3 = CheckedAdd(_2, const 1_u8); // scope 0 at $DIR/indirect.rs:5:13: 5:29
 -         assert(!move (_3.1: bool), "attempt to compute `{} + {}` which would overflow", move _2, const 1_u8) -> bb1; // scope 0 at $DIR/indirect.rs:5:13: 5:29
 +         _2 = const 2_u8;                 // scope 0 at $DIR/indirect.rs:5:13: 5:25
-+         _3 = (const 3_u8, const false);  // scope 0 at $DIR/indirect.rs:5:13: 5:29
++         _3 = const (3_u8, false);        // scope 0 at $DIR/indirect.rs:5:13: 5:29
++                                          // ty::Const
++                                          // + ty: (u8, bool)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [3, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [3], len: Size { raw: 2 } }, size: Size { raw: 2 }, align: Align { pow2: 0 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/indirect.rs:5:13: 5:29
++                                          // + literal: Const { ty: (u8, bool), val: Value(ByRef { alloc: Allocation { bytes: [3, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [3], len: Size { raw: 2 } }, size: Size { raw: 2 }, align: Align { pow2: 0 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
 +         assert(!const false, "attempt to compute `{} + {}` which would overflow", const 2_u8, const 1_u8) -> bb1; // scope 0 at $DIR/indirect.rs:5:13: 5:29
       }
   
index 8b47f66a1178b714ead3ac78e72fe367f3544ae1..be978d04bea2fe37219758eb3b7422f0dd28638d 100644 (file)
           (_3.0: u8) = const 1_u8;         // scope 0 at $DIR/issue-67019.rs:11:11: 11:17
           (_3.1: u8) = const 2_u8;         // scope 0 at $DIR/issue-67019.rs:11:11: 11:17
 -         (_2.0: (u8, u8)) = move _3;      // scope 0 at $DIR/issue-67019.rs:11:10: 11:19
-+         (_2.0: (u8, u8)) = (const 1_u8, const 2_u8); // scope 0 at $DIR/issue-67019.rs:11:10: 11:19
++         (_2.0: (u8, u8)) = const (1_u8, 2_u8); // scope 0 at $DIR/issue-67019.rs:11:10: 11:19
++                                          // ty::Const
++                                          // + ty: (u8, u8)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [1, 2], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [3], len: Size { raw: 2 } }, size: Size { raw: 2 }, align: Align { pow2: 0 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/issue-67019.rs:11:10: 11:19
++                                          // + literal: Const { ty: (u8, u8), val: Value(ByRef { alloc: Allocation { bytes: [1, 2], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [3], len: Size { raw: 2 } }, size: Size { raw: 2 }, align: Align { pow2: 0 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
           StorageDead(_3);                 // scope 0 at $DIR/issue-67019.rs:11:18: 11:19
           _1 = const test(move _2) -> bb1; // scope 0 at $DIR/issue-67019.rs:11:5: 11:20
                                            // ty::Const
index 05c618b3a1279e091a6c458621adf3c6841a3714..f05b117b8fe401d841a6d3081a0408c4e6187568 100644 (file)
           (_1.1: i32) = const 99_i32;      // scope 1 at $DIR/mutable_variable_aggregate.rs:6:5: 6:13
           StorageLive(_2);                 // scope 1 at $DIR/mutable_variable_aggregate.rs:7:9: 7:10
 -         _2 = _1;                         // scope 1 at $DIR/mutable_variable_aggregate.rs:7:13: 7:14
-+         _2 = (const 42_i32, const 99_i32); // scope 1 at $DIR/mutable_variable_aggregate.rs:7:13: 7:14
++         _2 = const (42_i32, 99_i32);     // scope 1 at $DIR/mutable_variable_aggregate.rs:7:13: 7:14
++                                          // ty::Const
++                                          // + ty: (i32, i32)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [42, 0, 0, 0, 99, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [255], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/mutable_variable_aggregate.rs:7:13: 7:14
++                                          // + literal: Const { ty: (i32, i32), val: Value(ByRef { alloc: Allocation { bytes: [42, 0, 0, 0, 99, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [255], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
           _0 = const ();                   // scope 0 at $DIR/mutable_variable_aggregate.rs:4:11: 8:2
                                            // ty::Const
                                            // + ty: ()
index 3bdf9e753c044579d43442f75af46d7727e38eb4..d76a69fcd43ac32eba596eaea5d496bae96e5ac3 100644 (file)
           StorageLive(_1);                 // scope 0 at $DIR/optimizes_into_variable.rs:12:9: 12:10
 -         _2 = CheckedAdd(const 2_i32, const 2_i32); // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
 -         assert(!move (_2.1: bool), "attempt to compute `{} + {}` which would overflow", const 2_i32, const 2_i32) -> bb1; // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
-+         _2 = (const 4_i32, const false); // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
++         _2 = const (4_i32, false);       // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
++                                          // ty::Const
++                                          // + ty: (i32, bool)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [4, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/optimizes_into_variable.rs:12:13: 12:18
++                                          // + literal: Const { ty: (i32, bool), val: Value(ByRef { alloc: Allocation { bytes: [4, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
 +         assert(!const false, "attempt to compute `{} + {}` which would overflow", const 2_i32, const 2_i32) -> bb1; // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
       }
   
index 3bdf9e753c044579d43442f75af46d7727e38eb4..d76a69fcd43ac32eba596eaea5d496bae96e5ac3 100644 (file)
           StorageLive(_1);                 // scope 0 at $DIR/optimizes_into_variable.rs:12:9: 12:10
 -         _2 = CheckedAdd(const 2_i32, const 2_i32); // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
 -         assert(!move (_2.1: bool), "attempt to compute `{} + {}` which would overflow", const 2_i32, const 2_i32) -> bb1; // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
-+         _2 = (const 4_i32, const false); // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
++         _2 = const (4_i32, false);       // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
++                                          // ty::Const
++                                          // + ty: (i32, bool)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [4, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/optimizes_into_variable.rs:12:13: 12:18
++                                          // + literal: Const { ty: (i32, bool), val: Value(ByRef { alloc: Allocation { bytes: [4, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
 +         assert(!const false, "attempt to compute `{} + {}` which would overflow", const 2_i32, const 2_i32) -> bb1; // scope 0 at $DIR/optimizes_into_variable.rs:12:13: 12:18
       }
   
index 99893aa19cff354791f46bee022fb3aa54d29976..d61a04d1e03226c52e0b3d4502d9f93fb277f689 100644 (file)
@@ -8,7 +8,13 @@
       bb0: {
 -         _1 = CheckedAdd(const 2_u32, const 2_u32); // scope 0 at $DIR/return_place.rs:6:5: 6:10
 -         assert(!move (_1.1: bool), "attempt to compute `{} + {}` which would overflow", const 2_u32, const 2_u32) -> bb1; // scope 0 at $DIR/return_place.rs:6:5: 6:10
-+         _1 = (const 4_u32, const false); // scope 0 at $DIR/return_place.rs:6:5: 6:10
++         _1 = const (4_u32, false);       // scope 0 at $DIR/return_place.rs:6:5: 6:10
++                                          // ty::Const
++                                          // + ty: (u32, bool)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [4, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/return_place.rs:6:5: 6:10
++                                          // + literal: Const { ty: (u32, bool), val: Value(ByRef { alloc: Allocation { bytes: [4, 0, 0, 0, 0, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [31], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
 +         assert(!const false, "attempt to compute `{} + {}` which would overflow", const 2_u32, const 2_u32) -> bb1; // scope 0 at $DIR/return_place.rs:6:5: 6:10
       }
   
index 83cdd6b5836aec9293f0cd080a509ffc30a57fcf..38ed439bd86cd2bafdf7cb0a60579a90ecc455b1 100644 (file)
           StorageLive(_2);                 // scope 1 at $DIR/tuple_literal_propagation.rs:5:5: 5:15
           StorageLive(_3);                 // scope 1 at $DIR/tuple_literal_propagation.rs:5:13: 5:14
 -         _3 = _1;                         // scope 1 at $DIR/tuple_literal_propagation.rs:5:13: 5:14
-+         _3 = (const 1_u32, const 2_u32); // scope 1 at $DIR/tuple_literal_propagation.rs:5:13: 5:14
++         _3 = const (1_u32, 2_u32);       // scope 1 at $DIR/tuple_literal_propagation.rs:5:13: 5:14
++                                          // ty::Const
++                                          // + ty: (u32, u32)
++                                          // + val: Value(ByRef { alloc: Allocation { bytes: [1, 0, 0, 0, 2, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [255], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } })
++                                          // mir::Constant
++                                          // + span: $DIR/tuple_literal_propagation.rs:5:13: 5:14
++                                          // + literal: Const { ty: (u32, u32), val: Value(ByRef { alloc: Allocation { bytes: [1, 0, 0, 0, 2, 0, 0, 0], relocations: Relocations(SortedMap { data: [] }), init_mask: InitMask { blocks: [255], len: Size { raw: 8 } }, size: Size { raw: 8 }, align: Align { pow2: 2 }, mutability: Not, extra: () }, offset: Size { raw: 0 } }) }
           _2 = const consume(move _3) -> bb1; // scope 1 at $DIR/tuple_literal_propagation.rs:5:5: 5:15
                                            // ty::Const
                                            // + ty: fn((u32, u32)) {consume}
index 796bec0be1e0d71eed0abd7e2c99927c54876cf8..64fbdc9ded13950469c494ad1c25c134c269b153 100644 (file)
@@ -1,4 +1,4 @@
-// compile-flags: -Zmir-opt-level=0 -Zvalidate-mir
+// compile-flags: -Zmir-opt-level=0
 
 // Tests that the `<fn() as Fn>` shim does not create a `Call` terminator with a `Self` callee
 // (as only `FnDef` and `FnPtr` callees are allowed in MIR).
index 41fc04774dbca3c44cbcc3c2d2711bb3b1823648..0877dd15635d3b8cac2297b943d66712e3c00398 100644 (file)
@@ -41,7 +41,8 @@ fn main::{{closure}}#0(_1: std::pin::Pin<&mut [generator@$DIR/generator-tiny.rs:
     bb2: {
         StorageLive(_6);                 // scope 1 at $DIR/generator-tiny.rs:22:13: 22:18
         StorageLive(_7);                 // scope 1 at $DIR/generator-tiny.rs:22:13: 22:18
-        _0 = std::ops::GeneratorState::<(), ()>::Yielded(move _7); // scope 1 at $DIR/generator-tiny.rs:22:13: 22:18
+        ((_0 as Yielded).0: ()) = move _7; // scope 1 at $DIR/generator-tiny.rs:22:13: 22:18
+        discriminant(_0) = 0;            // scope 1 at $DIR/generator-tiny.rs:22:13: 22:18
         discriminant((*(_1.0: &mut [generator@$DIR/generator-tiny.rs:19:16: 25:6 {u8, HasDrop, ()}]))) = 3; // scope 1 at $DIR/generator-tiny.rs:22:13: 22:18
         return;                          // scope 1 at $DIR/generator-tiny.rs:22:13: 22:18
     }
index 03136921ad626b83f937a99c042062381043c2c5..8318a0a5ad0451328409226b2a6bb0b74c65d30e 100644 (file)
@@ -1942,6 +1942,7 @@ fn make_compile_args(
                 rustc.args(&[
                     "-Zdump-mir=all",
                     "-Zmir-opt-level=3",
+                    "-Zvalidate-mir",
                     "-Zdump-mir-exclude-pass-number",
                 ]);