[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