]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir/src/dataflow/framework/engine.rs
Rollup merge of #77786 - jyn514:rustdoc, r=Mark-Simulacrum
[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::path::PathBuf;
6
7 use rustc_ast as ast;
8 use rustc_data_structures::work_queue::WorkQueue;
9 use rustc_graphviz as dot;
10 use rustc_hir::def_id::DefId;
11 use rustc_index::bit_set::BitSet;
12 use rustc_index::vec::{Idx, IndexVec};
13 use rustc_middle::mir::{self, traversal, BasicBlock};
14 use rustc_middle::ty::TyCtxt;
15 use rustc_span::symbol::{sym, Symbol};
16
17 use super::fmt::DebugWithContext;
18 use super::graphviz;
19 use super::{
20     visit_results, Analysis, Direction, GenKill, GenKillAnalysis, GenKillSet, JoinSemiLattice,
21     ResultsCursor, ResultsVisitor,
22 };
23 use crate::util::pretty::{create_dump_file, dump_enabled};
24
25 /// A dataflow analysis that has converged to fixpoint.
26 pub struct Results<'tcx, A>
27 where
28     A: Analysis<'tcx>,
29 {
30     pub analysis: A,
31     pub(super) entry_sets: IndexVec<BasicBlock, A::Domain>,
32 }
33
34 impl<A> Results<'tcx, A>
35 where
36     A: Analysis<'tcx>,
37 {
38     /// Creates a `ResultsCursor` that can inspect these `Results`.
39     pub fn into_results_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
40         ResultsCursor::new(body, self)
41     }
42
43     /// Gets the dataflow state for the given block.
44     pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain {
45         &self.entry_sets[block]
46     }
47
48     pub fn visit_with(
49         &self,
50         body: &'mir mir::Body<'tcx>,
51         blocks: impl IntoIterator<Item = BasicBlock>,
52         vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
53     ) {
54         visit_results(body, blocks, self, vis)
55     }
56
57     pub fn visit_reachable_with(
58         &self,
59         body: &'mir mir::Body<'tcx>,
60         vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
61     ) {
62         let blocks = mir::traversal::reachable(body);
63         visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
64     }
65
66     pub fn visit_in_rpo_with(
67         &self,
68         body: &'mir mir::Body<'tcx>,
69         vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
70     ) {
71         let blocks = mir::traversal::reverse_postorder(body);
72         visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
73     }
74 }
75
76 /// A solver for dataflow problems.
77 pub struct Engine<'a, 'tcx, A>
78 where
79     A: Analysis<'tcx>,
80 {
81     tcx: TyCtxt<'tcx>,
82     body: &'a mir::Body<'tcx>,
83     dead_unwinds: Option<&'a BitSet<BasicBlock>>,
84     entry_sets: IndexVec<BasicBlock, A::Domain>,
85     pass_name: Option<&'static str>,
86     analysis: A,
87
88     /// Cached, cumulative transfer functions for each block.
89     //
90     // FIXME(ecstaticmorse): This boxed `Fn` trait object is invoked inside a tight loop for
91     // gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
92     // performance in practice. I've tried a few ways to avoid this, but they have downsides. See
93     // the message for the commit that added this FIXME for more information.
94     apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
95 }
96
97 impl<A, D, T> Engine<'a, 'tcx, A>
98 where
99     A: GenKillAnalysis<'tcx, Idx = T, Domain = D>,
100     D: Clone + JoinSemiLattice + GenKill<T> + BorrowMut<BitSet<T>>,
101     T: Idx,
102 {
103     /// Creates a new `Engine` to solve a gen-kill dataflow problem.
104     pub fn new_gen_kill(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self {
105         // If there are no back-edges in the control-flow graph, we only ever need to apply the
106         // transfer function for each block exactly once (assuming that we process blocks in RPO).
107         //
108         // In this case, there's no need to compute the block transfer functions ahead of time.
109         if !body.is_cfg_cyclic() {
110             return Self::new(tcx, body, analysis, None);
111         }
112
113         // Otherwise, compute and store the cumulative transfer function for each block.
114
115         let identity = GenKillSet::identity(analysis.bottom_value(body).borrow().domain_size());
116         let mut trans_for_block = IndexVec::from_elem(identity, body.basic_blocks());
117
118         for (block, block_data) in body.basic_blocks().iter_enumerated() {
119             let trans = &mut trans_for_block[block];
120             A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data);
121         }
122
123         let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
124             trans_for_block[bb].apply(state.borrow_mut());
125         });
126
127         Self::new(tcx, body, analysis, Some(apply_trans as Box<_>))
128     }
129 }
130
131 impl<A, D> Engine<'a, 'tcx, A>
132 where
133     A: Analysis<'tcx, Domain = D>,
134     D: Clone + JoinSemiLattice,
135 {
136     /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
137     /// function.
138     ///
139     /// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
140     /// better performance.
141     pub fn new_generic(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self {
142         Self::new(tcx, body, analysis, None)
143     }
144
145     fn new(
146         tcx: TyCtxt<'tcx>,
147         body: &'a mir::Body<'tcx>,
148         analysis: A,
149         apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
150     ) -> Self {
151         let bottom_value = analysis.bottom_value(body);
152         let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), body.basic_blocks());
153         analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
154
155         if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value {
156             bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
157         }
158
159         Engine {
160             analysis,
161             tcx,
162             body,
163             dead_unwinds: None,
164             pass_name: None,
165             entry_sets,
166             apply_trans_for_block,
167         }
168     }
169
170     /// Signals that we do not want dataflow state to propagate across unwind edges for these
171     /// `BasicBlock`s.
172     ///
173     /// You must take care that `dead_unwinds` does not contain a `BasicBlock` that *can* actually
174     /// unwind during execution. Otherwise, your dataflow results will not be correct.
175     pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
176         self.dead_unwinds = Some(dead_unwinds);
177         self
178     }
179
180     /// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
181     ///
182     /// Some analyses are run multiple times in the compilation pipeline. Give them a `pass_name`
183     /// to differentiate them. Otherwise, only the results for the latest run will be saved.
184     pub fn pass_name(mut self, name: &'static str) -> Self {
185         self.pass_name = Some(name);
186         self
187     }
188
189     /// Computes the fixpoint for this dataflow problem and returns it.
190     pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
191     where
192         A::Domain: DebugWithContext<A>,
193     {
194         let Engine {
195             analysis,
196             body,
197             dead_unwinds,
198             mut entry_sets,
199             tcx,
200             apply_trans_for_block,
201             pass_name,
202             ..
203         } = self;
204
205         let mut dirty_queue: WorkQueue<BasicBlock> =
206             WorkQueue::with_none(body.basic_blocks().len());
207
208         if A::Direction::is_forward() {
209             for (bb, _) in traversal::reverse_postorder(body) {
210                 dirty_queue.insert(bb);
211             }
212         } else {
213             // Reverse post-order on the reverse CFG may generate a better iteration order for
214             // backward dataflow analyses, but probably not enough to matter.
215             for (bb, _) in traversal::postorder(body) {
216                 dirty_queue.insert(bb);
217             }
218         }
219
220         let mut state = analysis.bottom_value(body);
221         while let Some(bb) = dirty_queue.pop() {
222             let bb_data = &body[bb];
223
224             // Apply the block transfer function, using the cached one if it exists.
225             state.clone_from(&entry_sets[bb]);
226             match &apply_trans_for_block {
227                 Some(apply) => apply(bb, &mut state),
228                 None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data),
229             }
230
231             A::Direction::join_state_into_successors_of(
232                 &analysis,
233                 tcx,
234                 body,
235                 dead_unwinds,
236                 &mut state,
237                 (bb, bb_data),
238                 |target: BasicBlock, state: &A::Domain| {
239                     let set_changed = entry_sets[target].join(state);
240                     if set_changed {
241                         dirty_queue.insert(target);
242                     }
243                 },
244             );
245         }
246
247         let results = Results { analysis, entry_sets };
248
249         let res = write_graphviz_results(tcx, &body, &results, pass_name);
250         if let Err(e) = res {
251             error!("Failed to write graphviz dataflow results: {}", e);
252         }
253
254         results
255     }
256 }
257
258 // Graphviz
259
260 /// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
261 /// `rustc_mir` attributes.
262 fn write_graphviz_results<A>(
263     tcx: TyCtxt<'tcx>,
264     body: &mir::Body<'tcx>,
265     results: &Results<'tcx, A>,
266     pass_name: Option<&'static str>,
267 ) -> std::io::Result<()>
268 where
269     A: Analysis<'tcx>,
270     A::Domain: DebugWithContext<A>,
271 {
272     use std::fs;
273     use std::io::{self, Write};
274
275     let def_id = body.source.def_id();
276     let attrs = match RustcMirAttrs::parse(tcx, def_id) {
277         Ok(attrs) => attrs,
278
279         // Invalid `rustc_mir` attrs are reported in `RustcMirAttrs::parse`
280         Err(()) => return Ok(()),
281     };
282
283     let mut file = match attrs.output_path(A::NAME) {
284         Some(path) => {
285             debug!("printing dataflow results for {:?} to {}", def_id, path.display());
286             if let Some(parent) = path.parent() {
287                 fs::create_dir_all(parent)?;
288             }
289             io::BufWriter::new(fs::File::create(&path)?)
290         }
291
292         None if tcx.sess.opts.debugging_opts.dump_mir_dataflow
293             && dump_enabled(tcx, A::NAME, def_id) =>
294         {
295             create_dump_file(
296                 tcx,
297                 ".dot",
298                 None,
299                 A::NAME,
300                 &pass_name.unwrap_or("-----"),
301                 body.source,
302             )?
303         }
304
305         _ => return Ok(()),
306     };
307
308     let style = match attrs.formatter {
309         Some(sym::two_phase) => graphviz::OutputStyle::BeforeAndAfter,
310         _ => graphviz::OutputStyle::AfterOnly,
311     };
312
313     let mut buf = Vec::new();
314
315     let graphviz = graphviz::Formatter::new(body, results, style);
316     let mut render_opts =
317         vec![dot::RenderOption::Fontname(tcx.sess.opts.debugging_opts.graphviz_font.clone())];
318     if tcx.sess.opts.debugging_opts.graphviz_dark_mode {
319         render_opts.push(dot::RenderOption::DarkTheme);
320     }
321     dot::render_opts(&graphviz, &mut buf, &render_opts)?;
322
323     file.write_all(&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 }