Skip to main content

rustc_mir_dataflow/impls/
storage_liveness.rs

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