1 //! A solver for dataflow problems.
2
3 use crate::errors::{
4 DuplicateValuesFor, PathMustEndInFilename, RequiresAnArgument, UnknownFormatter,
5 };
6 use crate::framework::BitSetExt;
7
8 use std::borrow::Borrow;
9 use std::ffi::OsString;
10 use std::marker::PhantomData;
11 use std::path::PathBuf;
12
13 use rustc_ast as ast;
14 use rustc_data_structures::work_queue::WorkQueue;
15 use rustc_graphviz as dot;
16 use rustc_hir::def_id::DefId;
17 use rustc_index::{Idx, IndexVec};
18 use rustc_middle::mir::{self, traversal, BasicBlock};
19 use rustc_middle::mir::{create_dump_file, dump_enabled};
20 use rustc_middle::ty::print::with_no_trimmed_paths;
21 use rustc_middle::ty::TyCtxt;
22 use rustc_span::symbol::{sym, Symbol};
23
24 use super::fmt::DebugWithContext;
25 use super::graphviz;
26 use super::{
27 visit_results, Analysis, AnalysisDomain, CloneAnalysis, Direction, GenKill, GenKillAnalysis,
28 GenKillSet, JoinSemiLattice, ResultsClonedCursor, ResultsCursor, ResultsRefCursor,
29 ResultsVisitor,
30 };
31
32 pub type EntrySets<'tcx, A> = IndexVec<BasicBlock, <A as AnalysisDomain<'tcx>>::Domain>;
33
34 /// A dataflow analysis that has converged to fixpoint.
35 pub struct Results<'tcx, A, E = EntrySets<'tcx, A>>
36 where
37 A: Analysis<'tcx>,
38 {
39 pub analysis: A,
40 pub(super) entry_sets: E,
41 pub(super) _marker: PhantomData<&'tcx ()>,
42 }
43
44 /// `Results` type with a cloned `Analysis` and borrowed entry sets.
45 pub type ResultsCloned<'res, 'tcx, A> = Results<'tcx, A, &'res EntrySets<'tcx, A>>;
46
47 impl<'tcx, A, E> Results<'tcx, A, E>
48 where
49 A: Analysis<'tcx>,
50 E: Borrow<EntrySets<'tcx, A>>,
51 {
52 /// Creates a `ResultsCursor` that can inspect these `Results`.
into_results_cursor<'mir>( self, body: &'mir mir::Body<'tcx>, ) -> ResultsCursor<'mir, 'tcx, A, Self>53 pub fn into_results_cursor<'mir>(
54 self,
55 body: &'mir mir::Body<'tcx>,
56 ) -> ResultsCursor<'mir, 'tcx, A, Self> {
57 ResultsCursor::new(body, self)
58 }
59
60 /// Gets the dataflow state for the given block.
entry_set_for_block(&self, block: BasicBlock) -> &A::Domain61 pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain {
62 &self.entry_sets.borrow()[block]
63 }
64
visit_with<'mir>( &mut self, body: &'mir mir::Body<'tcx>, blocks: impl IntoIterator<Item = BasicBlock>, vis: &mut impl ResultsVisitor<'mir, 'tcx, Self, FlowState = A::Domain>, )65 pub fn visit_with<'mir>(
66 &mut self,
67 body: &'mir mir::Body<'tcx>,
68 blocks: impl IntoIterator<Item = BasicBlock>,
69 vis: &mut impl ResultsVisitor<'mir, 'tcx, Self, FlowState = A::Domain>,
70 ) {
71 visit_results(body, blocks, self, vis)
72 }
73
visit_reachable_with<'mir>( &mut self, body: &'mir mir::Body<'tcx>, vis: &mut impl ResultsVisitor<'mir, 'tcx, Self, FlowState = A::Domain>, )74 pub fn visit_reachable_with<'mir>(
75 &mut self,
76 body: &'mir mir::Body<'tcx>,
77 vis: &mut impl ResultsVisitor<'mir, 'tcx, Self, FlowState = A::Domain>,
78 ) {
79 let blocks = mir::traversal::reachable(body);
80 visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
81 }
82 }
83 impl<'tcx, A> Results<'tcx, A>
84 where
85 A: Analysis<'tcx>,
86 {
87 /// Creates a `ResultsCursor` that can inspect these `Results`.
as_results_cursor<'a, 'mir>( &'a mut self, body: &'mir mir::Body<'tcx>, ) -> ResultsRefCursor<'a, 'mir, 'tcx, A>88 pub fn as_results_cursor<'a, 'mir>(
89 &'a mut self,
90 body: &'mir mir::Body<'tcx>,
91 ) -> ResultsRefCursor<'a, 'mir, 'tcx, A> {
92 ResultsCursor::new(body, self)
93 }
94 }
95 impl<'tcx, A> Results<'tcx, A>
96 where
97 A: Analysis<'tcx> + CloneAnalysis,
98 {
99 /// Creates a new `Results` type with a cloned `Analysis` and borrowed entry sets.
clone_analysis(&self) -> ResultsCloned<'_, 'tcx, A>100 pub fn clone_analysis(&self) -> ResultsCloned<'_, 'tcx, A> {
101 Results {
102 analysis: self.analysis.clone_analysis(),
103 entry_sets: &self.entry_sets,
104 _marker: PhantomData,
105 }
106 }
107
108 /// Creates a `ResultsCursor` that can inspect these `Results`.
cloned_results_cursor<'mir>( &self, body: &'mir mir::Body<'tcx>, ) -> ResultsClonedCursor<'_, 'mir, 'tcx, A>109 pub fn cloned_results_cursor<'mir>(
110 &self,
111 body: &'mir mir::Body<'tcx>,
112 ) -> ResultsClonedCursor<'_, 'mir, 'tcx, A> {
113 self.clone_analysis().into_results_cursor(body)
114 }
115 }
116 impl<'res, 'tcx, A> Results<'tcx, A, &'res EntrySets<'tcx, A>>
117 where
118 A: Analysis<'tcx> + CloneAnalysis,
119 {
120 /// Creates a new `Results` type with a cloned `Analysis` and borrowed entry sets.
reclone_analysis(&self) -> Self121 pub fn reclone_analysis(&self) -> Self {
122 Results {
123 analysis: self.analysis.clone_analysis(),
124 entry_sets: self.entry_sets,
125 _marker: PhantomData,
126 }
127 }
128 }
129
130 /// A solver for dataflow problems.
131 pub struct Engine<'a, 'tcx, A>
132 where
133 A: Analysis<'tcx>,
134 {
135 tcx: TyCtxt<'tcx>,
136 body: &'a mir::Body<'tcx>,
137 entry_sets: IndexVec<BasicBlock, A::Domain>,
138 pass_name: Option<&'static str>,
139 analysis: A,
140
141 /// Cached, cumulative transfer functions for each block.
142 //
143 // FIXME(ecstaticmorse): This boxed `Fn` trait object is invoked inside a tight loop for
144 // gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
145 // performance in practice. I've tried a few ways to avoid this, but they have downsides. See
146 // the message for the commit that added this FIXME for more information.
147 apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
148 }
149
150 impl<'a, 'tcx, A, D, T> Engine<'a, 'tcx, A>
151 where
152 A: GenKillAnalysis<'tcx, Idx = T, Domain = D>,
153 D: Clone + JoinSemiLattice + GenKill<T> + BitSetExt<T>,
154 T: Idx,
155 {
156 /// Creates a new `Engine` to solve a gen-kill dataflow problem.
new_gen_kill(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, mut analysis: A) -> Self157 pub fn new_gen_kill(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, mut analysis: A) -> Self {
158 // If there are no back-edges in the control-flow graph, we only ever need to apply the
159 // transfer function for each block exactly once (assuming that we process blocks in RPO).
160 //
161 // In this case, there's no need to compute the block transfer functions ahead of time.
162 if !body.basic_blocks.is_cfg_cyclic() {
163 return Self::new(tcx, body, analysis, None);
164 }
165
166 // Otherwise, compute and store the cumulative transfer function for each block.
167
168 let identity = GenKillSet::identity(analysis.bottom_value(body).domain_size());
169 let mut trans_for_block = IndexVec::from_elem(identity, &body.basic_blocks);
170
171 for (block, block_data) in body.basic_blocks.iter_enumerated() {
172 let trans = &mut trans_for_block[block];
173 A::Direction::gen_kill_effects_in_block(&mut analysis, trans, block, block_data);
174 }
175
176 let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
177 trans_for_block[bb].apply(state);
178 });
179
180 Self::new(tcx, body, analysis, Some(apply_trans as Box<_>))
181 }
182 }
183
184 impl<'a, 'tcx, A, D> Engine<'a, 'tcx, A>
185 where
186 A: Analysis<'tcx, Domain = D>,
187 D: Clone + JoinSemiLattice,
188 {
189 /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
190 /// function.
191 ///
192 /// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
193 /// better performance.
new_generic(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self194 pub fn new_generic(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self {
195 Self::new(tcx, body, analysis, None)
196 }
197
new( tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A, apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>, ) -> Self198 fn new(
199 tcx: TyCtxt<'tcx>,
200 body: &'a mir::Body<'tcx>,
201 analysis: A,
202 apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
203 ) -> Self {
204 let bottom_value = analysis.bottom_value(body);
205 let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), &body.basic_blocks);
206 analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
207
208 if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != bottom_value {
209 bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
210 }
211
212 Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_trans_for_block }
213 }
214
215 /// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
216 ///
217 /// Some analyses are run multiple times in the compilation pipeline. Give them a `pass_name`
218 /// to differentiate them. Otherwise, only the results for the latest run will be saved.
pass_name(mut self, name: &'static str) -> Self219 pub fn pass_name(mut self, name: &'static str) -> Self {
220 self.pass_name = Some(name);
221 self
222 }
223
224 /// Computes the fixpoint for this dataflow problem and returns it.
iterate_to_fixpoint(self) -> Results<'tcx, A> where A::Domain: DebugWithContext<A>,225 pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
226 where
227 A::Domain: DebugWithContext<A>,
228 {
229 let Engine {
230 mut analysis,
231 body,
232 mut entry_sets,
233 tcx,
234 apply_trans_for_block,
235 pass_name,
236 ..
237 } = self;
238
239 let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());
240
241 if A::Direction::IS_FORWARD {
242 for (bb, _) in traversal::reverse_postorder(body) {
243 dirty_queue.insert(bb);
244 }
245 } else {
246 // Reverse post-order on the reverse CFG may generate a better iteration order for
247 // backward dataflow analyses, but probably not enough to matter.
248 for (bb, _) in traversal::postorder(body) {
249 dirty_queue.insert(bb);
250 }
251 }
252
253 // `state` is not actually used between iterations;
254 // this is just an optimization to avoid reallocating
255 // every iteration.
256 let mut state = analysis.bottom_value(body);
257 while let Some(bb) = dirty_queue.pop() {
258 let bb_data = &body[bb];
259
260 // Set the state to the entry state of the block.
261 // This is equivalent to `state = entry_sets[bb].clone()`,
262 // but it saves an allocation, thus improving compile times.
263 state.clone_from(&entry_sets[bb]);
264
265 // Apply the block transfer function, using the cached one if it exists.
266 match &apply_trans_for_block {
267 Some(apply) => apply(bb, &mut state),
268 None => {
269 A::Direction::apply_effects_in_block(&mut analysis, &mut state, bb, bb_data)
270 }
271 }
272
273 A::Direction::join_state_into_successors_of(
274 &mut analysis,
275 tcx,
276 body,
277 &mut state,
278 (bb, bb_data),
279 |target: BasicBlock, state: &A::Domain| {
280 let set_changed = entry_sets[target].join(state);
281 if set_changed {
282 dirty_queue.insert(target);
283 }
284 },
285 );
286 }
287
288 let mut results = Results { analysis, entry_sets, _marker: PhantomData };
289
290 if tcx.sess.opts.unstable_opts.dump_mir_dataflow {
291 let res = write_graphviz_results(tcx, &body, &mut results, pass_name);
292 if let Err(e) = res {
293 error!("Failed to write graphviz dataflow results: {}", e);
294 }
295 }
296
297 results
298 }
299 }
300
301 // Graphviz
302
303 /// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
304 /// `rustc_mir` attributes and `-Z dump-mir-dataflow`.
write_graphviz_results<'tcx, A>( tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>, results: &mut Results<'tcx, A>, pass_name: Option<&'static str>, ) -> std::io::Result<()> where A: Analysis<'tcx>, A::Domain: DebugWithContext<A>,305 fn write_graphviz_results<'tcx, A>(
306 tcx: TyCtxt<'tcx>,
307 body: &mir::Body<'tcx>,
308 results: &mut Results<'tcx, A>,
309 pass_name: Option<&'static str>,
310 ) -> std::io::Result<()>
311 where
312 A: Analysis<'tcx>,
313 A::Domain: DebugWithContext<A>,
314 {
315 use std::fs;
316 use std::io::{self, Write};
317
318 let def_id = body.source.def_id();
319 let Ok(attrs) = RustcMirAttrs::parse(tcx, def_id) else {
320 // Invalid `rustc_mir` attrs are reported in `RustcMirAttrs::parse`
321 return Ok(());
322 };
323
324 let mut file = match attrs.output_path(A::NAME) {
325 Some(path) => {
326 debug!("printing dataflow results for {:?} to {}", def_id, path.display());
327 if let Some(parent) = path.parent() {
328 fs::create_dir_all(parent)?;
329 }
330 io::BufWriter::new(fs::File::create(&path)?)
331 }
332
333 None if dump_enabled(tcx, A::NAME, def_id) => {
334 create_dump_file(tcx, ".dot", false, A::NAME, &pass_name.unwrap_or("-----"), body)?
335 }
336
337 _ => return Ok(()),
338 };
339
340 let style = match attrs.formatter {
341 Some(sym::two_phase) => graphviz::OutputStyle::BeforeAndAfter,
342 _ => graphviz::OutputStyle::AfterOnly,
343 };
344
345 let mut buf = Vec::new();
346
347 let graphviz = graphviz::Formatter::new(body, results, style);
348 let mut render_opts =
349 vec![dot::RenderOption::Fontname(tcx.sess.opts.unstable_opts.graphviz_font.clone())];
350 if tcx.sess.opts.unstable_opts.graphviz_dark_mode {
351 render_opts.push(dot::RenderOption::DarkTheme);
352 }
353 with_no_trimmed_paths!(dot::render_opts(&graphviz, &mut buf, &render_opts)?);
354
355 file.write_all(&buf)?;
356
357 Ok(())
358 }
359
360 #[derive(Default)]
361 struct RustcMirAttrs {
362 basename_and_suffix: Option<PathBuf>,
363 formatter: Option<Symbol>,
364 }
365
366 impl RustcMirAttrs {
parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result<Self, ()>367 fn parse(tcx: TyCtxt<'_>, def_id: DefId) -> Result<Self, ()> {
368 let mut result = Ok(());
369 let mut ret = RustcMirAttrs::default();
370
371 let rustc_mir_attrs = tcx
372 .get_attrs(def_id, sym::rustc_mir)
373 .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));
374
375 for attr in rustc_mir_attrs {
376 let attr_result = if attr.has_name(sym::borrowck_graphviz_postflow) {
377 Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| {
378 let path = PathBuf::from(s.to_string());
379 match path.file_name() {
380 Some(_) => Ok(path),
381 None => {
382 tcx.sess.emit_err(PathMustEndInFilename { span: attr.span() });
383 Err(())
384 }
385 }
386 })
387 } else if attr.has_name(sym::borrowck_graphviz_format) {
388 Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s {
389 sym::gen_kill | sym::two_phase => Ok(s),
390 _ => {
391 tcx.sess.emit_err(UnknownFormatter { span: attr.span() });
392 Err(())
393 }
394 })
395 } else {
396 Ok(())
397 };
398
399 result = result.and(attr_result);
400 }
401
402 result.map(|()| ret)
403 }
404
set_field<T>( field: &mut Option<T>, tcx: TyCtxt<'_>, attr: &ast::NestedMetaItem, mapper: impl FnOnce(Symbol) -> Result<T, ()>, ) -> Result<(), ()>405 fn set_field<T>(
406 field: &mut Option<T>,
407 tcx: TyCtxt<'_>,
408 attr: &ast::NestedMetaItem,
409 mapper: impl FnOnce(Symbol) -> Result<T, ()>,
410 ) -> Result<(), ()> {
411 if field.is_some() {
412 tcx.sess.emit_err(DuplicateValuesFor { span: attr.span(), name: attr.name_or_empty() });
413
414 return Err(());
415 }
416
417 if let Some(s) = attr.value_str() {
418 *field = Some(mapper(s)?);
419 Ok(())
420 } else {
421 tcx.sess.emit_err(RequiresAnArgument { span: attr.span(), name: attr.name_or_empty() });
422 Err(())
423 }
424 }
425
426 /// Returns the path where dataflow results should be written, or `None`
427 /// `borrowck_graphviz_postflow` was not specified.
428 ///
429 /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
430 ///
431 /// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
output_path(&self, analysis_name: &str) -> Option<PathBuf>432 fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
433 let mut ret = self.basename_and_suffix.as_ref().cloned()?;
434 let suffix = ret.file_name().unwrap(); // Checked when parsing attrs
435
436 let mut file_name: OsString = analysis_name.into();
437 file_name.push("_");
438 file_name.push(suffix);
439 ret.set_file_name(file_name);
440
441 Some(ret)
442 }
443 }
444