make target feature ABI check a hard error on ARM
Project: rust-lang/rust | Language: Rust | Concepts: error-handling, api-design, testing-strategy | Difficulty: mid
Link: https://github.com/rust-lang/rust/pull/161280
The problem
On ARM targets, if the required target features for a given ABI (like hard-float) are missing, LLVM 24 now emits a hard-to-understand backend error. Previously, Rust only emitted a warning for this situation. This means users would see a cryptic LLVM error instead of a clear Rust error, making it hard to diagnose and fix the issue.
Why it's non-trivial
A naive fix might just always error if features are missing, but:
- The handling must match what LLVM actually enforces, and only on ARM for now.
- Other targets may have different requirements, and making this a hard error everywhere could break code unnecessarily.
- The error message should be clear and actionable, not just a backend failure.
- The compiler's diagnostic infrastructure needs to distinguish between warnings and errors based on target and context.
The approach
1. Update the diagnostic struct to allow toggling between warning and error:
-pub(crate) struct AbiRequiredTargetFeature<'a> {
- pub feature: &'a str,
- pub enabled: &'a str,
-}
+pub(crate) struct AbiRequiredTargetFeature<'a> {
+ pub feature: &'a str,
+ pub enabled: &'a str,
+ #[note(
+ "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!"
+ )]
+ #[note(
+ "for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>"
+ )]
+ pub fcw: bool,
+}
What this does:
Adds a fcw (future compatibility warning) flag to the diagnostic struct, so the compiler can decide whether to show the "will become a hard error" note or not, depending on if it's currently a warning or an error.
2. Make the check a hard error specifically for ARM targets:
- for feature in abi_feature_constraints.required {
- if !sess.internal_target_features.contains(&Symbol::intern(feature)) {
- sess.dcx()
- .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "enabled" });
- }
- }
+ // Make this a hard error on ARM since starting with LLVM24, the backend will otherwise
+ // emit a (less friendly) hard error.
+ let hard_error = matches!(sess.target.arch, Arch::Arm);
+
+ for feature in abi_feature_constraints.required {
+ if !sess.internal_target_features.contains(&Symbol::intern(feature)) {
+ let diag = diagnostics::AbiRequiredTargetFeature {
+ feature,
+ enabled: "enabled",
+ fcw: !hard_error,
+ };
+ if hard_error {
+ sess.dcx().emit_err(diag);
+ } else {
+ sess.dcx().emit_warn(diag);
+ }
+ }
+ }
What this does:
Checks if the target architecture is ARM. If so, missing required features now cause a hard error (emit_err), not just a warning (emit_warn). The diagnostic struct is set up so the "future compatibility" note is only shown for warnings, not errors.
3. Update tests to expect an error on ARM, not just a warning:
-warning: target feature `fpregs` must be enabled to ensure that the ABI of the current target can be implemented correctly
- |
- = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
- = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
+error: target feature `fpregs` must be enabled to ensure that the ABI of the current target can be implemented correctly
-
-warning: 1 warning emitted
+error: aborting due to 1 previous error
What this does:
Changes the test expectation for ARM targets from a warning to a hard error, matching the new compiler behavior.
4. Adjust test directives to reflect the new error/warning split:
-// For now this is just a warning.
-//@ build-pass
+// On x86 this is just a warning.
+//@[x86] check-pass
+//@[arm] check-fail
What this does:
Makes the test pass on x86 (where it's still a warning) and fail on ARM (where it's now an error).
From the review thread
On the llvm pr adding the error there is a comment https://github.com/llvm/llvm-project/pull/111334#issuecomment-5328542824 arguing against this general requirement. Arguing this should only error if hard-float and soft-float ABI disagree on a function signature.
RalfJung:
We've been emitting the warning without regard for which function signatures are actually used, and so far nobody complained about those warnings AFAIK. Doing this for function signatures would require completely overhauling the check we do (and that would be hard to do without impacting compile times). Also, since the underlying ABI of many types is not guaranteed, checking the function signature would expose internal rustc implementation details in a way where changing such details (which we are explicitly permitted to do any time) could lead to compilation failures, so a proper check here would require developing an entire new set of guarantees around which types are definitely not using float registers for their ABI.
scottmcm:
Sounds good. Let's avoid the ABI footgun.
This passes my usual heuristic of "should it be a deny lint instead" because the rule for being an error is direct and it's not a place where there's a defined-but-unwanted behaviour. When it's a "that can't work at all" a hard error is good.
Takeaways
- Emit errors as early and clearly as possible when a configuration is fundamentally unsupported, rather than letting backend tools fail with cryptic messages.
- When upgrading a warning to a hard error, restrict the change to the minimal scope needed (e.g., only for specific targets) to avoid unnecessary breakage elsewhere.
- Diagnostic messages should be context-sensitive, providing future-compatibility notes only when appropriate (e.g., for warnings, not errors).