about summary refs log tree commit diff
path: root/compiler/rustc_mir_dataflow/src/value_analysis.rs
diff options
context:
space:
mode:
authorJannis Christopher Köhl <mail@koehl.dev>2022-11-09 18:03:30 +0100
committerJannis Christopher Köhl <mail@koehl.dev>2022-11-09 18:03:30 +0100
commitbfbca6c75c1502b14ffda12afa2b688fe42288fc (patch)
tree7821af0d6d87c75d5314f2ecfd07c59b2d22014f /compiler/rustc_mir_dataflow/src/value_analysis.rs
parent3997893ccb56dde4671cf7213faf9581714da402 (diff)
downloadrust-bfbca6c75c1502b14ffda12afa2b688fe42288fc.tar.gz
rust-bfbca6c75c1502b14ffda12afa2b688fe42288fc.zip
Completely remove tracking of references for now
Diffstat (limited to 'compiler/rustc_mir_dataflow/src/value_analysis.rs')
-rw-r--r--compiler/rustc_mir_dataflow/src/value_analysis.rs172
1 files changed, 37 insertions, 135 deletions
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
index 3472f2a51d2..f428cd5ee82 100644
--- a/compiler/rustc_mir_dataflow/src/value_analysis.rs
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -13,11 +13,10 @@
 //! 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. In particular, the features related to references are
-//! currently guarded behind `-Zunsound-mir-opts`, because their correctness relies on Stacked
-//! Borrows. Also, only places with scalar types can be tracked currently. This is because scalar
-//! types are indivisible, which simplifies the current implementation. But this limitation could be
-//! lifted in the future.
+//! 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
@@ -28,29 +27,17 @@
 //! - The assignment logic in `State::assign_place_idx` assumes that the places are non-overlapping,
 //! or identical. Note that this refers to place expressions, not memory locations.
 //!
-//! - Since pointers (and mutable references) are not tracked, but can be used to change the
-//! underlying values, we are conservative and immediately flood the referenced place upon creation
-//! of the pointer. Also, we have to uphold the invariant that the place must stay that way as long
-//! as this mutable access could exist. However...
-//!
-//! - Without an aliasing model like Stacked Borrows (i.e., `-Zunsound-mir-opts` is not given),
-//! such mutable access is never revoked. And even shared references could be used to obtain the
-//! address of a value an modify it. When not assuming Stacked Borrows, we prevent such places from
-//! being tracked at all. This means that the analysis itself can assume that writes to a *tracked*
-//! place always invalidate all other means of mutable access, regardless of the aliasing model.
-//!
-//! - Likewise, the analysis itself assumes that if the value of a *tracked* place behind a shared
-//! reference is changed, the reference may not be used to access that value anymore. This is true
-//! for all places if the referenced type is `Freeze` and we assume Stacked Borrows. If we are not
-//! assuming Stacking Borrows (or if the referenced type could be `!Freeze`), we again prevent such
-//! places from being tracked at all, making this assertion trivially true.
+//! - 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::fmt::{Debug, Formatter};
 
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_index::vec::IndexVec;
 use rustc_middle::mir::tcx::PlaceTy;
-use rustc_middle::mir::visit::{PlaceContext, Visitor};
+use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
 use rustc_middle::mir::*;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_target::abi::VariantIdx;
@@ -96,10 +83,7 @@ pub trait ValueAnalysis<'tcx> {
                 state.flood_with(place.as_ref(), self.map(), Self::Value::bottom());
             }
             StatementKind::Retag(..) => {
-                // A retag modifies the provenance of references. Currently references are only
-                // tracked if `-Zunsound-mir-opts` is given, but this might change in the future.
-                // However, it is still unclear how retags should be handled:
-                // https://github.com/rust-lang/rust/pull/101168#discussion_r985304895
+                // We don't track references.
             }
             StatementKind::Nop
             | StatementKind::FakeRead(..)
@@ -156,7 +140,7 @@ pub trait ValueAnalysis<'tcx> {
         &self,
         rvalue: &Rvalue<'tcx>,
         state: &mut State<Self::Value>,
-    ) -> ValueOrPlaceOrRef<Self::Value> {
+    ) -> ValueOrPlace<Self::Value> {
         self.super_rvalue(rvalue, state)
     }
 
@@ -164,21 +148,15 @@ pub trait ValueAnalysis<'tcx> {
         &self,
         rvalue: &Rvalue<'tcx>,
         state: &mut State<Self::Value>,
-    ) -> ValueOrPlaceOrRef<Self::Value> {
+    ) -> ValueOrPlace<Self::Value> {
         match rvalue {
-            Rvalue::Use(operand) => self.handle_operand(operand, state).into(),
-            Rvalue::Ref(_, BorrowKind::Shared, place) => self
-                .map()
-                .find(place.as_ref())
-                .map(ValueOrPlaceOrRef::Ref)
-                .unwrap_or(ValueOrPlaceOrRef::top()),
-            Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
-                // This is not a `&x` reference and could be used for modification.
-                state.flood(place.as_ref(), self.map());
-                ValueOrPlaceOrRef::top()
-            }
+            Rvalue::Use(operand) => self.handle_operand(operand, state),
             Rvalue::CopyForDeref(place) => {
-                self.handle_operand(&Operand::Copy(*place), state).into()
+                self.handle_operand(&Operand::Copy(*place), state)
+            }
+            Rvalue::Ref(..) | Rvalue::AddressOf(..) => {
+                // We don't track such places.
+                ValueOrPlace::top()
             }
             Rvalue::Repeat(..)
             | Rvalue::ThreadLocalRef(..)
@@ -192,7 +170,7 @@ pub trait ValueAnalysis<'tcx> {
             | Rvalue::Aggregate(..)
             | Rvalue::ShallowInitBox(..) => {
                 // No modification is possible through these r-values.
-                ValueOrPlaceOrRef::top()
+                ValueOrPlace::top()
             }
         }
     }
@@ -247,14 +225,13 @@ pub trait ValueAnalysis<'tcx> {
         self.super_terminator(terminator, state)
     }
 
-    fn super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
+    fn super_terminator(&self, terminator: &Terminator<'tcx>, _state: &mut State<Self::Value>) {
         match &terminator.kind {
             TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
                 // Effect is applied by `handle_call_return`.
             }
-            TerminatorKind::Drop { place, .. } => {
-                // Place can still be accessed after drop, and drop has mutable access to it.
-                state.flood(place.as_ref(), self.map());
+            TerminatorKind::Drop { .. } => {
+                // We don't track dropped places.
             }
             TerminatorKind::DropAndReplace { .. } | TerminatorKind::Yield { .. } => {
                 // They would have an effect, but are not allowed in this phase.
@@ -522,7 +499,7 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
         }
     }
 
-    pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlaceOrRef<V>, map: &Map) {
+    pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
         if let Some(target) = map.find(target) {
             self.assign_idx(target, result, map);
         } else {
@@ -530,9 +507,9 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
         }
     }
 
-    pub fn assign_idx(&mut self, target: PlaceIndex, result: ValueOrPlaceOrRef<V>, map: &Map) {
+    pub fn assign_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map) {
         match result {
-            ValueOrPlaceOrRef::Value(value) => {
+            ValueOrPlace::Value(value) => {
                 // First flood the target place in case we also track any projections (although
                 // this scenario is currently not well-supported by the API).
                 self.flood_idx(target, map);
@@ -541,21 +518,7 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
                     values[value_index] = value;
                 }
             }
-            ValueOrPlaceOrRef::Place(source) => self.assign_place_idx(target, source, map),
-            ValueOrPlaceOrRef::Ref(source) => {
-                let StateData::Reachable(values) = &mut self.0 else { return };
-                if let Some(value_index) = map.places[target].value_index {
-                    values[value_index] = V::top();
-                }
-                // Instead of tracking of *where* a reference points to (as in, which memory
-                // location), we track *what* it points to (as in, what do we know about the
-                // target). For an assignment `x = &y`, we thus copy the info of `y` to `*x`.
-                if let Some(target_deref) = map.apply(target, TrackElem::Deref) {
-                    // We know here that `*x` is `Freeze`, because we only track through
-                    // dereferences if the target type is `Freeze`.
-                    self.assign_place_idx(target_deref, source, map);
-                }
-            }
+            ValueOrPlace::Place(source) => self.assign_place_idx(target, source, map),
         }
     }
 
@@ -625,45 +588,27 @@ impl Map {
         filter: impl FnMut(Ty<'tcx>) -> bool,
     ) -> Self {
         let mut map = Self::new();
-
-        // If `-Zunsound-mir-opts` is given, tracking through references, and tracking of places
-        // that have their reference taken is allowed. This would be "unsound" in the sense that
-        // the correctness relies on an aliasing model similar to Stacked Borrows (which is
-        // not yet guaranteed).
-        if tcx.sess.opts.unstable_opts.unsound_mir_opts {
-            // We might want to add additional limitations. If a struct has 10 boxed fields of
-            // itself, there will currently be `10.pow(max_derefs)` tracked places.
-            map.register_with_filter(tcx, body, 2, filter, &FxHashSet::default());
-        } else {
-            map.register_with_filter(tcx, body, 0, filter, &escaped_places(body));
-        }
-
+        map.register_with_filter(tcx, body, filter, &escaped_places(body));
         debug!("registered {} places ({} nodes in total)", map.value_count, map.places.len());
         map
     }
 
-    /// Register all non-excluded places that pass the filter, up to a certain dereference depth.
+    /// Register all non-excluded places that pass the filter.
     fn register_with_filter<'tcx>(
         &mut self,
         tcx: TyCtxt<'tcx>,
         body: &Body<'tcx>,
-        max_derefs: u32,
         mut filter: impl FnMut(Ty<'tcx>) -> bool,
         exclude: &FxHashSet<Place<'tcx>>,
     ) {
-        // This is used to tell whether a type is `Freeze`.
-        let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
-
         let mut projection = Vec::new();
         for (local, decl) in body.local_decls.iter_enumerated() {
             self.register_with_filter_rec(
                 tcx,
-                max_derefs,
                 local,
                 &mut projection,
                 decl.ty,
                 &mut filter,
-                param_env,
                 exclude,
             );
         }
@@ -672,12 +617,10 @@ impl Map {
     fn register_with_filter_rec<'tcx>(
         &mut self,
         tcx: TyCtxt<'tcx>,
-        max_derefs: u32,
         local: Local,
         projection: &mut Vec<PlaceElem<'tcx>>,
         ty: Ty<'tcx>,
         filter: &mut impl FnMut(Ty<'tcx>) -> bool,
-        param_env: ty::ParamEnv<'tcx>,
         exclude: &FxHashSet<Place<'tcx>>,
     ) {
         if exclude.contains(&Place { local, projection: tcx.intern_place_elems(projection) }) {
@@ -689,26 +632,6 @@ impl Map {
             // This might fail if `ty` is not scalar.
             let _ = self.register_with_ty(local, projection, ty);
         }
-
-        if max_derefs > 0 {
-            if let Some(ty::TypeAndMut { ty: deref_ty, .. }) = ty.builtin_deref(false) {
-                // Values behind references can only be tracked if the target is `Freeze`.
-                if deref_ty.is_freeze(tcx, param_env) {
-                    projection.push(PlaceElem::Deref);
-                    self.register_with_filter_rec(
-                        tcx,
-                        max_derefs - 1,
-                        local,
-                        projection,
-                        deref_ty,
-                        filter,
-                        param_env,
-                        exclude,
-                    );
-                    projection.pop();
-                }
-            }
-        }
         iter_fields(ty, tcx, |variant, field, ty| {
             if variant.is_some() {
                 // Downcasts are currently not supported.
@@ -716,7 +639,7 @@ impl Map {
             }
             projection.push(PlaceElem::Field(field, ty));
             self.register_with_filter_rec(
-                tcx, max_derefs, local, projection, ty, filter, param_env, exclude,
+                tcx, local, projection, ty, filter, exclude,
             );
             projection.pop();
         });
@@ -875,7 +798,7 @@ impl<'a> Iterator for Children<'a> {
     }
 }
 
-/// Used as the result of an operand.
+/// Used as the result of an operand or r-value.
 pub enum ValueOrPlace<V> {
     Value(V),
     Place(PlaceIndex),
@@ -887,34 +810,11 @@ impl<V: HasTop> ValueOrPlace<V> {
     }
 }
 
-/// Used as the result of an r-value.
-pub enum ValueOrPlaceOrRef<V> {
-    Value(V),
-    Place(PlaceIndex),
-    Ref(PlaceIndex),
-}
-
-impl<V: HasTop> ValueOrPlaceOrRef<V> {
-    pub fn top() -> Self {
-        ValueOrPlaceOrRef::Value(V::top())
-    }
-}
-
-impl<V> From<ValueOrPlace<V>> for ValueOrPlaceOrRef<V> {
-    fn from(x: ValueOrPlace<V>) -> Self {
-        match x {
-            ValueOrPlace::Value(value) => ValueOrPlaceOrRef::Value(value),
-            ValueOrPlace::Place(place) => ValueOrPlaceOrRef::Place(place),
-        }
-    }
-}
-
 /// The set of projection elements that can be used by a tracked place.
 ///
-/// For now, downcast is not allowed due to aliasing between variants (see #101168).
+/// Although only field projections are currently allowed, this could change in the future.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub enum TrackElem {
-    Deref,
     Field(Field),
 }
 
@@ -923,7 +823,6 @@ impl<V, T> TryFrom<ProjectionElem<V, T>> for TrackElem {
 
     fn try_from(value: ProjectionElem<V, T>) -> Result<Self, Self::Error> {
         match value {
-            ProjectionElem::Deref => Ok(TrackElem::Deref),
             ProjectionElem::Field(field, _) => Ok(TrackElem::Field(field)),
             _ => Err(()),
         }
@@ -962,7 +861,7 @@ fn iter_fields<'tcx>(
 
 /// Returns all places, that have their reference or address taken.
 ///
-/// This includes shared references.
+/// This includes shared references, and also drops and `InlineAsm` out parameters.
 fn escaped_places<'tcx>(body: &Body<'tcx>) -> FxHashSet<Place<'tcx>> {
     struct Collector<'tcx> {
         result: FxHashSet<Place<'tcx>>,
@@ -970,7 +869,11 @@ fn escaped_places<'tcx>(body: &Body<'tcx>) -> FxHashSet<Place<'tcx>> {
 
     impl<'tcx> Visitor<'tcx> for Collector<'tcx> {
         fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
-            if context.is_borrow() || context.is_address_of() {
+            if context.is_borrow()
+                || context.is_address_of()
+                || context.is_drop()
+                || context == PlaceContext::MutatingUse(MutatingUseContext::AsmOutput)
+            {
                 self.result.insert(*place);
             }
         }
@@ -1032,7 +935,6 @@ fn debug_with_context_rec<V: Debug + Eq>(
     for child in map.children(place) {
         let info_elem = map.places[child].proj_elem.unwrap();
         let child_place_str = match info_elem {
-            TrackElem::Deref => format!("*{}", place_str),
             TrackElem::Field(field) => {
                 if place_str.starts_with("*") {
                     format!("({}).{}", place_str, field.index())