Skip to main content

rustc_mir_transform/
remove_zsts.rs

1//! Removes operations on ZST places, and convert ZST operands to constants.
2
3use rustc_middle::mir::visit::*;
4use rustc_middle::mir::*;
5use rustc_middle::ty::{self, Ty, TyCtxt};
6
7use crate::PassPolicy;
8
9pub(super) struct RemoveZsts;
10
11impl<'tcx> crate::MirPass<'tcx> for RemoveZsts {
12    fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
13        PassPolicy::optional_non_optimization(sess.mir_opt_level() > 0)
14    }
15
16    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
17        // Avoid query cycles (coroutines require optimized MIR for layout).
18        if tcx.type_of(body.source.def_id()).instantiate_identity().skip_norm_wip().is_coroutine() {
19            return;
20        }
21
22        let typing_env = body.typing_env(tcx);
23        let local_decls = &body.local_decls;
24        let mut replacer = Replacer { tcx, typing_env, local_decls };
25        for var_debug_info in &mut body.var_debug_info {
26            replacer.visit_var_debug_info(var_debug_info);
27        }
28        for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
29            replacer.visit_basic_block_data(bb, data);
30        }
31    }
32}
33
34struct Replacer<'a, 'tcx> {
35    tcx: TyCtxt<'tcx>,
36    typing_env: ty::TypingEnv<'tcx>,
37    local_decls: &'a LocalDecls<'tcx>,
38}
39
40/// A cheap, approximate check to avoid unnecessary `layout_of` calls.
41///
42/// `Some(true)` is definitely ZST; `Some(false)` is definitely *not* ZST.
43///
44/// `None` may or may not be, and must check `layout_of` to be sure.
45fn trivially_zst<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Option<bool> {
46    match ty.kind() {
47        // definitely ZST
48        ty::FnDef(..) | ty::Never => Some(true),
49        ty::Tuple(fields) if fields.is_empty() => Some(true),
50        ty::Array(_ty, len) if let Some(0) = len.try_to_target_usize(tcx) => Some(true),
51        // clearly not ZST
52        ty::Bool
53        | ty::Char
54        | ty::Int(..)
55        | ty::Uint(..)
56        | ty::Float(..)
57        | ty::RawPtr(..)
58        | ty::Ref(..)
59        | ty::FnPtr(..) => Some(false),
60        ty::Coroutine(def_id, _) => {
61            // For async_drop_in_place::{closure} this is load bearing, not just a perf fix,
62            // because we don't want to compute the layout before mir analysis is done
63            if tcx.is_async_drop_in_place_coroutine(*def_id) { Some(false) } else { None }
64        }
65        // check `layout_of` to see (including unreachable things we won't actually see)
66        _ => None,
67    }
68}
69
70impl<'tcx> Replacer<'_, 'tcx> {
71    fn known_to_be_zst(&self, ty: Ty<'tcx>) -> bool {
72        if let Some(is_zst) = trivially_zst(ty, self.tcx) {
73            is_zst
74        } else {
75            self.tcx
76                .layout_of(self.typing_env.as_query_input(ty))
77                .is_ok_and(|layout| layout.is_zst())
78        }
79    }
80
81    fn make_zst(&self, ty: Ty<'tcx>) -> ConstOperand<'tcx> {
82        debug_assert!(self.known_to_be_zst(ty));
83        ConstOperand {
84            span: rustc_span::DUMMY_SP,
85            user_ty: None,
86            const_: Const::Val(ConstValue::ZeroSized, ty),
87        }
88    }
89}
90
91impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
92    fn tcx(&self) -> TyCtxt<'tcx> {
93        self.tcx
94    }
95
96    fn visit_var_debug_info(&mut self, var_debug_info: &mut VarDebugInfo<'tcx>) {
97        match var_debug_info.value {
98            VarDebugInfoContents::Const(_) => {}
99            VarDebugInfoContents::Place(place) => {
100                let place_ty = place.ty(self.local_decls, self.tcx).ty;
101                if self.known_to_be_zst(place_ty) {
102                    var_debug_info.value = VarDebugInfoContents::Const(self.make_zst(place_ty))
103                }
104            }
105        }
106    }
107
108    fn visit_operand(&mut self, operand: &mut Operand<'tcx>, _: Location) {
109        if let Operand::Constant(_) = operand {
110            return;
111        }
112        let op_ty = operand.ty(self.local_decls, self.tcx);
113        if self.known_to_be_zst(op_ty) {
114            *operand = Operand::Constant(Box::new(self.make_zst(op_ty)))
115        }
116    }
117
118    fn visit_statement(&mut self, statement: &mut Statement<'tcx>, loc: Location) {
119        let place_for_ty = match statement.kind {
120            StatementKind::Assign((place, ref rvalue)) => {
121                rvalue.is_safe_to_remove().then_some(place)
122            }
123            StatementKind::SetDiscriminant { ref place, variant_index: _ }
124            | StatementKind::PlaceMention(ref place) => Some(**place),
125            StatementKind::AscribeUserType((place, _), _) | StatementKind::FakeRead((_, place)) => {
126                Some(place)
127            }
128            StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
129                Some(local.into())
130            }
131            StatementKind::Coverage(_)
132            | StatementKind::Intrinsic(_)
133            | StatementKind::Nop
134            | StatementKind::BackwardIncompatibleDropHint { .. }
135            | StatementKind::ConstEvalCounter => None,
136        };
137        if let Some(place_for_ty) = place_for_ty
138            && let ty = place_for_ty.ty(self.local_decls, self.tcx).ty
139            && self.known_to_be_zst(ty)
140        {
141            statement.make_nop(true);
142        } else {
143            self.super_statement(statement, loc);
144        }
145    }
146}