use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_index::bit_set::HybridBitSet;
use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
use rustc_middle::ty::{Ty, TypeFoldable};
use rustc_trait_selection::traits::query::dropck_outlives::DropckOutlivesResult;
use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives;
use rustc_trait_selection::traits::query::type_op::TypeOp;
use std::rc::Rc;
use crate::dataflow::impls::MaybeInitializedPlaces;
use crate::dataflow::indexes::MovePathIndex;
use crate::dataflow::move_paths::{HasMoveData, MoveData};
use crate::dataflow::ResultsCursor;
use crate::borrow_check::{
region_infer::values::{self, PointIndex, RegionValueElements},
type_check::liveness::local_use_map::LocalUseMap,
type_check::liveness::polonius,
type_check::NormalizeLocation,
type_check::TypeChecker,
};
pub(super) fn trace(
typeck: &mut TypeChecker<'_, 'tcx>,
body: &Body<'tcx>,
elements: &Rc<RegionValueElements>,
flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
move_data: &MoveData<'tcx>,
live_locals: Vec<Local>,
polonius_drop_used: Option<Vec<(Local, Location)>>,
) {
debug!("trace()");
let local_use_map = &LocalUseMap::build(&live_locals, elements, body);
let cx = LivenessContext {
typeck,
body,
flow_inits,
elements,
local_use_map,
move_data,
drop_data: FxHashMap::default(),
};
let mut results = LivenessResults::new(cx);
if let Some(drop_used) = polonius_drop_used {
results.add_extra_drop_facts(drop_used, live_locals.iter().copied().collect())
}
results.compute_for_all_locals(live_locals);
}
struct LivenessContext<'me, 'typeck, 'flow, 'tcx> {
typeck: &'me mut TypeChecker<'typeck, 'tcx>,
elements: &'me RegionValueElements,
body: &'me Body<'tcx>,
move_data: &'me MoveData<'tcx>,
drop_data: FxHashMap<Ty<'tcx>, DropData<'tcx>>,
flow_inits: &'me mut ResultsCursor<'flow, 'tcx, MaybeInitializedPlaces<'flow, 'tcx>>,
local_use_map: &'me LocalUseMap,
}
struct DropData<'tcx> {
dropck_result: DropckOutlivesResult<'tcx>,
region_constraint_data: Option<Rc<QueryRegionConstraints<'tcx>>>,
}
struct LivenessResults<'me, 'typeck, 'flow, 'tcx> {
cx: LivenessContext<'me, 'typeck, 'flow, 'tcx>,
defs: HybridBitSet<PointIndex>,
use_live_at: HybridBitSet<PointIndex>,
drop_live_at: HybridBitSet<PointIndex>,
drop_locations: Vec<Location>,
stack: Vec<PointIndex>,
}
impl LivenessResults<'me, 'typeck, 'flow, 'tcx> {
fn new(cx: LivenessContext<'me, 'typeck, 'flow, 'tcx>) -> Self {
let num_points = cx.elements.num_points();
LivenessResults {
cx,
defs: HybridBitSet::new_empty(num_points),
use_live_at: HybridBitSet::new_empty(num_points),
drop_live_at: HybridBitSet::new_empty(num_points),
drop_locations: vec![],
stack: vec![],
}
}
fn compute_for_all_locals(&mut self, live_locals: Vec<Local>) {
for local in live_locals {
self.reset_local_state();
self.add_defs_for(local);
self.compute_use_live_points_for(local);
self.compute_drop_live_points_for(local);
let local_ty = self.cx.body.local_decls[local].ty;
if !self.use_live_at.is_empty() {
self.cx.add_use_live_facts_for(local_ty, &self.use_live_at);
}
if !self.drop_live_at.is_empty() {
self.cx.add_drop_live_facts_for(
local,
local_ty,
&self.drop_locations,
&self.drop_live_at,
);
}
}
}
fn add_extra_drop_facts(
&mut self,
drop_used: Vec<(Local, Location)>,
live_locals: FxHashSet<Local>,
) {
let locations = HybridBitSet::new_empty(self.cx.elements.num_points());
for (local, location) in drop_used {
if !live_locals.contains(&local) {
let local_ty = self.cx.body.local_decls[local].ty;
if local_ty.has_free_regions() {
self.cx.add_drop_live_facts_for(local, local_ty, &[location], &locations);
}
}
}
}
fn reset_local_state(&mut self) {
self.defs.clear();
self.use_live_at.clear();
self.drop_live_at.clear();
self.drop_locations.clear();
assert!(self.stack.is_empty());
}
fn add_defs_for(&mut self, local: Local) {
for def in self.cx.local_use_map.defs(local) {
debug!("- defined at {:?}", def);
self.defs.insert(def);
}
}
fn compute_use_live_points_for(&mut self, local: Local) {
debug!("compute_use_live_points_for(local={:?})", local);
self.stack.extend(self.cx.local_use_map.uses(local));
while let Some(p) = self.stack.pop() {
if self.defs.contains(p) {
continue;
}
if self.use_live_at.insert(p) {
self.cx.elements.push_predecessors(self.cx.body, p, &mut self.stack)
}
}
}
fn compute_drop_live_points_for(&mut self, local: Local) {
debug!("compute_drop_live_points_for(local={:?})", local);
let mpi = self.cx.move_data.rev_lookup.find_local(local);
debug!("compute_drop_live_points_for: mpi = {:?}", mpi);
for drop_point in self.cx.local_use_map.drops(local) {
let location = self.cx.elements.to_location(drop_point);
debug_assert_eq!(self.cx.body.terminator_loc(location.block), location,);
if self.cx.initialized_at_terminator(location.block, mpi) {
if self.drop_live_at.insert(drop_point) {
self.drop_locations.push(location);
self.stack.push(drop_point);
}
}
}
debug!("compute_drop_live_points_for: drop_locations={:?}", self.drop_locations);
while let Some(term_point) = self.stack.pop() {
self.compute_drop_live_points_for_block(mpi, term_point);
}
}
fn compute_drop_live_points_for_block(&mut self, mpi: MovePathIndex, term_point: PointIndex) {
debug!(
"compute_drop_live_points_for_block(mpi={:?}, term_point={:?})",
self.cx.move_data.move_paths[mpi].place,
self.cx.elements.to_location(term_point),
);
debug_assert!(self.drop_live_at.contains(term_point));
let term_location = self.cx.elements.to_location(term_point);
debug_assert_eq!(self.cx.body.terminator_loc(term_location.block), term_location,);
let block = term_location.block;
let entry_point = self.cx.elements.entry_point(term_location.block);
for p in (entry_point..term_point).rev() {
debug!("compute_drop_live_points_for_block: p = {:?}", self.cx.elements.to_location(p));
if self.defs.contains(p) {
debug!("compute_drop_live_points_for_block: def site");
return;
}
if self.use_live_at.contains(p) {
debug!("compute_drop_live_points_for_block: use-live at {:?}", p);
return;
}
if !self.drop_live_at.insert(p) {
debug!("compute_drop_live_points_for_block: already drop-live");
return;
}
}
let body = self.cx.body;
for &pred_block in body.predecessors()[block].iter() {
debug!("compute_drop_live_points_for_block: pred_block = {:?}", pred_block,);
if !self.cx.initialized_at_exit(pred_block, mpi) {
debug!("compute_drop_live_points_for_block: not initialized");
continue;
}
let pred_term_loc = self.cx.body.terminator_loc(pred_block);
let pred_term_point = self.cx.elements.point_from_location(pred_term_loc);
if self.defs.contains(pred_term_point) {
debug!("compute_drop_live_points_for_block: defined at {:?}", pred_term_loc);
continue;
}
if self.use_live_at.contains(pred_term_point) {
debug!("compute_drop_live_points_for_block: use-live at {:?}", pred_term_loc);
continue;
}
if self.drop_live_at.insert(pred_term_point) {
debug!("compute_drop_live_points_for_block: pushed to stack");
self.stack.push(pred_term_point);
}
}
}
}
impl LivenessContext<'_, '_, '_, 'tcx> {
fn initialized_at_curr_loc(&self, mpi: MovePathIndex) -> bool {
let state = self.flow_inits.get();
if state.contains(mpi) {
return true;
}
let move_paths = &self.flow_inits.analysis().move_data().move_paths;
move_paths[mpi].find_descendant(&move_paths, |mpi| state.contains(mpi)).is_some()
}
fn initialized_at_terminator(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool {
self.flow_inits.seek_before_primary_effect(self.body.terminator_loc(block));
self.initialized_at_curr_loc(mpi)
}
fn initialized_at_exit(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool {
self.flow_inits.seek_after_primary_effect(self.body.terminator_loc(block));
self.initialized_at_curr_loc(mpi)
}
fn add_use_live_facts_for(
&mut self,
value: impl TypeFoldable<'tcx>,
live_at: &HybridBitSet<PointIndex>,
) {
debug!("add_use_live_facts_for(value={:?})", value);
Self::make_all_regions_live(self.elements, &mut self.typeck, value, live_at)
}
fn add_drop_live_facts_for(
&mut self,
dropped_local: Local,
dropped_ty: Ty<'tcx>,
drop_locations: &[Location],
live_at: &HybridBitSet<PointIndex>,
) {
debug!(
"add_drop_live_constraint(\
dropped_local={:?}, \
dropped_ty={:?}, \
drop_locations={:?}, \
live_at={:?})",
dropped_local,
dropped_ty,
drop_locations,
values::location_set_str(self.elements, live_at.iter()),
);
let drop_data = self.drop_data.entry(dropped_ty).or_insert_with({
let typeck = &mut self.typeck;
move || Self::compute_drop_data(typeck, dropped_ty)
});
if let Some(data) = &drop_data.region_constraint_data {
for &drop_location in drop_locations {
self.typeck.push_region_constraints(
drop_location.to_locations(),
ConstraintCategory::Boring,
data,
);
}
}
drop_data.dropck_result.report_overflows(
self.typeck.infcx.tcx,
self.body.source_info(*drop_locations.first().unwrap()).span,
dropped_ty,
);
for &kind in &drop_data.dropck_result.kinds {
Self::make_all_regions_live(self.elements, &mut self.typeck, kind, live_at);
polonius::add_drop_of_var_derefs_origin(&mut self.typeck, dropped_local, &kind);
}
}
fn make_all_regions_live(
elements: &RegionValueElements,
typeck: &mut TypeChecker<'_, 'tcx>,
value: impl TypeFoldable<'tcx>,
live_at: &HybridBitSet<PointIndex>,
) {
debug!("make_all_regions_live(value={:?})", value);
debug!(
"make_all_regions_live: live_at={}",
values::location_set_str(elements, live_at.iter()),
);
let tcx = typeck.tcx();
tcx.for_each_free_region(&value, |live_region| {
let live_region_vid =
typeck.borrowck_context.universal_regions.to_region_vid(live_region);
typeck
.borrowck_context
.constraints
.liveness_constraints
.add_elements(live_region_vid, live_at);
});
}
fn compute_drop_data(
typeck: &mut TypeChecker<'_, 'tcx>,
dropped_ty: Ty<'tcx>,
) -> DropData<'tcx> {
debug!("compute_drop_data(dropped_ty={:?})", dropped_ty,);
let param_env = typeck.param_env;
let (dropck_result, region_constraint_data) =
param_env.and(DropckOutlives::new(dropped_ty)).fully_perform(typeck.infcx).unwrap();
DropData { dropck_result, region_constraint_data }
}
}