|
1 | 1 | #![cfg(any(
|
2 | 2 | target_os = "linux",
|
| 3 | + target_vendor = "apple", |
3 | 4 | target_os = "android",
|
4 | 5 | all(target_os = "emscripten", target_feature = "atomics"),
|
5 | 6 | target_os = "freebsd",
|
@@ -145,6 +146,189 @@ pub fn futex_wake_all(futex: &AtomicU32) {
|
145 | 146 | };
|
146 | 147 | }
|
147 | 148 |
|
| 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 | + |
148 | 332 | #[cfg(target_os = "openbsd")]
|
149 | 333 | pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
|
150 | 334 | use super::time::Timespec;
|
|
0 commit comments