Skip to content

Commit

Permalink
Optimize OS version lookup
Browse files Browse the repository at this point in the history
Use atomics directly instead of the heavy `OnceLock`.
  • Loading branch information
madsmtm committed Oct 8, 2024
1 parent f2d6711 commit e8eab1e
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 697 deletions.
24 changes: 24 additions & 0 deletions crates/objc2/src/__macro_helpers/os_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ impl OSVersion {
let (major, minor, patch) = (self.major as u32, self.minor as u32, self.patch as u32);
(major << 16) | (minor << 8) | patch
}

/// Construct the version from a `u32`.
#[inline]
pub const fn from_u32(version: u32) -> Self {
// See comments in `OSVersion`, this should compile down to nothing.
let major = (version >> 16) as u16;
let minor = (version >> 8) as u8;
let patch = version as u8;
Self {
major,
minor,
patch,
}
}
}

impl PartialEq for OSVersion {
Expand Down Expand Up @@ -406,4 +420,14 @@ mod tests {
assert!(available!(tvos = 1.2, ..));
}
}

#[test]
fn test_u32_roundtrip() {
let version = OSVersion {
major: 1000,
minor: 100,
patch: 200,
};
assert_eq!(version, OSVersion::from_u32(version.to_u32()));
}
}
29 changes: 22 additions & 7 deletions crates/objc2/src/__macro_helpers/os_version/apple.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use core::ffi::{c_char, c_uint, c_void};
use core::num::NonZeroU32;
use core::ptr;
use core::sync::atomic::{AtomicU32, Ordering};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
use std::sync::OnceLock;

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

*CURRENT_VERSION.get_or_init(lookup_version)
// We use relaxed atomics, it doesn't matter if two threads end up racing
// to read or write the version.
let version = CURRENT_VERSION.load(Ordering::Relaxed);
OSVersion::from_u32(if version == 0 {
// TODO: Consider using `std::panic::abort_unwind` here for code-size?
let version = lookup_version().get();
CURRENT_VERSION.store(version, Ordering::Relaxed);
version
} else {
version
})
}

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

/// Read the version from `kern.osproductversion` or `kern.iossupportversion`.
Expand Down
175 changes: 36 additions & 139 deletions crates/test-assembly/crates/test_available/expected/apple-aarch64.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e8eab1e

Please sign in to comment.