Merge remote-tracking branch 'origin/master' into oflo

This commit is contained in:
Oliver Schneider
2016-06-20 12:33:09 +02:00
22 changed files with 229 additions and 228 deletions

View File

@@ -1,52 +1,58 @@
# Miri
# Miri [[slides](https://solson.me/miri-slides.pdf)] [[report](https://solson.me/miri-report.pdf)] [![Build Status](https://travis-ci.org/solson/miri.svg?branch=master)](https://travis-ci.org/solson/miri)
[[slides](https://solson.me/miri-slides.pdf)]
[[report](https://solson.me/miri-report.pdf)]
An experimental interpreter for [Rust][rust]'s [mid-level intermediate
representation][mir] (MIR). This project began as part of my work for the
undergraduate research course at the [University of Saskatchewan][usask].
[![Build Status](https://travis-ci.org/solson/miri.svg?branch=master)](https://travis-ci.org/solson/miri)
## Installing Rust
## Download Rust nightly
I currently recommend that you install [multirust][multirust] and then use it to
install the current rustc nightly version:
I recommend that you install [rustup][rustup] and then use it to install the
current Rust nightly version:
```sh
multirust update nightly
rustup update nightly
```
## Build
You should also make `nightly` the default version for your Miri directory by
running the following command while you're in it. If you don't do this, you can
run the later `cargo` commands by prefixing them with `rustup run nightly`.
```sh
multirust run nightly cargo build
rustup override add nightly
```
## Building Miri
```sh
cargo build
```
If Miri fails to build, it's likely because a change in the latest nightly
compiler broke it. You could try an older nightly with `multirust update
compiler broke it. You could try an older nightly with `rustup update
nightly-<date>` where `<date>` is a few days or weeks ago, e.g. `2016-05-20` for
May 20th. Otherwise, you could notify me in an issue or on IRC. Or, if you know
how to fix it, you could send a PR. :smile:
## Run a test
## Running tests
```sh
multirust run nightly cargo run -- \
--sysroot $HOME/.multirust/toolchains/nightly \
test/filename.rs
cargo run tests/run-pass/vecs.rs # Or whatever test you like.
```
If you are using [rustup][rustup] (the name of the multirust rewrite in Rust),
the `sysroot` path will also include your build target (e.g.
`$HOME/.multirust/toolchains/nightly-x86_64-apple-darwin`). You can see the
current toolchain's directory by running `rustup which cargo` (ignoring the
trailing `/bin/cargo`).
## Debugging
If you installed without using multirust or rustup, you'll need to adjust the
command to run your cargo and set the `sysroot` to the directory where your
Rust compiler is installed (`$sysroot/bin/rustc` should be a valid path).
You can get detailed, statement-by-statement traces by setting the `MIRI_RUN`
environment variable to `trace`. These traces are indented based on call stack
depth. You can get a much less verbose set of information with other logging
levels such as `warn`.
## Contributing and getting help
Check out the issues on this GitHub repository for some ideas. There's lots that
needs to be done that I haven't documented in the issues yet, however. For more
ideas or help with running or hacking on Miri, you can contact me (`scott`) on
Mozilla IRC in any of the Rust IRC channels (`#rust`, `#rust-offtopic`, etc).
## License
@@ -65,5 +71,4 @@ additional terms or conditions.
[rust]: https://www.rust-lang.org/
[mir]: https://github.com/rust-lang/rfcs/blob/master/text/1211-mir.md
[usask]: https://www.usask.ca/
[multirust]: https://github.com/brson/multirust
[rustup]: https://www.rustup.rs

View File

@@ -1,11 +1,9 @@
#![feature(custom_attribute, test)]
#![feature(rustc_private)]
#![allow(unused_attributes)]
#![feature(test, rustc_private)]
extern crate test;
use test::Bencher;
mod fibonacci_helper;
mod helpers;
use helpers::*;
#[bench]
fn fib(bencher: &mut Bencher) {
@@ -14,15 +12,11 @@ fn fib(bencher: &mut Bencher) {
})
}
mod miri_helper;
#[bench]
fn fib_miri(bencher: &mut Bencher) {
miri_helper::run("fibonacci_helper", bencher);
}
mod fibonacci_helper_iterative;
#[bench]
fn fib_iter(bencher: &mut Bencher) {
bencher.iter(|| {

View File

@@ -0,0 +1,65 @@
extern crate getopts;
extern crate miri;
extern crate rustc;
extern crate rustc_driver;
extern crate test;
use self::miri::eval_main;
use self::rustc::session::Session;
use self::rustc_driver::{driver, CompilerCalls, Compilation};
use std::cell::RefCell;
use std::rc::Rc;
use test::Bencher;
pub struct MiriCompilerCalls<'a>(Rc<RefCell<&'a mut Bencher>>);
fn find_sysroot() -> String {
// Taken from 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"));
match (home, toolchain) {
(Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain),
_ => option_env!("RUST_SYSROOT")
.expect("need to specify RUST_SYSROOT env var or use rustup or multirust")
.to_owned(),
}
}
pub fn run(filename: &str, bencher: &mut Bencher) {
let args = &[
"miri".to_string(),
format!("benches/helpers/{}.rs", filename),
"--sysroot".to_string(),
find_sysroot()
];
let compiler_calls = &mut MiriCompilerCalls(Rc::new(RefCell::new(bencher)));
rustc_driver::run_compiler(args, compiler_calls);
}
impl<'a> CompilerCalls<'a> for MiriCompilerCalls<'a> {
fn build_controller(
&mut self,
_: &Session,
_: &getopts::Matches
) -> driver::CompileController<'a> {
let mut control: driver::CompileController<'a> = driver::CompileController::basic();
let bencher = self.0.clone();
control.after_analysis.stop = Compilation::Stop;
control.after_analysis.callback = Box::new(move |state| {
state.session.abort_if_errors();
let tcx = state.tcx.unwrap();
let mir_map = state.mir_map.unwrap();
let (node_id, _) = state.session.entry_fn.borrow()
.expect("no main or start function found");
bencher.borrow_mut().iter(|| { eval_main(tcx, mir_map, node_id); });
state.session.abort_if_errors();
});
control
}
}

7
benches/helpers/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
// This module gets included in multiple crates, and they each only use part of it.
#![allow(dead_code)]
pub mod fibonacci_helper;
pub mod fibonacci_helper_iterative;
pub mod miri_helper;
pub mod smoke_helper;

View File

@@ -1,48 +0,0 @@
#![feature(custom_attribute, test)]
#![feature(rustc_private)]
#![allow(unused_attributes)]
extern crate getopts;
extern crate miri;
extern crate rustc;
extern crate rustc_driver;
extern crate test;
use self::miri::interpreter;
use self::rustc::session::Session;
use self::rustc_driver::{driver, CompilerCalls, Compilation};
use std::cell::RefCell;
use std::rc::Rc;
use std::env::var;
use test::Bencher;
pub struct MiriCompilerCalls<'a>(Rc<RefCell<&'a mut Bencher>>);
pub fn run(filename: &str, bencher: &mut Bencher) {
let path = var("RUST_SYSROOT").expect("env variable `RUST_SYSROOT` not set");
rustc_driver::run_compiler(&[
"miri".to_string(), format!("benches/{}.rs", filename), "--sysroot".to_string(), path.to_string(),
], &mut MiriCompilerCalls(Rc::new(RefCell::new(bencher))));
}
impl<'a> CompilerCalls<'a> for MiriCompilerCalls<'a> {
fn build_controller(
&mut self,
_: &Session,
_: &getopts::Matches
) -> driver::CompileController<'a> {
let mut control: driver::CompileController<'a> = driver::CompileController::basic();
let bencher = self.0.clone();
control.after_analysis.stop = Compilation::Stop;
control.after_analysis.callback = Box::new(move |state| {
state.session.abort_if_errors();
bencher.borrow_mut().iter(|| {
interpreter::interpret_start_points(state.tcx.unwrap(), state.mir_map.unwrap());
})
});
control
}
}

View File

@@ -1,11 +1,9 @@
#![feature(custom_attribute, test)]
#![feature(rustc_private)]
#![allow(unused_attributes)]
#![feature(test, rustc_private)]
extern crate test;
use test::Bencher;
mod smoke_helper;
mod helpers;
use helpers::*;
#[bench]
fn noop(bencher: &mut Bencher) {
@@ -33,8 +31,6 @@ fn noop_miri_full(bencher: &mut Bencher) {
}
*/
mod miri_helper;
#[bench]
fn noop_miri_interpreter(bencher: &mut Bencher) {
miri_helper::run("smoke_helper", bencher);

View File

@@ -9,17 +9,9 @@ extern crate log_settings;
extern crate syntax;
#[macro_use] extern crate log;
use miri::{
EvalContext,
CachedMir,
step,
EvalError,
Frame,
};
use miri::eval_main;
use rustc::session::Session;
use rustc_driver::{driver, CompilerCalls, Compilation};
use rustc::ty::{TyCtxt, subst};
use rustc::hir::def_id::DefId;
struct MiriCompilerCalls;
@@ -37,40 +29,10 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
let tcx = state.tcx.unwrap();
let mir_map = state.mir_map.unwrap();
let (node_id, _) = state.session.entry_fn.borrow()
.expect("no main or start function found");
eval_main(tcx, mir_map, node_id);
let (node_id, span) = state.session.entry_fn.borrow().expect("no main or start function found");
debug!("found `main` function at: {:?}", span);
let mir = mir_map.map.get(&node_id).expect("no mir for main function");
let def_id = tcx.map.local_def_id(node_id);
let mut ecx = EvalContext::new(tcx, mir_map);
let substs = tcx.mk_substs(subst::Substs::empty());
let return_ptr = ecx.alloc_ret_ptr(mir.return_ty, substs).expect("main function should not be diverging");
ecx.push_stack_frame(def_id, mir.span, CachedMir::Ref(mir), substs, Some(return_ptr));
if mir.arg_decls.len() == 2 {
// start function
let ptr_size = ecx.memory().pointer_size;
let nargs = ecx.memory_mut().allocate(ptr_size);
ecx.memory_mut().write_usize(nargs, 0).unwrap();
let args = ecx.memory_mut().allocate(ptr_size);
ecx.memory_mut().write_usize(args, 0).unwrap();
ecx.frame_mut().locals[0] = nargs;
ecx.frame_mut().locals[1] = args;
}
loop {
match step(&mut ecx) {
Ok(true) => {}
Ok(false) => break,
// FIXME: diverging functions can end up here in some future miri
Err(e) => {
report(tcx, &ecx, e);
break;
}
}
}
state.session.abort_if_errors();
});
@@ -78,43 +40,26 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
}
}
fn report(tcx: TyCtxt, ecx: &EvalContext, e: EvalError) {
let frame = ecx.stack().last().expect("stackframe was empty");
let block = &frame.mir.basic_blocks()[frame.block];
let span = if frame.stmt < block.statements.len() {
block.statements[frame.stmt].source_info.span
} else {
block.terminator().source_info.span
};
let mut err = tcx.sess.struct_span_err(span, &e.to_string());
for &Frame { def_id, substs, span, .. } in ecx.stack().iter().rev() {
// FIXME(solson): Find a way to do this without this Display impl hack.
use rustc::util::ppaux;
use std::fmt;
struct Instance<'tcx>(DefId, &'tcx subst::Substs<'tcx>);
impl<'tcx> fmt::Display for Instance<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
ppaux::parameterized(f, self.1, self.0, ppaux::Ns::Value, &[],
|tcx| Some(tcx.lookup_item_type(self.0).generics))
}
}
err.span_note(span, &format!("inside call to {}", Instance(def_id, substs)));
}
err.emit();
}
fn init_logger() {
const NSPACES: usize = 40;
const MAX_INDENT: usize = 40;
let format = |record: &log::LogRecord| {
// prepend spaces to indent the final string
let indentation = log_settings::settings().indentation;
format!("{lvl}:{module}{depth:2}{indent:<indentation$} {text}",
lvl = record.level(),
module = record.location().module_path(),
depth = indentation / NSPACES,
indentation = indentation % NSPACES,
indent = "",
text = record.args())
if record.level() == log::LogLevel::Trace {
// prepend spaces to indent the final string
let indentation = log_settings::settings().indentation;
format!("{lvl}:{module}{depth:2}{indent:<indentation$} {text}",
lvl = record.level(),
module = record.location().module_path(),
depth = indentation / MAX_INDENT,
indentation = indentation % MAX_INDENT,
indent = "",
text = record.args())
} else {
format!("{lvl}:{module}: {text}",
lvl = record.level(),
module = record.location().module_path(),
text = record.args())
}
};
let mut builder = env_logger::LogBuilder::new();

View File

@@ -1546,3 +1546,65 @@ impl StructExt for layout::Struct {
}
}
}
pub fn eval_main<'a, 'tcx: 'a>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
mir_map: &'a MirMap<'tcx>,
node_id: ast::NodeId,
) {
let mir = mir_map.map.get(&node_id).expect("no mir for main function");
let def_id = tcx.map.local_def_id(node_id);
let mut ecx = EvalContext::new(tcx, mir_map);
let substs = tcx.mk_substs(subst::Substs::empty());
let return_ptr = ecx.alloc_ret_ptr(mir.return_ty, substs).expect("main function should not be diverging");
ecx.push_stack_frame(def_id, mir.span, CachedMir::Ref(mir), substs, Some(return_ptr));
if mir.arg_decls.len() == 2 {
// start function
let ptr_size = ecx.memory().pointer_size;
let nargs = ecx.memory_mut().allocate(ptr_size);
ecx.memory_mut().write_usize(nargs, 0).unwrap();
let args = ecx.memory_mut().allocate(ptr_size);
ecx.memory_mut().write_usize(args, 0).unwrap();
ecx.frame_mut().locals[0] = nargs;
ecx.frame_mut().locals[1] = args;
}
loop {
match step(&mut ecx) {
Ok(true) => {}
Ok(false) => break,
// FIXME: diverging functions can end up here in some future miri
Err(e) => {
report(tcx, &ecx, e);
break;
}
}
}
}
fn report(tcx: TyCtxt, ecx: &EvalContext, e: EvalError) {
let frame = ecx.stack().last().expect("stackframe was empty");
let block = &frame.mir.basic_blocks()[frame.block];
let span = if frame.stmt < block.statements.len() {
block.statements[frame.stmt].source_info.span
} else {
block.terminator().source_info.span
};
let mut err = tcx.sess.struct_span_err(span, &e.to_string());
for &Frame { def_id, substs, span, .. } in ecx.stack().iter().rev() {
// FIXME(solson): Find a way to do this without this Display impl hack.
use rustc::util::ppaux;
use std::fmt;
struct Instance<'tcx>(DefId, &'tcx subst::Substs<'tcx>);
impl<'tcx> fmt::Display for Instance<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
ppaux::parameterized(f, self.1, self.0, ppaux::Ns::Value, &[],
|tcx| Some(tcx.lookup_item_type(self.0).generics))
}
}
err.span_note(span, &format!("inside call to {}", Instance(def_id, substs)));
}
err.emit();
}

View File

@@ -32,10 +32,11 @@ pub use error::{
};
pub use interpreter::{
EvalContext,
step,
Frame,
CachedMir,
EvalContext,
Frame,
eval_main,
step,
};
pub use memory::Memory;

View File

@@ -57,20 +57,23 @@ fn compile_test() {
for_all_targets(&sysroot, |target| {
for file in std::fs::read_dir("tests/run-pass").unwrap() {
let file = file.unwrap();
if !file.metadata().unwrap().is_file() {
let path = file.path();
if !file.metadata().unwrap().is_file() || !path.to_str().unwrap().ends_with(".rs") {
continue;
}
let file = file.path();
let stderr = std::io::stderr();
write!(stderr.lock(), "test [miri-pass] {} ", file.to_str().unwrap()).unwrap();
write!(stderr.lock(), "test [miri-pass] {} ... ", path.display()).unwrap();
let mut cmd = std::process::Command::new("target/debug/miri");
cmd.arg(file);
cmd.arg(path);
cmd.arg("-Dwarnings");
cmd.arg(format!("--target={}", target));
let libs = Path::new(&sysroot).join("lib");
let sysroot = libs.join("rustlib").join(&target).join("lib");
let paths = std::env::join_paths(&[libs, sysroot]).unwrap();
cmd.env(compiletest::procsrv::dylib_env_var(), paths);
match cmd.output() {
Ok(ref output) if output.status.success() => writeln!(stderr.lock(), "ok").unwrap(),
Ok(output) => {

View File

@@ -1,14 +1,9 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
#[miri_run]
fn simple() -> i32 {
let y = 10;
let f = |x| x + y;
f(2)
}
#[miri_run]
fn crazy_closure() -> (i32, i32, i32) {
fn inner<T: Copy>(t: T) -> (i32, T, T) {
struct NonCopy;
@@ -26,7 +21,7 @@ fn crazy_closure() -> (i32, i32, i32) {
inner(10)
}
// #[miri_run]
// TODO(solson): Implement closure argument adjustment and uncomment this test.
// fn closure_arg_adjustment_problem() -> i64 {
// fn once<F: FnOnce(i64)>(f: F) { f(2); }
// let mut y = 1;
@@ -37,7 +32,7 @@ fn crazy_closure() -> (i32, i32, i32) {
// y
// }
#[miri_run]
fn main() {
assert_eq!(simple(), 12);
assert_eq!(crazy_closure(), (84, 10, 10));
}

View File

@@ -1,11 +1,9 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
const A: usize = *&5;
#[miri_run]
fn foo() -> usize {
A
}
fn main() {}
fn main() {
assert_eq!(foo(), A);
}

View File

@@ -1,9 +1,4 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
// This tests that the size of Option<Box<i32>> is the same as *const i32.
#[miri_run]
fn option_box_deref() -> i32 {
let val = Some(Box::new(42));
unsafe {
@@ -12,4 +7,6 @@ fn option_box_deref() -> i32 {
}
}
fn main() {}
fn main() {
assert_eq!(option_box_deref(), 42);
}

View File

@@ -1,17 +1,11 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
#[miri_run]
fn tuple() -> (i16,) {
(1,)
}
#[miri_run]
fn tuple_2() -> (i16, i16) {
(1, 2)
}
#[miri_run]
fn tuple_5() -> (i16, i16, i16, i16, i16) {
(1, 2, 3, 4, 5)
}
@@ -19,19 +13,16 @@ fn tuple_5() -> (i16, i16, i16, i16, i16) {
#[derive(Debug, PartialEq)]
struct Pair { x: i8, y: i8 }
#[miri_run]
fn pair() -> Pair {
Pair { x: 10, y: 20 }
}
#[miri_run]
fn field_access() -> (i8, i8) {
let mut p = Pair { x: 10, y: 20 };
p.x += 5;
(p.x, p.y)
}
#[miri_run]
fn main() {
assert_eq!(tuple(), (1,));
assert_eq!(tuple_2(), (1, 2));

View File

@@ -1,6 +1,3 @@
#![feature(custom_attribute, specialization)]
#![allow(dead_code, unused_attributes)]
trait IsUnit {
fn is_unit() -> bool;
}
@@ -13,12 +10,10 @@ impl IsUnit for () {
fn is_unit() -> bool { true }
}
#[miri_run]
fn specialization() -> (bool, bool) {
(i32::is_unit(), <()>::is_unit())
}
#[miri_run]
fn main() {
assert_eq!(specialization(), (false, true));
}

View File

@@ -1,29 +1,27 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
#[miri_run]
fn empty() -> &'static str {
""
}
#[miri_run]
fn hello() -> &'static str {
"Hello, world!"
}
#[miri_run]
fn hello_bytes() -> &'static [u8; 13] {
b"Hello, world!"
}
#[miri_run]
fn hello_bytes_fat() -> &'static [u8] {
b"Hello, world!"
}
#[miri_run]
fn fat_pointer_on_32_bit() {
Some(5).expect("foo");
}
fn main() {}
fn main() {
assert_eq!(empty(), "");
assert_eq!(hello(), "Hello, world!");
assert_eq!(hello_bytes(), b"Hello, world!");
assert_eq!(hello_bytes_fat(), b"Hello, world!");
fat_pointer_on_32_bit(); // Should run without crashing.
}

View File

@@ -1,5 +1,5 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
// FIXME(solson): 32-bit mode doesn't test anything currently.
#![cfg_attr(target_pointer_width = "32", allow(dead_code))]
#[derive(Debug, PartialEq)]
enum Unit { Unit(()) } // Force non-C-enum representation.

View File

@@ -1,13 +1,11 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
#[miri_run]
fn empty() {}
#[miri_run]
fn unit_var() {
let x = ();
x
}
fn main() {}
fn main() {
empty();
unit_var();
}

View File

@@ -1,17 +1,16 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]
#[derive(PartialEq, Debug)]
struct A;
#[miri_run]
fn zst_ret() -> A {
A
}
#[miri_run]
fn use_zst() -> A {
let a = A;
a
}
fn main() {}
fn main() {
assert_eq!(zst_ret(), A);
assert_eq!(use_zst(), A);
}