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