Merge pull request #566 from RalfJung/foreign-full-mir

Support building and running with full MIR on foreign architectures, drop support for missing MIR
This commit is contained in:
Oliver S̶c̶h̶n̶e̶i̶d̶e̶r Scherer 2018-12-10 13:43:29 +01:00 committed by GitHub
commit 4f61314fc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 139 additions and 238 deletions

View file

@ -9,47 +9,52 @@ cache:
os:
- linux
- osx
dist: xenial
before_script:
# install extra stuff for cross-compilation
- if [[ "$TRAVIS_OS_NAME" == linux ]]; then sudo apt update && sudo apt install gcc-multilib; fi
# macOS weirdness (https://github.com/travis-ci/travis-ci/issues/6307, https://github.com/travis-ci/travis-ci/issues/10165)
- if [[ "$TRAVIS_OS_NAME" == osx ]]; then rvm get stable; fi
# Compute the rust version we use. We do not use "language: rust" to have more control here.
- |
if [ "$TRAVIS_EVENT_TYPE" = cron ]; then
if [[ "$TRAVIS_EVENT_TYPE" == cron ]]; then
RUST_TOOLCHAIN=nightly
else
RUST_TOOLCHAIN=$(cat rust-version)
fi
- |
if [ "$TRAVIS_OS_NAME" == osx ]; then
export MIRI_SYSROOT_BASE=~/Library/Caches/miri.miri.miri/
else
export MIRI_SYSROOT_BASE=~/.cache/miri/
fi
# install Rust
- curl https://build.travis-ci.org/files/rustup-init.sh -sSf | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN"
- export PATH=$HOME/.cargo/bin:$PATH
- rustc --version
# customize installation
- rustup target add i686-unknown-linux-gnu
- rustup target add i686-pc-windows-gnu
- rustup target add i686-pc-windows-msvc
script:
- set -e
- |
# Test and install plain miri
# Build and install miri
cargo build --release --all-features --all-targets &&
cargo test --release --all-features &&
cargo install --all-features --force --path .
- |
# Get ourselves a MIR-full libstd, and use it henceforth
# Get ourselves a MIR-full libstd for the host and a foreign architecture
cargo miri setup &&
if [ "$TRAVIS_OS_NAME" == osx ]; then
export MIRI_SYSROOT=~/Library/Caches/miri.miri.miri/HOST
if [[ "$TRAVIS_OS_NAME" == osx ]]; then
cargo miri setup --target i686-apple-darwin
else
export MIRI_SYSROOT=~/.cache/miri/HOST
cargo miri setup --target i686-unknown-linux-gnu
fi
- |
# Test miri with full MIR
cargo test --release --all-features
# Test miri with full MIR, on the host and other architectures
MIRI_SYSROOT=$MIRI_SYSROOT_BASE/HOST cargo test --release --all-features &&
MIRI_SYSROOT=$MIRI_SYSROOT_BASE cargo test --release --all-features
- |
# Test cargo integration
(cd cargo-miri-test && ./run-test.py)
(cd test-cargo-miri && MIRI_SYSROOT=$MIRI_SYSROOT_BASE/HOST ./run-test.py)
notifications:
email:

View file

@ -36,6 +36,7 @@ required-features = ["rustc_tests"]
byteorder = { version = "1.1", features = ["i128"]}
cargo_metadata = { version = "0.6", optional = true }
directories = { version = "1.0", optional = true }
rustc_version = { version = "0.2.3", optional = true }
env_logger = "0.5"
log = "0.4"
@ -44,7 +45,7 @@ vergen = "3"
[features]
default = ["cargo_miri"]
cargo_miri = ["cargo_metadata", "directories"]
cargo_miri = ["cargo_metadata", "directories", "rustc_version"]
rustc_tests = []
[dev-dependencies]

View file

@ -133,9 +133,6 @@ cp config.toml.example config.toml
rustup toolchain link custom build/x86_64-unknown-linux-gnu/stage2
# Now cd to your Miri directory, then configure rustup
rustup override set custom
# We also need to tell Miri where to find its sysroot. Since we set
# `test-miri` above, we can just use rustc' sysroot.
export MIRI_SYSROOT=$(rustc --print sysroot)
```
With this, you should now have a working development setup! See

View file

@ -27,13 +27,12 @@ build: false
test_script:
- set RUSTFLAGS=-g
- set RUST_BACKTRACE=1
# Test plain miri
# Build miri
- cargo build --release --all-features --all-targets
- cargo test --release --all-features
# Get ourselves a MIR-full libstd, and use it henceforth
- cargo run --release --all-features --bin cargo-miri -- miri setup
- set MIRI_SYSROOT=%USERPROFILE%\AppData\Local\miri\miri\cache\HOST
# Test miri with full MIR
# Test miri
- cargo test --release --all-features
notifications:

View file

@ -53,14 +53,34 @@ fn show_error(msg: String) -> ! {
std::process::exit(1)
}
fn list_targets(mut args: impl Iterator<Item=String>) -> impl Iterator<Item=cargo_metadata::Target> {
fn get_arg_flag_value(name: &str) -> Option<String> {
// stop searching at `--`
let mut args = std::env::args().skip_while(|val| !(val.starts_with(name) || val == "--"));
match args.next() {
Some(ref p) if p == "--" => None,
Some(ref p) if p == name => args.next(),
Some(p) => {
// Make sure this really starts with `$name=`, we didn't test for the `=` yet.
let v = &p[name.len()..]; // strip leading `$name`
if v.starts_with('=') {
Some(v[1..].to_owned()) // strip leading `=`
} else {
None
}
},
None => None,
}
}
fn list_targets() -> impl Iterator<Item=cargo_metadata::Target> {
// We need to get the manifest, and then the metadata, to enumerate targets.
let manifest_path_arg = args.find(|val| {
val.starts_with("--manifest-path=")
});
let manifest_path = get_arg_flag_value("--manifest-path").map(|m|
Path::new(&m).canonicalize().unwrap()
);
let mut metadata = if let Ok(metadata) = cargo_metadata::metadata(
manifest_path_arg.as_ref().map(AsRef::as_ref),
manifest_path.as_ref().map(AsRef::as_ref),
)
{
metadata
@ -68,10 +88,6 @@ fn list_targets(mut args: impl Iterator<Item=String>) -> impl Iterator<Item=carg
show_error(format!("error: Could not obtain cargo metadata."));
};
let manifest_path = manifest_path_arg.map(|arg| {
PathBuf::from(Path::new(&arg["--manifest-path=".len()..]))
});
let current_dir = std::env::current_dir();
let package_index = metadata
@ -176,17 +192,28 @@ path = "lib.rs"
"#).unwrap();
File::create(dir.join("lib.rs")).unwrap();
// Run xargo
if !Command::new("xargo").arg("build").arg("-q")
let target = get_arg_flag_value("--target");
let mut command = Command::new("xargo");
command.arg("build").arg("-q")
.current_dir(&dir)
.env("RUSTFLAGS", miri::miri_default_args().join(" "))
.env("XARGO_HOME", dir.to_str().unwrap())
.status().unwrap().success()
.env("XARGO_HOME", dir.to_str().unwrap());
if let Some(ref target) = target {
command.arg("--target").arg(&target);
}
if !command.status().unwrap().success()
{
show_error(format!("Failed to run xargo"));
}
// That should be it!
let sysroot = dir.join("HOST");
// 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) };
std::env::set_var("MIRI_SYSROOT", &sysroot);
if !ask_user {
println!("A libstd for miri is now available in `{}`", sysroot.display());
@ -232,7 +259,7 @@ fn main() {
}
// Now run the command.
for target in list_targets(std::env::args().skip(skip)) {
for target in list_targets() {
let args = std::env::args().skip(skip);
let kind = target.kind.get(0).expect(
"badly formatted cargo metadata: target::kind is an empty array",
@ -315,11 +342,11 @@ fn main() {
.collect()
};
args.splice(0..0, miri::miri_default_args().iter().map(ToString::to_string));
args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-miri""#.to_owned()]);
// this check ensures that dependencies are built but not interpreted and the final crate is
// interpreted but not built
let miri_enabled = std::env::args().any(|s| s == "--emit=dep-info,metadata");
let mut command = if miri_enabled {
let mut path = std::env::current_exe().expect("current executable path invalid");
path.set_file_name("miri");
@ -327,10 +354,9 @@ fn main() {
} else {
Command::new("rustc")
};
command.args(&args);
args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-miri""#.to_owned()]);
match command.args(&args).status() {
match command.status() {
Ok(exit) => {
if !exit.success() {
std::process::exit(exit.code().unwrap_or(42));
@ -361,7 +387,7 @@ where
args.push(r#"feature="cargo-miri""#.to_owned());
let path = std::env::current_exe().expect("current executable path invalid");
let exit_status = std::process::Command::new("cargo")
let exit_status = Command::new("cargo")
.args(&args)
.env("RUSTC", path)
.spawn()

View file

@ -17,18 +17,6 @@ pub trait EvalContextExt<'tcx, 'mir> {
ret: mir::BasicBlock,
) -> EvalResult<'tcx>;
/// Emulate a function that should have MIR but does not.
/// This is solely to support execution without full MIR.
/// Fail if emulating this function is not supported.
/// This function will handle `goto_block` if needed.
fn emulate_missing_fn(
&mut self,
path: String,
args: &[OpTy<'tcx, Borrow>],
dest: Option<PlaceTy<'tcx, Borrow>>,
ret: Option<mir::BasicBlock>,
) -> EvalResult<'tcx>;
fn find_fn(
&mut self,
instance: ty::Instance<'tcx>,
@ -81,24 +69,8 @@ impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for super::MiriEvalCo
return Ok(None);
}
// Otherwise we really want to see the MIR -- but if we do not have it, maybe we can
// emulate something. This is a HACK to support running without a full-MIR libstd.
let mir = match self.load_mir(instance.def) {
Ok(mir) => mir,
Err(EvalError { kind: EvalErrorKind::NoMirFor(path), .. }) => {
self.emulate_missing_fn(
path,
args,
dest,
ret,
)?;
// `goto_block` already handled
return Ok(None);
}
Err(other) => return Err(other),
};
Ok(Some(mir))
// Otherwise, load the MIR
Ok(Some(self.load_mir(instance.def)?))
}
fn emulate_foreign_item(
@ -113,6 +85,8 @@ impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for super::MiriEvalCo
Some(name) => name.as_str(),
None => self.tcx.item_name(def_id).as_str(),
};
// Strip linker suffixes (seen on 32bit macOS)
let link_name = link_name.trim_end_matches("$UNIX2003");
let tcx = &{self.tcx.tcx};
@ -655,58 +629,6 @@ impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for super::MiriEvalCo
Ok(())
}
fn emulate_missing_fn(
&mut self,
path: String,
_args: &[OpTy<'tcx, Borrow>],
dest: Option<PlaceTy<'tcx, Borrow>>,
ret: Option<mir::BasicBlock>,
) -> EvalResult<'tcx> {
// In some cases in non-MIR libstd-mode, not having a destination is legit. Handle these early.
match &path[..] {
"std::panicking::rust_panic_with_hook" |
"core::panicking::panic_fmt::::panic_impl" |
"std::rt::begin_panic_fmt" =>
return err!(MachineError("the evaluated program panicked".to_string())),
_ => {}
}
let dest = dest.ok_or_else(
// Must be some function we do not support
|| EvalErrorKind::NoMirFor(path.clone()),
)?;
match &path[..] {
// A Rust function is missing, which means we are running with MIR missing for libstd (or other dependencies).
// Still, we can make many things mostly work by "emulating" or ignoring some functions.
"std::io::_print" |
"std::io::_eprint" => {
warn!(
"Ignoring output. To run programs that prints, make sure you have a libstd with full MIR."
);
}
"std::thread::Builder::new" => {
return err!(Unimplemented("miri does not support threading".to_owned()))
}
"std::env::args" => {
return err!(Unimplemented(
"miri does not support program arguments".to_owned(),
))
}
"std::panicking::panicking" |
"std::rt::panicking" => {
// we abort on panic -> `std::rt::panicking` always returns false
self.write_scalar(Scalar::from_bool(false), dest)?;
}
_ => return err!(NoMirFor(path)),
}
self.goto_block(ret)?;
self.dump_place(*dest);
Ok(())
}
fn write_null(&mut self, dest: PlaceTy<'tcx, Borrow>) -> EvalResult<'tcx> {
self.write_scalar(Scalar::from_int(0, dest.layout.size), dest)
}

View file

@ -80,82 +80,62 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>(
));
}
let libstd_has_mir = {
let rustc_panic = ecx.resolve_path(&["std", "panicking", "rust_panic"])?;
ecx.load_mir(rustc_panic.def).is_ok()
};
let start_id = tcx.lang_items().start_fn().unwrap();
let main_ret_ty = tcx.fn_sig(main_id).output();
let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
let start_instance = ty::Instance::resolve(
ecx.tcx.tcx,
ty::ParamEnv::reveal_all(),
start_id,
ecx.tcx.mk_substs(
::std::iter::once(ty::subst::Kind::from(main_ret_ty)))
).unwrap();
let start_mir = ecx.load_mir(start_instance.def)?;
if libstd_has_mir {
let start_id = tcx.lang_items().start_fn().unwrap();
let main_ret_ty = tcx.fn_sig(main_id).output();
let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
let start_instance = ty::Instance::resolve(
ecx.tcx.tcx,
ty::ParamEnv::reveal_all(),
start_id,
ecx.tcx.mk_substs(
::std::iter::once(ty::subst::Kind::from(main_ret_ty)))
).unwrap();
let start_mir = ecx.load_mir(start_instance.def)?;
if start_mir.arg_count != 3 {
return err!(AbiViolation(format!(
"'start' lang item should have three arguments, but has {}",
start_mir.arg_count
)));
}
// Return value (in static memory so that it does not count as leak)
let ret = ecx.layout_of(start_mir.return_ty())?;
let ret_ptr = ecx.allocate(ret, MiriMemoryKind::MutStatic.into())?;
// Push our stack frame
ecx.push_stack_frame(
start_instance,
DUMMY_SP, // there is no call site, we want no span
start_mir,
Some(ret_ptr.into()),
StackPopCleanup::None { cleanup: true },
)?;
let mut args = ecx.frame().mir.args_iter();
// First argument: pointer to main()
let main_ptr = ecx.memory_mut().create_fn_alloc(main_instance).with_default_tag();
let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?;
ecx.write_scalar(Scalar::Ptr(main_ptr), dest)?;
// Second argument (argc): 1
let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?;
ecx.write_scalar(Scalar::from_int(1, dest.layout.size), dest)?;
// FIXME: extract main source file path
// Third argument (argv): &[b"foo"]
let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?;
let foo = ecx.memory_mut().allocate_static_bytes(b"foo\0").with_default_tag();
let foo_ty = ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8);
let foo_layout = ecx.layout_of(foo_ty)?;
let foo_place = ecx.allocate(foo_layout, MiriMemoryKind::Env.into())?;
ecx.write_scalar(Scalar::Ptr(foo), foo_place.into())?;
ecx.memory_mut().mark_immutable(foo_place.to_ptr()?.alloc_id)?;
ecx.write_scalar(foo_place.ptr, dest)?;
assert!(args.next().is_none(), "start lang item has more arguments than expected");
} else {
let ret_place = MPlaceTy::dangling(ecx.layout_of(tcx.mk_unit())?, &ecx).into();
ecx.push_stack_frame(
main_instance,
DUMMY_SP, // there is no call site, we want no span
main_mir,
Some(ret_place),
StackPopCleanup::None { cleanup: true },
)?;
// No arguments
let mut args = ecx.frame().mir.args_iter();
assert!(args.next().is_none(), "main function must not have arguments");
if start_mir.arg_count != 3 {
return err!(AbiViolation(format!(
"'start' lang item should have three arguments, but has {}",
start_mir.arg_count
)));
}
// Return value (in static memory so that it does not count as leak)
let ret = ecx.layout_of(start_mir.return_ty())?;
let ret_ptr = ecx.allocate(ret, MiriMemoryKind::MutStatic.into())?;
// Push our stack frame
ecx.push_stack_frame(
start_instance,
DUMMY_SP, // there is no call site, we want no span
start_mir,
Some(ret_ptr.into()),
StackPopCleanup::None { cleanup: true },
)?;
let mut args = ecx.frame().mir.args_iter();
// First argument: pointer to main()
let main_ptr = ecx.memory_mut().create_fn_alloc(main_instance).with_default_tag();
let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?;
ecx.write_scalar(Scalar::Ptr(main_ptr), dest)?;
// Second argument (argc): 1
let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?;
ecx.write_scalar(Scalar::from_int(1, dest.layout.size), dest)?;
// FIXME: extract main source file path
// Third argument (argv): &[b"foo"]
let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?;
let foo = ecx.memory_mut().allocate_static_bytes(b"foo\0").with_default_tag();
let foo_ty = ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8);
let foo_layout = ecx.layout_of(foo_ty)?;
let foo_place = ecx.allocate(foo_layout, MiriMemoryKind::Env.into())?;
ecx.write_scalar(Scalar::Ptr(foo), foo_place.into())?;
ecx.memory_mut().mark_immutable(foo_place.to_ptr()?.alloc_id)?;
ecx.write_scalar(foo_place.ptr, dest)?;
assert!(args.next().is_none(), "start lang item has more arguments than expected");
Ok(ecx)
}

View file

@ -1,4 +1,5 @@
#![feature(slice_concat_ext, custom_test_frameworks)]
// Custom test runner, to avoid libtest being wrapped around compiletest which wraps libtest.
#![test_runner(test_runner)]
use std::slice::SliceConcatExt;
@ -24,11 +25,6 @@ fn rustc_lib_path() -> PathBuf {
option_env!("RUSTC_LIB_PATH").unwrap().into()
}
fn have_fullmir() -> bool {
// We assume we have full MIR when MIRI_SYSROOT is set or when we are in rustc
std::env::var("MIRI_SYSROOT").is_ok() || rustc_test_suite().is_some()
}
fn mk_config(mode: &str) -> compiletest::common::ConfigWithTemp {
let mut config = compiletest::Config::default().tempdir();
config.mode = mode.parse().expect("Invalid mode");
@ -41,16 +37,7 @@ fn mk_config(mode: &str) -> compiletest::common::ConfigWithTemp {
config
}
fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, need_fullmir: bool, opt: bool) {
if need_fullmir && !have_fullmir() {
eprintln!("{}\n", format!(
"## Skipping compile-fail tests in {} against miri for target {} due to missing mir",
path,
target
).yellow().bold());
return;
}
fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, opt: bool) {
let opt_str = if opt { " with optimizations" } else { "" };
eprintln!("{}", format!(
"## Running compile-fail tests in {} against miri for target {}{}",
@ -78,16 +65,7 @@ fn compile_fail(sysroot: &Path, path: &str, target: &str, host: &str, need_fullm
compiletest::run_tests(&config);
}
fn miri_pass(sysroot: &Path, path: &str, target: &str, host: &str, need_fullmir: bool, opt: bool) {
if need_fullmir && !have_fullmir() {
eprintln!("{}\n", format!(
"## Skipping run-pass tests in {} against miri for target {} due to missing mir",
path,
target
).yellow().bold());
return;
}
fn miri_pass(sysroot: &Path, path: &str, target: &str, host: &str, opt: bool) {
let opt_str = if opt { " with optimizations" } else { "" };
eprintln!("{}", format!(
"## Running run-pass tests in {} against miri for target {}{}",
@ -105,10 +83,6 @@ fn miri_pass(sysroot: &Path, path: &str, target: &str, host: &str, need_fullmir:
// whitelist.
flags.push("-Zmir-opt-level=1".to_owned());
}
if !have_fullmir() {
// FIXME: Validation relies on the EscapeToRaw statements being emitted
flags.push("-Zmiri-disable-validation".to_owned());
}
let mut config = mk_config("ui");
config.src_base = PathBuf::from(path);
@ -132,7 +106,7 @@ fn target_has_std<P: Into<PathBuf>>(path: P) -> bool {
.map(|entry| entry.unwrap())
.filter(|entry| entry.file_type().unwrap().is_file())
.filter_map(|entry| entry.file_name().into_string().ok())
.any(|file_name| file_name.starts_with("libstd") && file_name.ends_with(".rlib"))
.any(|file_name| file_name == "libstd.rlib")
}
@ -168,17 +142,15 @@ fn get_sysroot() -> PathBuf {
fn get_host() -> String {
let rustc = rustc_test_suite().unwrap_or(PathBuf::from("rustc"));
let host = std::process::Command::new(rustc)
let rustc_version = std::process::Command::new(rustc)
.arg("-vV")
.output()
.expect("rustc not found for -vV")
.stdout;
let host = std::str::from_utf8(&host).expect("sysroot is not utf8");
let host = host.split("\nhost: ").nth(1).expect(
"no host: part in rustc -vV",
);
let host = host.split('\n').next().expect("no \n after host");
String::from(host)
let rustc_version = std::str::from_utf8(&rustc_version).expect("rustc -vV is not utf8");
let version_meta = rustc_version::version_meta_for(&rustc_version)
.expect("failed to parse rustc version info");
version_meta.host
}
fn run_pass_miri(opt: bool) {
@ -186,18 +158,17 @@ fn run_pass_miri(opt: bool) {
let host = get_host();
for_all_targets(&sysroot, |target| {
miri_pass(&sysroot, "tests/run-pass", &target, &host, false, opt);
miri_pass(&sysroot, "tests/run-pass", &target, &host, opt);
});
miri_pass(&sysroot, "tests/run-pass-fullmir", &host, &host, true, opt);
}
fn compile_fail_miri(opt: bool) {
let sysroot = get_sysroot();
let host = get_host();
// FIXME: run tests for other targets, too
compile_fail(&sysroot, "tests/compile-fail", &host, &host, false, opt);
compile_fail(&sysroot, "tests/compile-fail-fullmir", &host, &host, true, opt);
for_all_targets(&sysroot, |target| {
compile_fail(&sysroot, "tests/compile-fail", &target, &host, opt);
});
}
fn test_runner(_tests: &[&()]) {