Skip to content

Commit 32bdd31

Browse files
committed
shim Apple's futex primitives
This is necessary to unblock rust-lang/rust#122408. The documentation for these is available [here](https://developer.apple.com/documentation/os/os_sync_wait_on_address?language=objc). Because the futex wait operations (`os_sync_wait_on_address` et al.) return the number of remaining waiters after returning, this required some changes to the common futex infrastructure, which I've changed to take a callback instead of precalculating the return values.
1 parent c2a3761 commit 32bdd31

File tree

6 files changed

+521
-28
lines changed

6 files changed

+521
-28
lines changed

src/concurrency/sync.rs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ struct Futex {
140140
#[derive(Default, Clone)]
141141
pub struct FutexRef(Rc<RefCell<Futex>>);
142142

143+
impl FutexRef {
144+
pub fn waiters(&self) -> usize {
145+
self.0.borrow().waiters.len()
146+
}
147+
}
148+
143149
impl VisitProvenance for FutexRef {
144150
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
145151
// No provenance in `Futex`.
@@ -729,17 +735,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
729735
}
730736

731737
/// Wait for the futex to be signaled, or a timeout.
732-
/// On a signal, `retval_succ` is written to `dest`.
733-
/// On a timeout, `retval_timeout` is written to `dest` and `errno_timeout` is set as the last error.
738+
/// * On a signal, `retval_succ` is called with the number of waiters on the
739+
/// futex and its result is written to `dest`.
740+
/// * On a timeout, `retval_timeout` is written to `dest` and `errno_timeout`
741+
/// is set as the last error.
734742
fn futex_wait(
735743
&mut self,
736744
futex_ref: FutexRef,
737745
bitset: u32,
738746
timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
739-
retval_succ: Scalar,
740-
retval_timeout: Scalar,
741-
dest: MPlaceTy<'tcx>,
742-
errno_timeout: IoError,
747+
callback: DynUnblockCallback<'tcx>,
743748
) {
744749
let this = self.eval_context_mut();
745750
let thread = this.active_thread();
@@ -755,10 +760,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
755760
callback!(
756761
@capture<'tcx> {
757762
futex_ref: FutexRef,
758-
retval_succ: Scalar,
759-
retval_timeout: Scalar,
760-
dest: MPlaceTy<'tcx>,
761-
errno_timeout: IoError,
763+
callback: DynUnblockCallback<'tcx>,
762764
}
763765
|this, unblock: UnblockKind| {
764766
match unblock {
@@ -768,21 +770,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
768770
if let Some(data_race) = &this.machine.data_race {
769771
data_race.acquire_clock(&futex.clock, &this.machine.threads);
770772
}
771-
// Write the return value.
772-
this.write_scalar(retval_succ, &dest)?;
773-
interp_ok(())
774773
},
775774
UnblockKind::TimedOut => {
776775
// Remove the waiter from the futex.
777776
let thread = this.active_thread();
778777
let mut futex = futex_ref.0.borrow_mut();
779778
futex.waiters.retain(|waiter| waiter.thread != thread);
780-
// Set errno and write return value.
781-
this.set_last_error(errno_timeout)?;
782-
this.write_scalar(retval_timeout, &dest)?;
783-
interp_ok(())
784779
},
785780
}
781+
782+
callback.call(this, unblock)
786783
}
787784
),
788785
);

src/shims/unix/linux_like/sync.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,24 @@ pub fn futex<'tcx>(
156156
.futex
157157
.clone();
158158

159+
let dest = dest.clone();
159160
ecx.futex_wait(
160161
futex_ref,
161162
bitset,
162163
timeout,
163-
Scalar::from_target_isize(0, ecx), // retval_succ
164-
Scalar::from_target_isize(-1, ecx), // retval_timeout
165-
dest.clone(),
166-
LibcError("ETIMEDOUT"), // errno_timeout
164+
callback!(
165+
@capture<'tcx> {
166+
dest: MPlaceTy<'tcx>,
167+
}
168+
|ecx, unblock: UnblockKind| match unblock {
169+
UnblockKind::Ready => {
170+
ecx.write_scalar(Scalar::from_target_isize(0, ecx), &dest)
171+
}
172+
UnblockKind::TimedOut => {
173+
ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
174+
}
175+
}
176+
),
167177
);
168178
} else {
169179
// The futex value doesn't match the expected value, so we return failure

src/shims/unix/macos/foreign_items.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@ use rustc_middle::ty::Ty;
22
use rustc_span::Symbol;
33
use rustc_target::callconv::{Conv, FnAbi};
44

5-
use super::sync::EvalContextExt as _;
5+
use super::sync::{EvalContextExt as _, MacOsFutexTimeout};
66
use crate::helpers::check_min_arg_count;
77
use crate::shims::unix::*;
88
use crate::*;
99

10-
pub fn is_dyn_sym(_name: &str) -> bool {
11-
false
10+
pub fn is_dyn_sym(name: &str) -> bool {
11+
match name {
12+
// These only became available with macOS 11.0, so std looks them up dynamically.
13+
"os_sync_wait_on_address"
14+
| "os_sync_wait_on_address_with_deadline"
15+
| "os_sync_wait_on_address_with_timeout"
16+
| "os_sync_wake_by_address_any"
17+
| "os_sync_wake_by_address_all" => true,
18+
_ => false,
19+
}
1220
}
1321

1422
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -216,6 +224,53 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
216224
this.write_scalar(res, dest)?;
217225
}
218226

227+
"os_sync_wait_on_address" => {
228+
let [addr_op, value_op, size_op, flags_op] =
229+
this.check_shim(abi, Conv::C, link_name, args)?;
230+
this.os_sync_wait_on_address(
231+
addr_op,
232+
value_op,
233+
size_op,
234+
flags_op,
235+
MacOsFutexTimeout::None,
236+
dest,
237+
)?;
238+
}
239+
"os_sync_wait_on_address_with_deadline" => {
240+
let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
241+
this.check_shim(abi, Conv::C, link_name, args)?;
242+
this.os_sync_wait_on_address(
243+
addr_op,
244+
value_op,
245+
size_op,
246+
flags_op,
247+
MacOsFutexTimeout::Absolute { clock_op, timeout_op },
248+
dest,
249+
)?;
250+
}
251+
"os_sync_wait_on_address_with_timeout" => {
252+
let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
253+
this.check_shim(abi, Conv::C, link_name, args)?;
254+
this.os_sync_wait_on_address(
255+
addr_op,
256+
value_op,
257+
size_op,
258+
flags_op,
259+
MacOsFutexTimeout::Relative { clock_op, timeout_op },
260+
dest,
261+
)?;
262+
}
263+
"os_sync_wake_by_address_any" => {
264+
let [addr_op, size_op, flags_op] =
265+
this.check_shim(abi, Conv::C, link_name, args)?;
266+
this.os_sync_wake_by_address(addr_op, size_op, flags_op, false, dest)?;
267+
}
268+
"os_sync_wake_by_address_all" => {
269+
let [addr_op, size_op, flags_op] =
270+
this.check_shim(abi, Conv::C, link_name, args)?;
271+
this.os_sync_wake_by_address(addr_op, size_op, flags_op, true, dest)?;
272+
}
273+
219274
"os_unfair_lock_lock" => {
220275
let [lock_op] = this.check_shim(abi, Conv::C, link_name, args)?;
221276
this.os_unfair_lock_lock(lock_op)?;

0 commit comments

Comments
 (0)