Skip to content

Commit

Permalink
Merge pull request #566 from RalfJung/foreign-full-mir
Browse files Browse the repository at this point in the history
Support building and running with full MIR on foreign architectures, drop support for missing MIR
  • Loading branch information
oli-obk authored Dec 10, 2018
2 parents bccadeb + 5689366 commit 4f61314
Show file tree
Hide file tree
Showing 90 changed files with 140 additions and 239 deletions.
33 changes: 19 additions & 14 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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]
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
66 changes: 46 additions & 20 deletions src/bin/cargo-miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,41 @@ 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
} else {
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
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -315,22 +342,21 @@ 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");
Command::new(path)
} 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));
Expand Down Expand Up @@ -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()
Expand Down
86 changes: 4 additions & 82 deletions src/fn_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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(
Expand All @@ -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};

Expand Down Expand Up @@ -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)
}
Expand Down
Loading

0 comments on commit 4f61314

Please sign in to comment.