summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_mir_dataflow
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_mir_dataflow')
-rw-r--r--compiler/rustc_mir_dataflow/src/elaborate_drops.rs48
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/direction.rs173
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/engine.rs41
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/fmt.rs32
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/graphviz.rs6
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/lattice.rs94
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/mod.rs111
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/tests.rs7
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs65
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/initialized.rs778
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/liveness.rs92
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/mod.rs757
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs83
-rw-r--r--compiler/rustc_mir_dataflow/src/lib.rs5
-rw-r--r--compiler/rustc_mir_dataflow/src/move_paths/builder.rs125
-rw-r--r--compiler/rustc_mir_dataflow/src/move_paths/mod.rs72
-rw-r--r--compiler/rustc_mir_dataflow/src/rustc_peek.rs6
-rw-r--r--compiler/rustc_mir_dataflow/src/un_derefer.rs100
-rw-r--r--compiler/rustc_mir_dataflow/src/value_analysis.rs73
19 files changed, 1413 insertions, 1255 deletions
diff --git a/compiler/rustc_mir_dataflow/src/elaborate_drops.rs b/compiler/rustc_mir_dataflow/src/elaborate_drops.rs
index 0540a5e94..9e02b0271 100644
--- a/compiler/rustc_mir_dataflow/src/elaborate_drops.rs
+++ b/compiler/rustc_mir_dataflow/src/elaborate_drops.rs
@@ -4,8 +4,8 @@ use rustc_index::Idx;
use rustc_middle::mir::patch::MirPatch;
use rustc_middle::mir::*;
use rustc_middle::traits::Reveal;
-use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::util::IntTypeExt;
+use rustc_middle::ty::GenericArgsRef;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT};
use std::{fmt, iter};
@@ -263,7 +263,7 @@ where
base_place: Place<'tcx>,
variant_path: D::Path,
variant: &'tcx ty::VariantDef,
- substs: SubstsRef<'tcx>,
+ args: GenericArgsRef<'tcx>,
) -> Vec<(Place<'tcx>, Option<D::Path>)> {
variant
.fields
@@ -276,7 +276,7 @@ where
assert_eq!(self.elaborator.param_env().reveal(), Reveal::All);
let field_ty =
- tcx.normalize_erasing_regions(self.elaborator.param_env(), f.ty(tcx, substs));
+ tcx.normalize_erasing_regions(self.elaborator.param_env(), f.ty(tcx, args));
(tcx.mk_place_field(base_place, field, field_ty), subpath)
})
@@ -414,16 +414,16 @@ where
fn open_drop_for_box_contents(
&mut self,
adt: ty::AdtDef<'tcx>,
- substs: SubstsRef<'tcx>,
+ args: GenericArgsRef<'tcx>,
succ: BasicBlock,
unwind: Unwind,
) -> BasicBlock {
// drop glue is sent straight to codegen
// box cannot be directly dereferenced
- let unique_ty = adt.non_enum_variant().fields[FieldIdx::new(0)].ty(self.tcx(), substs);
+ let unique_ty = adt.non_enum_variant().fields[FieldIdx::new(0)].ty(self.tcx(), args);
let unique_variant = unique_ty.ty_adt_def().unwrap().non_enum_variant();
- let nonnull_ty = unique_variant.fields[FieldIdx::from_u32(0)].ty(self.tcx(), substs);
- let ptr_ty = Ty::new_imm_ptr(self.tcx(), substs[0].expect_ty());
+ let nonnull_ty = unique_variant.fields[FieldIdx::from_u32(0)].ty(self.tcx(), args);
+ let ptr_ty = Ty::new_imm_ptr(self.tcx(), args[0].expect_ty());
let unique_place = self.tcx().mk_place_field(self.place, FieldIdx::new(0), unique_ty);
let nonnull_place = self.tcx().mk_place_field(unique_place, FieldIdx::new(0), nonnull_ty);
@@ -436,7 +436,11 @@ where
}
#[instrument(level = "debug", ret)]
- fn open_drop_for_adt(&mut self, adt: ty::AdtDef<'tcx>, substs: SubstsRef<'tcx>) -> BasicBlock {
+ fn open_drop_for_adt(
+ &mut self,
+ adt: ty::AdtDef<'tcx>,
+ args: GenericArgsRef<'tcx>,
+ ) -> BasicBlock {
if adt.variants().is_empty() {
return self.elaborator.patch().new_block(BasicBlockData {
statements: vec![],
@@ -453,7 +457,7 @@ where
let contents_drop = if skip_contents {
(self.succ, self.unwind)
} else {
- self.open_drop_for_adt_contents(adt, substs)
+ self.open_drop_for_adt_contents(adt, args)
};
if adt.is_box() {
@@ -463,7 +467,7 @@ where
.1
.map(|unwind| self.destructor_call_block((unwind, Unwind::InCleanup)));
- self.open_drop_for_box_contents(adt, substs, succ, unwind)
+ self.open_drop_for_box_contents(adt, args, succ, unwind)
} else if adt.has_dtor(self.tcx()) {
self.destructor_call_block(contents_drop)
} else {
@@ -474,7 +478,7 @@ where
fn open_drop_for_adt_contents(
&mut self,
adt: ty::AdtDef<'tcx>,
- substs: SubstsRef<'tcx>,
+ args: GenericArgsRef<'tcx>,
) -> (BasicBlock, Unwind) {
let (succ, unwind) = self.drop_ladder_bottom();
if !adt.is_enum() {
@@ -482,18 +486,18 @@ where
self.place,
self.path,
&adt.variant(FIRST_VARIANT),
- substs,
+ args,
);
self.drop_ladder(fields, succ, unwind)
} else {
- self.open_drop_for_multivariant(adt, substs, succ, unwind)
+ self.open_drop_for_multivariant(adt, args, succ, unwind)
}
}
fn open_drop_for_multivariant(
&mut self,
adt: ty::AdtDef<'tcx>,
- substs: SubstsRef<'tcx>,
+ args: GenericArgsRef<'tcx>,
succ: BasicBlock,
unwind: Unwind,
) -> (BasicBlock, Unwind) {
@@ -515,7 +519,7 @@ where
self.place,
ProjectionElem::Downcast(Some(variant.name), variant_index),
);
- let fields = self.move_paths_for_fields(base_place, variant_path, &variant, substs);
+ let fields = self.move_paths_for_fields(base_place, variant_path, &variant, args);
values.push(discr.val);
if let Unwind::To(unwind) = unwind {
// We can't use the half-ladder from the original
@@ -550,7 +554,7 @@ where
let have_field_with_drop_glue = variant
.fields
.iter()
- .any(|field| field.ty(tcx, substs).needs_drop(tcx, param_env));
+ .any(|field| field.ty(tcx, args).needs_drop(tcx, param_env));
if have_field_with_drop_glue {
have_otherwise_with_drop_glue = true;
}
@@ -856,22 +860,16 @@ where
fn open_drop(&mut self) -> BasicBlock {
let ty = self.place_ty(self.place);
match ty.kind() {
- ty::Closure(_, substs) => {
- let tys: Vec<_> = substs.as_closure().upvar_tys().collect();
- self.open_drop_for_tuple(&tys)
- }
+ ty::Closure(_, args) => self.open_drop_for_tuple(&args.as_closure().upvar_tys()),
// Note that `elaborate_drops` only drops the upvars of a generator,
// and this is ok because `open_drop` here can only be reached
// within that own generator's resume function.
// This should only happen for the self argument on the resume function.
// It effectively only contains upvars until the generator transformation runs.
// See librustc_body/transform/generator.rs for more details.
- ty::Generator(_, substs, _) => {
- let tys: Vec<_> = substs.as_generator().upvar_tys().collect();
- self.open_drop_for_tuple(&tys)
- }
+ ty::Generator(_, args, _) => self.open_drop_for_tuple(&args.as_generator().upvar_tys()),
ty::Tuple(fields) => self.open_drop_for_tuple(fields),
- ty::Adt(def, substs) => self.open_drop_for_adt(*def, substs),
+ ty::Adt(def, args) => self.open_drop_for_adt(*def, args),
ty::Dynamic(..) => self.complete_drop(self.succ, self.unwind),
ty::Array(ety, size) => {
let size = size.try_eval_target_usize(self.tcx(), self.elaborator.param_env());
diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs
index 804b44a6b..8a9e37c5a 100644
--- a/compiler/rustc_mir_dataflow/src/framework/direction.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs
@@ -1,11 +1,10 @@
-use rustc_middle::mir::{self, BasicBlock, Location, SwitchTargets, UnwindAction};
-use rustc_middle::ty::TyCtxt;
+use rustc_middle::mir::{
+ self, BasicBlock, CallReturnPlaces, Location, SwitchTargets, TerminatorEdges, UnwindAction,
+};
use std::ops::RangeInclusive;
use super::visitor::{ResultsVisitable, ResultsVisitor};
-use super::{
- Analysis, CallReturnPlaces, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget,
-};
+use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget};
pub trait Direction {
const IS_FORWARD: bool;
@@ -24,15 +23,17 @@ pub trait Direction {
) where
A: Analysis<'tcx>;
- fn apply_effects_in_block<'tcx, A>(
+ fn apply_effects_in_block<'mir, 'tcx, A>(
analysis: &mut A,
state: &mut A::Domain,
block: BasicBlock,
- block_data: &mir::BasicBlockData<'tcx>,
- ) where
+ block_data: &'mir mir::BasicBlockData<'tcx>,
+ statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
+ ) -> TerminatorEdges<'mir, 'tcx>
+ where
A: Analysis<'tcx>;
- fn gen_kill_effects_in_block<'tcx, A>(
+ fn gen_kill_statement_effects_in_block<'tcx, A>(
analysis: &mut A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
@@ -51,10 +52,10 @@ pub trait Direction {
fn join_state_into_successors_of<'tcx, A>(
analysis: &mut A,
- tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
exit_state: &mut A::Domain,
- block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
+ block: BasicBlock,
+ edges: TerminatorEdges<'_, 'tcx>,
propagate: impl FnMut(BasicBlock, &A::Domain),
) where
A: Analysis<'tcx>;
@@ -66,27 +67,33 @@ pub struct Backward;
impl Direction for Backward {
const IS_FORWARD: bool = false;
- fn apply_effects_in_block<'tcx, A>(
+ fn apply_effects_in_block<'mir, 'tcx, A>(
analysis: &mut A,
state: &mut A::Domain,
block: BasicBlock,
- block_data: &mir::BasicBlockData<'tcx>,
- ) where
+ block_data: &'mir mir::BasicBlockData<'tcx>,
+ statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
+ ) -> TerminatorEdges<'mir, 'tcx>
+ where
A: Analysis<'tcx>,
{
let terminator = block_data.terminator();
let location = Location { block, statement_index: block_data.statements.len() };
analysis.apply_before_terminator_effect(state, terminator, location);
- analysis.apply_terminator_effect(state, terminator, location);
-
- for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
- let location = Location { block, statement_index };
- analysis.apply_before_statement_effect(state, statement, location);
- analysis.apply_statement_effect(state, statement, location);
+ let edges = analysis.apply_terminator_effect(state, terminator, location);
+ if let Some(statement_effect) = statement_effect {
+ statement_effect(block, state)
+ } else {
+ for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
+ let location = Location { block, statement_index };
+ analysis.apply_before_statement_effect(state, statement, location);
+ analysis.apply_statement_effect(state, statement, location);
+ }
}
+ edges
}
- fn gen_kill_effects_in_block<'tcx, A>(
+ fn gen_kill_statement_effects_in_block<'tcx, A>(
analysis: &mut A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
@@ -94,11 +101,6 @@ impl Direction for Backward {
) where
A: GenKillAnalysis<'tcx>,
{
- let terminator = block_data.terminator();
- let location = Location { block, statement_index: block_data.statements.len() };
- analysis.before_terminator_effect(trans, terminator, location);
- analysis.terminator_effect(trans, terminator, location);
-
for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
let location = Location { block, statement_index };
analysis.before_statement_effect(trans, statement, location);
@@ -217,10 +219,10 @@ impl Direction for Backward {
fn join_state_into_successors_of<'tcx, A>(
analysis: &mut A,
- _tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
exit_state: &mut A::Domain,
- (bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
+ bb: BasicBlock,
+ _edges: TerminatorEdges<'_, 'tcx>,
mut propagate: impl FnMut(BasicBlock, &A::Domain),
) where
A: Analysis<'tcx>,
@@ -254,7 +256,11 @@ impl Direction for Backward {
mir::TerminatorKind::Yield { resume, resume_arg, .. } if resume == bb => {
let mut tmp = exit_state.clone();
- analysis.apply_yield_resume_effect(&mut tmp, resume, resume_arg);
+ analysis.apply_call_return_effect(
+ &mut tmp,
+ resume,
+ CallReturnPlaces::Yield(resume_arg),
+ );
propagate(pred, &tmp);
}
@@ -318,27 +324,33 @@ pub struct Forward;
impl Direction for Forward {
const IS_FORWARD: bool = true;
- fn apply_effects_in_block<'tcx, A>(
+ fn apply_effects_in_block<'mir, 'tcx, A>(
analysis: &mut A,
state: &mut A::Domain,
block: BasicBlock,
- block_data: &mir::BasicBlockData<'tcx>,
- ) where
+ block_data: &'mir mir::BasicBlockData<'tcx>,
+ statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
+ ) -> TerminatorEdges<'mir, 'tcx>
+ where
A: Analysis<'tcx>,
{
- for (statement_index, statement) in block_data.statements.iter().enumerate() {
- let location = Location { block, statement_index };
- analysis.apply_before_statement_effect(state, statement, location);
- analysis.apply_statement_effect(state, statement, location);
+ if let Some(statement_effect) = statement_effect {
+ statement_effect(block, state)
+ } else {
+ for (statement_index, statement) in block_data.statements.iter().enumerate() {
+ let location = Location { block, statement_index };
+ analysis.apply_before_statement_effect(state, statement, location);
+ analysis.apply_statement_effect(state, statement, location);
+ }
}
let terminator = block_data.terminator();
let location = Location { block, statement_index: block_data.statements.len() };
analysis.apply_before_terminator_effect(state, terminator, location);
- analysis.apply_terminator_effect(state, terminator, location);
+ analysis.apply_terminator_effect(state, terminator, location)
}
- fn gen_kill_effects_in_block<'tcx, A>(
+ fn gen_kill_statement_effects_in_block<'tcx, A>(
analysis: &mut A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
@@ -351,11 +363,6 @@ impl Direction for Forward {
analysis.before_statement_effect(trans, statement, location);
analysis.statement_effect(trans, statement, location);
}
-
- let terminator = block_data.terminator();
- let location = Location { block, statement_index: block_data.statements.len() };
- analysis.before_terminator_effect(trans, terminator, location);
- analysis.terminator_effect(trans, terminator, location);
}
fn apply_effects_in_range<'tcx, A>(
@@ -464,86 +471,32 @@ impl Direction for Forward {
fn join_state_into_successors_of<'tcx, A>(
analysis: &mut A,
- _tcx: TyCtxt<'tcx>,
_body: &mir::Body<'tcx>,
exit_state: &mut A::Domain,
- (bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
+ bb: BasicBlock,
+ edges: TerminatorEdges<'_, 'tcx>,
mut propagate: impl FnMut(BasicBlock, &A::Domain),
) where
A: Analysis<'tcx>,
{
- use mir::TerminatorKind::*;
- match bb_data.terminator().kind {
- Return | Resume | Terminate | GeneratorDrop | Unreachable => {}
-
- Goto { target } => propagate(target, exit_state),
-
- Assert { target, unwind, expected: _, msg: _, cond: _ }
- | Drop { target, unwind, place: _, replace: _ }
- | FalseUnwind { real_target: target, unwind } => {
- if let UnwindAction::Cleanup(unwind) = unwind {
- propagate(unwind, exit_state);
- }
-
+ match edges {
+ TerminatorEdges::None => {}
+ TerminatorEdges::Single(target) => propagate(target, exit_state),
+ TerminatorEdges::Double(target, unwind) => {
propagate(target, exit_state);
+ propagate(unwind, exit_state);
}
-
- FalseEdge { real_target, imaginary_target } => {
- propagate(real_target, exit_state);
- propagate(imaginary_target, exit_state);
- }
-
- Yield { resume: target, drop, resume_arg, value: _ } => {
- if let Some(drop) = drop {
- propagate(drop, exit_state);
- }
-
- analysis.apply_yield_resume_effect(exit_state, target, resume_arg);
- propagate(target, exit_state);
- }
-
- Call { unwind, destination, target, func: _, args: _, call_source: _, fn_span: _ } => {
- if let UnwindAction::Cleanup(unwind) = unwind {
- propagate(unwind, exit_state);
- }
-
- if let Some(target) = target {
- // N.B.: This must be done *last*, otherwise the unwind path will see the call
- // return effect.
- analysis.apply_call_return_effect(
- exit_state,
- bb,
- CallReturnPlaces::Call(destination),
- );
- propagate(target, exit_state);
- }
- }
-
- InlineAsm {
- template: _,
- ref operands,
- options: _,
- line_spans: _,
- destination,
- unwind,
- } => {
+ TerminatorEdges::AssignOnReturn { return_, unwind, place } => {
+ // This must be done *first*, otherwise the unwind path will see the assignments.
if let UnwindAction::Cleanup(unwind) = unwind {
propagate(unwind, exit_state);
}
-
- if let Some(target) = destination {
- // N.B.: This must be done *last*, otherwise the unwind path will see the call
- // return effect.
- analysis.apply_call_return_effect(
- exit_state,
- bb,
- CallReturnPlaces::InlineAsm(operands),
- );
- propagate(target, exit_state);
+ if let Some(return_) = return_ {
+ analysis.apply_call_return_effect(exit_state, bb, place);
+ propagate(return_, exit_state);
}
}
-
- SwitchInt { ref targets, ref discr } => {
+ TerminatorEdges::SwitchInt { targets, discr } => {
let mut applier = ForwardSwitchIntEdgeEffectsApplier {
exit_state,
targets,
diff --git a/compiler/rustc_mir_dataflow/src/framework/engine.rs b/compiler/rustc_mir_dataflow/src/framework/engine.rs
index c755d7588..a29962d77 100644
--- a/compiler/rustc_mir_dataflow/src/framework/engine.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/engine.rs
@@ -144,7 +144,7 @@ where
// gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
// performance in practice. I've tried a few ways to avoid this, but they have downsides. See
// the message for the commit that added this FIXME for more information.
- apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
+ apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
}
impl<'a, 'tcx, A, D, T> Engine<'a, 'tcx, A>
@@ -165,12 +165,17 @@ where
// Otherwise, compute and store the cumulative transfer function for each block.
- let identity = GenKillSet::identity(analysis.bottom_value(body).domain_size());
+ let identity = GenKillSet::identity(analysis.domain_size(body));
let mut trans_for_block = IndexVec::from_elem(identity, &body.basic_blocks);
for (block, block_data) in body.basic_blocks.iter_enumerated() {
let trans = &mut trans_for_block[block];
- A::Direction::gen_kill_effects_in_block(&mut analysis, trans, block, block_data);
+ A::Direction::gen_kill_statement_effects_in_block(
+ &mut analysis,
+ trans,
+ block,
+ block_data,
+ );
}
let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
@@ -199,17 +204,18 @@ where
tcx: TyCtxt<'tcx>,
body: &'a mir::Body<'tcx>,
analysis: A,
- apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
+ apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
) -> Self {
- let bottom_value = analysis.bottom_value(body);
- let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), &body.basic_blocks);
+ let mut entry_sets =
+ IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len());
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
- if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != bottom_value {
+ if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != analysis.bottom_value(body)
+ {
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
}
- Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_trans_for_block }
+ Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_statement_trans_for_block }
}
/// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
@@ -231,7 +237,7 @@ where
body,
mut entry_sets,
tcx,
- apply_trans_for_block,
+ apply_statement_trans_for_block,
pass_name,
..
} = self;
@@ -263,19 +269,20 @@ where
state.clone_from(&entry_sets[bb]);
// Apply the block transfer function, using the cached one if it exists.
- match &apply_trans_for_block {
- Some(apply) => apply(bb, &mut state),
- None => {
- A::Direction::apply_effects_in_block(&mut analysis, &mut state, bb, bb_data)
- }
- }
+ let edges = A::Direction::apply_effects_in_block(
+ &mut analysis,
+ &mut state,
+ bb,
+ bb_data,
+ apply_statement_trans_for_block.as_deref(),
+ );
A::Direction::join_state_into_successors_of(
&mut analysis,
- tcx,
body,
&mut state,
- (bb, bb_data),
+ bb,
+ edges,
|target: BasicBlock, state: &A::Domain| {
let set_changed = entry_sets[target].join(state);
if set_changed {
diff --git a/compiler/rustc_mir_dataflow/src/framework/fmt.rs b/compiler/rustc_mir_dataflow/src/framework/fmt.rs
index 6a256fae3..e3a66bd95 100644
--- a/compiler/rustc_mir_dataflow/src/framework/fmt.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/fmt.rs
@@ -1,6 +1,7 @@
//! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow
//! analysis.
+use super::lattice::MaybeReachable;
use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
use rustc_index::Idx;
use std::fmt;
@@ -124,6 +125,37 @@ where
}
}
+impl<S, C> DebugWithContext<C> for MaybeReachable<S>
+where
+ S: DebugWithContext<C>,
+{
+ fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ MaybeReachable::Unreachable => {
+ write!(f, "unreachable")
+ }
+ MaybeReachable::Reachable(set) => set.fmt_with(ctxt, f),
+ }
+ }
+
+ fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match (self, old) {
+ (MaybeReachable::Unreachable, MaybeReachable::Unreachable) => Ok(()),
+ (MaybeReachable::Unreachable, MaybeReachable::Reachable(set)) => {
+ write!(f, "\u{001f}+")?;
+ set.fmt_with(ctxt, f)
+ }
+ (MaybeReachable::Reachable(set), MaybeReachable::Unreachable) => {
+ write!(f, "\u{001f}-")?;
+ set.fmt_with(ctxt, f)
+ }
+ (MaybeReachable::Reachable(this), MaybeReachable::Reachable(old)) => {
+ this.fmt_diff_with(old, ctxt, f)
+ }
+ }
+ }
+}
+
fn fmt_diff<T, C>(
inserted: &HybridBitSet<T>,
removed: &HybridBitSet<T>,
diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
index e331533c3..1421d9b45 100644
--- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
@@ -269,7 +269,11 @@ where
self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
let state_on_generator_drop = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| {
- analysis.apply_yield_resume_effect(state, resume, resume_arg);
+ analysis.apply_call_return_effect(
+ state,
+ resume,
+ CallReturnPlaces::Yield(resume_arg),
+ );
});
write!(
diff --git a/compiler/rustc_mir_dataflow/src/framework/lattice.rs b/compiler/rustc_mir_dataflow/src/framework/lattice.rs
index 3952f44ad..3b89598d2 100644
--- a/compiler/rustc_mir_dataflow/src/framework/lattice.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/lattice.rs
@@ -187,10 +187,6 @@ impl<T: Idx> MeetSemiLattice for ChunkedBitSet<T> {
pub struct Dual<T>(pub T);
impl<T: Idx> BitSetExt<T> for Dual<BitSet<T>> {
- fn domain_size(&self) -> usize {
- self.0.domain_size()
- }
-
fn contains(&self, elem: T) -> bool {
self.0.contains(elem)
}
@@ -276,3 +272,93 @@ impl<T> HasBottom for FlatSet<T> {
impl<T> HasTop for FlatSet<T> {
const TOP: Self = Self::Top;
}
+
+/// Extend a lattice with a bottom value to represent an unreachable execution.
+///
+/// The only useful action on an unreachable state is joining it with a reachable one to make it
+/// reachable. All other actions, gen/kill for instance, are no-ops.
+#[derive(PartialEq, Eq, Debug)]
+pub enum MaybeReachable<T> {
+ Unreachable,
+ Reachable(T),
+}
+
+impl<T> MaybeReachable<T> {
+ pub fn is_reachable(&self) -> bool {
+ matches!(self, MaybeReachable::Reachable(_))
+ }
+}
+
+impl<T> HasBottom for MaybeReachable<T> {
+ const BOTTOM: Self = MaybeReachable::Unreachable;
+}
+
+impl<T: HasTop> HasTop for MaybeReachable<T> {
+ const TOP: Self = MaybeReachable::Reachable(T::TOP);
+}
+
+impl<S> MaybeReachable<S> {
+ /// Return whether the current state contains the given element. If the state is unreachable,
+ /// it does no contain anything.
+ pub fn contains<T>(&self, elem: T) -> bool
+ where
+ S: BitSetExt<T>,
+ {
+ match self {
+ MaybeReachable::Unreachable => false,
+ MaybeReachable::Reachable(set) => set.contains(elem),
+ }
+ }
+}
+
+impl<T, S: BitSetExt<T>> BitSetExt<T> for MaybeReachable<S> {
+ fn contains(&self, elem: T) -> bool {
+ self.contains(elem)
+ }
+
+ fn union(&mut self, other: &HybridBitSet<T>) {
+ match self {
+ MaybeReachable::Unreachable => {}
+ MaybeReachable::Reachable(set) => set.union(other),
+ }
+ }
+
+ fn subtract(&mut self, other: &HybridBitSet<T>) {
+ match self {
+ MaybeReachable::Unreachable => {}
+ MaybeReachable::Reachable(set) => set.subtract(other),
+ }
+ }
+}
+
+impl<V: Clone> Clone for MaybeReachable<V> {
+ fn clone(&self) -> Self {
+ match self {
+ MaybeReachable::Reachable(x) => MaybeReachable::Reachable(x.clone()),
+ MaybeReachable::Unreachable => MaybeReachable::Unreachable,
+ }
+ }
+
+ fn clone_from(&mut self, source: &Self) {
+ match (&mut *self, source) {
+ (MaybeReachable::Reachable(x), MaybeReachable::Reachable(y)) => {
+ x.clone_from(&y);
+ }
+ _ => *self = source.clone(),
+ }
+ }
+}
+
+impl<T: JoinSemiLattice + Clone> JoinSemiLattice for MaybeReachable<T> {
+ fn join(&mut self, other: &Self) -> bool {
+ // Unreachable acts as a bottom.
+ match (&mut *self, &other) {
+ (_, MaybeReachable::Unreachable) => false,
+ (MaybeReachable::Unreachable, _) => {
+ *self = other.clone();
+ true
+ }
+ (MaybeReachable::Reachable(this), MaybeReachable::Reachable(other)) => this.join(other),
+ }
+ }
+}
diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs
index 58df9b9a7..ce30c642f 100644
--- a/compiler/rustc_mir_dataflow/src/framework/mod.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs
@@ -34,7 +34,7 @@ use std::cmp::Ordering;
use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
use rustc_index::Idx;
-use rustc_middle::mir::{self, BasicBlock, Location};
+use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges};
use rustc_middle::ty::TyCtxt;
mod cursor;
@@ -48,23 +48,18 @@ mod visitor;
pub use self::cursor::{AnalysisResults, ResultsClonedCursor, ResultsCursor, ResultsRefCursor};
pub use self::direction::{Backward, Direction, Forward};
pub use self::engine::{Engine, EntrySets, Results, ResultsCloned};
-pub use self::lattice::{JoinSemiLattice, MeetSemiLattice};
+pub use self::lattice::{JoinSemiLattice, MaybeReachable, MeetSemiLattice};
pub use self::visitor::{visit_results, ResultsVisitable, ResultsVisitor};
/// Analysis domains are all bitsets of various kinds. This trait holds
/// operations needed by all of them.
pub trait BitSetExt<T> {
- fn domain_size(&self) -> usize;
fn contains(&self, elem: T) -> bool;
fn union(&mut self, other: &HybridBitSet<T>);
fn subtract(&mut self, other: &HybridBitSet<T>);
}
impl<T: Idx> BitSetExt<T> for BitSet<T> {
- fn domain_size(&self) -> usize {
- self.domain_size()
- }
-
fn contains(&self, elem: T) -> bool {
self.contains(elem)
}
@@ -79,10 +74,6 @@ impl<T: Idx> BitSetExt<T> for BitSet<T> {
}
impl<T: Idx> BitSetExt<T> for ChunkedBitSet<T> {
- fn domain_size(&self) -> usize {
- self.domain_size()
- }
-
fn contains(&self, elem: T) -> bool {
self.contains(elem)
}
@@ -172,12 +163,12 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
/// in this function. That should go in `apply_call_return_effect`. For example, in the
/// `InitializedPlaces` analyses, the return place for a function call is not marked as
/// initialized here.
- fn apply_terminator_effect(
+ fn apply_terminator_effect<'mir>(
&mut self,
state: &mut Self::Domain,
- terminator: &mir::Terminator<'tcx>,
+ terminator: &'mir mir::Terminator<'tcx>,
location: Location,
- );
+ ) -> TerminatorEdges<'mir, 'tcx>;
/// Updates the current dataflow state with an effect that occurs immediately *before* the
/// given terminator.
@@ -207,20 +198,6 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
return_places: CallReturnPlaces<'_, 'tcx>,
);
- /// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
- ///
- /// This is similar to `apply_call_return_effect` in that it only takes place after the
- /// generator is resumed, not when it is dropped.
- ///
- /// By default, no effects happen.
- fn apply_yield_resume_effect(
- &mut self,
- _state: &mut Self::Domain,
- _resume_block: BasicBlock,
- _resume_place: mir::Place<'tcx>,
- ) {
- }
-
/// Updates the current dataflow state with the effect of taking a particular branch in a
/// `SwitchInt` terminator.
///
@@ -295,6 +272,8 @@ where
pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
type Idx: Idx;
+ fn domain_size(&self, body: &mir::Body<'tcx>) -> usize;
+
/// See `Analysis::apply_statement_effect`.
fn statement_effect(
&mut self,
@@ -313,12 +292,12 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
}
/// See `Analysis::apply_terminator_effect`.
- fn terminator_effect(
+ fn terminator_effect<'mir>(
&mut self,
- trans: &mut impl GenKill<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
+ trans: &mut Self::Domain,
+ terminator: &'mir mir::Terminator<'tcx>,
location: Location,
- );
+ ) -> TerminatorEdges<'mir, 'tcx>;
/// See `Analysis::apply_before_terminator_effect`.
fn before_terminator_effect(
@@ -339,15 +318,6 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
return_places: CallReturnPlaces<'_, 'tcx>,
);
- /// See `Analysis::apply_yield_resume_effect`.
- fn yield_resume_effect(
- &mut self,
- _trans: &mut impl GenKill<Self::Idx>,
- _resume_block: BasicBlock,
- _resume_place: mir::Place<'tcx>,
- ) {
- }
-
/// See `Analysis::apply_switch_int_edge_effects`.
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
&mut self,
@@ -381,13 +351,13 @@ where
self.before_statement_effect(state, statement, location);
}
- fn apply_terminator_effect(
+ fn apply_terminator_effect<'mir>(
&mut self,
state: &mut A::Domain,
- terminator: &mir::Terminator<'tcx>,
+ terminator: &'mir mir::Terminator<'tcx>,
location: Location,
- ) {
- self.terminator_effect(state, terminator, location);
+ ) -> TerminatorEdges<'mir, 'tcx> {
+ self.terminator_effect(state, terminator, location)
}
fn apply_before_terminator_effect(
@@ -410,15 +380,6 @@ where
self.call_return_effect(state, block, return_places);
}
- fn apply_yield_resume_effect(
- &mut self,
- state: &mut A::Domain,
- resume_block: BasicBlock,
- resume_place: mir::Place<'tcx>,
- ) {
- self.yield_resume_effect(state, resume_block, resume_place);
- }
-
fn apply_switch_int_edge_effects(
&mut self,
block: BasicBlock,
@@ -531,6 +492,24 @@ impl<T: Idx> GenKill<T> for ChunkedBitSet<T> {
}
}
+impl<T, S: GenKill<T>> GenKill<T> for MaybeReachable<S> {
+ fn gen(&mut self, elem: T) {
+ match self {
+ // If the state is not reachable, adding an element does nothing.
+ MaybeReachable::Unreachable => {}
+ MaybeReachable::Reachable(set) => set.gen(elem),
+ }
+ }
+
+ fn kill(&mut self, elem: T) {
+ match self {
+ // If the state is not reachable, killing an element does nothing.
+ MaybeReachable::Unreachable => {}
+ MaybeReachable::Reachable(set) => set.kill(elem),
+ }
+ }
+}
+
impl<T: Idx> GenKill<T> for lattice::Dual<BitSet<T>> {
fn gen(&mut self, elem: T) {
self.0.insert(elem);
@@ -612,29 +591,5 @@ pub trait SwitchIntEdgeEffects<D> {
fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget));
}
-/// List of places that are written to after a successful (non-unwind) return
-/// from a `Call` or `InlineAsm`.
-pub enum CallReturnPlaces<'a, 'tcx> {
- Call(mir::Place<'tcx>),
- InlineAsm(&'a [mir::InlineAsmOperand<'tcx>]),
-}
-
-impl<'tcx> CallReturnPlaces<'_, 'tcx> {
- pub fn for_each(&self, mut f: impl FnMut(mir::Place<'tcx>)) {
- match *self {
- Self::Call(place) => f(place),
- Self::InlineAsm(operands) => {
- for op in operands {
- match *op {
- mir::InlineAsmOperand::Out { place: Some(place), .. }
- | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place),
- _ => {}
- }
- }
- }
- }
- }
-}
-
#[cfg(test)]
mod tests;
diff --git a/compiler/rustc_mir_dataflow/src/framework/tests.rs b/compiler/rustc_mir_dataflow/src/framework/tests.rs
index cb0ec144e..9cce5b26c 100644
--- a/compiler/rustc_mir_dataflow/src/framework/tests.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/tests.rs
@@ -198,14 +198,15 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
assert!(state.insert(idx));
}
- fn apply_terminator_effect(
+ fn apply_terminator_effect<'mir>(
&mut self,
state: &mut Self::Domain,
- _terminator: &mir::Terminator<'tcx>,
+ terminator: &'mir mir::Terminator<'tcx>,
location: Location,
- ) {
+ ) -> TerminatorEdges<'mir, 'tcx> {
let idx = self.effect(Effect::Primary.at_index(location.statement_index));
assert!(state.insert(idx));
+ terminator.edges()
}
fn apply_before_terminator_effect(
diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
index b88ed32b6..8d7b50796 100644
--- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
@@ -1,9 +1,9 @@
-use super::*;
-
-use crate::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis};
+use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
+use crate::{AnalysisDomain, GenKill, GenKillAnalysis};
+
/// A dataflow analysis that tracks whether a pointer or reference could possibly exist that points
/// to a given local.
///
@@ -14,7 +14,7 @@ use rustc_middle::mir::*;
pub struct MaybeBorrowedLocals;
impl MaybeBorrowedLocals {
- fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> {
+ pub(super) fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> {
TransferFunction { trans }
}
}
@@ -23,12 +23,12 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals {
type Domain = BitSet<Local>;
const NAME: &'static str = "maybe_borrowed_locals";
- fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
// bottom = unborrowed
BitSet::new_empty(body.local_decls().len())
}
- fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
+ fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
// No locals are aliased on function entry
}
}
@@ -36,35 +36,40 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals {
impl<'tcx> GenKillAnalysis<'tcx> for MaybeBorrowedLocals {
type Idx = Local;
+ fn domain_size(&self, body: &Body<'tcx>) -> usize {
+ body.local_decls.len()
+ }
+
fn statement_effect(
&mut self,
trans: &mut impl GenKill<Self::Idx>,
- statement: &mir::Statement<'tcx>,
+ statement: &Statement<'tcx>,
location: Location,
) {
self.transfer_function(trans).visit_statement(statement, location);
}
- fn terminator_effect(
+ fn terminator_effect<'mir>(
&mut self,
- trans: &mut impl GenKill<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
+ trans: &mut Self::Domain,
+ terminator: &'mir Terminator<'tcx>,
location: Location,
- ) {
+ ) -> TerminatorEdges<'mir, 'tcx> {
self.transfer_function(trans).visit_terminator(terminator, location);
+ terminator.edges()
}
fn call_return_effect(
&mut self,
_trans: &mut impl GenKill<Self::Idx>,
- _block: mir::BasicBlock,
+ _block: BasicBlock,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
}
}
/// A `Visitor` that defines the transfer function for `MaybeBorrowedLocals`.
-struct TransferFunction<'a, T> {
+pub(super) struct TransferFunction<'a, T> {
trans: &'a mut T,
}
@@ -82,37 +87,37 @@ where
}
}
- fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
+ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
self.super_rvalue(rvalue, location);
match rvalue {
- mir::Rvalue::AddressOf(_, borrowed_place) | mir::Rvalue::Ref(_, _, borrowed_place) => {
+ Rvalue::AddressOf(_, borrowed_place) | Rvalue::Ref(_, _, borrowed_place) => {
if !borrowed_place.is_indirect() {
self.trans.gen(borrowed_place.local);
}
}
- mir::Rvalue::Cast(..)
- | mir::Rvalue::ShallowInitBox(..)
- | mir::Rvalue::Use(..)
- | mir::Rvalue::ThreadLocalRef(..)
- | mir::Rvalue::Repeat(..)
- | mir::Rvalue::Len(..)
- | mir::Rvalue::BinaryOp(..)
- | mir::Rvalue::CheckedBinaryOp(..)
- | mir::Rvalue::NullaryOp(..)
- | mir::Rvalue::UnaryOp(..)
- | mir::Rvalue::Discriminant(..)
- | mir::Rvalue::Aggregate(..)
- | mir::Rvalue::CopyForDeref(..) => {}
+ Rvalue::Cast(..)
+ | Rvalue::ShallowInitBox(..)
+ | Rvalue::Use(..)
+ | Rvalue::ThreadLocalRef(..)
+ | Rvalue::Repeat(..)
+ | Rvalue::Len(..)
+ | Rvalue::BinaryOp(..)
+ | Rvalue::CheckedBinaryOp(..)
+ | Rvalue::NullaryOp(..)
+ | Rvalue::UnaryOp(..)
+ | Rvalue::Discriminant(..)
+ | Rvalue::Aggregate(..)
+ | Rvalue::CopyForDeref(..) => {}
}
}
- fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
+ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
self.super_terminator(terminator, location);
match terminator.kind {
- mir::TerminatorKind::Drop { place: dropped_place, .. } => {
+ TerminatorKind::Drop { place: dropped_place, .. } => {
// Drop terminators may call custom drop glue (`Drop::drop`), which takes `&mut
// self` as a parameter. In the general case, a drop impl could launder that
// reference into the surrounding environment through a raw pointer, thus creating
diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs
new file mode 100644
index 000000000..e6d383d62
--- /dev/null
+++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs
@@ -0,0 +1,778 @@
+use rustc_index::bit_set::{BitSet, ChunkedBitSet};
+use rustc_index::Idx;
+use rustc_middle::mir::{self, Body, CallReturnPlaces, Location, TerminatorEdges};
+use rustc_middle::ty::{self, TyCtxt};
+
+use crate::drop_flag_effects_for_function_entry;
+use crate::drop_flag_effects_for_location;
+use crate::elaborate_drops::DropFlagState;
+use crate::framework::SwitchIntEdgeEffects;
+use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
+use crate::on_lookup_result_bits;
+use crate::MoveDataParamEnv;
+use crate::{drop_flag_effects, on_all_children_bits, on_all_drop_children_bits};
+use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis, MaybeReachable};
+
+/// `MaybeInitializedPlaces` tracks all places that might be
+/// initialized upon reaching a particular point in the control flow
+/// for a function.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) { // maybe-init:
+/// // {}
+/// let a = S; let mut b = S; let c; let d; // {a, b}
+///
+/// if pred {
+/// drop(a); // { b}
+/// b = S; // { b}
+///
+/// } else {
+/// drop(b); // {a}
+/// d = S; // {a, d}
+///
+/// } // {a, b, d}
+///
+/// c = S; // {a, b, c, d}
+/// }
+/// ```
+///
+/// To determine whether a place *must* be initialized at a
+/// particular control-flow point, one can take the set-difference
+/// between this data and the data from `MaybeUninitializedPlaces` at the
+/// corresponding control-flow point.
+///
+/// Similarly, at a given `drop` statement, the set-intersection
+/// between this data and `MaybeUninitializedPlaces` yields the set of
+/// places that would require a dynamic drop-flag at that statement.
+pub struct MaybeInitializedPlaces<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+ mdpe: &'a MoveDataParamEnv<'tcx>,
+ skip_unreachable_unwind: bool,
+}
+
+impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
+ pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+ MaybeInitializedPlaces { tcx, body, mdpe, skip_unreachable_unwind: false }
+ }
+
+ pub fn skipping_unreachable_unwind(mut self) -> Self {
+ self.skip_unreachable_unwind = true;
+ self
+ }
+
+ pub fn is_unwind_dead(
+ &self,
+ place: mir::Place<'tcx>,
+ state: &MaybeReachable<ChunkedBitSet<MovePathIndex>>,
+ ) -> bool {
+ if let LookupResult::Exact(path) = self.move_data().rev_lookup.find(place.as_ref()) {
+ let mut maybe_live = false;
+ on_all_drop_children_bits(self.tcx, self.body, self.mdpe, path, |child| {
+ maybe_live |= state.contains(child);
+ });
+ !maybe_live
+ } else {
+ false
+ }
+ }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
+ fn move_data(&self) -> &MoveData<'tcx> {
+ &self.mdpe.move_data
+ }
+}
+
+/// `MaybeUninitializedPlaces` tracks all places that might be
+/// uninitialized upon reaching a particular point in the control flow
+/// for a function.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) { // maybe-uninit:
+/// // {a, b, c, d}
+/// let a = S; let mut b = S; let c; let d; // { c, d}
+///
+/// if pred {
+/// drop(a); // {a, c, d}
+/// b = S; // {a, c, d}
+///
+/// } else {
+/// drop(b); // { b, c, d}
+/// d = S; // { b, c }
+///
+/// } // {a, b, c, d}
+///
+/// c = S; // {a, b, d}
+/// }
+/// ```
+///
+/// To determine whether a place *must* be uninitialized at a
+/// particular control-flow point, one can take the set-difference
+/// between this data and the data from `MaybeInitializedPlaces` at the
+/// corresponding control-flow point.
+///
+/// Similarly, at a given `drop` statement, the set-intersection
+/// between this data and `MaybeInitializedPlaces` yields the set of
+/// places that would require a dynamic drop-flag at that statement.
+pub struct MaybeUninitializedPlaces<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+ mdpe: &'a MoveDataParamEnv<'tcx>,
+
+ mark_inactive_variants_as_uninit: bool,
+ skip_unreachable_unwind: BitSet<mir::BasicBlock>,
+}
+
+impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
+ pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+ MaybeUninitializedPlaces {
+ tcx,
+ body,
+ mdpe,
+ mark_inactive_variants_as_uninit: false,
+ skip_unreachable_unwind: BitSet::new_empty(body.basic_blocks.len()),
+ }
+ }
+
+ /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an
+ /// enum discriminant.
+ ///
+ /// This is correct in a vacuum but is not the default because it causes problems in the borrow
+ /// checker, where this information gets propagated along `FakeEdge`s.
+ pub fn mark_inactive_variants_as_uninit(mut self) -> Self {
+ self.mark_inactive_variants_as_uninit = true;
+ self
+ }
+
+ pub fn skipping_unreachable_unwind(
+ mut self,
+ unreachable_unwind: BitSet<mir::BasicBlock>,
+ ) -> Self {
+ self.skip_unreachable_unwind = unreachable_unwind;
+ self
+ }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> {
+ fn move_data(&self) -> &MoveData<'tcx> {
+ &self.mdpe.move_data
+ }
+}
+
+/// `DefinitelyInitializedPlaces` tracks all places that are definitely
+/// initialized upon reaching a particular point in the control flow
+/// for a function.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) { // definite-init:
+/// // { }
+/// let a = S; let mut b = S; let c; let d; // {a, b }
+///
+/// if pred {
+/// drop(a); // { b, }
+/// b = S; // { b, }
+///
+/// } else {
+/// drop(b); // {a, }
+/// d = S; // {a, d}
+///
+/// } // { }
+///
+/// c = S; // { c }
+/// }
+/// ```
+///
+/// To determine whether a place *may* be uninitialized at a
+/// particular control-flow point, one can take the set-complement
+/// of this data.
+///
+/// Similarly, at a given `drop` statement, the set-difference between
+/// this data and `MaybeInitializedPlaces` yields the set of places
+/// that would require a dynamic drop-flag at that statement.
+pub struct DefinitelyInitializedPlaces<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+ mdpe: &'a MoveDataParamEnv<'tcx>,
+}
+
+impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
+ pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+ DefinitelyInitializedPlaces { tcx, body, mdpe }
+ }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
+ fn move_data(&self) -> &MoveData<'tcx> {
+ &self.mdpe.move_data
+ }
+}
+
+/// `EverInitializedPlaces` tracks all places that might have ever been
+/// initialized upon reaching a particular point in the control flow
+/// for a function, without an intervening `StorageDead`.
+///
+/// This dataflow is used to determine if an immutable local variable may
+/// be assigned to.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) { // ever-init:
+/// // { }
+/// let a = S; let mut b = S; let c; let d; // {a, b }
+///
+/// if pred {
+/// drop(a); // {a, b, }
+/// b = S; // {a, b, }
+///
+/// } else {
+/// drop(b); // {a, b, }
+/// d = S; // {a, b, d }
+///
+/// } // {a, b, d }
+///
+/// c = S; // {a, b, c, d }
+/// }
+/// ```
+pub struct EverInitializedPlaces<'a, 'tcx> {
+ #[allow(dead_code)]
+ tcx: TyCtxt<'tcx>,
+ body: &'a Body<'tcx>,
+ mdpe: &'a MoveDataParamEnv<'tcx>,
+}
+
+impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> {
+ pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+ EverInitializedPlaces { tcx, body, mdpe }
+ }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> {
+ fn move_data(&self) -> &MoveData<'tcx> {
+ &self.mdpe.move_data
+ }
+}
+
+impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
+ fn update_bits(
+ trans: &mut impl GenKill<MovePathIndex>,
+ path: MovePathIndex,
+ state: DropFlagState,
+ ) {
+ match state {
+ DropFlagState::Absent => trans.kill(path),
+ DropFlagState::Present => trans.gen(path),
+ }
+ }
+}
+
+impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
+ fn update_bits(
+ trans: &mut impl GenKill<MovePathIndex>,
+ path: MovePathIndex,
+ state: DropFlagState,
+ ) {
+ match state {
+ DropFlagState::Absent => trans.gen(path),
+ DropFlagState::Present => trans.kill(path),
+ }
+ }
+}
+
+impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
+ fn update_bits(
+ trans: &mut impl GenKill<MovePathIndex>,
+ path: MovePathIndex,
+ state: DropFlagState,
+ ) {
+ match state {
+ DropFlagState::Absent => trans.kill(path),
+ DropFlagState::Present => trans.gen(path),
+ }
+ }
+}
+
+impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
+ type Domain = MaybeReachable<ChunkedBitSet<MovePathIndex>>;
+ const NAME: &'static str = "maybe_init";
+
+ fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+ // bottom = uninitialized
+ MaybeReachable::Unreachable
+ }
+
+ fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
+ *state =
+ MaybeReachable::Reachable(ChunkedBitSet::new_empty(self.move_data().move_paths.len()));
+ drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
+ assert!(s == DropFlagState::Present);
+ state.gen(path);
+ });
+ }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
+ type Idx = MovePathIndex;
+
+ fn domain_size(&self, _: &Body<'tcx>) -> usize {
+ self.move_data().move_paths.len()
+ }
+
+ fn statement_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+ Self::update_bits(trans, path, s)
+ });
+
+ // Mark all places as "maybe init" if they are mutably borrowed. See #90752.
+ if self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration
+ && let Some((_, rvalue)) = statement.kind.as_assign()
+ && let mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place)
+ // FIXME: Does `&raw const foo` allow mutation? See #90413.
+ | mir::Rvalue::AddressOf(_, place) = rvalue
+ && let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref())
+ {
+ on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
+ trans.gen(child);
+ })
+ }
+ }
+
+ fn terminator_effect<'mir>(
+ &mut self,
+ state: &mut Self::Domain,
+ terminator: &'mir mir::Terminator<'tcx>,
+ location: Location,
+ ) -> TerminatorEdges<'mir, 'tcx> {
+ let mut edges = terminator.edges();
+ if self.skip_unreachable_unwind
+ && let mir::TerminatorKind::Drop { target, unwind, place, replace: _ } = terminator.kind
+ && matches!(unwind, mir::UnwindAction::Cleanup(_))
+ && self.is_unwind_dead(place, state)
+ {
+ edges = TerminatorEdges::Single(target);
+ }
+ drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+ Self::update_bits(state, path, s)
+ });
+ edges
+ }
+
+ fn call_return_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ _block: mir::BasicBlock,
+ return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ return_places.for_each(|place| {
+ // when a call returns successfully, that means we need to set
+ // the bits for that dest_place to 1 (initialized).
+ on_lookup_result_bits(
+ self.tcx,
+ self.body,
+ self.move_data(),
+ self.move_data().rev_lookup.find(place.as_ref()),
+ |mpi| {
+ trans.gen(mpi);
+ },
+ );
+ });
+ }
+
+ fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
+ &mut self,
+ block: mir::BasicBlock,
+ discr: &mir::Operand<'tcx>,
+ edge_effects: &mut impl SwitchIntEdgeEffects<G>,
+ ) {
+ if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
+ return;
+ }
+
+ let enum_ = discr.place().and_then(|discr| {
+ switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
+ });
+
+ let Some((enum_place, enum_def)) = enum_ else {
+ return;
+ };
+
+ let mut discriminants = enum_def.discriminants(self.tcx);
+ edge_effects.apply(|trans, edge| {
+ let Some(value) = edge.value else {
+ return;
+ };
+
+ // MIR building adds discriminants to the `values` array in the same order as they
+ // are yielded by `AdtDef::discriminants`. We rely on this to match each
+ // discriminant in `values` to its corresponding variant in linear time.
+ let (variant, _) = discriminants
+ .find(|&(_, discr)| discr.val == value)
+ .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
+
+ // Kill all move paths that correspond to variants we know to be inactive along this
+ // particular outgoing edge of a `SwitchInt`.
+ drop_flag_effects::on_all_inactive_variants(
+ self.tcx,
+ self.body,
+ self.move_data(),
+ enum_place,
+ variant,
+ |mpi| trans.kill(mpi),
+ );
+ });
+ }
+}
+
+impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
+ type Domain = ChunkedBitSet<MovePathIndex>;
+
+ const NAME: &'static str = "maybe_uninit";
+
+ fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+ // bottom = initialized (start_block_effect counters this at outset)
+ ChunkedBitSet::new_empty(self.move_data().move_paths.len())
+ }
+
+ // sets on_entry bits for Arg places
+ fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
+ // set all bits to 1 (uninit) before gathering counter-evidence
+ state.insert_all();
+
+ drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
+ assert!(s == DropFlagState::Present);
+ state.remove(path);
+ });
+ }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
+ type Idx = MovePathIndex;
+
+ fn domain_size(&self, _: &Body<'tcx>) -> usize {
+ self.move_data().move_paths.len()
+ }
+
+ fn statement_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ _statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+ Self::update_bits(trans, path, s)
+ });
+
+ // Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a
+ // mutable borrow occurs. Places cannot become uninitialized through a mutable reference.
+ }
+
+ fn terminator_effect<'mir>(
+ &mut self,
+ trans: &mut Self::Domain,
+ terminator: &'mir mir::Terminator<'tcx>,
+ location: Location,
+ ) -> TerminatorEdges<'mir, 'tcx> {
+ drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+ Self::update_bits(trans, path, s)
+ });
+ if self.skip_unreachable_unwind.contains(location.block) {
+ let mir::TerminatorKind::Drop { target, unwind, .. } = terminator.kind else { bug!() };
+ assert!(matches!(unwind, mir::UnwindAction::Cleanup(_)));
+ TerminatorEdges::Single(target)
+ } else {
+ terminator.edges()
+ }
+ }
+
+ fn call_return_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ _block: mir::BasicBlock,
+ return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ return_places.for_each(|place| {
+ // when a call returns successfully, that means we need to set
+ // the bits for that dest_place to 0 (initialized).
+ on_lookup_result_bits(
+ self.tcx,
+ self.body,
+ self.move_data(),
+ self.move_data().rev_lookup.find(place.as_ref()),
+ |mpi| {
+ trans.kill(mpi);
+ },
+ );
+ });
+ }
+
+ fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
+ &mut self,
+ block: mir::BasicBlock,
+ discr: &mir::Operand<'tcx>,
+ edge_effects: &mut impl SwitchIntEdgeEffects<G>,
+ ) {
+ if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
+ return;
+ }
+
+ if !self.mark_inactive_variants_as_uninit {
+ return;
+ }
+
+ let enum_ = discr.place().and_then(|discr| {
+ switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
+ });
+
+ let Some((enum_place, enum_def)) = enum_ else {
+ return;
+ };
+
+ let mut discriminants = enum_def.discriminants(self.tcx);
+ edge_effects.apply(|trans, edge| {
+ let Some(value) = edge.value else {
+ return;
+ };
+
+ // MIR building adds discriminants to the `values` array in the same order as they
+ // are yielded by `AdtDef::discriminants`. We rely on this to match each
+ // discriminant in `values` to its corresponding variant in linear time.
+ let (variant, _) = discriminants
+ .find(|&(_, discr)| discr.val == value)
+ .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
+
+ // Mark all move paths that correspond to variants other than this one as maybe
+ // uninitialized (in reality, they are *definitely* uninitialized).
+ drop_flag_effects::on_all_inactive_variants(
+ self.tcx,
+ self.body,
+ self.move_data(),
+ enum_place,
+ variant,
+ |mpi| trans.gen(mpi),
+ );
+ });
+ }
+}
+
+impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
+ /// Use set intersection as the join operator.
+ type Domain = lattice::Dual<BitSet<MovePathIndex>>;
+
+ const NAME: &'static str = "definite_init";
+
+ fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+ // bottom = initialized (start_block_effect counters this at outset)
+ lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len()))
+ }
+
+ // sets on_entry bits for Arg places
+ fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
+ state.0.clear();
+
+ drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
+ assert!(s == DropFlagState::Present);
+ state.0.insert(path);
+ });
+ }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
+ type Idx = MovePathIndex;
+
+ fn domain_size(&self, _: &Body<'tcx>) -> usize {
+ self.move_data().move_paths.len()
+ }
+
+ fn statement_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ _statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+ Self::update_bits(trans, path, s)
+ })
+ }
+
+ fn terminator_effect<'mir>(
+ &mut self,
+ trans: &mut Self::Domain,
+ terminator: &'mir mir::Terminator<'tcx>,
+ location: Location,
+ ) -> TerminatorEdges<'mir, 'tcx> {
+ drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+ Self::update_bits(trans, path, s)
+ });
+ terminator.edges()
+ }
+
+ fn call_return_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ _block: mir::BasicBlock,
+ return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ return_places.for_each(|place| {
+ // when a call returns successfully, that means we need to set
+ // the bits for that dest_place to 1 (initialized).
+ on_lookup_result_bits(
+ self.tcx,
+ self.body,
+ self.move_data(),
+ self.move_data().rev_lookup.find(place.as_ref()),
+ |mpi| {
+ trans.gen(mpi);
+ },
+ );
+ });
+ }
+}
+
+impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
+ type Domain = ChunkedBitSet<InitIndex>;
+
+ const NAME: &'static str = "ever_init";
+
+ fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+ // bottom = no initialized variables by default
+ ChunkedBitSet::new_empty(self.move_data().inits.len())
+ }
+
+ fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
+ for arg_init in 0..body.arg_count {
+ state.insert(InitIndex::new(arg_init));
+ }
+ }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
+ type Idx = InitIndex;
+
+ fn domain_size(&self, _: &Body<'tcx>) -> usize {
+ self.move_data().inits.len()
+ }
+
+ #[instrument(skip(self, trans), level = "debug")]
+ fn statement_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ stmt: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ let move_data = self.move_data();
+ let init_path_map = &move_data.init_path_map;
+ let init_loc_map = &move_data.init_loc_map;
+ let rev_lookup = &move_data.rev_lookup;
+
+ debug!("initializes move_indexes {:?}", &init_loc_map[location]);
+ trans.gen_all(init_loc_map[location].iter().copied());
+
+ if let mir::StatementKind::StorageDead(local) = stmt.kind {
+ // End inits for StorageDead, so that an immutable variable can
+ // be reinitialized on the next iteration of the loop.
+ let move_path_index = rev_lookup.find_local(local);
+ debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]);
+ trans.kill_all(init_path_map[move_path_index].iter().copied());
+ }
+ }
+
+ #[instrument(skip(self, trans, terminator), level = "debug")]
+ fn terminator_effect<'mir>(
+ &mut self,
+ trans: &mut Self::Domain,
+ terminator: &'mir mir::Terminator<'tcx>,
+ location: Location,
+ ) -> TerminatorEdges<'mir, 'tcx> {
+ let (body, move_data) = (self.body, self.move_data());
+ let term = body[location.block].terminator();
+ let init_loc_map = &move_data.init_loc_map;
+ debug!(?term);
+ debug!("initializes move_indexes {:?}", init_loc_map[location]);
+ trans.gen_all(
+ init_loc_map[location]
+ .iter()
+ .filter(|init_index| {
+ move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
+ })
+ .copied(),
+ );
+ terminator.edges()
+ }
+
+ fn call_return_effect(
+ &mut self,
+ trans: &mut impl GenKill<Self::Idx>,
+ block: mir::BasicBlock,
+ _return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ let move_data = self.move_data();
+ let init_loc_map = &move_data.init_loc_map;
+
+ let call_loc = self.body.terminator_loc(block);
+ for init_index in &init_loc_map[call_loc] {
+ trans.gen(*init_index);
+ }
+ }
+}
+
+/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
+/// an enum discriminant.
+///
+/// We expect such blocks to have a call to `discriminant` as their last statement like so:
+///
+/// ```text
+/// ...
+/// _42 = discriminant(_1)
+/// SwitchInt(_42, ..)
+/// ```
+///
+/// If the basic block matches this pattern, this function returns the place corresponding to the
+/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
+fn switch_on_enum_discriminant<'mir, 'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &'mir mir::Body<'tcx>,
+ block: &'mir mir::BasicBlockData<'tcx>,
+ switch_on: mir::Place<'tcx>,
+) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> {
+ for statement in block.statements.iter().rev() {
+ match &statement.kind {
+ mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated)))
+ if *lhs == switch_on =>
+ {
+ match discriminated.ty(body, tcx).ty.kind() {
+ ty::Adt(def, _) => return Some((*discriminated, *def)),
+
+ // `Rvalue::Discriminant` is also used to get the active yield point for a
+ // generator, but we do not need edge-specific effects in that case. This may
+ // change in the future.
+ ty::Generator(..) => return None,
+
+ t => bug!("`discriminant` called on unexpected type {:?}", t),
+ }
+ }
+ mir::StatementKind::Coverage(_) => continue,
+ _ => return None,
+ }
+ }
+ None
+}
diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs
index 9662c1977..5aa73c7a9 100644
--- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs
@@ -1,8 +1,10 @@
use rustc_index::bit_set::{BitSet, ChunkedBitSet};
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
-use rustc_middle::mir::{self, Local, Location, Place, StatementKind};
+use rustc_middle::mir::{
+ self, CallReturnPlaces, Local, Location, Place, StatementKind, TerminatorEdges,
+};
-use crate::{Analysis, AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKillAnalysis};
+use crate::{Analysis, AnalysisDomain, Backward, GenKill, GenKillAnalysis};
/// A [live-variable dataflow analysis][liveness].
///
@@ -43,6 +45,10 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals {
impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
type Idx = Local;
+ fn domain_size(&self, body: &mir::Body<'tcx>) -> usize {
+ body.local_decls.len()
+ }
+
fn statement_effect(
&mut self,
trans: &mut impl GenKill<Self::Idx>,
@@ -52,13 +58,14 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
TransferFunction(trans).visit_statement(statement, location);
}
- fn terminator_effect(
+ fn terminator_effect<'mir>(
&mut self,
- trans: &mut impl GenKill<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
+ trans: &mut Self::Domain,
+ terminator: &'mir mir::Terminator<'tcx>,
location: Location,
- ) {
+ ) -> TerminatorEdges<'mir, 'tcx> {
TransferFunction(trans).visit_terminator(terminator, location);
+ terminator.edges()
}
fn call_return_effect(
@@ -67,28 +74,23 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
_block: mir::BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
- return_places.for_each(|place| {
- if let Some(local) = place.as_local() {
- trans.kill(local);
- }
- });
- }
-
- fn yield_resume_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _resume_block: mir::BasicBlock,
- resume_place: mir::Place<'tcx>,
- ) {
- YieldResumeEffect(trans).visit_place(
- &resume_place,
- PlaceContext::MutatingUse(MutatingUseContext::Yield),
- Location::START,
- )
+ if let CallReturnPlaces::Yield(resume_place) = return_places {
+ YieldResumeEffect(trans).visit_place(
+ &resume_place,
+ PlaceContext::MutatingUse(MutatingUseContext::Yield),
+ Location::START,
+ )
+ } else {
+ return_places.for_each(|place| {
+ if let Some(local) = place.as_local() {
+ trans.kill(local);
+ }
+ });
+ }
}
}
-struct TransferFunction<'a, T>(&'a mut T);
+pub struct TransferFunction<'a, T>(pub &'a mut T);
impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, T>
where
@@ -97,7 +99,7 @@ where
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 generator resumes, so its
- // effect is handled separately in `yield_resume_effect`.
+ // effect is handled separately in `call_resume_effect`.
return;
}
@@ -283,13 +285,14 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
TransferFunction(trans).visit_statement(statement, location);
}
- fn apply_terminator_effect(
+ fn apply_terminator_effect<'mir>(
&mut self,
trans: &mut Self::Domain,
- terminator: &mir::Terminator<'tcx>,
+ terminator: &'mir mir::Terminator<'tcx>,
location: Location,
- ) {
+ ) -> TerminatorEdges<'mir, 'tcx> {
TransferFunction(trans).visit_terminator(terminator, location);
+ terminator.edges()
}
fn apply_call_return_effect(
@@ -298,23 +301,18 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
_block: mir::BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
- return_places.for_each(|place| {
- if let Some(local) = place.as_local() {
- trans.remove(local);
- }
- });
- }
-
- fn apply_yield_resume_effect(
- &mut self,
- trans: &mut Self::Domain,
- _resume_block: mir::BasicBlock,
- resume_place: mir::Place<'tcx>,
- ) {
- YieldResumeEffect(trans).visit_place(
- &resume_place,
- PlaceContext::MutatingUse(MutatingUseContext::Yield),
- Location::START,
- )
+ if let CallReturnPlaces::Yield(resume_place) = return_places {
+ YieldResumeEffect(trans).visit_place(
+ &resume_place,
+ PlaceContext::MutatingUse(MutatingUseContext::Yield),
+ Location::START,
+ )
+ } else {
+ return_places.for_each(|place| {
+ if let Some(local) = place.as_local() {
+ trans.remove(local);
+ }
+ });
+ }
}
}
diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs
index 98cec1c67..f8db18fc1 100644
--- a/compiler/rustc_mir_dataflow/src/impls/mod.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs
@@ -2,763 +2,18 @@
//! bitvectors attached to each basic block, represented via a
//! zero-sized structure.
-use rustc_index::bit_set::{BitSet, ChunkedBitSet};
-use rustc_index::Idx;
-use rustc_middle::mir::visit::{MirVisitable, Visitor};
-use rustc_middle::mir::{self, Body, Location};
-use rustc_middle::ty::{self, TyCtxt};
-
-use crate::drop_flag_effects_for_function_entry;
-use crate::drop_flag_effects_for_location;
-use crate::elaborate_drops::DropFlagState;
-use crate::framework::{CallReturnPlaces, SwitchIntEdgeEffects};
-use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
-use crate::on_lookup_result_bits;
-use crate::MoveDataParamEnv;
-use crate::{drop_flag_effects, on_all_children_bits};
-use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis};
-
mod borrowed_locals;
+mod initialized;
mod liveness;
mod storage_liveness;
pub use self::borrowed_locals::borrowed_locals;
pub use self::borrowed_locals::MaybeBorrowedLocals;
+pub use self::initialized::{
+ DefinitelyInitializedPlaces, EverInitializedPlaces, MaybeInitializedPlaces,
+ MaybeUninitializedPlaces,
+};
pub use self::liveness::MaybeLiveLocals;
pub use self::liveness::MaybeTransitiveLiveLocals;
+pub use self::liveness::TransferFunction as LivenessTransferFunction;
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive};
-
-/// `MaybeInitializedPlaces` tracks all places that might be
-/// initialized upon reaching a particular point in the control flow
-/// for a function.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) { // maybe-init:
-/// // {}
-/// let a = S; let mut b = S; let c; let d; // {a, b}
-///
-/// if pred {
-/// drop(a); // { b}
-/// b = S; // { b}
-///
-/// } else {
-/// drop(b); // {a}
-/// d = S; // {a, d}
-///
-/// } // {a, b, d}
-///
-/// c = S; // {a, b, c, d}
-/// }
-/// ```
-///
-/// To determine whether a place *must* be initialized at a
-/// particular control-flow point, one can take the set-difference
-/// between this data and the data from `MaybeUninitializedPlaces` at the
-/// corresponding control-flow point.
-///
-/// Similarly, at a given `drop` statement, the set-intersection
-/// between this data and `MaybeUninitializedPlaces` yields the set of
-/// places that would require a dynamic drop-flag at that statement.
-pub struct MaybeInitializedPlaces<'a, 'tcx> {
- tcx: TyCtxt<'tcx>,
- body: &'a Body<'tcx>,
- mdpe: &'a MoveDataParamEnv<'tcx>,
-}
-
-impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
- pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
- MaybeInitializedPlaces { tcx, body, mdpe }
- }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
- fn move_data(&self) -> &MoveData<'tcx> {
- &self.mdpe.move_data
- }
-}
-
-/// `MaybeUninitializedPlaces` tracks all places that might be
-/// uninitialized upon reaching a particular point in the control flow
-/// for a function.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) { // maybe-uninit:
-/// // {a, b, c, d}
-/// let a = S; let mut b = S; let c; let d; // { c, d}
-///
-/// if pred {
-/// drop(a); // {a, c, d}
-/// b = S; // {a, c, d}
-///
-/// } else {
-/// drop(b); // { b, c, d}
-/// d = S; // { b, c }
-///
-/// } // {a, b, c, d}
-///
-/// c = S; // {a, b, d}
-/// }
-/// ```
-///
-/// To determine whether a place *must* be uninitialized at a
-/// particular control-flow point, one can take the set-difference
-/// between this data and the data from `MaybeInitializedPlaces` at the
-/// corresponding control-flow point.
-///
-/// Similarly, at a given `drop` statement, the set-intersection
-/// between this data and `MaybeInitializedPlaces` yields the set of
-/// places that would require a dynamic drop-flag at that statement.
-pub struct MaybeUninitializedPlaces<'a, 'tcx> {
- tcx: TyCtxt<'tcx>,
- body: &'a Body<'tcx>,
- mdpe: &'a MoveDataParamEnv<'tcx>,
-
- mark_inactive_variants_as_uninit: bool,
-}
-
-impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
- pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
- MaybeUninitializedPlaces { tcx, body, mdpe, mark_inactive_variants_as_uninit: false }
- }
-
- /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an
- /// enum discriminant.
- ///
- /// This is correct in a vacuum but is not the default because it causes problems in the borrow
- /// checker, where this information gets propagated along `FakeEdge`s.
- pub fn mark_inactive_variants_as_uninit(mut self) -> Self {
- self.mark_inactive_variants_as_uninit = true;
- self
- }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> {
- fn move_data(&self) -> &MoveData<'tcx> {
- &self.mdpe.move_data
- }
-}
-
-/// `DefinitelyInitializedPlaces` tracks all places that are definitely
-/// initialized upon reaching a particular point in the control flow
-/// for a function.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) { // definite-init:
-/// // { }
-/// let a = S; let mut b = S; let c; let d; // {a, b }
-///
-/// if pred {
-/// drop(a); // { b, }
-/// b = S; // { b, }
-///
-/// } else {
-/// drop(b); // {a, }
-/// d = S; // {a, d}
-///
-/// } // { }
-///
-/// c = S; // { c }
-/// }
-/// ```
-///
-/// To determine whether a place *may* be uninitialized at a
-/// particular control-flow point, one can take the set-complement
-/// of this data.
-///
-/// Similarly, at a given `drop` statement, the set-difference between
-/// this data and `MaybeInitializedPlaces` yields the set of places
-/// that would require a dynamic drop-flag at that statement.
-pub struct DefinitelyInitializedPlaces<'a, 'tcx> {
- tcx: TyCtxt<'tcx>,
- body: &'a Body<'tcx>,
- mdpe: &'a MoveDataParamEnv<'tcx>,
-}
-
-impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
- pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
- DefinitelyInitializedPlaces { tcx, body, mdpe }
- }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
- fn move_data(&self) -> &MoveData<'tcx> {
- &self.mdpe.move_data
- }
-}
-
-/// `EverInitializedPlaces` tracks all places that might have ever been
-/// initialized upon reaching a particular point in the control flow
-/// for a function, without an intervening `StorageDead`.
-///
-/// This dataflow is used to determine if an immutable local variable may
-/// be assigned to.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) { // ever-init:
-/// // { }
-/// let a = S; let mut b = S; let c; let d; // {a, b }
-///
-/// if pred {
-/// drop(a); // {a, b, }
-/// b = S; // {a, b, }
-///
-/// } else {
-/// drop(b); // {a, b, }
-/// d = S; // {a, b, d }
-///
-/// } // {a, b, d }
-///
-/// c = S; // {a, b, c, d }
-/// }
-/// ```
-pub struct EverInitializedPlaces<'a, 'tcx> {
- #[allow(dead_code)]
- tcx: TyCtxt<'tcx>,
- body: &'a Body<'tcx>,
- mdpe: &'a MoveDataParamEnv<'tcx>,
-}
-
-impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> {
- pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
- EverInitializedPlaces { tcx, body, mdpe }
- }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> {
- fn move_data(&self) -> &MoveData<'tcx> {
- &self.mdpe.move_data
- }
-}
-
-impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
- fn update_bits(
- trans: &mut impl GenKill<MovePathIndex>,
- path: MovePathIndex,
- state: DropFlagState,
- ) {
- match state {
- DropFlagState::Absent => trans.kill(path),
- DropFlagState::Present => trans.gen(path),
- }
- }
-}
-
-impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
- fn update_bits(
- trans: &mut impl GenKill<MovePathIndex>,
- path: MovePathIndex,
- state: DropFlagState,
- ) {
- match state {
- DropFlagState::Absent => trans.gen(path),
- DropFlagState::Present => trans.kill(path),
- }
- }
-}
-
-impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
- fn update_bits(
- trans: &mut impl GenKill<MovePathIndex>,
- path: MovePathIndex,
- state: DropFlagState,
- ) {
- match state {
- DropFlagState::Absent => trans.kill(path),
- DropFlagState::Present => trans.gen(path),
- }
- }
-}
-
-impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
- type Domain = ChunkedBitSet<MovePathIndex>;
- const NAME: &'static str = "maybe_init";
-
- fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
- // bottom = uninitialized
- ChunkedBitSet::new_empty(self.move_data().move_paths.len())
- }
-
- fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
- drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
- assert!(s == DropFlagState::Present);
- state.insert(path);
- });
- }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
- type Idx = MovePathIndex;
-
- fn statement_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- statement: &mir::Statement<'tcx>,
- location: Location,
- ) {
- drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
- Self::update_bits(trans, path, s)
- });
-
- if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
- return;
- }
-
- // Mark all places as "maybe init" if they are mutably borrowed. See #90752.
- for_each_mut_borrow(statement, location, |place| {
- let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else { return };
- on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
- trans.gen(child);
- })
- })
- }
-
- fn terminator_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
- Self::update_bits(trans, path, s)
- });
-
- if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
- return;
- }
-
- for_each_mut_borrow(terminator, location, |place| {
- let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else { return };
- on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
- trans.gen(child);
- })
- })
- }
-
- fn call_return_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _block: mir::BasicBlock,
- return_places: CallReturnPlaces<'_, 'tcx>,
- ) {
- return_places.for_each(|place| {
- // when a call returns successfully, that means we need to set
- // the bits for that dest_place to 1 (initialized).
- on_lookup_result_bits(
- self.tcx,
- self.body,
- self.move_data(),
- self.move_data().rev_lookup.find(place.as_ref()),
- |mpi| {
- trans.gen(mpi);
- },
- );
- });
- }
-
- fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
- &mut self,
- block: mir::BasicBlock,
- discr: &mir::Operand<'tcx>,
- edge_effects: &mut impl SwitchIntEdgeEffects<G>,
- ) {
- if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
- return;
- }
-
- let enum_ = discr.place().and_then(|discr| {
- switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
- });
-
- let Some((enum_place, enum_def)) = enum_ else {
- return;
- };
-
- let mut discriminants = enum_def.discriminants(self.tcx);
- edge_effects.apply(|trans, edge| {
- let Some(value) = edge.value else {
- return;
- };
-
- // MIR building adds discriminants to the `values` array in the same order as they
- // are yielded by `AdtDef::discriminants`. We rely on this to match each
- // discriminant in `values` to its corresponding variant in linear time.
- let (variant, _) = discriminants
- .find(|&(_, discr)| discr.val == value)
- .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
-
- // Kill all move paths that correspond to variants we know to be inactive along this
- // particular outgoing edge of a `SwitchInt`.
- drop_flag_effects::on_all_inactive_variants(
- self.tcx,
- self.body,
- self.move_data(),
- enum_place,
- variant,
- |mpi| trans.kill(mpi),
- );
- });
- }
-}
-
-impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
- type Domain = ChunkedBitSet<MovePathIndex>;
-
- const NAME: &'static str = "maybe_uninit";
-
- fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
- // bottom = initialized (start_block_effect counters this at outset)
- ChunkedBitSet::new_empty(self.move_data().move_paths.len())
- }
-
- // sets on_entry bits for Arg places
- fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
- // set all bits to 1 (uninit) before gathering counter-evidence
- state.insert_all();
-
- drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
- assert!(s == DropFlagState::Present);
- state.remove(path);
- });
- }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
- type Idx = MovePathIndex;
-
- fn statement_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _statement: &mir::Statement<'tcx>,
- location: Location,
- ) {
- drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
- Self::update_bits(trans, path, s)
- });
-
- // Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a
- // mutable borrow occurs. Places cannot become uninitialized through a mutable reference.
- }
-
- fn terminator_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
- Self::update_bits(trans, path, s)
- });
- }
-
- fn call_return_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _block: mir::BasicBlock,
- return_places: CallReturnPlaces<'_, 'tcx>,
- ) {
- return_places.for_each(|place| {
- // when a call returns successfully, that means we need to set
- // the bits for that dest_place to 0 (initialized).
- on_lookup_result_bits(
- self.tcx,
- self.body,
- self.move_data(),
- self.move_data().rev_lookup.find(place.as_ref()),
- |mpi| {
- trans.kill(mpi);
- },
- );
- });
- }
-
- fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
- &mut self,
- block: mir::BasicBlock,
- discr: &mir::Operand<'tcx>,
- edge_effects: &mut impl SwitchIntEdgeEffects<G>,
- ) {
- if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
- return;
- }
-
- if !self.mark_inactive_variants_as_uninit {
- return;
- }
-
- let enum_ = discr.place().and_then(|discr| {
- switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
- });
-
- let Some((enum_place, enum_def)) = enum_ else {
- return;
- };
-
- let mut discriminants = enum_def.discriminants(self.tcx);
- edge_effects.apply(|trans, edge| {
- let Some(value) = edge.value else {
- return;
- };
-
- // MIR building adds discriminants to the `values` array in the same order as they
- // are yielded by `AdtDef::discriminants`. We rely on this to match each
- // discriminant in `values` to its corresponding variant in linear time.
- let (variant, _) = discriminants
- .find(|&(_, discr)| discr.val == value)
- .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
-
- // Mark all move paths that correspond to variants other than this one as maybe
- // uninitialized (in reality, they are *definitely* uninitialized).
- drop_flag_effects::on_all_inactive_variants(
- self.tcx,
- self.body,
- self.move_data(),
- enum_place,
- variant,
- |mpi| trans.gen(mpi),
- );
- });
- }
-}
-
-impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
- /// Use set intersection as the join operator.
- type Domain = lattice::Dual<BitSet<MovePathIndex>>;
-
- const NAME: &'static str = "definite_init";
-
- fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
- // bottom = initialized (start_block_effect counters this at outset)
- lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len()))
- }
-
- // sets on_entry bits for Arg places
- fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
- state.0.clear();
-
- drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
- assert!(s == DropFlagState::Present);
- state.0.insert(path);
- });
- }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
- type Idx = MovePathIndex;
-
- fn statement_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _statement: &mir::Statement<'tcx>,
- location: Location,
- ) {
- drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
- Self::update_bits(trans, path, s)
- })
- }
-
- fn terminator_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
- Self::update_bits(trans, path, s)
- })
- }
-
- fn call_return_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _block: mir::BasicBlock,
- return_places: CallReturnPlaces<'_, 'tcx>,
- ) {
- return_places.for_each(|place| {
- // when a call returns successfully, that means we need to set
- // the bits for that dest_place to 1 (initialized).
- on_lookup_result_bits(
- self.tcx,
- self.body,
- self.move_data(),
- self.move_data().rev_lookup.find(place.as_ref()),
- |mpi| {
- trans.gen(mpi);
- },
- );
- });
- }
-}
-
-impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
- type Domain = ChunkedBitSet<InitIndex>;
-
- const NAME: &'static str = "ever_init";
-
- fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
- // bottom = no initialized variables by default
- ChunkedBitSet::new_empty(self.move_data().inits.len())
- }
-
- fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
- for arg_init in 0..body.arg_count {
- state.insert(InitIndex::new(arg_init));
- }
- }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
- type Idx = InitIndex;
-
- #[instrument(skip(self, trans), level = "debug")]
- fn statement_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- stmt: &mir::Statement<'tcx>,
- location: Location,
- ) {
- let move_data = self.move_data();
- let init_path_map = &move_data.init_path_map;
- let init_loc_map = &move_data.init_loc_map;
- let rev_lookup = &move_data.rev_lookup;
-
- debug!("initializes move_indexes {:?}", &init_loc_map[location]);
- trans.gen_all(init_loc_map[location].iter().copied());
-
- if let mir::StatementKind::StorageDead(local) = stmt.kind {
- // End inits for StorageDead, so that an immutable variable can
- // be reinitialized on the next iteration of the loop.
- let move_path_index = rev_lookup.find_local(local);
- debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]);
- trans.kill_all(init_path_map[move_path_index].iter().copied());
- }
- }
-
- #[instrument(skip(self, trans, _terminator), level = "debug")]
- fn terminator_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- let (body, move_data) = (self.body, self.move_data());
- let term = body[location.block].terminator();
- let init_loc_map = &move_data.init_loc_map;
- debug!(?term);
- debug!("initializes move_indexes {:?}", init_loc_map[location]);
- trans.gen_all(
- init_loc_map[location]
- .iter()
- .filter(|init_index| {
- move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
- })
- .copied(),
- );
- }
-
- fn call_return_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- block: mir::BasicBlock,
- _return_places: CallReturnPlaces<'_, 'tcx>,
- ) {
- let move_data = self.move_data();
- let init_loc_map = &move_data.init_loc_map;
-
- let call_loc = self.body.terminator_loc(block);
- for init_index in &init_loc_map[call_loc] {
- trans.gen(*init_index);
- }
- }
-}
-
-/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
-/// an enum discriminant.
-///
-/// We expect such blocks to have a call to `discriminant` as their last statement like so:
-///
-/// ```text
-/// ...
-/// _42 = discriminant(_1)
-/// SwitchInt(_42, ..)
-/// ```
-///
-/// If the basic block matches this pattern, this function returns the place corresponding to the
-/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
-fn switch_on_enum_discriminant<'mir, 'tcx>(
- tcx: TyCtxt<'tcx>,
- body: &'mir mir::Body<'tcx>,
- block: &'mir mir::BasicBlockData<'tcx>,
- switch_on: mir::Place<'tcx>,
-) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> {
- for statement in block.statements.iter().rev() {
- match &statement.kind {
- mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated)))
- if *lhs == switch_on =>
- {
- match discriminated.ty(body, tcx).ty.kind() {
- ty::Adt(def, _) => return Some((*discriminated, *def)),
-
- // `Rvalue::Discriminant` is also used to get the active yield point for a
- // generator, but we do not need edge-specific effects in that case. This may
- // change in the future.
- ty::Generator(..) => return None,
-
- t => bug!("`discriminant` called on unexpected type {:?}", t),
- }
- }
- mir::StatementKind::Coverage(_) => continue,
- _ => return None,
- }
- }
- None
-}
-
-struct OnMutBorrow<F>(F);
-
-impl<F> Visitor<'_> for OnMutBorrow<F>
-where
- F: FnMut(&mir::Place<'_>),
-{
- fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'_>, location: Location) {
- // FIXME: Does `&raw const foo` allow mutation? See #90413.
- match rvalue {
- mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place)
- | mir::Rvalue::AddressOf(_, place) => (self.0)(place),
-
- _ => {}
- }
-
- self.super_rvalue(rvalue, location)
- }
-}
-
-/// Calls `f` for each mutable borrow or raw reference in the program.
-///
-/// This DOES NOT call `f` for a shared borrow of a type with interior mutability. That's okay for
-/// initializedness, because we cannot move from an `UnsafeCell` (outside of `core::cell`), but
-/// other analyses will likely need to check for `!Freeze`.
-fn for_each_mut_borrow<'tcx>(
- mir: &impl MirVisitable<'tcx>,
- location: Location,
- f: impl FnMut(&mir::Place<'_>),
-) {
- let mut vis = OnMutBorrow(f);
-
- mir.apply(location, &mut vis);
-}
diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
index 666c8d50a..bea23b7f7 100644
--- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
@@ -1,10 +1,12 @@
-pub use super::*;
-
-use crate::{CallReturnPlaces, GenKill, ResultsClonedCursor};
+use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
+
use std::borrow::Cow;
+use super::MaybeBorrowedLocals;
+use crate::{GenKill, ResultsClonedCursor};
+
#[derive(Clone)]
pub struct MaybeStorageLive<'a> {
always_live_locals: Cow<'a, BitSet<Local>>,
@@ -27,12 +29,12 @@ impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> {
const NAME: &'static str = "maybe_storage_live";
- fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
// bottom = dead
BitSet::new_empty(body.local_decls.len())
}
- fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
+ fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
for local in self.always_live_locals.iter() {
on_entry.insert(local);
@@ -47,10 +49,14 @@ impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> {
impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> {
type Idx = Local;
+ fn domain_size(&self, body: &Body<'tcx>) -> usize {
+ body.local_decls.len()
+ }
+
fn statement_effect(
&mut self,
trans: &mut impl GenKill<Self::Idx>,
- stmt: &mir::Statement<'tcx>,
+ stmt: &Statement<'tcx>,
_: Location,
) {
match stmt.kind {
@@ -60,13 +66,14 @@ impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> {
}
}
- fn terminator_effect(
+ fn terminator_effect<'mir>(
&mut self,
- _trans: &mut impl GenKill<Self::Idx>,
- _: &mir::Terminator<'tcx>,
+ _trans: &mut Self::Domain,
+ terminator: &'mir Terminator<'tcx>,
_: Location,
- ) {
+ ) -> TerminatorEdges<'mir, 'tcx> {
// Terminators have no effect
+ terminator.edges()
}
fn call_return_effect(
@@ -95,12 +102,12 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead {
const NAME: &'static str = "maybe_storage_dead";
- fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
// bottom = live
BitSet::new_empty(body.local_decls.len())
}
- fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
+ fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
// Do not iterate on return place and args, as they are trivially always live.
for local in body.vars_and_temps_iter() {
@@ -114,10 +121,14 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead {
impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead {
type Idx = Local;
+ fn domain_size(&self, body: &Body<'tcx>) -> usize {
+ body.local_decls.len()
+ }
+
fn statement_effect(
&mut self,
trans: &mut impl GenKill<Self::Idx>,
- stmt: &mir::Statement<'tcx>,
+ stmt: &Statement<'tcx>,
_: Location,
) {
match stmt.kind {
@@ -127,13 +138,14 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead {
}
}
- fn terminator_effect(
+ fn terminator_effect<'mir>(
&mut self,
- _trans: &mut impl GenKill<Self::Idx>,
- _: &mir::Terminator<'tcx>,
+ _: &mut Self::Domain,
+ terminator: &'mir Terminator<'tcx>,
_: Location,
- ) {
+ ) -> TerminatorEdges<'mir, 'tcx> {
// Terminators have no effect
+ terminator.edges()
}
fn call_return_effect(
@@ -172,12 +184,12 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
const NAME: &'static str = "requires_storage";
- fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
// bottom = dead
BitSet::new_empty(body.local_decls.len())
}
- fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
+ fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
// The resume argument is live on function entry (we don't care about
// the `self` argument)
for arg in body.args_iter().skip(1) {
@@ -189,10 +201,14 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
type Idx = Local;
+ fn domain_size(&self, body: &Body<'tcx>) -> usize {
+ body.local_decls.len()
+ }
+
fn before_statement_effect(
&mut self,
trans: &mut impl GenKill<Self::Idx>,
- stmt: &mir::Statement<'tcx>,
+ stmt: &Statement<'tcx>,
loc: Location,
) {
// If a place is borrowed in a statement, it needs storage for that statement.
@@ -225,7 +241,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
fn statement_effect(
&mut self,
trans: &mut impl GenKill<Self::Idx>,
- _: &mir::Statement<'tcx>,
+ _: &Statement<'tcx>,
loc: Location,
) {
// If we move from a place then it only stops needing storage *after*
@@ -236,11 +252,14 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
fn before_terminator_effect(
&mut self,
trans: &mut impl GenKill<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
+ terminator: &Terminator<'tcx>,
loc: Location,
) {
// If a place is borrowed in a terminator, it needs storage for that terminator.
- self.borrowed_locals.mut_analysis().terminator_effect(trans, terminator, loc);
+ self.borrowed_locals
+ .mut_analysis()
+ .transfer_function(trans)
+ .visit_terminator(terminator, loc);
match &terminator.kind {
TerminatorKind::Call { destination, .. } => {
@@ -286,12 +305,12 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
}
}
- fn terminator_effect(
+ fn terminator_effect<'t>(
&mut self,
- trans: &mut impl GenKill<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
+ trans: &mut Self::Domain,
+ terminator: &'t Terminator<'tcx>,
loc: Location,
- ) {
+ ) -> TerminatorEdges<'t, 'tcx> {
match terminator.kind {
// For call terminators the destination requires storage for the call
// and after the call returns successfully, but not after a panic.
@@ -323,6 +342,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
}
self.check_for_move(trans, loc);
+ terminator.edges()
}
fn call_return_effect(
@@ -333,15 +353,6 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
) {
return_places.for_each(|place| trans.gen(place.local));
}
-
- fn yield_resume_effect(
- &mut self,
- trans: &mut impl GenKill<Self::Idx>,
- _resume_block: BasicBlock,
- resume_place: mir::Place<'tcx>,
- ) {
- trans.gen(resume_place.local);
- }
}
impl<'tcx> MaybeRequiresStorage<'_, '_, 'tcx> {
diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs
index d43446bc5..0cdbee19d 100644
--- a/compiler/rustc_mir_dataflow/src/lib.rs
+++ b/compiler/rustc_mir_dataflow/src/lib.rs
@@ -28,8 +28,8 @@ pub use self::drop_flag_effects::{
};
pub use self::framework::{
fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, AnalysisResults, Backward,
- CallReturnPlaces, CloneAnalysis, Direction, Engine, Forward, GenKill, GenKillAnalysis,
- JoinSemiLattice, Results, ResultsCloned, ResultsClonedCursor, ResultsCursor, ResultsRefCursor,
+ CloneAnalysis, Direction, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice,
+ MaybeReachable, Results, ResultsCloned, ResultsClonedCursor, ResultsCursor, ResultsRefCursor,
ResultsVisitable, ResultsVisitor, SwitchIntEdgeEffects,
};
@@ -43,6 +43,7 @@ pub mod impls;
pub mod move_paths;
pub mod rustc_peek;
pub mod storage;
+pub mod un_derefer;
pub mod value_analysis;
fluent_messages! { "../messages.ftl" }
diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
index dc7e9ab3c..5052de991 100644
--- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
+++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
@@ -4,7 +4,6 @@ use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
use smallvec::{smallvec, SmallVec};
-use std::iter;
use std::mem;
use super::abs_domain::Lift;
@@ -40,22 +39,22 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
locals: body
.local_decls
.iter_enumerated()
- .filter(|(_, l)| !l.is_deref_temp())
- .map(|(i, _)| {
- (
- i,
+ .map(|(i, l)| {
+ if l.is_deref_temp() {
+ MovePathIndex::MAX
+ } else {
Self::new_move_path(
&mut move_paths,
&mut path_map,
&mut init_path_map,
None,
Place::from(i),
- ),
- )
+ )
+ }
})
.collect(),
projections: Default::default(),
- derefer_sidetable: Default::default(),
+ un_derefer: Default::default(),
},
move_paths,
path_map,
@@ -100,11 +99,10 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
///
/// Maybe we should have separate "borrowck" and "moveck" modes.
fn move_path_for(&mut self, place: Place<'tcx>) -> Result<MovePathIndex, MoveError<'tcx>> {
- let deref_chain = self.builder.data.rev_lookup.deref_chain(place.as_ref());
+ let data = &mut self.builder.data;
debug!("lookup({:?})", place);
- let mut base =
- self.builder.data.rev_lookup.find_local(deref_chain.first().unwrap_or(&place).local);
+ let mut base = data.rev_lookup.find_local(place.local);
// The move path index of the first union that we find. Once this is
// some we stop creating child move paths, since moves from unions
@@ -113,55 +111,60 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
// from `*(u.f: &_)` isn't allowed.
let mut union_path = None;
- for place in deref_chain.into_iter().chain(iter::once(place)) {
- for (place_ref, elem) in place.as_ref().iter_projections() {
- let body = self.builder.body;
- let tcx = self.builder.tcx;
- let place_ty = place_ref.ty(body, tcx).ty;
- match place_ty.kind() {
- ty::Ref(..) | ty::RawPtr(..) => {
- return Err(MoveError::cannot_move_out_of(
- self.loc,
- BorrowedContent {
- target_place: place_ref.project_deeper(&[elem], tcx),
- },
- ));
- }
- ty::Adt(adt, _) if adt.has_dtor(tcx) && !adt.is_box() => {
- return Err(MoveError::cannot_move_out_of(
- self.loc,
- InteriorOfTypeWithDestructor { container_ty: place_ty },
- ));
- }
- ty::Adt(adt, _) if adt.is_union() => {
- union_path.get_or_insert(base);
- }
- ty::Slice(_) => {
+ for (place_ref, elem) in data.rev_lookup.un_derefer.iter_projections(place.as_ref()) {
+ let body = self.builder.body;
+ let tcx = self.builder.tcx;
+ let place_ty = place_ref.ty(body, tcx).ty;
+ match place_ty.kind() {
+ ty::Ref(..) | ty::RawPtr(..) => {
+ return Err(MoveError::cannot_move_out_of(
+ self.loc,
+ BorrowedContent { target_place: place_ref.project_deeper(&[elem], tcx) },
+ ));
+ }
+ ty::Adt(adt, _) if adt.has_dtor(tcx) && !adt.is_box() => {
+ return Err(MoveError::cannot_move_out_of(
+ self.loc,
+ InteriorOfTypeWithDestructor { container_ty: place_ty },
+ ));
+ }
+ ty::Adt(adt, _) if adt.is_union() => {
+ union_path.get_or_insert(base);
+ }
+ ty::Slice(_) => {
+ return Err(MoveError::cannot_move_out_of(
+ self.loc,
+ InteriorOfSliceOrArray {
+ ty: place_ty,
+ is_index: matches!(elem, ProjectionElem::Index(..)),
+ },
+ ));
+ }
+
+ ty::Array(..) => {
+ if let ProjectionElem::Index(..) = elem {
return Err(MoveError::cannot_move_out_of(
self.loc,
- InteriorOfSliceOrArray {
- ty: place_ty,
- is_index: matches!(elem, ProjectionElem::Index(..)),
- },
+ InteriorOfSliceOrArray { ty: place_ty, is_index: true },
));
}
+ }
- ty::Array(..) => {
- if let ProjectionElem::Index(..) = elem {
- return Err(MoveError::cannot_move_out_of(
- self.loc,
- InteriorOfSliceOrArray { ty: place_ty, is_index: true },
- ));
- }
- }
-
- _ => {}
- };
+ _ => {}
+ };
- if union_path.is_none() {
- base = self
- .add_move_path(base, elem, |tcx| place_ref.project_deeper(&[elem], tcx));
- }
+ if union_path.is_none() {
+ // inlined from add_move_path because of a borrowck conflict with the iterator
+ base =
+ *data.rev_lookup.projections.entry((base, elem.lift())).or_insert_with(|| {
+ MoveDataBuilder::new_move_path(
+ &mut data.move_paths,
+ &mut data.path_map,
+ &mut data.init_path_map,
+ Some(base),
+ place_ref.project_deeper(&[elem], tcx),
+ )
+ })
}
}
@@ -282,10 +285,14 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
fn gather_statement(&mut self, stmt: &Statement<'tcx>) {
match &stmt.kind {
StatementKind::Assign(box (place, Rvalue::CopyForDeref(reffed))) => {
- assert!(place.projection.is_empty());
- if self.builder.body.local_decls[place.local].is_deref_temp() {
- self.builder.data.rev_lookup.derefer_sidetable.insert(place.local, *reffed);
- }
+ let local = place.as_local().unwrap();
+ assert!(self.builder.body.local_decls[local].is_deref_temp());
+
+ let rev_lookup = &mut self.builder.data.rev_lookup;
+
+ rev_lookup.un_derefer.insert(local, reffed.as_ref());
+ let base_local = rev_lookup.un_derefer.deref_chain(local).first().unwrap().local;
+ rev_lookup.locals[local] = rev_lookup.locals[base_local];
}
StatementKind::Assign(box (place, rval)) => {
self.create_move_path(*place);
@@ -306,7 +313,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
StatementKind::StorageLive(_) => {}
StatementKind::StorageDead(local) => {
// DerefTemp locals (results of CopyForDeref) don't actually move anything.
- if !self.builder.data.rev_lookup.derefer_sidetable.contains_key(&local) {
+ if !self.builder.body.local_decls[*local].is_deref_temp() {
self.gather_move(Place::from(*local));
}
}
diff --git a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs
index aa901f66d..0c7aa6676 100644
--- a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs
+++ b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs
@@ -1,5 +1,6 @@
use crate::move_paths::builder::MoveDat;
-use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
+use crate::un_derefer::UnDerefer;
+use rustc_data_structures::fx::FxHashMap;
use rustc_index::{IndexSlice, IndexVec};
use rustc_middle::mir::*;
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
@@ -290,7 +291,7 @@ impl Init {
/// Tables mapping from a place to its MovePathIndex.
#[derive(Debug)]
pub struct MovePathLookup<'tcx> {
- locals: FxIndexMap<Local, MovePathIndex>,
+ locals: IndexVec<Local, MovePathIndex>,
/// projections are made from a base-place and a projection
/// elem. The base-place will have a unique MovePathIndex; we use
@@ -300,8 +301,7 @@ pub struct MovePathLookup<'tcx> {
/// elem to the associated MovePathIndex.
projections: FxHashMap<(MovePathIndex, AbstractElem), MovePathIndex>,
- /// Maps `DerefTemp` locals to the `Place`s assigned to them.
- derefer_sidetable: FxHashMap<Local, Place<'tcx>>,
+ un_derefer: UnDerefer<'tcx>,
}
mod builder;
@@ -317,54 +317,23 @@ impl<'tcx> MovePathLookup<'tcx> {
// alternative will *not* create a MovePath on the fly for an
// unknown place, but will rather return the nearest available
// parent.
- pub fn find(&self, place: PlaceRef<'_>) -> LookupResult {
- let deref_chain = self.deref_chain(place);
+ pub fn find(&self, place: PlaceRef<'tcx>) -> LookupResult {
+ let mut result = self.find_local(place.local);
- let local = match deref_chain.first() {
- Some(place) => place.local,
- None => place.local,
- };
-
- let mut result = *self.locals.get(&local).unwrap_or_else(|| {
- bug!("base local ({local:?}) of deref_chain should not be a deref temp")
- });
-
- // this needs to be a closure because `place` has a different lifetime than `prefix`'s places
- let mut subpaths_for_place = |place: PlaceRef<'_>| {
- for elem in place.projection.iter() {
- if let Some(&subpath) = self.projections.get(&(result, elem.lift())) {
- result = subpath;
- } else {
- return Some(result);
- }
- }
- None
- };
-
- for place in deref_chain {
- if let Some(result) = subpaths_for_place(place.as_ref()) {
+ for (_, elem) in self.un_derefer.iter_projections(place) {
+ if let Some(&subpath) = self.projections.get(&(result, elem.lift())) {
+ result = subpath;
+ } else {
return LookupResult::Parent(Some(result));
}
}
- if let Some(result) = subpaths_for_place(place) {
- return LookupResult::Parent(Some(result));
- }
-
LookupResult::Exact(result)
}
+ #[inline]
pub fn find_local(&self, local: Local) -> MovePathIndex {
- let deref_chain = self.deref_chain(Place::from(local).as_ref());
-
- let local = match deref_chain.last() {
- Some(place) => place.local,
- None => local,
- };
-
- *self.locals.get(&local).unwrap_or_else(|| {
- bug!("base local ({local:?}) of deref_chain should not be a deref temp")
- })
+ self.locals[local]
}
/// An enumerated iterator of `local`s and their associated
@@ -372,22 +341,7 @@ impl<'tcx> MovePathLookup<'tcx> {
pub fn iter_locals_enumerated(
&self,
) -> impl DoubleEndedIterator<Item = (Local, MovePathIndex)> + ExactSizeIterator + '_ {
- self.locals.iter().map(|(&l, &idx)| (l, idx))
- }
-
- /// Returns the chain of places behind `DerefTemp` locals in `place`
- pub fn deref_chain(&self, place: PlaceRef<'_>) -> Vec<Place<'tcx>> {
- let mut prefix = Vec::new();
- let mut local = place.local;
-
- while let Some(&reffed) = self.derefer_sidetable.get(&local) {
- prefix.insert(0, reffed);
- local = reffed.local;
- }
-
- debug!("deref_chain({place:?}) = {prefix:?}");
-
- prefix
+ self.locals.iter_enumerated().map(|(l, &idx)| (l, idx))
}
}
diff --git a/compiler/rustc_mir_dataflow/src/rustc_peek.rs b/compiler/rustc_mir_dataflow/src/rustc_peek.rs
index 156231c3a..775c522b4 100644
--- a/compiler/rustc_mir_dataflow/src/rustc_peek.rs
+++ b/compiler/rustc_mir_dataflow/src/rustc_peek.rs
@@ -190,14 +190,14 @@ impl PeekCall {
if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
&terminator.kind
{
- if let ty::FnDef(def_id, substs) = *func.literal.ty().kind() {
+ if let ty::FnDef(def_id, fn_args) = *func.literal.ty().kind() {
let name = tcx.item_name(def_id);
if !tcx.is_intrinsic(def_id) || name != sym::rustc_peek {
return None;
}
- assert_eq!(args.len(), 1);
- let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
+ assert_eq!(fn_args.len(), 1);
+ let kind = PeekCallKind::from_arg_ty(fn_args.type_at(0));
let arg = match &args[0] {
Operand::Copy(place) | Operand::Move(place) => {
if let Some(local) = place.as_local() {
diff --git a/compiler/rustc_mir_dataflow/src/un_derefer.rs b/compiler/rustc_mir_dataflow/src/un_derefer.rs
new file mode 100644
index 000000000..874d50ffd
--- /dev/null
+++ b/compiler/rustc_mir_dataflow/src/un_derefer.rs
@@ -0,0 +1,100 @@
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::mir::*;
+
+/// Used for reverting changes made by `DerefSeparator`
+#[derive(Default, Debug)]
+pub struct UnDerefer<'tcx> {
+ deref_chains: FxHashMap<Local, Vec<PlaceRef<'tcx>>>,
+}
+
+impl<'tcx> UnDerefer<'tcx> {
+ #[inline]
+ pub fn insert(&mut self, local: Local, reffed: PlaceRef<'tcx>) {
+ let mut chain = self.deref_chains.remove(&reffed.local).unwrap_or_default();
+ chain.push(reffed);
+ self.deref_chains.insert(local, chain);
+ }
+
+ /// Returns the chain of places behind `DerefTemp` locals
+ #[inline]
+ pub fn deref_chain(&self, local: Local) -> &[PlaceRef<'tcx>] {
+ self.deref_chains.get(&local).map(Vec::as_slice).unwrap_or_default()
+ }
+
+ /// Iterates over the projections of a place and its deref chain.
+ ///
+ /// See [`PlaceRef::iter_projections`]
+ #[inline]
+ pub fn iter_projections(
+ &self,
+ place: PlaceRef<'tcx>,
+ ) -> impl Iterator<Item = (PlaceRef<'tcx>, PlaceElem<'tcx>)> + '_ {
+ ProjectionIter::new(self.deref_chain(place.local), place)
+ }
+}
+
+/// The iterator returned by [`UnDerefer::iter_projections`].
+struct ProjectionIter<'a, 'tcx> {
+ places: SlicePlusOne<'a, PlaceRef<'tcx>>,
+ proj_idx: usize,
+}
+
+impl<'a, 'tcx> ProjectionIter<'a, 'tcx> {
+ #[inline]
+ fn new(deref_chain: &'a [PlaceRef<'tcx>], place: PlaceRef<'tcx>) -> Self {
+ // just return an empty iterator for a bare local
+ let last = if place.as_local().is_none() {
+ Some(place)
+ } else {
+ debug_assert!(deref_chain.is_empty());
+ None
+ };
+
+ ProjectionIter { places: SlicePlusOne { slice: deref_chain, last }, proj_idx: 0 }
+ }
+}
+
+impl<'tcx> Iterator for ProjectionIter<'_, 'tcx> {
+ type Item = (PlaceRef<'tcx>, PlaceElem<'tcx>);
+
+ #[inline]
+ fn next(&mut self) -> Option<(PlaceRef<'tcx>, PlaceElem<'tcx>)> {
+ let place = self.places.read()?;
+
+ // the projection should never be empty except for a bare local which is handled in new
+ let partial_place =
+ PlaceRef { local: place.local, projection: &place.projection[..self.proj_idx] };
+ let elem = place.projection[self.proj_idx];
+
+ if self.proj_idx == place.projection.len() - 1 {
+ self.proj_idx = 0;
+ self.places.advance();
+ } else {
+ self.proj_idx += 1;
+ }
+
+ Some((partial_place, elem))
+ }
+}
+
+struct SlicePlusOne<'a, T> {
+ slice: &'a [T],
+ last: Option<T>,
+}
+
+impl<T: Copy> SlicePlusOne<'_, T> {
+ #[inline]
+ fn read(&self) -> Option<T> {
+ self.slice.first().copied().or(self.last)
+ }
+
+ #[inline]
+ fn advance(&mut self) {
+ match self.slice {
+ [_, ref remainder @ ..] => {
+ self.slice = remainder;
+ }
+ [] => self.last = None,
+ }
+ }
+}
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
index 5693e5a4a..766e0257e 100644
--- a/compiler/rustc_mir_dataflow/src/value_analysis.rs
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -47,8 +47,7 @@ use rustc_target::abi::{FieldIdx, VariantIdx};
use crate::lattice::{HasBottom, HasTop};
use crate::{
- fmt::DebugWithContext, Analysis, AnalysisDomain, CallReturnPlaces, JoinSemiLattice,
- SwitchIntEdgeEffects,
+ fmt::DebugWithContext, Analysis, AnalysisDomain, JoinSemiLattice, SwitchIntEdgeEffects,
};
pub trait ValueAnalysis<'tcx> {
@@ -242,11 +241,19 @@ pub trait ValueAnalysis<'tcx> {
/// The effect of a successful function call return should not be
/// applied here, see [`Analysis::apply_terminator_effect`].
- fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
+ fn handle_terminator<'mir>(
+ &self,
+ terminator: &'mir Terminator<'tcx>,
+ state: &mut State<Self::Value>,
+ ) -> TerminatorEdges<'mir, 'tcx> {
self.super_terminator(terminator, state)
}
- fn super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
+ fn super_terminator<'mir>(
+ &self,
+ terminator: &'mir Terminator<'tcx>,
+ state: &mut State<Self::Value>,
+ ) -> TerminatorEdges<'mir, 'tcx> {
match &terminator.kind {
TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
// Effect is applied by `handle_call_return`.
@@ -258,8 +265,10 @@ pub trait ValueAnalysis<'tcx> {
// They would have an effect, but are not allowed in this phase.
bug!("encountered disallowed terminator");
}
+ TerminatorKind::SwitchInt { discr, targets } => {
+ return self.handle_switch_int(discr, targets, state);
+ }
TerminatorKind::Goto { .. }
- | TerminatorKind::SwitchInt { .. }
| TerminatorKind::Resume
| TerminatorKind::Terminate
| TerminatorKind::Return
@@ -271,6 +280,7 @@ pub trait ValueAnalysis<'tcx> {
// These terminators have no effect on the analysis.
}
}
+ terminator.edges()
}
fn handle_call_return(
@@ -291,19 +301,22 @@ pub trait ValueAnalysis<'tcx> {
})
}
- fn handle_switch_int(
+ fn handle_switch_int<'mir>(
&self,
- discr: &Operand<'tcx>,
- apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
- ) {
- self.super_switch_int(discr, apply_edge_effects)
+ discr: &'mir Operand<'tcx>,
+ targets: &'mir SwitchTargets,
+ state: &mut State<Self::Value>,
+ ) -> TerminatorEdges<'mir, 'tcx> {
+ self.super_switch_int(discr, targets, state)
}
- fn super_switch_int(
+ fn super_switch_int<'mir>(
&self,
- _discr: &Operand<'tcx>,
- _apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
- ) {
+ discr: &'mir Operand<'tcx>,
+ targets: &'mir SwitchTargets,
+ _state: &mut State<Self::Value>,
+ ) -> TerminatorEdges<'mir, 'tcx> {
+ TerminatorEdges::SwitchInt { discr, targets }
}
fn wrap(self) -> ValueAnalysisWrapper<Self>
@@ -353,14 +366,16 @@ where
}
}
- fn apply_terminator_effect(
+ fn apply_terminator_effect<'mir>(
&mut self,
state: &mut Self::Domain,
- terminator: &Terminator<'tcx>,
+ terminator: &'mir Terminator<'tcx>,
_location: Location,
- ) {
+ ) -> TerminatorEdges<'mir, 'tcx> {
if state.is_reachable() {
- self.0.handle_terminator(terminator, state);
+ self.0.handle_terminator(terminator, state)
+ } else {
+ TerminatorEdges::None
}
}
@@ -368,7 +383,7 @@ where
&mut self,
state: &mut Self::Domain,
_block: BasicBlock,
- return_places: crate::CallReturnPlaces<'_, 'tcx>,
+ return_places: CallReturnPlaces<'_, 'tcx>,
) {
if state.is_reachable() {
self.0.handle_call_return(return_places, state)
@@ -378,11 +393,9 @@ where
fn apply_switch_int_edge_effects(
&mut self,
_block: BasicBlock,
- discr: &Operand<'tcx>,
- apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
+ _discr: &Operand<'tcx>,
+ _apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
) {
- // FIXME: Dataflow framework provides no access to current state here.
- self.0.handle_switch_int(discr, apply_edge_effects)
}
}
@@ -839,7 +852,7 @@ impl Map {
tail_elem: Option<TrackElem>,
f: &mut impl FnMut(ValueIndex),
) {
- if place.has_deref() {
+ if place.is_indirect_first_projection() {
// We do not track indirect places.
return;
}
@@ -999,14 +1012,14 @@ pub fn iter_fields<'tcx>(
f(None, field.into(), ty);
}
}
- ty::Adt(def, substs) => {
+ ty::Adt(def, args) => {
if def.is_union() {
return;
}
for (v_index, v_def) in def.variants().iter_enumerated() {
let variant = if def.is_struct() { None } else { Some(v_index) };
for (f_index, f_def) in v_def.fields.iter().enumerate() {
- let field_ty = f_def.ty(tcx, substs);
+ let field_ty = f_def.ty(tcx, args);
let field_ty = tcx
.try_normalize_erasing_regions(param_env, field_ty)
.unwrap_or_else(|_| tcx.erase_regions(field_ty));
@@ -1014,8 +1027,8 @@ pub fn iter_fields<'tcx>(
}
}
}
- ty::Closure(_, substs) => {
- iter_fields(substs.as_closure().tupled_upvars_ty(), tcx, param_env, f);
+ ty::Closure(_, args) => {
+ iter_fields(args.as_closure().tupled_upvars_ty(), tcx, param_env, f);
}
_ => (),
}
@@ -1099,10 +1112,10 @@ fn debug_with_context_rec<V: Debug + Eq>(
let info_elem = map.places[child].proj_elem.unwrap();
let child_place_str = match info_elem {
TrackElem::Discriminant => {
- format!("discriminant({})", place_str)
+ format!("discriminant({place_str})")
}
TrackElem::Variant(idx) => {
- format!("({} as {:?})", place_str, idx)
+ format!("({place_str} as {idx:?})")
}
TrackElem::Field(field) => {
if place_str.starts_with('*') {