Skip to main content

rustc_mir_dataflow/impls/
storage_liveness.rs

1use std::borrow::Cow;
2
3use rustc_data_structures::fx::FxHashMap;
4use rustc_index::bit_set::DenseBitSet;
5use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
6use rustc_middle::mir::*;
7use smallvec::SmallVec;
8
9use super::MaybeBorrowedLocals;
10use crate::{Analysis, GenKill, Results, ResultsCursor};
11
12/// The set of locals in a MIR body that do not have `StorageLive`/`StorageDead` annotations.
13///
14/// These locals have fixed storage for the duration of the body.
15pub fn always_storage_live_locals(body: &Body<'_>) -> DenseBitSet<Local> {
16    let mut always_live_locals = DenseBitSet::new_filled(body.local_decls.len());
17
18    for block in &*body.basic_blocks {
19        for statement in &block.statements {
20            if let StatementKind::StorageLive(l) | StatementKind::StorageDead(l) = statement.kind {
21                always_live_locals.remove(l);
22            }
23        }
24    }
25
26    always_live_locals
27}
28
29pub struct MaybeStorageLive<'a> {
30    always_live_locals: Cow<'a, DenseBitSet<Local>>,
31}
32
33impl<'a> MaybeStorageLive<'a> {
34    pub fn new(always_live_locals: Cow<'a, DenseBitSet<Local>>) -> Self {
35        MaybeStorageLive { always_live_locals }
36    }
37}
38
39impl<'a, 'tcx> Analysis<'tcx> for MaybeStorageLive<'a> {
40    type Domain = DenseBitSet<Local>;
41
42    const NAME: &'static str = "maybe_storage_live";
43
44    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
45        // bottom = dead
46        DenseBitSet::new_empty(body.local_decls.len())
47    }
48
49    fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
50        state.union(&*self.always_live_locals);
51
52        for arg in body.args_iter() {
53            state.insert(arg);
54        }
55    }
56
57    fn apply_primary_statement_effect(
58        &self,
59        state: &mut Self::Domain,
60        stmt: &Statement<'tcx>,
61        _: Location,
62    ) {
63        match stmt.kind {
64            StatementKind::StorageLive(l) => state.gen_(l),
65            StatementKind::StorageDead(l) => state.kill(l),
66            _ => (),
67        }
68    }
69}
70
71pub struct MaybeStorageDead<'a> {
72    always_live_locals: Cow<'a, DenseBitSet<Local>>,
73}
74
75impl<'a> MaybeStorageDead<'a> {
76    pub fn new(always_live_locals: Cow<'a, DenseBitSet<Local>>) -> Self {
77        MaybeStorageDead { always_live_locals }
78    }
79}
80
81impl<'a, 'tcx> Analysis<'tcx> for MaybeStorageDead<'a> {
82    type Domain = DenseBitSet<Local>;
83
84    const NAME: &'static str = "maybe_storage_dead";
85
86    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
87        // bottom = live
88        DenseBitSet::new_empty(body.local_decls.len())
89    }
90
91    fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
92        {
    match (&body.local_decls.len(), &self.always_live_locals.domain_size()) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
93        // Do not iterate on return place and args, as they are trivially always live.
94        for local in body.vars_and_temps_iter() {
95            if !self.always_live_locals.contains(local) {
96                state.insert(local);
97            }
98        }
99    }
100
101    fn apply_primary_statement_effect(
102        &self,
103        state: &mut Self::Domain,
104        stmt: &Statement<'tcx>,
105        _: Location,
106    ) {
107        match stmt.kind {
108            StatementKind::StorageLive(l) => state.kill(l),
109            StatementKind::StorageDead(l) => state.gen_(l),
110            _ => (),
111        }
112    }
113}
114
115/// For each location, records which locals can be killed by `MaybeRequiresStorage`.
116type KillableLocals = FxHashMap<Location, SmallVec<[Local; 4]>>;
117
118/// Dataflow analysis that determines whether each local requires storage at a
119/// given location; i.e. whether its storage can go away without being observed.
120pub struct MaybeRequiresStorage {
121    /// Used to kill locals that are fully moved and have not been borrowed.
122    killable_locals: KillableLocals,
123}
124
125impl MaybeRequiresStorage {
126    pub fn new<'tcx>(
127        body: &Body<'tcx>,
128        borrowed_locals: &Results<'tcx, MaybeBorrowedLocals>,
129    ) -> Self {
130        struct KillableLocalsVisitor<'mir, 'tcx> {
131            borrowed_locals_cursor: ResultsCursor<'mir, 'tcx, MaybeBorrowedLocals>,
132            killable_locals: KillableLocals,
133        }
134
135        impl<'tcx> Visitor<'tcx> for KillableLocalsVisitor<'_, 'tcx> {
136            fn visit_local(&mut self, local: Local, context: PlaceContext, loc: Location) {
137                if PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) == context {
138                    self.borrowed_locals_cursor.seek_before_primary_effect(loc);
139                    if !self.borrowed_locals_cursor.get().contains(local) {
140                        self.killable_locals.entry(loc).or_default().push(local);
141                    }
142                }
143            }
144        }
145
146        let mut visitor = KillableLocalsVisitor {
147            borrowed_locals_cursor: ResultsCursor::new_borrowing(body, borrowed_locals),
148            killable_locals: Default::default(),
149        };
150
151        for (bb, data) in traversal::reachable(body) {
152            visitor.visit_basic_block_data(bb, data);
153        }
154
155        MaybeRequiresStorage { killable_locals: visitor.killable_locals }
156    }
157}
158
159impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage {
160    type Domain = DenseBitSet<Local>;
161
162    const NAME: &'static str = "requires_storage";
163
164    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
165        // bottom = dead
166        DenseBitSet::new_empty(body.local_decls.len())
167    }
168
169    fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
170        // The resume argument is live on function entry (we don't care about
171        // the `self` argument)
172        for arg in body.args_iter().skip(1) {
173            state.insert(arg);
174        }
175    }
176
177    fn apply_early_statement_effect(
178        &self,
179        state: &mut Self::Domain,
180        stmt: &Statement<'tcx>,
181        _loc: Location,
182    ) {
183        // If a place is borrowed in a statement, it needs storage for that statement.
184        MaybeBorrowedLocals::gen_statement(state, stmt);
185
186        match &stmt.kind {
187            StatementKind::StorageDead(l) => state.kill(*l),
188
189            StatementKind::Assign((place, _)) => {
190                state.gen_(place.local);
191            }
192            StatementKind::SetDiscriminant { place, .. } => {
193                state.gen_(place.local);
194            }
195
196            // Nothing to do for these. Match exhaustively so this fails to compile when new
197            // variants are added.
198            StatementKind::AscribeUserType(..)
199            | StatementKind::PlaceMention(..)
200            | StatementKind::Coverage(..)
201            | StatementKind::FakeRead(..)
202            | StatementKind::ConstEvalCounter
203            | StatementKind::Nop
204            | StatementKind::Intrinsic(..)
205            | StatementKind::BackwardIncompatibleDropHint { .. }
206            | StatementKind::StorageLive(..) => {}
207        }
208    }
209
210    fn apply_primary_statement_effect(
211        &self,
212        state: &mut Self::Domain,
213        stmt: &Statement<'tcx>,
214        loc: Location,
215    ) {
216        // If we move from a place then it only stops needing storage *after* that statement.
217        self.check_for_move(state, loc);
218
219        match &stmt.kind {
220            // If a place is assigned to in a statement, it needs storage after that statement.
221            // Even if the place was moved from in the rvalue (e.g. `x = x + 1` or `x = f(move x)`),
222            // the assignment restores a valid value into the place.
223            StatementKind::Assign((place, _)) => {
224                state.gen_(place.local);
225            }
226            StatementKind::SetDiscriminant { place, .. } => {
227                state.gen_(place.local);
228            }
229
230            StatementKind::StorageDead(_)
231            | StatementKind::AscribeUserType(..)
232            | StatementKind::PlaceMention(..)
233            | StatementKind::Coverage(..)
234            | StatementKind::FakeRead(..)
235            | StatementKind::ConstEvalCounter
236            | StatementKind::Nop
237            | StatementKind::Intrinsic(..)
238            | StatementKind::BackwardIncompatibleDropHint { .. }
239            | StatementKind::StorageLive(..) => {}
240        }
241    }
242
243    fn apply_early_terminator_effect(
244        &self,
245        state: &mut Self::Domain,
246        terminator: &Terminator<'tcx>,
247        _loc: Location,
248    ) {
249        // If a place is borrowed in a terminator, it needs storage for that terminator.
250        MaybeBorrowedLocals::gen_terminator(state, terminator);
251
252        match &terminator.kind {
253            TerminatorKind::Call { destination, .. } => {
254                state.gen_(destination.local);
255            }
256
257            // Note that we do *not* gen the `resume_arg` of `Yield` terminators. The reason for
258            // that is that a `yield` will return from the function, and `resume_arg` is written
259            // only when the coroutine is later resumed. Unlike `Call`, this doesn't require the
260            // place to have storage *before* the yield, only after.
261            TerminatorKind::Yield { .. } => {}
262
263            TerminatorKind::InlineAsm { operands, .. } => {
264                for op in operands {
265                    match op {
266                        InlineAsmOperand::Out { place, .. }
267                        | InlineAsmOperand::InOut { out_place: place, .. } => {
268                            if let Some(place) = place {
269                                state.gen_(place.local);
270                            }
271                        }
272                        InlineAsmOperand::In { .. }
273                        | InlineAsmOperand::Const { .. }
274                        | InlineAsmOperand::SymFn { .. }
275                        | InlineAsmOperand::SymStatic { .. }
276                        | InlineAsmOperand::Label { .. } => {}
277                    }
278                }
279            }
280
281            // Nothing to do for these. Match exhaustively so this fails to compile when new
282            // variants are added.
283            TerminatorKind::UnwindTerminate(_)
284            | TerminatorKind::Assert { .. }
285            | TerminatorKind::Drop { .. }
286            | TerminatorKind::FalseEdge { .. }
287            | TerminatorKind::FalseUnwind { .. }
288            | TerminatorKind::CoroutineDrop
289            | TerminatorKind::Goto { .. }
290            | TerminatorKind::UnwindResume
291            | TerminatorKind::Return
292            | TerminatorKind::TailCall { .. }
293            | TerminatorKind::SwitchInt { .. }
294            | TerminatorKind::Unreachable => {}
295        }
296    }
297
298    fn apply_primary_terminator_effect(
299        &self,
300        state: &mut Self::Domain,
301        terminator: &Terminator<'tcx>,
302        loc: Location,
303    ) {
304        match terminator.kind {
305            // For call terminators the destination requires storage for the call
306            // and after the call returns successfully, but not after a panic.
307            // Since `propagate_call_unwind` doesn't exist, we have to kill the
308            // destination here, and then gen it again in `call_return_effect`.
309            TerminatorKind::Call { destination, .. } => {
310                state.kill(destination.local);
311            }
312
313            // The same applies to InlineAsm outputs.
314            TerminatorKind::InlineAsm { ref operands, .. } => {
315                CallReturnPlaces::InlineAsm(operands).for_each(|place| state.kill(place.local));
316            }
317
318            // Nothing to do for these. Match exhaustively so this fails to compile when new
319            // variants are added.
320            TerminatorKind::Yield { .. }
321            | TerminatorKind::UnwindTerminate(_)
322            | TerminatorKind::Assert { .. }
323            | TerminatorKind::Drop { .. }
324            | TerminatorKind::FalseEdge { .. }
325            | TerminatorKind::FalseUnwind { .. }
326            | TerminatorKind::CoroutineDrop
327            | TerminatorKind::Goto { .. }
328            | TerminatorKind::UnwindResume
329            | TerminatorKind::Return
330            | TerminatorKind::TailCall { .. }
331            | TerminatorKind::SwitchInt { .. }
332            | TerminatorKind::Unreachable => {}
333        }
334
335        self.check_for_move(state, loc);
336    }
337
338    fn apply_call_return_effect(
339        &self,
340        state: &mut Self::Domain,
341        _block: BasicBlock,
342        return_places: CallReturnPlaces<'_, 'tcx>,
343    ) {
344        return_places.for_each(|place| state.gen_(place.local));
345    }
346}
347
348impl MaybeRequiresStorage {
349    /// Kill locals that are fully moved and have not been borrowed.
350    fn check_for_move(&self, state: &mut <Self as Analysis<'_>>::Domain, loc: Location) {
351        if let Some(locals) = self.killable_locals.get(&loc) {
352            for &l in locals {
353                state.kill(l);
354            }
355        }
356    }
357}