Implement non-deterministc mode
Part of #653 This allows us to properly implement getrandom(), which unlocks the default HashMap type (e.g. HashMap<K, V>) with RandomState) This commit adds a new '-Zmiri-seed=<seed>' option. When present, this option takes a 64-bit hex value, which is used as the seed to an internal PRNG. This PRNG is used to implement the 'getrandom()' syscall. When '-Zmiri-seed' is not passed, 'getrandom()' will be disabled.
This commit is contained in:
parent
68711458b6
commit
73239573c9
7 changed files with 88 additions and 13 deletions
|
|
@ -44,6 +44,8 @@ shell-escape = "0.1.4"
|
|||
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
|
||||
# for more information.
|
||||
rustc-workspace-hack = "1.0.0"
|
||||
hex = "0.3.2"
|
||||
rand = "0.6.5"
|
||||
|
||||
[build-dependencies]
|
||||
vergen = "3"
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
fn visit_item(&mut self, i: &'hir hir::Item) {
|
||||
if let hir::ItemKind::Fn(.., body_id) = i.node {
|
||||
if i.attrs.iter().any(|attr| attr.check_name("test")) {
|
||||
let config = MiriConfig { validate: true, args: vec![] };
|
||||
let config = MiriConfig { validate: true, args: vec![], seed: None };
|
||||
let did = self.0.hir().body_owner_def_id(body_id);
|
||||
println!("running test: {}", self.0.def_path_debug_str(did));
|
||||
miri::eval_main(self.0, did, config);
|
||||
|
|
@ -61,7 +61,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
|
|||
}
|
||||
tcx.hir().krate().visit_all_item_likes(&mut Visitor(tcx));
|
||||
} else if let Some((entry_def_id, _)) = tcx.entry_fn(LOCAL_CRATE) {
|
||||
let config = MiriConfig { validate: true, args: vec![] };
|
||||
let config = MiriConfig { validate: true, args: vec![], seed: None };
|
||||
miri::eval_main(tcx, entry_def_id, config);
|
||||
|
||||
compiler.session().abort_if_errors();
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ fn main() {
|
|||
|
||||
// Parse our arguments and split them across `rustc` and `miri`.
|
||||
let mut validate = true;
|
||||
let mut seed: Option<u64> = None;
|
||||
let mut rustc_args = vec![];
|
||||
let mut miri_args = vec![];
|
||||
let mut after_dashdash = false;
|
||||
|
|
@ -146,7 +147,22 @@ fn main() {
|
|||
after_dashdash = true;
|
||||
}
|
||||
_ => {
|
||||
rustc_args.push(arg);
|
||||
let split: Vec<String> = arg.split("-Zmiri-seed=").map(|s| s.to_owned()).collect();
|
||||
if split.len() == 2 {
|
||||
if seed.is_some() {
|
||||
panic!("Cannot specify -Zmiri-seed multiple times!");
|
||||
}
|
||||
let seed_raw = hex::decode(&split[1]).unwrap();
|
||||
if seed_raw.len() > 8 {
|
||||
panic!(format!("-Zmiri-seed must be at most 8 bytes, was {}", seed_raw.len()));
|
||||
}
|
||||
|
||||
let mut bytes = [0; 8];
|
||||
bytes[..seed_raw.len()].copy_from_slice(&hex::decode(&split[1]).unwrap());
|
||||
seed = Some(u64::from_be_bytes(bytes));
|
||||
} else {
|
||||
rustc_args.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,7 +179,7 @@ fn main() {
|
|||
|
||||
debug!("rustc arguments: {:?}", rustc_args);
|
||||
debug!("miri arguments: {:?}", miri_args);
|
||||
let miri_config = miri::MiriConfig { validate, args: miri_args };
|
||||
let miri_config = miri::MiriConfig { validate, args: miri_args, seed };
|
||||
let result = rustc_driver::report_ices_to_stderr_if_any(move || {
|
||||
rustc_driver::run_compiler(&rustc_args, &mut MiriCompilerCalls { miri_config }, None, None)
|
||||
}).and_then(|result| result);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use rustc::hir::def_id::DefId;
|
|||
use rustc::mir;
|
||||
use syntax::attr;
|
||||
|
||||
use rand::RngCore;
|
||||
|
||||
use crate::*;
|
||||
|
||||
impl<'a, 'mir, 'tcx> EvalContextExt<'a, 'mir, 'tcx> for crate::MiriEvalContext<'a, 'mir, 'tcx> {}
|
||||
|
|
@ -216,9 +218,32 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
|
|||
// is called if a `HashMap` is created the regular way.
|
||||
match this.read_scalar(args[0])?.to_usize(this)? {
|
||||
318 | 511 => {
|
||||
return err!(Unimplemented(
|
||||
"miri does not support random number generators".to_owned(),
|
||||
))
|
||||
match this.machine.rng.as_ref() {
|
||||
Some(rng) => {
|
||||
let ptr = this.read_scalar(args[1])?.to_ptr()?;
|
||||
let len = this.read_scalar(args[2])?.to_usize(this)?;
|
||||
|
||||
// The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
|
||||
// neither of which have any effect on our current PRNG
|
||||
let _flags = this.read_scalar(args[3])?.to_i32()?;
|
||||
|
||||
let mut data = vec![0; len as usize];
|
||||
rng.borrow_mut().fill_bytes(&mut data);
|
||||
|
||||
this.memory_mut().get_mut(ptr.alloc_id)?
|
||||
.write_bytes(tcx, ptr, &data)?;
|
||||
|
||||
this.write_scalar(Scalar::from_uint(len, dest.layout.size), dest)?;
|
||||
|
||||
},
|
||||
None => {
|
||||
return err!(Unimplemented(
|
||||
"miri does not support random number generators in deterministic mode!
|
||||
Use '-Zmiri-seed=<seed>' to enable random number generation".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
id => {
|
||||
return err!(Unimplemented(
|
||||
|
|
|
|||
16
src/lib.rs
16
src/lib.rs
|
|
@ -23,6 +23,10 @@ mod stacked_borrows;
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
|
||||
use rustc::ty::{self, TyCtxt, query::TyCtxtAt};
|
||||
use rustc::ty::layout::{LayoutOf, Size, Align};
|
||||
|
|
@ -60,6 +64,9 @@ pub fn miri_default_args() -> &'static [&'static str] {
|
|||
pub struct MiriConfig {
|
||||
pub validate: bool,
|
||||
pub args: Vec<String>,
|
||||
|
||||
// The seed to use when non-determinism is required (e.g. getrandom())
|
||||
pub seed: Option<u64>
|
||||
}
|
||||
|
||||
// Used by priroda.
|
||||
|
|
@ -71,7 +78,7 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>(
|
|||
let mut ecx = InterpretCx::new(
|
||||
tcx.at(syntax::source_map::DUMMY_SP),
|
||||
ty::ParamEnv::reveal_all(),
|
||||
Evaluator::new(config.validate),
|
||||
Evaluator::new(config.validate, config.seed),
|
||||
);
|
||||
|
||||
let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id);
|
||||
|
|
@ -326,10 +333,14 @@ pub struct Evaluator<'tcx> {
|
|||
|
||||
/// Stacked Borrows state.
|
||||
pub(crate) stacked_borrows: stacked_borrows::State,
|
||||
|
||||
/// The random number generator to use if Miri
|
||||
/// is running in non-deterministic mode
|
||||
pub(crate) rng: Option<RefCell<StdRng>>
|
||||
}
|
||||
|
||||
impl<'tcx> Evaluator<'tcx> {
|
||||
fn new(validate: bool) -> Self {
|
||||
fn new(validate: bool, seed: Option<u64>) -> Self {
|
||||
Evaluator {
|
||||
env_vars: HashMap::default(),
|
||||
argc: None,
|
||||
|
|
@ -339,6 +350,7 @@ impl<'tcx> Evaluator<'tcx> {
|
|||
tls: TlsData::default(),
|
||||
validate,
|
||||
stacked_borrows: stacked_borrows::State::default(),
|
||||
rng: seed.map(|s| RefCell::new(StdRng::seed_from_u64(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
tests/compile-fail/getrandom.rs
Normal file
10
tests/compile-fail/getrandom.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#![feature(rustc_private)]
|
||||
extern crate libc;
|
||||
|
||||
fn main() {
|
||||
let mut buf = [0u8; 5];
|
||||
unsafe {
|
||||
libc::syscall(libc::SYS_getrandom, &mut buf as &mut [u8] as *mut [u8] as *mut u8 as *mut libc::c_void, 5, 0);
|
||||
//~^ ERROR constant evaluation error: miri does not support random number generators in deterministic mode!
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
use std::collections::{self, HashMap};
|
||||
use std::hash::BuildHasherDefault;
|
||||
// compile-flags: -Zmiri-seed=0000000000000000
|
||||
|
||||
fn main() {
|
||||
let mut map : HashMap<i32, i32, BuildHasherDefault<collections::hash_map::DefaultHasher>> = Default::default();
|
||||
use std::collections::{self, HashMap};
|
||||
use std::hash::{BuildHasherDefault, BuildHasher};
|
||||
|
||||
fn test_map<S: BuildHasher>(mut map: HashMap<i32, i32, S>) {
|
||||
map.insert(0, 0);
|
||||
assert_eq!(map.values().fold(0, |x, y| x+y), 0);
|
||||
|
||||
|
|
@ -22,4 +23,13 @@ fn main() {
|
|||
assert_eq!(map.values().fold(0, |x, y| x+y), num*(num-1)/2);
|
||||
|
||||
// TODO: Test Entry API, Iterators, ...
|
||||
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let map : HashMap<i32, i32, BuildHasherDefault<collections::hash_map::DefaultHasher>> = Default::default();
|
||||
let map_normal: HashMap<i32, i32> = HashMap::new();
|
||||
|
||||
test_map(map);
|
||||
test_map(map_normal);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue