Skip to content

Commit 2155a30

Browse files
committed
when reusing an address, most of the time only reuse from the current thread
1 parent c962d88 commit 2155a30

Some content is hidden

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

43 files changed

+183
-109
lines changed

README.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,13 @@ environment variable. We first document the most relevant and most commonly used
298298
* `-Zmiri-address-reuse-rate=<rate>` changes the probability that a freed *non-stack* allocation
299299
will be added to the pool for address reuse, and the probability that a new *non-stack* allocation
300300
will be taken from the pool. Stack allocations never get added to or taken from the pool. The
301-
default is `0.5`. Note that a very high reuse rate can mask concurrency bugs as address
302-
reuse induces synchronization between threads.
301+
default is `0.5`.
302+
* `-Zmiri-address-reuse-cross-thread-rate=<rate>` changes the probability that an allocation which
303+
attempts to reuse a previously freed block of memory will also consider blocks freed by *other
304+
threads*. The default is `0.1`, which means by default, in 90% of the cases where an address reuse
305+
attempt is made, only addresses from the same thread will be considered. Reusing an address from
306+
another thread induces synchronization between those threads, which can mask data races and weak
307+
memory bugs.
303308
* `-Zmiri-compare-exchange-weak-failure-rate=<rate>` changes the failure rate of
304309
`compare_exchange_weak` operations. The default is `0.8` (so 4 out of 5 weak ops will fail).
305310
You can change it to any value between `0.0` and `1.0`, where `1.0` means it

src/alloc_addresses/mod.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl GlobalStateInner {
7878
GlobalStateInner {
7979
int_to_ptr_map: Vec::default(),
8080
base_addr: FxHashMap::default(),
81-
reuse: ReusePool::new(config.address_reuse_rate),
81+
reuse: ReusePool::new(config),
8282
exposed: FxHashSet::default(),
8383
next_base_addr: stack_addr,
8484
provenance_mode: config.provenance_mode,
@@ -164,9 +164,13 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
164164
assert!(!matches!(kind, AllocKind::Dead));
165165

166166
// This allocation does not have a base address yet, pick or reuse one.
167-
let base_addr = if let Some((reuse_addr, clock)) =
168-
global_state.reuse.take_addr(&mut *rng, size, align, memory_kind)
169-
{
167+
let base_addr = if let Some((reuse_addr, clock)) = global_state.reuse.take_addr(
168+
&mut *rng,
169+
size,
170+
align,
171+
memory_kind,
172+
ecx.get_active_thread(),
173+
) {
170174
if let Some(data_race) = &ecx.machine.data_race {
171175
data_race.validate_lock_acquire(&clock, ecx.get_active_thread());
172176
}
@@ -363,12 +367,13 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
363367
// `alloc_id_from_addr` any more.
364368
global_state.exposed.remove(&dead_id);
365369
// Also remember this address for future reuse.
366-
global_state.reuse.add_addr(rng, addr, size, align, kind, || {
370+
let thread = self.threads.get_active_thread_id();
371+
global_state.reuse.add_addr(rng, addr, size, align, kind, thread, || {
367372
let mut clock = concurrency::VClock::default();
368373
if let Some(data_race) = &self.data_race {
369374
data_race.validate_lock_release(
370375
&mut clock,
371-
self.threads.get_active_thread_id(),
376+
thread,
372377
self.threads.active_thread_ref().current_span(),
373378
);
374379
}

src/alloc_addresses/reuse_pool.rs

+35-14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rand::Rng;
44

55
use rustc_target::abi::{Align, Size};
66

7-
use crate::{concurrency::VClock, MemoryKind};
7+
use crate::{concurrency::VClock, MemoryKind, MiriConfig, ThreadId};
88

99
const MAX_POOL_SIZE: usize = 64;
1010

@@ -15,23 +15,28 @@ const MAX_POOL_SIZE: usize = 64;
1515
#[derive(Debug)]
1616
pub struct ReusePool {
1717
address_reuse_rate: f64,
18+
address_reuse_cross_thread_rate: f64,
1819
/// The i-th element in `pool` stores allocations of alignment `2^i`. We store these reusable
19-
/// allocations as address-size pairs, the list must be sorted by the size.
20+
/// allocations as address-size pairs, the list must be sorted by the size and then the thread ID.
2021
///
2122
/// Each of these maps has at most MAX_POOL_SIZE elements, and since alignment is limited to
2223
/// less than 64 different possible value, that bounds the overall size of the pool.
2324
///
24-
/// We also store the clock from the thread that donated this pool element,
25+
/// We also store the ID and the data-race clock of the thread that donated this pool element,
2526
/// to ensure synchronization with the thread that picks up this address.
26-
pool: Vec<Vec<(u64, Size, VClock)>>,
27+
pool: Vec<Vec<(u64, Size, ThreadId, VClock)>>,
2728
}
2829

2930
impl ReusePool {
30-
pub fn new(address_reuse_rate: f64) -> Self {
31-
ReusePool { address_reuse_rate, pool: vec![] }
31+
pub fn new(config: &MiriConfig) -> Self {
32+
ReusePool {
33+
address_reuse_rate: config.address_reuse_rate,
34+
address_reuse_cross_thread_rate: config.address_reuse_cross_thread_rate,
35+
pool: vec![],
36+
}
3237
}
3338

34-
fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size, VClock)> {
39+
fn subpool(&mut self, align: Align) -> &mut Vec<(u64, Size, ThreadId, VClock)> {
3540
let pool_idx: usize = align.bytes().trailing_zeros().try_into().unwrap();
3641
if self.pool.len() <= pool_idx {
3742
self.pool.resize(pool_idx + 1, Vec::new());
@@ -46,6 +51,7 @@ impl ReusePool {
4651
size: Size,
4752
align: Align,
4853
kind: MemoryKind,
54+
thread: ThreadId,
4955
clock: impl FnOnce() -> VClock,
5056
) {
5157
// Let's see if we even want to remember this address.
@@ -55,18 +61,21 @@ impl ReusePool {
5561
if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) {
5662
return;
5763
}
64+
let clock = clock();
5865
// Determine the pool to add this to, and where in the pool to put it.
5966
let subpool = self.subpool(align);
60-
let pos = subpool.partition_point(|(_addr, other_size, _)| *other_size < size);
67+
let pos = subpool.partition_point(|(_addr, other_size, other_thread, _)| {
68+
(*other_size, *other_thread) < (size, thread)
69+
});
6170
// Make sure the pool does not grow too big.
6271
if subpool.len() >= MAX_POOL_SIZE {
6372
// Pool full. Replace existing element, or last one if this would be even bigger.
6473
let clamped_pos = pos.min(subpool.len() - 1);
65-
subpool[clamped_pos] = (addr, size, clock());
74+
subpool[clamped_pos] = (addr, size, thread, clock);
6675
return;
6776
}
6877
// Add address to pool, at the right position.
69-
subpool.insert(pos, (addr, size, clock()));
78+
subpool.insert(pos, (addr, size, thread, clock));
7079
}
7180

7281
pub fn take_addr(
@@ -75,21 +84,32 @@ impl ReusePool {
7584
size: Size,
7685
align: Align,
7786
kind: MemoryKind,
87+
thread: ThreadId,
7888
) -> Option<(u64, VClock)> {
7989
// Determine whether we'll even attempt a reuse. As above, we don't do reuse for stack addresses.
8090
if kind == MemoryKind::Stack || !rng.gen_bool(self.address_reuse_rate) {
8191
return None;
8292
}
93+
let cross_thread_reuse = rng.gen_bool(self.address_reuse_cross_thread_rate);
8394
// Determine the pool to take this from.
8495
let subpool = self.subpool(align);
8596
// Let's see if we can find something of the right size. We want to find the full range of
86-
// such items, beginning with the first, so we can't use `binary_search_by_key`.
87-
let begin = subpool.partition_point(|(_addr, other_size, _)| *other_size < size);
97+
// such items, beginning with the first, so we can't use `binary_search_by_key`. If we do
98+
// *not* want to consider other thread's allocations, we effectively use the lexicographic
99+
// order on `(size, thread)`.
100+
let begin = subpool.partition_point(|(_addr, other_size, other_thread, _)| {
101+
*other_size < size
102+
|| (*other_size == size && !cross_thread_reuse && *other_thread < thread)
103+
});
88104
let mut end = begin;
89-
while let Some((_addr, other_size, _)) = subpool.get(end) {
105+
while let Some((_addr, other_size, other_thread, _)) = subpool.get(end) {
90106
if *other_size != size {
91107
break;
92108
}
109+
if !cross_thread_reuse && *other_thread != thread {
110+
// We entered the allocations of another thread.
111+
break;
112+
}
93113
end += 1;
94114
}
95115
if end == begin {
@@ -99,8 +119,9 @@ impl ReusePool {
99119
// Pick a random element with the desired size.
100120
let idx = rng.gen_range(begin..end);
101121
// Remove it from the pool and return.
102-
let (chosen_addr, chosen_size, clock) = subpool.remove(idx);
122+
let (chosen_addr, chosen_size, chosen_thread, clock) = subpool.remove(idx);
103123
debug_assert!(chosen_size >= size && chosen_addr % align.bytes() == 0);
124+
debug_assert!(cross_thread_reuse || chosen_thread == thread);
104125
Some((chosen_addr, clock))
105126
}
106127
}

src/bin/miri.rs

+46-83
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,15 @@ fn parse_comma_list<T: FromStr>(input: &str) -> Result<Vec<T>, T::Err> {
307307
input.split(',').map(str::parse::<T>).collect()
308308
}
309309

310+
/// Parses the input as a float in the range from 0.0 to 1.0 (inclusive).
311+
fn parse_rate(input: &str) -> Result<f64, &'static str> {
312+
match input.parse::<f64>() {
313+
Ok(rate) if rate >= 0.0 && rate <= 1.0 => Ok(rate),
314+
Ok(_) => Err("must be between `0.0` and `1.0`"),
315+
Err(_) => Err("requires a `f64` between `0.0` and `1.0`"),
316+
}
317+
}
318+
310319
#[cfg(any(target_os = "linux", target_os = "macos"))]
311320
fn jemalloc_magic() {
312321
// These magic runes are copied from
@@ -499,14 +508,9 @@ fn main() {
499508
} else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") {
500509
miri_config.forwarded_env_vars.push(param.to_owned());
501510
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-pointer-tag=") {
502-
let ids: Vec<u64> = match parse_comma_list(param) {
503-
Ok(ids) => ids,
504-
Err(err) =>
505-
show_error!(
506-
"-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {}",
507-
err
508-
),
509-
};
511+
let ids: Vec<u64> = parse_comma_list(param).unwrap_or_else(|err| {
512+
show_error!("-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {err}")
513+
});
510514
for id in ids.into_iter().map(miri::BorTag::new) {
511515
if let Some(id) = id {
512516
miri_config.tracked_pointer_tags.insert(id);
@@ -515,14 +519,9 @@ fn main() {
515519
}
516520
}
517521
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-call-id=") {
518-
let ids: Vec<u64> = match parse_comma_list(param) {
519-
Ok(ids) => ids,
520-
Err(err) =>
521-
show_error!(
522-
"-Zmiri-track-call-id requires a comma separated list of valid `u64` arguments: {}",
523-
err
524-
),
525-
};
522+
let ids: Vec<u64> = parse_comma_list(param).unwrap_or_else(|err| {
523+
show_error!("-Zmiri-track-call-id requires a comma separated list of valid `u64` arguments: {err}")
524+
});
526525
for id in ids.into_iter().map(miri::CallId::new) {
527526
if let Some(id) = id {
528527
miri_config.tracked_call_ids.insert(id);
@@ -531,70 +530,37 @@ fn main() {
531530
}
532531
}
533532
} else if let Some(param) = arg.strip_prefix("-Zmiri-track-alloc-id=") {
534-
let ids: Vec<miri::AllocId> = match parse_comma_list::<NonZero<u64>>(param) {
535-
Ok(ids) => ids.into_iter().map(miri::AllocId).collect(),
536-
Err(err) =>
537-
show_error!(
538-
"-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {}",
539-
err
540-
),
541-
};
542-
miri_config.tracked_alloc_ids.extend(ids);
533+
let ids = parse_comma_list::<NonZero<u64>>(param).unwrap_or_else(|err| {
534+
show_error!("-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {err}")
535+
});
536+
miri_config.tracked_alloc_ids.extend(ids.into_iter().map(miri::AllocId));
543537
} else if arg == "-Zmiri-track-alloc-accesses" {
544538
miri_config.track_alloc_accesses = true;
545539
} else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-rate=") {
546-
let rate = match param.parse::<f64>() {
547-
Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
548-
Ok(_) =>
549-
show_error!(
550-
"-Zmiri-compare-exchange-weak-failure-rate must be between `0.0` and `1.0`"
551-
),
552-
Err(err) =>
553-
show_error!(
554-
"-Zmiri-compare-exchange-weak-failure-rate requires a `f64` between `0.0` and `1.0`: {}",
555-
err
556-
),
557-
};
558-
miri_config.address_reuse_rate = rate;
540+
miri_config.address_reuse_rate = parse_rate(param)
541+
.unwrap_or_else(|err| show_error!("-Zmiri-address-reuse-rate {err}"));
542+
} else if let Some(param) = arg.strip_prefix("-Zmiri-address-reuse-cross-thread-rate=") {
543+
miri_config.address_reuse_cross_thread_rate = parse_rate(param)
544+
.unwrap_or_else(|err| show_error!("-Zmiri-address-reuse-cross-thread-rate {err}"));
559545
} else if let Some(param) = arg.strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=") {
560-
let rate = match param.parse::<f64>() {
561-
Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
562-
Ok(_) =>
563-
show_error!(
564-
"-Zmiri-compare-exchange-weak-failure-rate must be between `0.0` and `1.0`"
565-
),
566-
Err(err) =>
567-
show_error!(
568-
"-Zmiri-compare-exchange-weak-failure-rate requires a `f64` between `0.0` and `1.0`: {}",
569-
err
570-
),
571-
};
572-
miri_config.cmpxchg_weak_failure_rate = rate;
546+
miri_config.cmpxchg_weak_failure_rate = parse_rate(param).unwrap_or_else(|err| {
547+
show_error!("-Zmiri-compare-exchange-weak-failure-rate {err}")
548+
});
573549
} else if let Some(param) = arg.strip_prefix("-Zmiri-preemption-rate=") {
574-
let rate = match param.parse::<f64>() {
575-
Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
576-
Ok(_) => show_error!("-Zmiri-preemption-rate must be between `0.0` and `1.0`"),
577-
Err(err) =>
578-
show_error!(
579-
"-Zmiri-preemption-rate requires a `f64` between `0.0` and `1.0`: {}",
580-
err
581-
),
582-
};
583-
miri_config.preemption_rate = rate;
550+
miri_config.preemption_rate =
551+
parse_rate(param).unwrap_or_else(|err| show_error!("-Zmiri-preemption-rate {err}"));
584552
} else if arg == "-Zmiri-report-progress" {
585553
// This makes it take a few seconds between progress reports on my laptop.
586554
miri_config.report_progress = Some(1_000_000);
587555
} else if let Some(param) = arg.strip_prefix("-Zmiri-report-progress=") {
588-
let interval = match param.parse::<u32>() {
589-
Ok(i) => i,
590-
Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err),
591-
};
556+
let interval = param.parse::<u32>().unwrap_or_else(|err| {
557+
show_error!("-Zmiri-report-progress requires a `u32`: {}", err)
558+
});
592559
miri_config.report_progress = Some(interval);
593560
} else if let Some(param) = arg.strip_prefix("-Zmiri-provenance-gc=") {
594-
let interval = match param.parse::<u32>() {
595-
Ok(i) => i,
596-
Err(err) => show_error!("-Zmiri-provenance-gc requires a `u32`: {}", err),
597-
};
561+
let interval = param.parse::<u32>().unwrap_or_else(|err| {
562+
show_error!("-Zmiri-provenance-gc requires a `u32`: {}", err)
563+
});
598564
miri_config.gc_interval = interval;
599565
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
600566
miri_config.measureme_out = Some(param.to_string());
@@ -619,23 +585,20 @@ fn main() {
619585
show_error!("-Zmiri-extern-so-file `{}` does not exist", filename);
620586
}
621587
} else if let Some(param) = arg.strip_prefix("-Zmiri-num-cpus=") {
622-
let num_cpus = match param.parse::<u32>() {
623-
Ok(i) => i,
624-
Err(err) => show_error!("-Zmiri-num-cpus requires a `u32`: {}", err),
625-
};
626-
588+
let num_cpus = param
589+
.parse::<u32>()
590+
.unwrap_or_else(|err| show_error!("-Zmiri-num-cpus requires a `u32`: {}", err));
627591
miri_config.num_cpus = num_cpus;
628592
} else if let Some(param) = arg.strip_prefix("-Zmiri-force-page-size=") {
629-
let page_size = match param.parse::<u64>() {
630-
Ok(i) =>
631-
if i.is_power_of_two() {
632-
i * 1024
633-
} else {
634-
show_error!("-Zmiri-force-page-size requires a power of 2: {}", i)
635-
},
636-
Err(err) => show_error!("-Zmiri-force-page-size requires a `u64`: {}", err),
593+
let page_size = param.parse::<u64>().unwrap_or_else(|err| {
594+
show_error!("-Zmiri-force-page-size requires a `u64`: {}", err)
595+
});
596+
// Convert from kilobytes to bytes.
597+
let page_size = if page_size.is_power_of_two() {
598+
page_size * 1024
599+
} else {
600+
show_error!("-Zmiri-force-page-size requires a power of 2: {page_size}");
637601
};
638-
639602
miri_config.page_size = Some(page_size);
640603
} else {
641604
// Forward to rustc.

src/eval.rs

+3
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ pub struct MiriConfig {
152152
pub collect_leak_backtraces: bool,
153153
/// Probability for address reuse.
154154
pub address_reuse_rate: f64,
155+
/// Probability for address reuse across threads.
156+
pub address_reuse_cross_thread_rate: f64,
155157
}
156158

157159
impl Default for MiriConfig {
@@ -189,6 +191,7 @@ impl Default for MiriConfig {
189191
page_size: None,
190192
collect_leak_backtraces: true,
191193
address_reuse_rate: 0.5,
194+
address_reuse_cross_thread_rate: 0.1,
192195
}
193196
}
194197
}

tests/fail/both_borrows/retag_data_race_write.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Make sure that a retag acts like a write for the data race model.
22
//@revisions: stack tree
33
//@compile-flags: -Zmiri-preemption-rate=0
4+
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
5+
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
46
//@[tree]compile-flags: -Zmiri-tree-borrows
57
#[derive(Copy, Clone)]
68
struct SendPtr(*mut u8);

tests/fail/data_race/alloc_read_race.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows
2+
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
3+
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
24
#![feature(new_uninit)]
35

46
use std::mem::MaybeUninit;

tests/fail/data_race/alloc_write_race.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows
2+
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
3+
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
24
#![feature(new_uninit)]
35

46
use std::ptr::null_mut;

0 commit comments

Comments
 (0)