Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make Miri work in rustc bootstrap stage 0 #1405

Merged
merged 3 commits into from
May 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ Moreover, Miri recognizes some environment variables:
* `MIRI_TEST_FLAGS` (recognized by the test suite) defines extra flags to be
passed to Miri.

The following environment variables are internal, but used to communicate between
different Miri binaries, and as such worth documenting:

* `MIRI_BE_RUSTC` when set to any value tells the Miri driver to actually not
interpret the code but compile it like rustc would. This is useful to be sure
that the compiled `rlib`s are compatible with Miri.

## Contributing and getting help

If you want to contribute to Miri, great! Please check out our
Expand Down
49 changes: 32 additions & 17 deletions src/bin/cargo-miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,15 @@ fn get_arg_flag_value(name: &str) -> Option<String> {
}
}

/// Returns a command for the right `miri` binary.
fn miri() -> Command {
/// Returns the path to the `miri` binary
fn find_miri() -> PathBuf {
let mut path = std::env::current_exe().expect("current executable path invalid");
path.set_file_name("miri");
Command::new(path)
path
}

fn miri() -> Command {
Command::new(find_miri())
}

fn cargo() -> Command {
Expand Down Expand Up @@ -322,7 +326,8 @@ fn setup(subcommand: MiriCommand) {
show_error(format!("Given Rust source directory `{}` does not exist.", rust_src.display()));
}

// Next, we need our own libstd. We will do this work in whatever is a good cache dir for this platform.
// Next, we need our own libstd. Prepare a xargo project for that purpose.
// We will do this work in whatever is a good cache dir for this platform.
let dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
let dir = dirs.cache_dir();
if !dir.exists() {
Expand Down Expand Up @@ -360,20 +365,31 @@ path = "lib.rs"
)
.unwrap();
File::create(dir.join("lib.rs")).unwrap();
// Prepare xargo invocation.

// Determine architectures.
// We always need to set a target so rustc bootstrap can tell apart host from target crates.
let host = rustc_version::version_meta().unwrap().host;
let target = get_arg_flag_value("--target");
let print_sysroot = subcommand == MiriCommand::Setup
&& has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
let target = target.as_ref().unwrap_or(&host);
// Now invoke xargo.
let mut command = xargo_check();
command.arg("build").arg("-q");
command.arg("--target").arg(target);
command.current_dir(&dir);
command.env("RUSTFLAGS", miri::miri_default_args().join(" "));
command.env("XARGO_HOME", &dir);
command.env("XARGO_RUST_SRC", &rust_src);
// Handle target flag.
if let Some(target) = &target {
command.arg("--target").arg(target);
// Use Miri as rustc to build a libstd compatible with us (and use the right flags).
// However, when we are running in bootstrap, we cannot just overwrite `RUSTC`,
// because we still need bootstrap to distinguish between host and target crates.
// In that case we overwrite `RUSTC_REAL` instead which determines the rustc used
// for target crates.
if env::var_os("RUSTC_STAGE").is_some() {
command.env("RUSTC_REAL", find_miri());
} else {
command.env("RUSTC", find_miri());
}
command.env("MIRI_BE_RUSTC", "1");
command.env("RUSTFLAGS", miri::miri_default_args().join(" "));
// Finally run it!
if command.status().expect("failed to run xargo").success().not() {
show_error(format!("Failed to run xargo"));
Expand All @@ -382,12 +398,11 @@ path = "lib.rs"
// That should be it! But we need to figure out where xargo built stuff.
// Unfortunately, it puts things into a different directory when the
// architecture matches the host.
let is_host = match &target {
None => true,
Some(target) => target == &rustc_version::version_meta().unwrap().host,
};
let sysroot = if is_host { dir.join("HOST") } else { PathBuf::from(dir) };
let sysroot = if target == &host { dir.join("HOST") } else { PathBuf::from(dir) };
std::env::set_var("MIRI_SYSROOT", &sysroot); // pass the env var to the processes we spawn, which will turn it into "--sysroot" flags
// Figure out what to print.
let print_sysroot = subcommand == MiriCommand::Setup
&& has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
if print_sysroot {
// Print just the sysroot and nothing else; this way we do not need any escaping.
println!("{}", sysroot.display());
Expand Down Expand Up @@ -476,7 +491,7 @@ fn in_cargo_miri() {

// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
// i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
// the two codepaths.
// the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
let path = std::env::current_exe().expect("current executable path invalid");
cmd.env("RUSTC_WRAPPER", path);
if verbose {
Expand Down
97 changes: 58 additions & 39 deletions src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use log::debug;
use rustc_session::CtfeBacktrace;
use rustc_driver::Compilation;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_interface::{interface, Queries};
use rustc_middle::ty::TyCtxt;

struct MiriCompilerCalls {
Expand All @@ -26,8 +25,8 @@ struct MiriCompilerCalls {
impl rustc_driver::Callbacks for MiriCompilerCalls {
fn after_analysis<'tcx>(
&mut self,
compiler: &interface::Compiler,
queries: &'tcx Queries<'tcx>,
compiler: &rustc_interface::interface::Compiler,
queries: &'tcx rustc_interface::Queries<'tcx>,
) -> Compilation {
compiler.session().abort_if_errors();

Expand Down Expand Up @@ -106,12 +105,12 @@ fn init_late_loggers(tcx: TyCtxt<'_>) {
fn compile_time_sysroot() -> Option<String> {
if option_env!("RUSTC_STAGE").is_some() {
// This is being built as part of rustc, and gets shipped with rustup.
// We can rely on the sysroot computation in librustc.
// We can rely on the sysroot computation in librustc_session.
return None;
}
// For builds outside rustc, we need to ensure that we got a sysroot
// that gets used as a default. The sysroot computation in librustc would
// end up somewhere in the build dir.
// that gets used as a default. The sysroot computation in librustc_session would
// end up somewhere in the build dir (see `get_or_default_sysroot`).
// Taken from PR <https://github.com/Manishearth/rust-clippy/pull/911>.
let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
Expand All @@ -123,7 +122,47 @@ fn compile_time_sysroot() -> Option<String> {
})
}

/// Execute a compiler with the given CLI arguments and callbacks.
fn run_compiler(mut args: Vec<String>, callbacks: &mut (dyn rustc_driver::Callbacks + Send)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's a nice convenience function. I wonder if we can upstream the non-sysroot part of it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I wondered the same actually.
Any proposals for a name, given that rustc_driver::run_compiler is already taken?

// Make sure we use the right default sysroot. The default sysroot is wrong,
// because `get_or_default_sysroot` in `librustc_session` bases that on `current_exe`.
//
// Make sure we always call `compile_time_sysroot` as that also does some sanity-checks
// of the environment we were built in.
// FIXME: Ideally we'd turn a bad build env into a compile-time error via CTFE or so.
if let Some(sysroot) = compile_time_sysroot() {
let sysroot_flag = "--sysroot";
if !args.iter().any(|e| e == sysroot_flag) {
// We need to overwrite the default that librustc_session would compute.
args.push(sysroot_flag.to_owned());
args.push(sysroot);
}
}

// Invoke compiler, and handle return code.
let result = rustc_driver::catch_fatal_errors(move || {
rustc_driver::run_compiler(&args, callbacks, None, None)
})
.and_then(|result| result);
let exit_code = match result {
Ok(()) => rustc_driver::EXIT_SUCCESS,
Err(_) => rustc_driver::EXIT_FAILURE,
};
std::process::exit(exit_code);
}

fn main() {
rustc_driver::install_ice_hook();

// If the environment asks us to actually be rustc, then do that.
if env::var_os("MIRI_BE_RUSTC").is_some() {
rustc_driver::init_rustc_env_logger();
// We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
let mut callbacks = rustc_driver::TimePassesCallbacks::default();
return run_compiler(env::args().collect(), &mut callbacks);
}

// Init loggers the Miri way.
init_early_loggers();

// Parse our arguments and split them across `rustc` and `miri`.
Expand All @@ -136,16 +175,20 @@ fn main() {
let mut tracked_pointer_tag: Option<miri::PtrId> = None;
let mut tracked_alloc_id: Option<miri::AllocId> = None;
let mut rustc_args = vec![];
let mut miri_args = vec![];
let mut crate_args = vec![];
let mut after_dashdash = false;
let mut excluded_env_vars = vec![];
for arg in std::env::args() {
for arg in env::args() {
if rustc_args.is_empty() {
// Very first arg: for `rustc`.
// Very first arg: binary name.
rustc_args.push(arg);
// After this, push Miri default args (before everything else so they can be overwritten).
for arg in miri::miri_default_args().iter() {
rustc_args.push(arg.to_string());
}
} else if after_dashdash {
// Everything that comes after are `miri` args.
miri_args.push(arg);
// Everything that comes after `--` is forwarded to the interpreted crate.
crate_args.push(arg);
} else {
match arg.as_str() {
"-Zmiri-disable-validation" => {
Expand Down Expand Up @@ -221,30 +264,15 @@ fn main() {
tracked_alloc_id = Some(miri::AllocId(id));
}
_ => {
// Forward to rustc.
rustc_args.push(arg);
}
}
}
}

// Determine sysroot if needed. Make sure we always call `compile_time_sysroot`
// as that also does some sanity-checks of the environment we were built in.
// FIXME: Ideally we'd turn a bad build env into a compile-time error, but
// CTFE does not seem powerful enough for that yet.
if let Some(sysroot) = compile_time_sysroot() {
let sysroot_flag = "--sysroot";
if !rustc_args.iter().any(|e| e == sysroot_flag) {
// We need to overwrite the default that librustc would compute.
rustc_args.push(sysroot_flag.to_owned());
rustc_args.push(sysroot);
}
}

// Finally, add the default flags all the way in the beginning, but after the binary name.
rustc_args.splice(1..1, miri::miri_default_args().iter().map(ToString::to_string));

debug!("rustc arguments: {:?}", rustc_args);
debug!("miri arguments: {:?}", miri_args);
debug!("crate arguments: {:?}", crate_args);
let miri_config = miri::MiriConfig {
validate,
stacked_borrows,
Expand All @@ -253,18 +281,9 @@ fn main() {
ignore_leaks,
excluded_env_vars,
seed,
args: miri_args,
args: crate_args,
tracked_pointer_tag,
tracked_alloc_id,
};
rustc_driver::install_ice_hook();
let result = rustc_driver::catch_fatal_errors(move || {
rustc_driver::run_compiler(&rustc_args, &mut MiriCompilerCalls { miri_config }, None, None)
})
.and_then(|result| result);
let exit_code = match result {
Ok(()) => rustc_driver::EXIT_SUCCESS,
Err(_) => rustc_driver::EXIT_FAILURE,
};
std::process::exit(exit_code);
return run_compiler(rustc_args, &mut MiriCompilerCalls { miri_config });
}