Skip to main content

std/sys/platform_version/darwin/
public_extern.rs

1//! # Runtime version checking ABI for other compilers.
2//!
3//! The symbols in this file are useful for us to expose to allow linking code written in the
4//! following languages when using their version checking functionality:
5//! - Clang's `__builtin_available` macro.
6//! - Objective-C's `@available`.
7//! - Swift's `#available`,
8//!
9//! Without Rust exposing these symbols, the user would encounter a linker error when linking to
10//! C/Objective-C/Swift libraries using these features.
11//!
12//! The presence of these symbols is mostly considered a quality-of-implementation detail, and
13//! should not be relied upon to be available. The intended effect is that linking with code built
14//! with Clang's `__builtin_available` (or similar) will continue to work. For example, we may
15//! decide to remove `__isOSVersionAtLeast` if support for Clang 11 (Xcode 11) is dropped.
16//!
17//! ## Background
18//!
19//! The original discussion of this feature can be found at:
20//! - <https://lists.llvm.org/pipermail/cfe-dev/2016-July/049851.html>
21//! - <https://reviews.llvm.org/D27827>
22//! - <https://reviews.llvm.org/D30136>
23//!
24//! And the upstream implementation of these can be found in `compiler-rt`:
25//! <https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/compiler-rt/lib/builtins/os_version_check.c>
26//!
27//! Ideally, these symbols should probably have been a part of Apple's `libSystem.dylib`, both
28//! because their implementation is quite complex, using allocation, environment variables, file
29//! access and dynamic library loading (and emitting all of this into every binary).
30//!
31//! The reason why Apple chose to not do that originally is lost to the sands of time, but a good
32//! reason would be that implementing it as part of `compiler-rt` allowed them to back-deploy this
33//! to older OSes immediately.
34//!
35//! In Rust's case, while we may provide a feature similar to `@available` in the future, we will
36//! probably do so as a macro exposed by `std` (and not as a compiler builtin). So implementing this
37//! in `std` makes sense, since then we can implement it using `std` utilities, and we can avoid
38//! having `compiler-builtins` depend on `libSystem.dylib`.
39//!
40//! This does mean that users that attempt to link C/Objective-C/Swift code _and_ use `#![no_std]`
41//! in all their crates may get a linker error because these symbols are missing. Using `no_std` is
42//! quite uncommon on Apple systems though, so it's probably fine to not support this use-case.
43//!
44//! The workaround would be to link `libclang_rt.osx.a` or otherwise use Clang's `compiler-rt`.
45//!
46//! See also discussion in <https://github.com/rust-lang/compiler-builtins/pull/794>.
47//!
48//! ## Implementation details
49//!
50//! NOTE: Since macOS 10.15, `libSystem.dylib` _has_ actually provided the undocumented
51//! `_availability_version_check` via `libxpc` for doing the version lookup (zippered, which is why
52//! it requires a platform parameter to differentiate between macOS and Mac Catalyst), though its
53//! usage may be a bit dangerous, see:
54//! - <https://reviews.llvm.org/D150397>
55//! - <https://github.com/llvm/llvm-project/issues/64227>
56//!
57//! Besides, we'd need to implement the version lookup via PList to support older versions anyhow,
58//! so we might as well use that everywhere (since it can also be optimized more after inlining).
59
60#![allow(non_snake_case)]
61
62use super::{current_version, pack_i32_os_version};
63
64/// Whether the current platform's OS version is higher than or equal to the given version.
65///
66/// The first argument is the _base_ Mach-O platform (i.e. `PLATFORM_MACOS`, `PLATFORM_IOS`, etc.,
67/// but not `PLATFORM_IOSSIMULATOR` or `PLATFORM_MACCATALYST`) of the invoking binary.
68///
69/// Arguments are specified statically by Clang. Inlining with LTO should allow the versions to be
70/// combined into a single `u32`, which should make comparisons faster, and should make the
71/// `BASE_TARGET_PLATFORM` check a no-op.
72//
73// SAFETY: The signature is the same as what Clang expects, and we export weakly to allow linking
74// both this and `libclang_rt.*.a`, similar to how `compiler-builtins` does it:
75// https://github.com/rust-lang/compiler-builtins/blob/0.1.113/src/macros.rs#L494
76//
77// NOTE: This symbol has a workaround in the compiler's symbol mangling to avoid mangling it, while
78// still not exposing it from non-cdylib (like `#[no_mangle]` would).
79#[rustc_std_internal_symbol]
80// NOTE: Making this a weak symbol might not be entirely the right solution for this, `compiler_rt`
81// doesn't do that, it instead makes the symbol have "hidden" visibility. But since this is placed
82// in `libstd`, which might be used as a dylib, we cannot do the same here.
83#[linkage = "weak"]
84// extern "C" is correct, Clang assumes the function cannot unwind:
85// https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/clang/lib/CodeGen/CGObjC.cpp#L3980
86//
87// If an error happens in this, we instead abort the process.
88pub(super) extern "C" fn __isPlatformVersionAtLeast(
89    platform: i32,
90    major: i32,
91    minor: i32,
92    subminor: i32,
93) -> i32 {
94    let version = pack_i32_os_version(major, minor, subminor);
95
96    // Mac Catalyst is a technology that allows macOS to run in a different "mode" that closely
97    // resembles iOS (and has iOS libraries like UIKit available).
98    //
99    // (Apple has added a "Designed for iPad" mode later on that allows running iOS apps
100    // natively, but we don't need to think too much about those, since they link to
101    // iOS-specific system binaries as well).
102    //
103    // To support Mac Catalyst, Apple added the concept of a "zippered" binary, which is a single
104    // binary that can be run on both macOS and Mac Catalyst (has two `LC_BUILD_VERSION` Mach-O
105    // commands, one set to `PLATFORM_MACOS` and one to `PLATFORM_MACCATALYST`).
106    //
107    // Most system libraries are zippered, which allows re-use across macOS and Mac Catalyst.
108    // This includes the `libclang_rt.osx.a` shipped with Xcode! This means that `compiler-rt`
109    // can't statically know whether it's compiled for macOS or Mac Catalyst, and thus this new
110    // API (which replaces `__isOSVersionAtLeast`) is needed.
111    //
112    // In short:
113    //      normal  binary calls  normal  compiler-rt --> `__isOSVersionAtLeast` was enough
114    //      normal  binary calls zippered compiler-rt --> `__isPlatformVersionAtLeast` required
115    //     zippered binary calls zippered compiler-rt --> `__isPlatformOrVariantPlatformVersionAtLeast` called
116
117    // FIXME(madsmtm): `rustc` doesn't support zippered binaries yet, see rust-lang/rust#131216.
118    // But once it does, we need the pre-compiled `std` shipped with rustup to be zippered, and thus
119    // we also need to handle the `platform` difference here:
120    //
121    // if cfg!(target_os = "macos") && platform == 2 /* PLATFORM_IOS */ && cfg!(zippered) {
122    //     return (version.to_u32() <= current_ios_version()) as i32;
123    // }
124    //
125    // `__isPlatformOrVariantPlatformVersionAtLeast` would also need to be implemented.
126
127    // The base Mach-O platform for the current target.
128    const BASE_TARGET_PLATFORM: i32 = if cfg!(target_os = "macos") {
129        1 // PLATFORM_MACOS
130    } else if cfg!(target_os = "ios") {
131        2 // PLATFORM_IOS
132    } else if cfg!(target_os = "tvos") {
133        3 // PLATFORM_TVOS
134    } else if cfg!(target_os = "watchos") {
135        4 // PLATFORM_WATCHOS
136    } else if cfg!(target_os = "visionos") {
137        11 // PLATFORM_VISIONOS
138    } else {
139        0 // PLATFORM_UNKNOWN
140    };
141    debug_assert_eq!(
142        platform, BASE_TARGET_PLATFORM,
143        "invalid platform provided to __isPlatformVersionAtLeast",
144    );
145
146    (version <= current_version()) as i32
147}
148
149/// Old entry point for availability. Used when compiling with older Clang versions.
150// SAFETY: Same as for `__isPlatformVersionAtLeast`.
151#[rustc_std_internal_symbol]
152#[linkage = "weak"]
153pub(super) extern "C" fn __isOSVersionAtLeast(major: i32, minor: i32, subminor: i32) -> i32 {
154    let version = pack_i32_os_version(major, minor, subminor);
155    (version <= current_version()) as i32
156}