about summary refs log tree commit diff
path: root/compiler/rustc_mir_dataflow/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-10-31 06:22:57 +0000
committerbors <bors@rust-lang.org>2024-10-31 06:22:57 +0000
commit4d296eabe4c5cfbce9bb68e6221bca2165aae97b (patch)
tree9feba64683f870341913af6122d2fa218e588f97 /compiler/rustc_mir_dataflow/src
parentc8b83785dccdd22345d193fed7431ce44074007b (diff)
parent49a76c14c447acc032d4a5358a668a13160067f6 (diff)
downloadrust-4d296eabe4c5cfbce9bb68e6221bca2165aae97b.tar.gz
rust-4d296eabe4c5cfbce9bb68e6221bca2165aae97b.zip
Auto merge of #132384 - matthiaskrgr:rollup-0ze5wc4, r=matthiaskrgr
Rollup of 4 pull requests

Successful merges:

 - #132347 (Remove `ValueAnalysis` and `ValueAnalysisWrapper`.)
 - #132365 (pass `RUSTC_HOST_FLAGS` at once without the for loop)
 - #132366 (Do not enforce `~const` constness effects in typeck if `rustc_do_not_const_check`)
 - #132376 (Annotate `input` reference tests)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'compiler/rustc_mir_dataflow/src')
-rw-r--r--compiler/rustc_mir_dataflow/src/value_analysis.rs416
1 files changed, 4 insertions, 412 deletions
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
index 80151b8ba2d..af2d514fc76 100644
--- a/compiler/rustc_mir_dataflow/src/value_analysis.rs
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -1,38 +1,3 @@
-//! This module provides a framework on top of the normal MIR dataflow framework to simplify the
-//! implementation of analyses that track information about the values stored in certain places.
-//! We are using the term "place" here to refer to a `mir::Place` (a place expression) instead of
-//! an `interpret::Place` (a memory location).
-//!
-//! The default methods of [`ValueAnalysis`] (prefixed with `super_` instead of `handle_`)
-//! provide some behavior that should be valid for all abstract domains that are based only on the
-//! value stored in a certain place. On top of these default rules, an implementation should
-//! override some of the `handle_` methods. For an example, see `ConstAnalysis`.
-//!
-//! An implementation must also provide a [`Map`]. Before the analysis begins, all places that
-//! should be tracked during the analysis must be registered. During the analysis, no new places
-//! can be registered. The [`State`] can be queried to retrieve the abstract value stored for a
-//! certain place by passing the map.
-//!
-//! This framework is currently experimental. Originally, it supported shared references and enum
-//! variants. However, it was discovered that both of these were unsound, and especially references
-//! had subtle but serious issues. In the future, they could be added back in, but we should clarify
-//! the rules for optimizations that rely on the aliasing model first.
-//!
-//!
-//! # Notes
-//!
-//! - The bottom state denotes uninitialized memory. Because we are only doing a sound approximation
-//! of the actual execution, we can also use this state for places where access would be UB.
-//!
-//! - The assignment logic in `State::insert_place_idx` assumes that the places are non-overlapping,
-//! or identical. Note that this refers to place expressions, not memory locations.
-//!
-//! - Currently, places that have their reference taken cannot be tracked. Although this would be
-//! possible, it has to rely on some aliasing model, which we are not ready to commit to yet.
-//! Because of that, we can assume that the only way to change the value behind a tracked place is
-//! by direct assignment.
-
-use std::assert_matches::assert_matches;
 use std::fmt::{Debug, Formatter};
 use std::ops::Range;
 
@@ -42,359 +7,14 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexSet, StdEntry};
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_index::IndexVec;
 use rustc_index::bit_set::BitSet;
-use rustc_middle::bug;
 use rustc_middle::mir::tcx::PlaceTy;
 use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
 use rustc_middle::mir::*;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use tracing::debug;
 
-use crate::fmt::DebugWithContext;
+use crate::JoinSemiLattice;
 use crate::lattice::{HasBottom, HasTop};
-use crate::{Analysis, JoinSemiLattice, SwitchIntEdgeEffects};
-
-pub trait ValueAnalysis<'tcx> {
-    /// For each place of interest, the analysis tracks a value of the given type.
-    type Value: Clone + JoinSemiLattice + HasBottom + HasTop + Debug;
-
-    const NAME: &'static str;
-
-    fn map(&self) -> &Map<'tcx>;
-
-    fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
-        self.super_statement(statement, state)
-    }
-
-    fn super_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
-        match &statement.kind {
-            StatementKind::Assign(box (place, rvalue)) => {
-                self.handle_assign(*place, rvalue, state);
-            }
-            StatementKind::SetDiscriminant { box place, variant_index } => {
-                self.handle_set_discriminant(*place, *variant_index, state);
-            }
-            StatementKind::Intrinsic(box intrinsic) => {
-                self.handle_intrinsic(intrinsic, state);
-            }
-            StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
-                // StorageLive leaves the local in an uninitialized state.
-                // StorageDead makes it UB to access the local afterwards.
-                state.flood_with(Place::from(*local).as_ref(), self.map(), Self::Value::BOTTOM);
-            }
-            StatementKind::Deinit(box place) => {
-                // Deinit makes the place uninitialized.
-                state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM);
-            }
-            StatementKind::Retag(..) => {
-                // We don't track references.
-            }
-            StatementKind::ConstEvalCounter
-            | StatementKind::Nop
-            | StatementKind::FakeRead(..)
-            | StatementKind::PlaceMention(..)
-            | StatementKind::Coverage(..)
-            | StatementKind::AscribeUserType(..) => (),
-        }
-    }
-
-    fn handle_set_discriminant(
-        &self,
-        place: Place<'tcx>,
-        variant_index: VariantIdx,
-        state: &mut State<Self::Value>,
-    ) {
-        self.super_set_discriminant(place, variant_index, state)
-    }
-
-    fn super_set_discriminant(
-        &self,
-        place: Place<'tcx>,
-        _variant_index: VariantIdx,
-        state: &mut State<Self::Value>,
-    ) {
-        state.flood_discr(place.as_ref(), self.map());
-    }
-
-    fn handle_intrinsic(
-        &self,
-        intrinsic: &NonDivergingIntrinsic<'tcx>,
-        state: &mut State<Self::Value>,
-    ) {
-        self.super_intrinsic(intrinsic, state);
-    }
-
-    fn super_intrinsic(
-        &self,
-        intrinsic: &NonDivergingIntrinsic<'tcx>,
-        _state: &mut State<Self::Value>,
-    ) {
-        match intrinsic {
-            NonDivergingIntrinsic::Assume(..) => {
-                // Could use this, but ignoring it is sound.
-            }
-            NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
-                dst: _,
-                src: _,
-                count: _,
-            }) => {
-                // This statement represents `*dst = *src`, `count` times.
-            }
-        }
-    }
-
-    fn handle_assign(
-        &self,
-        target: Place<'tcx>,
-        rvalue: &Rvalue<'tcx>,
-        state: &mut State<Self::Value>,
-    ) {
-        self.super_assign(target, rvalue, state)
-    }
-
-    fn super_assign(
-        &self,
-        target: Place<'tcx>,
-        rvalue: &Rvalue<'tcx>,
-        state: &mut State<Self::Value>,
-    ) {
-        let result = self.handle_rvalue(rvalue, state);
-        state.assign(target.as_ref(), result, self.map());
-    }
-
-    fn handle_rvalue(
-        &self,
-        rvalue: &Rvalue<'tcx>,
-        state: &mut State<Self::Value>,
-    ) -> ValueOrPlace<Self::Value> {
-        self.super_rvalue(rvalue, state)
-    }
-
-    fn super_rvalue(
-        &self,
-        rvalue: &Rvalue<'tcx>,
-        state: &mut State<Self::Value>,
-    ) -> ValueOrPlace<Self::Value> {
-        match rvalue {
-            Rvalue::Use(operand) => self.handle_operand(operand, state),
-            Rvalue::CopyForDeref(place) => self.handle_operand(&Operand::Copy(*place), state),
-            Rvalue::Ref(..) | Rvalue::RawPtr(..) => {
-                // We don't track such places.
-                ValueOrPlace::TOP
-            }
-            Rvalue::Repeat(..)
-            | Rvalue::ThreadLocalRef(..)
-            | Rvalue::Len(..)
-            | Rvalue::Cast(..)
-            | Rvalue::BinaryOp(..)
-            | Rvalue::NullaryOp(..)
-            | Rvalue::UnaryOp(..)
-            | Rvalue::Discriminant(..)
-            | Rvalue::Aggregate(..)
-            | Rvalue::ShallowInitBox(..) => {
-                // No modification is possible through these r-values.
-                ValueOrPlace::TOP
-            }
-        }
-    }
-
-    fn handle_operand(
-        &self,
-        operand: &Operand<'tcx>,
-        state: &mut State<Self::Value>,
-    ) -> ValueOrPlace<Self::Value> {
-        self.super_operand(operand, state)
-    }
-
-    fn super_operand(
-        &self,
-        operand: &Operand<'tcx>,
-        state: &mut State<Self::Value>,
-    ) -> ValueOrPlace<Self::Value> {
-        match operand {
-            Operand::Constant(box constant) => {
-                ValueOrPlace::Value(self.handle_constant(constant, state))
-            }
-            Operand::Copy(place) | Operand::Move(place) => {
-                // On move, we would ideally flood the place with bottom. But with the current
-                // framework this is not possible (similar to `InterpCx::eval_operand`).
-                self.map()
-                    .find(place.as_ref())
-                    .map(ValueOrPlace::Place)
-                    .unwrap_or(ValueOrPlace::TOP)
-            }
-        }
-    }
-
-    fn handle_constant(
-        &self,
-        constant: &ConstOperand<'tcx>,
-        state: &mut State<Self::Value>,
-    ) -> Self::Value {
-        self.super_constant(constant, state)
-    }
-
-    fn super_constant(
-        &self,
-        _constant: &ConstOperand<'tcx>,
-        _state: &mut State<Self::Value>,
-    ) -> Self::Value {
-        Self::Value::TOP
-    }
-
-    /// The effect of a successful function call return should not be
-    /// applied here, see [`Analysis::apply_terminator_effect`].
-    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<'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`.
-            }
-            TerminatorKind::Drop { place, .. } => {
-                state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM);
-            }
-            TerminatorKind::Yield { .. } => {
-                // 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::TailCall { .. } => {
-                // FIXME(explicit_tail_calls): determine if we need to do something here (probably not)
-            }
-            TerminatorKind::Goto { .. }
-            | TerminatorKind::UnwindResume
-            | TerminatorKind::UnwindTerminate(_)
-            | TerminatorKind::Return
-            | TerminatorKind::Unreachable
-            | TerminatorKind::Assert { .. }
-            | TerminatorKind::CoroutineDrop
-            | TerminatorKind::FalseEdge { .. }
-            | TerminatorKind::FalseUnwind { .. } => {
-                // These terminators have no effect on the analysis.
-            }
-        }
-        terminator.edges()
-    }
-
-    fn handle_call_return(
-        &self,
-        return_places: CallReturnPlaces<'_, 'tcx>,
-        state: &mut State<Self::Value>,
-    ) {
-        self.super_call_return(return_places, state)
-    }
-
-    fn super_call_return(
-        &self,
-        return_places: CallReturnPlaces<'_, 'tcx>,
-        state: &mut State<Self::Value>,
-    ) {
-        return_places.for_each(|place| {
-            state.flood(place.as_ref(), self.map());
-        })
-    }
-
-    fn handle_switch_int<'mir>(
-        &self,
-        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<'mir>(
-        &self,
-        discr: &'mir Operand<'tcx>,
-        targets: &'mir SwitchTargets,
-        _state: &mut State<Self::Value>,
-    ) -> TerminatorEdges<'mir, 'tcx> {
-        TerminatorEdges::SwitchInt { discr, targets }
-    }
-
-    fn wrap(self) -> ValueAnalysisWrapper<Self>
-    where
-        Self: Sized,
-    {
-        ValueAnalysisWrapper(self)
-    }
-}
-
-pub struct ValueAnalysisWrapper<T>(pub T);
-
-impl<'tcx, T: ValueAnalysis<'tcx>> Analysis<'tcx> for ValueAnalysisWrapper<T> {
-    type Domain = State<T::Value>;
-
-    const NAME: &'static str = T::NAME;
-
-    fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
-        State::Unreachable
-    }
-
-    fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
-        // The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥.
-        assert_matches!(state, State::Unreachable);
-        *state = State::new_reachable();
-        for arg in body.args_iter() {
-            state.flood(PlaceRef { local: arg, projection: &[] }, self.0.map());
-        }
-    }
-
-    fn apply_statement_effect(
-        &mut self,
-        state: &mut Self::Domain,
-        statement: &Statement<'tcx>,
-        _location: Location,
-    ) {
-        if state.is_reachable() {
-            self.0.handle_statement(statement, state);
-        }
-    }
-
-    fn apply_terminator_effect<'mir>(
-        &mut self,
-        state: &mut Self::Domain,
-        terminator: &'mir Terminator<'tcx>,
-        _location: Location,
-    ) -> TerminatorEdges<'mir, 'tcx> {
-        if state.is_reachable() {
-            self.0.handle_terminator(terminator, state)
-        } else {
-            TerminatorEdges::None
-        }
-    }
-
-    fn apply_call_return_effect(
-        &mut self,
-        state: &mut Self::Domain,
-        _block: BasicBlock,
-        return_places: CallReturnPlaces<'_, 'tcx>,
-    ) {
-        if state.is_reachable() {
-            self.0.handle_call_return(return_places, state)
-        }
-    }
-
-    fn apply_switch_int_edge_effects(
-        &mut self,
-        _block: BasicBlock,
-        _discr: &Operand<'tcx>,
-        _apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
-    ) {
-    }
-}
 
 rustc_index::newtype_index!(
     /// This index uniquely identifies a place.
@@ -464,7 +84,7 @@ impl<V: JoinSemiLattice + Clone + HasBottom> JoinSemiLattice for StateData<V> {
     }
 }
 
-/// The dataflow state for an instance of [`ValueAnalysis`].
+/// Dataflow state.
 ///
 /// Every instance specifies a lattice that represents the possible values of a single tracked
 /// place. If we call this lattice `V` and set of tracked places `P`, then a [`State`] is an
@@ -514,7 +134,7 @@ impl<V: Clone + HasBottom> State<V> {
         }
     }
 
-    fn is_reachable(&self) -> bool {
+    pub fn is_reachable(&self) -> bool {
         matches!(self, State::Reachable(_))
     }
 
@@ -1317,34 +937,6 @@ pub fn excluded_locals(body: &Body<'_>) -> BitSet<Local> {
     collector.result
 }
 
-/// This is used to visualize the dataflow analysis.
-impl<'tcx, T> DebugWithContext<ValueAnalysisWrapper<T>> for State<T::Value>
-where
-    T: ValueAnalysis<'tcx>,
-    T::Value: Debug,
-{
-    fn fmt_with(&self, ctxt: &ValueAnalysisWrapper<T>, f: &mut Formatter<'_>) -> std::fmt::Result {
-        match self {
-            State::Reachable(values) => debug_with_context(values, None, ctxt.0.map(), f),
-            State::Unreachable => write!(f, "unreachable"),
-        }
-    }
-
-    fn fmt_diff_with(
-        &self,
-        old: &Self,
-        ctxt: &ValueAnalysisWrapper<T>,
-        f: &mut Formatter<'_>,
-    ) -> std::fmt::Result {
-        match (self, old) {
-            (State::Reachable(this), State::Reachable(old)) => {
-                debug_with_context(this, Some(old), ctxt.0.map(), f)
-            }
-            _ => Ok(()), // Consider printing something here.
-        }
-    }
-}
-
 fn debug_with_context_rec<V: Debug + Eq + HasBottom>(
     place: PlaceIndex,
     place_str: &str,
@@ -1391,7 +983,7 @@ fn debug_with_context_rec<V: Debug + Eq + HasBottom>(
     Ok(())
 }
 
-fn debug_with_context<V: Debug + Eq + HasBottom>(
+pub fn debug_with_context<V: Debug + Eq + HasBottom>(
     new: &StateData<V>,
     old: Option<&StateData<V>>,
     map: &Map<'_>,