[llvm] [X86] Diagnose silent soft-float ABI fallback in the backend (PR #224891)
Phoebe Wang via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 20 05:25:49 PDT 2026
https://github.com/phoebewang updated https://github.com/llvm/llvm-project/pull/224891
>From 8fb1fe751769996e1f3907ebf6438772bf49c3ac Mon Sep 17 00:00:00 2001
From: Phoebe Wang <phoebe.wang at intel.com>
Date: Sun, 20 Sep 2026 00:43:57 -0700
Subject: [PATCH 1/2] [X86] Diagnose silent soft-float ABI fallback in the
backend
Fixes: #111406
Co-Authored-By: Claude Opus 4.8 <noreply at anthropic.com>
---
llvm/docs/ReleaseNotes.md | 4 ++
llvm/lib/Target/X86/X86TargetMachine.cpp | 31 ++++++++++++++
.../X86/x86-64-no-fpregs-fp-abi-error.ll | 41 +++++++++++++++++++
3 files changed, 76 insertions(+)
create mode 100644 llvm/test/CodeGen/X86/x86-64-no-fpregs-fp-abi-error.ll
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 739cf86ad54bb..792b8a7d9da61 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -256,6 +256,10 @@ Makes programs 10x faster by doing Special New Thing.
### Changes to the X86 Backend
+* On 64-bit targets, returning a floating-point value when both SSE and x87 are
+ disabled is now an error. Previously this would silently fall back to the
+ soft-float ABI, producing a silent ABI mismatch.
+
### Changes to the OCaml bindings
### Changes to the Python bindings
diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp
index 886405a0c7bae..15afc45718f15 100644
--- a/llvm/lib/Target/X86/X86TargetMachine.cpp
+++ b/llvm/lib/Target/X86/X86TargetMachine.cpp
@@ -37,7 +37,10 @@
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Function.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/InstrTypes.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Pass.h"
@@ -410,9 +413,37 @@ TargetPassConfig *X86TargetMachine::createPassConfig(PassManagerBase &PM) {
return new X86PassConfig(*this, PM);
}
+static void diagnoseX86FPABI(const Function &F, const X86Subtarget &ST) {
+ // A soft-float ABI has no SSE-register requirement to violate, and only the
+ // 64-bit ABI returns scalar FP in SSE registers. When x87 is available the
+ // illegal FP return is diagnosed during lowering instead.
+ if (ST.useSoftFloat() || !ST.is64Bit() || ST.hasX87())
+ return;
+
+ LLVMContext &Ctx = F.getContext();
+ auto CheckReturnType = [&](Type *RetTy, const DiagnosticLocation &Loc) {
+ if (!ST.hasSSE1() && RetTy->isFloatTy())
+ Ctx.diagnose(DiagnosticInfoUnsupported(
+ F, "SSE register return with SSE disabled", Loc));
+ else if (!ST.hasSSE2() && (RetTy->isDoubleTy() || RetTy->is16bitFPTy()))
+ Ctx.diagnose(DiagnosticInfoUnsupported(
+ F, "SSE2 register return with SSE2 disabled", Loc));
+ };
+
+ CheckReturnType(F.getReturnType(), DiagnosticLocation(F.getSubprogram()));
+ for (const Instruction &I : instructions(F)) {
+ const auto *CB = dyn_cast<CallBase>(&I);
+ if (!CB || CB->isInlineAsm() ||
+ (CB->getCalledFunction() && CB->getCalledFunction()->isIntrinsic()))
+ continue;
+ CheckReturnType(CB->getFunctionType()->getReturnType(), CB->getDebugLoc());
+ }
+}
+
MachineFunctionInfo *X86TargetMachine::createMachineFunctionInfo(
BumpPtrAllocator &Allocator, const Function &F,
const TargetSubtargetInfo *STI) const {
+ diagnoseX86FPABI(F, *static_cast<const X86Subtarget *>(STI));
return X86MachineFunctionInfo::create<X86MachineFunctionInfo>(Allocator, F,
STI);
}
diff --git a/llvm/test/CodeGen/X86/x86-64-no-fpregs-fp-abi-error.ll b/llvm/test/CodeGen/X86/x86-64-no-fpregs-fp-abi-error.ll
new file mode 100644
index 0000000000000..5b4f79abfddb5
--- /dev/null
+++ b/llvm/test/CodeGen/X86/x86-64-no-fpregs-fp-abi-error.ll
@@ -0,0 +1,41 @@
+; RUN: not llc -mtriple=x86_64-- -mattr=-sse,-x87 %s -o /dev/null 2>&1 | FileCheck %s --check-prefixes=CHECK,NOSSE --implicit-check-not=error:
+; RUN: not llc -mtriple=x86_64-- -mattr=-sse2,-x87 %s -o /dev/null 2>&1 | FileCheck %s --check-prefixes=CHECK --implicit-check-not=error:
+
+; On x86-64 the hard-float ABI returns scalar floating-point values in SSE
+; registers. When the required SSE feature and x87 are both disabled the type
+; is softened and the ABI silently degrades to soft-float. Diagnose the
+; mismatch instead. See https://github.com/llvm/llvm-project/issues/111406.
+
+; NOSSE: error: {{.*}} in function ret_float float (float): SSE register return with SSE disabled
+define float @ret_float(float %a) {
+ ret float %a
+}
+
+; CHECK: error: {{.*}} in function ret_double double (double): SSE2 register return with SSE2 disabled
+define double @ret_double(double %a) {
+ ret double %a
+}
+
+; CHECK: error: {{.*}} in function ret_half half (half): SSE2 register return with SSE2 disabled
+define half @ret_half(half %a) {
+ ret half %a
+}
+
+; A call whose result is returned in an SSE register is diagnosed as well.
+; NOSSE: error: {{.*}} in function call_float {{.*}}: SSE register return with SSE disabled
+define void @call_float() {
+ %r = call float @extern_float()
+ ret void
+}
+
+; An explicit soft-float ABI has no SSE-register requirement to violate.
+define float @soft_float_ok(float %a) "target-features"="+soft-float,-sse,-x87" {
+ ret float %a
+}
+
+; Integer-only functions are fine.
+define i32 @no_fp(i32 %a) {
+ ret i32 %a
+}
+
+declare float @extern_float()
>From bc8514c1a417e1e76e7c1c29bf3a688c7813ec9e Mon Sep 17 00:00:00 2001
From: Phoebe Wang <phoebe.wang at intel.com>
Date: Sun, 20 Sep 2026 05:25:35 -0700
Subject: [PATCH 2/2] Move diagnose to X86ISelLoweringCall.cpp
---
llvm/lib/Target/X86/X86ISelLoweringCall.cpp | 22 +++++++++++++++
llvm/lib/Target/X86/X86TargetMachine.cpp | 31 ---------------------
2 files changed, 22 insertions(+), 31 deletions(-)
diff --git a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
index 9a03da14ee10e..13de0bb4cf261 100644
--- a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
+++ b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
@@ -843,6 +843,17 @@ X86TargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv,
// not enabled.
errorUnsupported(DAG, dl, "SSE2 register return with SSE2 disabled");
VA.convertToReg(X86::FP0); // Set reg to FP0, avoid hitting asserts.
+ } else if (Subtarget.is64Bit() && !Subtarget.useSoftFloat() &&
+ !Subtarget.hasX87()) {
+ // On 64-bit targets the hard-float ABI returns scalar FP in XMM
+ // registers. When both SSE and x87 are disabled the value is softened to
+ // a GPR here, silently degrading to the soft-float ABI.
+ EVT ArgVT = Outs[OutsIndex].ArgVT;
+ if (!Subtarget.hasSSE1() && ArgVT == MVT::f32)
+ errorUnsupported(DAG, dl, "SSE register return with SSE disabled");
+ else if (!Subtarget.hasSSE2() &&
+ (ArgVT == MVT::f64 || ArgVT == MVT::f16 || ArgVT == MVT::bf16))
+ errorUnsupported(DAG, dl, "SSE2 register return with SSE2 disabled");
}
// Returns in ST0/ST1 are handled specially: these are pushed as operands to
@@ -1187,6 +1198,17 @@ SDValue X86TargetLowering::LowerCallResult(
VA.convertToReg(X86::FP1); // Set reg to FP1, avoid hitting asserts.
else
VA.convertToReg(X86::FP0); // Set reg to FP0, avoid hitting asserts.
+ } else if (Subtarget.is64Bit() && !Subtarget.useSoftFloat() &&
+ !Subtarget.hasX87()) {
+ // On 64-bit targets the hard-float ABI returns scalar FP in XMM
+ // registers. When both SSE and x87 are disabled the result is softened to
+ // a GPR here, silently degrading to the soft-float ABI.
+ EVT ArgVT = Ins[I].ArgVT;
+ if (!Subtarget.hasSSE1() && ArgVT == MVT::f32)
+ errorUnsupported(DAG, dl, "SSE register return with SSE disabled");
+ else if (!Subtarget.hasSSE2() &&
+ (ArgVT == MVT::f64 || ArgVT == MVT::f16 || ArgVT == MVT::bf16))
+ errorUnsupported(DAG, dl, "SSE2 register return with SSE2 disabled");
}
// If we prefer to use the value in xmm registers, copy it out as f80 and
diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp
index 15afc45718f15..886405a0c7bae 100644
--- a/llvm/lib/Target/X86/X86TargetMachine.cpp
+++ b/llvm/lib/Target/X86/X86TargetMachine.cpp
@@ -37,10 +37,7 @@
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/DataLayout.h"
-#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Function.h"
-#include "llvm/IR/InstIterator.h"
-#include "llvm/IR/InstrTypes.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Pass.h"
@@ -413,37 +410,9 @@ TargetPassConfig *X86TargetMachine::createPassConfig(PassManagerBase &PM) {
return new X86PassConfig(*this, PM);
}
-static void diagnoseX86FPABI(const Function &F, const X86Subtarget &ST) {
- // A soft-float ABI has no SSE-register requirement to violate, and only the
- // 64-bit ABI returns scalar FP in SSE registers. When x87 is available the
- // illegal FP return is diagnosed during lowering instead.
- if (ST.useSoftFloat() || !ST.is64Bit() || ST.hasX87())
- return;
-
- LLVMContext &Ctx = F.getContext();
- auto CheckReturnType = [&](Type *RetTy, const DiagnosticLocation &Loc) {
- if (!ST.hasSSE1() && RetTy->isFloatTy())
- Ctx.diagnose(DiagnosticInfoUnsupported(
- F, "SSE register return with SSE disabled", Loc));
- else if (!ST.hasSSE2() && (RetTy->isDoubleTy() || RetTy->is16bitFPTy()))
- Ctx.diagnose(DiagnosticInfoUnsupported(
- F, "SSE2 register return with SSE2 disabled", Loc));
- };
-
- CheckReturnType(F.getReturnType(), DiagnosticLocation(F.getSubprogram()));
- for (const Instruction &I : instructions(F)) {
- const auto *CB = dyn_cast<CallBase>(&I);
- if (!CB || CB->isInlineAsm() ||
- (CB->getCalledFunction() && CB->getCalledFunction()->isIntrinsic()))
- continue;
- CheckReturnType(CB->getFunctionType()->getReturnType(), CB->getDebugLoc());
- }
-}
-
MachineFunctionInfo *X86TargetMachine::createMachineFunctionInfo(
BumpPtrAllocator &Allocator, const Function &F,
const TargetSubtargetInfo *STI) const {
- diagnoseX86FPABI(F, *static_cast<const X86Subtarget *>(STI));
return X86MachineFunctionInfo::create<X86MachineFunctionInfo>(Allocator, F,
STI);
}
More information about the llvm-commits
mailing list