Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a8a1318

Browse files
authoredOct 30, 2024··
Rollup merge of #132347 - nnethercote:rm-ValueAnalysisWrapper, r=cjgillot
Remove `ValueAnalysis` and `ValueAnalysisWrapper`. They represent a lot of abstraction and indirection, but they're only used for `ConstAnalysis`, and apparently won't be used for any other analyses in the future. This commit inlines and removes them, which makes `ConstAnalysis` easier to read and understand. r? `@cjgillot`
2 parents 4b38c21 + edef792 commit a8a1318

File tree

2 files changed

+281
-474
lines changed

2 files changed

+281
-474
lines changed
 

‎compiler/rustc_mir_dataflow/src/value_analysis.rs

Lines changed: 4 additions & 412 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,3 @@
1-
//! This module provides a framework on top of the normal MIR dataflow framework to simplify the
2-
//! implementation of analyses that track information about the values stored in certain places.
3-
//! We are using the term "place" here to refer to a `mir::Place` (a place expression) instead of
4-
//! an `interpret::Place` (a memory location).
5-
//!
6-
//! The default methods of [`ValueAnalysis`] (prefixed with `super_` instead of `handle_`)
7-
//! provide some behavior that should be valid for all abstract domains that are based only on the
8-
//! value stored in a certain place. On top of these default rules, an implementation should
9-
//! override some of the `handle_` methods. For an example, see `ConstAnalysis`.
10-
//!
11-
//! An implementation must also provide a [`Map`]. Before the analysis begins, all places that
12-
//! should be tracked during the analysis must be registered. During the analysis, no new places
13-
//! can be registered. The [`State`] can be queried to retrieve the abstract value stored for a
14-
//! certain place by passing the map.
15-
//!
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.
20-
//!
21-
//!
22-
//! # Notes
23-
//!
24-
//! - The bottom state denotes uninitialized memory. Because we are only doing a sound approximation
25-
//! of the actual execution, we can also use this state for places where access would be UB.
26-
//!
27-
//! - The assignment logic in `State::insert_place_idx` assumes that the places are non-overlapping,
28-
//! or identical. Note that this refers to place expressions, not memory locations.
29-
//!
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.
34-
35-
use std::assert_matches::assert_matches;
361
use std::fmt::{Debug, Formatter};
372
use std::ops::Range;
383

@@ -42,359 +7,14 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexSet, StdEntry};
427
use rustc_data_structures::stack::ensure_sufficient_stack;
438
use rustc_index::IndexVec;
449
use rustc_index::bit_set::BitSet;
45-
use rustc_middle::bug;
4610
use rustc_middle::mir::tcx::PlaceTy;
4711
use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
4812
use rustc_middle::mir::*;
4913
use rustc_middle::ty::{self, Ty, TyCtxt};
5014
use tracing::debug;
5115

52-
use crate::fmt::DebugWithContext;
16+
use crate::JoinSemiLattice;
5317
use crate::lattice::{HasBottom, HasTop};
54-
use crate::{Analysis, JoinSemiLattice, SwitchIntEdgeEffects};
55-
56-
pub trait ValueAnalysis<'tcx> {
57-
/// For each place of interest, the analysis tracks a value of the given type.
58-
type Value: Clone + JoinSemiLattice + HasBottom + HasTop + Debug;
59-
60-
const NAME: &'static str;
61-
62-
fn map(&self) -> &Map<'tcx>;
63-
64-
fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
65-
self.super_statement(statement, state)
66-
}
67-
68-
fn super_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
69-
match &statement.kind {
70-
StatementKind::Assign(box (place, rvalue)) => {
71-
self.handle_assign(*place, rvalue, state);
72-
}
73-
StatementKind::SetDiscriminant { box place, variant_index } => {
74-
self.handle_set_discriminant(*place, *variant_index, state);
75-
}
76-
StatementKind::Intrinsic(box intrinsic) => {
77-
self.handle_intrinsic(intrinsic, state);
78-
}
79-
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
80-
// StorageLive leaves the local in an uninitialized state.
81-
// StorageDead makes it UB to access the local afterwards.
82-
state.flood_with(Place::from(*local).as_ref(), self.map(), Self::Value::BOTTOM);
83-
}
84-
StatementKind::Deinit(box place) => {
85-
// Deinit makes the place uninitialized.
86-
state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM);
87-
}
88-
StatementKind::Retag(..) => {
89-
// We don't track references.
90-
}
91-
StatementKind::ConstEvalCounter
92-
| StatementKind::Nop
93-
| StatementKind::FakeRead(..)
94-
| StatementKind::PlaceMention(..)
95-
| StatementKind::Coverage(..)
96-
| StatementKind::AscribeUserType(..) => (),
97-
}
98-
}
99-
100-
fn handle_set_discriminant(
101-
&self,
102-
place: Place<'tcx>,
103-
variant_index: VariantIdx,
104-
state: &mut State<Self::Value>,
105-
) {
106-
self.super_set_discriminant(place, variant_index, state)
107-
}
108-
109-
fn super_set_discriminant(
110-
&self,
111-
place: Place<'tcx>,
112-
_variant_index: VariantIdx,
113-
state: &mut State<Self::Value>,
114-
) {
115-
state.flood_discr(place.as_ref(), self.map());
116-
}
117-
118-
fn handle_intrinsic(
119-
&self,
120-
intrinsic: &NonDivergingIntrinsic<'tcx>,
121-
state: &mut State<Self::Value>,
122-
) {
123-
self.super_intrinsic(intrinsic, state);
124-
}
125-
126-
fn super_intrinsic(
127-
&self,
128-
intrinsic: &NonDivergingIntrinsic<'tcx>,
129-
_state: &mut State<Self::Value>,
130-
) {
131-
match intrinsic {
132-
NonDivergingIntrinsic::Assume(..) => {
133-
// Could use this, but ignoring it is sound.
134-
}
135-
NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
136-
dst: _,
137-
src: _,
138-
count: _,
139-
}) => {
140-
// This statement represents `*dst = *src`, `count` times.
141-
}
142-
}
143-
}
144-
145-
fn handle_assign(
146-
&self,
147-
target: Place<'tcx>,
148-
rvalue: &Rvalue<'tcx>,
149-
state: &mut State<Self::Value>,
150-
) {
151-
self.super_assign(target, rvalue, state)
152-
}
153-
154-
fn super_assign(
155-
&self,
156-
target: Place<'tcx>,
157-
rvalue: &Rvalue<'tcx>,
158-
state: &mut State<Self::Value>,
159-
) {
160-
let result = self.handle_rvalue(rvalue, state);
161-
state.assign(target.as_ref(), result, self.map());
162-
}
163-
164-
fn handle_rvalue(
165-
&self,
166-
rvalue: &Rvalue<'tcx>,
167-
state: &mut State<Self::Value>,
168-
) -> ValueOrPlace<Self::Value> {
169-
self.super_rvalue(rvalue, state)
170-
}
171-
172-
fn super_rvalue(
173-
&self,
174-
rvalue: &Rvalue<'tcx>,
175-
state: &mut State<Self::Value>,
176-
) -> ValueOrPlace<Self::Value> {
177-
match rvalue {
178-
Rvalue::Use(operand) => self.handle_operand(operand, state),
179-
Rvalue::CopyForDeref(place) => self.handle_operand(&Operand::Copy(*place), state),
180-
Rvalue::Ref(..) | Rvalue::RawPtr(..) => {
181-
// We don't track such places.
182-
ValueOrPlace::TOP
183-
}
184-
Rvalue::Repeat(..)
185-
| Rvalue::ThreadLocalRef(..)
186-
| Rvalue::Len(..)
187-
| Rvalue::Cast(..)
188-
| Rvalue::BinaryOp(..)
189-
| Rvalue::NullaryOp(..)
190-
| Rvalue::UnaryOp(..)
191-
| Rvalue::Discriminant(..)
192-
| Rvalue::Aggregate(..)
193-
| Rvalue::ShallowInitBox(..) => {
194-
// No modification is possible through these r-values.
195-
ValueOrPlace::TOP
196-
}
197-
}
198-
}
199-
200-
fn handle_operand(
201-
&self,
202-
operand: &Operand<'tcx>,
203-
state: &mut State<Self::Value>,
204-
) -> ValueOrPlace<Self::Value> {
205-
self.super_operand(operand, state)
206-
}
207-
208-
fn super_operand(
209-
&self,
210-
operand: &Operand<'tcx>,
211-
state: &mut State<Self::Value>,
212-
) -> ValueOrPlace<Self::Value> {
213-
match operand {
214-
Operand::Constant(box constant) => {
215-
ValueOrPlace::Value(self.handle_constant(constant, state))
216-
}
217-
Operand::Copy(place) | Operand::Move(place) => {
218-
// On move, we would ideally flood the place with bottom. But with the current
219-
// framework this is not possible (similar to `InterpCx::eval_operand`).
220-
self.map()
221-
.find(place.as_ref())
222-
.map(ValueOrPlace::Place)
223-
.unwrap_or(ValueOrPlace::TOP)
224-
}
225-
}
226-
}
227-
228-
fn handle_constant(
229-
&self,
230-
constant: &ConstOperand<'tcx>,
231-
state: &mut State<Self::Value>,
232-
) -> Self::Value {
233-
self.super_constant(constant, state)
234-
}
235-
236-
fn super_constant(
237-
&self,
238-
_constant: &ConstOperand<'tcx>,
239-
_state: &mut State<Self::Value>,
240-
) -> Self::Value {
241-
Self::Value::TOP
242-
}
243-
244-
/// The effect of a successful function call return should not be
245-
/// applied here, see [`Analysis::apply_terminator_effect`].
246-
fn handle_terminator<'mir>(
247-
&self,
248-
terminator: &'mir Terminator<'tcx>,
249-
state: &mut State<Self::Value>,
250-
) -> TerminatorEdges<'mir, 'tcx> {
251-
self.super_terminator(terminator, state)
252-
}
253-
254-
fn super_terminator<'mir>(
255-
&self,
256-
terminator: &'mir Terminator<'tcx>,
257-
state: &mut State<Self::Value>,
258-
) -> TerminatorEdges<'mir, 'tcx> {
259-
match &terminator.kind {
260-
TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
261-
// Effect is applied by `handle_call_return`.
262-
}
263-
TerminatorKind::Drop { place, .. } => {
264-
state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM);
265-
}
266-
TerminatorKind::Yield { .. } => {
267-
// They would have an effect, but are not allowed in this phase.
268-
bug!("encountered disallowed terminator");
269-
}
270-
TerminatorKind::SwitchInt { discr, targets } => {
271-
return self.handle_switch_int(discr, targets, state);
272-
}
273-
TerminatorKind::TailCall { .. } => {
274-
// FIXME(explicit_tail_calls): determine if we need to do something here (probably not)
275-
}
276-
TerminatorKind::Goto { .. }
277-
| TerminatorKind::UnwindResume
278-
| TerminatorKind::UnwindTerminate(_)
279-
| TerminatorKind::Return
280-
| TerminatorKind::Unreachable
281-
| TerminatorKind::Assert { .. }
282-
| TerminatorKind::CoroutineDrop
283-
| TerminatorKind::FalseEdge { .. }
284-
| TerminatorKind::FalseUnwind { .. } => {
285-
// These terminators have no effect on the analysis.
286-
}
287-
}
288-
terminator.edges()
289-
}
290-
291-
fn handle_call_return(
292-
&self,
293-
return_places: CallReturnPlaces<'_, 'tcx>,
294-
state: &mut State<Self::Value>,
295-
) {
296-
self.super_call_return(return_places, state)
297-
}
298-
299-
fn super_call_return(
300-
&self,
301-
return_places: CallReturnPlaces<'_, 'tcx>,
302-
state: &mut State<Self::Value>,
303-
) {
304-
return_places.for_each(|place| {
305-
state.flood(place.as_ref(), self.map());
306-
})
307-
}
308-
309-
fn handle_switch_int<'mir>(
310-
&self,
311-
discr: &'mir Operand<'tcx>,
312-
targets: &'mir SwitchTargets,
313-
state: &mut State<Self::Value>,
314-
) -> TerminatorEdges<'mir, 'tcx> {
315-
self.super_switch_int(discr, targets, state)
316-
}
317-
318-
fn super_switch_int<'mir>(
319-
&self,
320-
discr: &'mir Operand<'tcx>,
321-
targets: &'mir SwitchTargets,
322-
_state: &mut State<Self::Value>,
323-
) -> TerminatorEdges<'mir, 'tcx> {
324-
TerminatorEdges::SwitchInt { discr, targets }
325-
}
326-
327-
fn wrap(self) -> ValueAnalysisWrapper<Self>
328-
where
329-
Self: Sized,
330-
{
331-
ValueAnalysisWrapper(self)
332-
}
333-
}
334-
335-
pub struct ValueAnalysisWrapper<T>(pub T);
336-
337-
impl<'tcx, T: ValueAnalysis<'tcx>> Analysis<'tcx> for ValueAnalysisWrapper<T> {
338-
type Domain = State<T::Value>;
339-
340-
const NAME: &'static str = T::NAME;
341-
342-
fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
343-
State::Unreachable
344-
}
345-
346-
fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
347-
// The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥.
348-
assert_matches!(state, State::Unreachable);
349-
*state = State::new_reachable();
350-
for arg in body.args_iter() {
351-
state.flood(PlaceRef { local: arg, projection: &[] }, self.0.map());
352-
}
353-
}
354-
355-
fn apply_statement_effect(
356-
&mut self,
357-
state: &mut Self::Domain,
358-
statement: &Statement<'tcx>,
359-
_location: Location,
360-
) {
361-
if state.is_reachable() {
362-
self.0.handle_statement(statement, state);
363-
}
364-
}
365-
366-
fn apply_terminator_effect<'mir>(
367-
&mut self,
368-
state: &mut Self::Domain,
369-
terminator: &'mir Terminator<'tcx>,
370-
_location: Location,
371-
) -> TerminatorEdges<'mir, 'tcx> {
372-
if state.is_reachable() {
373-
self.0.handle_terminator(terminator, state)
374-
} else {
375-
TerminatorEdges::None
376-
}
377-
}
378-
379-
fn apply_call_return_effect(
380-
&mut self,
381-
state: &mut Self::Domain,
382-
_block: BasicBlock,
383-
return_places: CallReturnPlaces<'_, 'tcx>,
384-
) {
385-
if state.is_reachable() {
386-
self.0.handle_call_return(return_places, state)
387-
}
388-
}
389-
390-
fn apply_switch_int_edge_effects(
391-
&mut self,
392-
_block: BasicBlock,
393-
_discr: &Operand<'tcx>,
394-
_apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
395-
) {
396-
}
397-
}
39818

39919
rustc_index::newtype_index!(
40020
/// This index uniquely identifies a place.
@@ -464,7 +84,7 @@ impl<V: JoinSemiLattice + Clone + HasBottom> JoinSemiLattice for StateData<V> {
46484
}
46585
}
46686

467-
/// The dataflow state for an instance of [`ValueAnalysis`].
87+
/// Dataflow state.
46888
///
46989
/// Every instance specifies a lattice that represents the possible values of a single tracked
47090
/// 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> {
514134
}
515135
}
516136

517-
fn is_reachable(&self) -> bool {
137+
pub fn is_reachable(&self) -> bool {
518138
matches!(self, State::Reachable(_))
519139
}
520140

@@ -1317,34 +937,6 @@ pub fn excluded_locals(body: &Body<'_>) -> BitSet<Local> {
1317937
collector.result
1318938
}
1319939

1320-
/// This is used to visualize the dataflow analysis.
1321-
impl<'tcx, T> DebugWithContext<ValueAnalysisWrapper<T>> for State<T::Value>
1322-
where
1323-
T: ValueAnalysis<'tcx>,
1324-
T::Value: Debug,
1325-
{
1326-
fn fmt_with(&self, ctxt: &ValueAnalysisWrapper<T>, f: &mut Formatter<'_>) -> std::fmt::Result {
1327-
match self {
1328-
State::Reachable(values) => debug_with_context(values, None, ctxt.0.map(), f),
1329-
State::Unreachable => write!(f, "unreachable"),
1330-
}
1331-
}
1332-
1333-
fn fmt_diff_with(
1334-
&self,
1335-
old: &Self,
1336-
ctxt: &ValueAnalysisWrapper<T>,
1337-
f: &mut Formatter<'_>,
1338-
) -> std::fmt::Result {
1339-
match (self, old) {
1340-
(State::Reachable(this), State::Reachable(old)) => {
1341-
debug_with_context(this, Some(old), ctxt.0.map(), f)
1342-
}
1343-
_ => Ok(()), // Consider printing something here.
1344-
}
1345-
}
1346-
}
1347-
1348940
fn debug_with_context_rec<V: Debug + Eq + HasBottom>(
1349941
place: PlaceIndex,
1350942
place_str: &str,
@@ -1391,7 +983,7 @@ fn debug_with_context_rec<V: Debug + Eq + HasBottom>(
1391983
Ok(())
1392984
}
1393985

1394-
fn debug_with_context<V: Debug + Eq + HasBottom>(
986+
pub fn debug_with_context<V: Debug + Eq + HasBottom>(
1395987
new: &StateData<V>,
1396988
old: Option<&StateData<V>>,
1397989
map: &Map<'_>,

‎compiler/rustc_mir_transform/src/dataflow_const_prop.rs

Lines changed: 277 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
//!
33
//! Currently, this pass only propagates scalar values.
44
5+
use std::assert_matches::assert_matches;
6+
use std::fmt::Formatter;
7+
58
use rustc_const_eval::const_eval::{DummyMachine, throw_machine_stop_str};
69
use rustc_const_eval::interpret::{
710
ImmTy, Immediate, InterpCx, OpTy, PlaceTy, Projectable, interp_ok,
@@ -14,9 +17,10 @@ use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
1417
use rustc_middle::mir::*;
1518
use rustc_middle::ty::layout::{HasParamEnv, LayoutOf};
1619
use rustc_middle::ty::{self, Ty, TyCtxt};
17-
use rustc_mir_dataflow::lattice::FlatSet;
20+
use rustc_mir_dataflow::fmt::DebugWithContext;
21+
use rustc_mir_dataflow::lattice::{FlatSet, HasBottom};
1822
use rustc_mir_dataflow::value_analysis::{
19-
Map, PlaceIndex, State, TrackElem, ValueAnalysis, ValueAnalysisWrapper, ValueOrPlace,
23+
Map, PlaceIndex, State, TrackElem, ValueOrPlace, debug_with_context,
2024
};
2125
use rustc_mir_dataflow::{Analysis, Results, ResultsVisitor};
2226
use rustc_span::DUMMY_SP;
@@ -58,8 +62,8 @@ impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp {
5862

5963
// Perform the actual dataflow analysis.
6064
let analysis = ConstAnalysis::new(tcx, body, map);
61-
let mut results = debug_span!("analyze")
62-
.in_scope(|| analysis.wrap().iterate_to_fixpoint(tcx, body, None));
65+
let mut results =
66+
debug_span!("analyze").in_scope(|| analysis.iterate_to_fixpoint(tcx, body, None));
6367

6468
// Collect results and patch the body afterwards.
6569
let mut visitor = Collector::new(tcx, &body.local_decls);
@@ -69,6 +73,10 @@ impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp {
6973
}
7074
}
7175

76+
// Note: Currently, places that have their reference taken cannot be tracked. Although this would
77+
// be possible, it has to rely on some aliasing model, which we are not ready to commit to yet.
78+
// Because of that, we can assume that the only way to change the value behind a tracked place is
79+
// by direct assignment.
7280
struct ConstAnalysis<'a, 'tcx> {
7381
map: Map<'tcx>,
7482
tcx: TyCtxt<'tcx>,
@@ -77,20 +85,198 @@ struct ConstAnalysis<'a, 'tcx> {
7785
param_env: ty::ParamEnv<'tcx>,
7886
}
7987

80-
impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
81-
type Value = FlatSet<Scalar>;
88+
impl<'tcx> Analysis<'tcx> for ConstAnalysis<'_, 'tcx> {
89+
type Domain = State<FlatSet<Scalar>>;
8290

8391
const NAME: &'static str = "ConstAnalysis";
8492

85-
fn map(&self) -> &Map<'tcx> {
86-
&self.map
93+
// The bottom state denotes uninitialized memory. Because we are only doing a sound
94+
// approximation of the actual execution, we can also use this state for places where access
95+
// would be UB.
96+
fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
97+
State::Unreachable
98+
}
99+
100+
fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
101+
// The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥.
102+
assert_matches!(state, State::Unreachable);
103+
*state = State::new_reachable();
104+
for arg in body.args_iter() {
105+
state.flood(PlaceRef { local: arg, projection: &[] }, &self.map);
106+
}
107+
}
108+
109+
fn apply_statement_effect(
110+
&mut self,
111+
state: &mut Self::Domain,
112+
statement: &Statement<'tcx>,
113+
_location: Location,
114+
) {
115+
if state.is_reachable() {
116+
self.handle_statement(statement, state);
117+
}
118+
}
119+
120+
fn apply_terminator_effect<'mir>(
121+
&mut self,
122+
state: &mut Self::Domain,
123+
terminator: &'mir Terminator<'tcx>,
124+
_location: Location,
125+
) -> TerminatorEdges<'mir, 'tcx> {
126+
if state.is_reachable() {
127+
self.handle_terminator(terminator, state)
128+
} else {
129+
TerminatorEdges::None
130+
}
131+
}
132+
133+
fn apply_call_return_effect(
134+
&mut self,
135+
state: &mut Self::Domain,
136+
_block: BasicBlock,
137+
return_places: CallReturnPlaces<'_, 'tcx>,
138+
) {
139+
if state.is_reachable() {
140+
self.handle_call_return(return_places, state)
141+
}
142+
}
143+
}
144+
145+
impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
146+
fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map<'tcx>) -> Self {
147+
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
148+
Self {
149+
map,
150+
tcx,
151+
local_decls: &body.local_decls,
152+
ecx: InterpCx::new(tcx, DUMMY_SP, param_env, DummyMachine),
153+
param_env,
154+
}
155+
}
156+
157+
fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<FlatSet<Scalar>>) {
158+
match &statement.kind {
159+
StatementKind::Assign(box (place, rvalue)) => {
160+
self.handle_assign(*place, rvalue, state);
161+
}
162+
StatementKind::SetDiscriminant { box place, variant_index } => {
163+
self.handle_set_discriminant(*place, *variant_index, state);
164+
}
165+
StatementKind::Intrinsic(box intrinsic) => {
166+
self.handle_intrinsic(intrinsic);
167+
}
168+
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
169+
// StorageLive leaves the local in an uninitialized state.
170+
// StorageDead makes it UB to access the local afterwards.
171+
state.flood_with(
172+
Place::from(*local).as_ref(),
173+
&self.map,
174+
FlatSet::<Scalar>::BOTTOM,
175+
);
176+
}
177+
StatementKind::Deinit(box place) => {
178+
// Deinit makes the place uninitialized.
179+
state.flood_with(place.as_ref(), &self.map, FlatSet::<Scalar>::BOTTOM);
180+
}
181+
StatementKind::Retag(..) => {
182+
// We don't track references.
183+
}
184+
StatementKind::ConstEvalCounter
185+
| StatementKind::Nop
186+
| StatementKind::FakeRead(..)
187+
| StatementKind::PlaceMention(..)
188+
| StatementKind::Coverage(..)
189+
| StatementKind::AscribeUserType(..) => (),
190+
}
191+
}
192+
193+
fn handle_intrinsic(&self, intrinsic: &NonDivergingIntrinsic<'tcx>) {
194+
match intrinsic {
195+
NonDivergingIntrinsic::Assume(..) => {
196+
// Could use this, but ignoring it is sound.
197+
}
198+
NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
199+
dst: _,
200+
src: _,
201+
count: _,
202+
}) => {
203+
// This statement represents `*dst = *src`, `count` times.
204+
}
205+
}
206+
}
207+
208+
fn handle_operand(
209+
&self,
210+
operand: &Operand<'tcx>,
211+
state: &mut State<FlatSet<Scalar>>,
212+
) -> ValueOrPlace<FlatSet<Scalar>> {
213+
match operand {
214+
Operand::Constant(box constant) => {
215+
ValueOrPlace::Value(self.handle_constant(constant, state))
216+
}
217+
Operand::Copy(place) | Operand::Move(place) => {
218+
// On move, we would ideally flood the place with bottom. But with the current
219+
// framework this is not possible (similar to `InterpCx::eval_operand`).
220+
self.map.find(place.as_ref()).map(ValueOrPlace::Place).unwrap_or(ValueOrPlace::TOP)
221+
}
222+
}
223+
}
224+
225+
/// The effect of a successful function call return should not be
226+
/// applied here, see [`Analysis::apply_terminator_effect`].
227+
fn handle_terminator<'mir>(
228+
&self,
229+
terminator: &'mir Terminator<'tcx>,
230+
state: &mut State<FlatSet<Scalar>>,
231+
) -> TerminatorEdges<'mir, 'tcx> {
232+
match &terminator.kind {
233+
TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
234+
// Effect is applied by `handle_call_return`.
235+
}
236+
TerminatorKind::Drop { place, .. } => {
237+
state.flood_with(place.as_ref(), &self.map, FlatSet::<Scalar>::BOTTOM);
238+
}
239+
TerminatorKind::Yield { .. } => {
240+
// They would have an effect, but are not allowed in this phase.
241+
bug!("encountered disallowed terminator");
242+
}
243+
TerminatorKind::SwitchInt { discr, targets } => {
244+
return self.handle_switch_int(discr, targets, state);
245+
}
246+
TerminatorKind::TailCall { .. } => {
247+
// FIXME(explicit_tail_calls): determine if we need to do something here (probably
248+
// not)
249+
}
250+
TerminatorKind::Goto { .. }
251+
| TerminatorKind::UnwindResume
252+
| TerminatorKind::UnwindTerminate(_)
253+
| TerminatorKind::Return
254+
| TerminatorKind::Unreachable
255+
| TerminatorKind::Assert { .. }
256+
| TerminatorKind::CoroutineDrop
257+
| TerminatorKind::FalseEdge { .. }
258+
| TerminatorKind::FalseUnwind { .. } => {
259+
// These terminators have no effect on the analysis.
260+
}
261+
}
262+
terminator.edges()
263+
}
264+
265+
fn handle_call_return(
266+
&self,
267+
return_places: CallReturnPlaces<'_, 'tcx>,
268+
state: &mut State<FlatSet<Scalar>>,
269+
) {
270+
return_places.for_each(|place| {
271+
state.flood(place.as_ref(), &self.map);
272+
})
87273
}
88274

89275
fn handle_set_discriminant(
90276
&self,
91277
place: Place<'tcx>,
92278
variant_index: VariantIdx,
93-
state: &mut State<Self::Value>,
279+
state: &mut State<FlatSet<Scalar>>,
94280
) {
95281
state.flood_discr(place.as_ref(), &self.map);
96282
if self.map.find_discr(place.as_ref()).is_some() {
@@ -109,27 +295,27 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
109295
&self,
110296
target: Place<'tcx>,
111297
rvalue: &Rvalue<'tcx>,
112-
state: &mut State<Self::Value>,
298+
state: &mut State<FlatSet<Scalar>>,
113299
) {
114300
match rvalue {
115301
Rvalue::Use(operand) => {
116-
state.flood(target.as_ref(), self.map());
302+
state.flood(target.as_ref(), &self.map);
117303
if let Some(target) = self.map.find(target.as_ref()) {
118304
self.assign_operand(state, target, operand);
119305
}
120306
}
121307
Rvalue::CopyForDeref(rhs) => {
122-
state.flood(target.as_ref(), self.map());
308+
state.flood(target.as_ref(), &self.map);
123309
if let Some(target) = self.map.find(target.as_ref()) {
124310
self.assign_operand(state, target, &Operand::Copy(*rhs));
125311
}
126312
}
127313
Rvalue::Aggregate(kind, operands) => {
128314
// If we assign `target = Enum::Variant#0(operand)`,
129315
// we must make sure that all `target as Variant#i` are `Top`.
130-
state.flood(target.as_ref(), self.map());
316+
state.flood(target.as_ref(), &self.map);
131317

132-
let Some(target_idx) = self.map().find(target.as_ref()) else { return };
318+
let Some(target_idx) = self.map.find(target.as_ref()) else { return };
133319

134320
let (variant_target, variant_index) = match **kind {
135321
AggregateKind::Tuple | AggregateKind::Closure(..) => (Some(target_idx), None),
@@ -148,14 +334,14 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
148334
if let Some(variant_target_idx) = variant_target {
149335
for (field_index, operand) in operands.iter_enumerated() {
150336
if let Some(field) =
151-
self.map().apply(variant_target_idx, TrackElem::Field(field_index))
337+
self.map.apply(variant_target_idx, TrackElem::Field(field_index))
152338
{
153339
self.assign_operand(state, field, operand);
154340
}
155341
}
156342
}
157343
if let Some(variant_index) = variant_index
158-
&& let Some(discr_idx) = self.map().apply(target_idx, TrackElem::Discriminant)
344+
&& let Some(discr_idx) = self.map.apply(target_idx, TrackElem::Discriminant)
159345
{
160346
// We are assigning the discriminant as part of an aggregate.
161347
// This discriminant can only alias a variant field's value if the operand
@@ -170,23 +356,23 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
170356
}
171357
Rvalue::BinaryOp(op, box (left, right)) if op.is_overflowing() => {
172358
// Flood everything now, so we can use `insert_value_idx` directly later.
173-
state.flood(target.as_ref(), self.map());
359+
state.flood(target.as_ref(), &self.map);
174360

175-
let Some(target) = self.map().find(target.as_ref()) else { return };
361+
let Some(target) = self.map.find(target.as_ref()) else { return };
176362

177-
let value_target = self.map().apply(target, TrackElem::Field(0_u32.into()));
178-
let overflow_target = self.map().apply(target, TrackElem::Field(1_u32.into()));
363+
let value_target = self.map.apply(target, TrackElem::Field(0_u32.into()));
364+
let overflow_target = self.map.apply(target, TrackElem::Field(1_u32.into()));
179365

180366
if value_target.is_some() || overflow_target.is_some() {
181367
let (val, overflow) = self.binary_op(state, *op, left, right);
182368

183369
if let Some(value_target) = value_target {
184370
// We have flooded `target` earlier.
185-
state.insert_value_idx(value_target, val, self.map());
371+
state.insert_value_idx(value_target, val, &self.map);
186372
}
187373
if let Some(overflow_target) = overflow_target {
188374
// We have flooded `target` earlier.
189-
state.insert_value_idx(overflow_target, overflow, self.map());
375+
state.insert_value_idx(overflow_target, overflow, &self.map);
190376
}
191377
}
192378
}
@@ -196,27 +382,30 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
196382
_,
197383
) => {
198384
let pointer = self.handle_operand(operand, state);
199-
state.assign(target.as_ref(), pointer, self.map());
385+
state.assign(target.as_ref(), pointer, &self.map);
200386

201-
if let Some(target_len) = self.map().find_len(target.as_ref())
387+
if let Some(target_len) = self.map.find_len(target.as_ref())
202388
&& let operand_ty = operand.ty(self.local_decls, self.tcx)
203389
&& let Some(operand_ty) = operand_ty.builtin_deref(true)
204390
&& let ty::Array(_, len) = operand_ty.kind()
205391
&& let Some(len) = Const::Ty(self.tcx.types.usize, *len)
206392
.try_eval_scalar_int(self.tcx, self.param_env)
207393
{
208-
state.insert_value_idx(target_len, FlatSet::Elem(len.into()), self.map());
394+
state.insert_value_idx(target_len, FlatSet::Elem(len.into()), &self.map);
209395
}
210396
}
211-
_ => self.super_assign(target, rvalue, state),
397+
_ => {
398+
let result = self.handle_rvalue(rvalue, state);
399+
state.assign(target.as_ref(), result, &self.map);
400+
}
212401
}
213402
}
214403

215404
fn handle_rvalue(
216405
&self,
217406
rvalue: &Rvalue<'tcx>,
218-
state: &mut State<Self::Value>,
219-
) -> ValueOrPlace<Self::Value> {
407+
state: &mut State<FlatSet<Scalar>>,
408+
) -> ValueOrPlace<FlatSet<Scalar>> {
220409
let val = match rvalue {
221410
Rvalue::Len(place) => {
222411
let place_ty = place.ty(self.local_decls, self.tcx);
@@ -225,7 +414,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
225414
.try_eval_scalar(self.tcx, self.param_env)
226415
.map_or(FlatSet::Top, FlatSet::Elem)
227416
} else if let [ProjectionElem::Deref] = place.projection[..] {
228-
state.get_len(place.local.into(), self.map())
417+
state.get_len(place.local.into(), &self.map)
229418
} else {
230419
FlatSet::Top
231420
}
@@ -296,17 +485,33 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
296485
};
297486
FlatSet::Elem(Scalar::from_target_usize(val, &self.tcx))
298487
}
299-
Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), self.map()),
300-
_ => return self.super_rvalue(rvalue, state),
488+
Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), &self.map),
489+
Rvalue::Use(operand) => return self.handle_operand(operand, state),
490+
Rvalue::CopyForDeref(place) => {
491+
return self.handle_operand(&Operand::Copy(*place), state);
492+
}
493+
Rvalue::Ref(..) | Rvalue::RawPtr(..) => {
494+
// We don't track such places.
495+
return ValueOrPlace::TOP;
496+
}
497+
Rvalue::Repeat(..)
498+
| Rvalue::ThreadLocalRef(..)
499+
| Rvalue::Cast(..)
500+
| Rvalue::BinaryOp(..)
501+
| Rvalue::Aggregate(..)
502+
| Rvalue::ShallowInitBox(..) => {
503+
// No modification is possible through these r-values.
504+
return ValueOrPlace::TOP;
505+
}
301506
};
302507
ValueOrPlace::Value(val)
303508
}
304509

305510
fn handle_constant(
306511
&self,
307512
constant: &ConstOperand<'tcx>,
308-
_state: &mut State<Self::Value>,
309-
) -> Self::Value {
513+
_state: &mut State<FlatSet<Scalar>>,
514+
) -> FlatSet<Scalar> {
310515
constant
311516
.const_
312517
.try_eval_scalar(self.tcx, self.param_env)
@@ -317,11 +522,11 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
317522
&self,
318523
discr: &'mir Operand<'tcx>,
319524
targets: &'mir SwitchTargets,
320-
state: &mut State<Self::Value>,
525+
state: &mut State<FlatSet<Scalar>>,
321526
) -> TerminatorEdges<'mir, 'tcx> {
322527
let value = match self.handle_operand(discr, state) {
323528
ValueOrPlace::Value(value) => value,
324-
ValueOrPlace::Place(place) => state.get_idx(place, self.map()),
529+
ValueOrPlace::Place(place) => state.get_idx(place, &self.map),
325530
};
326531
match value {
327532
// We are branching on uninitialized data, this is UB, treat it as unreachable.
@@ -334,19 +539,6 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
334539
FlatSet::Top => TerminatorEdges::SwitchInt { discr, targets },
335540
}
336541
}
337-
}
338-
339-
impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
340-
fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map<'tcx>) -> Self {
341-
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
342-
Self {
343-
map,
344-
tcx,
345-
local_decls: &body.local_decls,
346-
ecx: InterpCx::new(tcx, DUMMY_SP, param_env, DummyMachine),
347-
param_env,
348-
}
349-
}
350542

351543
/// The caller must have flooded `place`.
352544
fn assign_operand(
@@ -537,16 +729,40 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
537729
}
538730
}
539731

540-
pub(crate) struct Patch<'tcx> {
732+
/// This is used to visualize the dataflow analysis.
733+
impl<'tcx> DebugWithContext<ConstAnalysis<'_, 'tcx>> for State<FlatSet<Scalar>> {
734+
fn fmt_with(&self, ctxt: &ConstAnalysis<'_, 'tcx>, f: &mut Formatter<'_>) -> std::fmt::Result {
735+
match self {
736+
State::Reachable(values) => debug_with_context(values, None, &ctxt.map, f),
737+
State::Unreachable => write!(f, "unreachable"),
738+
}
739+
}
740+
741+
fn fmt_diff_with(
742+
&self,
743+
old: &Self,
744+
ctxt: &ConstAnalysis<'_, 'tcx>,
745+
f: &mut Formatter<'_>,
746+
) -> std::fmt::Result {
747+
match (self, old) {
748+
(State::Reachable(this), State::Reachable(old)) => {
749+
debug_with_context(this, Some(old), &ctxt.map, f)
750+
}
751+
_ => Ok(()), // Consider printing something here.
752+
}
753+
}
754+
}
755+
756+
struct Patch<'tcx> {
541757
tcx: TyCtxt<'tcx>,
542758

543759
/// For a given MIR location, this stores the values of the operands used by that location. In
544760
/// particular, this is before the effect, such that the operands of `_1 = _1 + _2` are
545761
/// properly captured. (This may become UB soon, but it is currently emitted even by safe code.)
546-
pub(crate) before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>,
762+
before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>,
547763

548764
/// Stores the assigned values for assignments where the Rvalue is constant.
549-
pub(crate) assignments: FxHashMap<Location, Const<'tcx>>,
765+
assignments: FxHashMap<Location, Const<'tcx>>,
550766
}
551767

552768
impl<'tcx> Patch<'tcx> {
@@ -725,16 +941,15 @@ fn try_write_constant<'tcx>(
725941
interp_ok(())
726942
}
727943

728-
impl<'mir, 'tcx>
729-
ResultsVisitor<'mir, 'tcx, Results<'tcx, ValueAnalysisWrapper<ConstAnalysis<'_, 'tcx>>>>
944+
impl<'mir, 'tcx> ResultsVisitor<'mir, 'tcx, Results<'tcx, ConstAnalysis<'_, 'tcx>>>
730945
for Collector<'_, 'tcx>
731946
{
732947
type Domain = State<FlatSet<Scalar>>;
733948

734949
#[instrument(level = "trace", skip(self, results, statement))]
735950
fn visit_statement_before_primary_effect(
736951
&mut self,
737-
results: &mut Results<'tcx, ValueAnalysisWrapper<ConstAnalysis<'_, 'tcx>>>,
952+
results: &mut Results<'tcx, ConstAnalysis<'_, 'tcx>>,
738953
state: &Self::Domain,
739954
statement: &'mir Statement<'tcx>,
740955
location: Location,
@@ -744,8 +959,8 @@ impl<'mir, 'tcx>
744959
OperandCollector {
745960
state,
746961
visitor: self,
747-
ecx: &mut results.analysis.0.ecx,
748-
map: &results.analysis.0.map,
962+
ecx: &mut results.analysis.ecx,
963+
map: &results.analysis.map,
749964
}
750965
.visit_rvalue(rvalue, location);
751966
}
@@ -756,7 +971,7 @@ impl<'mir, 'tcx>
756971
#[instrument(level = "trace", skip(self, results, statement))]
757972
fn visit_statement_after_primary_effect(
758973
&mut self,
759-
results: &mut Results<'tcx, ValueAnalysisWrapper<ConstAnalysis<'_, 'tcx>>>,
974+
results: &mut Results<'tcx, ConstAnalysis<'_, 'tcx>>,
760975
state: &Self::Domain,
761976
statement: &'mir Statement<'tcx>,
762977
location: Location,
@@ -767,10 +982,10 @@ impl<'mir, 'tcx>
767982
}
768983
StatementKind::Assign(box (place, _)) => {
769984
if let Some(value) = self.try_make_constant(
770-
&mut results.analysis.0.ecx,
985+
&mut results.analysis.ecx,
771986
place,
772987
state,
773-
&results.analysis.0.map,
988+
&results.analysis.map,
774989
) {
775990
self.patch.assignments.insert(location, value);
776991
}
@@ -781,16 +996,16 @@ impl<'mir, 'tcx>
781996

782997
fn visit_terminator_before_primary_effect(
783998
&mut self,
784-
results: &mut Results<'tcx, ValueAnalysisWrapper<ConstAnalysis<'_, 'tcx>>>,
999+
results: &mut Results<'tcx, ConstAnalysis<'_, 'tcx>>,
7851000
state: &Self::Domain,
7861001
terminator: &'mir Terminator<'tcx>,
7871002
location: Location,
7881003
) {
7891004
OperandCollector {
7901005
state,
7911006
visitor: self,
792-
ecx: &mut results.analysis.0.ecx,
793-
map: &results.analysis.0.map,
1007+
ecx: &mut results.analysis.ecx,
1008+
map: &results.analysis.map,
7941009
}
7951010
.visit_terminator(terminator, location);
7961011
}

0 commit comments

Comments
 (0)
Please sign in to comment.