[llvm] [SelectionDAG] Fix isKnownNeverNaN OR-logic for FMINNUM/FMAXNUM with SNaN (PR #204736)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 29 05:01:34 PDT 2026
https://github.com/ayrai-gb updated https://github.com/llvm/llvm-project/pull/204736
>From 2407462f84e7b63463552ea57c83e06cfff03475 Mon Sep 17 00:00:00 2001
From: Ayush Rai <your-email at amd.com>
Date: Mon, 29 Jun 2026 17:00:19 +0530
Subject: [PATCH] [SelectionDAG] Fix isKnownNeverNaN for FMINNUM/FMAXNUM with
signaling NaNs
Co-authored-by: Cursor <cursoragent at cursor.com>
---
.../lib/CodeGen/SelectionDAG/SelectionDAG.cpp | 18 ++++++--
.../fminmaxnum-snan-isknownnevernan.ll | 44 +++++++++++++++++++
2 files changed, 58 insertions(+), 4 deletions(-)
create mode 100644 llvm/test/CodeGen/AArch64/fminmaxnum-snan-isknownnevernan.ll
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
index c542f8e7cc20b..a72885dcbdbbe 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
@@ -6423,10 +6423,20 @@ bool SelectionDAG::isKnownNeverNaN(SDValue Op, const APInt &DemandedElts,
case ISD::FMAXNUM:
case ISD::FMINIMUMNUM:
case ISD::FMAXIMUMNUM: {
- // Only one needs to be known not-nan, since it will be returned if the
- // other ends up being one.
- return isKnownNeverNaN(Op.getOperand(0), DemandedElts, SNaN, Depth + 1) ||
- isKnownNeverNaN(Op.getOperand(1), DemandedElts, SNaN, Depth + 1);
+ // The result is a NaN only if both operands are NaN, so it is never NaN
+ // (and hence never a signaling NaN) if either operand is known to never be
+ // NaN.
+ if (isKnownNeverNaN(Op.getOperand(0), DemandedElts, false, Depth + 1) ||
+ isKnownNeverNaN(Op.getOperand(1), DemandedElts, false, Depth + 1))
+ return true;
+ // Otherwise, for the signaling-NaN query, these do not quiet a signaling
+ // NaN: an input signaling NaN may be returned unchanged, so the result is
+ // never a signaling NaN only if both operands are known to never be one.
+ if (SNaN &&
+ isKnownNeverNaN(Op.getOperand(0), DemandedElts, true, Depth + 1) &&
+ isKnownNeverNaN(Op.getOperand(1), DemandedElts, true, Depth + 1))
+ return true;
+ return false;
}
case ISD::FMINNUM_IEEE:
case ISD::FMAXNUM_IEEE: {
diff --git a/llvm/test/CodeGen/AArch64/fminmaxnum-snan-isknownnevernan.ll b/llvm/test/CodeGen/AArch64/fminmaxnum-snan-isknownnevernan.ll
new file mode 100644
index 0000000000000..1b904ce603c23
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/fminmaxnum-snan-isknownnevernan.ll
@@ -0,0 +1,44 @@
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu < %s | FileCheck %s
+
+;; SelectionDAG::isKnownNeverNaN answers "is this value never a signaling NaN?"
+;; for FMINNUM/FMAXNUM/FMINIMUMNUM/FMAXIMUMNUM. These do not quiet a signaling
+;; NaN: when an operand is a signaling NaN it may be returned unchanged, so the
+;; result is known-never-signaling only if *both* operands are. The old code
+;; used OR (it sufficed for one operand to be known never-NaN), which is correct
+;; for the plain "never any NaN" query but unsound for the signaling query.
+;;
+;; This is observable through the bf16 narrowing in FP_TO_BF16 expansion, which
+;; inserts an FCANONICALIZE (quieting) step unless the source is known never to
+;; be a signaling NaN. On AArch64 that quieting lowers to setting the f32 quiet
+;; bit (orr #0x400000) under an unordered check (fcmp + csel vs).
+
+;; One operand is canonicalized (known never-sNaN), the other is unknown. The
+;; minnum result may be the unknown operand, so a signaling NaN can survive and
+;; the narrowing must quiet it. With the buggy OR-logic the source was wrongly
+;; proven never-sNaN and the quieting was dropped.
+; CHECK-LABEL: mixed:
+; CHECK: fminnm s0, s0, s1
+; CHECK: orr {{w[0-9]+}}, {{w[0-9]+}}, #0x400000
+; CHECK: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, vs
+define bfloat @mixed(float %a, float %b) {
+ %na = call float @llvm.canonicalize.f32(float %a)
+ %m = call float @llvm.minnum.f32(float %na, float %b)
+ %r = fptrunc float %m to bfloat
+ ret bfloat %r
+}
+
+;; Both operands are canonicalized, so the minnum result really is never a
+;; signaling NaN and the narrowing may skip quieting. This guards against the
+;; fix becoming over-broad (the AND must still fold to true here).
+; CHECK-LABEL: both:
+; CHECK-NOT: #0x400000
+define bfloat @both(float %a, float %b) {
+ %na = call float @llvm.canonicalize.f32(float %a)
+ %nb = call float @llvm.canonicalize.f32(float %b)
+ %m = call float @llvm.minnum.f32(float %na, float %nb)
+ %r = fptrunc float %m to bfloat
+ ret bfloat %r
+}
+
+declare float @llvm.canonicalize.f32(float)
+declare float @llvm.minnum.f32(float, float)
More information about the llvm-commits
mailing list