Is there any way to force the compiler to create "long calls" when branching to runtime linked functions.
My binary needs to be relocatable and to be able to call late linked functions potentially anywhere in memory.
Currently, the compiler is emitting immediate form BL instructions and a relocation type of R_ARM_THM_CALL, which has a limited range. For my scenario, it would be more ideal if the compiler emitted a register form BL instruction and a more flexible R_ARM_ABS32 for the value that is loaded into the register. Or something similar to that. What I need is something that allows branching over very large distances.
By way of comparison, GCC has the long_call function attribute, or the -mlong-calls option. However, there doesn't seem to be any equivalent option or attribute for Rust.
Is there any way of achieving this with current Rust? Or Or does anyone have any other suggestions.
1 Like
bjorn3
October 18, 2025, 11:30am
2
In what context? Embedded? Linux? A custom linking format? On Linux the linker is already supposed to generate the right PLT entries necessary to handle dynamic libraries if the compiler generates PLT relative rather than GOT relative calls for non-local function calls.
Embedded. The binary if a relocatable ELF but no PIE or PIC.
The target is ARMv6m
alice
October 18, 2025, 12:23pm
4
I know it probably gives a warning, but does
-Ctarget-feature=+long-calls
work?
1 Like
Yes! It does. At least the assembly looks correct.
Thanks
alice
October 18, 2025, 12:33pm
8
To silence the warning you can either use a custom target.json, or submit a PR to rustc to add the target feature.
I think this feature is very useful and something that should be blessed and for the warning to be removed. I'm very new to the Rust world so I'm unsure how to submit a PR. Can you guide me to the correct place?
Thanks again
alice
October 18, 2025, 12:45pm
10
You can use this PR of mine as a template:
master ← Darksonn:target-feature-reserve-x18
opened 06:20AM - 24 Apr 24 UTC
This PR resolves #121970 by adding `reserve-x18` as a target feature for the aar… ch64 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:
```text
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](https://github.com/rust-lang/rust/pull/98208)), because it uses x18 to store a pointer to the shadow stack. However, it is important to not conflate `reserve-x18` with the shadow call stack sanitizer — the latter depends on the former, but you can enable `reserve-x18` without enabling SCS.
# ABI compatibility
One concern that was brought up on #121970 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](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):
> 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](https://lore.kernel.org/all/20221027155908.1940624-4-ardb@kernel.org/), 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 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 #116852, which is a similar issue with a different target feature.
```Rust
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");
```
[link](https://github.com/Rust-for-Linux/linux/blob/2c1092853f163762ef0aabc551a630ef233e1be3/scripts/generate_rust_target.rs#L151-L165)
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](https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/-ffixed-x18/near/430864291).
2. Discussion [on the Linux Kernel mailing list](https://lore.kernel.org/rust-for-linux/20240305-shadow-call-stack-v2-1-c7b4a3f4d616@google.com/).
3. General issue on [unrecognized target features](https://github.com/rust-lang/rust/issues/96472).
4. List of [wanted Rust for Linux features](https://github.com/Rust-for-Linux/linux/issues/355).
Fixes #121970
r? rust-lang/compiler
That PR didn't get merged, but that's for reasons that don't apply in your case.
2 Likes