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