From 2b15cf6ae569c974c73c381c5e9feecb8e3fd28a Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 23 Apr 2024 16:04:17 +0200 Subject: [PATCH] Add `reserve-x18` target feature for aarch64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR resolves issue 121970 [1] by adding `reserve-x18` as a target feature for the aarch64 platform. Enabling the target feature marks the x18 register as reserved so that Rust doesn't use it as a temporary register when generating machine code. This means that passing the `-Ctarget-feature=+reserve-x18` flag will no longer result in the following warning: warning: unknown feature specified for `-Ctarget-feature`: `reserve-x18` | = note: it is still passed through to the codegen backend = help: consider filing a feature request Typically you will reserve the x18 register when you want to enable SCS (the shadow call stack sanitizer [9]), because it uses x18 to store a pointer to the shadow stack. However, it is important to not conflate `reserve-x18` with `-Zsanitizer=shadow-call-stack` — the latter depends on the former, but you can enable `reserve-x18` without enabling SCS. # ABI compatibility One concern that was brought up on issue 121970 [1] is that this flag affects the ABI. However, it does not affect the ABI in a way where it is a problem to mix code with and without the feature. From the ABI spec [2]: > X18 is the platform register and is reserved for the use of platform > ABIs. This is an additional temporary register on platforms that don't > assign a special meaning to it. That is to say, the register is either already reserved (this is the case on Android targets), or it is a caller-saved temporary register (this is the case on `aarch64-unknown-none`). Changing a register from caller-saved temporary register to reserved is not breaking, so selectively enabling `reserve-x18` on some compilation targets (or even on specific functions) cannot result in UB. That said, *removing* the `reserve-x18` target feature from a function can potentially trigger UB under some circumstances. This is because it is UB to link together `-Zsanitizer=shadow-call-stack` code with code where x18 is a temporary register. So enabling SCS in a binary requires that x18 is reserved globally. However, right now `-Zsanitizer=shadow-call-stack` can only be used on targets such as Android where x18 is never a temporary register, so this shouldn't be an issue for this PR. # Use in the Linux Kernel This motivation for this change is use in the Linux Kernel. When compiling Rust code for the kernel, the `aarch64-unknown-none` target is used, and this is a platform where x18 is a temporary caller-saved register by default. I am proposing to add this target feature so that the Linux Kernel can make x18 into a reserved register when necessary. The Linux Kernel has some cases where it needs to reserve x18, but does not pass the `-Zsanitizer=shadow-call-stack` flag. This is due to the dynamic shadow call stack feature [3], where the Linux Kernel is able to choose whether SCS should be enabled at boot. This works by having the compiler emit PACIASP/AUTIASP instructions instead of SCS_PUSH/SCS_POP. If Linux decides to enable SCS at boot, then it will use the unwind tables to find the PACIASP/AUTIASP instructions, and modify the machine code at runtime by replacing PACIASP/AUTIASP with SCS_PUSH/SCS_POP instructions in all functions. The transformation from PACIASP/AUTIASP to SCS_PUSH/SCS_POP is only valid if the x18 register is reserved globally. It is also possible to configure Linux to always use SCS. In this case, it does so using the `-fsanitize=shadow-call-stack` flag instead. The Linux Kernel configuration used by Android uses the dynamic shadow call stack feature in production, so `reserve-x18` is a prerequisite for using Rust in the Linux Kernel on Android. # Alternatives I have considered a few different alternatives. ## Add a `-Cfixed-x18` flag When compiling C code with clang or gcc, this is configured by passing the `-ffixed-x18` flag instead of using the target feature functionality. We could mirror that and add our own `-Cfixed-x18` flag to rustc. It would have the same effect as passing `-Ctarget-feature=+reserve-x18`. ## Use a different target The Rust compiler could provide a version of `aarch64-unknown-none` where x18 is reserved, and the Linux Kernel build system could switch to that target whenever `CONFIG_SHADOW_CALL_STACK` is enabled in the Linux build system. However, there are a few disadvantages with using that strategy for this kind of flag: * As the number of flags that are configured in this way increases, the number of targets increases exponentially. * It complicates the Kernel build system by significantly deviating from both clang and gcc on how this can be configured. My understanding is that the primary reason in favor of using a different target is that compiling the standard library yourself is unstable, so even if this target feature is added, there is no stable way to get a standard library compiled with `-Ctarget-feature=+reserve-x18`. However, as outlined in the abi stability section, there is no issue with enabling `reserve-x18` in some crates, but not in the standard library. The Linux Kernel already compiles the standard library manually. Using a prebuilt standard library is pretty unlikely to be the way forward for many other reasons unrelated to this flag. ## Use a `target.json` in the kernel The Linux Kernel is already using a `target.json` file for x86 targets due to issue 116852 [4], which is a similar issue with a different target feature. if cfg.has("ARM64") { panic!("arm64 uses the builtin rustc aarch64-unknown-none target"); } else if cfg.has("X86_64") { ts.push("arch", "x86_64"); ts.push( "data-layout", "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", ); let mut features = "-3dnow,-3dnowa,-mmx,+soft-float".to_string(); if cfg.has("MITIGATION_RETPOLINE") { features += ",+retpoline-external-thunk"; } ts.push("features", features); ts.push("llvm-target", "x86_64-linux-gnu"); ts.push("target-pointer-width", "64"); However, Linux is trying to move away from `target.json` targets because Rust considers `target.json` to be permanently unstable. # Future possibilities We could make it possible to use `-Zsanitizer=shadow-call-stack` together with `-Ctarget-feature=+reserve-x18` to enable SCS on targets where x18 is normally a temporary caller-saved register. This could be done similarly to `required_panic_strategy`, which enforces that all compilation units have a shared understanding of the panic strategy. That is, if `-Zsanitizer=shadow-call-stack` is passed, then fail compilation unless 1. the target is one where x18 is always reserved, or 2. `-Ctarget-feature=+reserve-x18` is passed as an argument to all crates in the crate graph. This lets us avoid adding any compiler flags combinations that trigger UB. # References 1. Discussion in the t-compiler stream on zulip. [5] 2. Discussion on the Linux Kernel mailing list. [6] 3. General issue on unrecognized target features. [7] 4. List of wanted Rust for Linux features. [8] Link: https://www.github.com/rust-lang/rust/issues/121970 [1] Link: https://developer.arm.com/documentation/den0024/a/The-ABI-for-ARM-64-bit-Architecture/Register-use-in-the-AArch64-Procedure-Call-Standard/Parameters-in-general-purpose-registers [2] Link: https://lore.kernel.org/all/20221027155908.1940624-4-ardb@kernel.org/ [3] Link: https://www.github.com/rust-lang/rust/issues/116852 [4] Link: https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/-ffixed-x18/near/430864291 [5] Link: https://lore.kernel.org/rust-for-linux/20240305-shadow-call-stack-v2-1-c7b4a3f4d616@google.com/ [6] Link: https://www.github.com/rust-lang/rust/issues/96472 [7] Link: https://www.github.com/Rust-for-Linux/linux/issues/355 [8] Link: https://www.github.com/rust-lang/rust/pull/98208 [9] Signed-off-by: Alice Ryhl --- compiler/rustc_target/src/target_features.rs | 1 + tests/ui/check-cfg/mix.stderr | 2 +- tests/ui/check-cfg/well-known-values.stderr | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index 1b507bb2a155e..9f60d8f6c4fbf 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -146,6 +146,7 @@ const AARCH64_ALLOWED_FEATURES: &[(&str, Stability)] = &[ ("rcpc2", Stable), // FEAT_RDM ("rdm", Stable), + ("reserve-x18", Stable), // FEAT_SB ("sb", Stable), // FEAT_SHA1 & FEAT_SHA256 diff --git a/tests/ui/check-cfg/mix.stderr b/tests/ui/check-cfg/mix.stderr index 557fdcbf38dbe..7a874dff9381d 100644 --- a/tests/ui/check-cfg/mix.stderr +++ b/tests/ui/check-cfg/mix.stderr @@ -251,7 +251,7 @@ warning: unexpected `cfg` condition value: `zebra` LL | cfg!(target_feature = "zebra"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, `bmi2` and 188 more + = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, `bmi2` and 189 more = note: see for more information about checking conditional configuration warning: 27 warnings emitted diff --git a/tests/ui/check-cfg/well-known-values.stderr b/tests/ui/check-cfg/well-known-values.stderr index 1863032c38659..0274ddbd0abfc 100644 --- a/tests/ui/check-cfg/well-known-values.stderr +++ b/tests/ui/check-cfg/well-known-values.stderr @@ -154,7 +154,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` LL | target_feature = "_UNEXPECTED_VALUE", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, `bmi2`, `bti`, `bulk-memory`, `c`, `cache`, `cmpxchg16b`, `crc`, `crt-static`, `d`, `d32`, `dit`, `doloop`, `dotprod`, `dpb`, `dpb2`, `dsp`, `dsp1e2`, `dspe60`, `e`, `e1`, `e2`, `edsp`, `elrw`, `ermsb`, `exception-handling`, `extended-const`, `f`, `f16c`, `f32mm`, `f64mm`, `fcma`, `fdivdu`, `fhm`, `flagm`, `float1e2`, `float1e3`, `float3e4`, `float7e60`, `floate1`, `fma`, `fp-armv8`, `fp16`, `fp64`, `fpuv2_df`, `fpuv2_sf`, `fpuv3_df`, `fpuv3_hf`, `fpuv3_hi`, `fpuv3_sf`, `frecipe`, `frintts`, `fxsr`, `gfni`, `hard-float`, `hard-float-abi`, `hard-tp`, `high-registers`, `hvx`, `hvx-length128b`, `hwdiv`, `i8mm`, `jsconv`, `lahfsahf`, `lasx`, `lbt`, `lor`, `lse`, `lsx`, `lvz`, `lzcnt`, `m`, `mclass`, `movbe`, `mp`, `mp1e2`, `msa`, `mte`, `multivalue`, `mutable-globals`, `neon`, `nontrapping-fptoint`, `nvic`, `paca`, `pacg`, `pan`, `pclmulqdq`, `pmuv3`, `popcnt`, `power10-vector`, `power8-altivec`, `power8-vector`, `power9-altivec`, `power9-vector`, `prfchw`, `rand`, `ras`, `rclass`, `rcpc`, `rcpc2`, `rdm`, `rdrand`, `rdseed`, `reference-types`, `relax`, `relaxed-simd`, `rtm`, `sb`, `sha`, `sha2`, `sha3`, `sign-ext`, `simd128`, `sm4`, `spe`, `ssbs`, `sse`, `sse2`, `sse3`, `sse4.1`, `sse4.2`, `sse4a`, `ssse3`, `sve`, `sve2`, `sve2-aes`, `sve2-bitperm`, `sve2-sha3`, `sve2-sm4`, `tbm`, `thumb-mode`, `thumb2`, `tme`, `trust`, `trustzone`, `ual`, `unaligned-scalar-mem`, `v`, `v5te`, `v6`, `v6k`, `v6t2`, `v7`, `v8`, `v8.1a`, `v8.2a`, `v8.3a`, `v8.4a`, `v8.5a`, `v8.6a`, `v8.7a`, `vaes`, `vdsp2e60f`, `vdspv1`, `vdspv2`, `vfp2`, `vfp3`, `vfp4`, `vh`, `virt`, `virtualization`, `vpclmulqdq`, `vsx`, `xsave`, `xsavec`, `xsaveopt`, `xsaves`, `zba`, `zbb`, `zbc`, `zbkb`, `zbkc`, `zbkx`, `zbs`, `zdinx`, `zfh`, `zfhmin`, `zfinx`, `zhinx`, `zhinxmin`, `zk`, `zkn`, `zknd`, `zkne`, `zknh`, `zkr`, `zks`, `zksed`, `zksh`, `zkt` + = note: expected values for `target_feature` are: `10e60`, `2e3`, `3e3r1`, `3e3r2`, `3e3r3`, `3e7`, `7e10`, `a`, `aclass`, `adx`, `aes`, `altivec`, `alu32`, `atomics`, `avx`, `avx2`, `avx512bf16`, `avx512bitalg`, `avx512bw`, `avx512cd`, `avx512dq`, `avx512er`, `avx512f`, `avx512fp16`, `avx512ifma`, `avx512pf`, `avx512vbmi`, `avx512vbmi2`, `avx512vl`, `avx512vnni`, `avx512vp2intersect`, `avx512vpopcntdq`, `bf16`, `bmi1`, `bmi2`, `bti`, `bulk-memory`, `c`, `cache`, `cmpxchg16b`, `crc`, `crt-static`, `d`, `d32`, `dit`, `doloop`, `dotprod`, `dpb`, `dpb2`, `dsp`, `dsp1e2`, `dspe60`, `e`, `e1`, `e2`, `edsp`, `elrw`, `ermsb`, `exception-handling`, `extended-const`, `f`, `f16c`, `f32mm`, `f64mm`, `fcma`, `fdivdu`, `fhm`, `flagm`, `float1e2`, `float1e3`, `float3e4`, `float7e60`, `floate1`, `fma`, `fp-armv8`, `fp16`, `fp64`, `fpuv2_df`, `fpuv2_sf`, `fpuv3_df`, `fpuv3_hf`, `fpuv3_hi`, `fpuv3_sf`, `frecipe`, `frintts`, `fxsr`, `gfni`, `hard-float`, `hard-float-abi`, `hard-tp`, `high-registers`, `hvx`, `hvx-length128b`, `hwdiv`, `i8mm`, `jsconv`, `lahfsahf`, `lasx`, `lbt`, `lor`, `lse`, `lsx`, `lvz`, `lzcnt`, `m`, `mclass`, `movbe`, `mp`, `mp1e2`, `msa`, `mte`, `multivalue`, `mutable-globals`, `neon`, `nontrapping-fptoint`, `nvic`, `paca`, `pacg`, `pan`, `pclmulqdq`, `pmuv3`, `popcnt`, `power10-vector`, `power8-altivec`, `power8-vector`, `power9-altivec`, `power9-vector`, `prfchw`, `rand`, `ras`, `rclass`, `rcpc`, `rcpc2`, `rdm`, `rdrand`, `rdseed`, `reference-types`, `relax`, `relaxed-simd`, `reserve-x18`, `rtm`, `sb`, `sha`, `sha2`, `sha3`, `sign-ext`, `simd128`, `sm4`, `spe`, `ssbs`, `sse`, `sse2`, `sse3`, `sse4.1`, `sse4.2`, `sse4a`, `ssse3`, `sve`, `sve2`, `sve2-aes`, `sve2-bitperm`, `sve2-sha3`, `sve2-sm4`, `tbm`, `thumb-mode`, `thumb2`, `tme`, `trust`, `trustzone`, `ual`, `unaligned-scalar-mem`, `v`, `v5te`, `v6`, `v6k`, `v6t2`, `v7`, `v8`, `v8.1a`, `v8.2a`, `v8.3a`, `v8.4a`, `v8.5a`, `v8.6a`, `v8.7a`, `vaes`, `vdsp2e60f`, `vdspv1`, `vdspv2`, `vfp2`, `vfp3`, `vfp4`, `vh`, `virt`, `virtualization`, `vpclmulqdq`, `vsx`, `xsave`, `xsavec`, `xsaveopt`, `xsaves`, `zba`, `zbb`, `zbc`, `zbkb`, `zbkc`, `zbkx`, `zbs`, `zdinx`, `zfh`, `zfhmin`, `zfinx`, `zhinx`, `zhinxmin`, `zk`, `zkn`, `zknd`, `zkne`, `zknh`, `zkr`, `zks`, `zksed`, `zksh`, `zkt` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE`