Skip to content

Commit bfbca6c

Browse files
committed
Completely remove tracking of references for now
1 parent 3997893 commit bfbca6c

28 files changed

+218
-723
lines changed

compiler/rustc_mir_dataflow/src/value_analysis.rs

Lines changed: 37 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
//! can be registered. The [`State`] can be queried to retrieve the abstract value stored for a
1414
//! certain place by passing the map.
1515
//!
16-
//! This framework is currently experimental. In particular, the features related to references are
17-
//! currently guarded behind `-Zunsound-mir-opts`, because their correctness relies on Stacked
18-
//! Borrows. Also, only places with scalar types can be tracked currently. This is because scalar
19-
//! types are indivisible, which simplifies the current implementation. But this limitation could be
20-
//! lifted in the future.
16+
//! This framework is currently experimental. Originally, it supported shared references and enum
17+
//! variants. However, it was discovered that both of these were unsound, and especially references
18+
//! had subtle but serious issues. In the future, they could be added back in, but we should clarify
19+
//! the rules for optimizations that rely on the aliasing model first.
2120
//!
2221
//!
2322
//! # Notes
@@ -28,29 +27,17 @@
2827
//! - The assignment logic in `State::assign_place_idx` assumes that the places are non-overlapping,
2928
//! or identical. Note that this refers to place expressions, not memory locations.
3029
//!
31-
//! - Since pointers (and mutable references) are not tracked, but can be used to change the
32-
//! underlying values, we are conservative and immediately flood the referenced place upon creation
33-
//! of the pointer. Also, we have to uphold the invariant that the place must stay that way as long
34-
//! as this mutable access could exist. However...
35-
//!
36-
//! - Without an aliasing model like Stacked Borrows (i.e., `-Zunsound-mir-opts` is not given),
37-
//! such mutable access is never revoked. And even shared references could be used to obtain the
38-
//! address of a value an modify it. When not assuming Stacked Borrows, we prevent such places from
39-
//! being tracked at all. This means that the analysis itself can assume that writes to a *tracked*
40-
//! place always invalidate all other means of mutable access, regardless of the aliasing model.
41-
//!
42-
//! - Likewise, the analysis itself assumes that if the value of a *tracked* place behind a shared
43-
//! reference is changed, the reference may not be used to access that value anymore. This is true
44-
//! for all places if the referenced type is `Freeze` and we assume Stacked Borrows. If we are not
45-
//! assuming Stacking Borrows (or if the referenced type could be `!Freeze`), we again prevent such
46-
//! places from being tracked at all, making this assertion trivially true.
30+
//! - Currently, places that have their reference taken cannot be tracked. Although this would be
31+
//! possible, it has to rely on some aliasing model, which we are not ready to commit to yet.
32+
//! Because of that, we can assume that the only way to change the value behind a tracked place is
33+
//! by direct assignment.
4734
4835
use std::fmt::{Debug, Formatter};
4936

5037
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
5138
use rustc_index::vec::IndexVec;
5239
use rustc_middle::mir::tcx::PlaceTy;
53-
use rustc_middle::mir::visit::{PlaceContext, Visitor};
40+
use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
5441
use rustc_middle::mir::*;
5542
use rustc_middle::ty::{self, Ty, TyCtxt};
5643
use rustc_target::abi::VariantIdx;
@@ -96,10 +83,7 @@ pub trait ValueAnalysis<'tcx> {
9683
state.flood_with(place.as_ref(), self.map(), Self::Value::bottom());
9784
}
9885
StatementKind::Retag(..) => {
99-
// A retag modifies the provenance of references. Currently references are only
100-
// tracked if `-Zunsound-mir-opts` is given, but this might change in the future.
101-
// However, it is still unclear how retags should be handled:
102-
// https://github.com/rust-lang/rust/pull/101168#discussion_r985304895
86+
// We don't track references.
10387
}
10488
StatementKind::Nop
10589
| StatementKind::FakeRead(..)
@@ -156,29 +140,23 @@ pub trait ValueAnalysis<'tcx> {
156140
&self,
157141
rvalue: &Rvalue<'tcx>,
158142
state: &mut State<Self::Value>,
159-
) -> ValueOrPlaceOrRef<Self::Value> {
143+
) -> ValueOrPlace<Self::Value> {
160144
self.super_rvalue(rvalue, state)
161145
}
162146

163147
fn super_rvalue(
164148
&self,
165149
rvalue: &Rvalue<'tcx>,
166150
state: &mut State<Self::Value>,
167-
) -> ValueOrPlaceOrRef<Self::Value> {
151+
) -> ValueOrPlace<Self::Value> {
168152
match rvalue {
169-
Rvalue::Use(operand) => self.handle_operand(operand, state).into(),
170-
Rvalue::Ref(_, BorrowKind::Shared, place) => self
171-
.map()
172-
.find(place.as_ref())
173-
.map(ValueOrPlaceOrRef::Ref)
174-
.unwrap_or(ValueOrPlaceOrRef::top()),
175-
Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
176-
// This is not a `&x` reference and could be used for modification.
177-
state.flood(place.as_ref(), self.map());
178-
ValueOrPlaceOrRef::top()
179-
}
153+
Rvalue::Use(operand) => self.handle_operand(operand, state),
180154
Rvalue::CopyForDeref(place) => {
181-
self.handle_operand(&Operand::Copy(*place), state).into()
155+
self.handle_operand(&Operand::Copy(*place), state)
156+
}
157+
Rvalue::Ref(..) | Rvalue::AddressOf(..) => {
158+
// We don't track such places.
159+
ValueOrPlace::top()
182160
}
183161
Rvalue::Repeat(..)
184162
| Rvalue::ThreadLocalRef(..)
@@ -192,7 +170,7 @@ pub trait ValueAnalysis<'tcx> {
192170
| Rvalue::Aggregate(..)
193171
| Rvalue::ShallowInitBox(..) => {
194172
// No modification is possible through these r-values.
195-
ValueOrPlaceOrRef::top()
173+
ValueOrPlace::top()
196174
}
197175
}
198176
}
@@ -247,14 +225,13 @@ pub trait ValueAnalysis<'tcx> {
247225
self.super_terminator(terminator, state)
248226
}
249227

250-
fn super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
228+
fn super_terminator(&self, terminator: &Terminator<'tcx>, _state: &mut State<Self::Value>) {
251229
match &terminator.kind {
252230
TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
253231
// Effect is applied by `handle_call_return`.
254232
}
255-
TerminatorKind::Drop { place, .. } => {
256-
// Place can still be accessed after drop, and drop has mutable access to it.
257-
state.flood(place.as_ref(), self.map());
233+
TerminatorKind::Drop { .. } => {
234+
// We don't track dropped places.
258235
}
259236
TerminatorKind::DropAndReplace { .. } | TerminatorKind::Yield { .. } => {
260237
// They would have an effect, but are not allowed in this phase.
@@ -522,17 +499,17 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
522499
}
523500
}
524501

525-
pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlaceOrRef<V>, map: &Map) {
502+
pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
526503
if let Some(target) = map.find(target) {
527504
self.assign_idx(target, result, map);
528505
} else {
529506
// We don't track this place nor any projections, assignment can be ignored.
530507
}
531508
}
532509

533-
pub fn assign_idx(&mut self, target: PlaceIndex, result: ValueOrPlaceOrRef<V>, map: &Map) {
510+
pub fn assign_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map) {
534511
match result {
535-
ValueOrPlaceOrRef::Value(value) => {
512+
ValueOrPlace::Value(value) => {
536513
// First flood the target place in case we also track any projections (although
537514
// this scenario is currently not well-supported by the API).
538515
self.flood_idx(target, map);
@@ -541,21 +518,7 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
541518
values[value_index] = value;
542519
}
543520
}
544-
ValueOrPlaceOrRef::Place(source) => self.assign_place_idx(target, source, map),
545-
ValueOrPlaceOrRef::Ref(source) => {
546-
let StateData::Reachable(values) = &mut self.0 else { return };
547-
if let Some(value_index) = map.places[target].value_index {
548-
values[value_index] = V::top();
549-
}
550-
// Instead of tracking of *where* a reference points to (as in, which memory
551-
// location), we track *what* it points to (as in, what do we know about the
552-
// target). For an assignment `x = &y`, we thus copy the info of `y` to `*x`.
553-
if let Some(target_deref) = map.apply(target, TrackElem::Deref) {
554-
// We know here that `*x` is `Freeze`, because we only track through
555-
// dereferences if the target type is `Freeze`.
556-
self.assign_place_idx(target_deref, source, map);
557-
}
558-
}
521+
ValueOrPlace::Place(source) => self.assign_place_idx(target, source, map),
559522
}
560523
}
561524

@@ -625,45 +588,27 @@ impl Map {
625588
filter: impl FnMut(Ty<'tcx>) -> bool,
626589
) -> Self {
627590
let mut map = Self::new();
628-
629-
// If `-Zunsound-mir-opts` is given, tracking through references, and tracking of places
630-
// that have their reference taken is allowed. This would be "unsound" in the sense that
631-
// the correctness relies on an aliasing model similar to Stacked Borrows (which is
632-
// not yet guaranteed).
633-
if tcx.sess.opts.unstable_opts.unsound_mir_opts {
634-
// We might want to add additional limitations. If a struct has 10 boxed fields of
635-
// itself, there will currently be `10.pow(max_derefs)` tracked places.
636-
map.register_with_filter(tcx, body, 2, filter, &FxHashSet::default());
637-
} else {
638-
map.register_with_filter(tcx, body, 0, filter, &escaped_places(body));
639-
}
640-
591+
map.register_with_filter(tcx, body, filter, &escaped_places(body));
641592
debug!("registered {} places ({} nodes in total)", map.value_count, map.places.len());
642593
map
643594
}
644595

645-
/// Register all non-excluded places that pass the filter, up to a certain dereference depth.
596+
/// Register all non-excluded places that pass the filter.
646597
fn register_with_filter<'tcx>(
647598
&mut self,
648599
tcx: TyCtxt<'tcx>,
649600
body: &Body<'tcx>,
650-
max_derefs: u32,
651601
mut filter: impl FnMut(Ty<'tcx>) -> bool,
652602
exclude: &FxHashSet<Place<'tcx>>,
653603
) {
654-
// This is used to tell whether a type is `Freeze`.
655-
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
656-
657604
let mut projection = Vec::new();
658605
for (local, decl) in body.local_decls.iter_enumerated() {
659606
self.register_with_filter_rec(
660607
tcx,
661-
max_derefs,
662608
local,
663609
&mut projection,
664610
decl.ty,
665611
&mut filter,
666-
param_env,
667612
exclude,
668613
);
669614
}
@@ -672,12 +617,10 @@ impl Map {
672617
fn register_with_filter_rec<'tcx>(
673618
&mut self,
674619
tcx: TyCtxt<'tcx>,
675-
max_derefs: u32,
676620
local: Local,
677621
projection: &mut Vec<PlaceElem<'tcx>>,
678622
ty: Ty<'tcx>,
679623
filter: &mut impl FnMut(Ty<'tcx>) -> bool,
680-
param_env: ty::ParamEnv<'tcx>,
681624
exclude: &FxHashSet<Place<'tcx>>,
682625
) {
683626
if exclude.contains(&Place { local, projection: tcx.intern_place_elems(projection) }) {
@@ -689,34 +632,14 @@ impl Map {
689632
// This might fail if `ty` is not scalar.
690633
let _ = self.register_with_ty(local, projection, ty);
691634
}
692-
693-
if max_derefs > 0 {
694-
if let Some(ty::TypeAndMut { ty: deref_ty, .. }) = ty.builtin_deref(false) {
695-
// Values behind references can only be tracked if the target is `Freeze`.
696-
if deref_ty.is_freeze(tcx, param_env) {
697-
projection.push(PlaceElem::Deref);
698-
self.register_with_filter_rec(
699-
tcx,
700-
max_derefs - 1,
701-
local,
702-
projection,
703-
deref_ty,
704-
filter,
705-
param_env,
706-
exclude,
707-
);
708-
projection.pop();
709-
}
710-
}
711-
}
712635
iter_fields(ty, tcx, |variant, field, ty| {
713636
if variant.is_some() {
714637
// Downcasts are currently not supported.
715638
return;
716639
}
717640
projection.push(PlaceElem::Field(field, ty));
718641
self.register_with_filter_rec(
719-
tcx, max_derefs, local, projection, ty, filter, param_env, exclude,
642+
tcx, local, projection, ty, filter, exclude,
720643
);
721644
projection.pop();
722645
});
@@ -875,7 +798,7 @@ impl<'a> Iterator for Children<'a> {
875798
}
876799
}
877800

878-
/// Used as the result of an operand.
801+
/// Used as the result of an operand or r-value.
879802
pub enum ValueOrPlace<V> {
880803
Value(V),
881804
Place(PlaceIndex),
@@ -887,34 +810,11 @@ impl<V: HasTop> ValueOrPlace<V> {
887810
}
888811
}
889812

890-
/// Used as the result of an r-value.
891-
pub enum ValueOrPlaceOrRef<V> {
892-
Value(V),
893-
Place(PlaceIndex),
894-
Ref(PlaceIndex),
895-
}
896-
897-
impl<V: HasTop> ValueOrPlaceOrRef<V> {
898-
pub fn top() -> Self {
899-
ValueOrPlaceOrRef::Value(V::top())
900-
}
901-
}
902-
903-
impl<V> From<ValueOrPlace<V>> for ValueOrPlaceOrRef<V> {
904-
fn from(x: ValueOrPlace<V>) -> Self {
905-
match x {
906-
ValueOrPlace::Value(value) => ValueOrPlaceOrRef::Value(value),
907-
ValueOrPlace::Place(place) => ValueOrPlaceOrRef::Place(place),
908-
}
909-
}
910-
}
911-
912813
/// The set of projection elements that can be used by a tracked place.
913814
///
914-
/// For now, downcast is not allowed due to aliasing between variants (see #101168).
815+
/// Although only field projections are currently allowed, this could change in the future.
915816
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
916817
pub enum TrackElem {
917-
Deref,
918818
Field(Field),
919819
}
920820

@@ -923,7 +823,6 @@ impl<V, T> TryFrom<ProjectionElem<V, T>> for TrackElem {
923823

924824
fn try_from(value: ProjectionElem<V, T>) -> Result<Self, Self::Error> {
925825
match value {
926-
ProjectionElem::Deref => Ok(TrackElem::Deref),
927826
ProjectionElem::Field(field, _) => Ok(TrackElem::Field(field)),
928827
_ => Err(()),
929828
}
@@ -962,15 +861,19 @@ fn iter_fields<'tcx>(
962861

963862
/// Returns all places, that have their reference or address taken.
964863
///
965-
/// This includes shared references.
864+
/// This includes shared references, and also drops and `InlineAsm` out parameters.
966865
fn escaped_places<'tcx>(body: &Body<'tcx>) -> FxHashSet<Place<'tcx>> {
967866
struct Collector<'tcx> {
968867
result: FxHashSet<Place<'tcx>>,
969868
}
970869

971870
impl<'tcx> Visitor<'tcx> for Collector<'tcx> {
972871
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
973-
if context.is_borrow() || context.is_address_of() {
872+
if context.is_borrow()
873+
|| context.is_address_of()
874+
|| context.is_drop()
875+
|| context == PlaceContext::MutatingUse(MutatingUseContext::AsmOutput)
876+
{
974877
self.result.insert(*place);
975878
}
976879
}
@@ -1032,7 +935,6 @@ fn debug_with_context_rec<V: Debug + Eq>(
1032935
for child in map.children(place) {
1033936
let info_elem = map.places[child].proj_elem.unwrap();
1034937
let child_place_str = match info_elem {
1035-
TrackElem::Deref => format!("*{}", place_str),
1036938
TrackElem::Field(field) => {
1037939
if place_str.starts_with("*") {
1038940
format!("({}).{}", place_str, field.index())

0 commit comments

Comments
 (0)