Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions .github/workflows/fork-arm64e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Fork arm64e validation

on:
workflow_dispatch:
push:
branches:
- codex/arm64e-darwin-ptrauth-spike
pull_request:
branches:
- main

permissions:
contents: read

concurrency:
group: fork-arm64e-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
arm64e:
if: github.repository == 'cypherair/rust'
name: arm64e targeted validation
runs-on: macos-15
timeout-minutes: 240
defaults:
run:
shell: bash
steps:
- name: Checkout source
uses: actions/checkout@v5
with:
fetch-depth: 2
submodules: recursive

# CypherAir fork push runs reproduced a shallow-clone case where bootstrap could not
# safely treat HEAD^1 as the upstream nightly base commit. Keep this in sync with src/stage0.
- name: Fetch nightly base ref
run: |
git fetch --no-tags --prune --depth=1 origin \
+refs/heads/main:refs/remotes/origin/main

- name: Show environment
run: |
echo "github.event_name=${GITHUB_EVENT_NAME}"
sw_vers
uname -a
git show -s --format='HEAD: %H %P %ae %s' HEAD
git show -s --format='origin/main: %H %P %ae %s' refs/remotes/origin/main
python3 --version
clang --version
rustc -vV || true

- 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/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: Diagnose arm64e function pointer codegen
id: fnptr_diag
continue-on-error: true
run: |
HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')"
STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc"

cat > /tmp/arm64e_fnptr.rs <<'EOF'
#![crate_type = "lib"]

#[no_mangle]
pub extern "C" fn call(f: extern "C" fn() -> i32) -> i32 { f() }
EOF

"$STAGE1" /tmp/arm64e_fnptr.rs --target arm64e-apple-darwin -O --emit=llvm-ir,asm -o /tmp/arm64e_fnptr

echo "LLVM IR excerpt:"
sed -n '1,24p' /tmp/arm64e_fnptr.ll
echo
echo "Assembly excerpt:"
sed -n '1,16p' /tmp/arm64e_fnptr.s

grep -n '"ptrauth"' /tmp/arm64e_fnptr.ll
grep -nE 'braaz|blraaz' /tmp/arm64e_fnptr.s

- name: Diagnose arm64e TLS runtime
id: tls_diag
continue-on-error: true
run: |
HOST_TRIPLE="$(rustc -vV | sed -n 's/^host: //p')"
STAGE1="build/${HOST_TRIPLE}/stage1/bin/rustc"

cat > /tmp/arm64e_tls_smoke.rs <<'EOF'
use std::cell::RefCell;

fn main() {
thread_local! {
static S: RefCell<String> = RefCell::default();
}

S.with(|x| *x.borrow_mut() = "pika pika".to_string());
S.with(|x| println!("{}", x.borrow()));
}
EOF

"$STAGE1" /tmp/arm64e_tls_smoke.rs --target arm64e-apple-darwin -O -o /tmp/arm64e_tls_smoke
file /tmp/arm64e_tls_smoke

python3 - <<'PY'
import subprocess

proc = subprocess.run(
["/tmp/arm64e_tls_smoke"],
capture_output=True,
text=True,
timeout=30,
check=True,
)
print(proc.stdout, end="")
if proc.stdout.strip() != "pika pika":
raise SystemExit(f"unexpected stdout: {proc.stdout!r}")
PY

- name: Upload arm64e diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: arm64e-diagnostics
if-no-files-found: ignore
path: |
/tmp/arm64e_fnptr.ll
/tmp/arm64e_fnptr.s
/tmp/arm64e_tls_smoke.rs

- name: Summarize arm64e validation
if: always()
run: |
{
echo "## arm64e validation"
echo
echo "- check_codegen: ${{ steps.check_codegen.outcome }}"
echo "- targeted_tests: ${{ steps.targeted_tests.outcome }}"
echo "- build_stage1: ${{ steps.build_stage1.outcome }}"
echo "- fnptr_diag (non-blocking): ${{ steps.fnptr_diag.outcome }}"
echo "- tls_diag (non-blocking): ${{ steps.tls_diag.outcome }}"
echo
echo "The two diagnostic steps are intentionally non-blocking while the arm64e indirect-call and TLS runtime issues are still being investigated."
} >> "$GITHUB_STEP_SUMMARY"
13 changes: 13 additions & 0 deletions compiler/rustc_codegen_llvm/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
14 changes: 12 additions & 2 deletions compiler/rustc_codegen_llvm/src/back/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ fn write_output_file<'ll>(
} else {
std::ptr::null()
};

// LLVM may devirtualize indirect arm64e calls into direct calls after we
// attached ptrauth operand bundles. Strip bundles from call shapes LLVM
// cannot lower just before object/assembly emission.
llvm::strip_unsupported_ptrauth_bundles(m);

let result = unsafe {
let pm = llvm::LLVMCreatePassManager();
llvm::LLVMAddAnalysisPasses(target, pm);
Expand Down Expand Up @@ -742,8 +748,12 @@ pub(crate) unsafe fn llvm_optimize(
}

if cgcx.target_is_like_gpu && config.offload.contains(&config::Offload::Device) {
let cx =
SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size);
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) {
Expand Down
35 changes: 31 additions & 4 deletions compiler/rustc_codegen_llvm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -187,6 +191,18 @@ impl<'a, 'll, CX: Borrow<SCx<'ll>>> GenericBuilder<'a, 'll, CX> {
load
}
}

fn ptrauth_operand_bundle(&mut self, llfn: &'ll Value) -> Option<llvm::OperandBundleBox<'ll>> {
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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_codegen_llvm/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions compiler/rustc_codegen_llvm/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading
Loading