unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
rust-lang/rust
senior8/10
compiler-internalserror-handlingapi-design

target_features: sse (or at least avx2) is incompatible with soft-float ABI

Project: rust-lang/rust | Language: Rust | Concepts: compiler-internals, error-handling, api-design | Difficulty: senior
Link: https://github.com/rust-lang/rust/pull/160302

The problem

On x86 and x86-64 targets with the soft-float ABI, enabling SIMD features like sse or avx2 is not supported by LLVM and can lead to backend crashes or silent miscompilation (e.g., SIMD code being silently replaced with slow software routines). Rust previously allowed this combination, which could result in broken or inefficient binaries without any warning.

Why it's non-trivial

A naive fix might simply block avx2 or sse when soft-float is enabled, but:

  • The interaction is not always a crash; sometimes code silently falls back to software, making the bug hard to detect.
  • The set of affected features is not fully documented by LLVM, so the fix must be conservative.
  • The warning must be surfaced in a way that users building affected binaries will see, even if the problem originates in a dependency.
  • The fix must not break valid code on other architectures or ABIs.

The approach

1. Add a new diagnostic for the x86 soft-float + sse incompatibility:

+#[derive(Diagnostic)]
+#[diag(
+    "enabling the `sse` target feature on the current target is unsupported due to LLVM backend issues"
+)]
+pub(crate) struct X86SoftfloatSse;

This defines a new compiler diagnostic to warn users about enabling sse (or features implying it) on a soft-float x86 target.

2. Register a new lint for this diagnostic:

+declare_lint! {
+    /// The `x86_softfloat_sse` lint detects usage of `#[target_feature(enable = "sse")]` or target
+    /// features that imply SSE on softfloat x86 and x86-64 targets. Enabling this target feature
+    /// in a soft-float configuration is not supported by LLVM and can lead to crashes.
+    pub X86_SOFTFLOAT_SSE,
+    Deny,
+    "detects code that could be affected by LLVM backend issues on x86 softfloat targets",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: fcw!(FutureReleaseError #117938),
+        report_in_deps: true,
+    };
+}

This creates a lint that will deny code using these features in the problematic configuration, and marks it as a "future compatibility warning" (FCW) so users are warned before it becomes a hard error.

3. Mark "sse" as incompatible with "soft-float" in the target feature constraints:

-FeatureConstraints { required: &["soft-float"], incompatible: &[] }
+FeatureConstraints { required: &["soft-float"], incompatible: &["sse"] }

This tells the compiler that "sse" and "soft-float" are not compatible, so it can check for and warn about this combination.

4. Emit the new lint when the combination is detected:

+                        } else if matches!(tcx.sess.target.arch, Arch::X86 | Arch::X86_64)
+                            && name.as_str() == "sse"
+                        {
+                            tcx.emit_node_span_lint(
+                                X86_SOFTFLOAT_SSE,
+                                tcx.local_def_id_to_hir_id(did),
+                                feature_span,
+                                diagnostics::X86SoftfloatSse,
+                            );

When the compiler encounters a #[target_feature(enable = "sse")] attribute (or similar) on an x86 soft-float target, it emits the new lint.

5. Update tests to cover both aarch64 and x86_64 soft-float cases:

-#[target_feature(enable = "neon")]
-//~^ERROR: enabling the `neon` target feature on the current target is unsound
-//~|WARN: previously accepted
+#[cfg_attr(aarch64, target_feature(enable = "neon"))]
+//[aarch64]~^ERROR: enabling the `neon` target feature on the current target is unsound
+//[aarch64]~|WARN: previously accepted
+#[cfg_attr(x86_64, target_feature(enable = "avx"))]
+//[x86_64]~^ERROR: enabling the `sse` target feature on the current target is unsupported
+//[x86_64]~|WARN: previously accepted

The test now checks that the new warning is emitted for x86_64 as well as aarch64.

6. Allow the new lint in internal crates that intentionally use these features:

+// Allow these FCW: anyone soundly using the intrinsics has to enable
+// the target feature, and that will generate a warning for them.
+#![allow(aarch64_softfloat_neon, x86_softfloat_sse)]

This ensures that the lint doesn't break internal uses where the warning is expected and handled.

From the review thread

Ranger3143:
A real-world data point in support of this FCW, from a target you can't crater — and a failure mode that isn't SIGILL.
...
Zero vector instructions. Every f32 op, including inside the target_feature(enable = "avx2") bodies, was lowered to soft-float libcalls.
It did not crash. No SIGILL, no link error, no warning. The binary booted and produced correct output — the soft-float path preserves single-rounding semantics — it was just catastrophically slow. ...
So: strong +1 on warning rather than staying silent. The SIGILL cases at least announce themselves. This one ships.

scottmcm:
Definitely happy to just say "no, you can't do that" for strange combinations.

Takeaways

  • Surface silent miscompilations, not just crashes: Compiler diagnostics should warn about configurations that silently degrade performance or correctness, not just those that crash.
  • Be conservative when backend support is unclear: If the underlying toolchain (e.g., LLVM) does not document support for certain feature combinations, it's safer to block or warn about them preemptively.
  • Design lints to reach end users, even for dependency code: Use mechanisms like "future compatibility warnings" and report_in_deps to ensure important warnings are visible to the people building affected binaries, not just library authors.