Skip to content

Commit d905901

Browse files
committed
make miri a better RUSTC by default inside cargo-miri
this requires a change in sysroot handling: miri driver now requires MIRI_SYSROOT to be set when it is in 'target' mode, rather than relying on `--sysroot` always being present.
1 parent a5f0a9b commit d905901

File tree

8 files changed

+107
-70
lines changed

8 files changed

+107
-70
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,9 @@ Moreover, Miri recognizes some environment variables:
409409
checkout. Note that changing files in that directory does not automatically
410410
trigger a re-build of the standard library; you have to clear the Miri build
411411
cache manually (on Linux, `rm -rf ~/.cache/miri`).
412-
* `MIRI_SYSROOT` (recognized by `cargo miri` and the test suite) indicates the
412+
* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the
413413
sysroot to use. Only set this if you do not want to use the automatically
414-
created sysroot. (The `miri` driver sysroot is controlled via the `--sysroot`
415-
flag instead.)
414+
created sysroot.
416415
* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target
417416
architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same
418417
purpose.

cargo-miri/bin.rs

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,6 @@ fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut
205205
}
206206
}
207207

208-
fn forward_miri_sysroot(cmd: &mut Command) {
209-
let sysroot = env::var_os("MIRI_SYSROOT").expect("the wrapper should have set MIRI_SYSROOT");
210-
cmd.arg("--sysroot");
211-
cmd.arg(sysroot);
212-
}
213-
214208
/// Escapes `s` in a way that is suitable for using it as a string literal in TOML syntax.
215209
fn escape_for_toml(s: &str) -> String {
216210
// We want to surround this string in quotes `"`. So we first escape all quotes,
@@ -237,8 +231,15 @@ fn miri() -> Command {
237231
Command::new(find_miri())
238232
}
239233

234+
fn miri_for_host() -> Command {
235+
let mut cmd = miri();
236+
cmd.env("MIRI_BE_RUSTC", "host");
237+
cmd
238+
}
239+
240240
fn version_info() -> VersionMeta {
241-
VersionMeta::for_command(miri()).expect("failed to determine underlying rustc version of Miri")
241+
VersionMeta::for_command(miri_for_host())
242+
.expect("failed to determine underlying rustc version of Miri")
242243
}
243244

244245
fn cargo() -> Command {
@@ -336,7 +337,7 @@ fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
336337
a => show_error(format!("invalid answer `{}`", a)),
337338
};
338339
} else {
339-
println!("Running `{:?}` to {}.", cmd, text);
340+
eprintln!("Running `{:?}` to {}.", cmd, text);
340341
}
341342

342343
if cmd.status().unwrap_or_else(|_| panic!("failed to execute {:?}", cmd)).success().not() {
@@ -364,7 +365,7 @@ fn write_to_file(filename: &Path, content: &str) {
364365
/// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
365366
/// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
366367
/// done all this already.
367-
fn setup(subcommand: &MiriCommand) {
368+
fn setup(subcommand: &MiriCommand, host: &str, target: &str) {
368369
let only_setup = matches!(subcommand, MiriCommand::Setup);
369370
let ask_user = !only_setup;
370371
let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
@@ -398,8 +399,10 @@ fn setup(subcommand: &MiriCommand) {
398399
}
399400
None => {
400401
// Check for `rust-src` rustup component.
401-
let output =
402-
miri().args(&["--print", "sysroot"]).output().expect("failed to determine sysroot");
402+
let output = miri_for_host()
403+
.args(&["--print", "sysroot"])
404+
.output()
405+
.expect("failed to determine sysroot");
403406
if !output.status.success() {
404407
show_error(format!(
405408
"Failed to determine sysroot; Miri said:\n{}",
@@ -472,18 +475,21 @@ path = "lib.rs"
472475
);
473476
write_to_file(&dir.join("lib.rs"), "#![no_std]");
474477

475-
// Determine architectures.
476-
// We always need to set a target so rustc bootstrap can tell apart host from target crates.
477-
let host = version_info().host;
478-
let target = get_arg_flag_value("--target");
479-
let target = target.as_ref().unwrap_or(&host);
478+
// Figure out where xargo will build its stuff.
479+
// Unfortunately, it puts things into a different directory when the
480+
// architecture matches the host.
481+
let sysroot = if target == host { dir.join("HOST") } else { PathBuf::from(dir) };
482+
// Make sure all target-level Miri invocations know their sysroot.
483+
std::env::set_var("MIRI_SYSROOT", &sysroot);
484+
480485
// Now invoke xargo.
481486
let mut command = xargo_check();
482487
command.arg("check").arg("-q");
483-
command.arg("--target").arg(target);
484488
command.current_dir(&dir);
485489
command.env("XARGO_HOME", &dir);
486490
command.env("XARGO_RUST_SRC", &rust_src);
491+
// We always need to set a target so rustc bootstrap can tell apart host from target crates.
492+
command.arg("--target").arg(target);
487493
// Use Miri as rustc to build a libstd compatible with us (and use the right flags).
488494
// However, when we are running in bootstrap, we cannot just overwrite `RUSTC`,
489495
// because we still need bootstrap to distinguish between host and target crates.
@@ -523,6 +529,7 @@ path = "lib.rs"
523529
command.stdout(process::Stdio::null());
524530
command.stderr(process::Stdio::null());
525531
}
532+
526533
// Finally run it!
527534
if command.status().expect("failed to run xargo").success().not() {
528535
if only_setup {
@@ -534,11 +541,6 @@ path = "lib.rs"
534541
}
535542
}
536543

537-
// That should be it! But we need to figure out where xargo built stuff.
538-
// Unfortunately, it puts things into a different directory when the
539-
// architecture matches the host.
540-
let sysroot = if target == &host { dir.join("HOST") } else { PathBuf::from(dir) };
541-
std::env::set_var("MIRI_SYSROOT", &sysroot); // pass the env var to the processes we spawn, which will turn it into "--sysroot" flags
542544
// Figure out what to print.
543545
if only_setup {
544546
eprintln!("A sysroot for Miri is now available in `{}`.", sysroot.display());
@@ -677,8 +679,13 @@ fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
677679
};
678680
let verbose = num_arg_flag("-v");
679681

682+
// Determine the involved architectures.
683+
let host = version_info().host;
684+
let target = get_arg_flag_value("--target");
685+
let target = target.as_ref().unwrap_or(&host);
686+
680687
// We always setup.
681-
setup(&subcommand);
688+
setup(&subcommand, &host, target);
682689

683690
// Invoke actual cargo for the job, but with different flags.
684691
// We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
@@ -727,7 +734,7 @@ fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
727734
if get_arg_flag_value("--target").is_none() {
728735
// No target given. Explicitly pick the host.
729736
cmd.arg("--target");
730-
cmd.arg(version_info().host);
737+
cmd.arg(&host);
731738
}
732739

733740
// Set ourselves as runner for al binaries invoked by cargo.
@@ -754,16 +761,19 @@ fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
754761
"WARNING: Ignoring `RUSTC` environment variable; set `MIRI` if you want to control the binary used as the driver."
755762
);
756763
}
757-
// We'd prefer to just clear this env var, but cargo does not always honor `RUSTC_WRAPPER`
758-
// (https://github.com/rust-lang/cargo/issues/10885). There is no good way to single out these invocations;
759-
// some build scripts use the RUSTC env var as well. So we set it directly to the `miri` driver and
760-
// hope that all they do is ask for the version number -- things could quickly go downhill from here.
764+
// Build scripts (and also cargo: https://github.com/rust-lang/cargo/issues/10885) will invoke `rustc` even when `RUSTC_WRAPPER` is set.
765+
// To make sure everything is coherent, we want that to be the Miri driver, but acting as rustc, on the target level.
766+
// (Target, rather than host, is needed for cross-interpretation situations.) This is not a
767+
// perfect emulation of real rustc (it might be unable to produce binaries since the sysroot is
768+
// check-only), but it's as close as we can get, and it's good enough for autocfg.
769+
//
761770
// In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc
762771
// or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that
763772
// there would be a collision with other invocations of cargo-miri (as rustdoc or as runner).
764773
// We explicitly do this even if RUSTC_STAGE is set, since for these builds we do *not* want the
765774
// bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host builds.
766775
cmd.env("RUSTC", &fs::canonicalize(find_miri()).unwrap());
776+
cmd.env("MIRI_BE_RUSTC", "target"); // we better remember to *unset* this in the other phases!
767777

768778
// Set rustdoc to us as well, so we can run doctests.
769779
cmd.env("RUSTDOC", &cargo_miri_path);
@@ -832,6 +842,11 @@ fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
832842
}
833843
}
834844

845+
// phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
846+
// however, if we get called back by cargo here, we'll carefully compute the right flags
847+
// ourselves, so we first un-do what the earlier phase did.
848+
env::remove_var("MIRI_BE_RUSTC");
849+
835850
let verbose = std::env::var("MIRI_VERBOSE")
836851
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
837852
let target_crate = is_target_crate();
@@ -946,11 +961,6 @@ fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
946961
}
947962
}
948963

949-
// Use our custom sysroot (but not if that is what we are currently building).
950-
if phase != RustcPhase::Setup {
951-
forward_miri_sysroot(&mut cmd);
952-
}
953-
954964
// During setup, patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does).
955965
if phase == RustcPhase::Setup
956966
&& get_arg_flag_value("--crate-name").as_deref() == Some("panic_abort")
@@ -1010,6 +1020,11 @@ enum RunnerPhase {
10101020
}
10111021

10121022
fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: RunnerPhase) {
1023+
// phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
1024+
// however, if we get called back by cargo here, we'll carefully compute the right flags
1025+
// ourselves, so we first un-do what the earlier phase did.
1026+
env::remove_var("MIRI_BE_RUSTC");
1027+
10131028
let verbose = std::env::var("MIRI_VERBOSE")
10141029
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
10151030

@@ -1077,10 +1092,6 @@ fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: RunnerPhas
10771092
cmd.arg(arg);
10781093
}
10791094
}
1080-
// Set sysroot (if we are inside rustdoc, we already did that in `phase_cargo_rustdoc`).
1081-
if phase != RunnerPhase::Rustdoc {
1082-
forward_miri_sysroot(&mut cmd);
1083-
}
10841095
// Respect `MIRIFLAGS`.
10851096
if let Ok(a) = env::var("MIRIFLAGS") {
10861097
// This code is taken from `RUSTFLAGS` handling in cargo.
@@ -1151,7 +1162,7 @@ fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
11511162
cmd.arg("-Z").arg("unstable-options");
11521163

11531164
// rustdoc needs to know the right sysroot.
1154-
forward_miri_sysroot(&mut cmd);
1165+
cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
11551166
// make sure the 'miri' flag is set for rustdoc
11561167
cmd.arg("--cfg").arg("miri");
11571168

miri

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS"
131131

132132
# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`.
133133
build_sysroot() {
134-
export MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -q -- miri setup --print-sysroot "$@")"
134+
if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -q -- miri setup --print-sysroot "$@")"; then
135+
echo "'cargo miri setup' failed"
136+
exit 1
137+
fi
138+
export MIRI_SYSROOT
135139
}
136140

137141
# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account
@@ -201,7 +205,7 @@ run)
201205
$CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml
202206
find_sysroot
203207
# Then run the actual command.
204-
exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- --sysroot "$MIRI_SYSROOT" $MIRIFLAGS "$@"
208+
exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@"
205209
;;
206210
fmt)
207211
find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \

src/bin/miri.rs

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use rustc_middle::{
2727
},
2828
ty::{query::ExternProviders, TyCtxt},
2929
};
30-
use rustc_session::{search_paths::PathKind, CtfeBacktrace};
30+
use rustc_session::{config::CrateType, search_paths::PathKind, CtfeBacktrace};
3131

3232
use miri::{BacktraceStyle, ProvenanceMode};
3333

@@ -60,6 +60,10 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
6060

6161
queries.global_ctxt().unwrap().peek_mut().enter(|tcx| {
6262
init_late_loggers(tcx);
63+
if !tcx.sess.crate_types().contains(&CrateType::Executable) {
64+
tcx.sess.fatal("miri only makes sense on bin crates");
65+
}
66+
6367
let (entry_def_id, entry_type) = if let Some(entry_def) = tcx.entry_fn(()) {
6468
entry_def
6569
} else {
@@ -204,9 +208,9 @@ fn init_late_loggers(tcx: TyCtxt<'_>) {
204208
}
205209
}
206210

207-
/// Returns the "default sysroot" that Miri will use if no `--sysroot` flag is set.
211+
/// Returns the "default sysroot" that Miri will use for host things if no `--sysroot` flag is set.
208212
/// Should be a compile-time constant.
209-
fn compile_time_sysroot() -> Option<String> {
213+
fn host_sysroot() -> Option<String> {
210214
if option_env!("RUSTC_STAGE").is_some() {
211215
// This is being built as part of rustc, and gets shipped with rustup.
212216
// We can rely on the sysroot computation in librustc_session.
@@ -227,7 +231,7 @@ fn compile_time_sysroot() -> Option<String> {
227231
if toolchain_runtime != toolchain {
228232
show_error(format!(
229233
"This Miri got built with local toolchain `{toolchain}`, but now is being run under a different toolchain. \n\
230-
Make sure to run Miri in the toolchain it got built with, e.g. via `cargo +{toolchain} miri`."
234+
Make sure to run Miri in the toolchain it got built with, e.g. via `cargo +{toolchain} miri`."
231235
));
232236
}
233237
}
@@ -246,25 +250,42 @@ fn compile_time_sysroot() -> Option<String> {
246250
/// Execute a compiler with the given CLI arguments and callbacks.
247251
fn run_compiler(
248252
mut args: Vec<String>,
253+
target_crate: bool,
249254
callbacks: &mut (dyn rustc_driver::Callbacks + Send),
250-
insert_default_args: bool,
251255
) -> ! {
252256
// Make sure we use the right default sysroot. The default sysroot is wrong,
253257
// because `get_or_default_sysroot` in `librustc_session` bases that on `current_exe`.
254258
//
255-
// Make sure we always call `compile_time_sysroot` as that also does some sanity-checks
256-
// of the environment we were built in.
257-
// FIXME: Ideally we'd turn a bad build env into a compile-time error via CTFE or so.
258-
if let Some(sysroot) = compile_time_sysroot() {
259-
let sysroot_flag = "--sysroot";
260-
if !args.iter().any(|e| e == sysroot_flag) {
259+
// Make sure we always call `host_sysroot` as that also does some sanity-checks
260+
// of the environment we were built in and whether it matches what we are running in.
261+
let host_default_sysroot = host_sysroot();
262+
// Now see if we even need to set something.
263+
let sysroot_flag = "--sysroot";
264+
if !args.iter().any(|e| e == sysroot_flag) {
265+
// No sysroot was set, let's see if we have a custom default we want to configure.
266+
let default_sysroot = if target_crate {
267+
// Using the built-in default here would be plain wrong, so we *require*
268+
// the env var to make sure things make sense.
269+
Some(env::var("MIRI_SYSROOT").unwrap_or_else(|_| {
270+
show_error(format!(
271+
"Miri was invoked in 'target' mode without `MIRI_SYSROOT` or `--sysroot` being set"
272+
))
273+
}))
274+
} else {
275+
host_default_sysroot
276+
};
277+
if let Some(sysroot) = default_sysroot {
261278
// We need to overwrite the default that librustc_session would compute.
262279
args.push(sysroot_flag.to_owned());
263280
args.push(sysroot);
264281
}
265282
}
266283

267-
if insert_default_args {
284+
// Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
285+
// a "host" crate. That may cause procedural macros (and probably build scripts) to
286+
// depend on Miri-only symbols, such as `miri_resolve_frame`:
287+
// https://github.com/rust-lang/miri/issues/1760
288+
if target_crate {
268289
// Some options have different defaults in Miri than in plain rustc; apply those by making
269290
// them the first arguments after the binary name (but later arguments can overwrite them).
270291
args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
@@ -302,13 +323,8 @@ fn main() {
302323
// We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
303324
run_compiler(
304325
env::args().collect(),
326+
target_crate,
305327
&mut MiriBeRustCompilerCalls { target_crate },
306-
// Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
307-
// a "host" crate. That may cause procedural macros (and probably build scripts) to
308-
// depend on Miri-only symbols, such as `miri_resolve_frame`:
309-
// https://github.com/rust-lang/miri/issues/1760
310-
#[rustfmt::skip]
311-
/* insert_default_args: */ target_crate,
312328
)
313329
}
314330

@@ -502,9 +518,5 @@ fn main() {
502518

503519
debug!("rustc arguments: {:?}", rustc_args);
504520
debug!("crate arguments: {:?}", miri_config.args);
505-
run_compiler(
506-
rustc_args,
507-
&mut MiriCompilerCalls { miri_config },
508-
/* insert_default_args: */ true,
509-
)
521+
run_compiler(rustc_args, /* target_crate: */ true, &mut MiriCompilerCalls { miri_config })
510522
}

test-cargo-miri/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test-cargo-miri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ issue_rust_86261 = { path = "issue-rust-86261" }
2121
byteorder_2 = { package = "byteorder", version = "0.5" } # to test dev-dependencies behave as expected, with renaming
2222
serde_derive = "1.0" # not actually used, but exercises some unique code path (`--extern` .so file)
2323

24+
[build-dependencies]
25+
autocfg = "1"
26+
2427
[lib]
2528
test = false # test that this is respected (will show in the output)
2629

0 commit comments

Comments
 (0)