Skip to content

Commit b394f0d

Browse files
committed
store futexes in per-allocation data rather than globally
1 parent 9160aa0 commit b394f0d

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_abi::Size;
@@ -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: IoError,
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
@@ -147,7 +147,7 @@ pub enum BlockReason {
147147
/// Blocked on a reader-writer lock.
148148
RwLock(RwLockId),
149149
/// Blocked on a Futex variable.
150-
Futex { addr: u64 },
150+
Futex,
151151
/// Blocked on an InitOnce.
152152
InitOnce(InitOnceId),
153153
/// Blocked on epoll.

src/shims/unix/linux/sync.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
use crate::concurrency::sync::FutexRef;
12
use crate::helpers::check_min_arg_count;
23
use crate::*;
34

5+
struct LinuxFutex {
6+
futex: FutexRef,
7+
}
8+
49
/// Implementation of the SYS_futex syscall.
510
/// `args` is the arguments *including* the syscall number.
611
pub fn futex<'tcx>(
@@ -25,9 +30,15 @@ pub fn futex<'tcx>(
2530
let op = this.read_scalar(op)?.to_i32()?;
2631
let val = this.read_scalar(val)?.to_i32()?;
2732

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

3243
let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG");
3344
let futex_wait = this.eval_libc_i32("FUTEX_WAIT");
@@ -144,7 +155,7 @@ pub fn futex<'tcx>(
144155
if val == futex_val {
145156
// The value still matches, so we block the thread and make it wait for FUTEX_WAKE.
146157
this.futex_wait(
147-
addr_usize,
158+
futex_ref,
148159
bitset,
149160
timeout,
150161
Scalar::from_target_isize(0, this), // retval_succ
@@ -184,7 +195,7 @@ pub fn futex<'tcx>(
184195
let mut n = 0;
185196
#[expect(clippy::arithmetic_side_effects)]
186197
for _ in 0..val {
187-
if this.futex_wake(addr_usize, bitset)? {
198+
if this.futex_wake(&futex_ref, bitset)? {
188199
n += 1;
189200
} else {
190201
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_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
@@ -36,17 +36,6 @@ fn wake_nobody() {
3636
}
3737
}
3838

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

@@ -287,7 +276,6 @@ fn concurrent_wait_wake() {
287276

288277
fn main() {
289278
wake_nobody();
290-
wake_dangling();
291279
wait_wrong_val();
292280
wait_timeout();
293281
wait_absolute_timeout();

0 commit comments

Comments
 (0)