1use rustc_index::bit_set::DenseBitSet;
2use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
3use rustc_middle::mir::{self, CallReturnPlaces, Local, Location, Place, StatementKind};
45use crate::{Analysis, Backward, GenKill};
67/// A [live-variable dataflow analysis][liveness].
8///
9/// This analysis considers references as being used only at the point of the
10/// borrow. In other words, this analysis does not track uses because of references that already
11/// exist. See [this `mir-dataflow` test][flow-test] for an example. You almost never want to use
12/// this analysis without also looking at the results of [`MaybeBorrowedLocals`].
13///
14/// ## Field-(in)sensitivity
15///
16/// As the name suggests, this analysis is field insensitive. If a projection of a variable `x` is
17/// assigned to (e.g. `x.0 = 42`), it does not "define" `x` as far as liveness is concerned. In fact,
18/// such an assignment is currently marked as a "use" of `x` in an attempt to be maximally
19/// conservative.
20///
21/// [`MaybeBorrowedLocals`]: super::MaybeBorrowedLocals
22/// [flow-test]: https://github.com/rust-lang/rust/blob/a08c47310c7d49cbdc5d7afb38408ba519967ecd/src/test/ui/mir-dataflow/liveness-ptr.rs
23/// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis
24pub struct MaybeLiveLocals;
2526impl<'tcx> Analysis<'tcx> for MaybeLiveLocals {
27type Domain = DenseBitSet<Local>;
28type Direction = Backward;
2930const NAME: &'static str = "liveness";
3132fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
33// bottom = not live
34DenseBitSet::new_empty(body.local_decls.len())
35 }
3637fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
38// No variables are live until we observe a use
39}
4041fn apply_primary_statement_effect(
42&self,
43 state: &mut Self::Domain,
44 statement: &mir::Statement<'tcx>,
45 location: Location,
46 ) {
47LivenessTransferFunction(state).visit_statement(statement, location);
48 }
4950fn apply_primary_terminator_effect(
51&self,
52 state: &mut Self::Domain,
53 terminator: &mir::Terminator<'tcx>,
54 location: Location,
55 ) {
56LivenessTransferFunction(state).visit_terminator(terminator, location);
57 }
5859fn apply_call_return_effect(
60&self,
61 state: &mut Self::Domain,
62 _block: mir::BasicBlock,
63 return_places: CallReturnPlaces<'_, 'tcx>,
64 ) {
65if let CallReturnPlaces::Yield(resume_place) = return_places {
66YieldResumeEffect(state).visit_place(
67&resume_place,
68 PlaceContext::MutatingUse(MutatingUseContext::Yield),
69 Location::START,
70 )
71 } else {
72return_places.for_each(|place| {
73if let Some(local) = place.as_local() {
74state.kill(local);
75 }
76 });
77 }
78 }
79}
8081pub struct LivenessTransferFunction<'a, I>(pub &'a mut I);
8283impl<'tcx, I> Visitor<'tcx> for LivenessTransferFunction<'_, I>
84where
85I: GenKill<Local>,
86{
87fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
88if let PlaceContext::MutatingUse(MutatingUseContext::Yield) = context {
89// The resume place is evaluated and assigned to only after coroutine resumes, so its
90 // effect is handled separately in `call_resume_effect`.
91return;
92 }
9394match DefUse::for_place(*place, context) {
95 DefUse::Def => {
96if let PlaceContext::MutatingUse(
97 MutatingUseContext::Call | MutatingUseContext::AsmOutput,
98 ) = context99 {
100// For the associated terminators, this is only a `Def` when the terminator
101 // returns "successfully." As such, we handle this case separately in
102 // `call_return_effect` above. However, if the place looks like `*_5`, this is
103 // still unconditionally a use of `_5`.
104} else {
105self.0.kill(place.local);
106 }
107 }
108 DefUse::Use => self.0.gen_(place.local),
109 DefUse::PartialWrite | DefUse::NonUse => {}
110 }
111112self.visit_projection(place.as_ref(), context, location);
113 }
114115fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
116DefUse::apply(self.0, local.into(), context);
117 }
118}
119120struct YieldResumeEffect<'a>(&'a mut DenseBitSet<Local>);
121122impl<'tcx> Visitor<'tcx> for YieldResumeEffect<'_> {
123fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
124DefUse::apply(self.0, *place, context);
125self.visit_projection(place.as_ref(), context, location);
126 }
127128fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
129DefUse::apply(self.0, local.into(), context);
130 }
131}
132133pub enum DefUse {
134/// Full write to the local.
135Def,
136/// Read of any part of the local.
137Use,
138/// Partial write to the local.
139PartialWrite,
140/// Non-use, like debuginfo.
141NonUse,
142}
143144impl DefUse {
145fn apply(state: &mut impl GenKill<Local>, place: Place<'_>, context: PlaceContext) {
146match DefUse::for_place(place, context) {
147 DefUse::Def => state.kill(place.local),
148 DefUse::Use => state.gen_(place.local),
149 DefUse::PartialWrite | DefUse::NonUse => {}
150 }
151 }
152153pub fn for_place(place: Place<'_>, context: PlaceContext) -> DefUse {
154match context {
155 PlaceContext::NonUse(_) => DefUse::NonUse,
156157 PlaceContext::MutatingUse(
158 MutatingUseContext::Call
159 | MutatingUseContext::Yield
160 | MutatingUseContext::AsmOutput
161 | MutatingUseContext::Store,
162 ) => {
163// Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a use.
164if place.is_indirect() {
165 DefUse::Use166 } else if place.projection.is_empty() {
167 DefUse::Def168 } else {
169 DefUse::PartialWrite170 }
171 }
172173// Setting the discriminant is not a use because it does no reading, but it is also not
174 // a def because it does not overwrite the whole place
175PlaceContext::MutatingUse(MutatingUseContext::SetDiscriminant) => {
176if place.is_indirect() { DefUse::Use } else { DefUse::PartialWrite }
177 }
178179// All other contexts are uses...
180PlaceContext::MutatingUse(
181 MutatingUseContext::RawBorrow
182 | MutatingUseContext::Borrow
183 | MutatingUseContext::Drop,
184 )
185 | PlaceContext::NonMutatingUse(
186 NonMutatingUseContext::RawBorrow
187 | NonMutatingUseContext::Copy
188 | NonMutatingUseContext::Inspect
189 | NonMutatingUseContext::Move
190 | NonMutatingUseContext::PlaceMention
191 | NonMutatingUseContext::FakeBorrow
192 | NonMutatingUseContext::SharedBorrow,
193 ) => DefUse::Use,
194195 PlaceContext::MutatingUse(MutatingUseContext::Projection)
196 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
197{
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("A projection could be a def or a use and must be handled separately")));
}unreachable!("A projection could be a def or a use and must be handled separately")198 }
199 }
200 }
201}
202203/// Like `MaybeLiveLocals` (and layered on top of `MaybeLiveLocals`), but does not mark locals as
204/// live if they are used in a dead assignment.
205///
206/// This is basically written for dead store elimination and nothing else.
207///
208/// All of the caveats of `MaybeLiveLocals` apply.
209pub struct MaybeTransitiveLiveLocals<'a> {
210 always_live: &'a DenseBitSet<Local>,
211 debuginfo_locals: &'a DenseBitSet<Local>,
212}
213214impl<'a> MaybeTransitiveLiveLocals<'a> {
215/// The `always_live` set is the set of locals to which all stores should unconditionally be
216 /// considered live.
217 ///
218 /// This should include at least all locals that are ever borrowed.
219pub fn new(
220 always_live: &'a DenseBitSet<Local>,
221 debuginfo_locals: &'a DenseBitSet<Local>,
222 ) -> Self {
223MaybeTransitiveLiveLocals { always_live, debuginfo_locals }
224 }
225226pub fn can_be_removed_if_dead<'tcx>(
227 stmt_kind: &StatementKind<'tcx>,
228 always_live: &DenseBitSet<Local>,
229 debuginfo_locals: &DenseBitSet<Local>,
230 ) -> Option<Place<'tcx>> {
231// Compute the place that we are storing to, if any
232let destination = match stmt_kind {
233 StatementKind::Assign((place, rvalue)) => (rvalue.is_safe_to_remove()
234// FIXME: We are not sure how we should represent this debugging information for some statements,
235 // keep it for now.
236&& (!debuginfo_locals.contains(place.local)
237 || (place.as_local().is_some() && stmt_kind.as_debuginfo().is_some())))
238 .then_some(*place),
239 StatementKind::SetDiscriminant { place, .. } => {
240 (!debuginfo_locals.contains(place.local)).then_some(**place)
241 }
242 StatementKind::FakeRead(_)
243 | StatementKind::StorageLive(_)
244 | StatementKind::StorageDead(_)
245 | StatementKind::AscribeUserType(..)
246 | StatementKind::PlaceMention(..)
247 | StatementKind::Coverage(..)
248 | StatementKind::Intrinsic(..)
249 | StatementKind::ConstEvalCounter
250 | StatementKind::BackwardIncompatibleDropHint { .. }
251 | StatementKind::Nop => None,
252 };
253if let Some(destination) = destination254 && !destination.is_indirect()
255 && !always_live.contains(destination.local)
256 {
257return Some(destination);
258 }
259None260 }
261}
262263impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
264type Domain = DenseBitSet<Local>;
265type Direction = Backward;
266267const NAME: &'static str = "transitive liveness";
268269fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
270MaybeLiveLocals.bottom_value(body)
271 }
272273fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
274MaybeLiveLocals.initialize_start_block(body, state)
275 }
276277fn apply_primary_statement_effect(
278&self,
279 state: &mut Self::Domain,
280 statement: &mir::Statement<'tcx>,
281 location: Location,
282 ) {
283// This is the one part of `MaybeTransitiveLiveLocals` that differs from `MaybeLiveLocals`.
284if let Some(destination) =
285Self::can_be_removed_if_dead(&statement.kind, self.always_live, self.debuginfo_locals)
286 && !state.contains(destination.local)
287 {
288// This store is dead
289return;
290 }
291292MaybeLiveLocals.apply_primary_statement_effect(state, statement, location);
293 }
294295fn apply_primary_terminator_effect(
296&self,
297 state: &mut Self::Domain,
298 terminator: &mir::Terminator<'tcx>,
299 location: Location,
300 ) {
301MaybeLiveLocals.apply_primary_terminator_effect(state, terminator, location)
302 }
303304fn apply_call_return_effect(
305&self,
306 state: &mut Self::Domain,
307 block: mir::BasicBlock,
308 return_places: CallReturnPlaces<'_, 'tcx>,
309 ) {
310MaybeLiveLocals.apply_call_return_effect(state, block, return_places);
311 }
312}