]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | //! A helpful diagram for debugging dataflow problems. |
2 | ||
e74abb32 | 3 | use std::cell::RefCell; |
dfeec247 | 4 | use std::{io, ops, str}; |
e74abb32 | 5 | |
e74abb32 | 6 | use rustc::mir::{self, BasicBlock, Body, Location}; |
dfeec247 | 7 | use rustc_hir::def_id::DefId; |
e74abb32 | 8 | use rustc_index::bit_set::{BitSet, HybridBitSet}; |
dfeec247 | 9 | use rustc_index::vec::{Idx, IndexVec}; |
e74abb32 | 10 | |
dfeec247 | 11 | use super::{Analysis, GenKillSet, Results, ResultsRefCursor}; |
e74abb32 | 12 | use crate::util::graphviz_safe_def_name; |
e74abb32 XL |
13 | |
14 | pub struct Formatter<'a, 'tcx, A> | |
15 | where | |
16 | A: Analysis<'tcx>, | |
17 | { | |
18 | body: &'a Body<'tcx>, | |
19 | def_id: DefId, | |
20 | ||
21 | // This must be behind a `RefCell` because `dot::Labeller` takes `&self`. | |
22 | block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>, | |
23 | } | |
24 | ||
25 | impl<A> Formatter<'a, 'tcx, A> | |
26 | where | |
27 | A: Analysis<'tcx>, | |
28 | { | |
29 | pub fn new( | |
30 | body: &'a Body<'tcx>, | |
31 | def_id: DefId, | |
32 | results: &'a Results<'tcx, A>, | |
dfeec247 | 33 | state_formatter: &'a mut dyn StateFormatter<'tcx, A>, |
e74abb32 XL |
34 | ) -> Self { |
35 | let block_formatter = BlockFormatter { | |
36 | bg: Background::Light, | |
e74abb32 | 37 | results: ResultsRefCursor::new(body, results), |
dfeec247 | 38 | state_formatter, |
e74abb32 XL |
39 | }; |
40 | ||
dfeec247 | 41 | Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) } |
e74abb32 XL |
42 | } |
43 | } | |
44 | ||
45 | /// A pair of a basic block and an index into that basic blocks `successors`. | |
46 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] | |
47 | pub struct CfgEdge { | |
48 | source: BasicBlock, | |
49 | index: usize, | |
50 | } | |
51 | ||
52 | fn outgoing_edges(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> { | |
53 | body[bb] | |
54 | .terminator() | |
55 | .successors() | |
56 | .enumerate() | |
57 | .map(|(index, _)| CfgEdge { source: bb, index }) | |
58 | .collect() | |
59 | } | |
60 | ||
61 | impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A> | |
62 | where | |
63 | A: Analysis<'tcx>, | |
64 | { | |
65 | type Node = BasicBlock; | |
66 | type Edge = CfgEdge; | |
67 | ||
68 | fn graph_id(&self) -> dot::Id<'_> { | |
69 | let name = graphviz_safe_def_name(self.def_id); | |
70 | dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap() | |
71 | } | |
72 | ||
73 | fn node_id(&self, n: &Self::Node) -> dot::Id<'_> { | |
74 | dot::Id::new(format!("bb_{}", n.index())).unwrap() | |
75 | } | |
76 | ||
77 | fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> { | |
78 | let mut label = Vec::new(); | |
dfeec247 | 79 | self.block_formatter.borrow_mut().write_node_label(&mut label, self.body, *block).unwrap(); |
e74abb32 XL |
80 | dot::LabelText::html(String::from_utf8(label).unwrap()) |
81 | } | |
82 | ||
83 | fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> { | |
84 | Some(dot::LabelText::label("none")) | |
85 | } | |
86 | ||
87 | fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> { | |
dfeec247 | 88 | let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index]; |
e74abb32 XL |
89 | dot::LabelText::label(label.clone()) |
90 | } | |
91 | } | |
92 | ||
93 | impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A> | |
94 | where | |
95 | A: Analysis<'tcx>, | |
96 | { | |
97 | type Node = BasicBlock; | |
98 | type Edge = CfgEdge; | |
99 | ||
100 | fn nodes(&self) -> dot::Nodes<'_, Self::Node> { | |
dfeec247 | 101 | self.body.basic_blocks().indices().collect::<Vec<_>>().into() |
e74abb32 XL |
102 | } |
103 | ||
104 | fn edges(&self) -> dot::Edges<'_, Self::Edge> { | |
105 | self.body | |
106 | .basic_blocks() | |
107 | .indices() | |
108 | .flat_map(|bb| outgoing_edges(self.body, bb)) | |
109 | .collect::<Vec<_>>() | |
110 | .into() | |
111 | } | |
112 | ||
113 | fn source(&self, edge: &Self::Edge) -> Self::Node { | |
114 | edge.source | |
115 | } | |
116 | ||
117 | fn target(&self, edge: &Self::Edge) -> Self::Node { | |
dfeec247 | 118 | self.body[edge.source].terminator().successors().nth(edge.index).copied().unwrap() |
e74abb32 XL |
119 | } |
120 | } | |
121 | ||
122 | struct BlockFormatter<'a, 'tcx, A> | |
123 | where | |
124 | A: Analysis<'tcx>, | |
125 | { | |
e74abb32 XL |
126 | results: ResultsRefCursor<'a, 'a, 'tcx, A>, |
127 | bg: Background, | |
dfeec247 | 128 | state_formatter: &'a mut dyn StateFormatter<'tcx, A>, |
e74abb32 XL |
129 | } |
130 | ||
131 | impl<A> BlockFormatter<'a, 'tcx, A> | |
132 | where | |
133 | A: Analysis<'tcx>, | |
134 | { | |
dfeec247 XL |
135 | const HEADER_COLOR: &'static str = "#a0a0a0"; |
136 | ||
137 | fn num_state_columns(&self) -> usize { | |
138 | std::cmp::max(1, self.state_formatter.column_names().len()) | |
139 | } | |
140 | ||
e74abb32 XL |
141 | fn toggle_background(&mut self) -> Background { |
142 | let bg = self.bg; | |
143 | self.bg = !bg; | |
144 | bg | |
145 | } | |
146 | ||
147 | fn write_node_label( | |
148 | &mut self, | |
149 | w: &mut impl io::Write, | |
150 | body: &'a Body<'tcx>, | |
151 | block: BasicBlock, | |
152 | ) -> io::Result<()> { | |
153 | // Sample output: | |
154 | // +-+-----------------------------------------------+ | |
155 | // A | bb4 | | |
156 | // +-+----------------------------------+------------+ | |
157 | // B | MIR | STATE | | |
158 | // +-+----------------------------------+------------+ | |
159 | // C | | (on entry) | {_0,_2,_3} | | |
160 | // +-+----------------------------------+------------+ | |
161 | // D |0| StorageLive(_7) | | | |
162 | // +-+----------------------------------+------------+ | |
163 | // |1| StorageLive(_8) | | | |
164 | // +-+----------------------------------+------------+ | |
165 | // |2| _8 = &mut _1 | +_8 | | |
166 | // +-+----------------------------------+------------+ | |
167 | // E |T| _4 = const Foo::twiddle(move _2) | -_2 | | |
168 | // +-+----------------------------------+------------+ | |
169 | // F | | (on unwind) | {_0,_3,_8} | | |
170 | // +-+----------------------------------+------------+ | |
171 | // | | (on successful return) | +_4 | | |
172 | // +-+----------------------------------+------------+ | |
173 | ||
74b04a01 XL |
174 | // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their |
175 | // children. This is because `xdot` seemed to have a hard time correctly propagating | |
176 | // attributes. Make sure to test the output before trying to remove the redundancy. | |
177 | // Notably, `align` was found to have no effect when applied only to <table>. | |
178 | ||
179 | let table_fmt = concat!( | |
180 | " border=\"1\"", | |
181 | " cellborder=\"1\"", | |
182 | " cellspacing=\"0\"", | |
183 | " cellpadding=\"3\"", | |
184 | " sides=\"rb\"", | |
185 | ); | |
186 | write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?; | |
e74abb32 | 187 | |
dfeec247 XL |
188 | // A + B: Block header |
189 | if self.state_formatter.column_names().is_empty() { | |
190 | self.write_block_header_simple(w, block)?; | |
191 | } else { | |
192 | self.write_block_header_with_state_columns(w, block)?; | |
193 | } | |
e74abb32 XL |
194 | |
195 | // C: Entry state | |
196 | self.bg = Background::Light; | |
197 | self.results.seek_to_block_start(block); | |
74b04a01 XL |
198 | let block_entry_state = self.results.get().clone(); |
199 | ||
200 | self.write_row_with_full_state(w, "", "(on entry)")?; | |
e74abb32 XL |
201 | |
202 | // D: Statement transfer functions | |
203 | for (i, statement) in body[block].statements.iter().enumerate() { | |
204 | let location = Location { block, statement_index: i }; | |
dfeec247 XL |
205 | let statement_str = format!("{:?}", statement); |
206 | self.write_row_for_location(w, &i.to_string(), &statement_str, location)?; | |
e74abb32 XL |
207 | } |
208 | ||
209 | // E: Terminator transfer function | |
210 | let terminator = body[block].terminator(); | |
dfeec247 XL |
211 | let terminator_loc = body.terminator_loc(block); |
212 | let mut terminator_str = String::new(); | |
213 | terminator.kind.fmt_head(&mut terminator_str).unwrap(); | |
e74abb32 | 214 | |
dfeec247 | 215 | self.write_row_for_location(w, "T", &terminator_str, terminator_loc)?; |
e74abb32 XL |
216 | |
217 | // F: Exit state | |
74b04a01 XL |
218 | |
219 | // Write the full dataflow state immediately after the terminator if it differs from the | |
220 | // state at block entry. | |
dfeec247 | 221 | self.results.seek_after(terminator_loc); |
74b04a01 XL |
222 | if self.results.get() != &block_entry_state { |
223 | let after_terminator_name = match terminator.kind { | |
224 | mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)", | |
225 | _ => "(on exit)", | |
226 | }; | |
227 | ||
228 | self.write_row_with_full_state(w, "", after_terminator_name)?; | |
e74abb32 XL |
229 | } |
230 | ||
74b04a01 XL |
231 | // Write any changes caused by terminator-specific effects |
232 | match terminator.kind { | |
233 | mir::TerminatorKind::Call { destination: Some(_), .. } => { | |
234 | let num_state_columns = self.num_state_columns(); | |
235 | self.write_row(w, "", "(on successful return)", |this, w, fmt| { | |
236 | write!( | |
237 | w, | |
238 | r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#, | |
239 | colspan = num_state_columns, | |
240 | fmt = fmt, | |
241 | )?; | |
242 | ||
243 | let state_on_unwind = this.results.get().clone(); | |
244 | this.results.seek_after_assume_call_returns(terminator_loc); | |
245 | write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?; | |
246 | ||
247 | write!(w, "</td>") | |
248 | })?; | |
249 | } | |
250 | ||
251 | _ => {} | |
252 | }; | |
253 | ||
e74abb32 XL |
254 | write!(w, "</table>") |
255 | } | |
256 | ||
dfeec247 | 257 | fn write_block_header_simple( |
e74abb32 XL |
258 | &mut self, |
259 | w: &mut impl io::Write, | |
dfeec247 | 260 | block: BasicBlock, |
e74abb32 | 261 | ) -> io::Result<()> { |
dfeec247 XL |
262 | // +-------------------------------------------------+ |
263 | // A | bb4 | | |
264 | // +-----------------------------------+-------------+ | |
265 | // B | MIR | STATE | | |
266 | // +-+---------------------------------+-------------+ | |
267 | // | | ... | | | |
e74abb32 | 268 | |
dfeec247 XL |
269 | // A |
270 | write!( | |
271 | w, | |
272 | concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",), | |
273 | block_id = block.index(), | |
274 | )?; | |
e74abb32 | 275 | |
dfeec247 | 276 | // B |
e74abb32 XL |
277 | write!( |
278 | w, | |
dfeec247 XL |
279 | concat!( |
280 | "<tr>", | |
281 | r#"<td colspan="2" {fmt}>MIR</td>"#, | |
282 | r#"<td {fmt}>STATE</td>"#, | |
283 | "</tr>", | |
284 | ), | |
285 | fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR), | |
e74abb32 XL |
286 | ) |
287 | } | |
288 | ||
dfeec247 | 289 | fn write_block_header_with_state_columns( |
e74abb32 XL |
290 | &mut self, |
291 | w: &mut impl io::Write, | |
dfeec247 | 292 | block: BasicBlock, |
e74abb32 | 293 | ) -> io::Result<()> { |
dfeec247 XL |
294 | // +------------------------------------+-------------+ |
295 | // A | bb4 | STATE | | |
296 | // +------------------------------------+------+------+ | |
297 | // B | MIR | GEN | KILL | | |
298 | // +-+----------------------------------+------+------+ | |
299 | // | | ... | | | | |
300 | ||
301 | let state_column_names = self.state_formatter.column_names(); | |
302 | ||
303 | // A | |
304 | write!( | |
305 | w, | |
306 | concat!( | |
307 | "<tr>", | |
308 | r#"<td {fmt} colspan="2">bb{block_id}</td>"#, | |
309 | r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#, | |
310 | "</tr>", | |
311 | ), | |
312 | fmt = "sides=\"tl\"", | |
313 | num_state_cols = state_column_names.len(), | |
314 | block_id = block.index(), | |
315 | )?; | |
e74abb32 | 316 | |
dfeec247 XL |
317 | // B |
318 | let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR); | |
319 | write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?; | |
e74abb32 | 320 | |
dfeec247 XL |
321 | for name in state_column_names { |
322 | write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?; | |
323 | } | |
e74abb32 | 324 | |
dfeec247 XL |
325 | write!(w, "</tr>") |
326 | } | |
327 | ||
328 | /// Write a row with the given index and MIR, using the function argument to fill in the | |
329 | /// "STATE" column(s). | |
330 | fn write_row<W: io::Write>( | |
331 | &mut self, | |
332 | w: &mut W, | |
333 | i: &str, | |
334 | mir: &str, | |
335 | f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>, | |
336 | ) -> io::Result<()> { | |
337 | let bg = self.toggle_background(); | |
74b04a01 XL |
338 | let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" }; |
339 | ||
340 | let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr()); | |
e74abb32 XL |
341 | |
342 | write!( | |
343 | w, | |
dfeec247 XL |
344 | concat!( |
345 | "<tr>", | |
346 | r#"<td {fmt} align="right">{i}</td>"#, | |
347 | r#"<td {fmt} align="left">{mir}</td>"#, | |
348 | ), | |
e74abb32 | 349 | i = i, |
dfeec247 | 350 | fmt = fmt, |
e74abb32 XL |
351 | mir = dot::escape_html(mir), |
352 | )?; | |
353 | ||
dfeec247 XL |
354 | f(self, w, &fmt)?; |
355 | write!(w, "</tr>") | |
356 | } | |
357 | ||
358 | fn write_row_with_full_state( | |
359 | &mut self, | |
360 | w: &mut impl io::Write, | |
361 | i: &str, | |
362 | mir: &str, | |
363 | ) -> io::Result<()> { | |
364 | self.write_row(w, i, mir, |this, w, fmt| { | |
365 | let state = this.results.get(); | |
366 | let analysis = this.results.analysis(); | |
367 | ||
e74abb32 XL |
368 | write!( |
369 | w, | |
dfeec247 XL |
370 | r#"<td colspan="{colspan}" {fmt} align="left">{{"#, |
371 | colspan = this.num_state_columns(), | |
372 | fmt = fmt, | |
e74abb32 | 373 | )?; |
74b04a01 | 374 | pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?; |
dfeec247 XL |
375 | write!(w, "}}</td>") |
376 | }) | |
377 | } | |
378 | ||
379 | fn write_row_for_location( | |
380 | &mut self, | |
381 | w: &mut impl io::Write, | |
382 | i: &str, | |
383 | mir: &str, | |
384 | location: Location, | |
385 | ) -> io::Result<()> { | |
386 | self.write_row(w, i, mir, |this, w, fmt| { | |
387 | this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location) | |
388 | }) | |
389 | } | |
390 | } | |
391 | ||
392 | /// Controls what gets printed under the `STATE` header. | |
393 | pub trait StateFormatter<'tcx, A> | |
394 | where | |
395 | A: Analysis<'tcx>, | |
396 | { | |
397 | /// The columns that will get printed under `STATE`. | |
398 | fn column_names(&self) -> &[&str]; | |
399 | ||
400 | fn write_state_for_location( | |
401 | &mut self, | |
402 | w: &mut dyn io::Write, | |
403 | fmt: &str, | |
404 | results: &mut ResultsRefCursor<'_, '_, 'tcx, A>, | |
405 | location: Location, | |
406 | ) -> io::Result<()>; | |
407 | } | |
408 | ||
409 | /// Prints a single column containing the state vector immediately *after* each statement. | |
410 | pub struct SimpleDiff<T: Idx> { | |
411 | prev_state: BitSet<T>, | |
412 | prev_loc: Location, | |
413 | } | |
414 | ||
415 | impl<T: Idx> SimpleDiff<T> { | |
dfeec247 XL |
416 | pub fn new(bits_per_block: usize) -> Self { |
417 | SimpleDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START } | |
418 | } | |
419 | } | |
420 | ||
421 | impl<A> StateFormatter<'tcx, A> for SimpleDiff<A::Idx> | |
422 | where | |
423 | A: Analysis<'tcx>, | |
424 | { | |
425 | fn column_names(&self) -> &[&str] { | |
426 | &[] | |
427 | } | |
428 | ||
429 | fn write_state_for_location( | |
430 | &mut self, | |
431 | mut w: &mut dyn io::Write, | |
432 | fmt: &str, | |
433 | results: &mut ResultsRefCursor<'_, '_, 'tcx, A>, | |
434 | location: Location, | |
435 | ) -> io::Result<()> { | |
436 | if location.statement_index == 0 { | |
437 | results.seek_to_block_start(location.block); | |
438 | self.prev_state.overwrite(results.get()); | |
439 | } else { | |
440 | // Ensure that we are visiting statements in order, so `prev_state` is correct. | |
441 | assert_eq!(self.prev_loc.successor_within_block(), location); | |
e74abb32 XL |
442 | } |
443 | ||
dfeec247 | 444 | self.prev_loc = location; |
74b04a01 XL |
445 | write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?; |
446 | results.seek_after(location); | |
dfeec247 XL |
447 | let curr_state = results.get(); |
448 | write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?; | |
449 | self.prev_state.overwrite(curr_state); | |
450 | write!(w, "</td>") | |
451 | } | |
452 | } | |
453 | ||
454 | /// Prints two state columns, one containing only the "before" effect of each statement and one | |
455 | /// containing the full effect. | |
456 | pub struct TwoPhaseDiff<T: Idx> { | |
457 | prev_state: BitSet<T>, | |
458 | prev_loc: Location, | |
459 | } | |
460 | ||
461 | impl<T: Idx> TwoPhaseDiff<T> { | |
dfeec247 XL |
462 | pub fn new(bits_per_block: usize) -> Self { |
463 | TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START } | |
464 | } | |
465 | } | |
466 | ||
467 | impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx> | |
468 | where | |
469 | A: Analysis<'tcx>, | |
470 | { | |
471 | fn column_names(&self) -> &[&str] { | |
74b04a01 | 472 | &["BEFORE", " AFTER"] |
dfeec247 XL |
473 | } |
474 | ||
475 | fn write_state_for_location( | |
476 | &mut self, | |
477 | mut w: &mut dyn io::Write, | |
478 | fmt: &str, | |
479 | results: &mut ResultsRefCursor<'_, '_, 'tcx, A>, | |
480 | location: Location, | |
481 | ) -> io::Result<()> { | |
482 | if location.statement_index == 0 { | |
483 | results.seek_to_block_start(location.block); | |
484 | self.prev_state.overwrite(results.get()); | |
485 | } else { | |
486 | // Ensure that we are visiting statements in order, so `prev_state` is correct. | |
487 | assert_eq!(self.prev_loc.successor_within_block(), location); | |
e74abb32 XL |
488 | } |
489 | ||
dfeec247 XL |
490 | self.prev_loc = location; |
491 | ||
74b04a01 | 492 | // Before |
dfeec247 XL |
493 | |
494 | write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?; | |
495 | results.seek_before(location); | |
496 | let curr_state = results.get(); | |
497 | write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?; | |
498 | self.prev_state.overwrite(curr_state); | |
499 | write!(w, "</td>")?; | |
500 | ||
74b04a01 | 501 | // After |
dfeec247 XL |
502 | |
503 | write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?; | |
504 | results.seek_after(location); | |
505 | let curr_state = results.get(); | |
506 | write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?; | |
507 | self.prev_state.overwrite(curr_state); | |
508 | write!(w, "</td>") | |
509 | } | |
510 | } | |
511 | ||
512 | /// Prints the gen/kill set for the entire block. | |
513 | pub struct BlockTransferFunc<'a, 'tcx, T: Idx> { | |
514 | body: &'a mir::Body<'tcx>, | |
515 | trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>, | |
516 | } | |
517 | ||
518 | impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> { | |
dfeec247 XL |
519 | pub fn new( |
520 | body: &'mir mir::Body<'tcx>, | |
521 | trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>, | |
522 | ) -> Self { | |
523 | BlockTransferFunc { body, trans_for_block } | |
524 | } | |
525 | } | |
526 | ||
527 | impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx> | |
528 | where | |
529 | A: Analysis<'tcx>, | |
530 | { | |
531 | fn column_names(&self) -> &[&str] { | |
532 | &["GEN", "KILL"] | |
533 | } | |
534 | ||
535 | fn write_state_for_location( | |
536 | &mut self, | |
537 | mut w: &mut dyn io::Write, | |
538 | fmt: &str, | |
539 | results: &mut ResultsRefCursor<'_, '_, 'tcx, A>, | |
540 | location: Location, | |
541 | ) -> io::Result<()> { | |
542 | // Only print a single row. | |
543 | if location.statement_index != 0 { | |
544 | return Ok(()); | |
545 | } | |
546 | ||
547 | let block_trans = &self.trans_for_block[location.block]; | |
548 | let rowspan = self.body.basic_blocks()[location.block].statements.len(); | |
549 | ||
550 | for set in &[&block_trans.gen, &block_trans.kill] { | |
e74abb32 XL |
551 | write!( |
552 | w, | |
74b04a01 | 553 | r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#, |
dfeec247 XL |
554 | fmt = fmt, |
555 | rowspan = rowspan | |
e74abb32 | 556 | )?; |
dfeec247 | 557 | |
74b04a01 | 558 | pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?; |
dfeec247 | 559 | write!(w, "</td>")?; |
e74abb32 XL |
560 | } |
561 | ||
dfeec247 | 562 | Ok(()) |
e74abb32 XL |
563 | } |
564 | } | |
565 | ||
dfeec247 XL |
566 | /// Writes two lines, one containing the added bits and one the removed bits. |
567 | fn write_diff<A: Analysis<'tcx>>( | |
568 | w: &mut impl io::Write, | |
569 | analysis: &A, | |
570 | from: &BitSet<A::Idx>, | |
571 | to: &BitSet<A::Idx>, | |
572 | ) -> io::Result<()> { | |
573 | assert_eq!(from.domain_size(), to.domain_size()); | |
574 | let len = from.domain_size(); | |
575 | ||
576 | let mut set = HybridBitSet::new_empty(len); | |
577 | let mut clear = HybridBitSet::new_empty(len); | |
578 | ||
579 | // FIXME: Implement a lazy iterator over the symmetric difference of two bitsets. | |
580 | for i in (0..len).map(|i| A::Idx::new(i)) { | |
581 | match (from.contains(i), to.contains(i)) { | |
582 | (false, true) => set.insert(i), | |
583 | (true, false) => clear.insert(i), | |
584 | _ => continue, | |
585 | }; | |
586 | } | |
e74abb32 | 587 | |
dfeec247 XL |
588 | if !set.is_empty() { |
589 | write!(w, r#"<font color="darkgreen">+"#)?; | |
74b04a01 | 590 | pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?; |
dfeec247 XL |
591 | write!(w, r#"</font>"#)?; |
592 | } | |
e74abb32 | 593 | |
dfeec247 | 594 | if !set.is_empty() && !clear.is_empty() { |
74b04a01 | 595 | write!(w, "{}", BR_LEFT)?; |
dfeec247 XL |
596 | } |
597 | ||
598 | if !clear.is_empty() { | |
599 | write!(w, r#"<font color="red">-"#)?; | |
74b04a01 | 600 | pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?; |
dfeec247 | 601 | write!(w, r#"</font>"#)?; |
e74abb32 | 602 | } |
dfeec247 XL |
603 | |
604 | Ok(()) | |
e74abb32 XL |
605 | } |
606 | ||
74b04a01 XL |
607 | const BR_LEFT: &str = r#"<br align="left"/>"#; |
608 | const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#; | |
609 | ||
dfeec247 | 610 | /// Line break policy that breaks at 40 characters and starts the next line with a single space. |
74b04a01 | 611 | const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 }); |
dfeec247 XL |
612 | |
613 | struct LineBreak { | |
614 | sequence: &'static str, | |
615 | limit: usize, | |
616 | } | |
617 | ||
618 | /// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given | |
619 | /// separator (`sep`). | |
620 | /// | |
621 | /// Optionally, it will break lines using the given character sequence (usually `<br/>`) and | |
622 | /// character limit. | |
e74abb32 XL |
623 | fn pretty_print_state_elems<A>( |
624 | w: &mut impl io::Write, | |
625 | analysis: &A, | |
626 | elems: impl Iterator<Item = A::Idx>, | |
dfeec247 XL |
627 | sep: &str, |
628 | line_break: Option<LineBreak>, | |
629 | ) -> io::Result<bool> | |
e74abb32 XL |
630 | where |
631 | A: Analysis<'tcx>, | |
632 | { | |
dfeec247 XL |
633 | let sep_width = sep.chars().count(); |
634 | ||
635 | let mut buf = Vec::new(); | |
636 | ||
e74abb32 | 637 | let mut first = true; |
dfeec247 XL |
638 | let mut curr_line_width = 0; |
639 | let mut line_break_inserted = false; | |
640 | ||
e74abb32 | 641 | for idx in elems { |
dfeec247 XL |
642 | buf.clear(); |
643 | analysis.pretty_print_idx(&mut buf, idx)?; | |
644 | let idx_str = | |
645 | str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8"); | |
646 | let escaped = dot::escape_html(idx_str); | |
647 | let escaped_width = escaped.chars().count(); | |
648 | ||
74b04a01 XL |
649 | if first { |
650 | first = false; | |
651 | } else { | |
652 | write!(w, "{}", sep)?; | |
653 | curr_line_width += sep_width; | |
654 | ||
655 | if let Some(line_break) = &line_break { | |
656 | if curr_line_width + sep_width + escaped_width > line_break.limit { | |
657 | write!(w, "{}", line_break.sequence)?; | |
658 | line_break_inserted = true; | |
659 | curr_line_width = 0; | |
660 | } | |
dfeec247 | 661 | } |
e74abb32 XL |
662 | } |
663 | ||
dfeec247 XL |
664 | write!(w, "{}", escaped)?; |
665 | curr_line_width += escaped_width; | |
e74abb32 XL |
666 | } |
667 | ||
dfeec247 | 668 | Ok(line_break_inserted) |
e74abb32 XL |
669 | } |
670 | ||
671 | /// The background color used for zebra-striping the table. | |
672 | #[derive(Clone, Copy)] | |
673 | enum Background { | |
674 | Light, | |
675 | Dark, | |
676 | } | |
677 | ||
678 | impl Background { | |
679 | fn attr(self) -> &'static str { | |
680 | match self { | |
681 | Self::Dark => "bgcolor=\"#f0f0f0\"", | |
682 | Self::Light => "", | |
683 | } | |
684 | } | |
685 | } | |
686 | ||
687 | impl ops::Not for Background { | |
688 | type Output = Self; | |
689 | ||
690 | fn not(self) -> Self { | |
691 | match self { | |
692 | Self::Light => Self::Dark, | |
693 | Self::Dark => Self::Light, | |
694 | } | |
695 | } | |
696 | } |