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 dc49e21

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

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 https://developer.apple.com/documentation/os/os_sync_wait_on_address?language=objc
158+
/// 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+
// Explicitly use `dlsym` for the private fallback API to ensure that the
166+
// App Store checks do not flag Apps using Rust as relying on private API.
167+
use crate::sys::pal::weak::{dlsym, weak};
168+
169+
pub const OS_CLOCK_MACH_ABSOLUTE_TIME: u32 = 32;
170+
pub const OS_SYNC_WAIT_ON_ADDRESS_NONE: u32 = 0;
171+
pub const OS_SYNC_WAKE_BY_ADDRESS_NONE: u32 = 0;
172+
173+
pub const UL_COMPARE_AND_WAIT: u32 = 1;
174+
pub const ULF_WAKE_ALL: u32 = 0x100;
175+
// The syscalls support directly returning errors instead of going through errno.
176+
pub const ULF_NO_ERRNO: u32 = 0x1000000;
177+
178+
// These functions appeared with macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, visionOS 1.1.
179+
weak! {
180+
pub fn os_sync_wait_on_address(*mut c_void, u64, usize, u32) -> c_int
181+
}
182+
183+
weak! {
184+
pub fn os_sync_wait_on_address_with_timeout(*mut c_void, u64, usize, u32, u32, u64) -> c_int
185+
}
186+
187+
weak! {
188+
pub fn os_sync_wake_by_address_any(*mut c_void, usize, u32) -> c_int
189+
}
190+
191+
weak! {
192+
pub fn os_sync_wake_by_address_all(*mut c_void, usize, u32) -> c_int
193+
}
194+
195+
// This syscall appeared with macOS 11.0.
196+
// It is used to support nanosecond precision for timeouts, among other features.
197+
dlsym! {
198+
pub fn __ulock_wait2(u32, *mut c_void, u64, u64, u64) -> c_int
199+
}
200+
201+
// These syscalls appeared with macOS 10.12.
202+
dlsym! {
203+
pub fn __ulock_wait(u32, *mut c_void, u64, u32) -> c_int
204+
}
205+
206+
dlsym! {
207+
pub fn __ulock_wake(u32, *mut c_void, u64) -> c_int
208+
}
209+
}
210+
211+
#[cfg(target_vendor = "apple")]
212+
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
213+
use apple::*;
214+
215+
use crate::mem::size_of;
216+
217+
let addr = futex.as_ptr().cast();
218+
let value = expected as u64;
219+
let size = size_of::<u32>();
220+
if let Some(timeout) = timeout {
221+
let timeout_ns = timeout.as_nanos().clamp(1, u64::MAX as u128) as u64;
222+
let timeout_ms = timeout.as_micros().clamp(1, u32::MAX as u128) as u32;
223+
224+
if let Some(wait) = os_sync_wait_on_address_with_timeout.get() {
225+
let r = unsafe {
226+
wait(
227+
addr,
228+
value,
229+
size,
230+
OS_SYNC_WAIT_ON_ADDRESS_NONE,
231+
OS_CLOCK_MACH_ABSOLUTE_TIME,
232+
timeout_ns,
233+
)
234+
};
235+
236+
// We promote spurious wakeups (reported as EINTR) to normal ones for
237+
// simplicity.
238+
r != -1 || super::os::errno() != libc::ETIMEDOUT
239+
} else if let Some(wait) = __ulock_wait2.get() {
240+
unsafe {
241+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, timeout_ns, 0)
242+
!= -libc::ETIMEDOUT
243+
}
244+
} else if let Some(wait) = __ulock_wait.get() {
245+
unsafe {
246+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, timeout_ms)
247+
!= -libc::ETIMEDOUT
248+
}
249+
} else {
250+
panic!("your system is below the minimum supported version of Rust");
251+
}
252+
} else {
253+
if let Some(wait) = os_sync_wait_on_address.get() {
254+
unsafe {
255+
wait(addr, value, size, OS_SYNC_WAIT_ON_ADDRESS_NONE);
256+
}
257+
} else if let Some(wait) = __ulock_wait.get() {
258+
unsafe {
259+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, 0);
260+
}
261+
} else {
262+
panic!("your system is below the minimum supported version of Rust");
263+
}
264+
true
265+
}
266+
}
267+
268+
#[cfg(target_vendor = "apple")]
269+
pub fn futex_wake(futex: &AtomicU32) -> bool {
270+
use apple::*;
271+
272+
use crate::io::Error;
273+
use crate::mem::size_of;
274+
275+
let addr = futex.as_ptr().cast();
276+
if let Some(wake) = os_sync_wake_by_address_any.get() {
277+
unsafe { wake(addr, size_of::<u32>(), OS_SYNC_WAKE_BY_ADDRESS_NONE) == 0 }
278+
} else if let Some(wake) = __ulock_wake.get() {
279+
// Judging by its use in pthreads, __ulock_wake can get interrupted, so
280+
// retry until either waking up a waiter or failing because there are no
281+
// 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.