Rollup merge of #94655 - JakobDegen:mir-phase-docs, r=oli-obk

Clarify which kinds of MIR are allowed during which phases.

This enhances documentation with these details and extends the validator to check these requirements more thoroughly. Most of these conditions were already being checked.

There was also some disagreement between the `MirPhase` docs and validator as to what it meant for the `body.phase` field to have a certain value. This PR resolves those disagreements in favor of the `MirPhase` docs (which is what the pass manager implemented), adjusting the validator accordingly. The result is now that the `DropLowering` phase begins with the end of the elaborate drops pass, and lasts until the beginning of the generator lowring pass. This doesn't feel entirely natural to me, but as long as it's documented accurately it should be ok.

r? rust-lang/mir-opt
This commit is contained in:
Dylan DPC 2022-03-25 01:34:29 +01:00 committed by GitHub
commit c66e0c8726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 52 deletions

View File

@ -42,7 +42,7 @@ pub struct PromoteTemps<'tcx> {
impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> { impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> {
fn phase_change(&self) -> Option<MirPhase> { fn phase_change(&self) -> Option<MirPhase> {
Some(MirPhase::ConstPromotion) Some(MirPhase::ConstsPromoted)
} }
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {

View File

@ -266,22 +266,15 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
); );
} }
} }
// The deaggregator currently does not deaggreagate arrays. Rvalue::Aggregate(agg_kind, _) => {
// So for now, we ignore them here. let disallowed = match **agg_kind {
Rvalue::Aggregate(box AggregateKind::Array { .. }, _) => {} AggregateKind::Array(..) => false,
// All other aggregates must be gone after some phases. AggregateKind::Generator(..) => {
Rvalue::Aggregate(box kind, _) => { self.mir_phase >= MirPhase::GeneratorsLowered
if self.mir_phase > MirPhase::DropLowering }
&& !matches!(kind, AggregateKind::Generator(..)) _ => self.mir_phase >= MirPhase::Deaggregated,
{ };
// Generators persist until the state machine transformation, but all if disallowed {
// 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( self.fail(
location, location,
format!("{:?} have been lowered to field assignments", rvalue), format!("{:?} have been lowered to field assignments", rvalue),
@ -289,7 +282,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
} }
} }
Rvalue::Ref(_, BorrowKind::Shallow, _) => { Rvalue::Ref(_, BorrowKind::Shallow, _) => {
if self.mir_phase > MirPhase::DropLowering { if self.mir_phase >= MirPhase::DropsLowered {
self.fail( self.fail(
location, location,
"`Assign` statement with a `Shallow` borrow should have been removed after drop lowering phase", "`Assign` statement with a `Shallow` borrow should have been removed after drop lowering phase",
@ -300,7 +293,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
} }
} }
StatementKind::AscribeUserType(..) => { StatementKind::AscribeUserType(..) => {
if self.mir_phase > MirPhase::DropLowering { if self.mir_phase >= MirPhase::DropsLowered {
self.fail( self.fail(
location, location,
"`AscribeUserType` should have been removed after drop lowering phase", "`AscribeUserType` should have been removed after drop lowering phase",
@ -308,7 +301,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
} }
} }
StatementKind::FakeRead(..) => { StatementKind::FakeRead(..) => {
if self.mir_phase > MirPhase::DropLowering { if self.mir_phase >= MirPhase::DropsLowered {
self.fail( self.fail(
location, location,
"`FakeRead` should have been removed after drop lowering phase", "`FakeRead` should have been removed after drop lowering phase",
@ -351,10 +344,18 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
self.fail(location, format!("bad arg ({:?} != usize)", op_cnt_ty)) self.fail(location, format!("bad arg ({:?} != usize)", op_cnt_ty))
} }
} }
StatementKind::SetDiscriminant { .. } StatementKind::SetDiscriminant { .. } => {
| StatementKind::StorageLive(..) if self.mir_phase < MirPhase::DropsLowered {
self.fail(location, "`SetDiscriminant` is not allowed until drop elaboration");
}
}
StatementKind::Retag(_, _) => {
// FIXME(JakobDegen) The validator should check that `self.mir_phase <
// DropsLowered`. However, this causes ICEs with generation of drop shims, which
// seem to fail to set their `MirPhase` correctly.
}
StatementKind::StorageLive(..)
| StatementKind::StorageDead(..) | StatementKind::StorageDead(..)
| StatementKind::Retag(_, _)
| StatementKind::Coverage(_) | StatementKind::Coverage(_)
| StatementKind::Nop => {} | StatementKind::Nop => {}
} }
@ -424,10 +425,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
} }
} }
TerminatorKind::DropAndReplace { target, unwind, .. } => { TerminatorKind::DropAndReplace { target, unwind, .. } => {
if self.mir_phase > MirPhase::DropLowering { if self.mir_phase >= MirPhase::DropsLowered {
self.fail( self.fail(
location, location,
"`DropAndReplace` is not permitted to exist after drop elaboration", "`DropAndReplace` should have been removed during drop elaboration",
); );
} }
self.check_edge(location, *target, EdgeKind::Normal); self.check_edge(location, *target, EdgeKind::Normal);
@ -494,7 +495,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
} }
} }
TerminatorKind::Yield { resume, drop, .. } => { TerminatorKind::Yield { resume, drop, .. } => {
if self.mir_phase > MirPhase::GeneratorLowering { if self.mir_phase >= MirPhase::GeneratorsLowered {
self.fail(location, "`Yield` should have been replaced by generator lowering"); self.fail(location, "`Yield` should have been replaced by generator lowering");
} }
self.check_edge(location, *resume, EdgeKind::Normal); self.check_edge(location, *resume, EdgeKind::Normal);
@ -503,10 +504,22 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
} }
} }
TerminatorKind::FalseEdge { real_target, imaginary_target } => { TerminatorKind::FalseEdge { real_target, imaginary_target } => {
if self.mir_phase >= MirPhase::DropsLowered {
self.fail(
location,
"`FalseEdge` should have been removed after drop elaboration",
);
}
self.check_edge(location, *real_target, EdgeKind::Normal); self.check_edge(location, *real_target, EdgeKind::Normal);
self.check_edge(location, *imaginary_target, EdgeKind::Normal); self.check_edge(location, *imaginary_target, EdgeKind::Normal);
} }
TerminatorKind::FalseUnwind { real_target, unwind } => { TerminatorKind::FalseUnwind { real_target, unwind } => {
if self.mir_phase >= MirPhase::DropsLowered {
self.fail(
location,
"`FalseUnwind` should have been removed after drop elaboration",
);
}
self.check_edge(location, *real_target, EdgeKind::Normal); self.check_edge(location, *real_target, EdgeKind::Normal);
if let Some(unwind) = unwind { if let Some(unwind) = unwind {
self.check_edge(location, *unwind, EdgeKind::Unwind); self.check_edge(location, *unwind, EdgeKind::Unwind);
@ -520,12 +533,19 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
self.check_edge(location, *cleanup, EdgeKind::Unwind); self.check_edge(location, *cleanup, EdgeKind::Unwind);
} }
} }
TerminatorKind::GeneratorDrop => {
if self.mir_phase >= MirPhase::GeneratorsLowered {
self.fail(
location,
"`GeneratorDrop` should have been replaced by generator lowering",
);
}
}
// Nothing to validate for these. // Nothing to validate for these.
TerminatorKind::Resume TerminatorKind::Resume
| TerminatorKind::Abort | TerminatorKind::Abort
| TerminatorKind::Return | TerminatorKind::Return
| TerminatorKind::Unreachable | TerminatorKind::Unreachable => {}
| TerminatorKind::GeneratorDrop => {}
} }
self.super_terminator(terminator, location); self.super_terminator(terminator, location);

View File

@ -127,14 +127,11 @@ pub trait MirPass<'tcx> {
/// These phases all describe dialects of MIR. Since all MIR uses the same datastructures, the /// These phases all describe dialects of MIR. Since all MIR uses the same datastructures, the
/// dialects forbid certain variants or values in certain phases. /// 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 upheld *after* that phase finishes.
///
/// Warning: ordering of variants is significant. /// Warning: ordering of variants is significant.
#[derive(Copy, Clone, TyEncodable, TyDecodable, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, TyEncodable, TyDecodable, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(HashStable)] #[derive(HashStable)]
pub enum MirPhase { pub enum MirPhase {
Build = 0, Built = 0,
// FIXME(oli-obk): it's unclear whether we still need this phase (and its corresponding query). // FIXME(oli-obk): 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. // We used to have this for pre-miri MIR based const eval.
Const = 1, Const = 1,
@ -142,17 +139,32 @@ pub enum MirPhase {
/// by creating a new MIR body per promoted element. After this phase (and thus the termination /// 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` /// of the `mir_promoted` query), these promoted elements are available in the `promoted_mir`
/// query. /// query.
ConstPromotion = 2, ConstsPromoted = 2,
/// After this phase /// Beginning with this phase, the following variants are disallowed:
/// * the only `AggregateKind`s allowed are `Array` and `Generator`, /// * [`TerminatorKind::DropAndReplace`](terminator::TerminatorKind::DropAndReplace)
/// * `DropAndReplace` is gone for good /// * [`TerminatorKind::FalseUnwind`](terminator::TerminatorKind::FalseUnwind)
/// * `Drop` now uses explicit drop flags visible in the MIR and reaching a `Drop` terminator /// * [`TerminatorKind::FalseEdge`](terminator::TerminatorKind::FalseEdge)
/// means that the auto-generated drop glue will be invoked. /// * [`StatementKind::FakeRead`]
DropLowering = 3, /// * [`StatementKind::AscribeUserType`]
/// After this phase, generators are explicit state machines (no more `Yield`). /// * [`Rvalue::Ref`] with `BorrowKind::Shallow`
/// `AggregateKind::Generator` is gone for good. ///
GeneratorLowering = 4, /// And the following variant is allowed:
Optimization = 5, /// * [`StatementKind::Retag`]
///
/// Furthermore, `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.
DropsLowered = 3,
/// Beginning with this phase, the following variant is disallowed:
/// * [`Rvalue::Aggregate`] for any `AggregateKind` except `Array`
///
/// And the following variant is allowed:
/// * [`StatementKind::SetDiscriminant`]
Deaggregated = 4,
/// Beginning with this phase, the following variants are disallowed:
/// * [`TerminatorKind::Yield`](terminator::TerminatorKind::Yield)
/// * [`TerminatorKind::GeneratorDrop](terminator::TerminatorKind::GeneratorDrop)
GeneratorsLowered = 5,
Optimized = 6,
} }
impl MirPhase { impl MirPhase {
@ -311,7 +323,7 @@ impl<'tcx> Body<'tcx> {
); );
let mut body = Body { let mut body = Body {
phase: MirPhase::Build, phase: MirPhase::Built,
source, source,
basic_blocks, basic_blocks,
source_scopes, source_scopes,
@ -346,7 +358,7 @@ impl<'tcx> Body<'tcx> {
/// crate. /// crate.
pub fn new_cfg_only(basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>) -> Self { pub fn new_cfg_only(basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>) -> Self {
let mut body = Body { let mut body = Body {
phase: MirPhase::Build, phase: MirPhase::Built,
source: MirSource::item(DefId::local(CRATE_DEF_INDEX)), source: MirSource::item(DefId::local(CRATE_DEF_INDEX)),
basic_blocks, basic_blocks,
source_scopes: IndexVec::new(), source_scopes: IndexVec::new(),
@ -1541,9 +1553,16 @@ impl Statement<'_> {
} }
} }
/// The various kinds of statements that can appear in MIR.
///
/// Not all of these are allowed at every [`MirPhase`]. Check the documentation there to see which
/// ones you do not have to worry about. The MIR validator will generally enforce such restrictions,
/// causing an ICE if they are violated.
#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)] #[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable)]
pub enum StatementKind<'tcx> { pub enum StatementKind<'tcx> {
/// Write the RHS Rvalue to the LHS Place. /// Write the RHS Rvalue to the LHS Place.
///
/// The LHS place may not overlap with any memory accessed on the RHS.
Assign(Box<(Place<'tcx>, Rvalue<'tcx>)>), Assign(Box<(Place<'tcx>, Rvalue<'tcx>)>),
/// This represents all the reading that a pattern match may do /// This represents all the reading that a pattern match may do
@ -1761,6 +1780,19 @@ static_assert_size!(Place<'_>, 16);
pub enum ProjectionElem<V, T> { pub enum ProjectionElem<V, T> {
Deref, Deref,
Field(Field, T), Field(Field, T),
/// Index into a slice/array.
///
/// Note that this does not also dereference, and so it does not exactly correspond to slice
/// indexing in Rust. In other words, in the below Rust code:
///
/// ```rust
/// let x = &[1, 2, 3, 4];
/// let i = 2;
/// x[i];
/// ```
///
/// The `x[i]` is turned into a `Deref` followed by an `Index`, not just an `Index`. The same
/// thing is true of the `ConstantIndex` and `Subslice` projections below.
Index(V), Index(V),
/// These indices are generated by slice patterns. Easiest to explain /// These indices are generated by slice patterns. Easiest to explain
@ -2223,6 +2255,11 @@ impl<'tcx> Operand<'tcx> {
/// Rvalues /// Rvalues
#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] #[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)]
/// The various kinds of rvalues that can appear in MIR.
///
/// Not all of these are allowed at every [`MirPhase`]. Check the documentation there to see which
/// ones you do not have to worry about. The MIR validator will generally enforce such restrictions,
/// causing an ICE if they are violated.
pub enum Rvalue<'tcx> { pub enum Rvalue<'tcx> {
/// x (either a move or copy, depending on type of x) /// x (either a move or copy, depending on type of x)
Use(Operand<'tcx>), Use(Operand<'tcx>),

View File

@ -6,6 +6,10 @@ use rustc_middle::ty::TyCtxt;
pub struct Deaggregator; pub struct Deaggregator;
impl<'tcx> MirPass<'tcx> for Deaggregator { impl<'tcx> MirPass<'tcx> for Deaggregator {
fn phase_change(&self) -> Option<MirPhase> {
Some(MirPhase::Deaggregated)
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut(); let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
let local_decls = &*local_decls; let local_decls = &*local_decls;

View File

@ -20,7 +20,7 @@ pub struct ElaborateDrops;
impl<'tcx> MirPass<'tcx> for ElaborateDrops { impl<'tcx> MirPass<'tcx> for ElaborateDrops {
fn phase_change(&self) -> Option<MirPhase> { fn phase_change(&self) -> Option<MirPhase> {
Some(MirPhase::DropLowering) Some(MirPhase::DropsLowered)
} }
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {

View File

@ -1235,7 +1235,7 @@ fn create_cases<'tcx>(
impl<'tcx> MirPass<'tcx> for StateTransform { impl<'tcx> MirPass<'tcx> for StateTransform {
fn phase_change(&self) -> Option<MirPhase> { fn phase_change(&self) -> Option<MirPhase> {
Some(MirPhase::GeneratorLowering) Some(MirPhase::GeneratorsLowered)
} }
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {

View File

@ -342,7 +342,7 @@ fn inner_mir_for_ctfe(tcx: TyCtxt<'_>, def: ty::WithOptConstParam<LocalDefId>) -
pm::run_passes( pm::run_passes(
tcx, tcx,
&mut body, &mut body,
&[&const_prop::ConstProp, &marker::PhaseChange(MirPhase::Optimization)], &[&const_prop::ConstProp, &marker::PhaseChange(MirPhase::Optimized)],
); );
} }
} }
@ -399,7 +399,7 @@ fn mir_drops_elaborated_and_const_checked<'tcx>(
} }
run_post_borrowck_cleanup_passes(tcx, &mut body); run_post_borrowck_cleanup_passes(tcx, &mut body);
assert!(body.phase == MirPhase::DropLowering); assert!(body.phase == MirPhase::Deaggregated);
tcx.alloc_steal_mir(body) tcx.alloc_steal_mir(body)
} }
@ -460,7 +460,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
], ],
); );
assert!(body.phase == MirPhase::GeneratorLowering); assert!(body.phase == MirPhase::GeneratorsLowered);
// The main optimizations that we do on MIR. // The main optimizations that we do on MIR.
pm::run_passes( pm::run_passes(
@ -497,7 +497,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
&deduplicate_blocks::DeduplicateBlocks, &deduplicate_blocks::DeduplicateBlocks,
// Some cleanup necessary at least for LLVM and potentially other codegen backends. // Some cleanup necessary at least for LLVM and potentially other codegen backends.
&add_call_guards::CriticalCallEdges, &add_call_guards::CriticalCallEdges,
&marker::PhaseChange(MirPhase::Optimization), &marker::PhaseChange(MirPhase::Optimized),
// Dump the end result for testing and debugging purposes. // Dump the end result for testing and debugging purposes.
&dump_mir::Marker("PreCodegen"), &dump_mir::Marker("PreCodegen"),
], ],

View File

@ -114,7 +114,7 @@ pub fn run_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, passes: &[&dyn
} }
} }
if validate || body.phase == MirPhase::Optimization { if validate || body.phase == MirPhase::Optimized {
validate_body(tcx, body, format!("end of phase transition to {:?}", body.phase)); validate_body(tcx, body, format!("end of phase transition to {:?}", body.phase));
} }
} }