Skip to content

Add __isPlatformVersionAtLeast and __isOSVersionAtLeast symbols #138944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@
#![feature(hasher_prefixfree_extras)]
#![feature(hashmap_internals)]
#![feature(hint_must_use)]
#![feature(int_from_ascii)]
#![feature(ip)]
#![feature(lazy_get)]
#![feature(maybe_uninit_slice)]
Expand All @@ -363,6 +364,7 @@
#![feature(slice_internals)]
#![feature(slice_ptr_get)]
#![feature(slice_range)]
#![feature(slice_split_once)]
#![feature(std_internals)]
#![feature(str_internals)]
#![feature(strict_provenance_atomic_ptr)]
Expand Down
1 change: 1 addition & 0 deletions library/std/src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod io;
pub mod net;
pub mod os_str;
pub mod path;
pub mod platform_version;
pub mod process;
pub mod random;
pub mod stdio;
Expand Down
149 changes: 149 additions & 0 deletions library/std/src/sys/platform_version/darwin/compiler_builtins.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! Runtime version checking ABI for other compilers.
//!
//! The symbols in this file are useful for us to expose to allow linking code written in the
//! following languages when using their version checking functionality:
//! - Objective-C's `@available`.
//! - Clang's `__builtin_available` macro.
//! - Swift's `#available`,
//!
//! The original discussion of this feature can be found at:
//! - <https://lists.llvm.org/pipermail/cfe-dev/2016-July/049851.html>
//! - <https://reviews.llvm.org/D27827>
//! - <https://reviews.llvm.org/D30136>
//!
//! The implementation of these is a bit weird, since they're actually implemented in `compiler-rt`:
//! <https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/compiler-rt/lib/builtins/os_version_check.c>
//!
//! While they probably should've been part of `libSystem.dylib`, both because they link to symbols
//! from that, and because their implementation is quite complex, using allocation, environment
//! variables, file access and dynamic library loading (and emitting all of this into every binary).
//!
//! The reason why Apple chose to not do that originally is lost to the sands of time, but a good
//! reason would be that implementing it as part of `compiler-rt` allowed them to back-deploy this
//! to older OSes immediately.
//!
//! In Rust's case, while we may provide a feature similar to `@available` in the future, we will
//! probably do so as a macro exposed by `std` (and not as a compiler builtin). So implementing this
//! in `std` makes sense, since then we can implement it using `std` utilities, and we can avoid
//! having `compiler-builtins` depend on `libSystem.dylib`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you mention why we need this as an extern symbol now, before we have the public API?

This would also be a good place to clarify that we aren't making any guarantees about the availability of these symbols and it's possible we remove them in the future for whatever reason.

Copy link
Contributor Author

@madsmtm madsmtm Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you mention why we need this as an extern symbol now, before we have the public API?

I've rewritten the section a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making guarantees about the availability warrants discussion though: Let's say I went and removed this code from curl-sys after this PR lands. If Rust later removed these symbols, users of curl-sys would encounter linker errors again.

That is, without some sort of guarantee of the continued availability of these symbols, they're basically useless.

What are your thoughts?

//!
//! This does mean that users that attempt to link Objective-C code _and_ use `#![no_std]` in all
//! their crates may get a linker error because these symbols are missing. Using `no_std` is quite
//! uncommon on Apple systems though, so it's probably fine to not support this use-case.
//!
//! The workaround would be to link `libclang_rt.osx.a` or otherwise use Clang's `compiler-rt`.
//!
//! See also discussion in <https://github.com/rust-lang/compiler-builtins/pull/794>.
//!
//! ---
//!
//! NOTE: Since macOS 10.15, `libSystem.dylib` _has_ actually provided the undocumented
//! `_availability_version_check` via `libxpc` for doing the version lookup (zippered, which is why
//! it requires a platform parameter to differentiate between macOS and Mac Catalyst), though its
//! usage may be a bit dangerous, see:
//! - https://reviews.llvm.org/D150397
//! - https://github.com/llvm/llvm-project/issues/64227
//!
//! Besides, we'd need to implement the version lookup via. PList to support older versions anyhow,
//! so we might as well use that everywhere (since it can also be optimized more after inlining).

#![allow(non_snake_case)]

use super::{OSVersion, current_version, pack_os_version};

/// Whether the current platform's OS version is higher than or equal to the given version.
///
/// The first argument is the _base_ Mach-O platform (i.e. `PLATFORM_MACOS`, `PLATFORM_IOS`, etc.,
/// but not `PLATFORM_IOSSIMULATOR` or `PLATFORM_MACCATALYST`) of the invoking binary.
///
/// Arguments are specified statically by Clang. Inlining with LTO should allow the versions to be
/// combined into a single `u32`, which should make comparisons faster, and should make the
/// `BASE_TARGET_PLATFORM` check a no-op.
//
// SAFETY: The signature is the same as what Clang expects, and we export weakly to allow linking
// both this and `libclang_rt.*.a`, similar to how `compiler-builtins` does it:
// https://github.com/rust-lang/compiler-builtins/blob/0.1.113/src/macros.rs#L494
#[cfg_attr(not(feature = "compiler-builtins-mangled-names"), unsafe(no_mangle), linkage = "weak")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the compiler-builtins-mangled-names config here? I don't really know why that feature is exposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure actually, I just sorta tried to make it do the same as what happens when using the compiler_builtins::intrinsics! macro.

Perhaps libstd can be compiled with compiler-rt instead, in which case we wouldn't need to export these symbols?

// extern "C" is correct, Clang assumes the function cannot unwind:
// https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/clang/lib/CodeGen/CGObjC.cpp#L3980
//
// If an error happens in this, we instead abort the process.
pub(super) extern "C" fn __isPlatformVersionAtLeast(
platform: i32,
major: u32,
minor: u32,
subminor: u32,
) -> i32 {
let version = pack_u32_os_version(major, minor, subminor);

// Mac Catalyst is a technology that allows macOS to run in a different "mode" that closely
// resembles iOS (and has iOS libraries like UIKit available).
//
// (Apple has added a "Designed for iPad" mode later on that allows running iOS apps
// natively, but we don't need to think too much about those, since they link to
// iOS-specific system binaries as well).
//
// To support Mac Catalyst, Apple added the concept of a "zippered" binary, which is a single
// binary that can be run on both macOS and Mac Catalyst (has two `LC_BUILD_VERSION` Mach-O
// commands, one set to `PLATFORM_MACOS` and one to `PLATFORM_MACCATALYST`).
//
// Most system libraries are zippered, which allows re-use across macOS and Mac Catalyst.
// This includes the `libclang_rt.osx.a` shipped with Xcode! This means that `compiler-rt`
// can't statically know whether it's compiled for macOS or Mac Catalyst, and thus this new
// API (which replaces `__isOSVersionAtLeast`) is needed.
//
// In short:
// normal binary calls normal compiler-rt --> `__isOSVersionAtLeast` was enough
// normal binary calls zippered compiler-rt --> `__isPlatformVersionAtLeast` required
// zippered binary calls zippered compiler-rt --> `__isPlatformOrVariantPlatformVersionAtLeast` called

// FIXME(madsmtm): `rustc` doesn't support zippered binaries yet, see rust-lang/rust#131216.
// But once it does, we need the pre-compiled `std` shipped with rustup to be zippered, and thus
// we also need to handle the `platform` difference here:
//
// if cfg!(target_os = "macos") && platform == 2 /* PLATFORM_IOS */ && cfg!(zippered) {
// return (version.to_u32() <= current_ios_version()) as i32;
// }
//
// `__isPlatformOrVariantPlatformVersionAtLeast` would also need to be implemented.

// The base Mach-O platform for the current target.
const BASE_TARGET_PLATFORM: i32 = if cfg!(target_os = "macos") {
1 // PLATFORM_MACOS
} else if cfg!(target_os = "ios") {
2 // PLATFORM_IOS
} else if cfg!(target_os = "tvos") {
3 // PLATFORM_TVOS
} else if cfg!(target_os = "watchos") {
4 // PLATFORM_WATCHOS
} else if cfg!(target_os = "visionos") {
11 // PLATFORM_VISIONOS
} else {
0 // PLATFORM_UNKNOWN
};
debug_assert_eq!(
platform, BASE_TARGET_PLATFORM,
"invalid platform provided to __isPlatformVersionAtLeast",
);

(version <= current_version()) as i32
}

/// Old entry point for availability. Used when compiling with older Clang versions.
// SAFETY: Same as for `__isPlatformVersionAtLeast`.
#[cfg_attr(not(feature = "compiler-builtins-mangled-names"), unsafe(no_mangle), linkage = "weak")]
pub(super) extern "C" fn __isOSVersionAtLeast(major: u32, minor: u32, subminor: u32) -> i32 {
let version = pack_u32_os_version(major, minor, subminor);
(version <= current_version()) as i32
}

/// [`pack_os_version`], but takes `u32` and saturates.
///
/// Instead of using e.g. `major as u16`, which truncates.
#[inline]
fn pack_u32_os_version(major: u32, minor: u32, patch: u32) -> OSVersion {
let major: u16 = major.try_into().unwrap_or(u16::MAX);
let minor: u8 = minor.try_into().unwrap_or(u8::MAX);
let patch: u8 = patch.try_into().unwrap_or(u8::MAX);
pack_os_version(major, minor, patch)
}
Loading
Loading