Skip to content

Commit a0061ac

Browse files
committed
Add __isOSVersionAtLeast and __isPlatformVersionAtLeast symbols
Allows users to link to Objective-C code using `@available(...)`.
1 parent f174fd7 commit a0061ac

File tree

12 files changed

+1044
-0
lines changed

12 files changed

+1044
-0
lines changed

library/std/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@
347347
#![feature(hasher_prefixfree_extras)]
348348
#![feature(hashmap_internals)]
349349
#![feature(hint_must_use)]
350+
#![feature(int_from_ascii)]
350351
#![feature(ip)]
351352
#![feature(lazy_get)]
352353
#![feature(maybe_uninit_slice)]
@@ -363,6 +364,7 @@
363364
#![feature(slice_internals)]
364365
#![feature(slice_ptr_get)]
365366
#![feature(slice_range)]
367+
#![feature(slice_split_once)]
366368
#![feature(std_internals)]
367369
#![feature(str_internals)]
368370
#![feature(strict_provenance_atomic_ptr)]

library/std/src/sys/mod.rs

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

0 commit comments

Comments
 (0)