1 use rustc_index::bit_set::{BitSet, ChunkedBitSet};
2 use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
3 use rustc_middle::mir::{self, Local, Location, Place, StatementKind};
5 use crate::{Analysis, AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKillAnalysis};
7 /// A [live-variable dataflow analysis][liveness].
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`].
14 /// ## Field-(in)sensitivity
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
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
24 pub struct MaybeLiveLocals;
26 impl MaybeLiveLocals {
27 fn transfer_function<'a, T>(&self, trans: &'a mut T) -> TransferFunction<'a, T> {
28 TransferFunction(trans)
32 impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals {
33 type Domain = ChunkedBitSet<Local>;
34 type Direction = Backward;
36 const NAME: &'static str = "liveness";
38 fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
40 ChunkedBitSet::new_empty(body.local_decls.len())
43 fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
44 // No variables are live until we observe a use
48 impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
53 trans: &mut impl GenKill<Self::Idx>,
54 statement: &mir::Statement<'tcx>,
57 self.transfer_function(trans).visit_statement(statement, location);
62 trans: &mut impl GenKill<Self::Idx>,
63 terminator: &mir::Terminator<'tcx>,
66 self.transfer_function(trans).visit_terminator(terminator, location);
69 fn call_return_effect(
71 trans: &mut impl GenKill<Self::Idx>,
72 _block: mir::BasicBlock,
73 return_places: CallReturnPlaces<'_, 'tcx>,
75 return_places.for_each(|place| {
76 if let Some(local) = place.as_local() {
82 fn yield_resume_effect(
84 trans: &mut impl GenKill<Self::Idx>,
85 _resume_block: mir::BasicBlock,
86 resume_place: mir::Place<'tcx>,
88 if let Some(local) = resume_place.as_local() {
94 struct TransferFunction<'a, T>(&'a mut T);
96 impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, T>
100 fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
101 let local = place.local;
103 // We purposefully do not call `super_place` here to avoid calling `visit_local` for this
104 // place with one of the `Projection` variants of `PlaceContext`.
105 self.visit_projection(place.as_ref(), context, location);
107 match DefUse::for_place(*place, context) {
108 Some(DefUse::Def) => self.0.kill(local),
109 Some(DefUse::Use) => self.0.gen(local),
114 fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
115 // Because we do not call `super_place` above, `visit_local` is only called for locals that
116 // do not appear as part of a `Place` in the MIR. This handles cases like the implicit use
117 // of the return place in a `Return` terminator or the index in an `Index` projection.
118 match DefUse::for_place(local.into(), context) {
119 Some(DefUse::Def) => self.0.kill(local),
120 Some(DefUse::Use) => self.0.gen(local),
126 #[derive(Eq, PartialEq, Clone)]
133 fn for_place<'tcx>(place: Place<'tcx>, context: PlaceContext) -> Option<DefUse> {
135 PlaceContext::NonUse(_) => None,
137 PlaceContext::MutatingUse(MutatingUseContext::Store | MutatingUseContext::Deinit) => {
138 if place.is_indirect() {
139 // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
142 } else if place.projection.is_empty() {
149 // Setting the discriminant is not a use because it does no reading, but it is also not
150 // a def because it does not overwrite the whole place
151 PlaceContext::MutatingUse(MutatingUseContext::SetDiscriminant) => {
152 place.is_indirect().then_some(DefUse::Use)
155 // For the associated terminators, this is only a `Def` when the terminator returns
156 // "successfully." As such, we handle this case separately in `call_return_effect`
157 // above. However, if the place looks like `*_5`, this is still unconditionally a use of
159 PlaceContext::MutatingUse(
160 MutatingUseContext::Call
161 | MutatingUseContext::Yield
162 | MutatingUseContext::AsmOutput,
163 ) => place.is_indirect().then_some(DefUse::Use),
165 // All other contexts are uses...
166 PlaceContext::MutatingUse(
167 MutatingUseContext::AddressOf
168 | MutatingUseContext::Borrow
169 | MutatingUseContext::Drop
170 | MutatingUseContext::Retag,
172 | PlaceContext::NonMutatingUse(
173 NonMutatingUseContext::AddressOf
174 | NonMutatingUseContext::Copy
175 | NonMutatingUseContext::Inspect
176 | NonMutatingUseContext::Move
177 | NonMutatingUseContext::ShallowBorrow
178 | NonMutatingUseContext::SharedBorrow
179 | NonMutatingUseContext::UniqueBorrow,
180 ) => Some(DefUse::Use),
182 PlaceContext::MutatingUse(MutatingUseContext::Projection)
183 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
184 unreachable!("A projection could be a def or a use and must be handled separately")
190 /// Like `MaybeLiveLocals`, but does not mark locals as live if they are used in a dead assignment.
192 /// This is basically written for dead store elimination and nothing else.
194 /// All of the caveats of `MaybeLiveLocals` apply.
195 pub struct MaybeTransitiveLiveLocals<'a> {
196 always_live: &'a BitSet<Local>,
199 impl<'a> MaybeTransitiveLiveLocals<'a> {
200 /// The `always_alive` set is the set of locals to which all stores should unconditionally be
203 /// This should include at least all locals that are ever borrowed.
204 pub fn new(always_live: &'a BitSet<Local>) -> Self {
205 MaybeTransitiveLiveLocals { always_live }
209 impl<'a, 'tcx> AnalysisDomain<'tcx> for MaybeTransitiveLiveLocals<'a> {
210 type Domain = ChunkedBitSet<Local>;
211 type Direction = Backward;
213 const NAME: &'static str = "transitive liveness";
215 fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
217 ChunkedBitSet::new_empty(body.local_decls.len())
220 fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
221 // No variables are live until we observe a use
225 struct TransferWrapper<'a>(&'a mut ChunkedBitSet<Local>);
227 impl<'a> GenKill<Local> for TransferWrapper<'a> {
228 fn gen(&mut self, l: Local) {
232 fn kill(&mut self, l: Local) {
237 impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
238 fn apply_statement_effect(
240 trans: &mut Self::Domain,
241 statement: &mir::Statement<'tcx>,
244 // Compute the place that we are storing to, if any
245 let destination = match &statement.kind {
246 StatementKind::Assign(assign) => {
247 if assign.1.is_safe_to_remove() {
253 StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
256 StatementKind::FakeRead(_)
257 | StatementKind::StorageLive(_)
258 | StatementKind::StorageDead(_)
259 | StatementKind::Retag(..)
260 | StatementKind::AscribeUserType(..)
261 | StatementKind::Coverage(..)
262 | StatementKind::CopyNonOverlapping(..)
263 | StatementKind::Nop => None,
265 if let Some(destination) = destination {
266 if !destination.is_indirect()
267 && !trans.contains(destination.local)
268 && !self.always_live.contains(destination.local)
270 // This store is dead
274 TransferFunction(&mut TransferWrapper(trans)).visit_statement(statement, location);
277 fn apply_terminator_effect(
279 trans: &mut Self::Domain,
280 terminator: &mir::Terminator<'tcx>,
283 TransferFunction(&mut TransferWrapper(trans)).visit_terminator(terminator, location);
286 fn apply_call_return_effect(
288 trans: &mut Self::Domain,
289 _block: mir::BasicBlock,
290 return_places: CallReturnPlaces<'_, 'tcx>,
292 return_places.for_each(|place| {
293 if let Some(local) = place.as_local() {
299 fn apply_yield_resume_effect(
301 trans: &mut Self::Domain,
302 _resume_block: mir::BasicBlock,
303 resume_place: mir::Place<'tcx>,
305 if let Some(local) = resume_place.as_local() {