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