Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7af895d

Browse files
committedFeb 3, 2025
std: use futex for everything but Mutex on Apple platforms
1 parent c9f7324 commit 7af895d

File tree

7 files changed

+193
-130
lines changed

7 files changed

+193
-130
lines changed
 

‎library/std/src/sys/pal/unix/futex.rs

+184
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![cfg(any(
22
target_os = "linux",
3+
target_vendor = "apple",
34
target_os = "android",
45
all(target_os = "emscripten", target_feature = "atomics"),
56
target_os = "freebsd",
@@ -145,6 +146,189 @@ pub fn futex_wake_all(futex: &AtomicU32) {
145146
};
146147
}
147148

149+
/// With macOS version 14.4, Apple introduced a public futex API. Unfortunately,
150+
/// our minimum supported version is 10.12, so we need a fallback API. Luckily
151+
/// for us, the underlying syscalls have been available since exactly that
152+
/// version, so we just use those when needed. This is private API however,
153+
/// which means we need to take care to avoid breakage if the syscall is removed
154+
/// and to avoid apps being rejected from the App Store. To do this, we use weak
155+
/// linkage emulation for both the public and the private API.
156+
///
157+
/// See os/os_sync_wait_on_address.h (Apple has failed to upload the documentation
158+
/// to their website) for documentation of the public API and
159+
/// https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/bsd/sys/ulock.h#L69
160+
/// for the header file of the private API, along with its usage in libpthread
161+
/// https://github.com/apple-oss-distributions/libpthread/blob/d8c4e3c212553d3e0f5d76bb7d45a8acd61302dc/src/pthread_cond.c#L463
162+
#[cfg(target_vendor = "apple")]
163+
mod apple {
164+
use crate::ffi::{c_int, c_void};
165+
use crate::sys::pal::weak::weak;
166+
167+
pub const OS_CLOCK_MACH_ABSOLUTE_TIME: u32 = 32;
168+
pub const OS_SYNC_WAIT_ON_ADDRESS_NONE: u32 = 0;
169+
pub const OS_SYNC_WAKE_BY_ADDRESS_NONE: u32 = 0;
170+
171+
pub const UL_COMPARE_AND_WAIT: u32 = 1;
172+
pub const ULF_WAKE_ALL: u32 = 0x100;
173+
// The syscalls support directly returning errors instead of going through errno.
174+
pub const ULF_NO_ERRNO: u32 = 0x1000000;
175+
176+
// These functions appeared with macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, visionOS 1.1.
177+
weak! {
178+
pub fn os_sync_wait_on_address(*mut c_void, u64, usize, u32) -> c_int
179+
}
180+
181+
weak! {
182+
pub fn os_sync_wait_on_address_with_timeout(*mut c_void, u64, usize, u32, u32, u64) -> c_int
183+
}
184+
185+
weak! {
186+
pub fn os_sync_wake_by_address_any(*mut c_void, usize, u32) -> c_int
187+
}
188+
189+
weak! {
190+
pub fn os_sync_wake_by_address_all(*mut c_void, usize, u32) -> c_int
191+
}
192+
193+
// This syscall appeared with macOS 11.0.
194+
// It is used to support nanosecond precision for timeouts, among other features.
195+
weak! {
196+
pub fn __ulock_wait2(u32, *mut c_void, u64, u64, u64) -> c_int
197+
}
198+
199+
// These syscalls appeared with macOS 10.12.
200+
weak! {
201+
pub fn __ulock_wait(u32, *mut c_void, u64, u32) -> c_int
202+
}
203+
204+
weak! {
205+
pub fn __ulock_wake(u32, *mut c_void, u64) -> c_int
206+
}
207+
}
208+
209+
#[cfg(target_vendor = "apple")]
210+
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
211+
use apple::*;
212+
213+
use crate::mem::size_of;
214+
215+
let addr = futex.as_ptr().cast();
216+
let value = expected as u64;
217+
let size = size_of::<u32>();
218+
if let Some(timeout) = timeout {
219+
let timeout_ns = timeout.as_nanos().clamp(1, u64::MAX as u128) as u64;
220+
let timeout_ms = timeout.as_micros().clamp(1, u32::MAX as u128) as u32;
221+
222+
if let Some(wait) = os_sync_wait_on_address_with_timeout.get() {
223+
let r = unsafe {
224+
wait(
225+
addr,
226+
value,
227+
size,
228+
OS_SYNC_WAIT_ON_ADDRESS_NONE,
229+
OS_CLOCK_MACH_ABSOLUTE_TIME,
230+
timeout_ns,
231+
)
232+
};
233+
234+
// We promote spurious wakeups (reported as EINTR) to normal ones for
235+
// simplicity and because Apple's documentation is unclear as to what the
236+
// unit for the deadline of `os_sync_wait_on_address_with_deadline` is,
237+
// making it hard to support timeouts in a fashion similar to the Linux
238+
// futex implementation.
239+
r != -1 || super::os::errno() != libc::ETIMEDOUT
240+
} else if let Some(wait) = __ulock_wait2.get() {
241+
unsafe {
242+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, timeout_ns, 0)
243+
!= -libc::ETIMEDOUT
244+
}
245+
} else if let Some(wait) = __ulock_wait.get() {
246+
unsafe {
247+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, timeout_ms)
248+
!= -libc::ETIMEDOUT
249+
}
250+
} else {
251+
panic!("your system is below the minimum supported version of Rust");
252+
}
253+
} else {
254+
if let Some(wait) = os_sync_wait_on_address.get() {
255+
unsafe {
256+
wait(addr, value, size, OS_SYNC_WAIT_ON_ADDRESS_NONE);
257+
}
258+
} else if let Some(wait) = __ulock_wait.get() {
259+
unsafe {
260+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, 0);
261+
}
262+
} else {
263+
panic!("your system is below the minimum supported version of Rust");
264+
}
265+
true
266+
}
267+
}
268+
269+
#[cfg(target_vendor = "apple")]
270+
pub fn futex_wake(futex: &AtomicU32) -> bool {
271+
use apple::*;
272+
273+
use crate::io::Error;
274+
use crate::mem::size_of;
275+
276+
let addr = futex.as_ptr().cast();
277+
if let Some(wake) = os_sync_wake_by_address_any.get() {
278+
unsafe { wake(addr, size_of::<u32>(), OS_SYNC_WAKE_BY_ADDRESS_NONE) == 0 }
279+
} else if let Some(wake) = __ulock_wake.get() {
280+
// __ulock_wake can get interrupted, so retry until either waking up a
281+
// waiter or failing because there are no waiters (ENOENT).
282+
loop {
283+
let r = unsafe { wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, 0) };
284+
285+
if r >= 0 {
286+
return true;
287+
} else {
288+
match -r {
289+
libc::ENOENT => return false,
290+
libc::EINTR => continue,
291+
err => panic!("__ulock_wake failed: {}", Error::from_raw_os_error(err)),
292+
}
293+
}
294+
}
295+
} else {
296+
panic!("your system is below the minimum supported version of Rust");
297+
}
298+
}
299+
300+
#[cfg(target_vendor = "apple")]
301+
pub fn futex_wake_all(futex: &AtomicU32) {
302+
use apple::*;
303+
304+
use crate::io::Error;
305+
use crate::mem::size_of;
306+
307+
let addr = futex.as_ptr().cast();
308+
309+
if let Some(wake) = os_sync_wake_by_address_all.get() {
310+
unsafe {
311+
wake(addr, size_of::<u32>(), OS_SYNC_WAKE_BY_ADDRESS_NONE);
312+
}
313+
} else if let Some(wake) = __ulock_wake.get() {
314+
loop {
315+
let r = unsafe { wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL | ULF_NO_ERRNO, addr, 0) };
316+
317+
if r >= 0 {
318+
return;
319+
} else {
320+
match -r {
321+
libc::ENOENT => return,
322+
libc::EINTR => continue,
323+
err => panic!("__ulock_wake failed: {}", Error::from_raw_os_error(err)),
324+
}
325+
}
326+
}
327+
} else {
328+
panic!("your system is below the minimum supported version of Rust");
329+
}
330+
}
331+
148332
#[cfg(target_os = "openbsd")]
149333
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
150334
use super::time::Timespec;

‎library/std/src/sys/pal/unix/sync/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
)))]
1010
#![forbid(unsafe_op_in_unsafe_fn)]
1111

12+
#[cfg(not(target_vendor = "apple"))]
1213
mod condvar;
1314
mod mutex;
1415

16+
#[cfg(not(target_vendor = "apple"))]
1517
pub use condvar::Condvar;
1618
pub use mutex::Mutex;

‎library/std/src/sys/sync/condvar/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ cfg_if::cfg_if! {
22
if #[cfg(any(
33
all(target_os = "windows", not(target_vendor="win7")),
44
target_os = "linux",
5+
target_vendor = "apple",
56
target_os = "android",
67
target_os = "freebsd",
78
target_os = "openbsd",

‎library/std/src/sys/sync/once/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ cfg_if::cfg_if! {
1818
target_os = "dragonfly",
1919
target_os = "fuchsia",
2020
target_os = "hermit",
21+
target_os = "macos",
22+
target_os = "ios",
23+
target_os = "tvos",
24+
target_os = "watchos",
2125
))] {
2226
mod futex;
2327
pub use futex::{Once, OnceState};

‎library/std/src/sys/sync/rwlock/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ cfg_if::cfg_if! {
22
if #[cfg(any(
33
all(target_os = "windows", not(target_vendor = "win7")),
44
target_os = "linux",
5+
target_vendor = "apple",
56
target_os = "android",
67
target_os = "freebsd",
78
target_os = "openbsd",

‎library/std/src/sys/sync/thread_parking/darwin.rs

-130
This file was deleted.

‎library/std/src/sys/sync/thread_parking/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ cfg_if::cfg_if! {
22
if #[cfg(any(
33
all(target_os = "windows", not(target_vendor = "win7")),
44
target_os = "linux",
5+
target_vendor = "apple",
56
target_os = "android",
67
all(target_arch = "wasm32", target_feature = "atomics"),
78
target_os = "freebsd",

0 commit comments

Comments
 (0)
Failed to load comments.