rustc_mir_transform/
pass_manager.rs

1use std::cell::RefCell;
2use std::collections::hash_map::Entry;
3
4use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
5use rustc_middle::mir::{self, Body, MirPhase, RuntimePhase};
6use rustc_middle::ty::TyCtxt;
7use rustc_session::Session;
8use tracing::trace;
9
10use crate::lint::lint_body;
11use crate::{errors, validate};
12
13thread_local! {
14    /// Maps MIR pass names to a snake case form to match profiling naming style
15    static PASS_TO_PROFILER_NAMES: RefCell<FxHashMap<&'static str, &'static str>> = {
16        RefCell::new(FxHashMap::default())
17    };
18}
19
20/// Converts a MIR pass name into a snake case form to match the profiling naming style.
21fn to_profiler_name(type_name: &'static str) -> &'static str {
22    PASS_TO_PROFILER_NAMES.with(|names| match names.borrow_mut().entry(type_name) {
23        Entry::Occupied(e) => *e.get(),
24        Entry::Vacant(e) => {
25            let snake_case: String = type_name
26                .chars()
27                .flat_map(|c| {
28                    if c.is_ascii_uppercase() {
29                        vec!['_', c.to_ascii_lowercase()]
30                    } else if c == '-' {
31                        vec!['_']
32                    } else {
33                        vec![c]
34                    }
35                })
36                .collect();
37            let result = &*String::leak(format!("mir_pass{}", snake_case));
38            e.insert(result);
39            result
40        }
41    })
42}
43
44// const wrapper for `if let Some((_, tail)) = name.rsplit_once(':') { tail } else { name }`
45const fn c_name(name: &'static str) -> &'static str {
46    // FIXME(const-hack) Simplify the implementation once more `str` methods get const-stable.
47    // and inline into call site
48    let bytes = name.as_bytes();
49    let mut i = bytes.len();
50    while i > 0 && bytes[i - 1] != b':' {
51        i = i - 1;
52    }
53    let (_, bytes) = bytes.split_at(i);
54    match std::str::from_utf8(bytes) {
55        Ok(name) => name,
56        Err(_) => name,
57    }
58}
59
60/// A streamlined trait that you can implement to create a pass; the
61/// pass will be named after the type, and it will consist of a main
62/// loop that goes over each available MIR and applies `run_pass`.
63pub(super) trait MirPass<'tcx> {
64    fn name(&self) -> &'static str {
65        // FIXME(const-hack) Simplify the implementation once more `str` methods get const-stable.
66        // See copypaste in `MirLint`
67        const {
68            let name = std::any::type_name::<Self>();
69            c_name(name)
70        }
71    }
72
73    fn profiler_name(&self) -> &'static str {
74        to_profiler_name(self.name())
75    }
76
77    /// Returns `true` if this pass is enabled with the current combination of compiler flags.
78    fn is_enabled(&self, _sess: &Session) -> bool {
79        true
80    }
81
82    /// Returns `true` if this pass can be overridden by `-Zenable-mir-passes`. This should be
83    /// true for basically every pass other than those that are necessary for correctness.
84    fn can_be_overridden(&self) -> bool {
85        true
86    }
87
88    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>);
89
90    fn is_mir_dump_enabled(&self) -> bool {
91        true
92    }
93
94    /// Returns `true` if this pass must be run (i.e. it is required for soundness).
95    /// For passes which are strictly optimizations, this should return `false`.
96    /// If this is `false`, `#[optimize(none)]` will disable the pass.
97    fn is_required(&self) -> bool;
98}
99
100/// Just like `MirPass`, except it cannot mutate `Body`, and MIR dumping is
101/// disabled (via the `Lint` adapter).
102pub(super) trait MirLint<'tcx> {
103    fn name(&self) -> &'static str {
104        // FIXME(const-hack) Simplify the implementation once more `str` methods get const-stable.
105        // See copypaste in `MirPass`
106        const {
107            let name = std::any::type_name::<Self>();
108            c_name(name)
109        }
110    }
111
112    fn is_enabled(&self, _sess: &Session) -> bool {
113        true
114    }
115
116    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>);
117}
118
119/// An adapter for `MirLint`s that implements `MirPass`.
120#[derive(Debug, Clone)]
121pub(super) struct Lint<T>(pub T);
122
123impl<'tcx, T> MirPass<'tcx> for Lint<T>
124where
125    T: MirLint<'tcx>,
126{
127    fn name(&self) -> &'static str {
128        self.0.name()
129    }
130
131    fn is_enabled(&self, sess: &Session) -> bool {
132        self.0.is_enabled(sess)
133    }
134
135    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
136        self.0.run_lint(tcx, body)
137    }
138
139    fn is_mir_dump_enabled(&self) -> bool {
140        false
141    }
142
143    fn is_required(&self) -> bool {
144        true
145    }
146}
147
148pub(super) struct WithMinOptLevel<T>(pub u32, pub T);
149
150impl<'tcx, T> MirPass<'tcx> for WithMinOptLevel<T>
151where
152    T: MirPass<'tcx>,
153{
154    fn name(&self) -> &'static str {
155        self.1.name()
156    }
157
158    fn is_enabled(&self, sess: &Session) -> bool {
159        sess.mir_opt_level() >= self.0 as usize
160    }
161
162    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
163        self.1.run_pass(tcx, body)
164    }
165
166    fn is_required(&self) -> bool {
167        self.1.is_required()
168    }
169}
170
171/// Whether to allow non-[required] optimizations
172///
173/// [required]: MirPass::is_required
174#[derive(Copy, Clone, Debug, PartialEq, Eq)]
175pub(crate) enum Optimizations {
176    Suppressed,
177    Allowed,
178}
179
180/// Run the sequence of passes without validating the MIR after each pass. The MIR is still
181/// validated at the end.
182pub(super) fn run_passes_no_validate<'tcx>(
183    tcx: TyCtxt<'tcx>,
184    body: &mut Body<'tcx>,
185    passes: &[&dyn MirPass<'tcx>],
186    phase_change: Option<MirPhase>,
187) {
188    run_passes_inner(tcx, body, passes, phase_change, false, Optimizations::Allowed);
189}
190
191/// The optional `phase_change` is applied after executing all the passes, if present
192pub(super) fn run_passes<'tcx>(
193    tcx: TyCtxt<'tcx>,
194    body: &mut Body<'tcx>,
195    passes: &[&dyn MirPass<'tcx>],
196    phase_change: Option<MirPhase>,
197    optimizations: Optimizations,
198) {
199    run_passes_inner(tcx, body, passes, phase_change, true, optimizations);
200}
201
202pub(super) fn should_run_pass<'tcx, P>(
203    tcx: TyCtxt<'tcx>,
204    pass: &P,
205    optimizations: Optimizations,
206) -> bool
207where
208    P: MirPass<'tcx> + ?Sized,
209{
210    let name = pass.name();
211
212    if !pass.can_be_overridden() {
213        return pass.is_enabled(tcx.sess);
214    }
215
216    let overridden_passes = &tcx.sess.opts.unstable_opts.mir_enable_passes;
217    let overridden =
218        overridden_passes.iter().rev().find(|(s, _)| s == &*name).map(|(_name, polarity)| {
219            trace!(
220                pass = %name,
221                "{} as requested by flag",
222                if *polarity { "Running" } else { "Not running" },
223            );
224            *polarity
225        });
226    let suppressed = !pass.is_required() && matches!(optimizations, Optimizations::Suppressed);
227    overridden.unwrap_or_else(|| !suppressed && pass.is_enabled(tcx.sess))
228}
229
230fn run_passes_inner<'tcx>(
231    tcx: TyCtxt<'tcx>,
232    body: &mut Body<'tcx>,
233    passes: &[&dyn MirPass<'tcx>],
234    phase_change: Option<MirPhase>,
235    validate_each: bool,
236    optimizations: Optimizations,
237) {
238    let overridden_passes = &tcx.sess.opts.unstable_opts.mir_enable_passes;
239    trace!(?overridden_passes);
240
241    let named_passes: FxIndexSet<_> =
242        overridden_passes.iter().map(|(name, _)| name.as_str()).collect();
243
244    for &name in named_passes.difference(&*crate::PASS_NAMES) {
245        tcx.dcx().emit_warn(errors::UnknownPassName { name });
246    }
247
248    // Verify that no passes are missing from the `declare_passes` invocation
249    #[cfg(debug_assertions)]
250    #[allow(rustc::diagnostic_outside_of_impl)]
251    #[allow(rustc::untranslatable_diagnostic)]
252    {
253        let used_passes: FxIndexSet<_> = passes.iter().map(|p| p.name()).collect();
254
255        let undeclared = used_passes.difference(&*crate::PASS_NAMES).collect::<Vec<_>>();
256        if let Some((name, rest)) = undeclared.split_first() {
257            let mut err =
258                tcx.dcx().struct_bug(format!("pass `{name}` is not declared in `PASS_NAMES`"));
259            for name in rest {
260                err.note(format!("pass `{name}` is also not declared in `PASS_NAMES`"));
261            }
262            err.emit();
263        }
264    }
265
266    let prof_arg = tcx.sess.prof.enabled().then(|| format!("{:?}", body.source.def_id()));
267
268    if !body.should_skip() {
269        let validate = validate_each & tcx.sess.opts.unstable_opts.validate_mir;
270        let lint = tcx.sess.opts.unstable_opts.lint_mir;
271
272        for pass in passes {
273            let name = pass.name();
274
275            if !should_run_pass(tcx, *pass, optimizations) {
276                continue;
277            };
278
279            let dump_enabled = pass.is_mir_dump_enabled();
280
281            if dump_enabled {
282                dump_mir_for_pass(tcx, body, name, false);
283            }
284
285            if let Some(prof_arg) = &prof_arg {
286                tcx.sess
287                    .prof
288                    .generic_activity_with_arg(pass.profiler_name(), &**prof_arg)
289                    .run(|| pass.run_pass(tcx, body));
290            } else {
291                pass.run_pass(tcx, body);
292            }
293
294            if dump_enabled {
295                dump_mir_for_pass(tcx, body, name, true);
296            }
297            if validate {
298                validate_body(tcx, body, format!("after pass {name}"));
299            }
300            if lint {
301                lint_body(tcx, body, format!("after pass {name}"));
302            }
303
304            body.pass_count += 1;
305        }
306    }
307
308    if let Some(new_phase) = phase_change {
309        if body.phase >= new_phase {
310            panic!("Invalid MIR phase transition from {:?} to {:?}", body.phase, new_phase);
311        }
312
313        body.phase = new_phase;
314        body.pass_count = 0;
315
316        dump_mir_for_phase_change(tcx, body);
317
318        let validate =
319            (validate_each & tcx.sess.opts.unstable_opts.validate_mir & !body.should_skip())
320                || new_phase == MirPhase::Runtime(RuntimePhase::Optimized);
321        let lint = tcx.sess.opts.unstable_opts.lint_mir & !body.should_skip();
322        if validate {
323            validate_body(tcx, body, format!("after phase change to {}", new_phase.name()));
324        }
325        if lint {
326            lint_body(tcx, body, format!("after phase change to {}", new_phase.name()));
327        }
328
329        body.pass_count = 1;
330    }
331}
332
333pub(super) fn validate_body<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, when: String) {
334    validate::Validator { when }.run_pass(tcx, body);
335}
336
337fn dump_mir_for_pass<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, pass_name: &str, is_after: bool) {
338    mir::dump_mir(
339        tcx,
340        true,
341        pass_name,
342        if is_after { &"after" } else { &"before" },
343        body,
344        |_, _| Ok(()),
345    );
346}
347
348pub(super) fn dump_mir_for_phase_change<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
349    assert_eq!(body.pass_count, 0);
350    mir::dump_mir(tcx, true, body.phase.name(), &"after", body, |_, _| Ok(()))
351}