From c8fbc41245587ef537aac0dc9a2e912ab6350615 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:17:37 -0700 Subject: [PATCH 01/13] target: add arm64e-apple-visionos --- compiler/rustc_target/src/spec/mod.rs | 1 + .../src/spec/targets/arm64e_apple_visionos.rs | 25 +++++++++++ src/bootstrap/src/core/sanity.rs | 4 +- 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 ++ 8 files changed, 82 insertions(+), 3 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_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/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index ca8af279b92bc..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] = &[ - // just a dummy comment so the list doesn't get onelined -]; +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/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 a260b8f5153605b2ad81649969fba7971291a3b5 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:45:42 -0700 Subject: [PATCH 02/13] arm64e: model default Apple ptrauth features --- compiler/rustc_target/src/spec/base/apple/mod.rs | 8 ++++++++ compiler/rustc_target/src/spec/mod.rs | 6 ++++++ .../rustc_target/src/spec/targets/arm64e_apple_darwin.rs | 3 ++- .../rustc_target/src/spec/targets/arm64e_apple_ios.rs | 4 ++-- .../rustc_target/src/spec/targets/arm64e_apple_tvos.rs | 4 ++-- 5 files changed, 20 insertions(+), 5 deletions(-) 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 }, From ec86bf8d5c47ff2b3ed542158dd80fc105052319 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:19:50 -0700 Subject: [PATCH 03/13] arm64e: emit clang-compatible ptrauth metadata --- compiler/rustc_codegen_llvm/src/attributes.rs | 13 +++++++ compiler/rustc_codegen_llvm/src/context.rs | 33 ++++++++++++++++++ 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 | 6 ++-- .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 7 ++++ compiler/rustc_span/src/symbol.rs | 2 ++ tests/codegen-llvm/arm64e-apple-ptrauth.rs | 34 +++++++++++++++++++ 8 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 tests/codegen-llvm/arm64e-apple-ptrauth.rs diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 1f59d250e08a0..a4b4ef9dfd287 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -578,6 +578,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 +620,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) } + +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 5b730b820b84a..d7fe486f67094 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -426,6 +426,39 @@ pub(crate) unsafe fn create_module<'ll>( } } + if attributes::has_default_arm64e_ptrauth(sess) { + 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::LLVMInt1TypeInContext(llcx), + 0, + llvm::FALSE, + )), + ] + .as_ptr(), + 2, + )] + .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..89711eea97570 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) -> Option 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_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index c310e580af559..f886c030dbf75 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1011,6 +1011,13 @@ extern "C" void LLVMRustAddModuleFlagString( MDString::get(unwrap(M)->getContext(), 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()) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e7574eaed5b27..b1129d86a1e35 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1449,6 +1449,8 @@ symbols! { overflow_checks, overlapping_marker_traits, owned_box, + paca, + pacg, packed, packed_bundled_libs, panic, diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth.rs b/tests/codegen-llvm/arm64e-apple-ptrauth.rs new file mode 100644 index 0000000000000..60e25b4cd691e --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth.rs @@ -0,0 +1,34 @@ +//@ add-minicore +//@ 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 +//@ [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{{.*}}" +// 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} From 6b1af5b0ecc4c8c1b5d61756a10dbf5e96ba0800 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:41:25 -0700 Subject: [PATCH 04/13] arm64e: authenticate indirect function calls --- compiler/rustc_codegen_llvm/src/back/write.rs | 20 +++++++- compiler/rustc_codegen_llvm/src/builder.rs | 35 +++++++++++-- compiler/rustc_codegen_llvm/src/context.rs | 22 ++++++-- compiler/rustc_codegen_llvm/src/lib.rs | 8 ++- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + compiler/rustc_codegen_llvm/src/llvm/mod.rs | 6 +++ compiler/rustc_codegen_ssa/src/back/write.rs | 2 + .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 50 ++++++++++++++++--- .../arm64e-apple-ptrauth-calls.rs | 44 ++++++++++++++++ .../arm64e-apple-ptrauth-calls.rs | 47 +++++++++++++++++ tests/codegen-llvm/arm64e-apple-ptrauth.rs | 5 +- 11 files changed, 218 insertions(+), 22 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..519866ad9ee73 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -60,6 +60,7 @@ fn write_output_file<'ll>( file_type: llvm::FileType, self_profiler_ref: &SelfProfilerRef, verify_llvm_ir: bool, + strip_unsupported_ptrauth_bundles: bool, ) { debug!("write_output_file output={:?} dwo_output={:?}", output, dwo_output); let output_c = path_to_c_string(output); @@ -70,6 +71,14 @@ fn write_output_file<'ll>( } else { std::ptr::null() }; + + if strip_unsupported_ptrauth_bundles { + // 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); @@ -742,8 +751,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); + 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) { @@ -875,6 +888,7 @@ pub(crate) unsafe fn llvm_optimize( llvm::FileType::ObjectFile, prof, true, + cgcx.target_is_apple_arm64e, ); // We ignore cgcx.save_temps here and unconditionally always keep our `host.out` artifact. // Otherwise, recompiling the host code would fail since we deleted that device artifact @@ -1128,6 +1142,7 @@ pub(crate) fn codegen( llvm::FileType::AssemblyFile, prof, config.verify_llvm_ir, + cgcx.target_is_apple_arm64e, ); } @@ -1163,6 +1178,7 @@ pub(crate) fn codegen( llvm::FileType::ObjectFile, prof, config.verify_llvm_ir, + cgcx.target_is_apple_arm64e, ); } diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 056a0763087a2..546b097cc7e6e 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,7 +1859,7 @@ 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); } @@ -1850,6 +1874,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/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index d7fe486f67094..5ee3edfa55c92 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,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()); + 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 +660,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 +793,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/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_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 5601a950fbdb5..3757d75cc1b54 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -330,6 +330,7 @@ pub struct CodegenContext { pub is_pe_coff: bool, pub target_can_use_split_dwarf: bool, pub target_arch: String, + pub target_is_apple_arm64e: bool, pub target_is_like_darwin: bool, pub target_is_like_aix: bool, pub target_is_like_gpu: bool, @@ -1306,6 +1307,7 @@ fn start_executing_work( is_pe_coff: tcx.sess.target.is_like_windows, target_can_use_split_dwarf: tcx.sess.target_can_use_split_dwarf(), target_arch: tcx.sess.target.arch.to_string(), + target_is_apple_arm64e: tcx.sess.target.is_apple_arm64e(), target_is_like_darwin: tcx.sess.target.is_like_darwin, target_is_like_aix: tcx.sess.target.is_like_aix, target_is_like_gpu: tcx.sess.target.is_like_gpu, 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) { 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..21c544bd476e6 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs @@ -0,0 +1,47 @@ +//@ add-minicore +//@ revisions: DARWIN IOS TVOS +//@ [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 +#![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) ]{{$}} diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth.rs b/tests/codegen-llvm/arm64e-apple-ptrauth.rs index 60e25b4cd691e..67049f0e82404 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth.rs @@ -1,5 +1,5 @@ //@ add-minicore -//@ revisions: DARWIN DARWIN_BTI DARWIN_GCS IOS TVOS VISIONOS +//@ revisions: DARWIN DARWIN_BTI DARWIN_GCS IOS TVOS //@ [DARWIN] compile-flags: --target arm64e-apple-darwin //@ [DARWIN] needs-llvm-components: aarch64 //@ [DARWIN_BTI] compile-flags: --target arm64e-apple-darwin -Zbranch-protection=bti @@ -10,9 +10,6 @@ //@ [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] From 901c0ca0e2e40fbedc8e908adecea0630360a6e2 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:46:49 -0700 Subject: [PATCH 05/13] arm64e: authenticate function pointers used as data --- compiler/rustc_codegen_llvm/src/common.rs | 4 ++- compiler/rustc_codegen_llvm/src/consts.rs | 18 ++++++++++ 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 | 35 +++++++++++++++++++ .../arm64e-apple-ptrauth-invoke.rs | 32 +++++++++++++++++ 9 files changed, 104 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/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..52a7b331ac4c6 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -229,6 +229,24 @@ 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 { + // 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.const_null(self.type_ptr()), + ) + } + } + /// 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 5ee3edfa55c92..5fec8fae9446f 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -885,6 +885,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..28d190ebd5b59 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs @@ -0,0 +1,35 @@ +//@ add-minicore +//@ revisions: DARWIN IOS TVOS +//@ [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 -Zverify-llvm-ir +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Zverify-llvm-ir +//@ [TVOS] 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..019e5d7fced88 --- /dev/null +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs @@ -0,0 +1,32 @@ +// ignore-tidy-linelength +//@ add-minicore +//@ revisions: DARWIN IOS TVOS +//@ [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 -Zverify-llvm-ir +//@ [IOS] needs-llvm-components: aarch64 +//@ [TVOS] compile-flags: --target arm64e-apple-tvos -Copt-level=0 -Cpanic=unwind -Zverify-llvm-ir +//@ [TVOS] 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 0ca6f1afb70a9fb789dc9e65bb465290f718abc8 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:47:42 -0700 Subject: [PATCH 06/13] arm64e: reject incompatible ptrauth flag overrides --- compiler/rustc_codegen_ssa/src/errors.rs | 6 +++ .../rustc_codegen_ssa/src/target_features.rs | 9 +++++ compiler/rustc_session/src/errors.rs | 6 +++ compiler/rustc_session/src/session.rs | 6 +++ .../src/compiler-flags/branch-protection.md | 7 +++- ...-arm64e-apple-pac-ret.DARWIN_PACRET.stderr | 3 ++ ...e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr | 3 ++ ...ion-arm64e-apple-pac-ret.IOS_PACRET.stderr | 3 ++ ...m64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr | 3 ++ ...on-arm64e-apple-pac-ret.TVOS_PACRET.stderr | 3 ++ ...64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr | 3 ++ .../branch-protection-arm64e-apple-pac-ret.rs | 37 +++++++++++++++++++ ...e-apple-ptrauth-disable.DARWIN_BOTH.stderr | 3 ++ ...e-apple-ptrauth-disable.DARWIN_PACA.stderr | 3 ++ ...e-apple-ptrauth-disable.DARWIN_PACG.stderr | 3 ++ ...m64e-apple-ptrauth-disable.IOS_BOTH.stderr | 3 ++ ...64e-apple-ptrauth-disable.TVOS_BOTH.stderr | 3 ++ ...et-feature-arm64e-apple-ptrauth-disable.rs | 33 +++++++++++++++++ 18 files changed, 136 insertions(+), 1 deletion(-) 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.rs 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.rs 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..a1b5c55c1e4d5 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,14 @@ 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_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/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/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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PACRET.stderr @@ -0,0 +1,3 @@ +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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.DARWIN_PAUTHLR_LEAF.stderr @@ -0,0 +1,3 @@ +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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PACRET.stderr @@ -0,0 +1,3 @@ +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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.IOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,3 @@ +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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PACRET.stderr @@ -0,0 +1,3 @@ +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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.TVOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,3 @@ +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..9989d7fbb7479 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.rs @@ -0,0 +1,37 @@ +// ignore-tidy-linelength +//@ ignore-backends: gcc +// gcc does not model these LLVM AArch64 Apple arm64e ptrauth diagnostics. +//@ normalize-stderr: "\nerror: aborting due to 1 previous error\n\n" -> "\nerror: aborting due to 1 previous error\n" +//@ revisions: DARWIN_PACRET DARWIN_PAUTHLR_LEAF IOS_PACRET IOS_PAUTHLR_LEAF TVOS_PACRET TVOS_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 +#![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 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..391177fdb1aee --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_BOTH.stderr @@ -0,0 +1,3 @@ +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..391177fdb1aee --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACA.stderr @@ -0,0 +1,3 @@ +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..391177fdb1aee --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.DARWIN_PACG.stderr @@ -0,0 +1,3 @@ +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..391177fdb1aee --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.IOS_BOTH.stderr @@ -0,0 +1,3 @@ +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..391177fdb1aee --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.TVOS_BOTH.stderr @@ -0,0 +1,3 @@ +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..d419f8fcf5f38 --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.rs @@ -0,0 +1,33 @@ +//@ ignore-backends: gcc +// gcc does not model these LLVM AArch64 Apple arm64e ptrauth diagnostics. +//@ normalize-stderr: "\nerror: aborting due to 1 previous error\n\n" -> "\nerror: aborting due to 1 previous error\n" +//@ revisions: DARWIN_PACA DARWIN_PACG DARWIN_BOTH IOS_BOTH TVOS_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 +#![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 35fb9b3b01d5340a4c15ef7c8f0e32d84d302264 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:44:21 -0700 Subject: [PATCH 07/13] bootstrap: handle fork shallow upstream detection --- src/bootstrap/src/core/config/tests.rs | 50 ++++++++++ src/bootstrap/src/utils/tests/git.rs | 9 ++ src/build_helper/src/git.rs | 128 ++++++++++++++++++------- 3 files changed, 152 insertions(+), 35 deletions(-) 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 c4091f51f396ed81810f4ed438d94bb0cbe6678c Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:26:40 -0700 Subject: [PATCH 08/13] ci: add fork arm64e validation workflow --- .github/workflows/fork-arm64e.yml | 421 ++++++++++++++++++++++++++++++ 1 file changed, 421 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..e5cd7a91697ac --- /dev/null +++ b/.github/workflows/fork-arm64e.yml @@ -0,0 +1,421 @@ +name: Fork arm64e validation + +# Fork-only CypherAir validation. Drop this workflow before proposing the +# upstreamable arm64e core to rust-lang/rust. + +on: + workflow_dispatch: + push: + branches: + - codex/arm64e-upstream-ready-integration-2026-04-24-u9836b06 + 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' + 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: + shell: bash + steps: + - name: Checkout source + uses: actions/checkout@v5 + with: + 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: | + 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 + 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/codegen-llvm/arm64e-apple-ptrauth-calls.rs \ + tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs \ + tests/codegen-llvm/arm64e-apple-ptrauth-invoke.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 + + - 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: 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-diagnostics/arm64e_fnptr.rs <<'EOF' + #[inline(never)] + pub extern "C" fn target(x: i32) -> i32 { x + 1 } + + fn main() { + let f: extern "C" fn(i32) -> i32 = target; + println!("{}", f(41)); + } + EOF + + 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,40p' /tmp/arm64e-diagnostics/arm64e_fnptr.ll || true + echo + echo "Assembly excerpt:" + sed -n '1,40p' /tmp/arm64e-diagnostics/arm64e_fnptr.s || true + + - 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-diagnostics/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 + + 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' + + 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-${{ matrix.runner_name }} + if-no-files-found: ignore + 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 non-blocking diagnostics for fork runner coverage; targeted tests and stage1 build remain blocking.") + PY + + { + echo "Smoke diagnostic JSON files:" + ls -1 /tmp/arm64e-diagnostics/*.json + } From 9a76bf1a752473960ed45d38f7019f4350f0261a Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Thu, 23 Apr 2026 23:00:19 -0700 Subject: [PATCH 09/13] arm64e: cover visionOS ptrauth integration --- tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs | 5 ++++- tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs | 5 ++++- tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs | 5 ++++- tests/codegen-llvm/arm64e-apple-ptrauth.rs | 5 ++++- ...rotection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr | 3 +++ ...ion-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr | 3 +++ .../invalid/branch-protection-arm64e-apple-pac-ret.rs | 9 ++++++++- ...ure-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr | 3 +++ .../target-feature-arm64e-apple-ptrauth-disable.rs | 6 +++++- 9 files changed, 38 insertions(+), 6 deletions(-) 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/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs index 21c544bd476e6..9702fad96f1c0 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs @@ -1,11 +1,14 @@ //@ add-minicore -//@ revisions: DARWIN IOS TVOS +//@ 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] diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs index 28d190ebd5b59..41a87c2c860c8 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs @@ -1,11 +1,14 @@ //@ add-minicore -//@ revisions: DARWIN IOS TVOS +//@ revisions: DARWIN IOS TVOS VISIONOS //@ [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 -Zverify-llvm-ir //@ [IOS] needs-llvm-components: aarch64 //@ [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 -Zverify-llvm-ir +//@ [VISIONOS] needs-llvm-components: aarch64 + #![crate_type = "lib"] #![feature(no_core, lang_items)] #![no_core] diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs index 019e5d7fced88..5e379f3739c13 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth-invoke.rs @@ -1,12 +1,15 @@ // ignore-tidy-linelength //@ add-minicore -//@ revisions: DARWIN IOS TVOS +//@ revisions: DARWIN IOS TVOS VISIONOS //@ [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 -Zverify-llvm-ir //@ [IOS] needs-llvm-components: aarch64 //@ [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 -Zverify-llvm-ir +//@ [VISIONOS] needs-llvm-components: aarch64 + #![crate_type = "lib"] #![feature(intrinsics, no_core, lang_items)] #![no_core] diff --git a/tests/codegen-llvm/arm64e-apple-ptrauth.rs b/tests/codegen-llvm/arm64e-apple-ptrauth.rs index 67049f0e82404..60e25b4cd691e 100644 --- a/tests/codegen-llvm/arm64e-apple-ptrauth.rs +++ b/tests/codegen-llvm/arm64e-apple-ptrauth.rs @@ -1,5 +1,5 @@ //@ add-minicore -//@ revisions: DARWIN DARWIN_BTI DARWIN_GCS IOS TVOS +//@ 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 @@ -10,6 +10,9 @@ //@ [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] 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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PACRET.stderr @@ -0,0 +1,3 @@ +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..50d4347075124 --- /dev/null +++ b/tests/ui/compile-flags/invalid/branch-protection-arm64e-apple-pac-ret.VISIONOS_PAUTHLR_LEAF.stderr @@ -0,0 +1,3 @@ +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 index 9989d7fbb7479..f03cf5ff8deaa 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 @@ -2,7 +2,7 @@ //@ ignore-backends: gcc // gcc does not model these LLVM AArch64 Apple arm64e ptrauth diagnostics. //@ normalize-stderr: "\nerror: aborting due to 1 previous error\n\n" -> "\nerror: aborting due to 1 previous error\n" -//@ revisions: DARWIN_PACRET DARWIN_PAUTHLR_LEAF IOS_PACRET IOS_PAUTHLR_LEAF TVOS_PACRET TVOS_PAUTHLR_LEAF +//@ 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 @@ -21,6 +21,13 @@ //@ [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] 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..391177fdb1aee --- /dev/null +++ b/tests/ui/compile-flags/invalid/target-feature-arm64e-apple-ptrauth-disable.VISIONOS_BOTH.stderr @@ -0,0 +1,3 @@ +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 index d419f8fcf5f38..cee0420424fad 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,7 +1,7 @@ //@ ignore-backends: gcc // gcc does not model these LLVM AArch64 Apple arm64e ptrauth diagnostics. //@ normalize-stderr: "\nerror: aborting due to 1 previous error\n\n" -> "\nerror: aborting due to 1 previous error\n" -//@ revisions: DARWIN_PACA DARWIN_PACG DARWIN_BOTH IOS_BOTH TVOS_BOTH +//@ 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 @@ -17,6 +17,10 @@ //@ [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] From 065f1a4132ba3c458c763e3a3e81a21359c0f0f1 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Fri, 24 Apr 2026 03:14:07 -0700 Subject: [PATCH 10/13] ci: publish arm64e stage1 prereleases --- .../workflows/arm64e-stage1-prerelease.yml | 372 ++++++++++++++++++ .../workflows/arm64e-upstream-sync-prep.yml | 130 ++++++ 2 files changed, 502 insertions(+) create mode 100644 .github/workflows/arm64e-stage1-prerelease.yml create mode 100644 .github/workflows/arm64e-upstream-sync-prep.yml diff --git a/.github/workflows/arm64e-stage1-prerelease.yml b/.github/workflows/arm64e-stage1-prerelease.yml new file mode 100644 index 0000000000000..9a89fce3b406b --- /dev/null +++ b/.github/workflows/arm64e-stage1-prerelease.yml @@ -0,0 +1,372 @@ +name: arm64e Stage1 Prerelease + +on: + workflow_dispatch: + inputs: + source_ref: + description: Branch, tag, or SHA to build. + required: true + default: codex/arm64e-upstream-ready-integration-2026-04-24-u9836b06 + type: string + release_tag_prefix: + description: Unique release tag prefix. + required: true + default: rust-arm64e-stage1 + type: string + create_release: + description: Publish the GitHub prerelease instead of uploading dry-run artifacts. + required: true + default: true + type: boolean + push: + branches: + - codex/arm64e-upstream-ready-integration-2026-04-24-u9836b06 + schedule: + - cron: "17 10 * * 1" + +permissions: + contents: write + id-token: write + attestations: write + +concurrency: + group: arm64e-stage1-prerelease-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +env: + ASSET_BASE: rust-stage1-arm64e-apple-darwin + INTEGRATION_BRANCH: codex/arm64e-upstream-ready-integration-2026-04-24-u9836b06 + +jobs: + publish-stage1: + if: github.repository == 'cypherair/rust' + runs-on: macos-26 + timeout-minutes: 240 + defaults: + run: + shell: bash + steps: + - name: Resolve release settings + id: settings + env: + INPUT_SOURCE_REF: ${{ github.event.inputs.source_ref }} + INPUT_RELEASE_TAG_PREFIX: ${{ github.event.inputs.release_tag_prefix }} + INPUT_CREATE_RELEASE: ${{ github.event.inputs.create_release }} + run: | + set -euo pipefail + + case "$GITHUB_EVENT_NAME" in + workflow_dispatch) + SOURCE_REF="${INPUT_SOURCE_REF:-$INTEGRATION_BRANCH}" + RELEASE_TAG_PREFIX="${INPUT_RELEASE_TAG_PREFIX:-rust-arm64e-stage1}" + CREATE_RELEASE="${INPUT_CREATE_RELEASE:-true}" + ;; + push) + SOURCE_REF="$GITHUB_REF" + RELEASE_TAG_PREFIX="rust-arm64e-stage1" + CREATE_RELEASE="true" + ;; + schedule) + SOURCE_REF="$INTEGRATION_BRANCH" + RELEASE_TAG_PREFIX="rust-arm64e-stage1" + CREATE_RELEASE="true" + ;; + *) + echo "::error::Unsupported event: $GITHUB_EVENT_NAME" + exit 1 + ;; + esac + + case "$RELEASE_TAG_PREFIX" in + *[!A-Za-z0-9._-]*) + echo "::error::release_tag_prefix may only contain letters, numbers, dots, underscores, and hyphens." + exit 1 + ;; + esac + + { + echo "SOURCE_REF=$SOURCE_REF" + echo "RELEASE_TAG_PREFIX=$RELEASE_TAG_PREFIX" + echo "CREATE_RELEASE=$CREATE_RELEASE" + } >> "$GITHUB_ENV" + { + echo "source_ref=$SOURCE_REF" + echo "release_tag_prefix=$RELEASE_TAG_PREFIX" + echo "create_release=$CREATE_RELEASE" + } >> "$GITHUB_OUTPUT" + + - name: Checkout source + uses: actions/checkout@v5 + with: + fetch-depth: 2 + ref: ${{ steps.settings.outputs.source_ref }} + submodules: recursive + + - name: Select Xcode 26.4.1 + run: | + set -euo pipefail + + if [ -n "${XCODE_26_DEVELOPER_DIR:-}" ] && [ -d "${XCODE_26_DEVELOPER_DIR}" ]; then + SELECTED_DEVELOPER_DIR="${XCODE_26_DEVELOPER_DIR}" + elif [ -d "/Applications/Xcode_26.4.1.app/Contents/Developer" ]; then + SELECTED_DEVELOPER_DIR="/Applications/Xcode_26.4.1.app/Contents/Developer" + else + echo "::error::Xcode 26.4.1 was not found on this runner." + exit 1 + fi + + echo "DEVELOPER_DIR=${SELECTED_DEVELOPER_DIR}" >> "$GITHUB_ENV" + export DEVELOPER_DIR="${SELECTED_DEVELOPER_DIR}" + xcodebuild -version + + - name: Fetch fork main for bootstrap ancestry checks + run: | + git fetch --no-tags --prune --depth=1 origin \ + +refs/heads/main:refs/remotes/origin/main + + - name: Show runner and source environment + run: | + set -euo pipefail + mkdir -p /tmp/arm64e-stage1-diagnostics + { + echo "event=${GITHUB_EVENT_NAME}" + echo "source_ref=${SOURCE_REF}" + sw_vers + uname -a + df -h + 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-stage1-diagnostics/runner-fingerprint.txt + + - name: Run targeted arm64e validation + run: | + set -euo pipefail + python3 x.py check compiler/rustc_codegen_ssa compiler/rustc_codegen_llvm --stage 1 + python3 x.py test --stage 1 --force-rerun \ + tests/codegen-llvm/arm64e-apple-ptrauth.rs \ + tests/codegen-llvm/arm64e-apple-ptrauth-calls.rs \ + tests/codegen-llvm/arm64e-apple-ptrauth-fnptr-data.rs \ + tests/codegen-llvm/arm64e-apple-ptrauth-invoke.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 + + - name: Build stage1 compiler and Apple std payload + run: | + set -euo pipefail + HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" + python3 x.py build compiler/rustc library/std library/proc_macro --stage 1 --target "${HOST_TRIPLE},arm64e-apple-darwin" + + - name: Package minimal stage1 toolchain + run: | + set -euo pipefail + + HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')" + STAGE1="build/${HOST_TRIPLE}/stage1" + if [ ! -x "$STAGE1/bin/rustc" ]; then + echo "::error::stage1 rustc missing at $STAGE1/bin/rustc" + exit 1 + fi + + RELEASE_TIMESTAMP="$(date -u +"%Y%m%dT%H%M%SZ")" + SHORT_SHA="$(git rev-parse --short=7 HEAD)" + RUN_ATTEMPT="${GITHUB_RUN_ATTEMPT:-1}" + RELEASE_TAG="${RELEASE_TAG_PREFIX}-${RELEASE_TIMESTAMP}-${SHORT_SHA}-r${GITHUB_RUN_ID}-a${RUN_ATTEMPT}" + RELEASE_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${RELEASE_TAG}" + WORKFLOW_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + RELEASE_TITLE="Rust arm64e stage1 toolchain (${RELEASE_TAG})" + + mkdir -p dist/stage1-arm64e-patch + rsync -a --delete "$STAGE1/" dist/stage1-arm64e-patch/ + find dist/stage1-arm64e-patch -name '*.dSYM' -prune -exec rm -rf {} + + + "./dist/stage1-arm64e-patch/bin/rustc" --print target-list | tee /tmp/arm64e-stage1-diagnostics/target-list.txt + for target in arm64e-apple-darwin arm64e-apple-ios arm64e-apple-tvos arm64e-apple-visionos; do + grep -qx "$target" /tmp/arm64e-stage1-diagnostics/target-list.txt + done + HOST_LIBDIR="$(./dist/stage1-arm64e-patch/bin/rustc --print target-libdir --target "$HOST_TRIPLE")" + test -n "$(find "$HOST_LIBDIR" -maxdepth 1 -name 'libstd-*.rlib' -print -quit)" + test -n "$(find "$HOST_LIBDIR" -maxdepth 1 -name 'libproc_macro-*.rlib' -print -quit)" + + cat > /tmp/arm64e-stage1-diagnostics/hello.rs <<'EOF' + fn main() { + println!("hello-stage1-arm64e"); + } + EOF + "./dist/stage1-arm64e-patch/bin/rustc" \ + /tmp/arm64e-stage1-diagnostics/hello.rs \ + --target arm64e-apple-darwin \ + -O \ + -o /tmp/arm64e-stage1-diagnostics/hello + /usr/bin/file /tmp/arm64e-stage1-diagnostics/hello | tee /tmp/arm64e-stage1-diagnostics/hello-file.txt + /tmp/arm64e-stage1-diagnostics/hello | tee /tmp/arm64e-stage1-diagnostics/hello-run.txt + + bsdtar -cf - -C dist stage1-arm64e-patch | zstd -T0 -19 -o "${ASSET_BASE}.tar.zst" + shasum -a 256 "${ASSET_BASE}.tar.zst" > "${ASSET_BASE}.sha256" + + STAGE1_RUSTC_VERBOSE="$(./dist/stage1-arm64e-patch/bin/rustc -vV)" + XCODE_VERSION="$(xcodebuild -version | tr '\n' ' ' | sed 's/ */ /g; s/ $//')" + BUILT_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + TOOLCHAIN_SIZE_BYTES="$(stat -f%z "${ASSET_BASE}.tar.zst")" + UPSTREAM_MAIN_SHA="$(git rev-parse refs/remotes/origin/main)" + export STAGE1_RUSTC_VERBOSE + + python3 - <> "$GITHUB_ENV" + + - name: Generate release notes + run: | + cat > release-notes.md </dev/null 2>&1; then + echo "::error::Release tag already exists: $RELEASE_TAG" + exit 1 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag "$RELEASE_TAG" "$(git rev-parse HEAD)" + git push origin "refs/tags/$RELEASE_TAG" + + - name: Create draft prerelease + if: env.CREATE_RELEASE == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh release create "$RELEASE_TAG" \ + --verify-tag \ + --target "$(git rev-parse HEAD)" \ + --title "$RELEASE_TITLE" \ + --notes-file release-notes.md \ + --prerelease \ + --latest=false \ + --draft + + - name: Upload prerelease assets + if: env.CREATE_RELEASE == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh release upload "$RELEASE_TAG" \ + "${ASSET_BASE}.tar.zst" \ + "${ASSET_BASE}.sha256" \ + "${ASSET_BASE}.json" \ + arm64e-stage1-diagnostics.tar.zst + + - name: Publish draft prerelease + if: env.CREATE_RELEASE == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh release edit "$RELEASE_TAG" --draft=false --prerelease --latest=false + + - name: Cleanup failed draft release/tag + if: ${{ failure() && env.CREATE_RELEASE == 'true' }} + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set +e + if [ -z "${RELEASE_TAG:-}" ]; then + exit 0 + fi + release_is_draft="$(gh release view "$RELEASE_TAG" -R "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft' 2>/dev/null)" + if [ "$release_is_draft" = "true" ]; then + gh release delete "$RELEASE_TAG" -R "$GITHUB_REPOSITORY" --cleanup-tag --yes + exit 0 + fi + git push origin ":refs/tags/$RELEASE_TAG" 2>/dev/null || true diff --git a/.github/workflows/arm64e-upstream-sync-prep.yml b/.github/workflows/arm64e-upstream-sync-prep.yml new file mode 100644 index 0000000000000..431d748afa001 --- /dev/null +++ b/.github/workflows/arm64e-upstream-sync-prep.yml @@ -0,0 +1,130 @@ +name: arm64e Upstream Sync Prep + +on: + workflow_dispatch: + inputs: + integration_branch: + description: CypherAir arm64e integration branch to inspect. + required: true + default: codex/arm64e-upstream-ready-integration-2026-04-24-u9836b06 + type: string + upstream_ref: + description: Upstream Rust ref to merge in the dry-run workspace. + required: true + default: upstream/main + type: string + create_refresh_pr: + description: Push a refresh branch and open a draft PR when the dry-run merge succeeds. + required: true + default: false + type: boolean + dispatch_stage1_prerelease: + description: Dispatch arm64e stage1 prerelease for the refresh branch. + required: true + default: false + type: boolean + +permissions: + contents: write + pull-requests: write + actions: write + +jobs: + sync-prep: + if: github.repository == 'cypherair/rust' + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Checkout fork + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Fetch upstream and integration refs + env: + INTEGRATION_BRANCH: ${{ inputs.integration_branch }} + run: | + set -euo pipefail + git remote add upstream https://github.com/rust-lang/rust.git 2>/dev/null || git remote set-url upstream https://github.com/rust-lang/rust.git + git fetch --prune origin \ + "+refs/heads/${INTEGRATION_BRANCH}:refs/remotes/origin/${INTEGRATION_BRANCH}" \ + +refs/heads/main:refs/remotes/origin/main + git fetch --prune upstream +refs/heads/main:refs/remotes/upstream/main + + - name: Report branch posture + env: + INTEGRATION_BRANCH: ${{ inputs.integration_branch }} + UPSTREAM_REF: ${{ inputs.upstream_ref }} + run: | + set -euo pipefail + { + echo "## arm64e upstream sync prep" + echo + echo "- integration branch: \`${INTEGRATION_BRANCH}\`" + echo "- upstream ref: \`${UPSTREAM_REF}\`" + echo "- origin/main: \`$(git rev-parse refs/remotes/origin/main)\`" + echo "- upstream/main: \`$(git rev-parse refs/remotes/upstream/main)\`" + echo "- integration head: \`$(git rev-parse refs/remotes/origin/${INTEGRATION_BRANCH})\`" + echo "- origin/main...upstream/main: \`$(git rev-list --left-right --count refs/remotes/origin/main...refs/remotes/upstream/main)\`" + echo "- integration...upstream: \`$(git rev-list --left-right --count refs/remotes/origin/${INTEGRATION_BRANCH}...${UPSTREAM_REF})\`" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Dry-run merge upstream into integration branch + id: dry_run + env: + INTEGRATION_BRANCH: ${{ inputs.integration_branch }} + UPSTREAM_REF: ${{ inputs.upstream_ref }} + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B arm64e-sync-dry-run "refs/remotes/origin/${INTEGRATION_BRANCH}" + if git merge --no-ff --no-commit "$UPSTREAM_REF"; then + echo "merge_ok=true" >> "$GITHUB_OUTPUT" + git diff --stat --cached || true + git merge --abort || true + else + echo "merge_ok=false" >> "$GITHUB_OUTPUT" + git status --short + exit 1 + fi + + - name: Create refresh branch and draft PR + id: refresh + if: ${{ inputs.create_refresh_pr && steps.dry_run.outputs.merge_ok == 'true' }} + env: + GITHUB_TOKEN: ${{ github.token }} + INTEGRATION_BRANCH: ${{ inputs.integration_branch }} + UPSTREAM_REF: ${{ inputs.upstream_ref }} + run: | + set -euo pipefail + SHORT_SHA="$(git rev-parse --short=7 "refs/remotes/origin/${INTEGRATION_BRANCH}")" + REFRESH_BRANCH="automation/arm64e-refresh-$(date -u +"%Y%m%dT%H%M%SZ")-${SHORT_SHA}" + + git checkout -B "$REFRESH_BRANCH" "refs/remotes/origin/${INTEGRATION_BRANCH}" + git merge --no-ff "$UPSTREAM_REF" -m "Merge ${UPSTREAM_REF} into ${INTEGRATION_BRANCH}" + git push origin "HEAD:refs/heads/${REFRESH_BRANCH}" + + gh pr create \ + --repo "$GITHUB_REPOSITORY" \ + --base "$INTEGRATION_BRANCH" \ + --head "$REFRESH_BRANCH" \ + --title "[automation] Refresh arm64e integration branch from ${UPSTREAM_REF}" \ + --body "Automated dry-run refresh branch for CypherAir arm64e Rust integration. This PR intentionally targets the integration branch and does not force-push it." \ + --draft + + echo "REFRESH_BRANCH=$REFRESH_BRANCH" >> "$GITHUB_ENV" + echo "branch=$REFRESH_BRANCH" >> "$GITHUB_OUTPUT" + + - name: Dispatch stage1 prerelease for refresh branch + if: ${{ inputs.dispatch_stage1_prerelease && inputs.create_refresh_pr && steps.refresh.outputs.branch != '' }} + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh workflow run arm64e-stage1-prerelease.yml \ + --repo "$GITHUB_REPOSITORY" \ + -f source_ref="${{ steps.refresh.outputs.branch }}" \ + -f release_tag_prefix=rust-arm64e-stage1-refresh \ + -f create_release=true From d9f18f74ec7ed20767966aae0bae9c4abba98d13 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Fri, 24 Apr 2026 03:45:54 -0700 Subject: [PATCH 11/13] ci: fix arm64e stage1 attestation --- .github/workflows/arm64e-stage1-prerelease.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/arm64e-stage1-prerelease.yml b/.github/workflows/arm64e-stage1-prerelease.yml index 9a89fce3b406b..25c3593c9c226 100644 --- a/.github/workflows/arm64e-stage1-prerelease.yml +++ b/.github/workflows/arm64e-stage1-prerelease.yml @@ -303,11 +303,13 @@ jobs: - name: Generate artifact attestation if: env.CREATE_RELEASE == 'true' - uses: actions/attest@v3 + uses: actions/attest-build-provenance@v3 with: subject-path: | ${{ env.ASSET_BASE }}.tar.zst + ${{ env.ASSET_BASE }}.sha256 ${{ env.ASSET_BASE }}.json + arm64e-stage1-diagnostics.tar.zst - name: Create release tag if: env.CREATE_RELEASE == 'true' From 43d4e546d3787e29e81c424b7ff1c6fda0ff800b Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Fri, 24 Apr 2026 04:46:18 -0700 Subject: [PATCH 12/13] ci: avoid stage1 release tag fetch --- .github/workflows/arm64e-stage1-prerelease.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/arm64e-stage1-prerelease.yml b/.github/workflows/arm64e-stage1-prerelease.yml index 25c3593c9c226..69c853e5b8eb6 100644 --- a/.github/workflows/arm64e-stage1-prerelease.yml +++ b/.github/workflows/arm64e-stage1-prerelease.yml @@ -315,7 +315,6 @@ jobs: if: env.CREATE_RELEASE == 'true' run: | set -euo pipefail - git fetch --tags --force origin if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then echo "::error::Release tag already exists: $RELEASE_TAG" exit 1 From 02240c72a3779297f5e6f624277147e42d2071fa Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Fri, 24 Apr 2026 07:50:36 -0700 Subject: [PATCH 13/13] ci: package rust-src in arm64e stage1 --- .github/workflows/arm64e-stage1-prerelease.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/arm64e-stage1-prerelease.yml b/.github/workflows/arm64e-stage1-prerelease.yml index 69c853e5b8eb6..64acbcb6a226b 100644 --- a/.github/workflows/arm64e-stage1-prerelease.yml +++ b/.github/workflows/arm64e-stage1-prerelease.yml @@ -184,6 +184,15 @@ jobs: rsync -a --delete "$STAGE1/" dist/stage1-arm64e-patch/ find dist/stage1-arm64e-patch -name '*.dSYM' -prune -exec rm -rf {} + + RUST_SRC_DEST="dist/stage1-arm64e-patch/lib/rustlib/src/rust" + rm -rf "$RUST_SRC_DEST" + mkdir -p "$RUST_SRC_DEST/src/llvm-project" + rsync -a --delete library "$RUST_SRC_DEST/" + rsync -a --delete src/llvm-project/libunwind "$RUST_SRC_DEST/src/llvm-project/" + test -d "$RUST_SRC_DEST/library" + test -d "$RUST_SRC_DEST/src/llvm-project/libunwind" + du -sh "$RUST_SRC_DEST" | tee /tmp/arm64e-stage1-diagnostics/rust-src-size.txt + "./dist/stage1-arm64e-patch/bin/rustc" --print target-list | tee /tmp/arm64e-stage1-diagnostics/target-list.txt for target in arm64e-apple-darwin arm64e-apple-ios arm64e-apple-tvos arm64e-apple-visionos; do grep -qx "$target" /tmp/arm64e-stage1-diagnostics/target-list.txt @@ -191,6 +200,7 @@ jobs: HOST_LIBDIR="$(./dist/stage1-arm64e-patch/bin/rustc --print target-libdir --target "$HOST_TRIPLE")" test -n "$(find "$HOST_LIBDIR" -maxdepth 1 -name 'libstd-*.rlib' -print -quit)" test -n "$(find "$HOST_LIBDIR" -maxdepth 1 -name 'libproc_macro-*.rlib' -print -quit)" + test -d "$(./dist/stage1-arm64e-patch/bin/rustc --print sysroot)/lib/rustlib/src/rust/library" cat > /tmp/arm64e-stage1-diagnostics/hello.rs <<'EOF' fn main() { @@ -231,6 +241,7 @@ jobs: "forkMainCommit": "${UPSTREAM_MAIN_SHA}", "hostTriple": "${HOST_TRIPLE}", "includedHostStdTarget": "${HOST_TRIPLE}", + "includedRustSrc": True, "targetTriple": "arm64e-apple-darwin", "builtAt": "${BUILT_AT}", "workflowURL": "${WORKFLOW_URL}",