| //! # 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: | |
| //! - Clang's `__builtin_available` macro. | |
| //! - Objective-C's `@available`. | |
| //! - Swift's `#available`, | |
| //! | |
| //! Without Rust exposing these symbols, the user would encounter a linker error when linking to | |
| //! C/Objective-C/Swift libraries using these features. | |
| //! | |
| //! The presence of these symbols is mostly considered a quality-of-implementation detail, and | |
| //! should not be relied upon to be available. The intended effect is that linking with code built | |
| //! with Clang's `__builtin_available` (or similar) will continue to work. For example, we may | |
| //! decide to remove `__isOSVersionAtLeast` if support for Clang 11 (Xcode 11) is dropped. | |
| //! | |
| //! ## Background | |
| //! | |
| //! 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> | |
| //! | |
| //! And the upstream implementation of these can be found in `compiler-rt`: | |
| //! <https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/compiler-rt/lib/builtins/os_version_check.c> | |
| //! | |
| //! Ideally, these symbols should probably have been a part of Apple's `libSystem.dylib`, both | |
| //! 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 C/Objective-C/Swift 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>. | |
| //! | |
| //! ## Implementation details | |
| //! | |
| //! 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::{current_version, pack_i32_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 | |
| // | |
| // NOTE: This symbol has a workaround in the compiler's symbol mangling to avoid mangling it, while | |
| // still not exposing it from non-cdylib (like `#[no_mangle]` would). | |
| #[rustc_std_internal_symbol] | |
| // NOTE: Making this a weak symbol might not be entirely the right solution for this, `compiler_rt` | |
| // doesn't do that, it instead makes the symbol have "hidden" visibility. But since this is placed | |
| // in `libstd`, which might be used as a dylib, we cannot do the same here. | |
| #[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: i32, | |
| minor: i32, | |
| subminor: i32, | |
| ) -> i32 { | |
| let version = pack_i32_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`. | |
| #[rustc_std_internal_symbol] | |
| #[linkage = "weak"] | |
| pub(super) extern "C" fn __isOSVersionAtLeast(major: i32, minor: i32, subminor: i32) -> i32 { | |
| let version = pack_i32_os_version(major, minor, subminor); | |
| (version <= current_version()) as i32 | |
| } |