Skip to content

Commit 7323957

Browse files
committed
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.
1 parent 6871145 commit 7323957

File tree

7 files changed

+87
-12
lines changed

7 files changed

+87
-12
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ shell-escape = "0.1.4"
4444
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
4545
# for more information.
4646
rustc-workspace-hack = "1.0.0"
47+
hex = "0.3.2"
48+
rand = "0.6.5"
4749

4850
[build-dependencies]
4951
vergen = "3"

src/bin/miri-rustc-tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
4848
fn visit_item(&mut self, i: &'hir hir::Item) {
4949
if let hir::ItemKind::Fn(.., body_id) = i.node {
5050
if i.attrs.iter().any(|attr| attr.check_name("test")) {
51-
let config = MiriConfig { validate: true, args: vec![] };
51+
let config = MiriConfig { validate: true, args: vec![], seed: None };
5252
let did = self.0.hir().body_owner_def_id(body_id);
5353
println!("running test: {}", self.0.def_path_debug_str(did));
5454
miri::eval_main(self.0, did, config);
@@ -61,7 +61,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
6161
}
6262
tcx.hir().krate().visit_all_item_likes(&mut Visitor(tcx));
6363
} else if let Some((entry_def_id, _)) = tcx.entry_fn(LOCAL_CRATE) {
64-
let config = MiriConfig { validate: true, args: vec![] };
64+
let config = MiriConfig { validate: true, args: vec![], seed: None };
6565
miri::eval_main(tcx, entry_def_id, config);
6666

6767
compiler.session().abort_if_errors();

src/bin/miri.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ fn main() {
126126

127127
// Parse our arguments and split them across `rustc` and `miri`.
128128
let mut validate = true;
129+
let mut seed: Option<u64> = None;
129130
let mut rustc_args = vec![];
130131
let mut miri_args = vec![];
131132
let mut after_dashdash = false;
@@ -146,7 +147,22 @@ fn main() {
146147
after_dashdash = true;
147148
}
148149
_ => {
149-
rustc_args.push(arg);
150+
let split: Vec<String> = arg.split("-Zmiri-seed=").map(|s| s.to_owned()).collect();
151+
if split.len() == 2 {
152+
if seed.is_some() {
153+
panic!("Cannot specify -Zmiri-seed multiple times!");
154+
}
155+
let seed_raw = hex::decode(&split[1]).unwrap();
156+
if seed_raw.len() > 8 {
157+
panic!(format!("-Zmiri-seed must be at most 8 bytes, was {}", seed_raw.len()));
158+
}
159+
160+
let mut bytes = [0; 8];
161+
bytes[..seed_raw.len()].copy_from_slice(&hex::decode(&split[1]).unwrap());
162+
seed = Some(u64::from_be_bytes(bytes));
163+
} else {
164+
rustc_args.push(arg);
165+
}
150166
}
151167
}
152168
}
@@ -163,7 +179,7 @@ fn main() {
163179

164180
debug!("rustc arguments: {:?}", rustc_args);
165181
debug!("miri arguments: {:?}", miri_args);
166-
let miri_config = miri::MiriConfig { validate, args: miri_args };
182+
let miri_config = miri::MiriConfig { validate, args: miri_args, seed };
167183
let result = rustc_driver::report_ices_to_stderr_if_any(move || {
168184
rustc_driver::run_compiler(&rustc_args, &mut MiriCompilerCalls { miri_config }, None, None)
169185
}).and_then(|result| result);

src/fn_call.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use rustc::hir::def_id::DefId;
44
use rustc::mir;
55
use syntax::attr;
66

7+
use rand::RngCore;
8+
79
use crate::*;
810

911
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<'
216218
// is called if a `HashMap` is created the regular way.
217219
match this.read_scalar(args[0])?.to_usize(this)? {
218220
318 | 511 => {
219-
return err!(Unimplemented(
220-
"miri does not support random number generators".to_owned(),
221-
))
221+
match this.machine.rng.as_ref() {
222+
Some(rng) => {
223+
let ptr = this.read_scalar(args[1])?.to_ptr()?;
224+
let len = this.read_scalar(args[2])?.to_usize(this)?;
225+
226+
// The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
227+
// neither of which have any effect on our current PRNG
228+
let _flags = this.read_scalar(args[3])?.to_i32()?;
229+
230+
let mut data = vec![0; len as usize];
231+
rng.borrow_mut().fill_bytes(&mut data);
232+
233+
this.memory_mut().get_mut(ptr.alloc_id)?
234+
.write_bytes(tcx, ptr, &data)?;
235+
236+
this.write_scalar(Scalar::from_uint(len, dest.layout.size), dest)?;
237+
238+
},
239+
None => {
240+
return err!(Unimplemented(
241+
"miri does not support random number generators in deterministic mode!
242+
Use '-Zmiri-seed=<seed>' to enable random number generation".to_owned(),
243+
))
244+
}
245+
}
246+
222247
}
223248
id => {
224249
return err!(Unimplemented(

src/lib.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ mod stacked_borrows;
2323

2424
use std::collections::HashMap;
2525
use std::borrow::Cow;
26+
use std::cell::RefCell;
27+
28+
use rand::rngs::StdRng;
29+
use rand::SeedableRng;
2630

2731
use rustc::ty::{self, TyCtxt, query::TyCtxtAt};
2832
use rustc::ty::layout::{LayoutOf, Size, Align};
@@ -60,6 +64,9 @@ pub fn miri_default_args() -> &'static [&'static str] {
6064
pub struct MiriConfig {
6165
pub validate: bool,
6266
pub args: Vec<String>,
67+
68+
// The seed to use when non-determinism is required (e.g. getrandom())
69+
pub seed: Option<u64>
6370
}
6471

6572
// Used by priroda.
@@ -71,7 +78,7 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>(
7178
let mut ecx = InterpretCx::new(
7279
tcx.at(syntax::source_map::DUMMY_SP),
7380
ty::ParamEnv::reveal_all(),
74-
Evaluator::new(config.validate),
81+
Evaluator::new(config.validate, config.seed),
7582
);
7683

7784
let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id);
@@ -326,10 +333,14 @@ pub struct Evaluator<'tcx> {
326333

327334
/// Stacked Borrows state.
328335
pub(crate) stacked_borrows: stacked_borrows::State,
336+
337+
/// The random number generator to use if Miri
338+
/// is running in non-deterministic mode
339+
pub(crate) rng: Option<RefCell<StdRng>>
329340
}
330341

331342
impl<'tcx> Evaluator<'tcx> {
332-
fn new(validate: bool) -> Self {
343+
fn new(validate: bool, seed: Option<u64>) -> Self {
333344
Evaluator {
334345
env_vars: HashMap::default(),
335346
argc: None,
@@ -339,6 +350,7 @@ impl<'tcx> Evaluator<'tcx> {
339350
tls: TlsData::default(),
340351
validate,
341352
stacked_borrows: stacked_borrows::State::default(),
353+
rng: seed.map(|s| RefCell::new(StdRng::seed_from_u64(s)))
342354
}
343355
}
344356
}

tests/compile-fail/getrandom.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(rustc_private)]
2+
extern crate libc;
3+
4+
fn main() {
5+
let mut buf = [0u8; 5];
6+
unsafe {
7+
libc::syscall(libc::SYS_getrandom, &mut buf as &mut [u8] as *mut [u8] as *mut u8 as *mut libc::c_void, 5, 0);
8+
//~^ ERROR constant evaluation error: miri does not support random number generators in deterministic mode!
9+
}
10+
}

tests/run-pass/hashmap.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
// compile-flags: -Zmiri-seed=0000000000000000
2+
13
use std::collections::{self, HashMap};
2-
use std::hash::BuildHasherDefault;
4+
use std::hash::{BuildHasherDefault, BuildHasher};
35

4-
fn main() {
5-
let mut map : HashMap<i32, i32, BuildHasherDefault<collections::hash_map::DefaultHasher>> = Default::default();
6+
fn test_map<S: BuildHasher>(mut map: HashMap<i32, i32, S>) {
67
map.insert(0, 0);
78
assert_eq!(map.values().fold(0, |x, y| x+y), 0);
89

@@ -22,4 +23,13 @@ fn main() {
2223
assert_eq!(map.values().fold(0, |x, y| x+y), num*(num-1)/2);
2324

2425
// TODO: Test Entry API, Iterators, ...
26+
27+
}
28+
29+
fn main() {
30+
let map : HashMap<i32, i32, BuildHasherDefault<collections::hash_map::DefaultHasher>> = Default::default();
31+
let map_normal: HashMap<i32, i32> = HashMap::new();
32+
33+
test_map(map);
34+
test_map(map_normal);
2535
}

0 commit comments

Comments
 (0)