Skip to content

Commit 35653b4

Browse files
committed
Optimize OS version lookup
Use atomics directly instead of the heavy `OnceLock`.
1 parent c3857d5 commit 35653b4

File tree

7 files changed

+223
-697
lines changed

7 files changed

+223
-697
lines changed

crates/objc2/src/__macro_helpers/os_version.rs

+24
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,20 @@ impl OSVersion {
150150
let (major, minor, patch) = (self.major as u32, self.minor as u32, self.patch as u32);
151151
(major << 16) | (minor << 8) | patch
152152
}
153+
154+
/// Construct the version from a `u32`.
155+
#[inline]
156+
pub const fn from_u32(version: u32) -> Self {
157+
// See comments in `OSVersion`, this should compile down to nothing.
158+
let major = (version >> 16) as u16;
159+
let minor = (version >> 8) as u8;
160+
let patch = version as u8;
161+
Self {
162+
major,
163+
minor,
164+
patch,
165+
}
166+
}
153167
}
154168

155169
impl PartialEq for OSVersion {
@@ -402,4 +416,14 @@ mod tests {
402416
assert!(available!(tvos = 1.2, ..));
403417
}
404418
}
419+
420+
#[test]
421+
fn test_u32_roundtrip() {
422+
let version = OSVersion {
423+
major: 1000,
424+
minor: 100,
425+
patch: 200,
426+
};
427+
assert_eq!(version, OSVersion::from_u32(version.to_u32()));
428+
}
405429
}

crates/objc2/src/__macro_helpers/os_version/apple.rs

+22-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use core::ffi::{c_char, c_uint, c_void};
2+
use core::num::NonZeroU32;
23
use core::ptr;
4+
use core::sync::atomic::{AtomicU32, Ordering};
35
use std::os::unix::ffi::OsStrExt;
46
use std::path::PathBuf;
5-
use std::sync::OnceLock;
67

78
use super::OSVersion;
89
use crate::rc::{autoreleasepool, Allocated, Retained};
@@ -97,14 +98,25 @@ pub(crate) const DEPLOYMENT_TARGET: OSVersion = {
9798
pub(crate) fn current_version() -> OSVersion {
9899
// Cache the lookup for performance.
99100
//
100-
// TODO: Maybe just use atomics, a `Once` seems like overkill, it doesn't
101-
// matter if two threads end up racing to read the version?
102-
static CURRENT_VERSION: OnceLock<OSVersion> = OnceLock::new();
101+
// We assume that 0.0.0 is never gonna be a valid version,
102+
// and use that as our sentinel value.
103+
static CURRENT_VERSION: AtomicU32 = AtomicU32::new(0);
103104

104-
*CURRENT_VERSION.get_or_init(lookup_version)
105+
// We use relaxed atomics, it doesn't matter if two threads end up racing
106+
// to read or write the version.
107+
let version = CURRENT_VERSION.load(Ordering::Relaxed);
108+
OSVersion::from_u32(if version == 0 {
109+
// TODO: Consider using `std::panic::abort_unwind` here for code-size?
110+
let version = lookup_version().get();
111+
CURRENT_VERSION.store(version, Ordering::Relaxed);
112+
version
113+
} else {
114+
version
115+
})
105116
}
106117

107-
fn lookup_version() -> OSVersion {
118+
#[cold]
119+
fn lookup_version() -> NonZeroU32 {
108120
// Since macOS 10.15, libSystem has provided the undocumented
109121
// `_availability_version_check` via `libxpc` for doing this version
110122
// lookup, though it's usage may be a bit dangerous, see:
@@ -114,7 +126,10 @@ fn lookup_version() -> OSVersion {
114126
// So instead, we use the safer approach of reading from `sysctl`, and
115127
// if that fails, we fall back to the property list (this is what
116128
// `_availability_version_check` does internally).
117-
version_from_sysctl().unwrap_or_else(version_from_plist)
129+
let version = version_from_sysctl().unwrap_or_else(version_from_plist);
130+
// Use `NonZeroU32` to try to make it clearer to the optimizer that this
131+
// will never return 0.
132+
NonZeroU32::new(version.to_u32()).expect("version cannot be 0.0.0")
118133
}
119134

120135
/// Read the version from `kern.osproductversion` or `kern.iossupportversion`.

crates/test-assembly/crates/test_available/expected/apple-aarch64.s

+36-139
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)