Skip to content
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

Add __isPlatformVersionAtLeast and __isOSVersionAtLeast symbols #138944

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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`.
//!
//! 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")]
// 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