Remove dead unwinds before drop elaboration
As a part of drop elaboration, we identify dead unwinds, i.e., unwind edges on a drop terminators which are known to be unreachable, because there is no need to drop anything. Previously, the data flow framework was informed about the dead unwinds, and it assumed those edges are absent from MIR. Unfortunately, the data flow framework wasn't consistent in maintaining this assumption. In particular, if a block was reachable only through a dead unwind edge, its state was propagated to other blocks still. This became an issue in the context of change removes DropAndReplace terminator, since it introduces initialization into cleanup blocks. To avoid this issue, remove unreachable unwind edges before the drop elaboration, and elaborate only blocks that remain reachable.
This commit is contained in:
parent
00eec854f1
commit
23e3840014
3 changed files with 44 additions and 76 deletions
|
@ -1,4 +1,3 @@
|
||||||
use rustc_index::bit_set::BitSet;
|
|
||||||
use rustc_middle::mir::{self, BasicBlock, Location, SwitchTargets};
|
use rustc_middle::mir::{self, BasicBlock, Location, SwitchTargets};
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
@ -54,7 +53,6 @@ pub trait Direction {
|
||||||
analysis: &A,
|
analysis: &A,
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
body: &mir::Body<'tcx>,
|
body: &mir::Body<'tcx>,
|
||||||
dead_unwinds: Option<&BitSet<BasicBlock>>,
|
|
||||||
exit_state: &mut A::Domain,
|
exit_state: &mut A::Domain,
|
||||||
block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||||
propagate: impl FnMut(BasicBlock, &A::Domain),
|
propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||||
|
@ -221,7 +219,6 @@ impl Direction for Backward {
|
||||||
analysis: &A,
|
analysis: &A,
|
||||||
_tcx: TyCtxt<'tcx>,
|
_tcx: TyCtxt<'tcx>,
|
||||||
body: &mir::Body<'tcx>,
|
body: &mir::Body<'tcx>,
|
||||||
dead_unwinds: Option<&BitSet<BasicBlock>>,
|
|
||||||
exit_state: &mut A::Domain,
|
exit_state: &mut A::Domain,
|
||||||
(bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
(bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||||
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||||
|
@ -278,20 +275,6 @@ impl Direction for Backward {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore dead unwinds.
|
|
||||||
mir::TerminatorKind::Call { cleanup: Some(unwind), .. }
|
|
||||||
| mir::TerminatorKind::Assert { cleanup: Some(unwind), .. }
|
|
||||||
| mir::TerminatorKind::Drop { unwind: Some(unwind), .. }
|
|
||||||
| mir::TerminatorKind::DropAndReplace { unwind: Some(unwind), .. }
|
|
||||||
| mir::TerminatorKind::FalseUnwind { unwind: Some(unwind), .. }
|
|
||||||
| mir::TerminatorKind::InlineAsm { cleanup: Some(unwind), .. }
|
|
||||||
if unwind == bb =>
|
|
||||||
{
|
|
||||||
if dead_unwinds.map_or(true, |dead| !dead.contains(pred)) {
|
|
||||||
propagate(pred, exit_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => propagate(pred, exit_state),
|
_ => propagate(pred, exit_state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,7 +287,6 @@ struct BackwardSwitchIntEdgeEffectsApplier<'a, 'tcx, D, F> {
|
||||||
exit_state: &'a mut D,
|
exit_state: &'a mut D,
|
||||||
bb: BasicBlock,
|
bb: BasicBlock,
|
||||||
propagate: &'a mut F,
|
propagate: &'a mut F,
|
||||||
|
|
||||||
effects_applied: bool,
|
effects_applied: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +466,6 @@ impl Direction for Forward {
|
||||||
analysis: &A,
|
analysis: &A,
|
||||||
_tcx: TyCtxt<'tcx>,
|
_tcx: TyCtxt<'tcx>,
|
||||||
_body: &mir::Body<'tcx>,
|
_body: &mir::Body<'tcx>,
|
||||||
dead_unwinds: Option<&BitSet<BasicBlock>>,
|
|
||||||
exit_state: &mut A::Domain,
|
exit_state: &mut A::Domain,
|
||||||
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||||
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||||
|
@ -502,10 +483,8 @@ impl Direction for Forward {
|
||||||
| DropAndReplace { target, unwind, value: _, place: _ }
|
| DropAndReplace { target, unwind, value: _, place: _ }
|
||||||
| FalseUnwind { real_target: target, unwind } => {
|
| FalseUnwind { real_target: target, unwind } => {
|
||||||
if let Some(unwind) = unwind {
|
if let Some(unwind) = unwind {
|
||||||
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
|
|
||||||
propagate(unwind, exit_state);
|
propagate(unwind, exit_state);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
propagate(target, exit_state);
|
propagate(target, exit_state);
|
||||||
}
|
}
|
||||||
|
@ -534,10 +513,8 @@ impl Direction for Forward {
|
||||||
fn_span: _,
|
fn_span: _,
|
||||||
} => {
|
} => {
|
||||||
if let Some(unwind) = cleanup {
|
if let Some(unwind) = cleanup {
|
||||||
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
|
|
||||||
propagate(unwind, exit_state);
|
propagate(unwind, exit_state);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(target) = target {
|
if let Some(target) = target {
|
||||||
// N.B.: This must be done *last*, otherwise the unwind path will see the call
|
// N.B.: This must be done *last*, otherwise the unwind path will see the call
|
||||||
|
@ -560,10 +537,8 @@ impl Direction for Forward {
|
||||||
cleanup,
|
cleanup,
|
||||||
} => {
|
} => {
|
||||||
if let Some(unwind) = cleanup {
|
if let Some(unwind) = cleanup {
|
||||||
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
|
|
||||||
propagate(unwind, exit_state);
|
propagate(unwind, exit_state);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(target) = destination {
|
if let Some(target) = destination {
|
||||||
// N.B.: This must be done *last*, otherwise the unwind path will see the call
|
// N.B.: This must be done *last*, otherwise the unwind path will see the call
|
||||||
|
|
|
@ -12,7 +12,6 @@ use rustc_ast as ast;
|
||||||
use rustc_data_structures::work_queue::WorkQueue;
|
use rustc_data_structures::work_queue::WorkQueue;
|
||||||
use rustc_graphviz as dot;
|
use rustc_graphviz as dot;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_index::bit_set::BitSet;
|
|
||||||
use rustc_index::vec::{Idx, IndexVec};
|
use rustc_index::vec::{Idx, IndexVec};
|
||||||
use rustc_middle::mir::{self, traversal, BasicBlock};
|
use rustc_middle::mir::{self, traversal, BasicBlock};
|
||||||
use rustc_middle::mir::{create_dump_file, dump_enabled};
|
use rustc_middle::mir::{create_dump_file, dump_enabled};
|
||||||
|
@ -78,7 +77,6 @@ where
|
||||||
{
|
{
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
body: &'a mir::Body<'tcx>,
|
body: &'a mir::Body<'tcx>,
|
||||||
dead_unwinds: Option<&'a BitSet<BasicBlock>>,
|
|
||||||
entry_sets: IndexVec<BasicBlock, A::Domain>,
|
entry_sets: IndexVec<BasicBlock, A::Domain>,
|
||||||
pass_name: Option<&'static str>,
|
pass_name: Option<&'static str>,
|
||||||
analysis: A,
|
analysis: A,
|
||||||
|
@ -154,25 +152,7 @@ where
|
||||||
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
|
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
|
||||||
}
|
}
|
||||||
|
|
||||||
Engine {
|
Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_trans_for_block }
|
||||||
analysis,
|
|
||||||
tcx,
|
|
||||||
body,
|
|
||||||
dead_unwinds: None,
|
|
||||||
pass_name: None,
|
|
||||||
entry_sets,
|
|
||||||
apply_trans_for_block,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signals that we do not want dataflow state to propagate across unwind edges for these
|
|
||||||
/// `BasicBlock`s.
|
|
||||||
///
|
|
||||||
/// You must take care that `dead_unwinds` does not contain a `BasicBlock` that *can* actually
|
|
||||||
/// unwind during execution. Otherwise, your dataflow results will not be correct.
|
|
||||||
pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
|
|
||||||
self.dead_unwinds = Some(dead_unwinds);
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
|
/// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
|
||||||
|
@ -190,14 +170,7 @@ where
|
||||||
A::Domain: DebugWithContext<A>,
|
A::Domain: DebugWithContext<A>,
|
||||||
{
|
{
|
||||||
let Engine {
|
let Engine {
|
||||||
analysis,
|
analysis, body, mut entry_sets, tcx, apply_trans_for_block, pass_name, ..
|
||||||
body,
|
|
||||||
dead_unwinds,
|
|
||||||
mut entry_sets,
|
|
||||||
tcx,
|
|
||||||
apply_trans_for_block,
|
|
||||||
pass_name,
|
|
||||||
..
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());
|
let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());
|
||||||
|
@ -236,7 +209,6 @@ where
|
||||||
&analysis,
|
&analysis,
|
||||||
tcx,
|
tcx,
|
||||||
body,
|
body,
|
||||||
dead_unwinds,
|
|
||||||
&mut state,
|
&mut state,
|
||||||
(bb, bb_data),
|
(bb, bb_data),
|
||||||
|target: BasicBlock, state: &A::Domain| {
|
|target: BasicBlock, state: &A::Domain| {
|
||||||
|
|
|
@ -67,13 +67,11 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||||
};
|
};
|
||||||
let un_derefer = UnDerefer { tcx: tcx, derefer_sidetable: side_table };
|
let un_derefer = UnDerefer { tcx: tcx, derefer_sidetable: side_table };
|
||||||
let elaborate_patch = {
|
let elaborate_patch = {
|
||||||
let body = &*body;
|
|
||||||
let env = MoveDataParamEnv { move_data, param_env };
|
let env = MoveDataParamEnv { move_data, param_env };
|
||||||
let dead_unwinds = find_dead_unwinds(tcx, body, &env, &un_derefer);
|
remove_dead_unwinds(tcx, body, &env, &un_derefer);
|
||||||
|
|
||||||
let inits = MaybeInitializedPlaces::new(tcx, body, &env)
|
let inits = MaybeInitializedPlaces::new(tcx, body, &env)
|
||||||
.into_engine(tcx, body)
|
.into_engine(tcx, body)
|
||||||
.dead_unwinds(&dead_unwinds)
|
|
||||||
.pass_name("elaborate_drops")
|
.pass_name("elaborate_drops")
|
||||||
.iterate_to_fixpoint()
|
.iterate_to_fixpoint()
|
||||||
.into_results_cursor(body);
|
.into_results_cursor(body);
|
||||||
|
@ -81,11 +79,12 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||||
let uninits = MaybeUninitializedPlaces::new(tcx, body, &env)
|
let uninits = MaybeUninitializedPlaces::new(tcx, body, &env)
|
||||||
.mark_inactive_variants_as_uninit()
|
.mark_inactive_variants_as_uninit()
|
||||||
.into_engine(tcx, body)
|
.into_engine(tcx, body)
|
||||||
.dead_unwinds(&dead_unwinds)
|
|
||||||
.pass_name("elaborate_drops")
|
.pass_name("elaborate_drops")
|
||||||
.iterate_to_fixpoint()
|
.iterate_to_fixpoint()
|
||||||
.into_results_cursor(body);
|
.into_results_cursor(body);
|
||||||
|
|
||||||
|
let reachable = traversal::reachable_as_bitset(body);
|
||||||
|
|
||||||
ElaborateDropsCtxt {
|
ElaborateDropsCtxt {
|
||||||
tcx,
|
tcx,
|
||||||
body,
|
body,
|
||||||
|
@ -94,6 +93,7 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||||
drop_flags: Default::default(),
|
drop_flags: Default::default(),
|
||||||
patch: MirPatch::new(body),
|
patch: MirPatch::new(body),
|
||||||
un_derefer: un_derefer,
|
un_derefer: un_derefer,
|
||||||
|
reachable,
|
||||||
}
|
}
|
||||||
.elaborate()
|
.elaborate()
|
||||||
};
|
};
|
||||||
|
@ -102,22 +102,21 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the set of basic blocks whose unwind edges are known
|
/// Removes unwind edges which are known to be unreachable, because they are in `drop` terminators
|
||||||
/// to not be reachable, because they are `drop` terminators
|
|
||||||
/// that can't drop anything.
|
/// that can't drop anything.
|
||||||
fn find_dead_unwinds<'tcx>(
|
fn remove_dead_unwinds<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
body: &Body<'tcx>,
|
body: &mut Body<'tcx>,
|
||||||
env: &MoveDataParamEnv<'tcx>,
|
env: &MoveDataParamEnv<'tcx>,
|
||||||
und: &UnDerefer<'tcx>,
|
und: &UnDerefer<'tcx>,
|
||||||
) -> BitSet<BasicBlock> {
|
) {
|
||||||
debug!("find_dead_unwinds({:?})", body.span);
|
debug!("remove_dead_unwinds({:?})", body.span);
|
||||||
// We only need to do this pass once, because unwind edges can only
|
// We only need to do this pass once, because unwind edges can only
|
||||||
// reach cleanup blocks, which can't have unwind edges themselves.
|
// reach cleanup blocks, which can't have unwind edges themselves.
|
||||||
let mut dead_unwinds = BitSet::new_empty(body.basic_blocks.len());
|
let mut dead_unwinds = Vec::new();
|
||||||
let mut flow_inits = MaybeInitializedPlaces::new(tcx, body, &env)
|
let mut flow_inits = MaybeInitializedPlaces::new(tcx, body, &env)
|
||||||
.into_engine(tcx, body)
|
.into_engine(tcx, body)
|
||||||
.pass_name("find_dead_unwinds")
|
.pass_name("remove_dead_unwinds")
|
||||||
.iterate_to_fixpoint()
|
.iterate_to_fixpoint()
|
||||||
.into_results_cursor(body);
|
.into_results_cursor(body);
|
||||||
for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
|
for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
|
||||||
|
@ -129,16 +128,16 @@ fn find_dead_unwinds<'tcx>(
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("find_dead_unwinds @ {:?}: {:?}", bb, bb_data);
|
debug!("remove_dead_unwinds @ {:?}: {:?}", bb, bb_data);
|
||||||
|
|
||||||
let LookupResult::Exact(path) = env.move_data.rev_lookup.find(place.as_ref()) else {
|
let LookupResult::Exact(path) = env.move_data.rev_lookup.find(place.as_ref()) else {
|
||||||
debug!("find_dead_unwinds: has parent; skipping");
|
debug!("remove_dead_unwinds: has parent; skipping");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
flow_inits.seek_before_primary_effect(body.terminator_loc(bb));
|
flow_inits.seek_before_primary_effect(body.terminator_loc(bb));
|
||||||
debug!(
|
debug!(
|
||||||
"find_dead_unwinds @ {:?}: path({:?})={:?}; init_data={:?}",
|
"remove_dead_unwinds @ {:?}: path({:?})={:?}; init_data={:?}",
|
||||||
bb,
|
bb,
|
||||||
place,
|
place,
|
||||||
path,
|
path,
|
||||||
|
@ -150,13 +149,22 @@ fn find_dead_unwinds<'tcx>(
|
||||||
maybe_live |= flow_inits.contains(child);
|
maybe_live |= flow_inits.contains(child);
|
||||||
});
|
});
|
||||||
|
|
||||||
debug!("find_dead_unwinds @ {:?}: maybe_live={}", bb, maybe_live);
|
debug!("remove_dead_unwinds @ {:?}: maybe_live={}", bb, maybe_live);
|
||||||
if !maybe_live {
|
if !maybe_live {
|
||||||
dead_unwinds.insert(bb);
|
dead_unwinds.push(bb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dead_unwinds
|
if dead_unwinds.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let basic_blocks = body.basic_blocks.as_mut();
|
||||||
|
for &bb in dead_unwinds.iter() {
|
||||||
|
if let Some(unwind) = basic_blocks[bb].terminator_mut().unwind_mut() {
|
||||||
|
*unwind = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InitializationData<'mir, 'tcx> {
|
struct InitializationData<'mir, 'tcx> {
|
||||||
|
@ -290,6 +298,7 @@ struct ElaborateDropsCtxt<'a, 'tcx> {
|
||||||
drop_flags: FxHashMap<MovePathIndex, Local>,
|
drop_flags: FxHashMap<MovePathIndex, Local>,
|
||||||
patch: MirPatch<'tcx>,
|
patch: MirPatch<'tcx>,
|
||||||
un_derefer: UnDerefer<'tcx>,
|
un_derefer: UnDerefer<'tcx>,
|
||||||
|
reachable: BitSet<BasicBlock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||||
|
@ -329,6 +338,9 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||||
|
|
||||||
fn collect_drop_flags(&mut self) {
|
fn collect_drop_flags(&mut self) {
|
||||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||||
|
if !self.reachable.contains(bb) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let terminator = data.terminator();
|
let terminator = data.terminator();
|
||||||
let place = match terminator.kind {
|
let place = match terminator.kind {
|
||||||
TerminatorKind::Drop { ref place, .. }
|
TerminatorKind::Drop { ref place, .. }
|
||||||
|
@ -384,6 +396,9 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||||
|
|
||||||
fn elaborate_drops(&mut self) {
|
fn elaborate_drops(&mut self) {
|
||||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||||
|
if !self.reachable.contains(bb) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let loc = Location { block: bb, statement_index: data.statements.len() };
|
let loc = Location { block: bb, statement_index: data.statements.len() };
|
||||||
let terminator = data.terminator();
|
let terminator = data.terminator();
|
||||||
|
|
||||||
|
@ -541,6 +556,9 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||||
|
|
||||||
fn drop_flags_for_fn_rets(&mut self) {
|
fn drop_flags_for_fn_rets(&mut self) {
|
||||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||||
|
if !self.reachable.contains(bb) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let TerminatorKind::Call {
|
if let TerminatorKind::Call {
|
||||||
destination, target: Some(tgt), cleanup: Some(_), ..
|
destination, target: Some(tgt), cleanup: Some(_), ..
|
||||||
} = data.terminator().kind
|
} = data.terminator().kind
|
||||||
|
@ -576,6 +594,9 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||||
// clobbered before they are read.
|
// clobbered before they are read.
|
||||||
|
|
||||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||||
|
if !self.reachable.contains(bb) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
debug!("drop_flags_for_locs({:?})", data);
|
debug!("drop_flags_for_locs({:?})", data);
|
||||||
for i in 0..(data.statements.len() + 1) {
|
for i in 0..(data.statements.len() + 1) {
|
||||||
debug!("drop_flag_for_locs: stmt {}", i);
|
debug!("drop_flag_for_locs: stmt {}", i);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue