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

Support building and running with full MIR on foreign architectures, drop support for missing MIR #566

Merged
merged 8 commits into from
Dec 10, 2018
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
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