Skip to content

Commit dc7c2b6

Browse files
committed
std: use futex for everything but Mutex on Apple platforms
1 parent 1c82aa7 commit dc7c2b6

File tree

8 files changed

+210
-131
lines changed

8 files changed

+210
-131
lines changed

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

Lines changed: 201 additions & 0 deletions
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",
@@ -147,6 +148,206 @@ pub fn futex_wake_all(futex: &Atomic<u32>) {
147148
};
148149
}
149150

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

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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ cfg_select! {
22
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ cfg_select! {
1919
target_os = "dragonfly",
2020
target_os = "fuchsia",
2121
target_os = "hermit",
22+
target_os = "macos",
23+
target_os = "ios",
24+
target_os = "tvos",
25+
target_os = "watchos",
2226
) => {
2327
mod futex;
2428
pub use futex::{Once, OnceState};

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ cfg_select! {
22
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

Lines changed: 0 additions & 130 deletions
This file was deleted.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ cfg_select! {
22
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",

triagebot.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@ trigger_files = [
385385
trigger_files = [
386386
"library/std/src/os/darwin",
387387
"library/std/src/sys/platform_version/darwin",
388-
"library/std/src/sys/sync/thread_parking/darwin.rs",
389388
"compiler/rustc_target/src/spec/base/apple",
390389
]
391390

0 commit comments

Comments
 (0)