#![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{ Body, Local, Location, Place, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, }; use rustc_middle::ty::TyCtxt; use crate::{borrow_set::BorrowSet, facts::AllFacts, location::LocationTable, places_conflict}; /// Emit `loan_killed_at` and `cfg_edge` facts at the same time. pub(super) fn emit_loan_kills<'tcx>( tcx: TyCtxt<'tcx>, all_facts: &mut AllFacts, location_table: &LocationTable, body: &Body<'tcx>, borrow_set: &BorrowSet<'tcx>, ) { let mut visitor = LoanKillsGenerator { borrow_set, tcx, location_table, all_facts, body }; for (bb, data) in body.basic_blocks.iter_enumerated() { visitor.visit_basic_block_data(bb, data); } } struct LoanKillsGenerator<'cx, 'tcx> { tcx: TyCtxt<'tcx>, all_facts: &'cx mut AllFacts, location_table: &'cx LocationTable, borrow_set: &'cx BorrowSet<'tcx>, body: &'cx Body<'tcx>, } impl<'cx, 'tcx> Visitor<'tcx> for LoanKillsGenerator<'cx, 'tcx> { fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { // Also record CFG facts here. self.all_facts.cfg_edge.push(( self.location_table.start_index(location), self.location_table.mid_index(location), )); self.all_facts.cfg_edge.push(( self.location_table.mid_index(location), self.location_table.start_index(location.successor_within_block()), )); // If there are borrows on this now dead local, we need to record them as `killed`. if let StatementKind::StorageDead(local) = statement.kind { self.record_killed_borrows_for_local(local, location); } self.super_statement(statement, location); } fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) { // When we see `X = ...`, then kill borrows of // `(*X).foo` and so forth. self.record_killed_borrows_for_place(*place, location); self.super_assign(place, rvalue, location); } fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { // Also record CFG facts here. self.all_facts.cfg_edge.push(( self.location_table.start_index(location), self.location_table.mid_index(location), )); let successor_blocks = terminator.successors(); self.all_facts.cfg_edge.reserve(successor_blocks.size_hint().0); for successor_block in successor_blocks { self.all_facts.cfg_edge.push(( self.location_table.mid_index(location), self.location_table.start_index(successor_block.start_location()), )); } // A `Call` terminator's return value can be a local which has borrows, // so we need to record those as `killed` as well. if let TerminatorKind::Call { destination, .. } = terminator.kind { self.record_killed_borrows_for_place(destination, location); } self.super_terminator(terminator, location); } } impl<'tcx> LoanKillsGenerator<'_, 'tcx> { /// Records the borrows on the specified place as `killed`. For example, when assigning to a /// local, or on a call's return destination. fn record_killed_borrows_for_place(&mut self, place: Place<'tcx>, location: Location) { // Depending on the `Place` we're killing: // - if it's a local, or a single deref of a local, // we kill all the borrows on the local. // - if it's a deeper projection, we have to filter which // of the borrows are killed: the ones whose `borrowed_place` // conflicts with the `place`. match place.as_ref() { PlaceRef { local, projection: &[] } | PlaceRef { local, projection: &[ProjectionElem::Deref] } => { debug!( "Recording `killed` facts for borrows of local={:?} at location={:?}", local, location ); self.record_killed_borrows_for_local(local, location); } PlaceRef { local, projection: &[.., _] } => { // Kill conflicting borrows of the innermost local. debug!( "Recording `killed` facts for borrows of \ innermost projected local={:?} at location={:?}", local, location ); if let Some(borrow_indices) = self.borrow_set.local_map.get(&local) { for &borrow_index in borrow_indices { let places_conflict = places_conflict::places_conflict( self.tcx, self.body, self.borrow_set[borrow_index].borrowed_place, place, places_conflict::PlaceConflictBias::NoOverlap, ); if places_conflict { let location_index = self.location_table.mid_index(location); self.all_facts.loan_killed_at.push((borrow_index, location_index)); } } } } } } /// Records the borrows on the specified local as `killed`. fn record_killed_borrows_for_local(&mut self, local: Local, location: Location) { if let Some(borrow_indices) = self.borrow_set.local_map.get(&local) { let location_index = self.location_table.mid_index(location); self.all_facts.loan_killed_at.reserve(borrow_indices.len()); for &borrow_index in borrow_indices { self.all_facts.loan_killed_at.push((borrow_index, location_index)); } } } }