-
Notifications
You must be signed in to change notification settings - Fork 13.4k
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
19cccaf
ab0c005
71ae9c8
d59cac3
f81d430
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Perhaps |
||
// 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) | ||
} |
There was a problem hiding this comment.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've rewritten the section a bit.
There was a problem hiding this comment.
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 ofcurl-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?