• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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