| use rustc_index::bit_set::DenseBitSet; | |
| use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; | |
| use rustc_middle::mir::{ | |
| self, CallReturnPlaces, Local, Location, Place, StatementKind, TerminatorEdges, | |
| }; | |
| use crate::{Analysis, Backward, GenKill}; | |
| /// A [live-variable dataflow analysis][liveness]. | |
| /// | |
| /// This analysis considers references as being used only at the point of the | |
| /// borrow. In other words, this analysis does not track uses because of references that already | |
| /// exist. See [this `mir-dataflow` test][flow-test] for an example. You almost never want to use | |
| /// this analysis without also looking at the results of [`MaybeBorrowedLocals`]. | |
| /// | |
| /// ## Field-(in)sensitivity | |
| /// | |
| /// As the name suggests, this analysis is field insensitive. If a projection of a variable `x` is | |
| /// assigned to (e.g. `x.0 = 42`), it does not "define" `x` as far as liveness is concerned. In fact, | |
| /// such an assignment is currently marked as a "use" of `x` in an attempt to be maximally | |
| /// conservative. | |
| /// | |
| /// [`MaybeBorrowedLocals`]: super::MaybeBorrowedLocals | |
| /// [flow-test]: https://github.com/rust-lang/rust/blob/a08c47310c7d49cbdc5d7afb38408ba519967ecd/src/test/ui/mir-dataflow/liveness-ptr.rs | |
| /// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis | |
| pub struct MaybeLiveLocals; | |
| impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { | |
| type Domain = DenseBitSet<Local>; | |
| type Direction = Backward; | |
| const NAME: &'static str = "liveness"; | |
| fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { | |
| // bottom = not live | |
| DenseBitSet::new_empty(body.local_decls.len()) | |
| } | |
| fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) { | |
| // No variables are live until we observe a use | |
| } | |
| fn apply_primary_statement_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| statement: &mir::Statement<'tcx>, | |
| location: Location, | |
| ) { | |
| TransferFunction(state).visit_statement(statement, location); | |
| } | |
| fn apply_primary_terminator_effect<'mir>( | |
| &self, | |
| state: &mut Self::Domain, | |
| terminator: &'mir mir::Terminator<'tcx>, | |
| location: Location, | |
| ) -> TerminatorEdges<'mir, 'tcx> { | |
| TransferFunction(state).visit_terminator(terminator, location); | |
| terminator.edges() | |
| } | |
| fn apply_call_return_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| _block: mir::BasicBlock, | |
| return_places: CallReturnPlaces<'_, 'tcx>, | |
| ) { | |
| if let CallReturnPlaces::Yield(resume_place) = return_places { | |
| YieldResumeEffect(state).visit_place( | |
| &resume_place, | |
| PlaceContext::MutatingUse(MutatingUseContext::Yield), | |
| Location::START, | |
| ) | |
| } else { | |
| return_places.for_each(|place| { | |
| if let Some(local) = place.as_local() { | |
| state.kill(local); | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| pub struct TransferFunction<'a>(pub &'a mut DenseBitSet<Local>); | |
| impl<'tcx> Visitor<'tcx> for TransferFunction<'_> { | |
| fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) { | |
| if let PlaceContext::MutatingUse(MutatingUseContext::Yield) = context { | |
| // The resume place is evaluated and assigned to only after coroutine resumes, so its | |
| // effect is handled separately in `call_resume_effect`. | |
| return; | |
| } | |
| match DefUse::for_place(*place, context) { | |
| DefUse::Def => { | |
| if let PlaceContext::MutatingUse( | |
| MutatingUseContext::Call | MutatingUseContext::AsmOutput, | |
| ) = context | |
| { | |
| // For the associated terminators, this is only a `Def` when the terminator | |
| // returns "successfully." As such, we handle this case separately in | |
| // `call_return_effect` above. However, if the place looks like `*_5`, this is | |
| // still unconditionally a use of `_5`. | |
| } else { | |
| self.0.kill(place.local); | |
| } | |
| } | |
| DefUse::Use => self.0.gen_(place.local), | |
| DefUse::PartialWrite | DefUse::NonUse => {} | |
| } | |
| self.visit_projection(place.as_ref(), context, location); | |
| } | |
| fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { | |
| DefUse::apply(self.0, local.into(), context); | |
| } | |
| } | |
| struct YieldResumeEffect<'a>(&'a mut DenseBitSet<Local>); | |
| impl<'tcx> Visitor<'tcx> for YieldResumeEffect<'_> { | |
| fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) { | |
| DefUse::apply(self.0, *place, context); | |
| self.visit_projection(place.as_ref(), context, location); | |
| } | |
| fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { | |
| DefUse::apply(self.0, local.into(), context); | |
| } | |
| } | |
| #[derive(Eq, PartialEq, Clone)] | |
| pub enum DefUse { | |
| /// Full write to the local. | |
| Def, | |
| /// Read of any part of the local. | |
| Use, | |
| /// Partial write to the local. | |
| PartialWrite, | |
| /// Non-use, like debuginfo. | |
| NonUse, | |
| } | |
| impl DefUse { | |
| fn apply(state: &mut DenseBitSet<Local>, place: Place<'_>, context: PlaceContext) { | |
| match DefUse::for_place(place, context) { | |
| DefUse::Def => state.kill(place.local), | |
| DefUse::Use => state.gen_(place.local), | |
| DefUse::PartialWrite | DefUse::NonUse => {} | |
| } | |
| } | |
| pub fn for_place(place: Place<'_>, context: PlaceContext) -> DefUse { | |
| match context { | |
| PlaceContext::NonUse(_) => DefUse::NonUse, | |
| PlaceContext::MutatingUse( | |
| MutatingUseContext::Call | |
| | MutatingUseContext::Yield | |
| | MutatingUseContext::AsmOutput | |
| | MutatingUseContext::Store, | |
| ) => { | |
| // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a use. | |
| if place.is_indirect() { | |
| DefUse::Use | |
| } else if place.projection.is_empty() { | |
| DefUse::Def | |
| } else { | |
| DefUse::PartialWrite | |
| } | |
| } | |
| // Setting the discriminant is not a use because it does no reading, but it is also not | |
| // a def because it does not overwrite the whole place | |
| PlaceContext::MutatingUse(MutatingUseContext::SetDiscriminant) => { | |
| if place.is_indirect() { DefUse::Use } else { DefUse::PartialWrite } | |
| } | |
| // All other contexts are uses... | |
| PlaceContext::MutatingUse( | |
| MutatingUseContext::RawBorrow | |
| | MutatingUseContext::Borrow | |
| | MutatingUseContext::Drop | |
| | MutatingUseContext::Retag, | |
| ) | |
| | PlaceContext::NonMutatingUse( | |
| NonMutatingUseContext::RawBorrow | |
| | NonMutatingUseContext::Copy | |
| | NonMutatingUseContext::Inspect | |
| | NonMutatingUseContext::Move | |
| | NonMutatingUseContext::PlaceMention | |
| | NonMutatingUseContext::FakeBorrow | |
| | NonMutatingUseContext::SharedBorrow, | |
| ) => DefUse::Use, | |
| PlaceContext::MutatingUse(MutatingUseContext::Projection) | |
| | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => { | |
| unreachable!("A projection could be a def or a use and must be handled separately") | |
| } | |
| } | |
| } | |
| } | |
| /// Like `MaybeLiveLocals`, but does not mark locals as live if they are used in a dead assignment. | |
| /// | |
| /// This is basically written for dead store elimination and nothing else. | |
| /// | |
| /// All of the caveats of `MaybeLiveLocals` apply. | |
| pub struct MaybeTransitiveLiveLocals<'a> { | |
| always_live: &'a DenseBitSet<Local>, | |
| debuginfo_locals: &'a DenseBitSet<Local>, | |
| } | |
| impl<'a> MaybeTransitiveLiveLocals<'a> { | |
| /// The `always_alive` set is the set of locals to which all stores should unconditionally be | |
| /// considered live. | |
| /// | |
| /// This should include at least all locals that are ever borrowed. | |
| pub fn new( | |
| always_live: &'a DenseBitSet<Local>, | |
| debuginfo_locals: &'a DenseBitSet<Local>, | |
| ) -> Self { | |
| MaybeTransitiveLiveLocals { always_live, debuginfo_locals } | |
| } | |
| pub fn can_be_removed_if_dead<'tcx>( | |
| stmt_kind: &StatementKind<'tcx>, | |
| always_live: &DenseBitSet<Local>, | |
| debuginfo_locals: &'a DenseBitSet<Local>, | |
| ) -> Option<Place<'tcx>> { | |
| // Compute the place that we are storing to, if any | |
| let destination = match stmt_kind { | |
| StatementKind::Assign(box (place, rvalue)) => (rvalue.is_safe_to_remove() | |
| // FIXME: We are not sure how we should represent this debugging information for some statements, | |
| // keep it for now. | |
| && (!debuginfo_locals.contains(place.local) | |
| || (place.as_local().is_some() && stmt_kind.as_debuginfo().is_some()))) | |
| .then_some(*place), | |
| StatementKind::SetDiscriminant { place, .. } => { | |
| (!debuginfo_locals.contains(place.local)).then_some(**place) | |
| } | |
| StatementKind::FakeRead(_) | |
| | StatementKind::StorageLive(_) | |
| | StatementKind::StorageDead(_) | |
| | StatementKind::Retag(..) | |
| | StatementKind::AscribeUserType(..) | |
| | StatementKind::PlaceMention(..) | |
| | StatementKind::Coverage(..) | |
| | StatementKind::Intrinsic(..) | |
| | StatementKind::ConstEvalCounter | |
| | StatementKind::BackwardIncompatibleDropHint { .. } | |
| | StatementKind::Nop => None, | |
| }; | |
| if let Some(destination) = destination | |
| && !destination.is_indirect() | |
| && !always_live.contains(destination.local) | |
| { | |
| return Some(destination); | |
| } | |
| None | |
| } | |
| } | |
| impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { | |
| type Domain = DenseBitSet<Local>; | |
| type Direction = Backward; | |
| const NAME: &'static str = "transitive liveness"; | |
| fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { | |
| // bottom = not live | |
| DenseBitSet::new_empty(body.local_decls.len()) | |
| } | |
| fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) { | |
| // No variables are live until we observe a use | |
| } | |
| fn apply_primary_statement_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| statement: &mir::Statement<'tcx>, | |
| location: Location, | |
| ) { | |
| if let Some(destination) = | |
| Self::can_be_removed_if_dead(&statement.kind, &self.always_live, &self.debuginfo_locals) | |
| && !state.contains(destination.local) | |
| { | |
| // This store is dead | |
| return; | |
| } | |
| TransferFunction(state).visit_statement(statement, location); | |
| } | |
| fn apply_primary_terminator_effect<'mir>( | |
| &self, | |
| state: &mut Self::Domain, | |
| terminator: &'mir mir::Terminator<'tcx>, | |
| location: Location, | |
| ) -> TerminatorEdges<'mir, 'tcx> { | |
| TransferFunction(state).visit_terminator(terminator, location); | |
| terminator.edges() | |
| } | |
| fn apply_call_return_effect( | |
| &self, | |
| state: &mut Self::Domain, | |
| _block: mir::BasicBlock, | |
| return_places: CallReturnPlaces<'_, 'tcx>, | |
| ) { | |
| if let CallReturnPlaces::Yield(resume_place) = return_places { | |
| YieldResumeEffect(state).visit_place( | |
| &resume_place, | |
| PlaceContext::MutatingUse(MutatingUseContext::Yield), | |
| Location::START, | |
| ) | |
| } else { | |
| return_places.for_each(|place| { | |
| if let Some(local) = place.as_local() { | |
| state.remove(local); | |
| } | |
| }); | |
| } | |
| } | |
| } |