]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_mir_dataflow/src/rustc_peek.rs
New upstream version 1.65.0+dfsg1
[rustc.git] / compiler / rustc_mir_dataflow / src / rustc_peek.rs
CommitLineData
dfeec247
XL
1use rustc_span::symbol::sym;
2use rustc_span::Span;
3157f602 3
923072b8 4use rustc_index::bit_set::ChunkedBitSet;
c295e0f8 5use rustc_middle::mir::MirPass;
f9f354fc 6use rustc_middle::mir::{self, Body, Local, Location};
ba9703b0 7use rustc_middle::ty::{self, Ty, TyCtxt};
3157f602 8
f2b60f7d
FG
9use crate::errors::{
10 PeekArgumentNotALocal, PeekArgumentUntracked, PeekBitNotSet, PeekMustBeNotTemporary,
11 PeekMustBePlaceOrRefPlace, StopAfterDataFlowEndedCompilation,
12};
5e7ed085 13use crate::framework::BitSetExt;
c295e0f8 14use crate::impls::{
3c0e092e 15 DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces,
f9f354fc 16};
c295e0f8
XL
17use crate::move_paths::{HasMoveData, MoveData};
18use crate::move_paths::{LookupResult, MovePathIndex};
19use crate::MoveDataParamEnv;
20use crate::{Analysis, JoinSemiLattice, Results, ResultsCursor};
041b39d2 21
041b39d2
XL
22pub struct SanityCheck;
23
a2a8927a 24// FIXME: This should be a `MirLint`, but it needs to be moved back to `rustc_mir_transform` first.
e1599b0c 25impl<'tcx> MirPass<'tcx> for SanityCheck {
29967ef6 26 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
c295e0f8 27 use crate::has_rustc_mir_with;
29967ef6 28 let def_id = body.source.def_id();
48663c56 29 if !tcx.has_attr(def_id, sym::rustc_mir) {
532ac7d7 30 debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
041b39d2
XL
31 return;
32 } else {
532ac7d7 33 debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
041b39d2
XL
34 }
35
041b39d2 36 let param_env = tcx.param_env(def_id);
064997fb 37 let (_, move_data) = MoveData::gather_moves(body, tcx, param_env).unwrap();
74b04a01
XL
38 let mdpe = MoveDataParamEnv { move_data, param_env };
39
04454e1e 40 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {
f9f354fc 41 let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
29967ef6 42 .into_engine(tcx, body)
f9f354fc
XL
43 .iterate_to_fixpoint();
44
04454e1e 45 sanity_check_via_rustc_peek(tcx, body, &flow_inits);
041b39d2 46 }
f9f354fc 47
04454e1e 48 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() {
f9f354fc 49 let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe)
29967ef6 50 .into_engine(tcx, body)
f9f354fc
XL
51 .iterate_to_fixpoint();
52
04454e1e 53 sanity_check_via_rustc_peek(tcx, body, &flow_uninits);
041b39d2 54 }
f9f354fc 55
04454e1e 56 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_definite_init).is_some() {
f9f354fc 57 let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe)
29967ef6 58 .into_engine(tcx, body)
f9f354fc
XL
59 .iterate_to_fixpoint();
60
04454e1e 61 sanity_check_via_rustc_peek(tcx, body, &flow_def_inits);
041b39d2 62 }
f9f354fc 63
04454e1e 64 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() {
29967ef6 65 let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint();
f9f354fc 66
04454e1e 67 sanity_check_via_rustc_peek(tcx, body, &flow_liveness);
f9f354fc
XL
68 }
69
04454e1e 70 if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() {
f2b60f7d 71 tcx.sess.emit_fatal(StopAfterDataFlowEndedCompilation);
041b39d2
XL
72 }
73 }
74}
3157f602
XL
75
76/// This function scans `mir` for all calls to the intrinsic
77/// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
78///
79/// For each such call, determines what the dataflow bit-state is for
80/// the L-value corresponding to `expr`; if the bit-state is a 1, then
81/// that call to `rustc_peek` is ignored by the sanity check. If the
94222f64 82/// bit-state is a 0, then this pass emits an error message saying
3157f602
XL
83/// "rustc_peek: bit not set".
84///
85/// The intention is that one can write unit tests for dataflow by
94222f64 86/// putting code into a UI test and using `rustc_peek` to
3157f602
XL
87/// make observations about the results of dataflow static analyses.
88///
89/// (If there are any calls to `rustc_peek` that do not match the
90/// expression form above, then that emits an error as well, but those
91/// errors are not intended to be used for unit tests.)
74b04a01 92pub fn sanity_check_via_rustc_peek<'tcx, A>(
dc9dc135
XL
93 tcx: TyCtxt<'tcx>,
94 body: &Body<'tcx>,
74b04a01 95 results: &Results<'tcx, A>,
dfeec247 96) where
74b04a01 97 A: RustcPeekAt<'tcx>,
dfeec247 98{
29967ef6 99 let def_id = body.source.def_id();
532ac7d7 100 debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
3157f602 101
74b04a01 102 let mut cursor = ResultsCursor::new(body, results);
e74abb32 103
f2b60f7d 104 let peek_calls = body.basic_blocks.iter_enumerated().filter_map(|(bb, block_data)| {
dfeec247
XL
105 PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
106 });
e74abb32
XL
107
108 for (bb, block_data, call) in peek_calls {
109 // Look for a sequence like the following to indicate that we should be peeking at `_1`:
110 // _2 = &_1;
111 // rustc_peek(_2);
112 //
113 // /* or */
114 //
115 // _2 = _1;
116 // rustc_peek(_2);
117 let (statement_index, peek_rval) = block_data
118 .statements
119 .iter()
120 .enumerate()
f9f354fc 121 .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
dfeec247
XL
122 .expect(
123 "call to rustc_peek should be preceded by \
124 assignment to temporary holding its argument",
125 );
e74abb32
XL
126
127 match (call.kind, peek_rval) {
dfeec247 128 (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
ba9703b0
XL
129 | (
130 PeekCallKind::ByVal,
131 mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
132 ) => {
e74abb32 133 let loc = Location { block: bb, statement_index };
f9f354fc 134 cursor.seek_before_primary_effect(loc);
e74abb32 135 let state = cursor.get();
ba9703b0 136 results.analysis.peek_at(tcx, *place, state, call);
e74abb32
XL
137 }
138
139 _ => {
f2b60f7d 140 tcx.sess.emit_err(PeekMustBePlaceOrRefPlace { span: call.span });
e74abb32
XL
141 }
142 }
3157f602
XL
143 }
144}
145
e74abb32
XL
146/// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
147/// RHS of the assignment.
148fn value_assigned_to_local<'a, 'tcx>(
149 stmt: &'a mir::Statement<'tcx>,
150 local: Local,
151) -> Option<&'a mir::Rvalue<'tcx>> {
152 if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
153 if let Some(l) = place.as_local() {
154 if local == l {
155 return Some(&*rvalue);
3157f602 156 }
e74abb32
XL
157 }
158 }
3157f602 159
e74abb32
XL
160 None
161}
162
163#[derive(Clone, Copy, Debug)]
164enum PeekCallKind {
165 ByVal,
166 ByRef,
167}
168
169impl PeekCallKind {
170 fn from_arg_ty(arg: Ty<'_>) -> Self {
1b1a35ee 171 match arg.kind() {
e74abb32
XL
172 ty::Ref(_, _, _) => PeekCallKind::ByRef,
173 _ => PeekCallKind::ByVal,
174 }
175 }
176}
177
178#[derive(Clone, Copy, Debug)]
179pub struct PeekCall {
180 arg: Local,
181 kind: PeekCallKind,
182 span: Span,
183}
184
185impl PeekCall {
186 fn from_terminator<'tcx>(
187 tcx: TyCtxt<'tcx>,
188 terminator: &mir::Terminator<'tcx>,
189 ) -> Option<Self> {
190 use mir::Operand;
191
192 let span = terminator.source_info.span;
193 if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
194 &terminator.kind
195 {
6a06907d 196 if let ty::FnDef(def_id, substs) = *func.literal.ty().kind() {
e74abb32 197 let name = tcx.item_name(def_id);
923072b8 198 if !tcx.is_intrinsic(def_id) || name != sym::rustc_peek {
e74abb32
XL
199 return None;
200 }
201
202 assert_eq!(args.len(), 1);
203 let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
204 let arg = match &args[0] {
205 Operand::Copy(place) | Operand::Move(place) => {
206 if let Some(local) = place.as_local() {
207 local
208 } else {
f2b60f7d 209 tcx.sess.emit_err(PeekMustBeNotTemporary { span });
e74abb32 210 return None;
9e0c209e
SL
211 }
212 }
e74abb32 213 _ => {
f2b60f7d 214 tcx.sess.emit_err(PeekMustBeNotTemporary { span });
e74abb32 215 return None;
9e0c209e 216 }
e74abb32
XL
217 };
218
dfeec247 219 return Some(PeekCall { arg, kind, span });
3157f602
XL
220 }
221 }
222
e74abb32 223 None
3157f602 224 }
e74abb32 225}
3157f602 226
74b04a01 227pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
e74abb32
XL
228 fn peek_at(
229 &self,
230 tcx: TyCtxt<'tcx>,
ba9703b0 231 place: mir::Place<'tcx>,
1b1a35ee 232 flow_state: &Self::Domain,
e74abb32
XL
233 call: PeekCall,
234 );
3157f602
XL
235}
236
1b1a35ee 237impl<'tcx, A, D> RustcPeekAt<'tcx> for A
dfeec247 238where
1b1a35ee 239 A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
5e7ed085 240 D: JoinSemiLattice + Clone + BitSetExt<MovePathIndex>,
e74abb32
XL
241{
242 fn peek_at(
243 &self,
244 tcx: TyCtxt<'tcx>,
ba9703b0 245 place: mir::Place<'tcx>,
1b1a35ee 246 flow_state: &Self::Domain,
e74abb32
XL
247 call: PeekCall,
248 ) {
249 match self.move_data().rev_lookup.find(place.as_ref()) {
250 LookupResult::Exact(peek_mpi) => {
5e7ed085 251 let bit_state = flow_state.contains(peek_mpi);
dfeec247 252 debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
e74abb32 253 if !bit_state {
f2b60f7d 254 tcx.sess.emit_err(PeekBitNotSet { span: call.span });
3157f602
XL
255 }
256 }
e74abb32
XL
257
258 LookupResult::Parent(..) => {
f2b60f7d 259 tcx.sess.emit_err(PeekArgumentUntracked { span: call.span });
e74abb32
XL
260 }
261 }
262 }
263}
264
f9f354fc
XL
265impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
266 fn peek_at(
267 &self,
268 tcx: TyCtxt<'tcx>,
269 place: mir::Place<'tcx>,
923072b8 270 flow_state: &ChunkedBitSet<Local>,
f9f354fc
XL
271 call: PeekCall,
272 ) {
c295e0f8 273 info!(?place, "peek_at");
3c0e092e 274 let Some(local) = place.as_local() else {
f2b60f7d 275 tcx.sess.emit_err(PeekArgumentNotALocal { span: call.span });
f9f354fc
XL
276 return;
277 };
278
279 if !flow_state.contains(local) {
f2b60f7d 280 tcx.sess.emit_err(PeekBitNotSet { span: call.span });
f9f354fc
XL
281 }
282 }
283}