Auto merge of #91475 - ecstatic-morse:mir-pass-manager3, r=oli-obk

Add a MIR pass manager (Taylor's Version)

The final draft of #91386 and #77665.

While the compile-time constraints in #91386 are cool, I decided on a more minimal approach for now. I want to explore phase constraints and maybe relative-ordering constraints in the future, though. This should preserve existing behavior **exactly** (please let me know if it doesn't) while making the following changes to the way we organize things today:

- Each `MirPhase` now corresponds to a single MIR pass. `run_passes` is not responsible for listing the correct MIR phase.
- `run_passes` no longer silently skips passes if the declared MIR phase is greater than or equal to the body's. This has bitten me multiple times. If you want this behavior, you can always branch on `body.phase` yourself.
- If your pass is solely to emit errors, you can use the `MirLint` interface instead, which gets a shared reference to `Body` instead of a mutable one. By differentiating the two, I hope to make it clearer in the short term where lints belong in the pipeline. In the long term perhaps we could enforce this at compile-time?
- MIR is no longer dumped for passes that aren't enabled, or for lints.

I tried to check that `-Zvalidate` still works correctly, since the MIR phase is now updated as soon as the associated pass is done, instead of at the end of all the passes in `run_passes`. However, it looks like `-Zvalidate` is broken with current nightlies anyways 😢 (it spits out a bunch of errors).

cc `@oli-obk` `@wesleywiser`

r? rust-lang/wg-mir-opt
This commit is contained in:
bors 2021-12-05 03:41:18 +00:00
commit bdaa901049
39 changed files with 448 additions and 282 deletions

View file

@ -41,6 +41,10 @@ pub struct PromoteTemps<'tcx> {
}
impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> {
fn phase_change(&self) -> Option<MirPhase> {
Some(MirPhase::ConstPromotion)
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// There's not really any point in promoting errorful MIR.
//

View file

@ -16,6 +16,7 @@
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
use rustc_hir::{self, GeneratorKind};
use rustc_hir::{self as hir, HirId};
use rustc_session::Session;
use rustc_target::abi::{Size, VariantIdx};
use polonius_engine::Atom;
@ -99,7 +100,21 @@ fn name(&self) -> Cow<'_, str> {
}
}
/// Returns `true` if this pass is enabled with the current combination of compiler flags.
fn is_enabled(&self, _sess: &Session) -> bool {
true
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>);
/// If this pass causes the MIR to enter a new phase, return that phase.
fn phase_change(&self) -> Option<MirPhase> {
None
}
fn is_mir_dump_enabled(&self) -> bool {
true
}
}
/// The various "big phases" that MIR goes through.

View file

@ -20,6 +20,7 @@
pub struct SanityCheck;
// FIXME: This should be a `MirLint`, but it needs to be moved back to `rustc_mir_transform` first.
impl<'tcx> MirPass<'tcx> for SanityCheck {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
use crate::has_rustc_mir_with;

View file

@ -58,11 +58,11 @@ fn may_be_reference(ty: Ty<'tcx>) -> bool {
}
impl<'tcx> MirPass<'tcx> for AddRetag {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if !tcx.sess.opts.debugging_opts.mir_emit_retag {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.opts.debugging_opts.mir_emit_retag
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// We need an `AllCallEdges` pass before we can do any work.
super::add_call_guards::AllCallEdges.run_pass(tcx, body);

View file

@ -6,12 +6,12 @@
use rustc_session::lint::builtin::CONST_ITEM_MUTATION;
use rustc_span::def_id::DefId;
use crate::MirPass;
use crate::MirLint;
pub struct CheckConstItemMutation;
impl<'tcx> MirPass<'tcx> for CheckConstItemMutation {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
impl<'tcx> MirLint<'tcx> for CheckConstItemMutation {
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
let mut checker = ConstMutationChecker { body, tcx, target_local: None };
checker.visit_body(&body);
}

View file

@ -7,7 +7,7 @@
use rustc_span::symbol::sym;
use crate::util;
use crate::MirPass;
use crate::MirLint;
pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers { unsafe_derive_on_repr_packed, ..*providers };
@ -15,8 +15,8 @@ pub(crate) fn provide(providers: &mut Providers) {
pub struct CheckPackedRef;
impl<'tcx> MirPass<'tcx> for CheckPackedRef {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
impl<'tcx> MirLint<'tcx> for CheckPackedRef {
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
let param_env = tcx.param_env(body.source.def_id());
let source_info = SourceInfo::outermost(body.span);
let mut checker = PackedRefChecker { body, tcx, param_env, source_info };

View file

@ -15,11 +15,11 @@
pub struct ConstDebugInfo;
impl<'tcx> MirPass<'tcx> for ConstDebugInfo {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if !tcx.sess.opts.debugging_opts.unsound_mir_opts {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.opts.debugging_opts.unsound_mir_opts && sess.mir_opt_level() > 0
}
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
trace!("running ConstDebugInfo on {:?}", body.source);
for (local, constant) in find_optimization_oportunities(body) {

View file

@ -27,10 +27,11 @@
pub struct ConstGoto;
impl<'tcx> MirPass<'tcx> for ConstGoto {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 4
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.mir_opt_level() < 4 {
return;
}
trace!("Running ConstGoto on {:?}", body.source);
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let mut opt_finder =

View file

@ -62,6 +62,13 @@ impl rustc_middle::mir::interpret::MachineStopType for Zst {}
pub struct ConstProp;
impl<'tcx> MirPass<'tcx> for ConstProp {
fn is_enabled(&self, _sess: &rustc_session::Session) -> bool {
// FIXME(#70073): Unlike the other passes in "optimizations", this one emits errors, so it
// runs even when MIR optimizations are disabled. We should separate the lint out from the
// transform and move the lint as early in the pipeline as possible.
true
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// will be evaluated by miri and produce its errors there
if body.source.promoted.is_some() {

View file

@ -49,6 +49,10 @@ pub fn from_string<T>(message: String) -> Result<T, Error> {
pub struct InstrumentCoverage;
impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.instrument_coverage()
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) {
let mir_source = mir_body.source;

View file

@ -15,10 +15,11 @@
pub struct DeduplicateBlocks;
impl<'tcx> MirPass<'tcx> for DeduplicateBlocks {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 4
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.mir_opt_level() < 4 {
return;
}
debug!("Running DeduplicateBlocks on `{:?}`", body.source);
let duplicates = find_duplicates(body);
let has_opts_to_apply = !duplicates.is_empty();

View file

@ -124,18 +124,15 @@
pub struct DestinationPropagation;
impl<'tcx> MirPass<'tcx> for DestinationPropagation {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// FIXME(#79191, #82678)
if !tcx.sess.opts.debugging_opts.unsound_mir_opts {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
// FIXME(#79191, #82678): This is unsound.
//
// Only run at mir-opt-level=3 or higher for now (we don't fix up debuginfo and remove
// storage statements at the moment).
if tcx.sess.mir_opt_level() < 3 {
return;
}
sess.opts.debugging_opts.unsound_mir_opts && sess.mir_opt_level() >= 3
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id();
let candidates = find_candidates(tcx, body);

View file

@ -25,16 +25,14 @@
pub struct EarlyOtherwiseBranch;
impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
// FIXME(#78496)
if !tcx.sess.opts.debugging_opts.unsound_mir_opts {
return;
}
sess.opts.debugging_opts.unsound_mir_opts && sess.mir_opt_level() >= 3
}
if tcx.sess.mir_opt_level() < 3 {
return;
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
trace!("running EarlyOtherwiseBranch on {:?}", body.source);
// we are only interested in this bb if the terminator is a switchInt
let bbs_with_switch =
body.basic_blocks().iter_enumerated().filter(|(_, bb)| is_switch(bb.terminator()));

View file

@ -19,6 +19,10 @@
pub struct ElaborateDrops;
impl<'tcx> MirPass<'tcx> for ElaborateDrops {
fn phase_change(&self) -> Option<MirPhase> {
Some(MirPhase::DropLowering)
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
debug!("elaborate_drops({:?} @ {:?})", body.source, body.span);

View file

@ -11,12 +11,12 @@
use rustc_span::{symbol::sym, Span};
use rustc_target::spec::abi::Abi;
use crate::MirPass;
use crate::MirLint;
pub struct FunctionItemReferences;
impl<'tcx> MirPass<'tcx> for FunctionItemReferences {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
impl<'tcx> MirLint<'tcx> for FunctionItemReferences {
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
let mut checker = FunctionItemRefChecker { tcx, body };
checker.visit_body(&body);
}

View file

@ -1232,6 +1232,10 @@ fn create_cases<'tcx>(
}
impl<'tcx> MirPass<'tcx> for StateTransform {
fn phase_change(&self) -> Option<MirPhase> {
Some(MirPhase::GeneratorLowering)
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let yield_ty = if let Some(yield_ty) = body.yield_ty() {
yield_ty

View file

@ -37,21 +37,16 @@ struct CallSite<'tcx> {
source_info: SourceInfo,
}
/// Returns true if MIR inlining is enabled in the current compilation session.
crate fn is_enabled(tcx: TyCtxt<'_>) -> bool {
if let Some(enabled) = tcx.sess.opts.debugging_opts.inline_mir {
return enabled;
}
tcx.sess.mir_opt_level() >= 3
}
impl<'tcx> MirPass<'tcx> for Inline {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if !is_enabled(tcx) {
return;
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
if let Some(enabled) = sess.opts.debugging_opts.inline_mir {
return enabled;
}
sess.opts.mir_opt_level() >= 3
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let span = trace_span!("inline", body = %tcx.def_path_str(body.source.def_id()));
let _guard = span.enter();
if inline(tcx, body) {

View file

@ -11,6 +11,10 @@
pub struct InstCombine;
impl<'tcx> MirPass<'tcx> for InstCombine {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
let ctx = InstCombineContext { tcx, local_decls };

View file

@ -27,11 +27,16 @@
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_index::vec::IndexVec;
use rustc_middle::mir::visit::Visitor as _;
use rustc_middle::mir::{dump_mir, traversal, Body, ConstQualifs, MirPass, MirPhase, Promoted};
use rustc_middle::mir::{traversal, Body, ConstQualifs, MirPass, MirPhase, Promoted};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
use rustc_span::{Span, Symbol};
#[macro_use]
mod pass_manager;
use pass_manager::{self as pm, Lint, MirLint, WithMinOptLevel};
mod abort_unwinding_calls;
mod add_call_guards;
mod add_moves_for_packed_drops;
@ -56,6 +61,7 @@
mod instcombine;
mod lower_intrinsics;
mod lower_slice_len;
mod marker;
mod match_branches;
mod multiple_return_terminators;
mod normalize_array_len;
@ -168,66 +174,6 @@ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
set
}
fn run_passes(
tcx: TyCtxt<'tcx>,
body: &mut Body<'tcx>,
mir_phase: MirPhase,
passes: &[&[&dyn MirPass<'tcx>]],
) {
let phase_index = mir_phase.phase_index();
let validate = tcx.sess.opts.debugging_opts.validate_mir;
if body.phase >= mir_phase {
return;
}
if validate {
validate::Validator { when: format!("input to phase {:?}", mir_phase), mir_phase }
.run_pass(tcx, body);
}
let mut index = 0;
let mut run_pass = |pass: &dyn MirPass<'tcx>| {
let run_hooks = |body: &_, index, is_after| {
let disambiguator = if is_after { "after" } else { "before" };
dump_mir(
tcx,
Some(&format_args!("{:03}-{:03}", phase_index, index)),
&pass.name(),
&disambiguator,
body,
|_, _| Ok(()),
);
};
run_hooks(body, index, false);
pass.run_pass(tcx, body);
run_hooks(body, index, true);
if validate {
validate::Validator {
when: format!("after {} in phase {:?}", pass.name(), mir_phase),
mir_phase,
}
.run_pass(tcx, body);
}
index += 1;
};
for pass_group in passes {
for pass in *pass_group {
run_pass(*pass);
}
}
body.phase = mir_phase;
if mir_phase == MirPhase::Optimization {
validate::Validator { when: format!("end of phase {:?}", mir_phase), mir_phase }
.run_pass(tcx, body);
}
}
fn mir_const_qualif(tcx: TyCtxt<'_>, def: ty::WithOptConstParam<LocalDefId>) -> ConstQualifs {
let const_kind = tcx.hir().body_const_context(def.did);
@ -279,19 +225,19 @@ fn mir_const<'tcx>(
rustc_middle::mir::dump_mir(tcx, None, "mir_map", &0, &body, |_, _| Ok(()));
run_passes(
pm::run_passes(
tcx,
&mut body,
MirPhase::Const,
&[&[
&[
// MIR-level lints.
&check_packed_ref::CheckPackedRef,
&check_const_item_mutation::CheckConstItemMutation,
&function_item_references::FunctionItemReferences,
&Lint(check_packed_ref::CheckPackedRef),
&Lint(check_const_item_mutation::CheckConstItemMutation),
&Lint(function_item_references::FunctionItemReferences),
// What we need to do constant evaluation.
&simplify::SimplifyCfg::new("initial"),
&rustc_peek::SanityCheck,
]],
&rustc_peek::SanityCheck, // Just a lint
&marker::PhaseChange(MirPhase::Const),
],
);
tcx.alloc_steal_mir(body)
}
@ -318,17 +264,17 @@ fn mir_promoted(
}
body.required_consts = required_consts;
// What we need to run borrowck etc.
let promote_pass = promote_consts::PromoteTemps::default();
let promote: &[&dyn MirPass<'tcx>] = &[
// What we need to run borrowck etc.
&promote_pass,
&simplify::SimplifyCfg::new("promote-consts"),
];
let opt_coverage: &[&dyn MirPass<'tcx>] =
if tcx.sess.instrument_coverage() { &[&coverage::InstrumentCoverage] } else { &[] };
run_passes(tcx, &mut body, MirPhase::ConstPromotion, &[promote, opt_coverage]);
pm::run_passes(
tcx,
&mut body,
&[
&promote_pass,
&simplify::SimplifyCfg::new("promote-consts"),
&coverage::InstrumentCoverage,
],
);
let promoted = promote_pass.promoted_fragments.into_inner();
(tcx.alloc_steal_mir(body), tcx.alloc_steal_promoted(promoted))
@ -390,19 +336,10 @@ fn inner_mir_for_ctfe(tcx: TyCtxt<'_>, def: ty::WithOptConstParam<LocalDefId>) -
// Technically we want to not run on regular const items, but oli-obk doesn't know how to
// conveniently detect that at this point without looking at the HIR.
hir::ConstContext::Const => {
#[rustfmt::skip]
let optimizations: &[&dyn MirPass<'_>] = &[
&const_prop::ConstProp,
];
#[rustfmt::skip]
run_passes(
pm::run_passes(
tcx,
&mut body,
MirPhase::Optimization,
&[
optimizations,
],
&[&const_prop::ConstProp, &marker::PhaseChange(MirPhase::Optimization)],
);
}
}
@ -438,7 +375,7 @@ fn mir_drops_elaborated_and_const_checked<'tcx>(
let def = ty::WithOptConstParam::unknown(did);
// Do not compute the mir call graph without said call graph actually being used.
if inline::is_enabled(tcx) {
if inline::Inline.is_enabled(&tcx.sess) {
let _ = tcx.mir_inliner_callees(ty::InstanceDef::Item(def));
}
}
@ -447,19 +384,23 @@ fn mir_drops_elaborated_and_const_checked<'tcx>(
let mut body = body.steal();
// IMPORTANT
remove_false_edges::RemoveFalseEdges.run_pass(tcx, &mut body);
pm::run_passes(tcx, &mut body, &[&remove_false_edges::RemoveFalseEdges]);
// Do a little drop elaboration before const-checking if `const_precise_live_drops` is enabled.
//
// FIXME: Can't use `run_passes` for these, since `run_passes` SILENTLY DOES NOTHING IF THE MIR
// PHASE DOESN'T CHANGE.
if check_consts::post_drop_elaboration::checking_enabled(&ConstCx::new(tcx, &body)) {
simplify::SimplifyCfg::new("remove-false-edges").run_pass(tcx, &mut body);
remove_uninit_drops::RemoveUninitDrops.run_pass(tcx, &mut body);
check_consts::post_drop_elaboration::check_live_drops(tcx, &body);
pm::run_passes(
tcx,
&mut body,
&[
&simplify::SimplifyCfg::new("remove-false-edges"),
&remove_uninit_drops::RemoveUninitDrops,
],
);
check_consts::post_drop_elaboration::check_live_drops(tcx, &body); // FIXME: make this a MIR lint
}
run_post_borrowck_cleanup_passes(tcx, &mut body);
assert!(body.phase == MirPhase::DropLowering);
tcx.alloc_steal_mir(body)
}
@ -493,95 +434,73 @@ fn run_post_borrowck_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tc
&deaggregator::Deaggregator,
];
run_passes(tcx, body, MirPhase::DropLowering, &[post_borrowck_cleanup]);
pm::run_passes(tcx, body, post_borrowck_cleanup);
}
fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let mir_opt_level = tcx.sess.mir_opt_level();
fn o1<T>(x: T) -> WithMinOptLevel<T> {
WithMinOptLevel(1, x)
}
// 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>] = &[
&reveal_all::RevealAll, // has to be done before inlining, since inlined code is in RevealAll mode.
&lower_slice_len::LowerSliceLenCalls, // has to be done before inlining, otherwise actual call will be almost always inlined. Also simple, so can just do first
&normalize_array_len::NormalizeArrayLen, // has to run after `slice::len` lowering
&unreachable_prop::UnreachablePropagation,
&uninhabited_enum_branching::UninhabitedEnumBranching,
&simplify::SimplifyCfg::new("after-uninhabited-enum-branching"),
&inline::Inline,
&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>] = &[
&remove_storage_markers::RemoveStorageMarkers,
&remove_zsts::RemoveZsts,
&const_goto::ConstGoto,
&remove_unneeded_drops::RemoveUnneededDrops,
&match_branches::MatchBranchSimplification,
// inst combine is after MatchBranchSimplification to clean up Ne(_1, false)
&multiple_return_terminators::MultipleReturnTerminators,
&instcombine::InstCombine,
&separate_const_switch::SeparateConstSwitch,
&const_prop::ConstProp,
&simplify_branches::SimplifyConstCondition::new("after-const-prop"),
&early_otherwise_branch::EarlyOtherwiseBranch,
&simplify_comparison_integral::SimplifyComparisonIntegral,
&simplify_try::SimplifyArmIdentity,
&simplify_try::SimplifyBranchSame,
&dest_prop::DestinationPropagation,
&simplify_branches::SimplifyConstCondition::new("final"),
&remove_noop_landing_pads::RemoveNoopLandingPads,
&simplify::SimplifyCfg::new("final"),
&nrvo::RenameReturnPlace,
&const_debuginfo::ConstDebugInfo,
&simplify::SimplifyLocals,
&multiple_return_terminators::MultipleReturnTerminators,
&deduplicate_blocks::DeduplicateBlocks,
];
// Optimizations to run even if mir optimizations have been disabled.
let no_optimizations: &[&dyn MirPass<'tcx>] = &[
// 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"),
];
// End of pass declarations, now actually run the passes.
// Generator Lowering
#[rustfmt::skip]
run_passes(
pm::run_passes(
tcx,
body,
MirPhase::GeneratorLowering,
&[
if mir_opt_level > 0 {
optimizations_with_generators
} else {
no_optimizations_with_generators
}
&reveal_all::RevealAll, // has to be done before inlining, since inlined code is in RevealAll mode.
&lower_slice_len::LowerSliceLenCalls, // has to be done before inlining, otherwise actual call will be almost always inlined. Also simple, so can just do first
&normalize_array_len::NormalizeArrayLen, // has to run after `slice::len` lowering
&unreachable_prop::UnreachablePropagation,
&uninhabited_enum_branching::UninhabitedEnumBranching,
&o1(simplify::SimplifyCfg::new("after-uninhabited-enum-branching")),
&inline::Inline,
&generator::StateTransform,
],
);
// Main optimization passes
#[rustfmt::skip]
run_passes(
assert!(body.phase == MirPhase::GeneratorLowering);
// The main optimizations that we do on MIR.
pm::run_passes(
tcx,
body,
MirPhase::Optimization,
&[
if mir_opt_level > 0 { optimizations } else { no_optimizations },
pre_codegen_cleanup,
&remove_storage_markers::RemoveStorageMarkers,
&remove_zsts::RemoveZsts,
&const_goto::ConstGoto,
&remove_unneeded_drops::RemoveUnneededDrops,
&match_branches::MatchBranchSimplification,
// inst combine is after MatchBranchSimplification to clean up Ne(_1, false)
&multiple_return_terminators::MultipleReturnTerminators,
&instcombine::InstCombine,
&separate_const_switch::SeparateConstSwitch,
//
// FIXME(#70073): This pass is responsible for both optimization as well as some lints.
&const_prop::ConstProp,
//
// FIXME: The old pass manager ran this only at mir-opt-level >= 1, but
// const-prop runs unconditionally. Should this run unconditionally as well?
&o1(simplify_branches::SimplifyConstCondition::new("after-const-prop")),
&early_otherwise_branch::EarlyOtherwiseBranch,
&simplify_comparison_integral::SimplifyComparisonIntegral,
&simplify_try::SimplifyArmIdentity,
&simplify_try::SimplifyBranchSame,
&dest_prop::DestinationPropagation,
&o1(simplify_branches::SimplifyConstCondition::new("final")),
&o1(remove_noop_landing_pads::RemoveNoopLandingPads),
&o1(simplify::SimplifyCfg::new("final")),
&nrvo::RenameReturnPlace,
&const_debuginfo::ConstDebugInfo,
&simplify::SimplifyLocals,
&multiple_return_terminators::MultipleReturnTerminators,
&deduplicate_blocks::DeduplicateBlocks,
// Some cleanup necessary at least for LLVM and potentially other codegen backends.
&add_call_guards::CriticalCallEdges,
&marker::PhaseChange(MirPhase::Optimization),
// Dump the end result for testing and debugging purposes.
&dump_mir::Marker("PreCodegen"),
],
);
}

View file

@ -10,6 +10,10 @@
pub struct LowerSliceLenCalls;
impl<'tcx> MirPass<'tcx> for LowerSliceLenCalls {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.opts.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
lower_slice_len_calls(tcx, body)
}

View file

@ -0,0 +1,20 @@
use std::borrow::Cow;
use crate::MirPass;
use rustc_middle::mir::{Body, MirPhase};
use rustc_middle::ty::TyCtxt;
/// Changes the MIR phase without changing the MIR itself.
pub struct PhaseChange(pub MirPhase);
impl<'tcx> MirPass<'tcx> for PhaseChange {
fn phase_change(&self) -> Option<MirPhase> {
Some(self.0)
}
fn name(&self) -> Cow<'_, str> {
Cow::from(format!("PhaseChange-{:?}", self.0))
}
fn run_pass(&self, _: TyCtxt<'tcx>, _body: &mut Body<'tcx>) {}
}

View file

@ -40,11 +40,11 @@
/// ```
impl<'tcx> MirPass<'tcx> for MatchBranchSimplification {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.mir_opt_level() < 3 {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 3
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id();
let param_env = tcx.param_env(def_id);

View file

@ -9,11 +9,11 @@
pub struct MultipleReturnTerminators;
impl<'tcx> MirPass<'tcx> for MultipleReturnTerminators {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.mir_opt_level() < 4 {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 4
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// find basic blocks with no statement and a return terminator
let mut bbs_simple_returns = BitSet::new_empty(body.basic_blocks().len());
let def_id = body.source.def_id();

View file

@ -14,11 +14,11 @@
pub struct NormalizeArrayLen;
impl<'tcx> MirPass<'tcx> for NormalizeArrayLen {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.mir_opt_level() < 4 {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 4
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// early returns for edge cases of highly unrolled functions
if body.basic_blocks().len() > MAX_NUM_BLOCKS {
return;

View file

@ -33,11 +33,11 @@
pub struct RenameReturnPlace;
impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
if tcx.sess.mir_opt_level() == 0 {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
let def_id = body.source.def_id();
let returned_local = match local_eligible_for_nrvo(body) {
Some(l) => l,

View file

@ -0,0 +1,144 @@
use std::borrow::Cow;
use rustc_middle::mir::{self, Body, MirPhase};
use rustc_middle::ty::TyCtxt;
use rustc_session::Session;
use crate::{validate, MirPass};
/// Just like `MirPass`, except it cannot mutate `Body`.
pub trait MirLint<'tcx> {
fn name(&self) -> Cow<'_, str> {
let name = std::any::type_name::<Self>();
if let Some(tail) = name.rfind(':') {
Cow::from(&name[tail + 1..])
} else {
Cow::from(name)
}
}
fn is_enabled(&self, _sess: &Session) -> bool {
true
}
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>);
}
/// An adapter for `MirLint`s that implements `MirPass`.
#[derive(Debug, Clone)]
pub struct Lint<T>(pub T);
impl<T> MirPass<'tcx> for Lint<T>
where
T: MirLint<'tcx>,
{
fn name(&self) -> Cow<'_, str> {
self.0.name()
}
fn is_enabled(&self, sess: &Session) -> bool {
self.0.is_enabled(sess)
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
self.0.run_lint(tcx, body)
}
fn is_mir_dump_enabled(&self) -> bool {
false
}
}
pub struct WithMinOptLevel<T>(pub u32, pub T);
impl<T> MirPass<'tcx> for WithMinOptLevel<T>
where
T: MirPass<'tcx>,
{
fn name(&self) -> Cow<'_, str> {
self.1.name()
}
fn is_enabled(&self, sess: &Session) -> bool {
sess.mir_opt_level() >= self.0 as usize
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
self.1.run_pass(tcx, body)
}
fn phase_change(&self) -> Option<MirPhase> {
self.1.phase_change()
}
}
pub fn run_passes(tcx: TyCtxt<'tcx>, body: &'mir mut Body<'tcx>, passes: &[&dyn MirPass<'tcx>]) {
let start_phase = body.phase;
let mut cnt = 0;
let validate = tcx.sess.opts.debugging_opts.validate_mir;
if validate {
validate_body(tcx, body, format!("start of phase transition from {:?}", start_phase));
}
for pass in passes {
if !pass.is_enabled(&tcx.sess) {
continue;
}
let name = pass.name();
let dump_enabled = pass.is_mir_dump_enabled();
if dump_enabled {
dump_mir(tcx, body, start_phase, &name, cnt, false);
}
pass.run_pass(tcx, body);
if dump_enabled {
dump_mir(tcx, body, start_phase, &name, cnt, true);
cnt += 1;
}
if let Some(new_phase) = pass.phase_change() {
if body.phase >= new_phase {
panic!("Invalid MIR phase transition from {:?} to {:?}", body.phase, new_phase);
}
body.phase = new_phase;
}
if validate {
validate_body(tcx, body, format!("after pass {}", pass.name()));
}
}
if validate || body.phase == MirPhase::Optimization {
validate_body(tcx, body, format!("end of phase transition to {:?}", body.phase));
}
}
pub fn validate_body(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, when: String) {
validate::Validator { when, mir_phase: body.phase }.run_pass(tcx, body);
}
pub fn dump_mir(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
phase: MirPhase,
pass_name: &str,
cnt: usize,
is_after: bool,
) {
let phase_index = phase as u32;
mir::dump_mir(
tcx,
Some(&format_args!("{:03}-{:03}", phase_index, cnt)),
pass_name,
if is_after { &"after" } else { &"before" },
body,
|_, _| Ok(()),
);
}

View file

@ -10,18 +10,14 @@
/// code for these.
pub struct RemoveNoopLandingPads;
pub fn remove_noop_landing_pads<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.panic_strategy() == PanicStrategy::Abort {
return;
}
debug!("remove_noop_landing_pads({:?})", body);
RemoveNoopLandingPads.remove_nop_landing_pads(body)
}
impl<'tcx> MirPass<'tcx> for RemoveNoopLandingPads {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
remove_noop_landing_pads(tcx, body);
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.panic_strategy() != PanicStrategy::Abort
}
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
debug!("remove_noop_landing_pads({:?})", body);
self.remove_nop_landing_pads(body)
}
}

View file

@ -7,6 +7,10 @@
pub struct RemoveStorageMarkers;
impl<'tcx> MirPass<'tcx> for RemoveStorageMarkers {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.emit_lifetime_markers() {
return;

View file

@ -8,6 +8,10 @@
pub struct RemoveZsts;
impl<'tcx> MirPass<'tcx> for RemoveZsts {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// Avoid query cycles (generators require optimized MIR for layout).
if tcx.type_of(body.source.def_id()).is_generator() {

View file

@ -8,15 +8,18 @@
pub struct RevealAll;
impl<'tcx> MirPass<'tcx> for RevealAll {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.opts.mir_opt_level() >= 3 || super::inline::Inline.is_enabled(sess)
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// This pass must run before inlining, since we insert callee bodies in RevealAll mode.
// Do not apply this transformation to generators.
if (tcx.sess.mir_opt_level() >= 3 || super::inline::is_enabled(tcx))
&& body.generator.is_none()
{
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
RevealAllVisitor { tcx, param_env }.visit_body(body);
if body.generator.is_some() {
return;
}
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
RevealAllVisitor { tcx, param_env }.visit_body(body);
}
}

View file

@ -45,11 +45,11 @@
pub struct SeparateConstSwitch;
impl<'tcx> MirPass<'tcx> for SeparateConstSwitch {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.mir_opt_level() < 4 {
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 4
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// If execution did something, applying a simplification layer
// helps later passes optimize the copy away.
if separate_const_switch(body) > 0 {

View file

@ -17,8 +17,8 @@
use crate::util::expand_aggregate;
use crate::{
abort_unwinding_calls, add_call_guards, add_moves_for_packed_drops, remove_noop_landing_pads,
run_passes, simplify,
abort_unwinding_calls, add_call_guards, add_moves_for_packed_drops, marker, pass_manager as pm,
remove_noop_landing_pads, simplify,
};
use rustc_middle::mir::patch::MirPatch;
use rustc_mir_dataflow::elaborate_drops::{self, DropElaborator, DropFlagMode, DropStyle};
@ -75,17 +75,25 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
};
debug!("make_shim({:?}) = untransformed {:?}", instance, result);
run_passes(
// In some of the above cases, we seem to be invoking the passes for non-shim MIR bodies.
// If that happens, there's no need to run them again.
//
// FIXME: Is this intentional?
if result.phase >= MirPhase::Const {
return result;
}
pm::run_passes(
tcx,
&mut result,
MirPhase::Const,
&[&[
&[
&add_moves_for_packed_drops::AddMovesForPackedDrops,
&remove_noop_landing_pads::RemoveNoopLandingPads,
&simplify::SimplifyCfg::new("make_shim"),
&add_call_guards::CriticalCallEdges,
&abort_unwinding_calls::AbortUnwindingCalls,
]],
&marker::PhaseChange(MirPhase::Const),
],
);
debug!("make_shim({:?}) = {:?}", instance, result);

View file

@ -368,6 +368,10 @@ fn save_unreachable_coverage(
pub struct SimplifyLocals;
impl<'tcx> MirPass<'tcx> for SimplifyLocals {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
trace!("running SimplifyLocals on {:?}", body.source);
simplify_locals(body, tcx);

View file

@ -26,6 +26,10 @@
pub struct SimplifyComparisonIntegral;
impl<'tcx> MirPass<'tcx> for SimplifyComparisonIntegral {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
trace!("Running SimplifyComparisonIntegral on {:?}", body.source);

View file

@ -70,6 +70,10 @@ fn variant_discriminants<'tcx>(
}
impl<'tcx> MirPass<'tcx> for UninhabitedEnumBranching {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if body.source.promoted.is_some() {
return;

View file

@ -11,13 +11,13 @@
pub struct UnreachablePropagation;
impl MirPass<'_> for UnreachablePropagation {
fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
if tcx.sess.mir_opt_level() < 4 {
// Enable only under -Zmir-opt-level=4 as in some cases (check the deeply-nested-opt
// perf benchmark) LLVM may spend quite a lot of time optimizing the generated code.
return;
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
// Enable only under -Zmir-opt-level=4 as in some cases (check the deeply-nested-opt
// perf benchmark) LLVM may spend quite a lot of time optimizing the generated code.
sess.mir_opt_level() >= 4
}
fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let mut unreachable_blocks = FxHashSet::default();
let mut replacements = FxHashMap::default();

View file

@ -101,6 +101,29 @@ pub fn dep_tracking_hash(&self, for_crate_hash: bool) -> u64 {
);
}
impl Options {
pub fn mir_opt_level(&self) -> usize {
self.debugging_opts
.mir_opt_level
.unwrap_or_else(|| if self.optimize != OptLevel::No { 2 } else { 1 })
}
pub fn instrument_coverage(&self) -> bool {
self.debugging_opts.instrument_coverage.unwrap_or(InstrumentCoverage::Off)
!= InstrumentCoverage::Off
}
pub fn instrument_coverage_except_unused_generics(&self) -> bool {
self.debugging_opts.instrument_coverage.unwrap_or(InstrumentCoverage::Off)
== InstrumentCoverage::ExceptUnusedGenerics
}
pub fn instrument_coverage_except_unused_functions(&self) -> bool {
self.debugging_opts.instrument_coverage.unwrap_or(InstrumentCoverage::Off)
== InstrumentCoverage::ExceptUnusedFunctions
}
}
top_level_options!(
/// The top-level command-line options struct.
///

View file

@ -562,10 +562,7 @@ pub fn binary_dep_depinfo(&self) -> bool {
self.opts.debugging_opts.binary_dep_depinfo
}
pub fn mir_opt_level(&self) -> usize {
self.opts
.debugging_opts
.mir_opt_level
.unwrap_or_else(|| if self.opts.optimize != config::OptLevel::No { 2 } else { 1 })
self.opts.mir_opt_level()
}
/// Gets the features enabled for the current compilation session.
@ -1047,18 +1044,15 @@ pub fn link_dead_code(&self) -> bool {
}
pub fn instrument_coverage(&self) -> bool {
self.opts.debugging_opts.instrument_coverage.unwrap_or(config::InstrumentCoverage::Off)
!= config::InstrumentCoverage::Off
self.opts.instrument_coverage()
}
pub fn instrument_coverage_except_unused_generics(&self) -> bool {
self.opts.debugging_opts.instrument_coverage.unwrap_or(config::InstrumentCoverage::Off)
== config::InstrumentCoverage::ExceptUnusedGenerics
self.opts.instrument_coverage_except_unused_generics()
}
pub fn instrument_coverage_except_unused_functions(&self) -> bool {
self.opts.debugging_opts.instrument_coverage.unwrap_or(config::InstrumentCoverage::Off)
== config::InstrumentCoverage::ExceptUnusedFunctions
self.opts.instrument_coverage_except_unused_functions()
}
pub fn is_proc_macro_attr(&self, attr: &Attribute) -> bool {

View file

@ -1,4 +1,4 @@
// compile-flags: -Z mir-opt-level=4
// compile-flags: -Z mir-opt-level=4 -Zunsound-mir-opts
// must not optimize as it does not follow the pattern of
// left and right hand side being the same variant