Skip to content

Conversation

madsmtm
Copy link
Contributor

@madsmtm madsmtm commented Mar 25, 2025

Motivation

When Objective-C code uses @available(...), Clang inserts a call to __isPlatformVersionAtLeast (__isOSVersionAtLeast in older Clang versions). These symbols not being available sometimes ends up causing linker errors. See the new test tests/run-make/apple-c-available-links for a minimal reproducer.

The workaround is to link libclang_rt.osx.a, see e.g. alexcrichton/curl-rust#279. But that's very difficult for users to figure out (and the backreferences to that issue indicates that people are still running into this in their own projects every so often).

For another recent example, this is preventing rustc from using LLVM assertions on macOS, see #62592 (comment) and #134275 (comment).

It is also a blocker for setting the correct minimum OS version in cc-rs, since fixing this in cc-rs might end up introducing linker errors in places where we weren't before (by default, if using e.g. @available(macos 10.15, *), the symbol usually happens to be left out, since clang defaults to compiling for the host macOS version, and thus things seem to work - but the availability check actually compiles down to nothing, which is a huge correctness footgun for running on older OSes).

(My super secret evil agenda is also to expose some variant of @available in Rust's std after rust-lang/rfcs#3750 progresses further, will probably file an ACP for this later. But I believe this PR has value regardless of those future plans, since we'd be making C/Objective-C/Swift interop easier).

Solution

Implement __isPlatformVersionAtLeast and __isOSVersionAtLeast as part of the "public ABI" that std exposes.

This is insta-stable, in the same sense that additions to compiler-builtins are insta-stable, though the availability of these symbols can probably be considered a "quality of implementation" detail rather than a stable promise.

I originally proposed to implement this in compiler-builtins, see rust-lang/compiler-builtins#794, but we discussed moving it to std instead (Zulip thread), which makes the implementation substantially simpler, and we avoid gnarly issues with requiring the user to link libSystem.dylib (since std unconditionally does that).

Note that this does not solve the linker errors for (pure) #![no_std] users, but that's probably fine, if you are using @available to test the OS version on Apple platforms, you're likely also using std (and it is still possible to work around by linking libclang_rt.*.a).

A thing to note about the implementation, I've choosen to stray a bit from LLVM's upstream implementation, and not use _availability_version_check since it has problems when compiling with an older SDK. Instead, we use sysctl kern.osproductversion when available to still avoid the costly PList lookup in most cases, but still with a fall back to the PList lookup when that is not available (with the PList fallback being is similar to LLVM's implementation).

Testing

Apple has a lot of different "modes" that they can run binaries in, which can be a bit difficult to find your bearings in, but I've tried to be as thorough as I could in testing them all.

Tested using roughly the equivalent of ./x test library/std -- platform_version on the following configurations:

  • macOS 14.7.3 on a Macbook Pro M2
    • aarch64-apple-darwin
    • x86_64-apple-darwin (under Rosetta)
    • aarch64-apple-ios-macabi
    • x86_64-apple-ios-macabi (under Rosetta)
    • aarch64-apple-ios (using Xcode's "Designed for iPad" setting)
    • aarch64-apple-ios-sim (in iOS Simulator, as iPhone with iOS 17.5)
    • aarch64-apple-ios-sim (in iOS Simulator, as iPad with iOS 18.2)
    • aarch64-apple-tvos-sim (in tvOS Simulator)
    • aarch64-apple-watchos-sim (in watchOS Simulator)
    • aarch64-apple-ios-sim (in visionOS simulator, using Xcode's "Designed for iPad" setting)
    • aarch64-apple-visionos-sim (in visionOS Simulator)
  • macOS 15.3.1 VM
    • aarch64-apple-darwin
    • aarch64-apple-ios-macabi
  • macOS 10.12.6 on an Intel Macbook from 2013
    • x86_64-apple-darwin
    • i686-apple-darwin
    • x86_64-apple-ios (in iOS Simulator)
  • iOS 9.3.6 on a 1st generation iPad Mini
    • armv7-apple-ios with an older compiler

Along with manually inspecting the output of version_from_sysctl() and version_from_plist(), and verifying that they actually match what's expected.

I believe the only real omissions here would be:

  • aarch64-apple-ios on a newer iPhone that has sysctl available (iOS 11.4 or above).
  • aarch64-apple-ios on a Vision Pro using Xcode's "Designed for iPad" setting.

But I don't have the hardware available to test those.

@rustbot label O-apple A-linkage -T-compiler -A-meta -A-run-make

try-job: aarch64-apple

@rustbot
Copy link
Collaborator

rustbot commented Mar 25, 2025

r? @Amanieu

rustbot has assigned @Amanieu.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added A-meta Area: Issues & PRs about the rust-lang/rust repository itself A-run-make Area: port run-make Makefiles to rmake.rs S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 25, 2025
@rustbot
Copy link
Collaborator

rustbot commented Mar 25, 2025

This PR modifies run-make tests.

cc @jieyouxu

triagebot.toml has been modified, there may have been changes to the review queue.

cc @davidtwco, @wesleywiser

@madsmtm madsmtm changed the title Add __isOSVersionAtLeast and __isPlatformVersionAtLeast symbols Add __isPlatformVersionAtLeast and __isOSVersionAtLeast symbols Mar 25, 2025
@rust-log-analyzer

This comment has been minimized.

@rustbot rustbot added the O-apple Operating system: Apple (macOS, iOS, tvOS, visionOS, watchOS) label Mar 25, 2025
@madsmtm madsmtm force-pushed the apple_os_version_check branch from 3908474 to 721f8eb Compare March 25, 2025 20:21
@Amanieu
Copy link
Member

Amanieu commented Apr 3, 2025

r? @tgross35

@rustbot rustbot assigned tgross35 and unassigned Amanieu Apr 3, 2025
@tgross35
Copy link
Contributor

tgross35 commented Apr 3, 2025

Happy to review the implementation, but @Amanieu mind confirming you are okay providing these symbols from std? It seems reasonable to me but as far as I know we don't have anything like this. (Also no other builtins that effectively need std API).

@rust-log-analyzer

This comment has been minimized.

@madsmtm madsmtm force-pushed the apple_os_version_check branch from a0061ac to 19cccaf Compare April 4, 2025 12:13
madsmtm added a commit to madsmtm/objc2 that referenced this pull request Apr 4, 2025
Based on findings in rust-lang/rust#138944, we
were previously incorrectly falling back to the product version of the
host system.
@Amanieu
Copy link
Member

Amanieu commented Apr 5, 2025

It's a bit of a grey area since it's not clear how much it is Rust's responsibility to provide builtin symbols that are only used by C code. Arguably the more "correct" approach is to tell users of C code that we only provide symbols for Rust code and that they need to separately add a dependency on libclang_rt or libgcc, but that is a lot of hassle and thing usually just work without it.

I'm not opposed to adding this in libstd for now, but it might be worth looking into a better story for how to handle builtins that are needed by C code but not Rust code (e.g. emulated TLS, clear_cache, enable_execute_stack, etc).

Copy link
Contributor

@tgross35 tgross35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments, I still need to take a closer look at the version_from* implementations.

@@ -0,0 +1,12 @@
//! Runtime lookup of operating system / platform version.
//!
//! Related to [RFC 3750](https://github.com/rust-lang/rfcs/pull/3750), which
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cc RFC author @ChrisDenton just so you know this module exists

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 9, 2025
@madsmtm
Copy link
Contributor Author

madsmtm commented Apr 9, 2025

Thanks for the review so far. I've answered or fixed your comments in separate commits, to make it easier for you to review (if you're already halfway through a second review). I can squash once everything is ready.
@rustbot ready

@rustbot rustbot removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-meta Area: Issues & PRs about the rust-lang/rust repository itself labels Apr 9, 2025
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 13, 2025
@tgross35
Copy link
Contributor

tgross35 commented Sep 5, 2025

Retrying just to make sure

@bors2 try

rust-bors bot added a commit that referenced this pull request Sep 5, 2025
Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols

try-job: aarch64-apple
try-job: x86_64-apple*
@rust-bors

This comment has been minimized.

@rust-bors
Copy link

rust-bors bot commented Sep 5, 2025

💔 Test for 02ca2d1 failed: CI. Failed job:

@rust-log-analyzer

This comment has been minimized.

@tgross35
Copy link
Contributor

tgross35 commented Sep 5, 2025

@bors2 try

@rust-bors

This comment has been minimized.

rust-bors bot added a commit that referenced this pull request Sep 5, 2025
Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols

try-job: aarch64-apple
Copy link
Contributor

@tgross35 tgross35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this is good; one small doc request at #138944 (comment), then r=me with the try job.

@rustbot author

View changes since this review

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 5, 2025
@rust-bors
Copy link

rust-bors bot commented Sep 5, 2025

☀️ Try build successful (CI)
Build commit: 7c6385c (7c6385c8de59cf782e27f0974898c02dea1960b1, parent: 91edc3ebccc4daa46c20a93f4709862376da1fdd)

Allows users to link to Objective-C code using `@available(...)`.
@madsmtm madsmtm force-pushed the apple_os_version_check branch from 19c266b to 846d6a4 Compare September 5, 2025 14:19
@rustbot
Copy link
Collaborator

rustbot commented Sep 5, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@madsmtm
Copy link
Contributor Author

madsmtm commented Sep 5, 2025

Thanks for taking the time to review this, I know it's quite a lot of code!

@bors rollup r=tgross35

@bors
Copy link
Collaborator

bors commented Sep 5, 2025

📌 Commit 846d6a4 has been approved by tgross35

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 5, 2025
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Sep 5, 2025
…tgross35

Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols

## Motivation

When Objective-C code uses ``@available(...)`,` Clang inserts a call to [`__isPlatformVersionAtLeast`](https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/compiler-rt/lib/builtins/os_version_check.c#L276) (`__isOSVersionAtLeast` in older Clang versions). These symbols not being available sometimes ends up causing linker errors. See the new test `tests/run-make/apple-c-available-links` for a minimal reproducer.

The workaround is to link `libclang_rt.osx.a`, see e.g. alexcrichton/curl-rust#279. But that's very difficult for users to figure out (and the backreferences to that issue indicates that people are still running into this in their own projects every so often).

For another recent example, this is preventing `rustc` from using LLVM assertions on macOS, see rust-lang#62592 (comment) and rust-lang#134275 (comment).

It is also a blocker for [setting the correct minimum OS version in `cc-rs`](rust-lang#136113), since fixing this in `cc-rs` might end up introducing linker errors in places where we weren't before (by default, if using e.g. ``@available(macos` 10.15, *)`, the symbol usually happens to be left out, since `clang` defaults to compiling for the host macOS version, and thus things _seem_ to work - but the availability check actually compiles down to nothing, which is a huge correctness footgun for running on older OSes).

(My super secret evil agenda is also to expose some variant of ``@available`` in Rust's `std` after rust-lang/rfcs#3750 progresses further, will probably file an ACP for this later. But I believe this PR has value regardless of those future plans, since we'd be making C/Objective-C/Swift interop easier).

## Solution

Implement `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` as part of the "public ABI" that `std` exposes.

**This is insta-stable**, in the same sense that additions to `compiler-builtins` are insta-stable, though the availability of these symbols can probably be considered a "quality of implementation" detail rather than a stable promise.

I originally proposed to implement this in `compiler-builtins`, see rust-lang/compiler-builtins#794, but we discussed moving it to `std` instead ([Zulip thread](https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/Provide.20.60__isPlatformVersionAtLeast.60.20in.20.60std.60.3F/with/507880717)), which makes the implementation substantially simpler, and we avoid gnarly issues with requiring the user to link `libSystem.dylib` (since `std` unconditionally does that).

Note that this does not solve the linker errors for (pure) `#![no_std]` users, but that's _probably_ fine, if you are using ``@available`` to test the OS version on Apple platforms, you're likely also using `std` (and it is still possible to work around by linking `libclang_rt.*.a`).

A thing to note about the implementation, I've choosen to stray a bit from LLVM's upstream implementation, and not use `_availability_version_check` since [it has problems when compiling with an older SDK](llvm/llvm-project#64227). Instead, we use `sysctl kern.osproductversion` when available to still avoid the costly PList lookup in most cases, but still with a fall back to the PList lookup when that is not available (with the PList fallback being is similar to LLVM's implementation).

## Testing

Apple has a lot of different "modes" that they can run binaries in, which can be a bit difficult to find your bearings in, but I've tried to be as thorough as I could in testing them all.

Tested using roughly the equivalent of `./x test library/std -- platform_version` on the following configurations:
- macOS 14.7.3 on a Macbook Pro M2
    - `aarch64-apple-darwin`
    - `x86_64-apple-darwin` (under Rosetta)
    - `aarch64-apple-ios-macabi`
    - `x86_64-apple-ios-macabi` (under Rosetta)
    - `aarch64-apple-ios` (using Xcode's "Designed for iPad" setting)
    - `aarch64-apple-ios-sim` (in iOS Simulator, as iPhone with iOS 17.5)
    - `aarch64-apple-ios-sim` (in iOS Simulator, as iPad with iOS 18.2)
    - `aarch64-apple-tvos-sim` (in tvOS Simulator)
    - `aarch64-apple-watchos-sim` (in watchOS Simulator)
    - `aarch64-apple-ios-sim` (in visionOS simulator, using Xcode's "Designed for iPad" setting)
    - `aarch64-apple-visionos-sim` (in visionOS Simulator)
- macOS 15.3.1 VM
    - `aarch64-apple-darwin`
    - `aarch64-apple-ios-macabi`
- macOS 10.12.6 on an Intel Macbook from 2013
    - `x86_64-apple-darwin`
    - `i686-apple-darwin`
    - `x86_64-apple-ios` (in iOS Simulator)
- iOS 9.3.6 on a 1st generation iPad Mini
    - `armv7-apple-ios` with an older compiler

Along with manually inspecting the output of `version_from_sysctl()` and `version_from_plist()`, and verifying that they actually match what's expected.

I believe the only real omissions here would be:
- `aarch64-apple-ios` on a newer iPhone that has `sysctl` available (iOS 11.4 or above).
- `aarch64-apple-ios` on a Vision Pro using Xcode's "Designed for iPad" setting.

But I don't have the hardware available to test those.

`@rustbot` label O-apple A-linkage -T-compiler -A-meta -A-run-make

try-job: aarch64-apple
bors added a commit that referenced this pull request Sep 5, 2025
Rollup of 4 pull requests

Successful merges:

 - #138944 (Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols)
 - #146041 (tidy: --bless now makes escheck run with --fix)
 - #146121 (fix: Filter suggestion parts that match existing code)
 - #146241 (rustc_infer: change top-level doc comment to inner)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit that referenced this pull request Sep 5, 2025
Rollup of 4 pull requests

Successful merges:

 - #138944 (Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols)
 - #146041 (tidy: --bless now makes escheck run with --fix)
 - #146121 (fix: Filter suggestion parts that match existing code)
 - #146241 (rustc_infer: change top-level doc comment to inner)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit that referenced this pull request Sep 5, 2025
Rollup of 11 pull requests

Successful merges:

 - #138944 (Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols)
 - #139113 (unstable book: in a sanitizer example, check the code)
 - #145735 (style-guide: Document absence of trailing whitespace)
 - #146041 (tidy: --bless now makes escheck run with --fix)
 - #146144 (compiler: Apply target features to the entry function)
 - #146225 (Simplify `{f16, f32, f64, f128}::midpoint()`)
 - #146234 (change file-is-generated doc comment to inner)
 - #146241 (rustc_infer: change top-level doc comment to inner)
 - #146242 (Ensure that `--html-after-content` option is used to check `scrape_examples_ice` rustdoc GUI test)
 - #146243 (remove couple of redundant clones)
 - #146250 (Bump stage0 rustfmt)

Failed merges:

 - #146200 (Simplify rustdoc-gui tester by calling directly browser-ui-test)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 349fbba into rust-lang:master Sep 5, 2025
10 checks passed
@rustbot rustbot added this to the 1.91.0 milestone Sep 5, 2025
rust-timer added a commit that referenced this pull request Sep 6, 2025
Rollup merge of #138944 - madsmtm:apple_os_version_check, r=tgross35

Add `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` symbols

## Motivation

When Objective-C code uses ```@available(...)`,`` Clang inserts a call to [`__isPlatformVersionAtLeast`](https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/compiler-rt/lib/builtins/os_version_check.c#L276) (`__isOSVersionAtLeast` in older Clang versions). These symbols not being available sometimes ends up causing linker errors. See the new test `tests/run-make/apple-c-available-links` for a minimal reproducer.

The workaround is to link `libclang_rt.osx.a`, see e.g. alexcrichton/curl-rust#279. But that's very difficult for users to figure out (and the backreferences to that issue indicates that people are still running into this in their own projects every so often).

For another recent example, this is preventing `rustc` from using LLVM assertions on macOS, see #62592 (comment) and #134275 (comment).

It is also a blocker for [setting the correct minimum OS version in `cc-rs`](#136113), since fixing this in `cc-rs` might end up introducing linker errors in places where we weren't before (by default, if using e.g. ```@available(macos`` 10.15, *)`, the symbol usually happens to be left out, since `clang` defaults to compiling for the host macOS version, and thus things _seem_ to work - but the availability check actually compiles down to nothing, which is a huge correctness footgun for running on older OSes).

(My super secret evil agenda is also to expose some variant of ```@available``` in Rust's `std` after rust-lang/rfcs#3750 progresses further, will probably file an ACP for this later. But I believe this PR has value regardless of those future plans, since we'd be making C/Objective-C/Swift interop easier).

## Solution

Implement `__isPlatformVersionAtLeast` and `__isOSVersionAtLeast` as part of the "public ABI" that `std` exposes.

**This is insta-stable**, in the same sense that additions to `compiler-builtins` are insta-stable, though the availability of these symbols can probably be considered a "quality of implementation" detail rather than a stable promise.

I originally proposed to implement this in `compiler-builtins`, see rust-lang/compiler-builtins#794, but we discussed moving it to `std` instead ([Zulip thread](https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/Provide.20.60__isPlatformVersionAtLeast.60.20in.20.60std.60.3F/with/507880717)), which makes the implementation substantially simpler, and we avoid gnarly issues with requiring the user to link `libSystem.dylib` (since `std` unconditionally does that).

Note that this does not solve the linker errors for (pure) `#![no_std]` users, but that's _probably_ fine, if you are using ```@available``` to test the OS version on Apple platforms, you're likely also using `std` (and it is still possible to work around by linking `libclang_rt.*.a`).

A thing to note about the implementation, I've choosen to stray a bit from LLVM's upstream implementation, and not use `_availability_version_check` since [it has problems when compiling with an older SDK](llvm/llvm-project#64227). Instead, we use `sysctl kern.osproductversion` when available to still avoid the costly PList lookup in most cases, but still with a fall back to the PList lookup when that is not available (with the PList fallback being is similar to LLVM's implementation).

## Testing

Apple has a lot of different "modes" that they can run binaries in, which can be a bit difficult to find your bearings in, but I've tried to be as thorough as I could in testing them all.

Tested using roughly the equivalent of `./x test library/std -- platform_version` on the following configurations:
- macOS 14.7.3 on a Macbook Pro M2
    - `aarch64-apple-darwin`
    - `x86_64-apple-darwin` (under Rosetta)
    - `aarch64-apple-ios-macabi`
    - `x86_64-apple-ios-macabi` (under Rosetta)
    - `aarch64-apple-ios` (using Xcode's "Designed for iPad" setting)
    - `aarch64-apple-ios-sim` (in iOS Simulator, as iPhone with iOS 17.5)
    - `aarch64-apple-ios-sim` (in iOS Simulator, as iPad with iOS 18.2)
    - `aarch64-apple-tvos-sim` (in tvOS Simulator)
    - `aarch64-apple-watchos-sim` (in watchOS Simulator)
    - `aarch64-apple-ios-sim` (in visionOS simulator, using Xcode's "Designed for iPad" setting)
    - `aarch64-apple-visionos-sim` (in visionOS Simulator)
- macOS 15.3.1 VM
    - `aarch64-apple-darwin`
    - `aarch64-apple-ios-macabi`
- macOS 10.12.6 on an Intel Macbook from 2013
    - `x86_64-apple-darwin`
    - `i686-apple-darwin`
    - `x86_64-apple-ios` (in iOS Simulator)
- iOS 9.3.6 on a 1st generation iPad Mini
    - `armv7-apple-ios` with an older compiler

Along with manually inspecting the output of `version_from_sysctl()` and `version_from_plist()`, and verifying that they actually match what's expected.

I believe the only real omissions here would be:
- `aarch64-apple-ios` on a newer iPhone that has `sysctl` available (iOS 11.4 or above).
- `aarch64-apple-ios` on a Vision Pro using Xcode's "Designed for iPad" setting.

But I don't have the hardware available to test those.

``@rustbot`` label O-apple A-linkage -T-compiler -A-meta -A-run-make

try-job: aarch64-apple
@madsmtm madsmtm deleted the apple_os_version_check branch September 6, 2025 01:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries A-meta Area: Issues & PRs about the rust-lang/rust repository itself A-run-make Area: port run-make Makefiles to rmake.rs O-apple Operating system: Apple (macOS, iOS, tvOS, visionOS, watchOS) S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants