]>
Commit | Line | Data |
---|---|---|
f9f354fc XL |
1 | use std::collections::BTreeSet; |
2 | use std::fmt::Write as _; | |
3 | use std::fmt::{Debug, Display}; | |
4 | use std::fs; | |
5 | use std::io::{self, Write}; | |
6 | use std::path::{Path, PathBuf}; | |
7 | ||
dfeec247 XL |
8 | use super::graphviz::write_mir_fn_graphviz; |
9 | use crate::transform::MirSource; | |
ba9703b0 | 10 | use either::Either; |
476ff2be | 11 | use rustc_data_structures::fx::FxHashMap; |
dfeec247 | 12 | use rustc_hir::def_id::{DefId, LOCAL_CRATE}; |
e74abb32 | 13 | use rustc_index::vec::Idx; |
ba9703b0 | 14 | use rustc_middle::mir::interpret::{ |
f9f354fc | 15 | read_target_uint, AllocId, Allocation, ConstValue, GlobalAlloc, Pointer, |
ba9703b0 XL |
16 | }; |
17 | use rustc_middle::mir::visit::Visitor; | |
18 | use rustc_middle::mir::*; | |
19 | use rustc_middle::ty::{self, TyCtxt, TypeFoldable, TypeVisitor}; | |
20 | use rustc_target::abi::Size; | |
9cc50fc6 | 21 | |
0731742a | 22 | const INDENT: &str = " "; |
a7813a04 | 23 | /// Alignment for lining up comments following MIR statements |
ff7c6d11 | 24 | pub(crate) const ALIGN: usize = 40; |
9cc50fc6 | 25 | |
abe05a73 XL |
26 | /// An indication of where we are in the control flow graph. Used for printing |
27 | /// extra information in `dump_mir` | |
28 | pub enum PassWhere { | |
29 | /// We have not started dumping the control flow graph, but we are about to. | |
30 | BeforeCFG, | |
31 | ||
32 | /// We just finished dumping the control flow graph. This is right before EOF | |
33 | AfterCFG, | |
34 | ||
35 | /// We are about to start dumping the given basic block. | |
36 | BeforeBlock(BasicBlock), | |
37 | ||
ff7c6d11 XL |
38 | /// We are just about to dump the given statement or terminator. |
39 | BeforeLocation(Location), | |
40 | ||
41 | /// We just dumped the given statement or terminator. | |
42 | AfterLocation(Location), | |
8faf50e0 XL |
43 | |
44 | /// We just dumped the terminator for a block but not the closing `}`. | |
45 | AfterTerminator(BasicBlock), | |
abe05a73 XL |
46 | } |
47 | ||
54a0048b SL |
48 | /// If the session is properly configured, dumps a human-readable |
49 | /// representation of the mir into: | |
50 | /// | |
a7813a04 | 51 | /// ```text |
7cac9316 | 52 | /// rustc.node<node_id>.<pass_num>.<pass_name>.<disambiguator> |
54a0048b SL |
53 | /// ``` |
54 | /// | |
55 | /// Output from this function is controlled by passing `-Z dump-mir=<filter>`, | |
56 | /// where `<filter>` takes the following forms: | |
57 | /// | |
58 | /// - `all` -- dump MIR for all fns, all passes, all everything | |
ff7c6d11 XL |
59 | /// - a filter defined by a set of substrings combined with `&` and `|` |
60 | /// (`&` has higher precedence). At least one of the `|`-separated groups | |
61 | /// must match; an `|`-separated group matches if all of its `&`-separated | |
62 | /// substrings are matched. | |
63 | /// | |
64 | /// Example: | |
65 | /// | |
66 | /// - `nll` == match if `nll` appears in the name | |
67 | /// - `foo & nll` == match if `foo` and `nll` both appear in the name | |
68 | /// - `foo & nll | typeck` == match if `foo` and `nll` both appear in the name | |
69 | /// or `typeck` appears in the name. | |
70 | /// - `foo & nll | bar & typeck` == match if `foo` and `nll` both appear in the name | |
71 | /// or `typeck` and `bar` both appear in the name. | |
dc9dc135 XL |
72 | pub fn dump_mir<'tcx, F>( |
73 | tcx: TyCtxt<'tcx>, | |
0531ce1d | 74 | pass_num: Option<&dyn Display>, |
ff7c6d11 | 75 | pass_name: &str, |
0531ce1d | 76 | disambiguator: &dyn Display, |
9fa01778 | 77 | source: MirSource<'tcx>, |
dc9dc135 | 78 | body: &Body<'tcx>, |
ff7c6d11 XL |
79 | extra_data: F, |
80 | ) where | |
0531ce1d | 81 | F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, |
abe05a73 | 82 | { |
ba9703b0 | 83 | if !dump_enabled(tcx, pass_name, source.def_id()) { |
7cac9316 XL |
84 | return; |
85 | } | |
86 | ||
ba9703b0 | 87 | dump_matched_mir_node(tcx, pass_num, pass_name, disambiguator, source, body, extra_data); |
7cac9316 XL |
88 | } |
89 | ||
ba9703b0 | 90 | pub fn dump_enabled<'tcx>(tcx: TyCtxt<'tcx>, pass_name: &str, def_id: DefId) -> bool { |
54a0048b | 91 | let filters = match tcx.sess.opts.debugging_opts.dump_mir { |
7cac9316 | 92 | None => return false, |
54a0048b SL |
93 | Some(ref filters) => filters, |
94 | }; | |
532ac7d7 | 95 | let node_path = ty::print::with_forced_impl_filename_line(|| { |
ff7c6d11 | 96 | // see notes on #41697 below |
ba9703b0 | 97 | tcx.def_path_str(def_id) |
7cac9316 | 98 | }); |
8faf50e0 XL |
99 | filters.split('|').any(|or_filter| { |
100 | or_filter.split('&').all(|and_filter| { | |
ff7c6d11 XL |
101 | and_filter == "all" || pass_name.contains(and_filter) || node_path.contains(and_filter) |
102 | }) | |
103 | }) | |
7cac9316 | 104 | } |
54a0048b | 105 | |
7cac9316 | 106 | // #41697 -- we use `with_forced_impl_filename_line()` because |
532ac7d7 | 107 | // `def_path_str()` would otherwise trigger `type_of`, and this can |
7cac9316 XL |
108 | // run while we are already attempting to evaluate `type_of`. |
109 | ||
dc9dc135 XL |
110 | fn dump_matched_mir_node<'tcx, F>( |
111 | tcx: TyCtxt<'tcx>, | |
0531ce1d | 112 | pass_num: Option<&dyn Display>, |
ff7c6d11 | 113 | pass_name: &str, |
0531ce1d | 114 | disambiguator: &dyn Display, |
9fa01778 | 115 | source: MirSource<'tcx>, |
dc9dc135 | 116 | body: &Body<'tcx>, |
ff7c6d11 XL |
117 | mut extra_data: F, |
118 | ) where | |
0531ce1d | 119 | F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, |
abe05a73 | 120 | { |
9fa01778 | 121 | let _: io::Result<()> = try { |
ff7c6d11 | 122 | let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, source)?; |
ba9703b0 XL |
123 | let def_path = ty::print::with_forced_impl_filename_line(|| { |
124 | // see notes on #41697 above | |
125 | tcx.def_path_str(source.def_id()) | |
126 | }); | |
127 | write!(file, "// MIR for `{}", def_path)?; | |
128 | match source.promoted { | |
129 | None => write!(file, "`")?, | |
130 | Some(promoted) => write!(file, "::{:?}`", promoted)?, | |
131 | } | |
132 | writeln!(file, " {} {}", disambiguator, pass_name)?; | |
dc9dc135 | 133 | if let Some(ref layout) = body.generator_layout { |
f035d41b | 134 | writeln!(file, "/* generator_layout = {:#?} */", layout)?; |
ff7c6d11 | 135 | } |
ba9703b0 | 136 | writeln!(file)?; |
ff7c6d11 | 137 | extra_data(PassWhere::BeforeCFG, &mut file)?; |
f035d41b | 138 | write_user_type_annotations(tcx, body, &mut file)?; |
dc9dc135 | 139 | write_mir_fn(tcx, source, body, &mut extra_data, &mut file)?; |
ff7c6d11 | 140 | extra_data(PassWhere::AfterCFG, &mut file)?; |
94b46f34 | 141 | }; |
ff7c6d11 XL |
142 | |
143 | if tcx.sess.opts.debugging_opts.dump_mir_graphviz { | |
9fa01778 | 144 | let _: io::Result<()> = try { |
ff7c6d11 XL |
145 | let mut file = |
146 | create_dump_file(tcx, "dot", pass_num, pass_name, disambiguator, source)?; | |
e74abb32 | 147 | write_mir_fn_graphviz(tcx, source.def_id(), body, false, &mut file)?; |
94b46f34 | 148 | }; |
ff7c6d11 XL |
149 | } |
150 | } | |
151 | ||
152 | /// Returns the path to the filename where we should dump a given MIR. | |
153 | /// Also used by other bits of code (e.g., NLL inference) that dump | |
154 | /// graphviz data or other things. | |
155 | fn dump_path( | |
dc9dc135 | 156 | tcx: TyCtxt<'_>, |
ff7c6d11 | 157 | extension: &str, |
0531ce1d | 158 | pass_num: Option<&dyn Display>, |
ff7c6d11 | 159 | pass_name: &str, |
0531ce1d | 160 | disambiguator: &dyn Display, |
9fa01778 | 161 | source: MirSource<'tcx>, |
ff7c6d11 | 162 | ) -> PathBuf { |
abe05a73 XL |
163 | let promotion_id = match source.promoted { |
164 | Some(id) => format!("-{:?}", id), | |
ff7c6d11 | 165 | None => String::new(), |
3157f602 XL |
166 | }; |
167 | ||
7cac9316 | 168 | let pass_num = if tcx.sess.opts.debugging_opts.dump_mir_exclude_pass_number { |
8faf50e0 | 169 | String::new() |
7cac9316 XL |
170 | } else { |
171 | match pass_num { | |
8faf50e0 | 172 | None => ".-------".to_string(), |
abe05a73 | 173 | Some(pass_num) => format!(".{}", pass_num), |
7cac9316 XL |
174 | } |
175 | }; | |
176 | ||
5bcae85e | 177 | let mut file_path = PathBuf::new(); |
2c00a5a8 | 178 | file_path.push(Path::new(&tcx.sess.opts.debugging_opts.dump_mir_dir)); |
abe05a73 | 179 | |
dfeec247 | 180 | let item_name = tcx.def_path(source.def_id()).to_filename_friendly_no_crate(); |
9fa01778 XL |
181 | // All drop shims have the same DefId, so we have to add the type |
182 | // to get unique file names. | |
183 | let shim_disambiguator = match source.instance { | |
184 | ty::InstanceDef::DropGlue(_, Some(ty)) => { | |
185 | // Unfortunately, pretty-printed typed are not very filename-friendly. | |
186 | // We dome some filtering. | |
187 | let mut s = ".".to_owned(); | |
dfeec247 XL |
188 | s.extend(ty.to_string().chars().filter_map(|c| match c { |
189 | ' ' => None, | |
190 | ':' | '<' | '>' => Some('_'), | |
191 | c => Some(c), | |
192 | })); | |
9fa01778 XL |
193 | s |
194 | } | |
195 | _ => String::new(), | |
196 | }; | |
ff7c6d11 XL |
197 | |
198 | let file_name = format!( | |
9fa01778 | 199 | "rustc.{}{}{}{}.{}.{}.{}", |
dfeec247 | 200 | item_name, shim_disambiguator, promotion_id, pass_num, pass_name, disambiguator, extension, |
ff7c6d11 XL |
201 | ); |
202 | ||
5bcae85e | 203 | file_path.push(&file_name); |
abe05a73 | 204 | |
ff7c6d11 XL |
205 | file_path |
206 | } | |
207 | ||
208 | /// Attempts to open a file where we should dump a given MIR or other | |
209 | /// bit of MIR-related data. Used by `mir-dump`, but also by other | |
210 | /// bits of code (e.g., NLL inference) that dump graphviz data or | |
211 | /// other things, and hence takes the extension as an argument. | |
212 | pub(crate) fn create_dump_file( | |
dc9dc135 | 213 | tcx: TyCtxt<'_>, |
ff7c6d11 | 214 | extension: &str, |
0531ce1d | 215 | pass_num: Option<&dyn Display>, |
ff7c6d11 | 216 | pass_name: &str, |
0531ce1d | 217 | disambiguator: &dyn Display, |
9fa01778 | 218 | source: MirSource<'tcx>, |
e1599b0c | 219 | ) -> io::Result<io::BufWriter<fs::File>> { |
ff7c6d11 XL |
220 | let file_path = dump_path(tcx, extension, pass_num, pass_name, disambiguator, source); |
221 | if let Some(parent) = file_path.parent() { | |
222 | fs::create_dir_all(parent)?; | |
abe05a73 | 223 | } |
e1599b0c | 224 | Ok(io::BufWriter::new(fs::File::create(&file_path)?)) |
54a0048b SL |
225 | } |
226 | ||
9cc50fc6 | 227 | /// Write out a human-readable textual representation for the given MIR. |
dc9dc135 XL |
228 | pub fn write_mir_pretty<'tcx>( |
229 | tcx: TyCtxt<'tcx>, | |
ff7c6d11 | 230 | single: Option<DefId>, |
0531ce1d | 231 | w: &mut dyn Write, |
ff7c6d11 | 232 | ) -> io::Result<()> { |
dfeec247 XL |
233 | writeln!(w, "// WARNING: This output format is intended for human consumers only")?; |
234 | writeln!(w, "// and is subject to change without notice. Knock yourself out.")?; | |
cc61c64b | 235 | |
a7813a04 | 236 | let mut first = true; |
7cac9316 | 237 | for def_id in dump_mir_def_ids(tcx, single) { |
dc9dc135 | 238 | let body = &tcx.optimized_mir(def_id); |
5bcae85e | 239 | |
a7813a04 XL |
240 | if first { |
241 | first = false; | |
242 | } else { | |
243 | // Put empty lines between all items | |
ba9703b0 | 244 | writeln!(w)?; |
a7813a04 XL |
245 | } |
246 | ||
dc9dc135 | 247 | write_mir_fn(tcx, MirSource::item(def_id), body, &mut |_, _| Ok(()), w)?; |
a7813a04 | 248 | |
416331ca | 249 | for (i, body) in tcx.promoted_mir(def_id).iter_enumerated() { |
ba9703b0 | 250 | writeln!(w)?; |
dfeec247 | 251 | let src = MirSource { instance: ty::InstanceDef::Item(def_id), promoted: Some(i) }; |
dc9dc135 | 252 | write_mir_fn(tcx, src, body, &mut |_, _| Ok(()), w)?; |
a7813a04 | 253 | } |
54a0048b SL |
254 | } |
255 | Ok(()) | |
256 | } | |
9cc50fc6 | 257 | |
ba9703b0 | 258 | /// Write out a human-readable textual representation for the given function. |
dc9dc135 XL |
259 | pub fn write_mir_fn<'tcx, F>( |
260 | tcx: TyCtxt<'tcx>, | |
9fa01778 | 261 | src: MirSource<'tcx>, |
dc9dc135 | 262 | body: &Body<'tcx>, |
ff7c6d11 | 263 | extra_data: &mut F, |
0531ce1d | 264 | w: &mut dyn Write, |
ff7c6d11 | 265 | ) -> io::Result<()> |
abe05a73 | 266 | where |
0531ce1d | 267 | F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, |
abe05a73 | 268 | { |
dc9dc135 XL |
269 | write_mir_intro(tcx, src, body, w)?; |
270 | for block in body.basic_blocks().indices() { | |
abe05a73 | 271 | extra_data(PassWhere::BeforeBlock(block), w)?; |
dc9dc135 XL |
272 | write_basic_block(tcx, block, body, extra_data, w)?; |
273 | if block.index() + 1 != body.basic_blocks().len() { | |
ba9703b0 | 274 | writeln!(w)?; |
3157f602 | 275 | } |
54a0048b SL |
276 | } |
277 | ||
54a0048b | 278 | writeln!(w, "}}")?; |
ba9703b0 XL |
279 | |
280 | write_allocations(tcx, body, w)?; | |
281 | ||
54a0048b | 282 | Ok(()) |
9cc50fc6 SL |
283 | } |
284 | ||
285 | /// Write out a human-readable textual representation for the given basic block. | |
dc9dc135 XL |
286 | pub fn write_basic_block<'tcx, F>( |
287 | tcx: TyCtxt<'tcx>, | |
ff7c6d11 | 288 | block: BasicBlock, |
dc9dc135 | 289 | body: &Body<'tcx>, |
ff7c6d11 | 290 | extra_data: &mut F, |
0531ce1d | 291 | w: &mut dyn Write, |
ff7c6d11 | 292 | ) -> io::Result<()> |
abe05a73 | 293 | where |
0531ce1d | 294 | F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>, |
abe05a73 | 295 | { |
dc9dc135 | 296 | let data = &body[block]; |
9cc50fc6 SL |
297 | |
298 | // Basic block label at the top. | |
532ac7d7 XL |
299 | let cleanup_text = if data.is_cleanup { " (cleanup)" } else { "" }; |
300 | writeln!(w, "{}{:?}{}: {{", INDENT, block, cleanup_text)?; | |
9cc50fc6 SL |
301 | |
302 | // List of statements in the middle. | |
74b04a01 | 303 | let mut current_location = Location { block, statement_index: 0 }; |
9cc50fc6 | 304 | for statement in &data.statements { |
ff7c6d11 | 305 | extra_data(PassWhere::BeforeLocation(current_location), w)?; |
dc9dc135 | 306 | let indented_body = format!("{0}{0}{1:?};", INDENT, statement); |
ff7c6d11 XL |
307 | writeln!( |
308 | w, | |
f9f354fc | 309 | "{:A$} // {}{}", |
dc9dc135 | 310 | indented_body, |
f9f354fc | 311 | if tcx.sess.verbose() { format!("{:?}: ", current_location) } else { String::new() }, |
ff7c6d11 XL |
312 | comment(tcx, statement.source_info), |
313 | A = ALIGN, | |
314 | )?; | |
315 | ||
316 | write_extra(tcx, w, |visitor| { | |
48663c56 | 317 | visitor.visit_statement(statement, current_location); |
ff7c6d11 XL |
318 | })?; |
319 | ||
320 | extra_data(PassWhere::AfterLocation(current_location), w)?; | |
54a0048b SL |
321 | |
322 | current_location.statement_index += 1; | |
9cc50fc6 SL |
323 | } |
324 | ||
325 | // Terminator at the bottom. | |
ff7c6d11 | 326 | extra_data(PassWhere::BeforeLocation(current_location), w)?; |
a7813a04 | 327 | let indented_terminator = format!("{0}{0}{1:?};", INDENT, data.terminator().kind); |
ff7c6d11 XL |
328 | writeln!( |
329 | w, | |
f9f354fc | 330 | "{:A$} // {}{}", |
ff7c6d11 | 331 | indented_terminator, |
f9f354fc | 332 | if tcx.sess.verbose() { format!("{:?}: ", current_location) } else { String::new() }, |
ff7c6d11 XL |
333 | comment(tcx, data.terminator().source_info), |
334 | A = ALIGN, | |
335 | )?; | |
336 | ||
337 | write_extra(tcx, w, |visitor| { | |
48663c56 | 338 | visitor.visit_terminator(data.terminator(), current_location); |
ff7c6d11 XL |
339 | })?; |
340 | ||
341 | extra_data(PassWhere::AfterLocation(current_location), w)?; | |
8faf50e0 | 342 | extra_data(PassWhere::AfterTerminator(block), w)?; |
9cc50fc6 | 343 | |
5bcae85e | 344 | writeln!(w, "{}}}", INDENT) |
9cc50fc6 SL |
345 | } |
346 | ||
ff7c6d11 XL |
347 | /// After we print the main statement, we sometimes dump extra |
348 | /// information. There's often a lot of little things "nuzzled up" in | |
349 | /// a statement. | |
dc9dc135 | 350 | fn write_extra<'tcx, F>(tcx: TyCtxt<'tcx>, write: &mut dyn Write, mut visit_op: F) -> io::Result<()> |
ff7c6d11 | 351 | where |
dc9dc135 | 352 | F: FnMut(&mut ExtraComments<'tcx>), |
ff7c6d11 | 353 | { |
f035d41b | 354 | let mut extra_comments = ExtraComments { tcx, comments: vec![] }; |
ff7c6d11 XL |
355 | visit_op(&mut extra_comments); |
356 | for comment in extra_comments.comments { | |
357 | writeln!(write, "{:A$} // {}", "", comment, A = ALIGN)?; | |
358 | } | |
359 | Ok(()) | |
360 | } | |
361 | ||
dc9dc135 | 362 | struct ExtraComments<'tcx> { |
f035d41b | 363 | tcx: TyCtxt<'tcx>, |
ff7c6d11 XL |
364 | comments: Vec<String>, |
365 | } | |
366 | ||
dc9dc135 | 367 | impl ExtraComments<'tcx> { |
ff7c6d11 | 368 | fn push(&mut self, lines: &str) { |
8faf50e0 | 369 | for line in lines.split('\n') { |
ff7c6d11 XL |
370 | self.comments.push(line.to_string()); |
371 | } | |
372 | } | |
373 | } | |
374 | ||
dc9dc135 | 375 | impl Visitor<'tcx> for ExtraComments<'tcx> { |
ff7c6d11 XL |
376 | fn visit_constant(&mut self, constant: &Constant<'tcx>, location: Location) { |
377 | self.super_constant(constant, location); | |
e1599b0c | 378 | let Constant { span, user_ty, literal } = constant; |
8faf50e0 | 379 | self.push("mir::Constant"); |
f035d41b | 380 | self.push(&format!("+ span: {}", self.tcx.sess.source_map().span_to_string(*span))); |
b7449926 XL |
381 | if let Some(user_ty) = user_ty { |
382 | self.push(&format!("+ user_ty: {:?}", user_ty)); | |
383 | } | |
2c00a5a8 | 384 | self.push(&format!("+ literal: {:?}", literal)); |
ff7c6d11 XL |
385 | } |
386 | ||
532ac7d7 | 387 | fn visit_const(&mut self, constant: &&'tcx ty::Const<'tcx>, _: Location) { |
ff7c6d11 | 388 | self.super_const(constant); |
532ac7d7 XL |
389 | let ty::Const { ty, val, .. } = constant; |
390 | self.push("ty::Const"); | |
391 | self.push(&format!("+ ty: {:?}", ty)); | |
392 | self.push(&format!("+ val: {:?}", val)); | |
ff7c6d11 XL |
393 | } |
394 | ||
395 | fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { | |
396 | self.super_rvalue(rvalue, location); | |
ba9703b0 XL |
397 | if let Rvalue::Aggregate(kind, _) = rvalue { |
398 | match **kind { | |
ff7c6d11 | 399 | AggregateKind::Closure(def_id, substs) => { |
8faf50e0 | 400 | self.push("closure"); |
2c00a5a8 XL |
401 | self.push(&format!("+ def_id: {:?}", def_id)); |
402 | self.push(&format!("+ substs: {:#?}", substs)); | |
ff7c6d11 XL |
403 | } |
404 | ||
94b46f34 | 405 | AggregateKind::Generator(def_id, substs, movability) => { |
8faf50e0 | 406 | self.push("generator"); |
2c00a5a8 XL |
407 | self.push(&format!("+ def_id: {:?}", def_id)); |
408 | self.push(&format!("+ substs: {:#?}", substs)); | |
94b46f34 | 409 | self.push(&format!("+ movability: {:?}", movability)); |
ff7c6d11 XL |
410 | } |
411 | ||
b7449926 XL |
412 | AggregateKind::Adt(_, _, _, Some(user_ty), _) => { |
413 | self.push("adt"); | |
414 | self.push(&format!("+ user_ty: {:?}", user_ty)); | |
415 | } | |
416 | ||
ff7c6d11 | 417 | _ => {} |
ba9703b0 | 418 | } |
ff7c6d11 XL |
419 | } |
420 | } | |
421 | } | |
422 | ||
dc9dc135 | 423 | fn comment(tcx: TyCtxt<'_>, SourceInfo { span, scope }: SourceInfo) -> String { |
dfeec247 | 424 | format!("scope {} at {}", scope.index(), tcx.sess.source_map().span_to_string(span)) |
54a0048b SL |
425 | } |
426 | ||
48663c56 | 427 | /// Prints local variables in a scope tree. |
ff7c6d11 | 428 | fn write_scope_tree( |
dc9dc135 XL |
429 | tcx: TyCtxt<'_>, |
430 | body: &Body<'_>, | |
94b46f34 | 431 | scope_tree: &FxHashMap<SourceScope, Vec<SourceScope>>, |
0531ce1d | 432 | w: &mut dyn Write, |
94b46f34 | 433 | parent: SourceScope, |
ff7c6d11 XL |
434 | depth: usize, |
435 | ) -> io::Result<()> { | |
3157f602 | 436 | let indent = depth * INDENT.len(); |
a7813a04 | 437 | |
60c5eb7d XL |
438 | // Local variable debuginfo. |
439 | for var_debug_info in &body.var_debug_info { | |
440 | if var_debug_info.source_info.scope != parent { | |
441 | // Not declared in this scope. | |
442 | continue; | |
443 | } | |
444 | ||
445 | let indented_debug_info = format!( | |
446 | "{0:1$}debug {2} => {3:?};", | |
dfeec247 | 447 | INDENT, indent, var_debug_info.name, var_debug_info.place, |
60c5eb7d XL |
448 | ); |
449 | ||
450 | writeln!( | |
451 | w, | |
452 | "{0:1$} // in {2}", | |
453 | indented_debug_info, | |
454 | ALIGN, | |
455 | comment(tcx, var_debug_info.source_info), | |
456 | )?; | |
457 | } | |
458 | ||
f9f354fc | 459 | // Local variable types. |
dc9dc135 | 460 | for (local, local_decl) in body.local_decls.iter_enumerated() { |
dfeec247 | 461 | if (1..body.arg_count + 1).contains(&local.index()) { |
48663c56 XL |
462 | // Skip over argument locals, they're printed in the signature. |
463 | continue; | |
464 | } | |
465 | ||
466 | if local_decl.source_info.scope != parent { | |
467 | // Not declared in this scope. | |
468 | continue; | |
469 | } | |
470 | ||
dfeec247 | 471 | let mut_str = if local_decl.mutability == Mutability::Mut { "mut " } else { "" }; |
48663c56 | 472 | |
dfeec247 XL |
473 | let mut indented_decl = |
474 | format!("{0:1$}let {2}{3:?}: {4:?}", INDENT, indent, mut_str, local, local_decl.ty); | |
f9f354fc XL |
475 | if let Some(user_ty) = &local_decl.user_ty { |
476 | for user_ty in user_ty.projections() { | |
477 | write!(indented_decl, " as {:?}", user_ty).unwrap(); | |
478 | } | |
48663c56 XL |
479 | } |
480 | indented_decl.push_str(";"); | |
481 | ||
dfeec247 | 482 | let local_name = |
74b04a01 | 483 | if local == RETURN_PLACE { " return place".to_string() } else { String::new() }; |
48663c56 XL |
484 | |
485 | writeln!( | |
486 | w, | |
487 | "{0:1$} //{2} in {3}", | |
488 | indented_decl, | |
489 | ALIGN, | |
490 | local_name, | |
491 | comment(tcx, local_decl.source_info), | |
492 | )?; | |
493 | } | |
494 | ||
a7813a04 | 495 | let children = match scope_tree.get(&parent) { |
74b04a01 | 496 | Some(children) => children, |
a7813a04 XL |
497 | None => return Ok(()), |
498 | }; | |
499 | ||
3157f602 | 500 | for &child in children { |
dc9dc135 | 501 | assert_eq!(body.source_scopes[child].parent_scope, Some(parent)); |
3157f602 | 502 | writeln!(w, "{0:1$}scope {2} {{", "", indent, child.index())?; |
dc9dc135 | 503 | write_scope_tree(tcx, body, scope_tree, w, child, depth + 1)?; |
3157f602 | 504 | writeln!(w, "{0:1$}}}", "", depth * INDENT.len())?; |
54a0048b | 505 | } |
a7813a04 | 506 | |
54a0048b SL |
507 | Ok(()) |
508 | } | |
509 | ||
9cc50fc6 SL |
510 | /// Write out a human-readable textual representation of the MIR's `fn` type and the types of its |
511 | /// local variables (both user-defined bindings and compiler temporaries). | |
dc9dc135 XL |
512 | pub fn write_mir_intro<'tcx>( |
513 | tcx: TyCtxt<'tcx>, | |
9fa01778 | 514 | src: MirSource<'tcx>, |
dc9dc135 | 515 | body: &Body<'_>, |
0531ce1d | 516 | w: &mut dyn Write, |
ff7c6d11 | 517 | ) -> io::Result<()> { |
dc9dc135 | 518 | write_mir_sig(tcx, src, body, w)?; |
2c00a5a8 | 519 | writeln!(w, "{{")?; |
3157f602 XL |
520 | |
521 | // construct a scope tree and write it out | |
0bf4aa26 | 522 | let mut scope_tree: FxHashMap<SourceScope, Vec<SourceScope>> = Default::default(); |
dc9dc135 | 523 | for (index, scope_data) in body.source_scopes.iter().enumerate() { |
3157f602 | 524 | if let Some(parent) = scope_data.parent_scope { |
dfeec247 | 525 | scope_tree.entry(parent).or_default().push(SourceScope::new(index)); |
3157f602 XL |
526 | } else { |
527 | // Only the argument scope has no parent, because it's the root. | |
94b46f34 | 528 | assert_eq!(index, OUTERMOST_SOURCE_SCOPE.index()); |
3157f602 XL |
529 | } |
530 | } | |
531 | ||
dc9dc135 | 532 | write_scope_tree(tcx, body, &scope_tree, w, OUTERMOST_SOURCE_SCOPE, 1)?; |
3157f602 | 533 | |
c30ab7b3 | 534 | // Add an empty line before the first block is printed. |
ba9703b0 XL |
535 | writeln!(w)?; |
536 | ||
537 | Ok(()) | |
538 | } | |
539 | ||
540 | /// Find all `AllocId`s mentioned (recursively) in the MIR body and print their corresponding | |
541 | /// allocations. | |
542 | pub fn write_allocations<'tcx>( | |
543 | tcx: TyCtxt<'tcx>, | |
544 | body: &Body<'_>, | |
545 | w: &mut dyn Write, | |
546 | ) -> io::Result<()> { | |
547 | fn alloc_ids_from_alloc(alloc: &Allocation) -> impl DoubleEndedIterator<Item = AllocId> + '_ { | |
548 | alloc.relocations().values().map(|(_, id)| *id) | |
549 | } | |
550 | fn alloc_ids_from_const(val: ConstValue<'_>) -> impl Iterator<Item = AllocId> + '_ { | |
551 | match val { | |
552 | ConstValue::Scalar(interpret::Scalar::Ptr(ptr)) => { | |
553 | Either::Left(Either::Left(std::iter::once(ptr.alloc_id))) | |
554 | } | |
555 | ConstValue::Scalar(interpret::Scalar::Raw { .. }) => { | |
556 | Either::Left(Either::Right(std::iter::empty())) | |
557 | } | |
558 | ConstValue::ByRef { alloc, .. } | ConstValue::Slice { data: alloc, .. } => { | |
559 | Either::Right(alloc_ids_from_alloc(alloc)) | |
560 | } | |
561 | } | |
562 | } | |
563 | struct CollectAllocIds(BTreeSet<AllocId>); | |
564 | impl<'tcx> TypeVisitor<'tcx> for CollectAllocIds { | |
565 | fn visit_const(&mut self, c: &'tcx ty::Const<'tcx>) -> bool { | |
566 | if let ty::ConstKind::Value(val) = c.val { | |
567 | self.0.extend(alloc_ids_from_const(val)); | |
568 | } | |
569 | c.super_visit_with(self) | |
570 | } | |
571 | } | |
572 | let mut visitor = CollectAllocIds(Default::default()); | |
573 | body.visit_with(&mut visitor); | |
574 | // `seen` contains all seen allocations, including the ones we have *not* printed yet. | |
575 | // The protocol is to first `insert` into `seen`, and only if that returns `true` | |
576 | // then push to `todo`. | |
577 | let mut seen = visitor.0; | |
578 | let mut todo: Vec<_> = seen.iter().copied().collect(); | |
579 | while let Some(id) = todo.pop() { | |
580 | let mut write_allocation_track_relocs = | |
581 | |w: &mut dyn Write, alloc: &Allocation| -> io::Result<()> { | |
582 | // `.rev()` because we are popping them from the back of the `todo` vector. | |
583 | for id in alloc_ids_from_alloc(alloc).rev() { | |
584 | if seen.insert(id) { | |
585 | todo.push(id); | |
586 | } | |
587 | } | |
588 | write_allocation(tcx, alloc, w) | |
589 | }; | |
590 | write!(w, "\n{}", id)?; | |
f9f354fc | 591 | match tcx.get_global_alloc(id) { |
ba9703b0 XL |
592 | // This can't really happen unless there are bugs, but it doesn't cost us anything to |
593 | // gracefully handle it and allow buggy rustc to be debugged via allocation printing. | |
594 | None => write!(w, " (deallocated)")?, | |
595 | Some(GlobalAlloc::Function(inst)) => write!(w, " (fn: {})", inst)?, | |
596 | Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => { | |
597 | match tcx.const_eval_poly(did) { | |
598 | Ok(ConstValue::ByRef { alloc, .. }) => { | |
599 | write!(w, " (static: {}, ", tcx.def_path_str(did))?; | |
600 | write_allocation_track_relocs(w, alloc)?; | |
601 | } | |
602 | Ok(_) => { | |
603 | span_bug!(tcx.def_span(did), " static item without `ByRef` initializer") | |
604 | } | |
605 | Err(_) => write!( | |
606 | w, | |
607 | " (static: {}, error during initializer evaluation)", | |
608 | tcx.def_path_str(did) | |
609 | )?, | |
610 | } | |
611 | } | |
612 | Some(GlobalAlloc::Static(did)) => { | |
613 | write!(w, " (extern static: {})", tcx.def_path_str(did))? | |
614 | } | |
615 | Some(GlobalAlloc::Memory(alloc)) => { | |
616 | write!(w, " (")?; | |
617 | write_allocation_track_relocs(w, alloc)? | |
618 | } | |
619 | } | |
620 | writeln!(w)?; | |
621 | } | |
622 | Ok(()) | |
623 | } | |
624 | ||
625 | /// Dumps the size and metadata and content of an allocation to the given writer. | |
626 | /// The expectation is that the caller first prints other relevant metadata, so the exact | |
627 | /// format of this function is (*without* leading or trailing newline): | |
628 | /// ``` | |
629 | /// size: {}, align: {}) { | |
630 | /// <bytes> | |
631 | /// } | |
632 | /// ``` | |
633 | /// | |
634 | /// The byte format is similar to how hex editors print bytes. Each line starts with the address of | |
635 | /// the start of the line, followed by all bytes in hex format (space separated). | |
636 | /// If the allocation is small enough to fit into a single line, no start address is given. | |
637 | /// After the hex dump, an ascii dump follows, replacing all unprintable characters (control | |
638 | /// characters or characters whose value is larger than 127) with a `.` | |
639 | /// This also prints relocations adequately. | |
f9f354fc | 640 | pub fn write_allocation<Tag: Copy + Debug, Extra>( |
ba9703b0 XL |
641 | tcx: TyCtxt<'tcx>, |
642 | alloc: &Allocation<Tag, Extra>, | |
643 | w: &mut dyn Write, | |
644 | ) -> io::Result<()> { | |
645 | write!(w, "size: {}, align: {})", alloc.size.bytes(), alloc.align.bytes())?; | |
646 | if alloc.size == Size::ZERO { | |
647 | // We are done. | |
648 | return write!(w, " {{}}"); | |
649 | } | |
650 | // Write allocation bytes. | |
651 | writeln!(w, " {{")?; | |
652 | write_allocation_bytes(tcx, alloc, w, " ")?; | |
653 | write!(w, "}}")?; | |
654 | Ok(()) | |
655 | } | |
656 | ||
657 | fn write_allocation_endline(w: &mut dyn Write, ascii: &str) -> io::Result<()> { | |
658 | for _ in 0..(BYTES_PER_LINE - ascii.chars().count()) { | |
659 | write!(w, " ")?; | |
660 | } | |
661 | writeln!(w, " │ {}", ascii) | |
662 | } | |
663 | ||
664 | /// Number of bytes to print per allocation hex dump line. | |
665 | const BYTES_PER_LINE: usize = 16; | |
666 | ||
667 | /// Prints the line start address and returns the new line start address. | |
668 | fn write_allocation_newline( | |
669 | w: &mut dyn Write, | |
670 | mut line_start: Size, | |
671 | ascii: &str, | |
672 | pos_width: usize, | |
673 | prefix: &str, | |
674 | ) -> io::Result<Size> { | |
675 | write_allocation_endline(w, ascii)?; | |
676 | line_start += Size::from_bytes(BYTES_PER_LINE); | |
677 | write!(w, "{}0x{:02$x} │ ", prefix, line_start.bytes(), pos_width)?; | |
678 | Ok(line_start) | |
679 | } | |
680 | ||
681 | /// The `prefix` argument allows callers to add an arbitrary prefix before each line (even if there | |
682 | /// is only one line). Note that your prefix should contain a trailing space as the lines are | |
683 | /// printed directly after it. | |
f9f354fc | 684 | fn write_allocation_bytes<Tag: Copy + Debug, Extra>( |
ba9703b0 XL |
685 | tcx: TyCtxt<'tcx>, |
686 | alloc: &Allocation<Tag, Extra>, | |
687 | w: &mut dyn Write, | |
688 | prefix: &str, | |
689 | ) -> io::Result<()> { | |
690 | let num_lines = alloc.size.bytes_usize().saturating_sub(BYTES_PER_LINE); | |
691 | // Number of chars needed to represent all line numbers. | |
692 | let pos_width = format!("{:x}", alloc.size.bytes()).len(); | |
693 | ||
694 | if num_lines > 0 { | |
695 | write!(w, "{}0x{:02$x} │ ", prefix, 0, pos_width)?; | |
696 | } else { | |
697 | write!(w, "{}", prefix)?; | |
698 | } | |
699 | ||
700 | let mut i = Size::ZERO; | |
701 | let mut line_start = Size::ZERO; | |
702 | ||
703 | let ptr_size = tcx.data_layout.pointer_size; | |
704 | ||
705 | let mut ascii = String::new(); | |
706 | ||
707 | let oversized_ptr = |target: &mut String, width| { | |
708 | if target.len() > width { | |
709 | write!(target, " ({} ptr bytes)", ptr_size.bytes()).unwrap(); | |
710 | } | |
711 | }; | |
712 | ||
713 | while i < alloc.size { | |
714 | // The line start already has a space. While we could remove that space from the line start | |
715 | // printing and unconditionally print a space here, that would cause the single-line case | |
716 | // to have a single space before it, which looks weird. | |
717 | if i != line_start { | |
718 | write!(w, " ")?; | |
719 | } | |
f9f354fc | 720 | if let Some(&(tag, target_id)) = alloc.relocations().get(&i) { |
ba9703b0 XL |
721 | // Memory with a relocation must be defined |
722 | let j = i.bytes_usize(); | |
723 | let offset = | |
724 | alloc.inspect_with_undef_and_ptr_outside_interpreter(j..j + ptr_size.bytes_usize()); | |
725 | let offset = read_target_uint(tcx.data_layout.endian, offset).unwrap(); | |
f9f354fc | 726 | let offset = Size::from_bytes(offset); |
ba9703b0 | 727 | let relocation_width = |bytes| bytes * 3; |
f9f354fc XL |
728 | let ptr = Pointer::new_with_tag(target_id, offset, tag); |
729 | let mut target = format!("{:?}", ptr); | |
730 | if target.len() > relocation_width(ptr_size.bytes_usize() - 1) { | |
731 | // This is too long, try to save some space. | |
732 | target = format!("{:#?}", ptr); | |
733 | } | |
ba9703b0 XL |
734 | if ((i - line_start) + ptr_size).bytes_usize() > BYTES_PER_LINE { |
735 | // This branch handles the situation where a relocation starts in the current line | |
736 | // but ends in the next one. | |
737 | let remainder = Size::from_bytes(BYTES_PER_LINE) - (i - line_start); | |
738 | let overflow = ptr_size - remainder; | |
739 | let remainder_width = relocation_width(remainder.bytes_usize()) - 2; | |
740 | let overflow_width = relocation_width(overflow.bytes_usize() - 1) + 1; | |
741 | ascii.push('╾'); | |
742 | for _ in 0..remainder.bytes() - 1 { | |
743 | ascii.push('─'); | |
744 | } | |
745 | if overflow_width > remainder_width && overflow_width >= target.len() { | |
746 | // The case where the relocation fits into the part in the next line | |
747 | write!(w, "╾{0:─^1$}", "", remainder_width)?; | |
748 | line_start = | |
749 | write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?; | |
750 | ascii.clear(); | |
751 | write!(w, "{0:─^1$}╼", target, overflow_width)?; | |
752 | } else { | |
753 | oversized_ptr(&mut target, remainder_width); | |
754 | write!(w, "╾{0:─^1$}", target, remainder_width)?; | |
755 | line_start = | |
756 | write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?; | |
757 | write!(w, "{0:─^1$}╼", "", overflow_width)?; | |
758 | ascii.clear(); | |
759 | } | |
760 | for _ in 0..overflow.bytes() - 1 { | |
761 | ascii.push('─'); | |
762 | } | |
763 | ascii.push('╼'); | |
764 | i += ptr_size; | |
765 | continue; | |
766 | } else { | |
767 | // This branch handles a relocation that starts and ends in the current line. | |
768 | let relocation_width = relocation_width(ptr_size.bytes_usize() - 1); | |
769 | oversized_ptr(&mut target, relocation_width); | |
770 | ascii.push('╾'); | |
771 | write!(w, "╾{0:─^1$}╼", target, relocation_width)?; | |
772 | for _ in 0..ptr_size.bytes() - 2 { | |
773 | ascii.push('─'); | |
774 | } | |
775 | ascii.push('╼'); | |
776 | i += ptr_size; | |
777 | } | |
f9f354fc | 778 | } else if alloc.init_mask().is_range_initialized(i, i + Size::from_bytes(1)).is_ok() { |
ba9703b0 XL |
779 | let j = i.bytes_usize(); |
780 | ||
781 | // Checked definedness (and thus range) and relocations. This access also doesn't | |
782 | // influence interpreter execution but is only for debugging. | |
783 | let c = alloc.inspect_with_undef_and_ptr_outside_interpreter(j..j + 1)[0]; | |
784 | write!(w, "{:02x}", c)?; | |
785 | if c.is_ascii_control() || c >= 0x80 { | |
786 | ascii.push('.'); | |
787 | } else { | |
788 | ascii.push(char::from(c)); | |
789 | } | |
790 | i += Size::from_bytes(1); | |
791 | } else { | |
792 | write!(w, "__")?; | |
793 | ascii.push('░'); | |
794 | i += Size::from_bytes(1); | |
795 | } | |
796 | // Print a new line header if the next line still has some bytes to print. | |
797 | if i == line_start + Size::from_bytes(BYTES_PER_LINE) && i != alloc.size { | |
798 | line_start = write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?; | |
799 | ascii.clear(); | |
800 | } | |
801 | } | |
802 | write_allocation_endline(w, &ascii)?; | |
c30ab7b3 SL |
803 | |
804 | Ok(()) | |
3157f602 XL |
805 | } |
806 | ||
9fa01778 | 807 | fn write_mir_sig( |
dc9dc135 | 808 | tcx: TyCtxt<'_>, |
9fa01778 | 809 | src: MirSource<'tcx>, |
dc9dc135 | 810 | body: &Body<'_>, |
9fa01778 XL |
811 | w: &mut dyn Write, |
812 | ) -> io::Result<()> { | |
dfeec247 | 813 | use rustc_hir::def::DefKind; |
9fa01778 XL |
814 | |
815 | trace!("write_mir_sig: {:?}", src.instance); | |
48663c56 XL |
816 | let kind = tcx.def_kind(src.def_id()); |
817 | let is_function = match kind { | |
f9f354fc | 818 | DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, |
9fa01778 XL |
819 | _ => tcx.is_closure(src.def_id()), |
820 | }; | |
48663c56 | 821 | match (kind, src.promoted) { |
9fa01778 | 822 | (_, Some(i)) => write!(w, "{:?} in ", i)?, |
f9f354fc XL |
823 | (DefKind::Const | DefKind::AssocConst, _) => write!(w, "const ")?, |
824 | (DefKind::Static, _) => { | |
dfeec247 XL |
825 | write!(w, "static {}", if tcx.is_mutable_static(src.def_id()) { "mut " } else { "" })? |
826 | } | |
9fa01778 | 827 | (_, _) if is_function => write!(w, "fn ")?, |
f9f354fc | 828 | (DefKind::AnonConst, _) => {} // things like anon const, not an item |
48663c56 | 829 | _ => bug!("Unexpected def kind {:?}", kind), |
9cc50fc6 SL |
830 | } |
831 | ||
532ac7d7 | 832 | ty::print::with_forced_impl_filename_line(|| { |
ff7c6d11 | 833 | // see notes on #41697 elsewhere |
ba9703b0 | 834 | write!(w, "{}", tcx.def_path_str(src.def_id())) |
7cac9316 | 835 | })?; |
a7813a04 | 836 | |
9fa01778 XL |
837 | if src.promoted.is_none() && is_function { |
838 | write!(w, "(")?; | |
ea8adc8c | 839 | |
9fa01778 | 840 | // fn argument types. |
dc9dc135 | 841 | for (i, arg) in body.args_iter().enumerate() { |
9fa01778 XL |
842 | if i != 0 { |
843 | write!(w, ", ")?; | |
a7813a04 | 844 | } |
dc9dc135 | 845 | write!(w, "{:?}: {}", Place::from(arg), body.local_decls[arg].ty)?; |
ea8adc8c | 846 | } |
9fa01778 | 847 | |
dc9dc135 | 848 | write!(w, ") -> {}", body.return_ty())?; |
9fa01778 | 849 | } else { |
dc9dc135 XL |
850 | assert_eq!(body.arg_count, 0); |
851 | write!(w, ": {} =", body.return_ty())?; | |
9cc50fc6 | 852 | } |
2c00a5a8 | 853 | |
dc9dc135 | 854 | if let Some(yield_ty) = body.yield_ty { |
2c00a5a8 XL |
855 | writeln!(w)?; |
856 | writeln!(w, "yields {}", yield_ty)?; | |
857 | } | |
858 | ||
9fa01778 XL |
859 | write!(w, " ")?; |
860 | // Next thing that gets printed is the opening { | |
861 | ||
2c00a5a8 | 862 | Ok(()) |
3157f602 | 863 | } |
9cc50fc6 | 864 | |
f035d41b XL |
865 | fn write_user_type_annotations( |
866 | tcx: TyCtxt<'_>, | |
867 | body: &Body<'_>, | |
868 | w: &mut dyn Write, | |
869 | ) -> io::Result<()> { | |
dc9dc135 | 870 | if !body.user_type_annotations.is_empty() { |
0731742a XL |
871 | writeln!(w, "| User Type Annotations")?; |
872 | } | |
dc9dc135 | 873 | for (index, annotation) in body.user_type_annotations.iter_enumerated() { |
f035d41b XL |
874 | writeln!( |
875 | w, | |
876 | "| {:?}: {:?} at {}", | |
877 | index.index(), | |
878 | annotation.user_ty, | |
879 | tcx.sess.source_map().span_to_string(annotation.span) | |
880 | )?; | |
0731742a | 881 | } |
dc9dc135 | 882 | if !body.user_type_annotations.is_empty() { |
0731742a XL |
883 | writeln!(w, "|")?; |
884 | } | |
885 | Ok(()) | |
886 | } | |
887 | ||
dc9dc135 | 888 | pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option<DefId>) -> Vec<DefId> { |
f9f354fc XL |
889 | if let Some(i) = single { |
890 | vec![i] | |
891 | } else { | |
892 | tcx.mir_keys(LOCAL_CRATE).iter().map(|def_id| def_id.to_def_id()).collect() | |
893 | } | |
7cac9316 | 894 | } |