Skip to content

Commit 53ed500

Browse files
committed
Fully support FUTEX_*_BITSET.
1 parent 12c8888 commit 53ed500

File tree

2 files changed

+48
-18
lines changed

2 files changed

+48
-18
lines changed

src/shims/posix/linux/sync.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub fn futex<'tcx>(
3838
let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
3939
let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET")?;
4040
let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
41+
let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET")?;
4142
let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?;
4243

4344
// FUTEX_PRIVATE enables an optimization that stops it from working across processes.
@@ -48,32 +49,37 @@ pub fn futex<'tcx>(
4849
// or *timeout expires. `timeout == null` for an infinite timeout.
4950
//
5051
// FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
51-
// When bitset is u32::MAX, this is identical to FUTEX_WAIT, except the timeout is absolute rather than relative.
52+
// This is identical to FUTEX_WAIT, except:
53+
// - The timeout is absolute rather than relative.
54+
// - You can specify the bitset to selecting what WAKE operations to respond to.
5255
op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
5356
let wait_bitset = op & !futex_realtime == futex_wait_bitset;
5457

58+
let bitset;
59+
5560
if wait_bitset {
5661
if args.len() != 7 {
5762
throw_ub_format!(
5863
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected 7",
5964
args.len()
6065
);
6166
}
62-
63-
let bitset = this.read_scalar(&args[6])?.to_u32()?;
64-
65-
if bitset != u32::MAX {
66-
throw_unsup_format!(
67-
"Miri does not support `futex` syscall with `op=FUTEX_WAIT_BITSET` with a bitset other than UINT_MAX"
68-
);
69-
}
67+
bitset = this.read_scalar(&args[6])?.to_u32()?;
7068
} else {
7169
if args.len() < 5 {
7270
throw_ub_format!(
7371
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 5",
7472
args.len()
7573
);
7674
}
75+
bitset = u32::MAX;
76+
}
77+
78+
if bitset == 0 {
79+
let einval = this.eval_libc("EINVAL")?;
80+
this.set_last_error(einval)?;
81+
this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
82+
return Ok(());
7783
}
7884

7985
// `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
@@ -141,7 +147,7 @@ pub fn futex<'tcx>(
141147
if val == futex_val {
142148
// The value still matches, so we block the trait make it wait for FUTEX_WAKE.
143149
this.block_thread(thread);
144-
this.futex_wait(addr_scalar.to_machine_usize(this)?, thread);
150+
this.futex_wait(addr_scalar.to_machine_usize(this)?, thread, bitset);
145151
// Succesfully waking up from FUTEX_WAIT always returns zero.
146152
this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
147153
// Register a timeout callback if a timeout was specified.
@@ -173,10 +179,30 @@ pub fn futex<'tcx>(
173179
// Wakes at most `val` threads waiting on the futex at `addr`.
174180
// Returns the amount of threads woken up.
175181
// Does not access the futex value at *addr.
176-
op if op == futex_wake => {
182+
// FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
183+
// Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
184+
op if op == futex_wake || op == futex_wake_bitset => {
185+
let bitset;
186+
if op == futex_wake_bitset {
187+
if args.len() != 7 {
188+
throw_ub_format!(
189+
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected 7",
190+
args.len()
191+
);
192+
}
193+
bitset = this.read_scalar(&args[6])?.to_u32()?;
194+
} else {
195+
bitset = u32::MAX;
196+
}
197+
if bitset == 0 {
198+
let einval = this.eval_libc("EINVAL")?;
199+
this.set_last_error(einval)?;
200+
this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
201+
return Ok(());
202+
}
177203
let mut n = 0;
178204
for _ in 0..val {
179-
if let Some(thread) = this.futex_wake(addr_scalar.to_machine_usize(this)?) {
205+
if let Some(thread) = this.futex_wake(addr_scalar.to_machine_usize(this)?, bitset) {
180206
this.unblock_thread(thread);
181207
this.unregister_timeout_callback_if_exists(thread);
182208
n += 1;

src/sync.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ struct Futex {
144144
struct FutexWaiter {
145145
/// The thread that is waiting on this futex.
146146
thread: ThreadId,
147+
/// The bitset used by FUTEX_*_BITSET, or u32::MAX for other operations.
148+
bitset: u32,
147149
}
148150

149151
/// The state of all synchronization variables.
@@ -486,15 +488,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
486488
this.machine.threads.sync.condvars[id].waiters.retain(|waiter| waiter.thread != thread);
487489
}
488490

489-
fn futex_wait(&mut self, addr: u64, thread: ThreadId) {
491+
fn futex_wait(&mut self, addr: u64, thread: ThreadId, bitset: u32) {
490492
let this = self.eval_context_mut();
491493
let futex = &mut this.machine.threads.sync.futexes.entry(addr).or_default();
492494
let waiters = &mut futex.waiters;
493495
assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
494-
waiters.push_back(FutexWaiter { thread });
496+
waiters.push_back(FutexWaiter { thread, bitset });
495497
}
496498

497-
fn futex_wake(&mut self, addr: u64) -> Option<ThreadId> {
499+
fn futex_wake(&mut self, addr: u64, bitset: u32) -> Option<ThreadId> {
498500
let this = self.eval_context_mut();
499501
let current_thread = this.get_active_thread();
500502
let futex = &mut this.machine.threads.sync.futexes.get_mut(&addr)?;
@@ -504,13 +506,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
504506
if let Some(data_race) = data_race {
505507
data_race.validate_lock_release(&mut futex.data_race, current_thread);
506508
}
507-
let res = futex.waiters.pop_front().map(|waiter| {
509+
510+
// Wake up the first thread in the queue that matches any of the bits in the bitset.
511+
futex.waiters.iter().position(|w| w.bitset & bitset != 0).map(|i| {
512+
let waiter = futex.waiters.remove(i).unwrap();
508513
if let Some(data_race) = data_race {
509514
data_race.validate_lock_acquire(&futex.data_race, waiter.thread);
510515
}
511516
waiter.thread
512-
});
513-
res
517+
})
514518
}
515519

516520
fn futex_remove_waiter(&mut self, addr: u64, thread: ThreadId) {

0 commit comments

Comments
 (0)