Skip to content

Commit fc7eec2

Browse files
committed
store futexes in per-allocation data rather than globally
1 parent 23ff2ea commit fc7eec2

File tree

5 files changed

+59
-37
lines changed

5 files changed

+59
-37
lines changed

src/concurrency/sync.rs

+24-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::cell::RefCell;
12
use std::collections::VecDeque;
23
use std::collections::hash_map::Entry;
34
use std::ops::Not;
5+
use std::rc::Rc;
46
use std::time::Duration;
57

68
use rustc_data_structures::fx::FxHashMap;
@@ -121,6 +123,15 @@ struct Futex {
121123
clock: VClock,
122124
}
123125

126+
#[derive(Default, Clone)]
127+
pub struct FutexRef(Rc<RefCell<Futex>>);
128+
129+
impl VisitProvenance for FutexRef {
130+
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
131+
// No provenance
132+
}
133+
}
134+
124135
/// A thread waiting on a futex.
125136
#[derive(Debug)]
126137
struct FutexWaiter {
@@ -137,9 +148,6 @@ pub struct SynchronizationObjects {
137148
rwlocks: IndexVec<RwLockId, RwLock>,
138149
condvars: IndexVec<CondvarId, Condvar>,
139150
pub(super) init_onces: IndexVec<InitOnceId, InitOnce>,
140-
141-
/// Futex info for the futex at the given address.
142-
futexes: FxHashMap<u64, Futex>,
143151
}
144152

145153
// Private extension trait for local helper methods
@@ -184,7 +192,7 @@ impl SynchronizationObjects {
184192
}
185193

186194
impl<'tcx> AllocExtra<'tcx> {
187-
pub fn get_sync<T: 'static>(&self, offset: Size) -> Option<&T> {
195+
fn get_sync<T: 'static>(&self, offset: Size) -> Option<&T> {
188196
self.sync.get(&offset).and_then(|s| s.downcast_ref::<T>())
189197
}
190198
}
@@ -690,7 +698,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
690698
/// On a timeout, `retval_timeout` is written to `dest` and `errno_timeout` is set as the last error.
691699
fn futex_wait(
692700
&mut self,
693-
addr: u64,
701+
futex_ref: FutexRef,
694702
bitset: u32,
695703
timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
696704
retval_succ: Scalar,
@@ -700,23 +708,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
700708
) {
701709
let this = self.eval_context_mut();
702710
let thread = this.active_thread();
703-
let futex = &mut this.machine.sync.futexes.entry(addr).or_default();
711+
let mut futex = futex_ref.0.borrow_mut();
704712
let waiters = &mut futex.waiters;
705713
assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
706714
waiters.push_back(FutexWaiter { thread, bitset });
715+
drop(futex);
716+
707717
this.block_thread(
708-
BlockReason::Futex { addr },
718+
BlockReason::Futex,
709719
timeout,
710720
callback!(
711721
@capture<'tcx> {
712-
addr: u64,
722+
futex_ref: FutexRef,
713723
retval_succ: Scalar,
714724
retval_timeout: Scalar,
715725
dest: MPlaceTy<'tcx>,
716726
errno_timeout: Scalar,
717727
}
718728
@unblock = |this| {
719-
let futex = this.machine.sync.futexes.get(&addr).unwrap();
729+
let futex = futex_ref.0.borrow();
720730
// Acquire the clock of the futex.
721731
if let Some(data_race) = &this.machine.data_race {
722732
data_race.acquire_clock(&futex.clock, &this.machine.threads);
@@ -728,7 +738,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
728738
@timeout = |this| {
729739
// Remove the waiter from the futex.
730740
let thread = this.active_thread();
731-
let futex = this.machine.sync.futexes.get_mut(&addr).unwrap();
741+
let mut futex = futex_ref.0.borrow_mut();
732742
futex.waiters.retain(|waiter| waiter.thread != thread);
733743
// Set errno and write return value.
734744
this.set_last_error(errno_timeout)?;
@@ -740,11 +750,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
740750
}
741751

742752
/// Returns whether anything was woken.
743-
fn futex_wake(&mut self, addr: u64, bitset: u32) -> InterpResult<'tcx, bool> {
753+
fn futex_wake(&mut self, futex_ref: &FutexRef, bitset: u32) -> InterpResult<'tcx, bool> {
744754
let this = self.eval_context_mut();
745-
let Some(futex) = this.machine.sync.futexes.get_mut(&addr) else {
746-
return interp_ok(false);
747-
};
755+
let mut futex = futex_ref.0.borrow_mut();
748756
let data_race = &this.machine.data_race;
749757

750758
// Each futex-wake happens-before the end of the futex wait
@@ -757,7 +765,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
757765
return interp_ok(false);
758766
};
759767
let waiter = futex.waiters.remove(i).unwrap();
760-
this.unblock_thread(waiter.thread, BlockReason::Futex { addr })?;
768+
drop(futex);
769+
this.unblock_thread(waiter.thread, BlockReason::Futex)?;
761770
interp_ok(true)
762771
}
763772
}

src/concurrency/thread.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ pub enum BlockReason {
168168
/// Blocked on a reader-writer lock.
169169
RwLock(RwLockId),
170170
/// Blocked on a Futex variable.
171-
Futex { addr: u64 },
171+
Futex,
172172
/// Blocked on an InitOnce.
173173
InitOnce(InitOnceId),
174174
/// Blocked on epoll.

src/shims/unix/linux/sync.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
use crate::concurrency::sync::FutexRef;
12
use crate::*;
23

4+
struct LinuxFutex {
5+
futex: FutexRef,
6+
}
7+
38
/// Implementation of the SYS_futex syscall.
49
/// `args` is the arguments *after* the syscall number.
510
pub fn futex<'tcx>(
@@ -29,9 +34,15 @@ pub fn futex<'tcx>(
2934
let op = this.read_scalar(op)?.to_i32()?;
3035
let val = this.read_scalar(val)?.to_i32()?;
3136

37+
// Storing this inside the allocation means that if an address gets reused by a new allocation,
38+
// we'll use an independent futex queue for this... that seems acceptable.
39+
let futex_ref = this
40+
.get_sync_or_init(addr, |_| interp_ok(LinuxFutex { futex: Default::default() }))?
41+
.futex
42+
.clone();
43+
3244
// This is a vararg function so we have to bring our own type for this pointer.
3345
let addr = this.ptr_to_mplace(addr, this.machine.layouts.i32);
34-
let addr_usize = addr.ptr().addr().bytes();
3546

3647
let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG");
3748
let futex_wait = this.eval_libc_i32("FUTEX_WAIT");
@@ -159,7 +170,7 @@ pub fn futex<'tcx>(
159170
if val == futex_val {
160171
// The value still matches, so we block the thread and make it wait for FUTEX_WAKE.
161172
this.futex_wait(
162-
addr_usize,
173+
futex_ref,
163174
bitset,
164175
timeout,
165176
Scalar::from_target_isize(0, this), // retval_succ
@@ -207,7 +218,7 @@ pub fn futex<'tcx>(
207218
let mut n = 0;
208219
#[allow(clippy::arithmetic_side_effects)]
209220
for _ in 0..val {
210-
if this.futex_wake(addr_usize, bitset)? {
221+
if this.futex_wake(&futex_ref, bitset)? {
211222
n += 1;
212223
} else {
213224
break;

src/shims/windows/sync.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ use std::time::Duration;
33
use rustc_target::abi::Size;
44

55
use crate::concurrency::init_once::InitOnceStatus;
6+
use crate::concurrency::sync::FutexRef;
67
use crate::*;
78

89
#[derive(Copy, Clone)]
910
struct WindowsInitOnce {
1011
id: InitOnceId,
1112
}
1213

14+
struct WindowsFutex {
15+
futex: FutexRef,
16+
}
17+
1318
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
1419
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
1520
// Windows sync primitives are pointer sized.
@@ -168,7 +173,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
168173
let size = this.read_target_usize(size_op)?;
169174
let timeout_ms = this.read_scalar(timeout_op)?.to_u32()?;
170175

171-
let addr = ptr.addr().bytes();
176+
let futex_ref = this
177+
.get_sync_or_init(ptr, |_| interp_ok(WindowsFutex { futex: Default::default() }))?
178+
.futex
179+
.clone();
172180

173181
if size > 8 || !size.is_power_of_two() {
174182
let invalid_param = this.eval_windows("c", "ERROR_INVALID_PARAMETER");
@@ -196,7 +204,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
196204
if futex_val == compare_val {
197205
// If the values are the same, we have to block.
198206
this.futex_wait(
199-
addr,
207+
futex_ref,
200208
u32::MAX, // bitset
201209
timeout,
202210
Scalar::from_i32(1), // retval_succ
@@ -219,8 +227,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
219227
// See the Linux futex implementation for why this fence exists.
220228
this.atomic_fence(AtomicFenceOrd::SeqCst)?;
221229

222-
let addr = ptr.addr().bytes();
223-
this.futex_wake(addr, u32::MAX)?;
230+
let futex_ref = this
231+
.get_sync_or_init(ptr, |_| interp_ok(WindowsFutex { futex: Default::default() }))?
232+
.futex
233+
.clone();
234+
this.futex_wake(&futex_ref, u32::MAX)?;
224235

225236
interp_ok(())
226237
}
@@ -232,8 +243,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
232243
// See the Linux futex implementation for why this fence exists.
233244
this.atomic_fence(AtomicFenceOrd::SeqCst)?;
234245

235-
let addr = ptr.addr().bytes();
236-
while this.futex_wake(addr, u32::MAX)? {}
246+
let futex_ref = this
247+
.get_sync_or_init(ptr, |_| interp_ok(WindowsFutex { futex: Default::default() }))?
248+
.futex
249+
.clone();
250+
while this.futex_wake(&futex_ref, u32::MAX)? {}
237251

238252
interp_ok(())
239253
}

tests/pass-dep/concurrency/linux-futex.rs

-12
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,6 @@ fn wake_nobody() {
3535
}
3636
}
3737

38-
fn wake_dangling() {
39-
let futex = Box::new(0);
40-
let ptr: *const i32 = &*futex;
41-
drop(futex);
42-
43-
// Wake 1 waiter. Expect zero waiters woken up, as nobody is waiting.
44-
unsafe {
45-
assert_eq!(libc::syscall(libc::SYS_futex, ptr, libc::FUTEX_WAKE, 1), 0);
46-
}
47-
}
48-
4938
fn wait_wrong_val() {
5039
let futex: i32 = 123;
5140

@@ -286,7 +275,6 @@ fn concurrent_wait_wake() {
286275

287276
fn main() {
288277
wake_nobody();
289-
wake_dangling();
290278
wait_wrong_val();
291279
wait_timeout();
292280
wait_absolute_timeout();

0 commit comments

Comments
 (0)