rust/compiler/rustc_middle/src/mir/pretty.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1260 lines
47 KiB
Rust
Raw Normal View History

2020-04-26 15:58:53 +02:00
use std::collections::BTreeSet;
use std::fmt::Display;
2020-04-26 15:58:53 +02:00
use std::fmt::Write as _;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use super::graphviz::write_mir_fn_graphviz;
use super::spanview::write_mir_fn_spanview;
2020-03-11 11:49:00 +01:00
use either::Either;
use rustc_data_structures::fx::FxHashMap;
2021-05-11 12:26:53 +02:00
use rustc_hir::def_id::DefId;
use rustc_index::Idx;
2020-03-29 17:19:48 +02:00
use rustc_middle::mir::interpret::{
alloc_range, read_target_uint, AllocBytes, AllocId, Allocation, ConstAllocation, ConstValue,
GlobalAlloc, Pointer, Provenance,
2020-03-29 17:19:48 +02:00
};
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
use rustc_target::abi::Size;
const INDENT: &str = " ";
/// Alignment for lining up comments following MIR statements
pub(crate) const ALIGN: usize = 40;
/// An indication of where we are in the control flow graph. Used for printing
/// extra information in `dump_mir`
pub enum PassWhere {
/// We have not started dumping the control flow graph, but we are about to.
BeforeCFG,
/// We just finished dumping the control flow graph. This is right before EOF
AfterCFG,
/// We are about to start dumping the given basic block.
BeforeBlock(BasicBlock),
/// We are just about to dump the given statement or terminator.
BeforeLocation(Location),
/// We just dumped the given statement or terminator.
AfterLocation(Location),
2018-06-29 06:47:12 -04:00
/// We just dumped the terminator for a block but not the closing `}`.
AfterTerminator(BasicBlock),
}
/// If the session is properly configured, dumps a human-readable
/// representation of the mir into:
///
2016-04-13 16:16:14 +05:30
/// ```text
/// rustc.node<node_id>.<pass_num>.<pass_name>.<disambiguator>
/// ```
///
/// Output from this function is controlled by passing `-Z dump-mir=<filter>`,
/// where `<filter>` takes the following forms:
///
/// - `all` -- dump MIR for all fns, all passes, all everything
2017-11-17 14:08:47 -05:00
/// - a filter defined by a set of substrings combined with `&` and `|`
/// (`&` has higher precedence). At least one of the `|`-separated groups
/// must match; an `|`-separated group matches if all of its `&`-separated
/// substrings are matched.
///
/// Example:
///
/// - `nll` == match if `nll` appears in the name
/// - `foo & nll` == match if `foo` and `nll` both appear in the name
/// - `foo & nll | typeck` == match if `foo` and `nll` both appear in the name
/// or `typeck` appears in the name.
/// - `foo & nll | bar & typeck` == match if `foo` and `nll` both appear in the name
/// or `typeck` and `bar` both appear in the name.
#[inline]
2019-06-14 00:48:52 +03:00
pub fn dump_mir<'tcx, F>(
tcx: TyCtxt<'tcx>,
pass_num: bool,
pass_name: &str,
2018-02-23 10:15:26 -08:00
disambiguator: &dyn Display,
body: &Body<'tcx>,
extra_data: F,
) where
2018-02-23 10:15:26 -08:00
F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>,
{
if !dump_enabled(tcx, pass_name, body.source.def_id()) {
return;
}
dump_matched_mir_node(tcx, pass_num, pass_name, disambiguator, body, extra_data);
}
2022-12-20 22:10:40 +01:00
pub fn dump_enabled(tcx: TyCtxt<'_>, pass_name: &str, def_id: DefId) -> bool {
let Some(ref filters) = tcx.sess.opts.unstable_opts.dump_mir else {
2022-02-19 00:48:49 +01:00
return false;
};
// see notes on #41697 below
let node_path = ty::print::with_forced_impl_filename_line!(tcx.def_path_str(def_id));
filters.split('|').any(|or_filter| {
or_filter.split('&').all(|and_filter| {
2021-05-31 20:15:52 +02:00
let and_filter_trimmed = and_filter.trim();
and_filter_trimmed == "all"
|| pass_name.contains(and_filter_trimmed)
|| node_path.contains(and_filter_trimmed)
2017-11-17 14:08:47 -05:00
})
})
}
// #41697 -- we use `with_forced_impl_filename_line()` because
// `def_path_str()` would otherwise trigger `type_of`, and this can
// run while we are already attempting to evaluate `type_of`.
2019-06-14 00:48:52 +03:00
fn dump_matched_mir_node<'tcx, F>(
tcx: TyCtxt<'tcx>,
pass_num: bool,
pass_name: &str,
2018-02-23 10:15:26 -08:00
disambiguator: &dyn Display,
body: &Body<'tcx>,
mut extra_data: F,
) where
2018-02-23 10:15:26 -08:00
F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>,
{
2019-02-08 20:08:08 +09:00
let _: io::Result<()> = try {
let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, body)?;
// see notes on #41697 above
let def_path =
ty::print::with_forced_impl_filename_line!(tcx.def_path_str(body.source.def_id()));
// ignore-tidy-odd-backticks the literal below is fine
write!(file, "// MIR for `{def_path}")?;
match body.source.promoted {
2020-03-11 11:49:00 +01:00
None => write!(file, "`")?,
Some(promoted) => write!(file, "::{promoted:?}`")?,
2020-03-11 11:49:00 +01:00
}
writeln!(file, " {disambiguator} {pass_name}")?;
2021-01-17 13:27:05 +01:00
if let Some(ref layout) = body.generator_layout() {
writeln!(file, "/* generator_layout = {layout:#?} */")?;
}
writeln!(file)?;
extra_data(PassWhere::BeforeCFG, &mut file)?;
write_user_type_annotations(tcx, body, &mut file)?;
write_mir_fn(tcx, body, &mut extra_data, &mut file)?;
extra_data(PassWhere::AfterCFG, &mut file)?;
2018-05-10 12:02:19 -06:00
};
if tcx.sess.opts.unstable_opts.dump_mir_graphviz {
2019-02-08 20:08:08 +09:00
let _: io::Result<()> = try {
let mut file = create_dump_file(tcx, "dot", pass_num, pass_name, disambiguator, body)?;
write_mir_fn_graphviz(tcx, body, false, &mut file)?;
2018-05-10 12:02:19 -06:00
};
}
if let Some(spanview) = tcx.sess.opts.unstable_opts.dump_mir_spanview {
let _: io::Result<()> = try {
let file_basename = dump_file_basename(tcx, pass_num, pass_name, disambiguator, body);
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
let mut file = create_dump_file_with_basename(tcx, &file_basename, "html")?;
if body.source.def_id().is_local() {
write_mir_fn_spanview(tcx, body, spanview, &file_basename, &mut file)?;
}
};
}
}
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
/// Returns the file basename portion (without extension) of a filename path
/// where we should dump a MIR representation output files.
fn dump_file_basename<'tcx>(
tcx: TyCtxt<'tcx>,
pass_num: bool,
pass_name: &str,
2018-02-23 10:15:26 -08:00
disambiguator: &dyn Display,
body: &Body<'tcx>,
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
) -> String {
let source = body.source;
let promotion_id = match source.promoted {
Some(id) => format!("-{id:?}"),
None => String::new(),
};
let pass_num = if tcx.sess.opts.unstable_opts.dump_mir_exclude_pass_number {
2018-07-28 14:40:32 +02:00
String::new()
} else {
if pass_num {
format!(".{:03}-{:03}", body.phase.phase_index(), body.pass_count)
} else {
".-------".to_string()
}
};
2020-07-27 21:22:43 +02:00
let crate_name = tcx.crate_name(source.def_id().krate);
let item_name = tcx.def_path(source.def_id()).to_filename_friendly_no_crate();
// All drop shims have the same DefId, so we have to add the type
// to get unique file names.
let shim_disambiguator = match source.instance {
ty::InstanceDef::DropGlue(_, Some(ty)) => {
// Unfortunately, pretty-printed typed are not very filename-friendly.
// We dome some filtering.
let mut s = ".".to_owned();
s.extend(ty.to_string().chars().filter_map(|c| match c {
' ' => None,
':' | '<' | '>' => Some('_'),
c => Some(c),
}));
s
}
_ => String::new(),
};
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
format!(
"{crate_name}.{item_name}{shim_disambiguator}{promotion_id}{pass_num}.{pass_name}.{disambiguator}",
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
)
}
/// Returns the path to the filename where we should dump a given MIR.
/// Also used by other bits of code (e.g., NLL inference) that dump
/// graphviz data or other things.
fn dump_path(tcx: TyCtxt<'_>, basename: &str, extension: &str) -> PathBuf {
let mut file_path = PathBuf::new();
file_path.push(Path::new(&tcx.sess.opts.unstable_opts.dump_mir_dir));
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
let file_name = format!("{basename}.{extension}",);
file_path.push(&file_name);
file_path
}
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
/// Attempts to open the MIR dump file with the given name and extension.
fn create_dump_file_with_basename(
tcx: TyCtxt<'_>,
file_basename: &str,
extension: &str,
) -> io::Result<io::BufWriter<fs::File>> {
let file_path = dump_path(tcx, file_basename, extension);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).map_err(|e| {
io::Error::new(
e.kind(),
format!("IO error creating MIR dump directory: {parent:?}; {e}"),
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
)
})?;
}
Ok(io::BufWriter::new(fs::File::create(&file_path).map_err(|e| {
io::Error::new(e.kind(), format!("IO error creating MIR dump file: {file_path:?}; {e}"))
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
})?))
}
/// Attempts to open a file where we should dump a given MIR or other
/// bit of MIR-related data. Used by `mir-dump`, but also by other
/// bits of code (e.g., NLL inference) that dump graphviz data or
/// other things, and hence takes the extension as an argument.
pub fn create_dump_file<'tcx>(
tcx: TyCtxt<'tcx>,
extension: &str,
pass_num: bool,
pass_name: &str,
2018-02-23 10:15:26 -08:00
disambiguator: &dyn Display,
body: &Body<'tcx>,
) -> io::Result<io::BufWriter<fs::File>> {
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
create_dump_file_with_basename(
tcx,
&dump_file_basename(tcx, pass_num, pass_name, disambiguator, body),
Updates to experimental coverage counter injection This is a combination of 18 commits. Commit #2: Additional examples and some small improvements. Commit #3: fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted. Commit #4: added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment. Commit #5: Fixed rare issues after testing on real-world crate Commit #6: Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview. Commit #7: Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines. Commit #8: renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation. Commit #9: test coverage of inline generic struct function Commit #10: Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal Commit #11: refactoring based on feedback * refactored `fn coverage_spans()`. * changed the way I expand an empty coverage span to improve performance * fixed a typo that I had accidently left in, in visit.rs Commit #12: Optimized use of SourceMap and SourceFile Commit #13: Fixed a regression, and synched with upstream Some generated test file names changed due to some new change upstream. Commit #14: Stripping out crate disambiguators from demangled names These can vary depending on the test platform. Commit #15: Ignore llvm-cov show diff on test with generics, expand IO error message Tests with generics produce llvm-cov show results with demangled names that can include an unstable "crate disambiguator" (hex value). The value changes when run in the Rust CI Windows environment. I added a sed filter to strip them out (in a prior commit), but sed also appears to fail in the same environment. Until I can figure out a workaround, I'm just going to ignore this specific test result. I added a FIXME to follow up later, but it's not that critical. I also saw an error with Windows GNU, but the IO error did not specify a path for the directory or file that triggered the error. I updated the error messages to provide more info for next, time but also noticed some other tests with similar steps did not fail. Looks spurious. Commit #16: Modify rust-demangler to strip disambiguators by default Commit #17: Remove std::process::exit from coverage tests Due to Issue #77553, programs that call std::process::exit() do not generate coverage results on Windows MSVC. Commit #18: fix: test file paths exceeding Windows max path len
2020-09-01 16:15:17 -07:00
extension,
)
}
/// Write out a human-readable textual representation for the given MIR.
2019-06-14 00:48:52 +03:00
pub fn write_mir_pretty<'tcx>(
tcx: TyCtxt<'tcx>,
single: Option<DefId>,
2018-02-23 10:15:26 -08:00
w: &mut dyn Write,
) -> io::Result<()> {
writeln!(w, "// WARNING: This output format is intended for human consumers only")?;
writeln!(w, "// and is subject to change without notice. Knock yourself out.")?;
let mut first = true;
for def_id in dump_mir_def_ids(tcx, single) {
if first {
first = false;
} else {
// Put empty lines between all items
writeln!(w)?;
}
let render_body = |w: &mut dyn Write, body| -> io::Result<()> {
write_mir_fn(tcx, body, &mut |_, _| Ok(()), w)?;
for body in tcx.promoted_mir(def_id) {
writeln!(w)?;
write_mir_fn(tcx, body, &mut |_, _| Ok(()), w)?;
}
Ok(())
};
// For `const fn` we want to render both the optimized MIR and the MIR for ctfe.
if tcx.is_const_fn_raw(def_id) {
render_body(w, tcx.optimized_mir(def_id))?;
writeln!(w)?;
writeln!(w, "// MIR FOR CTFE")?;
// Do not use `render_body`, as that would render the promoteds again, but these
// are shared between mir_for_ctfe and optimized_mir
write_mir_fn(tcx, tcx.mir_for_ctfe(def_id), &mut |_, _| Ok(()), w)?;
} else {
2022-05-08 15:53:19 +02:00
let instance_mir = tcx.instance_mir(ty::InstanceDef::Item(def_id));
render_body(w, instance_mir)?;
}
}
Ok(())
}
/// Write out a human-readable textual representation for the given function.
2019-06-14 00:48:52 +03:00
pub fn write_mir_fn<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
extra_data: &mut F,
2018-02-23 10:15:26 -08:00
w: &mut dyn Write,
) -> io::Result<()>
where
2018-02-23 10:15:26 -08:00
F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>,
{
write_mir_intro(tcx, body, w)?;
for block in body.basic_blocks.indices() {
extra_data(PassWhere::BeforeBlock(block), w)?;
write_basic_block(tcx, block, body, extra_data, w)?;
if block.index() + 1 != body.basic_blocks.len() {
writeln!(w)?;
}
}
2016-03-19 07:13:54 -04:00
writeln!(w, "}}")?;
2020-03-11 11:49:00 +01:00
write_allocations(tcx, body, w)?;
2016-03-19 07:13:54 -04:00
Ok(())
}
/// Write out a human-readable textual representation for the given basic block.
2019-06-14 00:48:52 +03:00
pub fn write_basic_block<'tcx, F>(
tcx: TyCtxt<'tcx>,
block: BasicBlock,
body: &Body<'tcx>,
extra_data: &mut F,
2018-02-23 10:15:26 -08:00
w: &mut dyn Write,
) -> io::Result<()>
where
2018-02-23 10:15:26 -08:00
F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>,
{
let data = &body[block];
// Basic block label at the top.
let cleanup_text = if data.is_cleanup { " (cleanup)" } else { "" };
writeln!(w, "{INDENT}{block:?}{cleanup_text}: {{")?;
// List of statements in the middle.
let mut current_location = Location { block, statement_index: 0 };
for statement in &data.statements {
extra_data(PassWhere::BeforeLocation(current_location), w)?;
let indented_body = format!("{INDENT}{INDENT}{statement:?};");
2023-06-06 09:47:00 -04:00
if tcx.sess.opts.unstable_opts.mir_include_spans {
writeln!(
w,
"{:A$} // {}{}",
indented_body,
if tcx.sess.verbose() { format!("{current_location:?}: ") } else { String::new() },
2023-06-06 09:47:00 -04:00
comment(tcx, statement.source_info),
A = ALIGN,
)?;
} else {
writeln!(w, "{indented_body}")?;
2023-06-06 09:47:00 -04:00
}
write_extra(tcx, w, |visitor| {
visitor.visit_statement(statement, current_location);
})?;
extra_data(PassWhere::AfterLocation(current_location), w)?;
current_location.statement_index += 1;
}
// Terminator at the bottom.
extra_data(PassWhere::BeforeLocation(current_location), w)?;
let indented_terminator = format!("{0}{0}{1:?};", INDENT, data.terminator().kind);
2023-06-06 09:47:00 -04:00
if tcx.sess.opts.unstable_opts.mir_include_spans {
writeln!(
w,
"{:A$} // {}{}",
indented_terminator,
if tcx.sess.verbose() { format!("{current_location:?}: ") } else { String::new() },
2023-06-06 09:47:00 -04:00
comment(tcx, data.terminator().source_info),
A = ALIGN,
)?;
} else {
writeln!(w, "{indented_terminator}")?;
2023-06-06 09:47:00 -04:00
}
write_extra(tcx, w, |visitor| {
visitor.visit_terminator(data.terminator(), current_location);
})?;
extra_data(PassWhere::AfterLocation(current_location), w)?;
2018-06-29 06:47:12 -04:00
extra_data(PassWhere::AfterTerminator(block), w)?;
writeln!(w, "{INDENT}}}")
}
/// After we print the main statement, we sometimes dump extra
/// information. There's often a lot of little things "nuzzled up" in
/// a statement.
fn write_extra<'tcx, F>(tcx: TyCtxt<'tcx>, write: &mut dyn Write, mut visit_op: F) -> io::Result<()>
2017-11-17 14:08:47 -05:00
where
2019-06-14 00:48:52 +03:00
F: FnMut(&mut ExtraComments<'tcx>),
{
2023-06-06 09:47:00 -04:00
if tcx.sess.opts.unstable_opts.mir_include_spans {
let mut extra_comments = ExtraComments { tcx, comments: vec![] };
visit_op(&mut extra_comments);
for comment in extra_comments.comments {
writeln!(write, "{:A$} // {}", "", comment, A = ALIGN)?;
}
}
Ok(())
}
2019-06-14 00:48:52 +03:00
struct ExtraComments<'tcx> {
tcx: TyCtxt<'tcx>,
comments: Vec<String>,
}
impl<'tcx> ExtraComments<'tcx> {
fn push(&mut self, lines: &str) {
for line in lines.split('\n') {
self.comments.push(line.to_string());
}
}
}
2022-12-20 22:10:40 +01:00
fn use_verbose(ty: Ty<'_>, fn_def: bool) -> bool {
2022-01-25 14:13:38 +11:00
match *ty.kind() {
ty::Int(_) | ty::Uint(_) | ty::Bool | ty::Char | ty::Float(_) => false,
// Unit type
ty::Tuple(g_args) if g_args.is_empty() => false,
2022-02-07 16:06:31 +01:00
ty::Tuple(g_args) => g_args.iter().any(|g_arg| use_verbose(g_arg, fn_def)),
2021-06-23 10:39:23 +08:00
ty::Array(ty, _) => use_verbose(ty, fn_def),
ty::FnDef(..) => fn_def,
_ => true,
}
}
impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> {
2022-02-21 08:10:07 +01:00
fn visit_constant(&mut self, constant: &Constant<'tcx>, _location: Location) {
let Constant { span, user_ty, literal } = constant;
2022-01-25 14:13:38 +11:00
if use_verbose(literal.ty(), true) {
2021-06-23 10:39:23 +08:00
self.push("mir::Constant");
self.push(&format!(
"+ span: {}",
self.tcx.sess.source_map().span_to_embeddable_string(*span)
));
if let Some(user_ty) = user_ty {
self.push(&format!("+ user_ty: {user_ty:?}"));
2021-06-23 10:39:23 +08:00
}
2022-07-06 10:14:46 -04:00
// FIXME: this is a poor version of `pretty_print_const_value`.
let fmt_val = |val: &ConstValue<'tcx>| match val {
ConstValue::ZeroSized => "<ZST>".to_string(),
ConstValue::Scalar(s) => format!("Scalar({s:?})"),
ConstValue::Slice { .. } => "Slice(..)".to_string(),
ConstValue::Indirect { .. } => "ByRef(..)".to_string(),
};
let fmt_valtree = |valtree: &ty::ValTree<'tcx>| match valtree {
ty::ValTree::Leaf(leaf) => format!("ValTree::Leaf({leaf:?})"),
ty::ValTree::Branch(_) => "ValTree::Branch(..)".to_string(),
};
let val = match literal {
ConstantKind::Ty(ct) => match ct.kind() {
ty::ConstKind::Param(p) => format!("Param({p})"),
ty::ConstKind::Unevaluated(uv) => {
format!("Unevaluated({}, {:?})", self.tcx.def_path_str(uv.def), uv.args,)
}
ty::ConstKind::Value(val) => format!("Value({})", fmt_valtree(&val)),
2022-02-21 08:10:07 +01:00
ty::ConstKind::Error(_) => "Error".to_string(),
// These variants shouldn't exist in the MIR.
ty::ConstKind::Placeholder(_)
| ty::ConstKind::Infer(_)
| ty::ConstKind::Expr(_)
2022-02-21 08:10:07 +01:00
| ty::ConstKind::Bound(..) => bug!("unexpected MIR constant: {:?}", literal),
},
ConstantKind::Unevaluated(uv, _) => {
format!(
"Unevaluated({}, {:?}, {:?})",
2022-05-08 15:53:19 +02:00
self.tcx.def_path_str(uv.def),
uv.args,
uv.promoted,
)
}
2022-02-21 08:10:07 +01:00
// To keep the diffs small, we render this like we render `ty::Const::Value`.
//
// This changes once `ty::Const::Value` is represented using valtrees.
ConstantKind::Val(val, _) => format!("Value({})", fmt_val(&val)),
};
2022-02-21 08:10:07 +01:00
// This reflects what `Const` looked liked before `val` was renamed
// as `kind`. We print it like this to avoid having to update
// expected output in a lot of tests.
2022-06-14 17:34:37 +02:00
self.push(&format!("+ literal: Const {{ ty: {}, val: {} }}", literal.ty(), val));
}
}
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
self.super_rvalue(rvalue, location);
if let Rvalue::Aggregate(kind, _) = rvalue {
match **kind {
AggregateKind::Closure(def_id, args) => {
2018-07-28 14:40:32 +02:00
self.push("closure");
self.push(&format!("+ def_id: {def_id:?}"));
self.push(&format!("+ args: {args:#?}"));
}
AggregateKind::Generator(def_id, args, movability) => {
2018-07-28 14:40:32 +02:00
self.push("generator");
self.push(&format!("+ def_id: {def_id:?}"));
self.push(&format!("+ args: {args:#?}"));
self.push(&format!("+ movability: {movability:?}"));
}
2018-08-09 12:24:52 -04:00
AggregateKind::Adt(_, _, _, Some(user_ty), _) => {
self.push("adt");
self.push(&format!("+ user_ty: {user_ty:?}"));
2018-08-09 12:24:52 -04:00
}
_ => {}
}
}
}
}
2023-06-06 09:47:00 -04:00
fn comment(tcx: TyCtxt<'_>, SourceInfo { span, scope }: SourceInfo) -> String {
let location = tcx.sess.source_map().span_to_embeddable_string(span);
format!("scope {} at {}", scope.index(), location,)
}
/// Prints local variables in a scope tree.
fn write_scope_tree(
2019-06-14 00:48:52 +03:00
tcx: TyCtxt<'_>,
body: &Body<'_>,
scope_tree: &FxHashMap<SourceScope, Vec<SourceScope>>,
2018-02-23 10:15:26 -08:00
w: &mut dyn Write,
parent: SourceScope,
depth: usize,
) -> io::Result<()> {
let indent = depth * INDENT.len();
2016-05-13 23:36:50 +02:00
// Local variable debuginfo.
for var_debug_info in &body.var_debug_info {
if var_debug_info.source_info.scope != parent {
// Not declared in this scope.
continue;
}
let indented_debug_info = format!("{0:1$}debug {2:?};", INDENT, indent, var_debug_info);
2023-06-06 09:47:00 -04:00
if tcx.sess.opts.unstable_opts.mir_include_spans {
writeln!(
w,
"{0:1$} // in {2}",
indented_debug_info,
ALIGN,
comment(tcx, var_debug_info.source_info),
)?;
} else {
writeln!(w, "{indented_debug_info}")?;
2023-06-06 09:47:00 -04:00
}
}
// Local variable types.
for (local, local_decl) in body.local_decls.iter_enumerated() {
if (1..body.arg_count + 1).contains(&local.index()) {
// Skip over argument locals, they're printed in the signature.
continue;
}
if local_decl.source_info.scope != parent {
// Not declared in this scope.
continue;
}
2023-01-31 07:44:34 +00:00
let mut_str = local_decl.mutability.prefix_str();
let mut indented_decl = ty::print::with_no_trimmed_paths!(format!(
"{0:1$}let {2}{3:?}: {4}",
INDENT, indent, mut_str, local, local_decl.ty
));
if let Some(user_ty) = &local_decl.user_ty {
for user_ty in user_ty.projections() {
write!(indented_decl, " as {user_ty:?}").unwrap();
}
}
indented_decl.push(';');
let local_name = if local == RETURN_PLACE { " return place" } else { "" };
2023-06-06 09:47:00 -04:00
if tcx.sess.opts.unstable_opts.mir_include_spans {
writeln!(
w,
"{0:1$} //{2} in {3}",
indented_decl,
ALIGN,
local_name,
comment(tcx, local_decl.source_info),
)?;
} else {
writeln!(w, "{indented_decl}",)?;
2023-06-06 09:47:00 -04:00
}
}
2022-02-19 00:48:49 +01:00
let Some(children) = scope_tree.get(&parent) else {
return Ok(());
2016-05-13 23:36:50 +02:00
};
for &child in children {
let child_data = &body.source_scopes[child];
assert_eq!(child_data.parent_scope, Some(parent));
let (special, span) = if let Some((callee, callsite_span)) = child_data.inlined {
(
format!(
" (inlined {}{})",
if callee.def.requires_caller_location(tcx) { "#[track_caller] " } else { "" },
callee
),
Some(callsite_span),
)
} else {
(String::new(), None)
};
let indented_header = format!("{0:1$}scope {2}{3} {{", "", indent, child.index(), special);
2023-06-06 09:47:00 -04:00
if tcx.sess.opts.unstable_opts.mir_include_spans {
if let Some(span) = span {
writeln!(
w,
"{0:1$} // at {2}",
indented_header,
ALIGN,
tcx.sess.source_map().span_to_embeddable_string(span),
)?;
} else {
writeln!(w, "{indented_header}")?;
2023-06-06 09:47:00 -04:00
}
} else {
writeln!(w, "{indented_header}")?;
}
write_scope_tree(tcx, body, scope_tree, w, child, depth + 1)?;
writeln!(w, "{0:1$}}}", "", depth * INDENT.len())?;
}
Ok(())
}
/// Write out a human-readable textual representation of the MIR's `fn` type and the types of its
/// local variables (both user-defined bindings and compiler temporaries).
2019-06-14 00:48:52 +03:00
pub fn write_mir_intro<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'_>,
2018-02-23 10:15:26 -08:00
w: &mut dyn Write,
) -> io::Result<()> {
write_mir_sig(tcx, body, w)?;
writeln!(w, "{{")?;
// construct a scope tree and write it out
let mut scope_tree: FxHashMap<SourceScope, Vec<SourceScope>> = Default::default();
for (index, scope_data) in body.source_scopes.iter().enumerate() {
if let Some(parent) = scope_data.parent_scope {
scope_tree.entry(parent).or_default().push(SourceScope::new(index));
} else {
// Only the argument scope has no parent, because it's the root.
assert_eq!(index, OUTERMOST_SOURCE_SCOPE.index());
}
}
write_scope_tree(tcx, body, &scope_tree, w, OUTERMOST_SOURCE_SCOPE, 1)?;
// Add an empty line before the first block is printed.
writeln!(w)?;
Ok(())
}
2020-03-11 11:49:00 +01:00
/// Find all `AllocId`s mentioned (recursively) in the MIR body and print their corresponding
/// allocations.
pub fn write_allocations<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'_>,
w: &mut dyn Write,
) -> io::Result<()> {
fn alloc_ids_from_alloc(
alloc: ConstAllocation<'_>,
) -> impl DoubleEndedIterator<Item = AllocId> + '_ {
alloc.inner().provenance().ptrs().values().map(|id| *id)
2020-03-11 11:49:00 +01:00
}
fn alloc_ids_from_const_val(val: ConstValue<'_>) -> impl Iterator<Item = AllocId> + '_ {
2020-03-11 11:49:00 +01:00
match val {
2022-03-07 02:18:36 +09:00
ConstValue::Scalar(interpret::Scalar::Ptr(ptr, _)) => {
Either::Left(std::iter::once(ptr.provenance))
2020-03-11 11:49:00 +01:00
}
ConstValue::Scalar(interpret::Scalar::Int { .. }) => Either::Right(std::iter::empty()),
ConstValue::ZeroSized => Either::Right(std::iter::empty()),
ConstValue::Slice { .. } => {
// `u8`/`str` slices, shouldn't contain pointers that we want to print.
Either::Right(std::iter::empty())
2020-03-11 11:49:00 +01:00
}
ConstValue::Indirect { alloc_id, .. } => {
// FIXME: we don't actually want to print all of these, since some are printed nicely directly as values inline in MIR.
// Really we'd want `pretty_print_const_value` to decide which allocations to print, instead of having a separate visitor.
Either::Left(std::iter::once(alloc_id))
}
2020-03-11 11:49:00 +01:00
}
}
struct CollectAllocIds(BTreeSet<AllocId>);
impl<'tcx> Visitor<'tcx> for CollectAllocIds {
2022-07-04 20:07:14 +02:00
fn visit_constant(&mut self, c: &Constant<'tcx>, _: Location) {
match c.literal {
2022-07-04 20:07:14 +02:00
ConstantKind::Ty(_) | ConstantKind::Unevaluated(..) => {}
ConstantKind::Val(val, _) => {
self.0.extend(alloc_ids_from_const_val(val));
}
}
2020-03-11 11:49:00 +01:00
}
}
2020-03-11 11:49:00 +01:00
let mut visitor = CollectAllocIds(Default::default());
visitor.visit_body(body);
// `seen` contains all seen allocations, including the ones we have *not* printed yet.
// The protocol is to first `insert` into `seen`, and only if that returns `true`
// then push to `todo`.
2020-03-11 11:49:00 +01:00
let mut seen = visitor.0;
let mut todo: Vec<_> = seen.iter().copied().collect();
while let Some(id) = todo.pop() {
let mut write_allocation_track_relocs =
|w: &mut dyn Write, alloc: ConstAllocation<'tcx>| -> io::Result<()> {
// `.rev()` because we are popping them from the back of the `todo` vector.
for id in alloc_ids_from_alloc(alloc).rev() {
if seen.insert(id) {
todo.push(id);
2020-03-11 11:49:00 +01:00
}
}
write!(w, "{}", display_allocation(tcx, alloc.inner()))
2020-03-11 11:49:00 +01:00
};
write!(w, "\n{id:?}")?;
match tcx.try_get_global_alloc(id) {
2020-03-11 11:49:00 +01:00
// This can't really happen unless there are bugs, but it doesn't cost us anything to
// gracefully handle it and allow buggy rustc to be debugged via allocation printing.
None => write!(w, " (deallocated)")?,
Some(GlobalAlloc::Function(inst)) => write!(w, " (fn: {inst})")?,
Some(GlobalAlloc::VTable(ty, Some(trait_ref))) => {
write!(w, " (vtable: impl {trait_ref} for {ty})")?
}
Some(GlobalAlloc::VTable(ty, None)) => {
write!(w, " (vtable: impl <auto trait> for {ty})")?
}
2020-03-11 11:49:00 +01:00
Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
match tcx.eval_static_initializer(did) {
Ok(alloc) => {
2020-03-11 11:49:00 +01:00
write!(w, " (static: {}, ", tcx.def_path_str(did))?;
write_allocation_track_relocs(w, alloc)?;
2020-03-11 11:49:00 +01:00
}
Err(_) => write!(
w,
" (static: {}, error during initializer evaluation)",
tcx.def_path_str(did)
)?,
}
}
Some(GlobalAlloc::Static(did)) => {
write!(w, " (extern static: {})", tcx.def_path_str(did))?
}
Some(GlobalAlloc::Memory(alloc)) => {
write!(w, " (")?;
write_allocation_track_relocs(w, alloc)?
2020-03-11 11:49:00 +01:00
}
}
writeln!(w)?;
}
Ok(())
}
/// Dumps the size and metadata and content of an allocation to the given writer.
/// The expectation is that the caller first prints other relevant metadata, so the exact
/// format of this function is (*without* leading or trailing newline):
///
/// ```text
/// size: {}, align: {}) {
/// <bytes>
/// }
/// ```
///
/// The byte format is similar to how hex editors print bytes. Each line starts with the address of
/// the start of the line, followed by all bytes in hex format (space separated).
/// If the allocation is small enough to fit into a single line, no start address is given.
/// After the hex dump, an ascii dump follows, replacing all unprintable characters (control
/// characters or characters whose value is larger than 127) with a `.`
/// This also prints provenance adequately.
pub fn display_allocation<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>(
tcx: TyCtxt<'tcx>,
alloc: &'a Allocation<Prov, Extra, Bytes>,
) -> RenderAllocation<'a, 'tcx, Prov, Extra, Bytes> {
RenderAllocation { tcx, alloc }
}
#[doc(hidden)]
pub struct RenderAllocation<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes> {
tcx: TyCtxt<'tcx>,
alloc: &'a Allocation<Prov, Extra, Bytes>,
}
impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes> std::fmt::Display
for RenderAllocation<'a, 'tcx, Prov, Extra, Bytes>
2021-12-15 19:48:59 -05:00
{
fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let RenderAllocation { tcx, alloc } = *self;
2021-05-17 13:30:16 +02:00
write!(w, "size: {}, align: {})", alloc.size().bytes(), alloc.align.bytes())?;
if alloc.size() == Size::ZERO {
// We are done.
return write!(w, " {{}}");
}
// Write allocation bytes.
writeln!(w, " {{")?;
write_allocation_bytes(tcx, alloc, w, " ")?;
write!(w, "}}")?;
Ok(())
}
}
fn write_allocation_endline(w: &mut dyn std::fmt::Write, ascii: &str) -> std::fmt::Result {
2020-03-11 11:49:00 +01:00
for _ in 0..(BYTES_PER_LINE - ascii.chars().count()) {
write!(w, " ")?;
}
writeln!(w, " │ {ascii}")
2020-03-11 11:49:00 +01:00
}
/// Number of bytes to print per allocation hex dump line.
const BYTES_PER_LINE: usize = 16;
/// Prints the line start address and returns the new line start address.
fn write_allocation_newline(
w: &mut dyn std::fmt::Write,
2020-03-11 11:49:00 +01:00
mut line_start: Size,
ascii: &str,
pos_width: usize,
prefix: &str,
) -> Result<Size, std::fmt::Error> {
2020-03-11 11:49:00 +01:00
write_allocation_endline(w, ascii)?;
line_start += Size::from_bytes(BYTES_PER_LINE);
write!(w, "{}0x{:02$x} │ ", prefix, line_start.bytes(), pos_width)?;
Ok(line_start)
}
/// The `prefix` argument allows callers to add an arbitrary prefix before each line (even if there
/// is only one line). Note that your prefix should contain a trailing space as the lines are
/// printed directly after it.
pub fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>(
2020-03-11 11:49:00 +01:00
tcx: TyCtxt<'tcx>,
alloc: &Allocation<Prov, Extra, Bytes>,
w: &mut dyn std::fmt::Write,
2020-03-11 11:49:00 +01:00
prefix: &str,
) -> std::fmt::Result {
2021-05-17 13:30:16 +02:00
let num_lines = alloc.size().bytes_usize().saturating_sub(BYTES_PER_LINE);
2020-03-11 11:49:00 +01:00
// Number of chars needed to represent all line numbers.
let pos_width = hex_number_length(alloc.size().bytes());
2020-03-11 11:49:00 +01:00
if num_lines > 0 {
write!(w, "{}0x{:02$x} │ ", prefix, 0, pos_width)?;
} else {
write!(w, "{prefix}")?;
2020-03-11 11:49:00 +01:00
}
let mut i = Size::ZERO;
let mut line_start = Size::ZERO;
let ptr_size = tcx.data_layout.pointer_size;
let mut ascii = String::new();
let oversized_ptr = |target: &mut String, width| {
if target.len() > width {
write!(target, " ({} ptr bytes)", ptr_size.bytes()).unwrap();
}
};
2021-05-17 13:30:16 +02:00
while i < alloc.size() {
2020-03-11 11:49:00 +01:00
// The line start already has a space. While we could remove that space from the line start
// printing and unconditionally print a space here, that would cause the single-line case
// to have a single space before it, which looks weird.
if i != line_start {
write!(w, " ")?;
}
if let Some(prov) = alloc.provenance().get_ptr(i) {
// Memory with provenance must be defined
2022-11-06 13:44:50 +01:00
assert!(alloc.init_mask().is_range_initialized(alloc_range(i, ptr_size)).is_ok());
2020-03-11 11:49:00 +01:00
let j = i.bytes_usize();
let offset = alloc
.inspect_with_uninit_and_ptr_outside_interpreter(j..j + ptr_size.bytes_usize());
2020-03-11 11:49:00 +01:00
let offset = read_target_uint(tcx.data_layout.endian, offset).unwrap();
2020-04-26 15:58:53 +02:00
let offset = Size::from_bytes(offset);
let provenance_width = |bytes| bytes * 3;
let ptr = Pointer::new(prov, offset);
let mut target = format!("{ptr:?}");
if target.len() > provenance_width(ptr_size.bytes_usize() - 1) {
// This is too long, try to save some space.
target = format!("{ptr:#?}");
}
2020-03-11 11:49:00 +01:00
if ((i - line_start) + ptr_size).bytes_usize() > BYTES_PER_LINE {
// This branch handles the situation where a provenance starts in the current line
2020-03-11 11:49:00 +01:00
// but ends in the next one.
let remainder = Size::from_bytes(BYTES_PER_LINE) - (i - line_start);
let overflow = ptr_size - remainder;
let remainder_width = provenance_width(remainder.bytes_usize()) - 2;
let overflow_width = provenance_width(overflow.bytes_usize() - 1) + 1;
ascii.push('╾'); // HEAVY LEFT AND LIGHT RIGHT
for _ in 1..remainder.bytes() {
ascii.push('─'); // LIGHT HORIZONTAL
2020-03-11 11:49:00 +01:00
}
if overflow_width > remainder_width && overflow_width >= target.len() {
// The case where the provenance fits into the part in the next line
2020-03-11 11:49:00 +01:00
write!(w, "╾{0:─^1$}", "", remainder_width)?;
line_start =
write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?;
ascii.clear();
write!(w, "{target:─^overflow_width$}╼")?;
2020-03-11 11:49:00 +01:00
} else {
oversized_ptr(&mut target, remainder_width);
write!(w, "╾{target:─^remainder_width$}")?;
2020-03-11 11:49:00 +01:00
line_start =
write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?;
write!(w, "{0:─^1$}╼", "", overflow_width)?;
ascii.clear();
}
for _ in 0..overflow.bytes() - 1 {
ascii.push('─');
}
ascii.push('╼'); // LIGHT LEFT AND HEAVY RIGHT
2020-03-11 11:49:00 +01:00
i += ptr_size;
continue;
} else {
// This branch handles a provenance that starts and ends in the current line.
let provenance_width = provenance_width(ptr_size.bytes_usize() - 1);
oversized_ptr(&mut target, provenance_width);
2020-03-11 11:49:00 +01:00
ascii.push('╾');
write!(w, "╾{target:─^provenance_width$}╼")?;
2020-03-11 11:49:00 +01:00
for _ in 0..ptr_size.bytes() - 2 {
ascii.push('─');
}
ascii.push('╼');
i += ptr_size;
}
} else if let Some(prov) = alloc.provenance().get(i, &tcx) {
// Memory with provenance must be defined
2022-11-06 13:44:50 +01:00
assert!(
alloc.init_mask().is_range_initialized(alloc_range(i, Size::from_bytes(1))).is_ok()
);
ascii.push('━'); // HEAVY HORIZONTAL
// We have two characters to display this, which is obviously not enough.
// Format is similar to "oversized" above.
let j = i.bytes_usize();
let c = alloc.inspect_with_uninit_and_ptr_outside_interpreter(j..j + 1)[0];
write!(w, "╾{c:02x}{prov:#?} (1 ptr byte)╼")?;
i += Size::from_bytes(1);
2022-11-06 13:44:50 +01:00
} else if alloc
.init_mask()
.is_range_initialized(alloc_range(i, Size::from_bytes(1)))
.is_ok()
{
2020-03-11 11:49:00 +01:00
let j = i.bytes_usize();
// Checked definedness (and thus range) and provenance. This access also doesn't
2020-03-11 11:49:00 +01:00
// influence interpreter execution but is only for debugging.
let c = alloc.inspect_with_uninit_and_ptr_outside_interpreter(j..j + 1)[0];
write!(w, "{c:02x}")?;
2020-03-11 11:49:00 +01:00
if c.is_ascii_control() || c >= 0x80 {
ascii.push('.');
} else {
ascii.push(char::from(c));
}
i += Size::from_bytes(1);
} else {
write!(w, "__")?;
ascii.push('░');
i += Size::from_bytes(1);
}
// Print a new line header if the next line still has some bytes to print.
2021-05-17 13:30:16 +02:00
if i == line_start + Size::from_bytes(BYTES_PER_LINE) && i != alloc.size() {
2020-03-11 11:49:00 +01:00
line_start = write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?;
ascii.clear();
}
}
write_allocation_endline(w, &ascii)?;
Ok(())
}
fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn Write) -> io::Result<()> {
use rustc_hir::def::DefKind;
2019-02-02 16:59:20 +01:00
trace!("write_mir_sig: {:?}", body.source.instance);
let def_id = body.source.def_id();
let kind = tcx.def_kind(def_id);
let is_function = match kind {
DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true,
_ => tcx.is_closure(def_id),
2019-02-05 23:40:29 +01:00
};
match (kind, body.source.promoted) {
(_, Some(i)) => write!(w, "{i:?} in ")?,
(DefKind::Const | DefKind::AssocConst, _) => write!(w, "const ")?,
(DefKind::Static(hir::Mutability::Not), _) => write!(w, "static ")?,
(DefKind::Static(hir::Mutability::Mut), _) => write!(w, "static mut ")?,
2019-02-05 23:40:29 +01:00
(_, _) if is_function => write!(w, "fn ")?,
2021-10-02 12:59:26 +01:00
(DefKind::AnonConst | DefKind::InlineConst, _) => {} // things like anon const, not an item
_ => bug!("Unexpected def kind {:?}", kind),
}
ty::print::with_forced_impl_filename_line! {
// see notes on #41697 elsewhere
write!(w, "{}", tcx.def_path_str(def_id))?
}
if body.source.promoted.is_none() && is_function {
2019-02-05 23:40:29 +01:00
write!(w, "(")?;
2019-02-05 23:40:29 +01:00
// fn argument types.
for (i, arg) in body.args_iter().enumerate() {
2019-02-05 23:40:29 +01:00
if i != 0 {
write!(w, ", ")?;
}
write!(w, "{:?}: {}", Place::from(arg), body.local_decls[arg].ty)?;
2016-12-26 14:34:03 +01:00
}
2019-02-05 23:40:29 +01:00
write!(w, ") -> {}", body.return_ty())?;
2019-02-05 23:40:29 +01:00
} else {
assert_eq!(body.arg_count, 0);
write!(w, ": {} =", body.return_ty())?;
}
2021-01-17 13:27:05 +01:00
if let Some(yield_ty) = body.yield_ty() {
writeln!(w)?;
writeln!(w, "yields {yield_ty}")?;
}
2019-02-05 21:49:53 +01:00
write!(w, " ")?;
// Next thing that gets printed is the opening {
Ok(())
}
fn write_user_type_annotations(
tcx: TyCtxt<'_>,
body: &Body<'_>,
w: &mut dyn Write,
) -> io::Result<()> {
if !body.user_type_annotations.is_empty() {
writeln!(w, "| User Type Annotations")?;
}
for (index, annotation) in body.user_type_annotations.iter_enumerated() {
writeln!(
w,
"| {:?}: user_ty: {}, span: {}, inferred_ty: {}",
index.index(),
annotation.user_ty,
tcx.sess.source_map().span_to_embeddable_string(annotation.span),
with_no_trimmed_paths!(format!("{}", annotation.inferred_ty)),
)?;
}
if !body.user_type_annotations.is_empty() {
writeln!(w, "|")?;
}
Ok(())
}
2019-06-14 00:48:52 +03:00
pub fn dump_mir_def_ids(tcx: TyCtxt<'_>, single: Option<DefId>) -> Vec<DefId> {
if let Some(i) = single {
vec![i]
} else {
2021-05-11 12:26:53 +02:00
tcx.mir_keys(()).iter().map(|def_id| def_id.to_def_id()).collect()
}
}
fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Result {
write!(fmt, "b\"{}\"", byte_str.escape_ascii())
}
fn comma_sep<'tcx>(
fmt: &mut Formatter<'_>,
elems: Vec<(ConstValue<'tcx>, Ty<'tcx>)>,
) -> fmt::Result {
let mut first = true;
for (ct, ty) in elems {
if !first {
fmt.write_str(", ")?;
}
pretty_print_const_value(ct, ty, fmt)?;
first = false;
}
Ok(())
}
pub fn pretty_print_const_value<'tcx>(
ct: ConstValue<'tcx>,
ty: Ty<'tcx>,
fmt: &mut Formatter<'_>,
) -> fmt::Result {
use crate::ty::print::PrettyPrinter;
ty::tls::with(|tcx| {
let ct = tcx.lift(ct).unwrap();
let ty = tcx.lift(ty).unwrap();
if tcx.sess.verbose() {
fmt.write_str(&format!("ConstValue({ct:?}: {ty})"))?;
return Ok(());
}
let u8_type = tcx.types.u8;
match (ct, ty.kind()) {
// Byte/string slices, printed as (byte) string literals.
(_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Str) => {
if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) {
fmt.write_str(&format!("{:?}", String::from_utf8_lossy(data)))?;
return Ok(());
}
}
(_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Slice(t) if *t == u8_type) => {
if let Some(data) = ct.try_get_slice_bytes_for_diagnostics(tcx) {
pretty_print_byte_str(fmt, data)?;
return Ok(());
}
}
(ConstValue::Indirect { alloc_id, offset }, ty::Array(t, n)) if *t == u8_type => {
let n = n.try_to_target_usize(tcx).unwrap();
let alloc = tcx.global_alloc(alloc_id).unwrap_memory();
// cast is ok because we already checked for pointer size (32 or 64 bit) above
let range = AllocRange { start: offset, size: Size::from_bytes(n) };
let byte_str = alloc.inner().get_bytes_strip_provenance(&tcx, range).unwrap();
fmt.write_str("*")?;
pretty_print_byte_str(fmt, byte_str)?;
return Ok(());
}
// Aggregates, printed as array/tuple/struct/variant construction syntax.
//
// NB: the `has_non_region_param` check ensures that we can use
// the `destructure_const` query with an empty `ty::ParamEnv` without
// introducing ICEs (e.g. via `layout_of`) from missing bounds.
// E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized`
// to be able to destructure the tuple into `(0u8, *mut T)`
(_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_non_region_param() => {
let ct = tcx.lift(ct).unwrap();
let ty = tcx.lift(ty).unwrap();
if let Some(contents) = tcx.try_destructure_mir_constant_for_diagnostics((ct, ty)) {
let fields: Vec<(ConstValue<'_>, Ty<'_>)> = contents.fields.to_vec();
match *ty.kind() {
ty::Array(..) => {
fmt.write_str("[")?;
comma_sep(fmt, fields)?;
fmt.write_str("]")?;
}
ty::Tuple(..) => {
fmt.write_str("(")?;
comma_sep(fmt, fields)?;
if contents.fields.len() == 1 {
fmt.write_str(",")?;
}
fmt.write_str(")")?;
}
ty::Adt(def, _) if def.variants().is_empty() => {
fmt.write_str(&format!("{{unreachable(): {ty}}}"))?;
}
ty::Adt(def, args) => {
let variant_idx = contents
.variant
.expect("destructed mir constant of adt without variant idx");
let variant_def = &def.variant(variant_idx);
let args = tcx.lift(args).unwrap();
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let cx = cx.print_value_path(variant_def.def_id, args)?;
fmt.write_str(&cx.into_buffer())?;
match variant_def.ctor_kind() {
Some(CtorKind::Const) => {}
Some(CtorKind::Fn) => {
fmt.write_str("(")?;
comma_sep(fmt, fields)?;
fmt.write_str(")")?;
}
None => {
fmt.write_str(" {{ ")?;
let mut first = true;
for (field_def, (ct, ty)) in
iter::zip(&variant_def.fields, fields)
{
if !first {
fmt.write_str(", ")?;
}
write!(fmt, "{}: ", field_def.name)?;
pretty_print_const_value(ct, ty, fmt)?;
first = false;
}
fmt.write_str(" }}")?;
}
}
}
_ => unreachable!(),
}
return Ok(());
}
}
(ConstValue::Scalar(scalar), _) => {
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let ty = tcx.lift(ty).unwrap();
cx = cx.pretty_print_const_scalar(scalar, ty)?;
fmt.write_str(&cx.into_buffer())?;
return Ok(());
}
(ConstValue::ZeroSized, ty::FnDef(d, s)) => {
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let cx = cx.print_value_path(*d, s)?;
fmt.write_str(&cx.into_buffer())?;
return Ok(());
}
// FIXME(oli-obk): also pretty print arrays and other aggregate constants by reading
// their fields instead of just dumping the memory.
_ => {}
}
// Fall back to debug pretty printing for invalid constants.
write!(fmt, "{ct:?}: {ty}")
})
}
/// Calc converted u64 decimal into hex and return it's length in chars
///
/// ```ignore (cannot-test-private-function)
/// assert_eq!(1, hex_number_length(0));
/// assert_eq!(1, hex_number_length(1));
/// assert_eq!(2, hex_number_length(16));
/// ```
fn hex_number_length(x: u64) -> usize {
if x == 0 {
return 1;
}
let mut length = 0;
let mut x_left = x;
while x_left > 0 {
x_left /= 16;
length += 1;
}
length
}