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