[llvm] [DAGCombiner] Fold NaN-guard fptosi/fptoui select to saturating variant (PR #201435)
Adel Ejjeh via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 17 08:49:36 PDT 2026
https://github.com/adelejjeh updated https://github.com/llvm/llvm-project/pull/201435
>From b45036b003493a19926b7135f65509393c822372 Mon Sep 17 00:00:00 2001
From: Adel Ejjeh <adel.ejjeh at amd.com>
Date: Wed, 3 Jun 2026 09:39:39 -0500
Subject: [PATCH 1/3] [DAGCombiner] Fold NaN-guard fptosi/fptoui select to
saturating variant
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fold select (setcc X, 0, SETUO), 0, (fp_to_sint/fp_to_uint X) to
fp_to_sint_sat/fp_to_uint_sat when the target supports it. Also looks
through an AND mask on the conversion result.
For in-range values the result is identical. For NaN both return 0.
For out-of-range, fp_to_sint produces poison while fp_to_sint_sat
produces a saturated value — replacing poison with a defined value is
valid refinement.
Alive2: https://alive2.llvm.org/ce/z/ZbFdM4
Assisted-by: Claude Code
---
llvm/include/llvm/CodeGen/TargetLowering.h | 5 +-
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 61 ++++++++
.../CodeGen/AArch64/fptoi-nan-guard-fold.ll | 127 ++++++++++++++++
.../CodeGen/AMDGPU/fptoi-nan-guard-fold.ll | 140 ++++++++++++++++++
4 files changed, 331 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
create mode 100644 llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
diff --git a/llvm/include/llvm/CodeGen/TargetLowering.h b/llvm/include/llvm/CodeGen/TargetLowering.h
index 82c47cce0f522..d7c8690821f4c 100644
--- a/llvm/include/llvm/CodeGen/TargetLowering.h
+++ b/llvm/include/llvm/CodeGen/TargetLowering.h
@@ -3596,8 +3596,9 @@ class LLVM_ABI TargetLoweringBase {
/// passed to the fp16 to fp conversion library function.
virtual bool shouldKeepZExtForFP16Conv() const { return false; }
- /// Should we generate fp_to_si_sat and fp_to_ui_sat from type FPVT to type VT
- /// from min(max(fptoi)) saturation patterns.
+ /// Should we generate fp_to_si_sat and fp_to_ui_sat from type FPVT to type
+ /// VT. Used when folding idioms into a saturating fp-to-int conversion, such
+ /// as min(max(fptoi)) clamps or NaN-guarded selects.
virtual bool shouldConvertFpToSat(unsigned Op, EVT FPVT, EVT VT) const {
return isOperationLegalOrCustom(Op, VT);
}
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 0c9820fb64de9..ef080ac97a642 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6283,6 +6283,62 @@ static SDValue PerformUMinFpToSatCombine(SDValue N0, SDValue N1, SDValue N2,
return DAG.getZExtOrTrunc(Sat, SDLoc(N0), N3.getValueType());
}
+// Fold a NaN-guard select of fp_to_sint/fp_to_uint into the saturating
+// variant, which returns 0 for NaN. Matches both SETUO/SETO forms and
+// optionally looks through an AND mask on the conversion result.
+static SDValue PerformNanGuardFpToSatCombine(SDNode *N, SelectionDAG &DAG) {
+ EVT VT = N->getValueType(0);
+ SDLoc DL(N);
+
+ // Match an isnan-guarded select. Require the compare to be single-use since
+ // folding does not remove it otherwise.
+ // select (setcc X, RHS, uno), 0, guarded
+ // select (setcc X, RHS, ord), guarded, 0
+ SDValue X, CmpRHS, GuardedVal;
+ auto NaNCheck = [&](ISD::CondCode CC) {
+ return m_OneUse(
+ m_SetCC(m_Value(X), m_Value(CmpRHS), m_SpecificCondCode(CC)));
+ };
+ if (!sd_match(N, m_SelectLike(NaNCheck(ISD::SETUO), m_Zero(),
+ m_Value(GuardedVal))) &&
+ !sd_match(
+ N, m_SelectLike(NaNCheck(ISD::SETO), m_Value(GuardedVal), m_Zero())))
+ return SDValue();
+
+ // The compare must test X for NaN: RHS is +/-0.0 (canonical isnan) or X
+ // itself (self-compare form).
+ auto *CmpRHSC = isConstOrConstSplatFP(CmpRHS);
+ if (CmpRHS != X && !(CmpRHSC && CmpRHSC->isZero()))
+ return SDValue();
+
+ // Peel an optional AND mask, then require fp_to_sint/fp_to_uint of the same
+ // X.
+ SDValue Mask;
+ SDValue Conv = GuardedVal;
+ if (Conv.getOpcode() == ISD::AND) {
+ Mask = Conv.getOperand(1);
+ Conv = Conv.getOperand(0);
+ }
+
+ unsigned NewOpc;
+ if (sd_match(Conv, m_FPToSI(m_Specific(X))))
+ NewOpc = ISD::FP_TO_SINT_SAT;
+ else if (sd_match(Conv, m_FPToUI(m_Specific(X))))
+ NewOpc = ISD::FP_TO_UINT_SAT;
+ else
+ return SDValue();
+
+ if (!DAG.getTargetLoweringInfo().shouldConvertFpToSat(NewOpc,
+ X.getValueType(), VT))
+ return SDValue();
+
+ SDValue Sat =
+ DAG.getNode(NewOpc, DL, VT, X, DAG.getValueType(VT.getScalarType()));
+ if (Mask)
+ Sat = DAG.getNode(ISD::AND, DL, VT, Sat, Mask);
+ return Sat;
+}
+
SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
SDValue N0 = N->getOperand(0);
SDValue N1 = N->getOperand(1);
@@ -13178,6 +13234,9 @@ SDValue DAGCombiner::visitSELECT(SDNode *N) {
}
}
+ if (SDValue S = PerformNanGuardFpToSatCombine(N, DAG))
+ return S;
+
if (TLI.isOperationLegal(ISD::SELECT_CC, VT) ||
(!LegalOperations &&
TLI.isOperationLegalOrCustom(ISD::SELECT_CC, VT))) {
@@ -14187,6 +14246,8 @@ SDValue DAGCombiner::visitVSELECT(SDNode *N) {
return S;
if (SDValue S = PerformUMinFpToSatCombine(LHS, RHS, N1, N2, CC, DAG))
return S;
+ if (SDValue S = PerformNanGuardFpToSatCombine(N, DAG))
+ return S;
// If this select has a condition (setcc) with narrower operands than the
// select, try to widen the compare to match the select width.
diff --git a/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll b/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
new file mode 100644
index 0000000000000..1fba2b2f9647c
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
@@ -0,0 +1,127 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=aarch64 < %s | FileCheck %s
+
+; The generic DAG combiner folds select(setcc X, 0, SETUO), 0, (fp_to_sint X)
+; into fp_to_sint_sat when the target supports it. On AArch64, fcvtzs/fcvtzu
+; natively saturate for NaN, so fp_to_sint_sat lowers to a single instruction.
+
+;; Positive tests
+
+define i32 @nan_guard_fptosi_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_f32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w0, s0
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_ord_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_ord_f32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w0, s0
+; CHECK-NEXT: ret
+ %cmp = fcmp ord float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 %conv, i32 0
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptoui_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptoui_f32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzu w0, s0
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptoui float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptoui_ord_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptoui_ord_f32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzu w0, s0
+; CHECK-NEXT: ret
+ %cmp = fcmp ord float %x, 0.000000e+00
+ %conv = fptoui float %x to i32
+ %sel = select i1 %cmp, i32 %conv, i32 0
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_and_mask(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_and_mask:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w8, s0
+; CHECK-NEXT: and w0, w8, #0x3
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %and = and i32 %conv, 3
+ %sel = select i1 %cmp, i32 0, i32 %and
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_f64(double %x) {
+; CHECK-LABEL: nan_guard_fptosi_f64:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w0, d0
+; CHECK-NEXT: ret
+ %cmp = fcmp uno double %x, 0.000000e+00
+ %conv = fptosi double %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_cmp_self(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_cmp_self:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w0, s0
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, %x
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define <4 x i32> @nan_guard_fptosi_v4f32(<4 x float> %x) {
+; CHECK-LABEL: nan_guard_fptosi_v4f32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs v0.4s, v0.4s
+; CHECK-NEXT: ret
+ %cmp = fcmp uno <4 x float> %x, zeroinitializer
+ %conv = fptosi <4 x float> %x to <4 x i32>
+ %sel = select <4 x i1> %cmp, <4 x i32> zeroinitializer, <4 x i32> %conv
+ ret <4 x i32> %sel
+}
+
+;; Negative tests
+
+define i32 @nan_guard_nonzero_constant(float %x) {
+; CHECK-LABEL: nan_guard_nonzero_constant:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w8, s0
+; CHECK-NEXT: fcmp s0, s0
+; CHECK-NEXT: mov w9, #42 // =0x2a
+; CHECK-NEXT: csel w0, w9, w8, vs
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 42, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_mismatched_operands(float %x, float %y) {
+; CHECK-LABEL: nan_guard_mismatched_operands:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w8, s1
+; CHECK-NEXT: fcmp s0, s0
+; CHECK-NEXT: csel w0, wzr, w8, vs
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %y to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
diff --git a/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll b/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
new file mode 100644
index 0000000000000..a4334a4fb8ffc
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
@@ -0,0 +1,140 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx90a < %s | FileCheck %s
+
+; The generic DAG combiner folds select(setcc X, 0, SETUO), 0, (fp_to_sint X)
+; into fp_to_sint_sat when the target supports it. On AMDGPU, the V_CVT
+; instructions already return 0 for NaN, so fp_to_sint_sat lowers to a single
+; conversion instruction with no extra compare/select overhead.
+
+;; Positive tests
+
+define i32 @nan_guard_fptosi_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_f32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_ord_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_ord_f32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp ord float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 %conv, i32 0
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptoui_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptoui_f32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_u32_f32_e32 v0, v0
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptoui float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptoui_ord_f32(float %x) {
+; CHECK-LABEL: nan_guard_fptoui_ord_f32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_u32_f32_e32 v0, v0
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp ord float %x, 0.000000e+00
+ %conv = fptoui float %x to i32
+ %sel = select i1 %cmp, i32 %conv, i32 0
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_and_mask(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_and_mask:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
+; CHECK-NEXT: v_and_b32_e32 v0, 3, v0
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %and = and i32 %conv, 3
+ %sel = select i1 %cmp, i32 0, i32 %and
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_f64(double %x) {
+; CHECK-LABEL: nan_guard_fptosi_f64:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f64_e32 v0, v[0:1]
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno double %x, 0.000000e+00
+ %conv = fptosi double %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_fptosi_cmp_self(float %x) {
+; CHECK-LABEL: nan_guard_fptosi_cmp_self:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, %x
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+define <4 x i32> @nan_guard_fptosi_v4f32(<4 x float> %x) {
+; CHECK-LABEL: nan_guard_fptosi_v4f32:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
+; CHECK-NEXT: v_cvt_i32_f32_e32 v1, v1
+; CHECK-NEXT: v_cvt_i32_f32_e32 v2, v2
+; CHECK-NEXT: v_cvt_i32_f32_e32 v3, v3
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno <4 x float> %x, zeroinitializer
+ %conv = fptosi <4 x float> %x to <4 x i32>
+ %sel = select <4 x i1> %cmp, <4 x i32> zeroinitializer, <4 x i32> %conv
+ ret <4 x i32> %sel
+}
+
+;; Negative tests
+
+define i32 @nan_guard_nonzero_constant(float %x) {
+; CHECK-LABEL: nan_guard_nonzero_constant:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v1, v0
+; CHECK-NEXT: v_cmp_o_f32_e32 vcc, v0, v0
+; CHECK-NEXT: v_cndmask_b32_e32 v0, 42, v1, vcc
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 42, i32 %conv
+ ret i32 %sel
+}
+
+define i32 @nan_guard_mismatched_operands(float %x, float %y) {
+; CHECK-LABEL: nan_guard_mismatched_operands:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v1, v1
+; CHECK-NEXT: v_cmp_o_f32_e32 vcc, v0, v0
+; CHECK-NEXT: v_cndmask_b32_e32 v0, 0, v1, vcc
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %y to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
>From e10192ddd212c9b26517999f40323351a50d3f5c Mon Sep 17 00:00:00 2001
From: Adel Ejjeh <adel.ejjeh at amd.com>
Date: Mon, 13 Jul 2026 09:21:02 -0500
Subject: [PATCH 2/3] [DAGCombiner] Simplify NaN-guard fptosi fold per review
Rewrite performNanGuardFpToSatCombine using sd_match with a new
m_AnyZeroFP matcher, require the compare to be single-use, and match
only the canonical +0.0 compare (dropping the self-compare form, which
earlier passes canonicalize away). Rename to lowercase per LLVM
convention and trim the comment.
Assisted-by: Claude Code
---
llvm/include/llvm/CodeGen/SDPatternMatch.h | 11 +++++
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 40 ++++++++-----------
.../CodeGen/AArch64/fptoi-nan-guard-fold.ll | 11 -----
.../CodeGen/AMDGPU/fptoi-nan-guard-fold.ll | 28 ++++++-------
4 files changed, 39 insertions(+), 51 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/SDPatternMatch.h b/llvm/include/llvm/CodeGen/SDPatternMatch.h
index b98b8837ad4f5..e2e20b14bb7f7 100644
--- a/llvm/include/llvm/CodeGen/SDPatternMatch.h
+++ b/llvm/include/llvm/CodeGen/SDPatternMatch.h
@@ -1367,6 +1367,17 @@ inline SpecificFP_match m_SpecificFP(double V) {
return SpecificFP_match(APFloat(V));
}
+struct AnyZeroFP_match {
+ template <typename MatchContext> bool match(const MatchContext &, SDValue N) {
+ if (ConstantFPSDNode *C = isConstOrConstSplatFP(N))
+ return C->isZero();
+ return false;
+ }
+};
+
+/// Match a floating-point +0.0 or -0.0 constant or splat.
+inline AnyZeroFP_match m_AnyZeroFP() { return AnyZeroFP_match(); }
+
struct Negative_match {
template <typename MatchContext>
bool match(const MatchContext &Ctx, SDValue N) {
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index ef080ac97a642..280ef9981e84b 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6284,31 +6284,23 @@ static SDValue PerformUMinFpToSatCombine(SDValue N0, SDValue N1, SDValue N2,
}
// Fold a NaN-guard select of fp_to_sint/fp_to_uint into the saturating
-// variant, which returns 0 for NaN. Matches both SETUO/SETO forms and
-// optionally looks through an AND mask on the conversion result.
-static SDValue PerformNanGuardFpToSatCombine(SDNode *N, SelectionDAG &DAG) {
+// variant, which returns 0 for NaN.
+static SDValue performNanGuardFpToSatCombine(SDNode *N, SelectionDAG &DAG) {
EVT VT = N->getValueType(0);
SDLoc DL(N);
- // Match an isnan-guarded select. Require the compare to be single-use since
- // folding does not remove it otherwise.
- // select (setcc X, RHS, uno), 0, guarded
- // select (setcc X, RHS, ord), guarded, 0
- SDValue X, CmpRHS, GuardedVal;
- auto NaNCheck = [&](ISD::CondCode CC) {
- return m_OneUse(
- m_SetCC(m_Value(X), m_Value(CmpRHS), m_SpecificCondCode(CC)));
- };
- if (!sd_match(N, m_SelectLike(NaNCheck(ISD::SETUO), m_Zero(),
- m_Value(GuardedVal))) &&
- !sd_match(
- N, m_SelectLike(NaNCheck(ISD::SETO), m_Value(GuardedVal), m_Zero())))
- return SDValue();
-
- // The compare must test X for NaN: RHS is +/-0.0 (canonical isnan) or X
- // itself (self-compare form).
- auto *CmpRHSC = isConstOrConstSplatFP(CmpRHS);
- if (CmpRHS != X && !(CmpRHSC && CmpRHSC->isZero()))
+ // Match an isnan-guarded select, requiring the compare to be single-use:
+ // select (setcc X, 0.0, uno), 0, guarded
+ // select (setcc X, 0.0, ord), guarded, 0
+ SDValue X, GuardedVal;
+ if (!sd_match(N,
+ m_SelectLike(m_OneUse(m_SetCC(m_Value(X), m_AnyZeroFP(),
+ m_SpecificCondCode(ISD::SETUO))),
+ m_Zero(), m_Value(GuardedVal))) &&
+ !sd_match(N,
+ m_SelectLike(m_OneUse(m_SetCC(m_Value(X), m_AnyZeroFP(),
+ m_SpecificCondCode(ISD::SETO))),
+ m_Value(GuardedVal), m_Zero())))
return SDValue();
// Peel an optional AND mask, then require fp_to_sint/fp_to_uint of the same
@@ -13234,7 +13226,7 @@ SDValue DAGCombiner::visitSELECT(SDNode *N) {
}
}
- if (SDValue S = PerformNanGuardFpToSatCombine(N, DAG))
+ if (SDValue S = performNanGuardFpToSatCombine(N, DAG))
return S;
if (TLI.isOperationLegal(ISD::SELECT_CC, VT) ||
@@ -14246,7 +14238,7 @@ SDValue DAGCombiner::visitVSELECT(SDNode *N) {
return S;
if (SDValue S = PerformUMinFpToSatCombine(LHS, RHS, N1, N2, CC, DAG))
return S;
- if (SDValue S = PerformNanGuardFpToSatCombine(N, DAG))
+ if (SDValue S = performNanGuardFpToSatCombine(N, DAG))
return S;
// If this select has a condition (setcc) with narrower operands than the
diff --git a/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll b/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
index 1fba2b2f9647c..7c5a3e63feafe 100644
--- a/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
+++ b/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
@@ -75,17 +75,6 @@ define i32 @nan_guard_fptosi_f64(double %x) {
ret i32 %sel
}
-define i32 @nan_guard_fptosi_cmp_self(float %x) {
-; CHECK-LABEL: nan_guard_fptosi_cmp_self:
-; CHECK: // %bb.0:
-; CHECK-NEXT: fcvtzs w0, s0
-; CHECK-NEXT: ret
- %cmp = fcmp uno float %x, %x
- %conv = fptosi float %x to i32
- %sel = select i1 %cmp, i32 0, i32 %conv
- ret i32 %sel
-}
-
define <4 x i32> @nan_guard_fptosi_v4f32(<4 x float> %x) {
; CHECK-LABEL: nan_guard_fptosi_v4f32:
; CHECK: // %bb.0:
diff --git a/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll b/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
index a4334a4fb8ffc..81f86767e75fd 100644
--- a/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
+++ b/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
@@ -82,26 +82,22 @@ define i32 @nan_guard_fptosi_f64(double %x) {
ret i32 %sel
}
-define i32 @nan_guard_fptosi_cmp_self(float %x) {
-; CHECK-LABEL: nan_guard_fptosi_cmp_self:
-; CHECK: ; %bb.0:
-; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
-; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
-; CHECK-NEXT: s_setpc_b64 s[30:31]
- %cmp = fcmp uno float %x, %x
- %conv = fptosi float %x to i32
- %sel = select i1 %cmp, i32 0, i32 %conv
- ret i32 %sel
-}
-
define <4 x i32> @nan_guard_fptosi_v4f32(<4 x float> %x) {
; CHECK-LABEL: nan_guard_fptosi_v4f32:
; CHECK: ; %bb.0:
; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
-; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
-; CHECK-NEXT: v_cvt_i32_f32_e32 v1, v1
-; CHECK-NEXT: v_cvt_i32_f32_e32 v2, v2
-; CHECK-NEXT: v_cvt_i32_f32_e32 v3, v3
+; CHECK-NEXT: v_cvt_i32_f32_e32 v5, v0
+; CHECK-NEXT: v_cvt_i32_f32_e32 v7, v1
+; CHECK-NEXT: v_cvt_i32_f32_e32 v6, v2
+; CHECK-NEXT: v_cvt_i32_f32_e32 v4, v3
+; CHECK-NEXT: v_cmp_o_f32_e32 vcc, v0, v0
+; CHECK-NEXT: v_cndmask_b32_e32 v0, 0, v5, vcc
+; CHECK-NEXT: v_cmp_o_f32_e32 vcc, v1, v1
+; CHECK-NEXT: v_cndmask_b32_e32 v1, 0, v7, vcc
+; CHECK-NEXT: v_cmp_o_f32_e32 vcc, v2, v2
+; CHECK-NEXT: v_cndmask_b32_e32 v2, 0, v6, vcc
+; CHECK-NEXT: v_cmp_o_f32_e32 vcc, v3, v3
+; CHECK-NEXT: v_cndmask_b32_e32 v3, 0, v4, vcc
; CHECK-NEXT: s_setpc_b64 s[30:31]
%cmp = fcmp uno <4 x float> %x, zeroinitializer
%conv = fptosi <4 x float> %x to <4 x i32>
>From d0e1b85fee765e2b0a3a8f3a8d430e54e887008b Mon Sep 17 00:00:00 2001
From: Adel Ejjeh <adel.ejjeh at amd.com>
Date: Fri, 17 Jul 2026 10:48:35 -0500
Subject: [PATCH 3/3] [DAGCombiner] Address review: commutative AND matcher,
freeze mask
Use a commutative m_And matcher for the optional mask on the guarded
conversion, and freeze the mask when reapplying it: for NaN inputs the
saturating conversion yields 0, so (and 0, Mask) must stay 0, which a
poison Mask would break. Expand the comment to list the matched forms,
and add tests for a non-constant mask, a commuted mask, and a
non-uno/ord compare predicate.
Assisted-by: Claude Code
---
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 34 +++++++-------
.../CodeGen/AArch64/fptoi-nan-guard-fold.ll | 42 +++++++++++++++++
.../CodeGen/AMDGPU/fptoi-nan-guard-fold.ll | 45 +++++++++++++++++++
3 files changed, 106 insertions(+), 15 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 280ef9981e84b..ae57944079eb4 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6289,9 +6289,13 @@ static SDValue performNanGuardFpToSatCombine(SDNode *N, SelectionDAG &DAG) {
EVT VT = N->getValueType(0);
SDLoc DL(N);
- // Match an isnan-guarded select, requiring the compare to be single-use:
- // select (setcc X, 0.0, uno), 0, guarded
- // select (setcc X, 0.0, ord), guarded, 0
+ // Match an isnan-guarded select, requiring the compare to be single-use.
+ // The guarded value is fp_to_sint/fp_to_uint of X, optionally masked by an
+ // AND:
+ // select (setcc X, 0.0, uno), 0, (fp_to_sint/uint X)
+ // select (setcc X, 0.0, ord), (fp_to_sint/uint X), 0
+ // select (setcc X, 0.0, uno), 0, (and (fp_to_sint/uint X), M)
+ // select (setcc X, 0.0, ord), (and (fp_to_sint/uint X), M), 0
SDValue X, GuardedVal;
if (!sd_match(N,
m_SelectLike(m_OneUse(m_SetCC(m_Value(X), m_AnyZeroFP(),
@@ -6303,19 +6307,15 @@ static SDValue performNanGuardFpToSatCombine(SDNode *N, SelectionDAG &DAG) {
m_Value(GuardedVal), m_Zero())))
return SDValue();
- // Peel an optional AND mask, then require fp_to_sint/fp_to_uint of the same
- // X.
+ // The guarded value must be fp_to_sint/fp_to_uint of the same X, optionally
+ // masked by a (commutative) AND.
SDValue Mask;
- SDValue Conv = GuardedVal;
- if (Conv.getOpcode() == ISD::AND) {
- Mask = Conv.getOperand(1);
- Conv = Conv.getOperand(0);
- }
-
unsigned NewOpc;
- if (sd_match(Conv, m_FPToSI(m_Specific(X))))
+ if (sd_match(GuardedVal, m_FPToSI(m_Specific(X))) ||
+ sd_match(GuardedVal, m_And(m_FPToSI(m_Specific(X)), m_Value(Mask))))
NewOpc = ISD::FP_TO_SINT_SAT;
- else if (sd_match(Conv, m_FPToUI(m_Specific(X))))
+ else if (sd_match(GuardedVal, m_FPToUI(m_Specific(X))) ||
+ sd_match(GuardedVal, m_And(m_FPToUI(m_Specific(X)), m_Value(Mask))))
NewOpc = ISD::FP_TO_UINT_SAT;
else
return SDValue();
@@ -6326,8 +6326,12 @@ static SDValue performNanGuardFpToSatCombine(SDNode *N, SelectionDAG &DAG) {
SDValue Sat =
DAG.getNode(NewOpc, DL, VT, X, DAG.getValueType(VT.getScalarType()));
- if (Mask)
- Sat = DAG.getNode(ISD::AND, DL, VT, Sat, Mask);
+ if (Mask) {
+ // For NaN inputs the saturating conversion yields 0, so (and 0, Mask) must
+ // stay 0 to match the original select. A poison Mask would make it poison,
+ // so freeze Mask to guarantee a defined value.
+ Sat = DAG.getNode(ISD::AND, DL, VT, Sat, DAG.getFreeze(Mask));
+ }
return Sat;
}
diff --git a/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll b/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
index 7c5a3e63feafe..e13ff98076800 100644
--- a/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
+++ b/llvm/test/CodeGen/AArch64/fptoi-nan-guard-fold.ll
@@ -64,6 +64,34 @@ define i32 @nan_guard_fptosi_and_mask(float %x) {
ret i32 %sel
}
+; AND mask with a non-constant operand.
+define i32 @nan_guard_fptosi_and_mask_var(float %x, i32 %m) {
+; CHECK-LABEL: nan_guard_fptosi_and_mask_var:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w8, s0
+; CHECK-NEXT: and w0, w8, w0
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %and = and i32 %conv, %m
+ %sel = select i1 %cmp, i32 0, i32 %and
+ ret i32 %sel
+}
+
+; AND mask with a non-constant operand, commuted.
+define i32 @nan_guard_fptosi_and_mask_commuted(float %x, i32 %m) {
+; CHECK-LABEL: nan_guard_fptosi_and_mask_commuted:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w8, s0
+; CHECK-NEXT: and w0, w8, w0
+; CHECK-NEXT: ret
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %and = and i32 %m, %conv
+ %sel = select i1 %cmp, i32 0, i32 %and
+ ret i32 %sel
+}
+
define i32 @nan_guard_fptosi_f64(double %x) {
; CHECK-LABEL: nan_guard_fptosi_f64:
; CHECK: // %bb.0:
@@ -114,3 +142,17 @@ define i32 @nan_guard_mismatched_operands(float %x, float %y) {
%sel = select i1 %cmp, i32 0, i32 %conv
ret i32 %sel
}
+
+; Compare is a different predicate (not uno/ord) -- should not fold.
+define i32 @nan_guard_wrong_predicate(float %x) {
+; CHECK-LABEL: nan_guard_wrong_predicate:
+; CHECK: // %bb.0:
+; CHECK-NEXT: fcvtzs w8, s0
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: csel w0, wzr, w8, mi
+; CHECK-NEXT: ret
+ %cmp = fcmp olt float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
diff --git a/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll b/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
index 81f86767e75fd..29e04eae1a90c 100644
--- a/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
+++ b/llvm/test/CodeGen/AMDGPU/fptoi-nan-guard-fold.ll
@@ -70,6 +70,36 @@ define i32 @nan_guard_fptosi_and_mask(float %x) {
ret i32 %sel
}
+; AND mask with a non-constant operand.
+define i32 @nan_guard_fptosi_and_mask_var(float %x, i32 %m) {
+; CHECK-LABEL: nan_guard_fptosi_and_mask_var:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
+; CHECK-NEXT: v_and_b32_e32 v0, v0, v1
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %and = and i32 %conv, %m
+ %sel = select i1 %cmp, i32 0, i32 %and
+ ret i32 %sel
+}
+
+; AND mask with a non-constant operand, commuted.
+define i32 @nan_guard_fptosi_and_mask_commuted(float %x, i32 %m) {
+; CHECK-LABEL: nan_guard_fptosi_and_mask_commuted:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v0, v0
+; CHECK-NEXT: v_and_b32_e32 v0, v0, v1
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp uno float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %and = and i32 %m, %conv
+ %sel = select i1 %cmp, i32 0, i32 %and
+ ret i32 %sel
+}
+
define i32 @nan_guard_fptosi_f64(double %x) {
; CHECK-LABEL: nan_guard_fptosi_f64:
; CHECK: ; %bb.0:
@@ -134,3 +164,18 @@ define i32 @nan_guard_mismatched_operands(float %x, float %y) {
%sel = select i1 %cmp, i32 0, i32 %conv
ret i32 %sel
}
+
+; Compare is a different predicate (not uno/ord) -- should not fold.
+define i32 @nan_guard_wrong_predicate(float %x) {
+; CHECK-LABEL: nan_guard_wrong_predicate:
+; CHECK: ; %bb.0:
+; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT: v_cvt_i32_f32_e32 v1, v0
+; CHECK-NEXT: v_cmp_ngt_f32_e32 vcc, 0, v0
+; CHECK-NEXT: v_cndmask_b32_e32 v0, 0, v1, vcc
+; CHECK-NEXT: s_setpc_b64 s[30:31]
+ %cmp = fcmp olt float %x, 0.000000e+00
+ %conv = fptosi float %x to i32
+ %sel = select i1 %cmp, i32 0, i32 %conv
+ ret i32 %sel
+}
More information about the llvm-commits
mailing list