1use rustc_index::bit_set::DenseBitSet;
2use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
3use rustc_middle::mir::{
4self, CallReturnPlaces, Local, Location, Place, StatementKind, TerminatorEdges,
5};
67use crate::{Analysis, Backward, GenKill};
89/// A [live-variable dataflow analysis][liveness].
10///
11/// This analysis considers references as being used only at the point of the
12/// borrow. In other words, this analysis does not track uses because of references that already
13/// exist. See [this `mir-dataflow` test][flow-test] for an example. You almost never want to use
14/// this analysis without also looking at the results of [`MaybeBorrowedLocals`].
15///
16/// ## Field-(in)sensitivity
17///
18/// As the name suggests, this analysis is field insensitive. If a projection of a variable `x` is
19/// assigned to (e.g. `x.0 = 42`), it does not "define" `x` as far as liveness is concerned. In fact,
20/// such an assignment is currently marked as a "use" of `x` in an attempt to be maximally
21/// conservative.
22///
23/// [`MaybeBorrowedLocals`]: super::MaybeBorrowedLocals
24/// [flow-test]: https://github.com/rust-lang/rust/blob/a08c47310c7d49cbdc5d7afb38408ba519967ecd/src/test/ui/mir-dataflow/liveness-ptr.rs
25/// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis
26pub struct MaybeLiveLocals;
2728impl<'tcx> Analysis<'tcx> for MaybeLiveLocals {
29type Domain = DenseBitSet<Local>;
30type Direction = Backward;
3132const NAME: &'static str = "liveness";
3334fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
35// bottom = not live
36DenseBitSet::new_empty(body.local_decls.len())
37 }
3839fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
40// No variables are live until we observe a use
41}
4243fn apply_primary_statement_effect(
44&self,
45 state: &mut Self::Domain,
46 statement: &mir::Statement<'tcx>,
47 location: Location,
48 ) {
49TransferFunction(state).visit_statement(statement, location);
50 }
5152fn apply_primary_terminator_effect<'mir>(
53&self,
54 state: &mut Self::Domain,
55 terminator: &'mir mir::Terminator<'tcx>,
56 location: Location,
57 ) -> TerminatorEdges<'mir, 'tcx> {
58TransferFunction(state).visit_terminator(terminator, location);
59terminator.edges()
60 }
6162fn apply_call_return_effect(
63&self,
64 state: &mut Self::Domain,
65 _block: mir::BasicBlock,
66 return_places: CallReturnPlaces<'_, 'tcx>,
67 ) {
68if let CallReturnPlaces::Yield(resume_place) = return_places {
69YieldResumeEffect(state).visit_place(
70&resume_place,
71 PlaceContext::MutatingUse(MutatingUseContext::Yield),
72 Location::START,
73 )
74 } else {
75return_places.for_each(|place| {
76if let Some(local) = place.as_local() {
77state.kill(local);
78 }
79 });
80 }
81 }
82}
8384pub struct TransferFunction<'a>(pub &'a mut DenseBitSet<Local>);
8586impl<'tcx> Visitor<'tcx> for TransferFunction<'_> {
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}
132133#[derive(#[automatically_derived]
impl ::core::cmp::Eq for DefUse {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for DefUse {
#[inline]
fn eq(&self, other: &DefUse) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::clone::Clone for DefUse {
#[inline]
fn clone(&self) -> DefUse {
match self {
DefUse::Def => DefUse::Def,
DefUse::Use => DefUse::Use,
DefUse::PartialWrite => DefUse::PartialWrite,
DefUse::NonUse => DefUse::NonUse,
}
}
}Clone)]
134pub enum DefUse {
135/// Full write to the local.
136Def,
137/// Read of any part of the local.
138Use,
139/// Partial write to the local.
140PartialWrite,
141/// Non-use, like debuginfo.
142NonUse,
143}
144145impl DefUse {
146fn apply(state: &mut DenseBitSet<Local>, place: Place<'_>, context: PlaceContext) {
147match DefUse::for_place(place, context) {
148 DefUse::Def => state.kill(place.local),
149 DefUse::Use => state.gen_(place.local),
150 DefUse::PartialWrite | DefUse::NonUse => {}
151 }
152 }
153154pub fn for_place(place: Place<'_>, context: PlaceContext) -> DefUse {
155match context {
156 PlaceContext::NonUse(_) => DefUse::NonUse,
157158 PlaceContext::MutatingUse(
159 MutatingUseContext::Call
160 | MutatingUseContext::Yield
161 | MutatingUseContext::AsmOutput
162 | MutatingUseContext::Store,
163 ) => {
164// Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a use.
165if place.is_indirect() {
166 DefUse::Use167 } else if place.projection.is_empty() {
168 DefUse::Def169 } else {
170 DefUse::PartialWrite171 }
172 }
173174// Setting the discriminant is not a use because it does no reading, but it is also not
175 // a def because it does not overwrite the whole place
176PlaceContext::MutatingUse(MutatingUseContext::SetDiscriminant) => {
177if place.is_indirect() { DefUse::Use } else { DefUse::PartialWrite }
178 }
179180// All other contexts are uses...
181PlaceContext::MutatingUse(
182 MutatingUseContext::RawBorrow
183 | MutatingUseContext::Borrow
184 | MutatingUseContext::Drop
185 | MutatingUseContext::Retag,
186 )
187 | PlaceContext::NonMutatingUse(
188 NonMutatingUseContext::RawBorrow
189 | NonMutatingUseContext::Copy
190 | NonMutatingUseContext::Inspect
191 | NonMutatingUseContext::Move
192 | NonMutatingUseContext::PlaceMention
193 | NonMutatingUseContext::FakeBorrow
194 | NonMutatingUseContext::SharedBorrow,
195 ) => DefUse::Use,
196197 PlaceContext::MutatingUse(MutatingUseContext::Projection)
198 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
199{
::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")200 }
201 }
202 }
203}
204205/// Like `MaybeLiveLocals`, but does not mark locals as live if they are used in a dead assignment.
206///
207/// This is basically written for dead store elimination and nothing else.
208///
209/// All of the caveats of `MaybeLiveLocals` apply.
210pub struct MaybeTransitiveLiveLocals<'a> {
211 always_live: &'a DenseBitSet<Local>,
212 debuginfo_locals: &'a DenseBitSet<Local>,
213}
214215impl<'a> MaybeTransitiveLiveLocals<'a> {
216/// The `always_alive` set is the set of locals to which all stores should unconditionally be
217 /// considered live.
218 ///
219 /// This should include at least all locals that are ever borrowed.
220pub fn new(
221 always_live: &'a DenseBitSet<Local>,
222 debuginfo_locals: &'a DenseBitSet<Local>,
223 ) -> Self {
224MaybeTransitiveLiveLocals { always_live, debuginfo_locals }
225 }
226227pub fn can_be_removed_if_dead<'tcx>(
228 stmt_kind: &StatementKind<'tcx>,
229 always_live: &DenseBitSet<Local>,
230 debuginfo_locals: &'a DenseBitSet<Local>,
231 ) -> Option<Place<'tcx>> {
232// Compute the place that we are storing to, if any
233let destination = match stmt_kind {
234 StatementKind::Assign(box (place, rvalue)) => (rvalue.is_safe_to_remove()
235// FIXME: We are not sure how we should represent this debugging information for some statements,
236 // keep it for now.
237&& (!debuginfo_locals.contains(place.local)
238 || (place.as_local().is_some() && stmt_kind.as_debuginfo().is_some())))
239 .then_some(*place),
240 StatementKind::SetDiscriminant { place, .. } => {
241 (!debuginfo_locals.contains(place.local)).then_some(**place)
242 }
243 StatementKind::FakeRead(_)
244 | StatementKind::StorageLive(_)
245 | StatementKind::StorageDead(_)
246 | StatementKind::Retag(..)
247 | StatementKind::AscribeUserType(..)
248 | StatementKind::PlaceMention(..)
249 | StatementKind::Coverage(..)
250 | StatementKind::Intrinsic(..)
251 | StatementKind::ConstEvalCounter
252 | StatementKind::BackwardIncompatibleDropHint { .. }
253 | StatementKind::Nop => None,
254 };
255if let Some(destination) = destination256 && !destination.is_indirect()
257 && !always_live.contains(destination.local)
258 {
259return Some(destination);
260 }
261None262 }
263}
264265impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
266type Domain = DenseBitSet<Local>;
267type Direction = Backward;
268269const NAME: &'static str = "transitive liveness";
270271fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
272// bottom = not live
273DenseBitSet::new_empty(body.local_decls.len())
274 }
275276fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
277// No variables are live until we observe a use
278}
279280fn apply_primary_statement_effect(
281&self,
282 state: &mut Self::Domain,
283 statement: &mir::Statement<'tcx>,
284 location: Location,
285 ) {
286if let Some(destination) =
287Self::can_be_removed_if_dead(&statement.kind, &self.always_live, &self.debuginfo_locals)
288 && !state.contains(destination.local)
289 {
290// This store is dead
291return;
292 }
293TransferFunction(state).visit_statement(statement, location);
294 }
295296fn apply_primary_terminator_effect<'mir>(
297&self,
298 state: &mut Self::Domain,
299 terminator: &'mir mir::Terminator<'tcx>,
300 location: Location,
301 ) -> TerminatorEdges<'mir, 'tcx> {
302TransferFunction(state).visit_terminator(terminator, location);
303terminator.edges()
304 }
305306fn apply_call_return_effect(
307&self,
308 state: &mut Self::Domain,
309 _block: mir::BasicBlock,
310 return_places: CallReturnPlaces<'_, 'tcx>,
311 ) {
312if let CallReturnPlaces::Yield(resume_place) = return_places {
313YieldResumeEffect(state).visit_place(
314&resume_place,
315 PlaceContext::MutatingUse(MutatingUseContext::Yield),
316 Location::START,
317 )
318 } else {
319return_places.for_each(|place| {
320if let Some(local) = place.as_local() {
321state.remove(local);
322 }
323 });
324 }
325 }
326}