From 300c9fa5ccc3f80d27ee707361784bd224282d9c Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:38:12 -0700 Subject: [PATCH 01/13] arm64e: add darwin ptrauth codegen experiment --- compiler/rustc_codegen_llvm/src/attributes.rs | 23 +++++++++++++-- compiler/rustc_codegen_llvm/src/context.rs | 29 +++++++++++++++++++ compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 8 +++++ compiler/rustc_codegen_llvm/src/llvm/mod.rs | 17 +++++++++++ .../rustc_codegen_ssa/src/back/metadata.rs | 4 ++- .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 7 +++++ 6 files changed, 85 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 1f59d250e08a0..ab19ffd7abb50 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -9,7 +9,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_session::config::{BranchProtection, FunctionReturn, OptLevel, PAuthKey, PacRet}; use rustc_span::sym; use rustc_symbol_mangling::mangle_internal_symbol; -use rustc_target::spec::{Arch, FramePointer, SanitizerSet, StackProbeType, StackProtector}; +use rustc_target::spec::{Arch, FramePointer, Os, SanitizerSet, StackProbeType, StackProtector}; use smallvec::SmallVec; use crate::context::SimpleCx; @@ -349,10 +349,16 @@ pub(crate) fn target_features_attr<'ll, 'tcx>( tcx: TyCtxt<'tcx>, function_features: Vec, ) -> Option<&'ll Attribute> { + let mut synthesized_features = Vec::new(); + if has_default_arm64e_ptrauth(tcx.sess) { + synthesized_features.push("+pauth"); + } + let global_features = tcx.global_backend_features(()).iter().map(String::as_str); let function_features = function_features.iter().map(String::as_str); + let synthesized_features = synthesized_features.into_iter(); let target_features = - global_features.chain(function_features).intersperse(",").collect::(); + global_features.chain(function_features).chain(synthesized_features).intersperse(",").collect::(); (!target_features.is_empty()) .then(|| llvm::CreateAttrStringValue(cx.llcx, "target-features", &target_features)) } @@ -578,6 +584,13 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>( } } + if has_default_arm64e_ptrauth(sess) { + to_add.push(llvm::CreateAttrString(cx.llcx, "ptrauth-auth-traps")); + to_add.push(llvm::CreateAttrString(cx.llcx, "ptrauth-calls")); + to_add.push(llvm::CreateAttrString(cx.llcx, "ptrauth-indirect-gotos")); + to_add.push(llvm::CreateAttrString(cx.llcx, "ptrauth-returns")); + } + let function_features = function_features .iter() // Convert to LLVMFeatures and filter out unavailable ones @@ -613,3 +626,9 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>( fn wasm_import_module(tcx: TyCtxt<'_>, id: DefId) -> Option<&String> { tcx.wasm_import_module_map(id.krate).get(&id) } + +fn has_default_arm64e_ptrauth(sess: &Session) -> bool { + sess.target.arch == Arch::AArch64 + && sess.target.os == Os::MacOs + && sess.target.llvm_target.starts_with("arm64e") +} diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 5b730b820b84a..24129cf444d08 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -426,6 +426,35 @@ pub(crate) unsafe fn create_module<'ll>( } } + if sess.target.arch == Arch::AArch64 + && sess.target.os == Os::MacOs + && sess.target.llvm_target.starts_with("arm64e") + { + let ptrauth_abi_version = unsafe { + llvm::LLVMMDNodeInContext2( + llcx, + [llvm::LLVMMDNodeInContext2( + llcx, + [llvm::LLVMValueAsMetadata(llvm::LLVMConstInt( + llvm::LLVMInt32TypeInContext(llcx), + 0, + llvm::FALSE, + ))] + .as_ptr(), + 1, + )] + .as_ptr(), + 1, + ) + }; + llvm::add_module_flag_metadata( + llmod, + llvm::ModuleFlagMergeBehavior::AppendUnique, + "ptrauth.abi-version", + ptrauth_abi_version, + ); + } + // Pass on the control-flow protection flags to LLVM (equivalent to `-fcf-protection` in Clang). if let CFProtection::Branch | CFProtection::Full = sess.opts.unstable_opts.cf_protection { llvm::add_module_flag_u32( diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 525d1dbe9d0d3..e9acdd662f6f7 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2185,6 +2185,14 @@ unsafe extern "C" { ValueLen: size_t, ); + pub(crate) fn LLVMRustAddModuleFlagMetadata( + M: &Module, + MergeBehavior: ModuleFlagMergeBehavior, + Name: *const c_char, + NameLen: size_t, + Value: &Metadata, + ); + pub(crate) fn LLVMRustDIBuilderCreateCompileUnit<'a>( Builder: &DIBuilder<'a>, Lang: c_uint, diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index 2ec19b1795b5a..e7d2f323b5739 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -445,6 +445,23 @@ pub(crate) fn add_module_flag_str( } } +pub(crate) fn add_module_flag_metadata( + module: &Module, + merge_behavior: ModuleFlagMergeBehavior, + key: &str, + value: &Metadata, +) { + unsafe { + LLVMRustAddModuleFlagMetadata( + module, + merge_behavior, + key.as_c_char_ptr(), + key.len(), + value, + ); + } +} + pub(crate) fn set_dllimport_storage_class<'ll>(v: &'ll Value) { unsafe { LLVMSetDLLStorageClass(v, DLLStorageClass::DllImport); diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index f6544c0351457..1ab0faa0d3b69 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -216,7 +216,9 @@ pub(crate) fn create_object_file(sess: &Session) -> OptiongetContext(), StringRef(Value, ValueLen))); } +extern "C" void LLVMRustAddModuleFlagMetadata( + LLVMModuleRef M, LLVMRustModuleFlagMergeBehavior MergeBehavior, + const char *Name, size_t NameLen, LLVMMetadataRef Value) { + unwrap(M)->addModuleFlag(fromRust(MergeBehavior), StringRef(Name, NameLen), + unwrap(Value)); +} + extern "C" LLVMValueRef LLVMRustGetLastInstruction(LLVMBasicBlockRef BB) { auto Point = unwrap(BB)->rbegin(); if (Point != unwrap(BB)->rend()) From ad89eefb514688f0e9ab45f06af3b4e9200e5876 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:41:35 -0700 Subject: [PATCH 02/13] feat: extend Apple arm64e experiment targets --- compiler/rustc_codegen_llvm/src/attributes.rs | 2 +- compiler/rustc_codegen_llvm/src/context.rs | 2 +- compiler/rustc_target/src/spec/mod.rs | 1 + .../src/spec/targets/arm64e_apple_visionos.rs | 25 +++++++++++ src/doc/rustc/src/SUMMARY.md | 1 + src/doc/rustc/src/platform-support.md | 1 + .../src/platform-support/apple-visionos.md | 9 ++++ .../platform-support/arm64e-apple-visionos.md | 41 +++++++++++++++++++ tests/assembly-llvm/targets/targets-macho.rs | 3 ++ 9 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs create mode 100644 src/doc/rustc/src/platform-support/arm64e-apple-visionos.md diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index ab19ffd7abb50..e7b6765923463 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -629,6 +629,6 @@ fn wasm_import_module(tcx: TyCtxt<'_>, id: DefId) -> Option<&String> { fn has_default_arm64e_ptrauth(sess: &Session) -> bool { sess.target.arch == Arch::AArch64 - && sess.target.os == Os::MacOs + && matches!(sess.target.os, Os::MacOs | Os::IOs | Os::TvOs | Os::VisionOs) && sess.target.llvm_target.starts_with("arm64e") } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 24129cf444d08..8a83816bb4d42 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -427,7 +427,7 @@ pub(crate) unsafe fn create_module<'ll>( } if sess.target.arch == Arch::AArch64 - && sess.target.os == Os::MacOs + && matches!(sess.target.os, Os::MacOs | Os::IOs | Os::TvOs | Os::VisionOs) && sess.target.llvm_target.starts_with("arm64e") { let ptrauth_abi_version = unsafe { diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 768e43146a0c1..9d8372ca64f82 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1615,6 +1615,7 @@ supported_targets! { ("aarch64-apple-visionos", aarch64_apple_visionos), ("aarch64-apple-visionos-sim", aarch64_apple_visionos_sim), + ("arm64e-apple-visionos", arm64e_apple_visionos), ("armebv7r-none-eabi", armebv7r_none_eabi), ("armebv7r-none-eabihf", armebv7r_none_eabihf), diff --git a/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs b/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs new file mode 100644 index 0000000000000..b1000c2c1c7eb --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs @@ -0,0 +1,25 @@ +use crate::spec::base::apple::{Arch, TargetEnv, base}; +use crate::spec::{Os, SanitizerSet, Target, TargetMetadata, TargetOptions}; + +pub(crate) fn target() -> Target { + let (opts, llvm_target, arch) = base(Os::VisionOs, Arch::Arm64e, TargetEnv::Normal); + Target { + llvm_target, + metadata: TargetMetadata { + description: Some("ARM64e Apple visionOS".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 64, + data_layout: "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32" + .into(), + arch, + options: TargetOptions { + features: "+neon,+apple-a12,+v8.3a,+paca,+pacg".into(), + max_atomic_width: Some(128), + supported_sanitizers: SanitizerSet::ADDRESS | SanitizerSet::THREAD, + ..opts + }, + } +} diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index cc10f476780c5..915b8e7aa9a35 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -46,6 +46,7 @@ - [arm64e-apple-tvos](platform-support/arm64e-apple-tvos.md) - [\*-apple-watchos](platform-support/apple-watchos.md) - [\*-apple-visionos](platform-support/apple-visionos.md) + - [arm64e-apple-visionos](platform-support/arm64e-apple-visionos.md) - [aarch64-nintendo-switch-freestanding](platform-support/aarch64-nintendo-switch-freestanding.md) - [aarch64-unknown-linux-gnu](platform-support/aarch64-unknown-linux-gnu.md) - [aarch64-unknown-linux-musl](platform-support/aarch64-unknown-linux-musl.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 26dd6b31b8991..dc8a982e3b7a7 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -292,6 +292,7 @@ target | std | host | notes [`arm64e-apple-darwin`](platform-support/arm64e-apple-darwin.md) | ✓ | ✓ | ARM64e Apple Darwin [`arm64e-apple-ios`](platform-support/arm64e-apple-ios.md) | ✓ | | ARM64e Apple iOS [`arm64e-apple-tvos`](platform-support/arm64e-apple-tvos.md) | ✓ | | ARM64e Apple tvOS +[`arm64e-apple-visionos`](platform-support/arm64e-apple-visionos.md) | ✓ | | ARM64e Apple visionOS [`armeb-unknown-linux-gnueabi`](platform-support/armeb-unknown-linux-gnueabi.md) | ✓ | ? | Arm BE8 the default Arm big-endian architecture since [Armv6](https://developer.arm.com/documentation/101754/0616/armlink-Reference/armlink-Command-line-Options/--be8?lang=en). [`armebv7r-none-eabi`](platform-support/armebv7r-none-eabi.md) | * | | Bare Armv7-R, Big Endian [`armebv7r-none-eabihf`](platform-support/armebv7r-none-eabi.md) | * | | Bare Armv7-R, Big Endian, hardfloat diff --git a/src/doc/rustc/src/platform-support/apple-visionos.md b/src/doc/rustc/src/platform-support/apple-visionos.md index cd183e7d635da..92761322f719d 100644 --- a/src/doc/rustc/src/platform-support/apple-visionos.md +++ b/src/doc/rustc/src/platform-support/apple-visionos.md @@ -6,6 +6,7 @@ Apple visionOS / xrOS targets. - `aarch64-apple-visionos`: Apple visionOS on arm64. - `aarch64-apple-visionos-sim`: Apple visionOS Simulator on arm64. +- `arm64e-apple-visionos`: Apple visionOS on arm64e. ## Target maintainers @@ -37,6 +38,14 @@ $ rustup target add aarch64-apple-visionos $ rustup target add aarch64-apple-visionos-sim ``` +For the arm64e target, Rust does not currently ship pre-built artifacts. Build +Rust with the target enabled in `bootstrap.toml`: + +```toml +[build] +target = ["arm64e-apple-visionos"] +``` + ## Building Rust programs See [the instructions for iOS](./apple-ios.md#building-rust-programs). diff --git a/src/doc/rustc/src/platform-support/arm64e-apple-visionos.md b/src/doc/rustc/src/platform-support/arm64e-apple-visionos.md new file mode 100644 index 0000000000000..b0c27aa792ac7 --- /dev/null +++ b/src/doc/rustc/src/platform-support/arm64e-apple-visionos.md @@ -0,0 +1,41 @@ +# `arm64e-apple-visionos` + +**Tier: 3** + +ARM64e visionOS (1.0+) + +## Target maintainers + +[@arttet](https://github.com/arttet) + +## Requirements + +See the docs on [`*-apple-visionos`](apple-visionos.md) for general visionOS +requirements. + +## Building the target + +You can build Rust with support for the target by adding it to the `target` +list in `bootstrap.toml`: + +```toml +[build] +target = ["arm64e-apple-visionos"] +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. +To compile for this target, you will need to build Rust with the target enabled +(see [Building the target](#building-the-target) above). + +## Testing + +The target does support running binaries on visionOS platforms with `arm64e` +architecture. + +## Cross-compilation toolchains and C code + +The target does support `C` code. +To build compatible `C` code, you have to use Xcode with the same compiler and +flags. diff --git a/tests/assembly-llvm/targets/targets-macho.rs b/tests/assembly-llvm/targets/targets-macho.rs index 037f3c0093619..00383a6028d3e 100644 --- a/tests/assembly-llvm/targets/targets-macho.rs +++ b/tests/assembly-llvm/targets/targets-macho.rs @@ -34,6 +34,9 @@ //@ revisions: aarch64_apple_visionos //@ [aarch64_apple_visionos] compile-flags: --target aarch64-apple-visionos //@ [aarch64_apple_visionos] needs-llvm-components: aarch64 +//@ revisions: arm64e_apple_visionos +//@ [arm64e_apple_visionos] compile-flags: --target arm64e-apple-visionos +//@ [arm64e_apple_visionos] needs-llvm-components: aarch64 //@ revisions: aarch64_apple_visionos_sim //@ [aarch64_apple_visionos_sim] compile-flags: --target aarch64-apple-visionos-sim //@ [aarch64_apple_visionos_sim] needs-llvm-components: aarch64 From b945923415f1e1aa4e38d164c621288ff7d1888b Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:54:30 -0700 Subject: [PATCH 03/13] arm64e: align Apple ptrauth defaults with clang --- compiler/rustc_codegen_llvm/src/attributes.rs | 14 ++----- compiler/rustc_codegen_llvm/src/context.rs | 24 ++++++----- .../rustc_codegen_ssa/src/back/metadata.rs | 2 +- compiler/rustc_session/src/errors.rs | 6 +++ compiler/rustc_session/src/session.rs | 6 +++ .../rustc_target/src/spec/base/apple/mod.rs | 8 ++++ compiler/rustc_target/src/spec/mod.rs | 6 +++ .../src/spec/targets/arm64e_apple_darwin.rs | 3 +- .../src/spec/targets/arm64e_apple_ios.rs | 4 +- .../src/spec/targets/arm64e_apple_tvos.rs | 4 +- .../src/spec/targets/arm64e_apple_visionos.rs | 4 +- .../src/compiler-flags/branch-protection.md | 7 +++- tests/codegen-llvm/arm64e-apple-ptrauth.rs | 29 ++++++++++++++ ...-arm64e-apple-pac-ret.DARWIN_PACRET.stderr | 4 ++ ...e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr | 4 ++ ...ion-arm64e-apple-pac-ret.IOS_PACRET.stderr | 4 ++ ...m64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr | 4 ++ ...on-arm64e-apple-pac-ret.TVOS_PACRET.stderr | 4 ++ ...64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr | 4 ++ ...rm64e-apple-pac-ret.VISIONOS_PACRET.stderr | 4 ++ ...apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr | 4 ++ .../branch-protection-arm64e-apple-pac-ret.rs | 40 +++++++++++++++++++ 22 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 tests/codegen-llvm/arm64e-apple-ptrauth.rs create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr create mode 100644 tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index e7b6765923463..1bf0ba9b7e173 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -9,7 +9,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_session::config::{BranchProtection, FunctionReturn, OptLevel, PAuthKey, PacRet}; use rustc_span::sym; use rustc_symbol_mangling::mangle_internal_symbol; -use rustc_target::spec::{Arch, FramePointer, Os, SanitizerSet, StackProbeType, StackProtector}; +use rustc_target::spec::{Arch, FramePointer, SanitizerSet, StackProbeType, StackProtector}; use smallvec::SmallVec; use crate::context::SimpleCx; @@ -349,16 +349,10 @@ pub(crate) fn target_features_attr<'ll, 'tcx>( tcx: TyCtxt<'tcx>, function_features: Vec, ) -> Option<&'ll Attribute> { - let mut synthesized_features = Vec::new(); - if has_default_arm64e_ptrauth(tcx.sess) { - synthesized_features.push("+pauth"); - } - let global_features = tcx.global_backend_features(()).iter().map(String::as_str); let function_features = function_features.iter().map(String::as_str); - let synthesized_features = synthesized_features.into_iter(); let target_features = - global_features.chain(function_features).chain(synthesized_features).intersperse(",").collect::(); + global_features.chain(function_features).intersperse(",").collect::(); (!target_features.is_empty()) .then(|| llvm::CreateAttrStringValue(cx.llcx, "target-features", &target_features)) } @@ -628,7 +622,5 @@ fn wasm_import_module(tcx: TyCtxt<'_>, id: DefId) -> Option<&String> { } fn has_default_arm64e_ptrauth(sess: &Session) -> bool { - sess.target.arch == Arch::AArch64 - && matches!(sess.target.os, Os::MacOs | Os::IOs | Os::TvOs | Os::VisionOs) - && sess.target.llvm_target.starts_with("arm64e") + sess.target.is_apple_arm64e() } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 8a83816bb4d42..9910ff1f6be7d 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -426,22 +426,26 @@ pub(crate) unsafe fn create_module<'ll>( } } - if sess.target.arch == Arch::AArch64 - && matches!(sess.target.os, Os::MacOs | Os::IOs | Os::TvOs | Os::VisionOs) - && sess.target.llvm_target.starts_with("arm64e") - { + if sess.target.is_apple_arm64e() { let ptrauth_abi_version = unsafe { llvm::LLVMMDNodeInContext2( llcx, [llvm::LLVMMDNodeInContext2( llcx, - [llvm::LLVMValueAsMetadata(llvm::LLVMConstInt( - llvm::LLVMInt32TypeInContext(llcx), - 0, - llvm::FALSE, - ))] + [ + llvm::LLVMValueAsMetadata(llvm::LLVMConstInt( + llvm::LLVMInt32TypeInContext(llcx), + 0, + llvm::FALSE, + )), + llvm::LLVMValueAsMetadata(llvm::LLVMConstInt( + llvm::LLVMInt1TypeInContext(llcx), + 0, + llvm::FALSE, + )), + ] .as_ptr(), - 1, + 2, )] .as_ptr(), 1, diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index 1ab0faa0d3b69..89711eea97570 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -446,7 +446,7 @@ fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachO /// Is Apple's CPU subtype `arm64e`s fn macho_is_arm64e(target: &Target) -> bool { - target.llvm_target.starts_with("arm64e") + target.is_apple_arm64e() } pub(crate) enum MetadataPosition { diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 54f88aa22bdc4..fab5dc50bf929 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -218,6 +218,12 @@ pub(crate) struct SmallDataThresholdNotSupportedForTarget<'a> { #[diag("`-Zbranch-protection` is only supported on aarch64")] pub(crate) struct BranchProtectionRequiresAArch64; +#[derive(Diagnostic)] +#[diag( + "`-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default" +)] +pub(crate) struct BranchProtectionPacRetRequiresNonArm64eApple; + #[derive(Diagnostic)] #[diag("`-Csplit-debuginfo={$debuginfo}` is unstable on this platform")] pub(crate) struct SplitDebugInfoUnstablePlatform { diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index a9e7f1503b9ca..67d7221e47db3 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1282,6 +1282,12 @@ fn validate_commandline_args_with_session_available(sess: &Session) { if sess.opts.unstable_opts.branch_protection.is_some() && sess.target.arch != Arch::AArch64 { sess.dcx().emit_err(errors::BranchProtectionRequiresAArch64); } + if let Some(branch_protection) = sess.opts.unstable_opts.branch_protection + && sess.target.is_apple_arm64e() + && branch_protection.pac_ret.is_some() + { + sess.dcx().emit_err(errors::BranchProtectionPacRetRequiresNonArm64eApple); + } if let Some(dwarf_version) = sess.opts.cg.dwarf_version.or(sess.opts.unstable_opts.dwarf_version) diff --git a/compiler/rustc_target/src/spec/base/apple/mod.rs b/compiler/rustc_target/src/spec/base/apple/mod.rs index d1d91cbd37884..34bd6d8b4268c 100644 --- a/compiler/rustc_target/src/spec/base/apple/mod.rs +++ b/compiler/rustc_target/src/spec/base/apple/mod.rs @@ -207,6 +207,14 @@ pub(crate) fn base( (opts, unversioned_llvm_target, arch.target_arch()) } +pub(crate) fn arm64e_features(prefix: &'static str) -> StaticCow { + if prefix.is_empty() { + "+v8.3a,+paca,+pacg".into() + } else { + format!("{prefix},+v8.3a,+paca,+pacg").into() + } +} + /// Generate part of the LLVM target triple. /// /// See `rustc_codegen_ssa::back::versioned_llvm_target` for the full triple passed to LLVM and diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 9d8372ca64f82..986a17e2a8ede 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -2235,6 +2235,12 @@ impl Target { | X86_64 | Xtensa => true, } } + + pub fn is_apple_arm64e(&self) -> bool { + self.arch == Arch::AArch64 + && matches!(self.os, Os::MacOs | Os::IOs | Os::TvOs | Os::VisionOs) + && self.llvm_target.starts_with("arm64e") + } } pub trait HasTargetSpec { diff --git a/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs b/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs index ab8f369f1bee2..301cadea8e3c4 100644 --- a/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs +++ b/compiler/rustc_target/src/spec/targets/arm64e_apple_darwin.rs @@ -1,4 +1,4 @@ -use crate::spec::base::apple::{Arch, TargetEnv, base}; +use crate::spec::base::apple::{Arch, TargetEnv, arm64e_features, base}; use crate::spec::{Os, SanitizerSet, Target, TargetMetadata, TargetOptions}; pub(crate) fn target() -> Target { @@ -16,6 +16,7 @@ pub(crate) fn target() -> Target { .into(), arch, options: TargetOptions { + features: arm64e_features(""), mcount: "\u{1}mcount".into(), cpu: "apple-m1".into(), max_atomic_width: Some(128), diff --git a/compiler/rustc_target/src/spec/targets/arm64e_apple_ios.rs b/compiler/rustc_target/src/spec/targets/arm64e_apple_ios.rs index 396f0c347a086..d55ce2da7a14d 100644 --- a/compiler/rustc_target/src/spec/targets/arm64e_apple_ios.rs +++ b/compiler/rustc_target/src/spec/targets/arm64e_apple_ios.rs @@ -1,4 +1,4 @@ -use crate::spec::base::apple::{Arch, TargetEnv, base}; +use crate::spec::base::apple::{Arch, TargetEnv, arm64e_features, base}; use crate::spec::{Os, SanitizerSet, Target, TargetMetadata, TargetOptions}; pub(crate) fn target() -> Target { @@ -16,7 +16,7 @@ pub(crate) fn target() -> Target { .into(), arch, options: TargetOptions { - features: "+neon,+apple-a12,+v8.3a,+paca,+pacg".into(), + features: arm64e_features("+neon,+apple-a12"), max_atomic_width: Some(128), supported_sanitizers: SanitizerSet::ADDRESS | SanitizerSet::THREAD, ..opts diff --git a/compiler/rustc_target/src/spec/targets/arm64e_apple_tvos.rs b/compiler/rustc_target/src/spec/targets/arm64e_apple_tvos.rs index 3cd120567a33f..984b6d6060294 100644 --- a/compiler/rustc_target/src/spec/targets/arm64e_apple_tvos.rs +++ b/compiler/rustc_target/src/spec/targets/arm64e_apple_tvos.rs @@ -1,4 +1,4 @@ -use crate::spec::base::apple::{Arch, TargetEnv, base}; +use crate::spec::base::apple::{Arch, TargetEnv, arm64e_features, base}; use crate::spec::{Os, Target, TargetMetadata, TargetOptions}; pub(crate) fn target() -> Target { @@ -16,7 +16,7 @@ pub(crate) fn target() -> Target { .into(), arch, options: TargetOptions { - features: "+neon,+apple-a12,+v8.3a,+paca,+pacg".into(), + features: arm64e_features("+neon,+apple-a12"), max_atomic_width: Some(128), ..opts }, diff --git a/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs b/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs index b1000c2c1c7eb..170138d575d57 100644 --- a/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs +++ b/compiler/rustc_target/src/spec/targets/arm64e_apple_visionos.rs @@ -1,4 +1,4 @@ -use crate::spec::base::apple::{Arch, TargetEnv, base}; +use crate::spec::base::apple::{Arch, TargetEnv, arm64e_features, base}; use crate::spec::{Os, SanitizerSet, Target, TargetMetadata, TargetOptions}; pub(crate) fn target() -> Target { @@ -16,7 +16,7 @@ pub(crate) fn target() -> Target { .into(), arch, options: TargetOptions { - features: "+neon,+apple-a12,+v8.3a,+paca,+pacg".into(), + features: arm64e_features("+neon,+apple-a12"), max_atomic_width: Some(128), supported_sanitizers: SanitizerSet::ADDRESS | SanitizerSet::THREAD, ..opts diff --git a/src/doc/unstable-book/src/compiler-flags/branch-protection.md b/src/doc/unstable-book/src/compiler-flags/branch-protection.md index c15567dcac2ee..265b74dc72527 100644 --- a/src/doc/unstable-book/src/compiler-flags/branch-protection.md +++ b/src/doc/unstable-book/src/compiler-flags/branch-protection.md @@ -19,6 +19,11 @@ It takes some combination of the following values, separated by a `,`. For example, `-Z branch-protection=bti,pac-ret,leaf` is valid, but `-Z branch-protection=bti,leaf,pac-ret` is not. -Rust's standard library does not ship with BTI or pointer authentication enabled by default. +On arm64e Apple targets, return-address signing is already enabled by default through +`ptrauth-returns`. Those targets still allow `bti` and `gcs`, but reject any +`-Z branch-protection` mode that includes `pac-ret`. + +Outside of arm64e Apple targets, Rust's standard library does not ship with BTI or pointer +authentication enabled by default. In Cargo projects the standard library can be recompiled with pointer authentication using the nightly [build-std](../../cargo/reference/unstable.html#build-std) feature. diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth.rs b/tests/codegen-llvm/arm64e-apple-ptrauth.rs new file mode 100644 index 0000000000000..3b29ed14facd8 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth.rs @@ -0,0 +1,29 @@ +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; +use minicore::*; + +// CHECK: @test(){{.*}} [[ATTR:#[0-9]+]] { +#[no_mangle] +pub fn test() {} + +// CHECK: attributes [[ATTR]] = {{.*}} "ptrauth-auth-traps" "ptrauth-calls" "ptrauth-indirect-gotos" "ptrauth-returns" +// CHECK-SAME: "target-features"="{{.*}}+pauth{{.*}}" +// CHECK-NOT: "sign-return-address" +// CHECK-NOT: "sign-return-address-key" +// CHECK: !{{[0-9]+}} = !{i32 6, !"ptrauth.abi-version", ![[OUTER:[0-9]+]]} +// CHECK: ![[OUTER]] = !{![[PAYLOAD:[0-9]+]]} +// CHECK: ![[PAYLOAD]] = !{i32 0, i1 false} diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr new file mode 100644 index 0000000000000..1f1910f6ddb62 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,4 @@ +error: `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs new file mode 100644 index 0000000000000..3dacaa3ee83a3 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs @@ -0,0 +1,40 @@ +//@ revisions: DARWIN_PACRET DARWIN_PAUTHLR_LEAF IOS_PACRET IOS_PAUTHLR_LEAF TVOS_PACRET TVOS_PAUTHLR_LEAF VISIONOS_PACRET VISIONOS_PAUTHLR_LEAF +//@ [DARWIN_PACRET] compile-flags: --target=arm64e-apple-darwin -Zbranch-protection=pac-ret +//@ [DARWIN_PACRET] check-fail +//@ [DARWIN_PACRET] needs-llvm-components: aarch64 +//@ [DARWIN_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-darwin -Zbranch-protection=pac-ret,pc,leaf +//@ [DARWIN_PAUTHLR_LEAF] check-fail +//@ [DARWIN_PAUTHLR_LEAF] needs-llvm-components: aarch64 +//@ [IOS_PACRET] compile-flags: --target=arm64e-apple-ios -Zbranch-protection=pac-ret +//@ [IOS_PACRET] check-fail +//@ [IOS_PACRET] needs-llvm-components: aarch64 +//@ [IOS_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-ios -Zbranch-protection=pac-ret,pc,leaf +//@ [IOS_PAUTHLR_LEAF] check-fail +//@ [IOS_PAUTHLR_LEAF] needs-llvm-components: aarch64 +//@ [TVOS_PACRET] compile-flags: --target=arm64e-apple-tvos -Zbranch-protection=pac-ret +//@ [TVOS_PACRET] check-fail +//@ [TVOS_PACRET] needs-llvm-components: aarch64 +//@ [TVOS_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-tvos -Zbranch-protection=pac-ret,pc,leaf +//@ [TVOS_PAUTHLR_LEAF] check-fail +//@ [TVOS_PAUTHLR_LEAF] needs-llvm-components: aarch64 +//@ [VISIONOS_PACRET] compile-flags: --target=arm64e-apple-visionos -Zbranch-protection=pac-ret +//@ [VISIONOS_PACRET] check-fail +//@ [VISIONOS_PACRET] needs-llvm-components: aarch64 +//@ [VISIONOS_PAUTHLR_LEAF] compile-flags: --target=arm64e-apple-visionos -Zbranch-protection=pac-ret,pc,leaf +//@ [VISIONOS_PAUTHLR_LEAF] check-fail +//@ [VISIONOS_PAUTHLR_LEAF] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +//~? ERROR `-Zbranch-protection` with `pac-ret` is incompatible with arm64e Apple targets because they enable `ptrauth-returns` by default From c080e6a078e1cd62da853566c4db648a8004efdd Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:29:54 -0700 Subject: [PATCH 04/13] arm64e: reject disabling default Apple ptrauth --- compiler/rustc_codegen_llvm/src/attributes.rs | 4 ++- compiler/rustc_codegen_llvm/src/context.rs | 2 +- compiler/rustc_codegen_ssa/src/errors.rs | 6 ++++ .../rustc_codegen_ssa/src/target_features.rs | 12 +++++++ compiler/rustc_span/src/symbol.rs | 2 ++ tests/codegen-llvm/arm64e-apple-ptrauth.rs | 11 ++++-- ...e-apple-ptrauth-disable.DARWIN_BOTH.stderr | 4 +++ ...e-apple-ptrauth-disable.DARWIN_PACA.stderr | 4 +++ ...e-apple-ptrauth-disable.DARWIN_PACG.stderr | 4 +++ ...m64e-apple-ptrauth-disable.IOS_BOTH.stderr | 4 +++ ...64e-apple-ptrauth-disable.TVOS_BOTH.stderr | 4 +++ ...apple-ptrauth-disable.VISIONOS_BOTH.stderr | 4 +++ ...et-feature-arm64e-apple-ptrauth-disable.rs | 34 +++++++++++++++++++ 13 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr create mode 100644 tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr create mode 100644 tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr create mode 100644 tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr create mode 100644 tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr create mode 100644 tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr create mode 100644 tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 1bf0ba9b7e173..a4b4ef9dfd287 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -621,6 +621,8 @@ fn wasm_import_module(tcx: TyCtxt<'_>, id: DefId) -> Option<&String> { tcx.wasm_import_module_map(id.krate).get(&id) } -fn has_default_arm64e_ptrauth(sess: &Session) -> bool { +pub(crate) fn has_default_arm64e_ptrauth(sess: &Session) -> bool { sess.target.is_apple_arm64e() + && sess.unstable_target_features.contains(&sym::paca) + && sess.unstable_target_features.contains(&sym::pacg) } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 9910ff1f6be7d..d7fe486f67094 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -426,7 +426,7 @@ pub(crate) unsafe fn create_module<'ll>( } } - if sess.target.is_apple_arm64e() { + if attributes::has_default_arm64e_ptrauth(sess) { let ptrauth_abi_version = unsafe { llvm::LLVMMDNodeInContext2( llcx, diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index f1112510af0f0..71e2292181b21 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1211,6 +1211,12 @@ pub(crate) struct ForbiddenCTargetFeature<'a> { pub reason: &'a str, } +#[derive(Diagnostic)] +#[diag( + "`-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default" +)] +pub(crate) struct Arm64eApplePtrauthDisableForbidden; + pub(crate) struct TargetFeatureDisableOrEnable<'a> { pub features: &'a [&'a str], pub span: Option, diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 24f731c01996d..b51930b8b0ba0 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -254,6 +254,7 @@ pub fn cfg_target_feature<'a, const N: usize>( .collect(); let mut enabled_disabled_features = FxHashMap::default(); + let mut arm64e_apple_ptrauth_disable_forbidden = false; // Add enabled and remove disabled features. parse_rust_feature_list( @@ -264,6 +265,17 @@ pub fn cfg_target_feature<'a, const N: usize>( sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature }); }, |base_feature, new_features, enable| { + if !enable + && sess.target.is_apple_arm64e() + && matches!(base_feature, "paca" | "pacg") + { + if !arm64e_apple_ptrauth_disable_forbidden { + sess.dcx().emit_err(errors::Arm64eApplePtrauthDisableForbidden); + arm64e_apple_ptrauth_disable_forbidden = true; + } + return; + } + // Iteration order is irrelevant since this only influences an `FxHashMap`. #[allow(rustc::potential_query_instability)] enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable))); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e7574eaed5b27..a41e06bd9f32b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1451,6 +1451,8 @@ symbols! { owned_box, packed, packed_bundled_libs, + paca, + pacg, panic, panic_2015, panic_2021, diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth.rs b/tests/codegen-llvm/arm64e-apple-ptrauth.rs index 3b29ed14facd8..60e25b4cd691e 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth.rs @@ -1,7 +1,11 @@ //@ add-minicore -//@ revisions: DARWIN IOS TVOS VISIONOS +//@ revisions: DARWIN DARWIN_BTI DARWIN_GCS IOS TVOS VISIONOS //@ [DARWIN] compile-flags: --target arm64e-apple-darwin //@ [DARWIN] needs-llvm-components: aarch64 +//@ [DARWIN_BTI] compile-flags: --target arm64e-apple-darwin -Zbranch-protection=bti +//@ [DARWIN_BTI] needs-llvm-components: aarch64 +//@ [DARWIN_GCS] compile-flags: --target arm64e-apple-darwin -Zbranch-protection=gcs +//@ [DARWIN_GCS] needs-llvm-components: aarch64 //@ [IOS] compile-flags: --target arm64e-apple-ios //@ [IOS] needs-llvm-components: aarch64 //@ [TVOS] compile-flags: --target arm64e-apple-tvos @@ -22,8 +26,9 @@ pub fn test() {} // CHECK: attributes [[ATTR]] = {{.*}} "ptrauth-auth-traps" "ptrauth-calls" "ptrauth-indirect-gotos" "ptrauth-returns" // CHECK-SAME: "target-features"="{{.*}}+pauth{{.*}}" -// CHECK-NOT: "sign-return-address" -// CHECK-NOT: "sign-return-address-key" +// DARWIN_BTI: !{{[0-9]+}} = !{i32 8, !"branch-target-enforcement", i32 1} +// DARWIN_GCS: !{{[0-9]+}} = !{i32 8, !"guarded-control-stack", i32 1} + // CHECK: !{{[0-9]+}} = !{i32 6, !"ptrauth.abi-version", ![[OUTER:[0-9]+]]} // CHECK: ![[OUTER]] = !{![[PAYLOAD:[0-9]+]]} // CHECK: ![[PAYLOAD]] = !{i32 0, i1 false} diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr new file mode 100644 index 0000000000000..b4ab796c1eb59 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr new file mode 100644 index 0000000000000..b4ab796c1eb59 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr new file mode 100644 index 0000000000000..b4ab796c1eb59 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr new file mode 100644 index 0000000000000..b4ab796c1eb59 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr new file mode 100644 index 0000000000000..b4ab796c1eb59 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr new file mode 100644 index 0000000000000..b4ab796c1eb59 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr @@ -0,0 +1,4 @@ +error: `-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default + +error: aborting due to 1 previous error + diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs new file mode 100644 index 0000000000000..2cc3262be6c0b --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs @@ -0,0 +1,34 @@ +//@ revisions: DARWIN_PACA DARWIN_PACG DARWIN_BOTH IOS_BOTH TVOS_BOTH VISIONOS_BOTH +//@ [DARWIN_PACA] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-paca +//@ [DARWIN_PACA] check-fail +//@ [DARWIN_PACA] needs-llvm-components: aarch64 +//@ [DARWIN_PACG] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-pacg +//@ [DARWIN_PACG] check-fail +//@ [DARWIN_PACG] needs-llvm-components: aarch64 +//@ [DARWIN_BOTH] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-paca,-pacg +//@ [DARWIN_BOTH] check-fail +//@ [DARWIN_BOTH] needs-llvm-components: aarch64 +//@ [IOS_BOTH] compile-flags: --target=arm64e-apple-ios -Ctarget-feature=-paca,-pacg +//@ [IOS_BOTH] check-fail +//@ [IOS_BOTH] needs-llvm-components: aarch64 +//@ [TVOS_BOTH] compile-flags: --target=arm64e-apple-tvos -Ctarget-feature=-paca,-pacg +//@ [TVOS_BOTH] check-fail +//@ [TVOS_BOTH] needs-llvm-components: aarch64 +//@ [VISIONOS_BOTH] compile-flags: --target=arm64e-apple-visionos -Ctarget-feature=-paca,-pacg +//@ [VISIONOS_BOTH] check-fail +//@ [VISIONOS_BOTH] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +//~? ERROR `-Ctarget-feature` cannot disable `paca` or `pacg` on arm64e Apple targets because they enable ptrauth by default From 6d84c4e5b289c562df9b302bfc95267f0b176290 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:23:41 -0700 Subject: [PATCH 05/13] ci: add fork arm64e validation workflow --- .github/workflows/fork-arm64e.yml | 148 ++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 .github/workflows/fork-arm64e.yml diff --git a/.github/workflows/fork-arm64e.yml b/.github/workflows/fork-arm64e.yml new file mode 100644 index 0000000000000..8b70445a3b3c8 --- /dev/null +++ b/.github/workflows/fork-arm64e.yml @@ -0,0 +1,148 @@ +name: Fork arm64e validation + +on: + workflow_dispatch: + push: + branches: + - codex/arm64e-darwin-ptrauth-spike + pull_request: + branches: + - main + +permissions: + contents: read + +concurrency: + group: fork-arm64e-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + arm64e: + if: github.repository == 'cypherair/rust' + name: arm64e targeted validation + runs-on: macos-15 + timeout-minutes: 240 + defaults: + run: + shell: bash + steps: + - name: Checkout source + uses: actions/checkout@v5 + with: + submodules: recursive + + - name: Show environment + run: | + sw_vers + uname -a + python3 --version + clang --version + rustc -vV || true + + - name: Check arm64e codegen crates + id: check_codegen + run: | + python3 x.py check compiler/rustc_codegen_ssa compiler/rustc_codegen_llvm --stage 1 + + - name: Run targeted arm64e tests + id: targeted_tests + run: | + python3 x.py test --stage 1 --force-rerun \ + tests/codegen-llvm/arm64e-apple-ptrauth.rs \ + tests/assembly-llvm/targets/targets-macho.rs \ + tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs \ + tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs + + - name: Build stage1 arm64e std toolchain + id: build_stage1 + run: | + python3 x.py build compiler/rustc library/std --stage 1 --target arm64e-apple-darwin + + - name: Diagnose arm64e function pointer codegen + id: fnptr_diag + continue-on-error: true + run: | + HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e_fnptr.rs <<'EOF' + #![crate_type = "lib"] + + #[no_mangle] + pub extern "C" fn call(f: extern "C" fn() -> i32) -> i32 { f() } + EOF + + "$STAGE1" /tmp/arm64e_fnptr.rs --target arm64e-apple-darwin -O --emit=llvm-ir,asm -o /tmp/arm64e_fnptr + + echo "LLVM IR excerpt:" + sed -n '1,24p' /tmp/arm64e_fnptr.ll + echo + echo "Assembly excerpt:" + sed -n '1,16p' /tmp/arm64e_fnptr.s + + grep -n '"ptrauth"' /tmp/arm64e_fnptr.ll + grep -nE 'braaz|blraaz' /tmp/arm64e_fnptr.s + + - name: Diagnose arm64e TLS runtime + id: tls_diag + continue-on-error: true + run: | + HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e_tls_smoke.rs <<'EOF' + use std::cell::RefCell; + + fn main() { + thread_local! { + static S: RefCell = RefCell::default(); + } + + S.with(|x| *x.borrow_mut() = "pika pika".to_string()); + S.with(|x| println!("{}", x.borrow())); + } + EOF + + "$STAGE1" /tmp/arm64e_tls_smoke.rs --target arm64e-apple-darwin -O -o /tmp/arm64e_tls_smoke + file /tmp/arm64e_tls_smoke + + python3 - <<'PY' + import subprocess + + proc = subprocess.run( + ["/tmp/arm64e_tls_smoke"], + capture_output=True, + text=True, + timeout=30, + check=True, + ) + print(proc.stdout, end="") + if proc.stdout.strip() != "pika pika": + raise SystemExit(f"unexpected stdout: {proc.stdout!r}") + PY + + - name: Upload arm64e diagnostics + if: always() + uses: actions/upload-artifact@v4 + with: + name: arm64e-diagnostics + if-no-files-found: ignore + path: | + /tmp/arm64e_fnptr.ll + /tmp/arm64e_fnptr.s + /tmp/arm64e_tls_smoke.rs + + - name: Summarize arm64e validation + if: always() + run: | + { + echo "## arm64e validation" + echo + echo "- check_codegen: ${{ steps.check_codegen.outcome }}" + echo "- targeted_tests: ${{ steps.targeted_tests.outcome }}" + echo "- build_stage1: ${{ steps.build_stage1.outcome }}" + echo "- fnptr_diag (non-blocking): ${{ steps.fnptr_diag.outcome }}" + echo "- tls_diag (non-blocking): ${{ steps.tls_diag.outcome }}" + echo + echo "The two diagnostic steps are intentionally non-blocking while the arm64e indirect-call and TLS runtime issues are still being investigated." + } >> "$GITHUB_STEP_SUMMARY" From 3275be04627cc3b1fe1773905290a26366248da8 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:32:12 -0700 Subject: [PATCH 06/13] arm64e: authenticate indirect function calls --- compiler/rustc_codegen_llvm/src/back/write.rs | 2 +- compiler/rustc_codegen_llvm/src/builder.rs | 36 +++++++++++-- compiler/rustc_codegen_llvm/src/context.rs | 18 +++++-- compiler/rustc_codegen_llvm/src/lib.rs | 8 ++- .../arm64e-apple-ptrauth-calls.rs | 44 ++++++++++++++++ .../arm64e-apple-ptrauth-calls.rs | 50 +++++++++++++++++++ 6 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs create mode 100644 tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 515b571a9f4b3..69112cf732a75 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -743,7 +743,7 @@ pub(crate) unsafe fn llvm_optimize( if cgcx.target_is_like_gpu && config.offload.contains(&config::Offload::Device) { let cx = - SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size); + SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size, false); for func in cx.get_functions() { let offload_kernel = "offload-kernel"; if attributes::has_string_attr(func, offload_kernel) { diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 056a0763087a2..f75b93ee37c13 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -68,10 +68,14 @@ impl<'a, 'll> SBuilder<'a, 'll> { let args = self.check_call("call", llty, llfn, args); let funclet_bundle = funclet.map(|funclet| funclet.bundle()); - let mut bundles: SmallVec<[_; 2]> = SmallVec::new(); + let mut bundles: SmallVec<[_; 3]> = SmallVec::new(); if let Some(funclet_bundle) = funclet_bundle { bundles.push(funclet_bundle); } + let ptrauth_bundle = self.ptrauth_operand_bundle(llfn); + if let Some(ptrauth_bundle) = ptrauth_bundle.as_ref().map(|b| b.as_ref()) { + bundles.push(ptrauth_bundle); + } let call = unsafe { llvm::LLVMBuildCallWithOperandBundles( @@ -187,6 +191,18 @@ impl<'a, 'll, CX: Borrow>> GenericBuilder<'a, 'll, CX> { load } } + + fn ptrauth_operand_bundle(&mut self, llfn: &'ll Value) -> Option> { + let is_indirect_call = unsafe { llvm::LLVMRustIsNonGVFunctionPointerTy(llfn) }; + if self.cx.is_apple_arm64e() && is_indirect_call { + Some(llvm::OperandBundleBox::new( + "ptrauth", + &[self.cx.get_const_i32(0), self.cx.get_const_i64(0)], + )) + } else { + None + } + } } /// Empty string, to be used where LLVM expects an instruction name, indicating @@ -415,10 +431,14 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let args = self.check_call("invoke", llty, llfn, args); let funclet_bundle = funclet.map(|funclet| funclet.bundle()); - let mut bundles: SmallVec<[_; 2]> = SmallVec::new(); + let mut bundles: SmallVec<[_; 3]> = SmallVec::new(); if let Some(funclet_bundle) = funclet_bundle { bundles.push(funclet_bundle); } + let ptrauth_bundle = self.ptrauth_operand_bundle(llfn); + if let Some(ptrauth_bundle) = ptrauth_bundle.as_ref().map(|b| b.as_ref()) { + bundles.push(ptrauth_bundle); + } // Emit CFI pointer type membership test self.cfi_type_test(fn_attrs, fn_abi, instance, llfn); @@ -1388,10 +1408,14 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let args = self.check_call("call", llty, llfn, args); let funclet_bundle = funclet.map(|funclet| funclet.bundle()); - let mut bundles: SmallVec<[_; 2]> = SmallVec::new(); + let mut bundles: SmallVec<[_; 3]> = SmallVec::new(); if let Some(funclet_bundle) = funclet_bundle { bundles.push(funclet_bundle); } + let ptrauth_bundle = self.ptrauth_operand_bundle(llfn); + if let Some(ptrauth_bundle) = ptrauth_bundle.as_ref().map(|b| b.as_ref()) { + bundles.push(ptrauth_bundle); + } // Emit CFI pointer type membership test self.cfi_type_test(caller_attrs, fn_abi, callee_instance, llfn); @@ -1835,10 +1859,14 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { let args = self.check_call("callbr", llty, llfn, args); let funclet_bundle = funclet.map(|funclet| funclet.bundle()); - let mut bundles: SmallVec<[_; 2]> = SmallVec::new(); + let mut bundles: SmallVec<[_; 3]> = SmallVec::new(); if let Some(funclet_bundle) = funclet_bundle { bundles.push(funclet_bundle); } + let ptrauth_bundle = self.ptrauth_operand_bundle(llfn); + if let Some(ptrauth_bundle) = ptrauth_bundle.as_ref().map(|b| b.as_ref()) { + bundles.push(ptrauth_bundle); + } // Emit CFI pointer type membership test self.cfi_type_test(fn_attrs, fn_abi, instance, llfn); diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index d7fe486f67094..dd9d652c2a82a 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -48,6 +48,7 @@ pub(crate) struct SCx<'ll> { pub llmod: &'ll llvm::Module, pub llcx: &'ll llvm::Context, pub isize_ty: &'ll Type, + pub is_apple_arm64e: bool, } impl<'ll> Borrow> for FullCx<'ll, '_> { @@ -185,7 +186,8 @@ pub(crate) unsafe fn create_module<'ll>( let mod_name = SmallCStr::new(mod_name); let llmod = unsafe { llvm::LLVMModuleCreateWithNameInContext(mod_name.as_ptr(), llcx) }; - let cx = SimpleCx::new(llmod, llcx, tcx.data_layout.pointer_size()); + let cx = + SimpleCx::new(llmod, llcx, tcx.data_layout.pointer_size(), attributes::has_default_arm64e_ptrauth(sess)); let mut target_data_layout = sess.target.data_layout.to_string(); let llvm_version = llvm_util::get_version(); @@ -654,7 +656,12 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { GenericCx( FullCx { tcx, - scx: SimpleCx::new(llmod, llcx, tcx.data_layout.pointer_size()), + scx: SimpleCx::new( + llmod, + llcx, + tcx.data_layout.pointer_size(), + attributes::has_default_arm64e_ptrauth(tcx.sess), + ), use_dll_storage_attrs, tls_model, codegen_unit, @@ -782,13 +789,18 @@ impl<'ll> SimpleCx<'ll> { llmod: &'ll llvm::Module, llcx: &'ll llvm::Context, pointer_size: Size, + is_apple_arm64e: bool, ) -> Self { let isize_ty = llvm::LLVMIntTypeInContext(llcx, pointer_size.bits() as c_uint); - Self(SCx { llmod, llcx, isize_ty }, PhantomData) + Self(SCx { llmod, llcx, isize_ty, is_apple_arm64e }, PhantomData) } } impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { + pub(crate) fn is_apple_arm64e(&self) -> bool { + (**self).borrow().is_apple_arm64e + } + pub(crate) fn get_metadata_value(&self, metadata: &'ll Metadata) -> &'ll Value { llvm::LLVMMetadataAsValue(self.llcx(), metadata) } diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 65c70c754918d..f9d6a126eff73 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -100,8 +100,12 @@ impl ExtraBackendMethods for LlvmCodegenBackend { methods: &[AllocatorMethod], ) -> ModuleLlvm { let module_llvm = ModuleLlvm::new_metadata(tcx, module_name); - let cx = - SimpleCx::new(module_llvm.llmod(), &module_llvm.llcx, tcx.data_layout.pointer_size()); + let cx = SimpleCx::new( + module_llvm.llmod(), + &module_llvm.llcx, + tcx.data_layout.pointer_size(), + attributes::has_default_arm64e_ptrauth(tcx.sess), + ); unsafe { allocator::codegen(tcx, cx, module_name, methods); } diff --git a/tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs b/tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs new file mode 100644 index 0000000000000..9839aa2b1a673 --- /dev/null +++ b/tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs @@ -0,0 +1,44 @@ +//@ add-minicore +//@ assembly-output: emit-asm +//@ compile-flags: --target arm64e-apple-darwin -Copt-level=2 +//@ needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[inline(never)] +#[no_mangle] +pub extern "C" fn target(x: i32) -> i32 { + x +} + +#[no_mangle] +pub extern "C" fn direct(x: i32) -> i32 { + target(x) +} + +#[no_mangle] +pub extern "C" fn indirect_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + f(x) +} + +#[no_mangle] +pub extern "C" fn indirect_non_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + match f(x) { + 0 => target(x), + y => y, + } +} + +// CHECK-LABEL: _direct: +// CHECK-NOT: braaz +// CHECK-NOT: blraaz + +// CHECK-LABEL: _indirect_tail: +// CHECK: braaz + +// CHECK-LABEL: _indirect_non_tail: +// CHECK: blraaz diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs new file mode 100644 index 0000000000000..9702fad96f1c0 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs @@ -0,0 +1,50 @@ +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=2 +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=2 +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=2 +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=2 +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[inline(never)] +#[no_mangle] +pub extern "C" fn target(x: i32) -> i32 { + x +} + +#[no_mangle] +pub extern "C" fn direct(x: i32) -> i32 { + target(x) +} + +#[no_mangle] +pub extern "C" fn indirect_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + f(x) +} + +#[no_mangle] +pub extern "C" fn indirect_non_tail(f: extern "C" fn(i32) -> i32, x: i32) -> i32 { + match f(x) { + 0 => target(x), + y => y, + } +} + +// CHECK-LABEL: define{{.*}} @direct( +// CHECK-NOT: [ "ptrauth"(i32 0, i64 0) ] +// CHECK: ret i32 + +// CHECK-LABEL: define{{.*}} @indirect_tail( +// CHECK: {{(tail )?}}call{{.*}} {{%.*}}(i32 {{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ]{{$}} + +// CHECK-LABEL: define{{.*}} @indirect_non_tail( +// CHECK: {{(tail )?}}call{{.*}} {{%.*}}(i32 {{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ]{{$}} From 8b08511d9194e231b1c1ba4aa534f6db1b8c18bc Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 17:31:08 -0700 Subject: [PATCH 07/13] arm64e: restore stage1 std build --- .github/workflows/fork-arm64e.yml | 3 ++ compiler/rustc_codegen_llvm/src/back/write.rs | 6 +++ compiler/rustc_codegen_llvm/src/builder.rs | 11 ++-- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + compiler/rustc_codegen_llvm/src/llvm/mod.rs | 6 +++ .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 50 ++++++++++++++++--- 6 files changed, 62 insertions(+), 15 deletions(-) diff --git a/.github/workflows/fork-arm64e.yml b/.github/workflows/fork-arm64e.yml index 8b70445a3b3c8..50cf6dbab20cb 100644 --- a/.github/workflows/fork-arm64e.yml +++ b/.github/workflows/fork-arm64e.yml @@ -29,6 +29,7 @@ jobs: - name: Checkout source uses: actions/checkout@v5 with: + fetch-depth: 2 submodules: recursive - name: Show environment @@ -49,6 +50,8 @@ jobs: run: | python3 x.py test --stage 1 --force-rerun \ tests/codegen-llvm/arm64e-apple-ptrauth.rs \ + tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs \ + tests/assembly-llvm/arm64e-apple-ptrauth-calls.rs \ tests/assembly-llvm/targets/targets-macho.rs \ tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs \ tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 69112cf732a75..39422235ac8a7 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -70,6 +70,12 @@ fn write_output_file<'ll>( } else { std::ptr::null() }; + + // LLVM may devirtualize indirect arm64e calls into direct calls after we + // attached ptrauth operand bundles. Strip bundles from call shapes LLVM + // cannot lower just before object/assembly emission. + llvm::strip_unsupported_ptrauth_bundles(m); + let result = unsafe { let pm = llvm::LLVMCreatePassManager(); llvm::LLVMAddAnalysisPasses(target, pm); diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index f75b93ee37c13..090f3148a7ec0 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -435,10 +435,6 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { if let Some(funclet_bundle) = funclet_bundle { bundles.push(funclet_bundle); } - let ptrauth_bundle = self.ptrauth_operand_bundle(llfn); - if let Some(ptrauth_bundle) = ptrauth_bundle.as_ref().map(|b| b.as_ref()) { - bundles.push(ptrauth_bundle); - } // Emit CFI pointer type membership test self.cfi_type_test(fn_attrs, fn_abi, instance, llfn); @@ -1863,10 +1859,6 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { if let Some(funclet_bundle) = funclet_bundle { bundles.push(funclet_bundle); } - let ptrauth_bundle = self.ptrauth_operand_bundle(llfn); - if let Some(ptrauth_bundle) = ptrauth_bundle.as_ref().map(|b| b.as_ref()) { - bundles.push(ptrauth_bundle); - } // Emit CFI pointer type membership test self.cfi_type_test(fn_attrs, fn_abi, instance, llfn); @@ -1878,6 +1870,9 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { } let callbr = unsafe { + // LLVM cannot lower arm64e ptrauth operand bundles on callbr yet. + // Keep the ordinary call/invoke arm64e coverage, but leave asm-goto + // style control flow as a follow-up. llvm::LLVMBuildCallBr( self.llbuilder, llty, diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index e9acdd662f6f7..24fe7e8504c6c 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2502,6 +2502,7 @@ unsafe extern "C" { pub(crate) fn LLVMRustBufferFree(p: &'static mut Buffer); pub(crate) fn LLVMRustModuleCost(M: &Module) -> u64; pub(crate) fn LLVMRustModuleInstructionStats(M: &Module) -> u64; + pub(crate) fn LLVMRustStripUnsupportedPtrauthBundles(M: &Module); pub(crate) fn LLVMRustModuleSerialize(M: &Module, is_thin: bool) -> &'static mut Buffer; pub(crate) fn LLVMRustCreateThinLTOData( diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index e7d2f323b5739..9d57d291de363 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -462,6 +462,12 @@ pub(crate) fn add_module_flag_metadata( } } +pub(crate) fn strip_unsupported_ptrauth_bundles(module: &Module) { + unsafe { + LLVMRustStripUnsupportedPtrauthBundles(module); + } +} + pub(crate) fn set_dllimport_storage_class<'ll>(v: &'ll Value) { unsafe { LLVMSetDLLStorageClass(v, DLLStorageClass::DllImport); diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index f886c030dbf75..e81a0d169b547 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1590,6 +1590,40 @@ extern "C" uint64_t LLVMRustModuleInstructionStats(LLVMModuleRef M) { return unwrap(M)->getInstructionCount(); } +extern "C" void LLVMRustStripUnsupportedPtrauthBundles(LLVMModuleRef M) { + Module *Mod = unwrap(M); + LLVMContext &Ctx = Mod->getContext(); + const uint32_t PtrauthID = Ctx.getOperandBundleTagID("ptrauth"); + SmallVector ToRewrite; + + for (Function &F : *Mod) { + for (BasicBlock &BB : F) { + for (Instruction &I : BB) { + auto *CB = dyn_cast(&I); + if (!CB || !CB->countOperandBundlesOfType(PtrauthID)) + continue; + + bool ShouldStrip = isa(CB); + if (!ShouldStrip) { + Value *CalledOperand = CB->getCalledOperand()->stripPointerCasts(); + if (auto *GV = dyn_cast(CalledOperand)) + ShouldStrip = GV->getValueType()->isFunctionTy(); + } + + if (ShouldStrip) + ToRewrite.push_back(CB); + } + } + } + + for (CallBase *CB : ToRewrite) { + CallBase *Replacement = + CallBase::removeOperandBundle(CB, PtrauthID, CB->getIterator()); + CB->replaceAllUsesWith(Replacement); + CB->eraseFromParent(); + } +} + // Transfers ownership of DiagnosticHandler unique_ptr to the caller. extern "C" DiagnosticHandler * LLVMRustContextGetDiagnosticHandler(LLVMContextRef C) { @@ -1782,14 +1816,16 @@ extern "C" int32_t LLVMRustGetElementTypeArgIndex(LLVMValueRef CallSite) { } extern "C" bool LLVMRustIsNonGVFunctionPointerTy(LLVMValueRef V) { - if (unwrap(V)->getType()->isPointerTy()) { - if (auto *GV = dyn_cast(unwrap(V))) { - if (GV->getValueType()->isFunctionTy()) - return false; - } - return true; + auto *Stripped = unwrap(V)->stripPointerCasts(); + if (!Stripped->getType()->isPointerTy()) + return false; + + if (auto *GV = dyn_cast(Stripped)) { + if (GV->getValueType()->isFunctionTy()) + return false; } - return false; + + return true; } extern "C" LLVMValueRef LLVMRustStripPointerCasts(LLVMValueRef V) { From 6537b4bf47f3427e11ee07f6f52508ca62ddc73f Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:32:59 -0700 Subject: [PATCH 08/13] arm64e: authenticate function pointers used as data --- compiler/rustc_codegen_llvm/src/builder.rs | 4 ++ compiler/rustc_codegen_llvm/src/common.rs | 4 +- compiler/rustc_codegen_llvm/src/consts.rs | 16 ++++++++ compiler/rustc_codegen_llvm/src/context.rs | 4 ++ compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 6 +++ compiler/rustc_codegen_ssa/src/base.rs | 2 +- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 4 +- compiler/rustc_codegen_ssa/src/traits/misc.rs | 3 ++ .../arm64e-apple-ptrauth-fnptr-data.rs | 38 +++++++++++++++++++ .../arm64e-apple-ptrauth-invoke.rs | 30 +++++++++++++++ 10 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs create mode 100644 tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 090f3148a7ec0..546b097cc7e6e 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -435,6 +435,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { if let Some(funclet_bundle) = funclet_bundle { bundles.push(funclet_bundle); } + let ptrauth_bundle = self.ptrauth_operand_bundle(llfn); + if let Some(ptrauth_bundle) = ptrauth_bundle.as_ref().map(|b| b.as_ref()) { + bundles.push(ptrauth_bundle); + } // Emit CFI pointer type membership test self.cfi_type_test(fn_attrs, fn_abi, instance, llfn); diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index dadf8e9e7d5fa..8463f1e9cd636 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -319,7 +319,9 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { value } } - GlobalAlloc::Function { instance, .. } => self.get_fn_addr(instance), + GlobalAlloc::Function { instance, .. } => { + self.arm64e_fn_ptr_for_data(self.get_fn_addr(instance)) + } GlobalAlloc::VTable(ty, dyn_ty) => { let alloc = self .tcx diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 3514fb145612a..75367fc864150 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -229,6 +229,22 @@ impl<'ll> CodegenCx<'ll, '_> { unsafe { llvm::LLVMConstPointerCast(val, ty) } } + pub(crate) fn arm64e_fn_ptr_for_data(&self, fn_addr: &'ll Value) -> &'ll Value { + if !self.is_apple_arm64e() { + return fn_addr; + } + + let fn_addr = self.const_pointercast(fn_addr, self.type_ptr()); + unsafe { + llvm::LLVMConstantPtrAuth( + fn_addr, + self.get_const_i32(0), + self.get_const_i64(0), + self.get_const_i64(0), + ) + } + } + /// Create a global variable. /// /// The returned global variable is a pointer in the default address space for globals. diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index dd9d652c2a82a..24f551dc97412 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -881,6 +881,10 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { get_fn(self, instance) } + fn get_fn_addr_for_data(&self, instance: Instance<'tcx>) -> &'ll Value { + self.arm64e_fn_ptr_for_data(get_fn(self, instance)) + } + fn eh_personality(&self) -> &'ll Value { // The exception handling personality function. // diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 24fe7e8504c6c..019194b4d1505 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1023,6 +1023,12 @@ unsafe extern "C" { ConstantVals: *const &'a Value, Count: c_uint, ) -> &'a Value; + pub(crate) fn LLVMConstantPtrAuth<'a>( + Ptr: &'a Value, + Key: &'a Value, + Disc: &'a Value, + AddrDisc: &'a Value, + ) -> &'a Value; pub(crate) fn LLVMConstVector(ScalarConstantVals: *const &Value, Size: c_uint) -> &Value; // Constant expressions diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 50c439593c306..c46903f6f8963 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -492,7 +492,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( return None; } - let main_llfn = cx.get_fn_addr(instance); + let main_llfn = cx.get_fn_addr_for_data(instance); let entry_fn = create_entry_fn::(cx, main_llfn, main_def_id, entry_type); return Some(entry_fn); diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index f9e4a6a352bac..10cdfbbe3a546 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -423,7 +423,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { args, ) .unwrap(); - OperandValue::Immediate(bx.get_fn_addr(instance)) + OperandValue::Immediate(bx.get_fn_addr_for_data(instance)) } _ => bug!("{} cannot be reified to a fn ptr", operand.layout.ty), } @@ -437,7 +437,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { args, ty::ClosureKind::FnOnce, ); - OperandValue::Immediate(bx.cx().get_fn_addr(instance)) + OperandValue::Immediate(bx.cx().get_fn_addr_for_data(instance)) } _ => bug!("{} cannot be cast to a fn ptr", operand.layout.ty), } diff --git a/compiler/rustc_codegen_ssa/src/traits/misc.rs b/compiler/rustc_codegen_ssa/src/traits/misc.rs index 6a0f889833492..0a06cdfe72bee 100644 --- a/compiler/rustc_codegen_ssa/src/traits/misc.rs +++ b/compiler/rustc_codegen_ssa/src/traits/misc.rs @@ -19,6 +19,9 @@ pub trait MiscCodegenMethods<'tcx>: BackendTypes { } fn get_fn(&self, instance: Instance<'tcx>) -> Self::Function; fn get_fn_addr(&self, instance: Instance<'tcx>) -> Self::Value; + fn get_fn_addr_for_data(&self, instance: Instance<'tcx>) -> Self::Value { + self.get_fn_addr(instance) + } fn eh_personality(&self) -> Self::Function; fn sess(&self) -> &Session; fn set_frame_pointer_type(&self, llfn: Self::Function); diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs new file mode 100644 index 0000000000000..d62de90d4fbc6 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs @@ -0,0 +1,38 @@ +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[unsafe(no_mangle)] +pub extern "C" fn invoke_main(main_fn: extern "C" fn(*const u8) -> i32, state: *const u8) -> i32 { + main_fn(state) +} + +#[unsafe(no_mangle)] +pub extern "C" fn sample_main(_: *const u8) -> i32 { + 7 +} + +#[unsafe(no_mangle)] +pub extern "C" fn call_sample() -> i32 { + let f = sample_main; + invoke_main(f, 0 as *const u8) +} + +// CHECK-LABEL: define{{.*}} i32 @invoke_main( +// CHECK: call i32 %{{.*}}(ptr %{{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ] + +// CHECK-LABEL: define{{.*}} i32 @call_sample( +// CHECK: call i32 @invoke_main(ptr ptrauth (ptr @sample_main, i32 0), ptr null) diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs new file mode 100644 index 0000000000000..f499f2d89420e --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs @@ -0,0 +1,30 @@ +//@ add-minicore +//@ revisions: DARWIN IOS TVOS VISIONOS +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Cpanic=unwind +//@ [DARWIN] needs-llvm-components: aarch64 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Cpanic=unwind +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Cpanic=unwind +//@ [TVOS] needs-llvm-components: aarch64 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Cpanic=unwind +//@ [VISIONOS] needs-llvm-components: aarch64 + +#![crate_type = "lib"] +#![feature(intrinsics, no_core, lang_items)] +#![no_core] + +extern crate minicore; + +#[rustc_intrinsic] +pub unsafe fn catch_unwind(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32; + +fn try_fn(_: *mut u8) {} +fn catch_fn(_: *mut u8, _: *mut u8) {} + +#[unsafe(no_mangle)] +pub unsafe fn invoke_catch() -> i32 { + unsafe { catch_unwind(try_fn, 0 as *mut u8, catch_fn) } +} + +// CHECK: define {{.*}}@__rust_try( +// CHECK: invoke void %{{.*}}(ptr %{{.*}}){{.*}} [ "ptrauth"(i32 0, i64 0) ] From 769ed4bb7742e68509d2fbdac33dc4742a6e0e38 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 21:09:30 -0700 Subject: [PATCH 09/13] bootstrap: allow stage0-missing arm64e visionos target --- src/bootstrap/src/core/sanity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index ca8af279b92bc..984f44e933b8b 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -36,7 +36,7 @@ pub struct Finder { /// Targets can be removed from this list during the usual release process bootstrap compiler bumps, /// when the newly-bumped stage 0 compiler now knows about the formerly-missing targets. const STAGE0_MISSING_TARGETS: &[&str] = &[ - // just a dummy comment so the list doesn't get onelined + "arm64e-apple-visionos", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM From 3564a784e41c8a75e331d9e9e215fe345af78c37 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:50:33 -0700 Subject: [PATCH 10/13] arm64e: fix verified ptrauth data pointers --- compiler/rustc_codegen_llvm/src/back/write.rs | 8 ++++++-- compiler/rustc_codegen_llvm/src/consts.rs | 4 +++- compiler/rustc_codegen_llvm/src/context.rs | 8 ++++++-- compiler/rustc_codegen_ssa/src/target_features.rs | 5 +---- compiler/rustc_span/src/symbol.rs | 4 ++-- src/bootstrap/src/core/sanity.rs | 4 +--- .../arm64e-apple-ptrauth-fnptr-data.rs | 8 ++++---- tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs | 15 ++++++++++----- .../branch-protection-arm64e-apple-pac-ret.rs | 1 + 9 files changed, 34 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 39422235ac8a7..24153c043c4f7 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -748,8 +748,12 @@ pub(crate) unsafe fn llvm_optimize( } if cgcx.target_is_like_gpu && config.offload.contains(&config::Offload::Device) { - let cx = - SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size, false); + let cx = SimpleCx::new( + module.module_llvm.llmod(), + module.module_llvm.llcx, + cgcx.pointer_size, + false, + ); for func in cx.get_functions() { let offload_kernel = "offload-kernel"; if attributes::has_string_attr(func, offload_kernel) { diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 75367fc864150..52a7b331ac4c6 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -236,11 +236,13 @@ impl<'ll> CodegenCx<'ll, '_> { let fn_addr = self.const_pointercast(fn_addr, self.type_ptr()); unsafe { + // ConstantPtrAuth requires an i64 discriminator and a pointer-typed + // address discriminator, even when both are effectively "zero". llvm::LLVMConstantPtrAuth( fn_addr, self.get_const_i32(0), self.get_const_i64(0), - self.get_const_i64(0), + self.const_null(self.type_ptr()), ) } } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 24f551dc97412..5fec8fae9446f 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -186,8 +186,12 @@ pub(crate) unsafe fn create_module<'ll>( let mod_name = SmallCStr::new(mod_name); let llmod = unsafe { llvm::LLVMModuleCreateWithNameInContext(mod_name.as_ptr(), llcx) }; - let cx = - SimpleCx::new(llmod, llcx, tcx.data_layout.pointer_size(), attributes::has_default_arm64e_ptrauth(sess)); + let cx = SimpleCx::new( + llmod, + llcx, + tcx.data_layout.pointer_size(), + attributes::has_default_arm64e_ptrauth(sess), + ); let mut target_data_layout = sess.target.data_layout.to_string(); let llvm_version = llvm_util::get_version(); diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index b51930b8b0ba0..a1b5c55c1e4d5 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -265,10 +265,7 @@ pub fn cfg_target_feature<'a, const N: usize>( sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature }); }, |base_feature, new_features, enable| { - if !enable - && sess.target.is_apple_arm64e() - && matches!(base_feature, "paca" | "pacg") - { + if !enable && sess.target.is_apple_arm64e() && matches!(base_feature, "paca" | "pacg") { if !arm64e_apple_ptrauth_disable_forbidden { sess.dcx().emit_err(errors::Arm64eApplePtrauthDisableForbidden); arm64e_apple_ptrauth_disable_forbidden = true; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a41e06bd9f32b..b1129d86a1e35 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1449,10 +1449,10 @@ symbols! { overflow_checks, overlapping_marker_traits, owned_box, - packed, - packed_bundled_libs, paca, pacg, + packed, + packed_bundled_libs, panic, panic_2015, panic_2021, diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 984f44e933b8b..68a57b697ace4 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -35,9 +35,7 @@ pub struct Finder { /// /// Targets can be removed from this list during the usual release process bootstrap compiler bumps, /// when the newly-bumped stage 0 compiler now knows about the formerly-missing targets. -const STAGE0_MISSING_TARGETS: &[&str] = &[ - "arm64e-apple-visionos", -]; +const STAGE0_MISSING_TARGETS: &[&str] = &["arm64e-apple-visionos"]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM /// from CI (with`llvm.download-ci-llvm` option). diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs index d62de90d4fbc6..41a87c2c860c8 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs @@ -1,12 +1,12 @@ //@ add-minicore //@ revisions: DARWIN IOS TVOS VISIONOS -//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Zverify-llvm-ir //@ [DARWIN] needs-llvm-components: aarch64 -//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Zverify-llvm-ir //@ [IOS] needs-llvm-components: aarch64 -//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Zverify-llvm-ir //@ [TVOS] needs-llvm-components: aarch64 -//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Zverify-llvm-ir //@ [VISIONOS] needs-llvm-components: aarch64 #![crate_type = "lib"] diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs index f499f2d89420e..5e379f3739c13 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs @@ -1,12 +1,13 @@ +// ignore-tidy-linelength //@ add-minicore //@ revisions: DARWIN IOS TVOS VISIONOS -//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Cpanic=unwind +//@ [DARWIN] compile-flags: --target arm64e-apple-darwin -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir //@ [DARWIN] needs-llvm-components: aarch64 -//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Cpanic=unwind +//@ [IOS] compile-flags: --target arm64e-apple-ios -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir //@ [IOS] needs-llvm-components: aarch64 -//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Cpanic=unwind +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir //@ [TVOS] needs-llvm-components: aarch64 -//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Cpanic=unwind +//@ [VISIONOS] compile-flags: --target arm64e-apple-visionos -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir //@ [VISIONOS] needs-llvm-components: aarch64 #![crate_type = "lib"] @@ -16,7 +17,11 @@ extern crate minicore; #[rustc_intrinsic] -pub unsafe fn catch_unwind(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32; +pub unsafe fn catch_unwind( + try_fn: fn(*mut u8), + data: *mut u8, + catch_fn: fn(*mut u8, *mut u8), +) -> i32; fn try_fn(_: *mut u8) {} fn catch_fn(_: *mut u8, _: *mut u8) {} diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs index 3dacaa3ee83a3..f734a71516cd0 100644 --- a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs @@ -1,3 +1,4 @@ +// ignore-tidy-linelength //@ revisions: DARWIN_PACRET DARWIN_PAUTHLR_LEAF IOS_PACRET IOS_PAUTHLR_LEAF TVOS_PACRET TVOS_PAUTHLR_LEAF VISIONOS_PACRET VISIONOS_PAUTHLR_LEAF //@ [DARWIN_PACRET] compile-flags: --target=arm64e-apple-darwin -Zbranch-protection=pac-ret //@ [DARWIN_PACRET] check-fail From 705fa756acc56be6d764077df739476eaaa9b0fd Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:47:54 -0700 Subject: [PATCH 11/13] tests: skip arm64e ptrauth ui cases under cg_gcc --- .../invalid/branch-protection-arm64e-apple-pac-ret.rs | 1 + .../invalid/target-feature-arm64e-apple-ptrauth-disable.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs index f734a71516cd0..cabc06cc9d5d1 100644 --- a/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs @@ -1,4 +1,5 @@ // ignore-tidy-linelength +//@ ignore-backends: gcc //@ revisions: DARWIN_PACRET DARWIN_PAUTHLR_LEAF IOS_PACRET IOS_PAUTHLR_LEAF TVOS_PACRET TVOS_PAUTHLR_LEAF VISIONOS_PACRET VISIONOS_PAUTHLR_LEAF //@ [DARWIN_PACRET] compile-flags: --target=arm64e-apple-darwin -Zbranch-protection=pac-ret //@ [DARWIN_PACRET] check-fail diff --git a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs index 2cc3262be6c0b..5eeec7abc73a3 100644 --- a/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs @@ -1,3 +1,4 @@ +//@ ignore-backends: gcc //@ revisions: DARWIN_PACA DARWIN_PACG DARWIN_BOTH IOS_BOTH TVOS_BOTH VISIONOS_BOTH //@ [DARWIN_PACA] compile-flags: --target=arm64e-apple-darwin -Ctarget-feature=-paca //@ [DARWIN_PACA] check-fail From 944ca394113d6e3d6ac5c388f1f3d3e5dd36a5c8 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:18:31 -0700 Subject: [PATCH 12/13] bootstrap: fix fork push upstream detection --- .github/workflows/fork-arm64e.yml | 10 ++ src/bootstrap/src/core/config/tests.rs | 50 ++++++++++ src/bootstrap/src/utils/tests/git.rs | 9 ++ src/build_helper/src/git.rs | 128 ++++++++++++++++++------- 4 files changed, 162 insertions(+), 35 deletions(-) diff --git a/.github/workflows/fork-arm64e.yml b/.github/workflows/fork-arm64e.yml index 50cf6dbab20cb..3075aac121f2f 100644 --- a/.github/workflows/fork-arm64e.yml +++ b/.github/workflows/fork-arm64e.yml @@ -32,10 +32,20 @@ jobs: fetch-depth: 2 submodules: recursive + # CypherAir fork push runs reproduced a shallow-clone case where bootstrap could not + # safely treat HEAD^1 as the upstream nightly base commit. Keep this in sync with src/stage0. + - name: Fetch nightly base ref + run: | + git fetch --no-tags --prune --depth=1 origin \ + +refs/heads/main:refs/remotes/origin/main + - name: Show environment run: | + echo "github.event_name=${GITHUB_EVENT_NAME}" sw_vers uname -a + git show -s --format='HEAD: %H %P %ae %s' HEAD + git show -s --format='origin/main: %H %P %ae %s' refs/remotes/origin/main python3 --version clang --version rustc -vV || true diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs index c281aa94f64e6..50903f74733c2 100644 --- a/src/bootstrap/src/core/config/tests.rs +++ b/src/bootstrap/src/core/config/tests.rs @@ -723,6 +723,56 @@ fn test_auto_ci_changed_in_pr() { }); } +#[test] +fn test_push_ci_unchanged_anywhere_uses_nightly_ref() { + git_test(|ctx| { + let sha = ctx.create_upstream_merge(&["a"]); + ctx.set_origin_nightly_ref(&sha); + + ctx.create_branch("feature"); + ctx.modify("b"); + ctx.commit(); + + let src = ctx.check_modifications(&["c"], CiEnv::GitHubActions); + assert_eq!(src, PathFreshness::LastModifiedUpstream { upstream: sha }); + }); +} + +#[test] +fn test_push_ci_changed_in_branch_uses_nightly_ref() { + git_test(|ctx| { + let sha = ctx.create_upstream_merge(&["a"]); + ctx.set_origin_nightly_ref(&sha); + + ctx.create_branch("feature"); + ctx.modify("b"); + ctx.commit(); + + let src = ctx.check_modifications(&["b"], CiEnv::GitHubActions); + assert_eq!(src, modified(sha, &["b"])); + }); +} + +#[test] +fn test_ci_merge_without_upstream_parent_falls_back_to_nightly_ref() { + git_test(|ctx| { + let sha = ctx.create_upstream_merge(&["a"]); + ctx.set_origin_nightly_ref(&sha); + + ctx.create_branch("feature"); + ctx.modify("b"); + ctx.commit(); + ctx.create_branch("nested"); + ctx.modify("c"); + ctx.commit(); + ctx.switch_to_branch("feature"); + ctx.merge("nested", "Tester "); + + let src = ctx.check_modifications(&["d"], CiEnv::GitHubActions); + assert_eq!(src, PathFreshness::LastModifiedUpstream { upstream: sha }); + }); +} + #[test] fn test_local_uncommitted_modifications() { git_test(|ctx| { diff --git a/src/bootstrap/src/utils/tests/git.rs b/src/bootstrap/src/utils/tests/git.rs index d9dd9ab980088..ec25c72af51b1 100644 --- a/src/bootstrap/src/utils/tests/git.rs +++ b/src/bootstrap/src/utils/tests/git.rs @@ -74,6 +74,15 @@ impl GitCtx { self.run_git(&["rev-parse", "--abbrev-ref", "HEAD"]) } + pub fn set_origin_branch_ref(&self, branch: &str, commit: &str) { + let refname = format!("refs/remotes/origin/{branch}"); + self.run_git(&["update-ref", &refname, commit]); + } + + pub fn set_origin_nightly_ref(&self, commit: &str) { + self.set_origin_branch_ref(&self.nightly_branch, commit); + } + pub fn merge(&self, branch: &str, author: &str) { self.run_git(&["merge", "--no-commit", "--no-ff", branch]); self.run_git(&[ diff --git a/src/build_helper/src/git.rs b/src/build_helper/src/git.rs index 87a52eee49b85..64e86a651ab00 100644 --- a/src/build_helper/src/git.rs +++ b/src/build_helper/src/git.rs @@ -70,14 +70,11 @@ pub enum PathFreshness { /// were not modified upstream in the meantime. In that case we would be redownloading CI /// artifacts unnecessarily. /// -/// - In CI, we use a shallow clone of depth 2, i.e., we fetch only a single parent commit -/// (which will be the most recent bors merge commit) and do not have access -/// to the full git history. Luckily, we only need to distinguish between two situations: -/// 1) The current PR made modifications to `target_paths`. -/// In that case, a build is typically necessary. -/// 2) The current PR did not make modifications to `target_paths`. -/// In that case we simply take the latest upstream commit, because on CI there is no need to avoid -/// redownloading. +/// - In CI, we prefer a shallow merge-parent fast path when `HEAD` is a CI-generated merge +/// commit. However, fork push workflows can also run in shallow clones where `HEAD` is just the +/// branch tip, so blindly using `HEAD^1` there would pick a fork commit instead of the upstream +/// base. In those cases we fall back to the fetched nightly branch ref, and only then to the +/// normal upstream search logic. pub fn check_path_modifications( git_dir: &Path, config: &GitConfig<'_>, @@ -90,24 +87,9 @@ pub fn check_path_modifications( } let upstream_sha = if matches!(ci_env, CiEnv::GitHubActions) { - // Here the situation is different for PR CI and try/auto CI. - // For PR CI, we have the following history: - // - // 1-N PR commits - // upstream merge commit made by bors - // - // For try/auto CI, we have the following history: - // <**non-upstream** merge commit made by bors> - // 1-N PR commits - // upstream merge commit made by bors - // - // But on both cases, HEAD should be a merge commit. - // So if HEAD contains modifications of `target_paths`, our PR has modified - // them. If not, we can use the only available upstream commit for downloading - // artifacts. - - // Do not include HEAD, as it is never an upstream commit - // If we do not find an upstream commit in CI, something is seriously wrong. + // CI may be running on a synthetic merge ref or a shallow fork push ref. + // `get_closest_upstream_commit` handles the trusted merge-parent fast path and falls back + // to the fetched nightly branch ref when the merge-parent assumption is not valid. Some( get_closest_upstream_commit(Some(git_dir), config, ci_env)? .expect("No upstream commit was found on CI"), @@ -224,23 +206,45 @@ fn get_latest_upstream_commit_that_modified_files( /// Returns the most recent (ordered chronologically) commit found in the local history that /// should exist upstream. We identify upstream commits by the e-mail of the commit /// author. -/// -/// If we are in CI, we simply return our first parent. pub fn get_closest_upstream_commit( git_dir: Option<&Path>, config: &GitConfig<'_>, env: CiEnv, ) -> Result, String> { - let base = match env { - CiEnv::None => "HEAD", + match env { + CiEnv::None => get_closest_upstream_commit_from_ref(git_dir, config, "HEAD"), CiEnv::GitHubActions => { - // On CI, we should always have a non-upstream merge commit at the tip, - // and our first parent should be the most recently merged upstream commit. - // We thus simply return our first parent. - return resolve_commit_sha(git_dir, "HEAD^1").map(Some); + // CI-generated PR and auto-merge refs put a synthetic merge commit at HEAD, so the + // first parent is usually the most recent upstream merge commit. Fork push workflows + // do not have that shape, though, and in shallow clones `HEAD^1` can just be the + // previous fork commit. Only trust the fast path when it points at an actual upstream + // merge-bot commit, otherwise fall back to the fetched nightly branch. + if is_merge_commit(git_dir, "HEAD")? { + let parent = resolve_commit_sha(git_dir, "HEAD^1")?; + if is_upstream_merge_commit(git_dir, &parent, config)? { + return Ok(Some(parent)); + } + } + + let nightly_ref = format!("refs/remotes/origin/{}", config.nightly_branch); + if git_ref_exists(git_dir, &nightly_ref)? { + if let Some(upstream) = + get_closest_upstream_commit_from_ref(git_dir, config, &nightly_ref)? + { + return Ok(Some(upstream)); + } + } + + get_closest_upstream_commit_from_ref(git_dir, config, "HEAD") } - }; + } +} +fn get_closest_upstream_commit_from_ref( + git_dir: Option<&Path>, + config: &GitConfig<'_>, + base: &str, +) -> Result, String> { let mut git = Command::new("git"); if let Some(git_dir) = git_dir { @@ -272,6 +276,60 @@ pub fn get_closest_upstream_commit( if output.is_empty() { Ok(None) } else { Ok(Some(output)) } } +fn is_merge_commit(git_dir: Option<&Path>, commit_ref: &str) -> Result { + let mut git = Command::new("git"); + if let Some(git_dir) = git_dir { + git.current_dir(git_dir); + } + + let output = git + .args(["rev-parse", "--verify", "--quiet", &format!("{commit_ref}^2")]) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .status() + .map_err(|e| format!("failed to run command: {git:?}: {e}"))?; + Ok(output.success()) +} + +fn git_ref_exists(git_dir: Option<&Path>, refname: &str) -> Result { + let mut git = Command::new("git"); + if let Some(git_dir) = git_dir { + git.current_dir(git_dir); + } + + let output = git + .args(["rev-parse", "--verify", "--quiet", refname]) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .status() + .map_err(|e| format!("failed to run command: {git:?}: {e}"))?; + Ok(output.success()) +} + +fn is_upstream_merge_commit( + git_dir: Option<&Path>, + commit_ref: &str, + config: &GitConfig<'_>, +) -> Result { + let mut git = Command::new("git"); + if let Some(git_dir) = git_dir { + git.current_dir(git_dir); + } + + git.args(["show", "-s", "--format=%ae", commit_ref]); + let author_email = output_result(&mut git)?.trim().to_owned(); + let merge_bot_email = extract_author_email(config.git_merge_commit_email); + Ok(author_email == merge_bot_email || author_email == TEMPORARY_BORS_EMAIL) +} + +fn extract_author_email(author: &str) -> &str { + author + .split_once('<') + .and_then(|(_, email)| email.trim().strip_suffix('>')) + .map(str::trim) + .unwrap_or_else(|| author.trim()) +} + /// Resolve the commit SHA of `commit_ref`. fn resolve_commit_sha(git_dir: Option<&Path>, commit_ref: &str) -> Result { let mut git = Command::new("git"); From a6ff114ac1a12feb34849eeb6bdeaf0095916e4d Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 06:36:27 -0700 Subject: [PATCH 13/13] ci: isolate arm64e runner diagnostics --- .github/workflows/fork-arm64e.yml | 361 +++++++++++++++++++++++++----- 1 file changed, 308 insertions(+), 53 deletions(-) diff --git a/.github/workflows/fork-arm64e.yml b/.github/workflows/fork-arm64e.yml index 3075aac121f2f..3d7ef68fa390c 100644 --- a/.github/workflows/fork-arm64e.yml +++ b/.github/workflows/fork-arm64e.yml @@ -19,8 +19,16 @@ concurrency: jobs: arm64e: if: github.repository == 'cypherair/rust' - name: arm64e targeted validation - runs-on: macos-15 + strategy: + fail-fast: false + matrix: + include: + - runner_label: macos-15 + runner_name: macos-15 + - runner_label: macos-26 + runner_name: macos-26 + name: arm64e targeted validation (${{ matrix.runner_name }}) + runs-on: ${{ matrix.runner_label }} timeout-minutes: 240 defaults: run: @@ -41,14 +49,20 @@ jobs: - name: Show environment run: | - echo "github.event_name=${GITHUB_EVENT_NAME}" - sw_vers - uname -a - git show -s --format='HEAD: %H %P %ae %s' HEAD - git show -s --format='origin/main: %H %P %ae %s' refs/remotes/origin/main - python3 --version - clang --version - rustc -vV || true + mkdir -p /tmp/arm64e-diagnostics + { + echo "github.event_name=${GITHUB_EVENT_NAME}" + echo "runner_label=${{ matrix.runner_name }}" + sw_vers + uname -a + sysctl -n kern.osproductversion kern.osversion + sysctl -n machdep.cpu.brand_string 2>/dev/null || true + git show -s --format='HEAD: %H %P %ae %s' HEAD + git show -s --format='origin/main: %H %P %ae %s' refs/remotes/origin/main + python3 --version + clang --version + rustc -vV || true + } | tee /tmp/arm64e-diagnostics/runner-fingerprint.txt - name: Check arm64e codegen crates id: check_codegen @@ -71,30 +85,222 @@ jobs: run: | python3 x.py build compiler/rustc library/std --stage 1 --target arm64e-apple-darwin - - name: Diagnose arm64e function pointer codegen + - name: Prepare arm64e smoke helpers + run: | + mkdir -p /tmp/arm64e-diagnostics + cat > /tmp/arm64e_collect_smoke.py <<'PY' + import argparse + import json + import pathlib + import re + import subprocess + import sys + + + def run_capture(cmd, timeout=None): + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return { + "cmd": cmd, + "returncode": proc.returncode, + "signal": -proc.returncode if proc.returncode < 0 else None, + "stdout": proc.stdout, + "stderr": proc.stderr, + } + + + def load_text(path): + try: + return pathlib.Path(path).read_text() + except FileNotFoundError: + return None + + + parser = argparse.ArgumentParser() + parser.add_argument("--name", required=True) + parser.add_argument("--rustc", required=True) + parser.add_argument("--source", required=True) + parser.add_argument("--output-base", required=True) + parser.add_argument("--json-out", required=True) + parser.add_argument("--target", default="arm64e-apple-darwin") + parser.add_argument("--emit", default="link") + parser.add_argument("--run-binary", action="store_true") + parser.add_argument("--expected-stdout") + parser.add_argument("--expected-stdout-regex") + parser.add_argument("--asm-pattern") + args = parser.parse_args() + + output_base = pathlib.Path(args.output_base) + compile_cmd = [ + args.rustc, + args.source, + "--target", + args.target, + "-O", + "--emit", + args.emit, + "-o", + str(output_base), + ] + payload = { + "name": args.name, + "target": args.target, + "emit": args.emit, + "compile": run_capture(compile_cmd), + "output_base": str(output_base), + "paths": { + "source": args.source, + "binary": str(output_base), + "asm": str(output_base.with_suffix(".s")), + "ir": str(output_base.with_suffix(".ll")), + }, + } + + overall_ok = payload["compile"]["returncode"] == 0 + + if overall_ok: + payload["file"] = run_capture(["/usr/bin/file", str(output_base)]) + else: + payload["file"] = None + + asm_path = output_base.with_suffix(".s") + asm_text = load_text(asm_path) + if asm_text is not None and args.asm_pattern: + payload["asm_matches"] = re.findall(args.asm_pattern, asm_text) + else: + payload["asm_matches"] = [] + + if args.run_binary and overall_ok: + payload["run"] = run_capture([str(output_base)], timeout=30) + if payload["run"]["returncode"] != 0: + overall_ok = False + stdout = payload["run"]["stdout"] + if args.expected_stdout is not None: + if stdout.rstrip("\n") != args.expected_stdout.rstrip("\n"): + payload["stdout_mismatch"] = { + "expected": args.expected_stdout, + "actual": stdout, + } + overall_ok = False + if args.expected_stdout_regex is not None: + if re.fullmatch(args.expected_stdout_regex, stdout.strip()) is None: + payload["stdout_regex_mismatch"] = { + "expected_regex": args.expected_stdout_regex, + "actual": stdout, + } + overall_ok = False + else: + payload["run"] = None + + json_path = pathlib.Path(args.json_out) + json_path.write_text(json.dumps(payload, indent=2, sort_keys=True)) + + print(f"== {args.name} compile ==") + print(f"returncode={payload['compile']['returncode']}") + if payload["compile"]["stdout"]: + print("-- stdout --") + print(payload["compile"]["stdout"], end="") + if payload["compile"]["stderr"]: + print("-- stderr --") + print(payload["compile"]["stderr"], end="") + if payload["file"] is not None: + print("== file ==") + print(payload["file"]["stdout"], end="") + if payload["run"] is not None: + print("== run ==") + print(f"returncode={payload['run']['returncode']}") + if payload["run"]["signal"] is not None: + print(f"signal={payload['run']['signal']}") + print("-- stdout --") + print(payload["run"]["stdout"], end="") + print("-- stderr --") + print(payload["run"]["stderr"], end="") + if payload["asm_matches"]: + print("== asm matches ==") + for match in payload["asm_matches"]: + print(match) + + sys.exit(0 if overall_ok else 1) + PY + + - name: Diagnose arm64e hello runtime + id: hello_diag + continue-on-error: true + run: | + HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_hello.rs <<'EOF' + fn main() { + println!("hello-stage1"); + } + EOF + + python3 /tmp/arm64e_collect_smoke.py \ + --name hello \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_hello.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_hello \ + --json-out /tmp/arm64e-diagnostics/arm64e_hello.json \ + --emit link \ + --run-binary \ + --expected-stdout 'hello-stage1' + + - name: Diagnose arm64e thread id runtime + id: thread_id_diag + continue-on-error: true + run: | + HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" + STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" + + cat > /tmp/arm64e-diagnostics/arm64e_thread_id.rs <<'EOF' + fn main() { + println!("{:?}", std::thread::current().id()); + } + EOF + + python3 /tmp/arm64e_collect_smoke.py \ + --name thread_id \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_thread_id.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_thread_id \ + --json-out /tmp/arm64e-diagnostics/arm64e_thread_id.json \ + --emit link \ + --run-binary \ + --expected-stdout-regex '^ThreadId\([0-9]+\)$' + + - name: Diagnose arm64e function pointer runtime id: fnptr_diag continue-on-error: true run: | HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" - cat > /tmp/arm64e_fnptr.rs <<'EOF' - #![crate_type = "lib"] + cat > /tmp/arm64e-diagnostics/arm64e_fnptr.rs <<'EOF' + #[inline(never)] + pub extern "C" fn target(x: i32) -> i32 { x + 1 } - #[no_mangle] - pub extern "C" fn call(f: extern "C" fn() -> i32) -> i32 { f() } + fn main() { + let f: extern "C" fn(i32) -> i32 = target; + println!("{}", f(41)); + } EOF - "$STAGE1" /tmp/arm64e_fnptr.rs --target arm64e-apple-darwin -O --emit=llvm-ir,asm -o /tmp/arm64e_fnptr + python3 /tmp/arm64e_collect_smoke.py \ + --name fnptr \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_fnptr.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_fnptr \ + --json-out /tmp/arm64e-diagnostics/arm64e_fnptr.json \ + --emit llvm-ir,asm,link \ + --run-binary \ + --expected-stdout '42' \ + --asm-pattern 'braaz|blraaz|blr' echo "LLVM IR excerpt:" - sed -n '1,24p' /tmp/arm64e_fnptr.ll + sed -n '1,40p' /tmp/arm64e-diagnostics/arm64e_fnptr.ll || true echo echo "Assembly excerpt:" - sed -n '1,16p' /tmp/arm64e_fnptr.s - - grep -n '"ptrauth"' /tmp/arm64e_fnptr.ll - grep -nE 'braaz|blraaz' /tmp/arm64e_fnptr.s + sed -n '1,40p' /tmp/arm64e-diagnostics/arm64e_fnptr.s || true - name: Diagnose arm64e TLS runtime id: tls_diag @@ -103,7 +309,7 @@ jobs: HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc" - cat > /tmp/arm64e_tls_smoke.rs <<'EOF' + cat > /tmp/arm64e-diagnostics/arm64e_tls_smoke.rs <<'EOF' use std::cell::RefCell; fn main() { @@ -116,46 +322,95 @@ jobs: } EOF - "$STAGE1" /tmp/arm64e_tls_smoke.rs --target arm64e-apple-darwin -O -o /tmp/arm64e_tls_smoke - file /tmp/arm64e_tls_smoke - - python3 - <<'PY' - import subprocess + python3 /tmp/arm64e_collect_smoke.py \ + --name tls \ + --rustc "$STAGE1" \ + --source /tmp/arm64e-diagnostics/arm64e_tls_smoke.rs \ + --output-base /tmp/arm64e-diagnostics/arm64e_tls_smoke \ + --json-out /tmp/arm64e-diagnostics/arm64e_tls_smoke.json \ + --emit llvm-ir,asm,link \ + --run-binary \ + --expected-stdout 'pika pika' \ + --asm-pattern 'braaz|blraaz|blr' - proc = subprocess.run( - ["/tmp/arm64e_tls_smoke"], - capture_output=True, - text=True, - timeout=30, - check=True, - ) - print(proc.stdout, end="") - if proc.stdout.strip() != "pika pika": - raise SystemExit(f"unexpected stdout: {proc.stdout!r}") - PY + echo "LLVM IR excerpt:" + sed -n '1,40p' /tmp/arm64e-diagnostics/arm64e_tls_smoke.ll || true + echo + echo "Assembly excerpt:" + sed -n '1,60p' /tmp/arm64e-diagnostics/arm64e_tls_smoke.s || true - name: Upload arm64e diagnostics if: always() uses: actions/upload-artifact@v4 with: - name: arm64e-diagnostics + name: arm64e-diagnostics-${{ matrix.runner_name }} if-no-files-found: ignore - path: | - /tmp/arm64e_fnptr.ll - /tmp/arm64e_fnptr.s - /tmp/arm64e_tls_smoke.rs + path: /tmp/arm64e-diagnostics - name: Summarize arm64e validation if: always() run: | + python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY" + import json + import pathlib + + root = pathlib.Path("/tmp/arm64e-diagnostics") + cases = [ + ("hello", "arm64e_hello.json"), + ("thread_id", "arm64e_thread_id.json"), + ("fnptr", "arm64e_fnptr.json"), + ("tls", "arm64e_tls_smoke.json"), + ] + + def sanitize(text): + if text is None: + return "" + text = text.strip() + if not text: + return "" + return text.replace("|", "\\|").replace("\n", "
") + + print("## arm64e validation (${{ matrix.runner_name }})") + print() + print("- check_codegen: ${{ steps.check_codegen.outcome }}") + print("- targeted_tests: ${{ steps.targeted_tests.outcome }}") + print("- build_stage1: ${{ steps.build_stage1.outcome }}") + print("- hello_diag (non-blocking): ${{ steps.hello_diag.outcome }}") + print("- thread_id_diag (non-blocking): ${{ steps.thread_id_diag.outcome }}") + print("- fnptr_diag (non-blocking): ${{ steps.fnptr_diag.outcome }}") + print("- tls_diag (non-blocking): ${{ steps.tls_diag.outcome }}") + print() + print("| case | compile | run | rc/signal | file | stdout | asm hits |") + print("| --- | --- | --- | --- | --- | --- | --- |") + for case_name, json_name in cases: + path = root / json_name + if not path.exists(): + print(f"| {case_name} | missing | missing | | | | |") + continue + data = json.loads(path.read_text()) + compile_status = "ok" if data["compile"]["returncode"] == 0 else "fail" + run = data.get("run") + if run is None: + run_status = "not-run" + rc_signal = "" + stdout = "" + else: + run_status = "ok" if run["returncode"] == 0 else "fail" + rc_signal = f"rc={run['returncode']}" + if run.get("signal") is not None: + rc_signal += f" sig={run['signal']}" + stdout = sanitize(run.get("stdout")) + file_output = sanitize((data.get("file") or {}).get("stdout")) + asm_hits = sanitize(",".join(data.get("asm_matches") or [])) + print( + f"| {case_name} | {compile_status} | {run_status} | {sanitize(rc_signal)} | " + f"{file_output} | {stdout} | {asm_hits} |" + ) + print() + print("The four host-binary smoke steps are intentionally non-blocking while runner and TLS isolation is being investigated.") + PY + { - echo "## arm64e validation" - echo - echo "- check_codegen: ${{ steps.check_codegen.outcome }}" - echo "- targeted_tests: ${{ steps.targeted_tests.outcome }}" - echo "- build_stage1: ${{ steps.build_stage1.outcome }}" - echo "- fnptr_diag (non-blocking): ${{ steps.fnptr_diag.outcome }}" - echo "- tls_diag (non-blocking): ${{ steps.tls_diag.outcome }}" - echo - echo "The two diagnostic steps are intentionally non-blocking while the arm64e indirect-call and TLS runtime issues are still being investigated." - } >> "$GITHUB_STEP_SUMMARY" + echo "Smoke diagnostic JSON files:" + ls -1 /tmp/arm64e-diagnostics/*.json + }