Skip to content

Commit ad576d8

Browse files
committed
Auto merge of rust-lang#2208 - RalfJung:preempt, r=RalfJung
Make scheduler preemptive This is actually fairly easy. :D I just roll the dice on each terminator to decide whether we want to yield the active thread. I think with this we are also justified to no longer show "experimental" warnings when a thread is spawned. :) Closes rust-lang/miri#1388
2 parents d98bd98 + 11a8b3a commit ad576d8

File tree

88 files changed

+192
-273
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+192
-273
lines changed

README.md

+10-8
Original file line numberDiff line numberDiff line change
@@ -288,14 +288,16 @@ environment variable. We first document the most relevant and most commonly used
288288
`-Zmiri-disable-isolation` is set.
289289
* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some
290290
remaining threads to exist when the main thread exits.
291-
* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve
292-
non-determinism. This RNG is used to pick base addresses for allocations. When
293-
isolation is enabled (the default), this is also used to emulate system
294-
entropy. The default seed is 0. You can increase test coverage by running Miri
295-
multiple times with different seeds.
296-
**NOTE**: This entropy is not good enough for cryptographic use! Do not
297-
generate secret keys in Miri or perform other kinds of cryptographic
298-
operations that rely on proper random numbers.
291+
* `-Zmiri-preemption-rate` configures the probability that at the end of a basic block, the active
292+
thread will be preempted. The default is `0.01` (i.e., 1%). Setting this to `0` disables
293+
preemption.
294+
* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve non-determinism. This
295+
RNG is used to pick base addresses for allocations, to determine preemption and failure of
296+
`compare_exchange_weak`, and to control store buffering for weak memory emulation. When isolation
297+
is enabled (the default), this is also used to emulate system entropy. The default seed is 0. You
298+
can increase test coverage by running Miri multiple times with different seeds. **NOTE**: This
299+
entropy is not good enough for cryptographic use! Do not generate secret keys in Miri or perform
300+
other kinds of cryptographic operations that rely on proper random numbers.
299301
* `-Zmiri-strict-provenance` enables [strict
300302
provenance](https://github.com/rust-lang/rust/issues/95228) checking in Miri. This means that
301303
casting an integer to a pointer yields a result with 'invalid' provenance, i.e., with provenance

src/bin/miri.rs

+11
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,17 @@ fn main() {
449449
),
450450
};
451451
miri_config.cmpxchg_weak_failure_rate = rate;
452+
} else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") {
453+
let rate = match param.parse::<f64>() {
454+
Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
455+
Ok(_) => panic!("-Zmiri-preemption-rate must be between `0.0` and `1.0`"),
456+
Err(err) =>
457+
panic!(
458+
"-Zmiri-preemption-rate requires a `f64` between `0.0` and `1.0`: {}",
459+
err
460+
),
461+
};
462+
miri_config.preemption_rate = rate;
452463
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
453464
miri_config.measureme_out = Some(param.to_string());
454465
} else if let Some(param) = arg.strip_prefix("-Zmiri-backtrace=") {

src/concurrency/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
mod allocation_map;
21
pub mod data_race;
2+
mod range_object_map;
33
pub mod weak_memory;

src/concurrency/allocation_map.rs renamed to src/concurrency/range_object_map.rs

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
//! Implements a map from allocation ranges to data.
2-
//! This is somewhat similar to RangeMap, but the ranges
3-
//! and data are discrete and non-splittable. An allocation in the
4-
//! map will always have the same range until explicitly removed
1+
//! Implements a map from allocation ranges to data. This is somewhat similar to RangeMap, but the
2+
//! ranges and data are discrete and non-splittable -- they represent distinct "objects". An
3+
//! allocation in the map will always have the same range until explicitly removed
54
65
use rustc_target::abi::Size;
76
use std::ops::{Index, IndexMut, Range};
@@ -20,7 +19,7 @@ struct Elem<T> {
2019
type Position = usize;
2120

2221
#[derive(Clone, Debug)]
23-
pub struct AllocationMap<T> {
22+
pub struct RangeObjectMap<T> {
2423
v: Vec<Elem<T>>,
2524
}
2625

@@ -34,7 +33,7 @@ pub enum AccessType {
3433
ImperfectlyOverlapping(Range<Position>),
3534
}
3635

37-
impl<T> AllocationMap<T> {
36+
impl<T> RangeObjectMap<T> {
3837
pub fn new() -> Self {
3938
Self { v: Vec::new() }
4039
}
@@ -135,15 +134,15 @@ impl<T> AllocationMap<T> {
135134
}
136135
}
137136

138-
impl<T> Index<Position> for AllocationMap<T> {
137+
impl<T> Index<Position> for RangeObjectMap<T> {
139138
type Output = T;
140139

141140
fn index(&self, pos: Position) -> &Self::Output {
142141
&self.v[pos].data
143142
}
144143
}
145144

146-
impl<T> IndexMut<Position> for AllocationMap<T> {
145+
impl<T> IndexMut<Position> for RangeObjectMap<T> {
147146
fn index_mut(&mut self, pos: Position) -> &mut Self::Output {
148147
&mut self.v[pos].data
149148
}
@@ -159,7 +158,7 @@ mod tests {
159158
fn empty_map() {
160159
// FIXME: make Size::from_bytes const
161160
let four = Size::from_bytes(4);
162-
let map = AllocationMap::<()>::new();
161+
let map = RangeObjectMap::<()>::new();
163162

164163
// Correctly tells where we should insert the first element (at position 0)
165164
assert_eq!(map.find_offset(Size::from_bytes(3)), Err(0));
@@ -173,7 +172,7 @@ mod tests {
173172
fn no_overlapping_inserts() {
174173
let four = Size::from_bytes(4);
175174

176-
let mut map = AllocationMap::<&str>::new();
175+
let mut map = RangeObjectMap::<&str>::new();
177176

178177
// |_|_|_|_|#|#|#|#|_|_|_|_|...
179178
// 0 1 2 3 4 5 6 7 8 9 a b c d
@@ -187,7 +186,7 @@ mod tests {
187186
fn boundaries() {
188187
let four = Size::from_bytes(4);
189188

190-
let mut map = AllocationMap::<&str>::new();
189+
let mut map = RangeObjectMap::<&str>::new();
191190

192191
// |#|#|#|#|_|_|...
193192
// 0 1 2 3 4 5
@@ -215,7 +214,7 @@ mod tests {
215214
fn perfectly_overlapping() {
216215
let four = Size::from_bytes(4);
217216

218-
let mut map = AllocationMap::<&str>::new();
217+
let mut map = RangeObjectMap::<&str>::new();
219218

220219
// |#|#|#|#|_|_|...
221220
// 0 1 2 3 4 5
@@ -241,7 +240,7 @@ mod tests {
241240
fn straddling() {
242241
let four = Size::from_bytes(4);
243242

244-
let mut map = AllocationMap::<&str>::new();
243+
let mut map = RangeObjectMap::<&str>::new();
245244

246245
// |_|_|_|_|#|#|#|#|_|_|_|_|...
247246
// 0 1 2 3 4 5 6 7 8 9 a b c d

src/concurrency/weak_memory.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ use rustc_data_structures::fx::FxHashMap;
8585
use crate::{AtomicReadOp, AtomicRwOp, AtomicWriteOp, Tag, VClock, VTimestamp, VectorIdx};
8686

8787
use super::{
88-
allocation_map::{AccessType, AllocationMap},
8988
data_race::{GlobalState, ThreadClockSet},
89+
range_object_map::{AccessType, RangeObjectMap},
9090
};
9191

9292
pub type AllocExtra = StoreBufferAlloc;
@@ -101,7 +101,7 @@ const STORE_BUFFER_LIMIT: usize = 128;
101101
pub struct StoreBufferAlloc {
102102
/// Store buffer of each atomic object in this allocation
103103
// Behind a RefCell because we need to allocate/remove on read access
104-
store_buffers: RefCell<AllocationMap<StoreBuffer>>,
104+
store_buffers: RefCell<RangeObjectMap<StoreBuffer>>,
105105
}
106106

107107
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -134,7 +134,7 @@ struct StoreElement {
134134

135135
impl StoreBufferAlloc {
136136
pub fn new_allocation() -> Self {
137-
Self { store_buffers: RefCell::new(AllocationMap::new()) }
137+
Self { store_buffers: RefCell::new(RangeObjectMap::new()) }
138138
}
139139

140140
/// Checks if the range imperfectly overlaps with existing buffers

src/eval.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ pub struct MiriConfig {
122122
/// Whether to ignore any output by the program. This is helpful when debugging miri
123123
/// as its messages don't get intermingled with the program messages.
124124
pub mute_stdout_stderr: bool,
125+
/// The probability of the active thread being preempted at the end of each basic block.
126+
pub preemption_rate: f64,
125127
}
126128

127129
impl Default for MiriConfig {
@@ -145,12 +147,13 @@ impl Default for MiriConfig {
145147
tag_raw: false,
146148
data_race_detector: true,
147149
weak_memory_emulation: true,
148-
cmpxchg_weak_failure_rate: 0.8,
150+
cmpxchg_weak_failure_rate: 0.8, // 80%
149151
measureme_out: None,
150152
panic_on_unsupported: false,
151153
backtrace_style: BacktraceStyle::Short,
152154
provenance_mode: ProvenanceMode::Legacy,
153155
mute_stdout_stderr: false,
156+
preemption_rate: 0.01, // 1%
154157
}
155158
}
156159
}

src/machine.rs

+9
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ pub struct Evaluator<'mir, 'tcx> {
333333

334334
/// Whether weak memory emulation is enabled
335335
pub(crate) weak_memory: bool,
336+
337+
/// The probability of the active thread being preempted at the end of each basic block.
338+
pub(crate) preemption_rate: f64,
336339
}
337340

338341
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
@@ -389,6 +392,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
389392
cmpxchg_weak_failure_rate: config.cmpxchg_weak_failure_rate,
390393
mute_stdout_stderr: config.mute_stdout_stderr,
391394
weak_memory: config.weak_memory_emulation,
395+
preemption_rate: config.preemption_rate,
392396
}
393397
}
394398

@@ -846,6 +850,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
846850
ecx.active_thread_stack_mut()
847851
}
848852

853+
fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
854+
ecx.maybe_preempt_active_thread();
855+
Ok(())
856+
}
857+
849858
#[inline(always)]
850859
fn after_stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
851860
if ecx.machine.stacked_borrows.is_some() { ecx.retag_return_place() } else { Ok(()) }

src/shims/unix/thread.rs

-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1313
) -> InterpResult<'tcx, i32> {
1414
let this = self.eval_context_mut();
1515

16-
this.tcx.sess.warn(
17-
"thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.\n(see https://github.com/rust-lang/miri/issues/1388)",
18-
);
19-
2016
// Create the new thread
2117
let new_thread_id = this.create_thread();
2218

src/thread.rs

+10
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
717717
this.machine.threads.yield_active_thread();
718718
}
719719

720+
#[inline]
721+
fn maybe_preempt_active_thread(&mut self) {
722+
use rand::Rng as _;
723+
724+
let this = self.eval_context_mut();
725+
if this.machine.rng.get_mut().gen_bool(this.machine.preemption_rate) {
726+
this.yield_active_thread();
727+
}
728+
}
729+
720730
#[inline]
721731
fn register_timeout_callback(
722732
&mut self,
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: the main thread terminated without waiting for all remaining threads
52

63
note: pass `-Zmiri-ignore-leaks` to disable this check
74

8-
error: aborting due to previous error; 1 warning emitted
5+
error: aborting due to previous error
96

tests/fail/concurrency/libc_pthread_join_detached.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: trying to join a detached or already joined thread
52
--> $DIR/libc_pthread_join_detached.rs:LL:CC
63
|
@@ -14,5 +11,5 @@ LL | ... assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
1411

1512
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
1613

17-
error: aborting due to previous error; 1 warning emitted
14+
error: aborting due to previous error
1815

tests/fail/concurrency/libc_pthread_join_joined.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: trying to join a detached or already joined thread
52
--> $DIR/libc_pthread_join_joined.rs:LL:CC
63
|
@@ -14,5 +11,5 @@ LL | ... assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
1411

1512
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
1613

17-
error: aborting due to previous error; 1 warning emitted
14+
error: aborting due to previous error
1815

tests/fail/concurrency/libc_pthread_join_main.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: trying to join a detached or already joined thread
52
--> $DIR/libc_pthread_join_main.rs:LL:CC
63
|
@@ -14,5 +11,5 @@ LL | ... assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
1411

1512
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
1613

17-
error: aborting due to previous error; 1 warning emitted
14+
error: aborting due to previous error
1815

tests/fail/concurrency/libc_pthread_join_multiple.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: trying to join a detached or already joined thread
52
--> $DIR/libc_pthread_join_multiple.rs:LL:CC
63
|
@@ -14,5 +11,5 @@ LL | ... assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0);
1411

1512
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
1613

17-
error: aborting due to previous error; 1 warning emitted
14+
error: aborting due to previous error
1815

tests/fail/concurrency/libc_pthread_join_self.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// ignore-windows: No libc on Windows
2+
// We are making scheduler assumptions here.
3+
// compile-flags: -Zmiri-preemption-rate=0
24

35
// Joining itself is undefined behavior.
46

tests/fail/concurrency/libc_pthread_join_self.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: trying to join itself
52
--> $DIR/libc_pthread_join_self.rs:LL:CC
63
|
@@ -14,5 +11,5 @@ LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
1411

1512
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
1613

17-
error: aborting due to previous error; 1 warning emitted
14+
error: aborting due to previous error
1815

tests/fail/concurrency/thread_local_static_dealloc.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
52
--> $DIR/thread_local_static_dealloc.rs:LL:CC
63
|
@@ -14,5 +11,5 @@ LL | let _val = *(dangling_ptr as *const u8);
1411

1512
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
1613

17-
error: aborting due to previous error; 1 warning emitted
14+
error: aborting due to previous error
1815

tests/fail/concurrency/too_few_args.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: callee has fewer arguments than expected
52
--> $DIR/too_few_args.rs:LL:CC
63
|
@@ -13,5 +10,5 @@ LL | panic!()
1310
= note: inside `thread_start` at RUSTLIB/std/src/panic.rs:LL:CC
1411
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
1512

16-
error: aborting due to previous error; 1 warning emitted
13+
error: aborting due to previous error
1714

tests/fail/concurrency/too_many_args.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
error: Undefined Behavior: callee has more arguments than expected
52
--> $DIR/too_many_args.rs:LL:CC
63
|
@@ -13,5 +10,5 @@ LL | panic!()
1310
= note: inside `thread_start` at RUSTLIB/std/src/panic.rs:LL:CC
1411
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
1512

16-
error: aborting due to previous error; 1 warning emitted
13+
error: aborting due to previous error
1714

tests/fail/concurrency/unwind_top_of_stack.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
warning: thread support is experimental: the scheduler is not preemptive, and can get stuck in spin loops.
2-
(see https://github.com/rust-lang/miri/issues/1388)
3-
41
thread '<unnamed>' panicked at 'explicit panic', $DIR/unwind_top_of_stack.rs:LL:CC
52
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
63
error: Undefined Behavior: unwinding past the topmost frame of the stack
@@ -16,5 +13,5 @@ LL | | }
1613

1714
= note: inside `thread_start` at $DIR/unwind_top_of_stack.rs:LL:CC
1815

19-
error: aborting due to previous error; 1 warning emitted
16+
error: aborting due to previous error
2017

0 commit comments

Comments
 (0)