[llvm] [AMDGPU] Fold isnan+fptosi select to fptosi (PR #200960)
Adel Ejjeh via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 2 07:43:54 PDT 2026
https://github.com/adelejjeh updated https://github.com/llvm/llvm-project/pull/200960
>From 06e563d5f219ed2953b00e9c2a294110ebb4425e Mon Sep 17 00:00:00 2001
From: Adel Ejjeh <adel.ejjeh at amd.com>
Date: Mon, 1 Jun 2026 17:10:40 -0500
Subject: [PATCH 1/2] [AMDGPU] Fold isnan+fptosi select to fptosi
Fold `select (fcmp uno X, NonNaN), 0, (fp_to_sint X)` to `fp_to_sint X`
on AMDGPU, and the inverted `select (fcmp ord ...)` form. Also looks
through an AND mask on the result.
V_CVT_I32_F32, V_CVT_U32_F32, V_CVT_I32_F64, and V_CVT_U32_F64 all
return 0 for NaN inputs, making the isnan guard redundant at the
hardware level.
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
llvm/lib/Target/AMDGPU/SIISelLowering.cpp | 35 ++++++
.../CodeGen/AMDGPU/fptosi-nan-guard-fold.ll | 118 ++++++++++++++++++
2 files changed, 153 insertions(+)
create mode 100644 llvm/test/CodeGen/AMDGPU/fptosi-nan-guard-fold.ll
diff --git a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
index 86f2479490c29..ec38273922459 100644
--- a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
+++ b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
@@ -18412,6 +18412,41 @@ SDValue SITargetLowering::performSelectCombine(SDNode *N,
SDValue RHS = Cond.getOperand(1);
ISD::CondCode CC = cast<CondCodeSDNode>(Cond.getOperand(2))->get();
+ // Fold: select (setcc X, NonNaN, SETUO), 0, (fp_to_sint X) -> fp_to_sint X
+ // Fold: select (setcc X, NonNaN, SETO), (fp_to_sint X), 0 -> fp_to_sint X
+ // Also look through an AND mask on the fp_to_sint result.
+ // V_CVT_I32_F32, V_CVT_U32_F32, V_CVT_I32_F64, V_CVT_U32_F64 already
+ // return 0 for NaN inputs, so the isnan guard is redundant.
+ {
+ // Identify the guarded value and the zero constant based on SETUO/SETO.
+ SDValue GuardedVal;
+ if (CC == ISD::SETUO && isNullConstant(TrueVal))
+ GuardedVal = FalseVal;
+ else if (CC == ISD::SETO && isNullConstant(FalseVal))
+ GuardedVal = TrueVal;
+
+ if (GuardedVal) {
+ // RHS of the comparison must be a known non-NaN value or equal to LHS
+ // (fcmp uno X, X). SETUO is true when either operand is NaN, so we
+ // must ensure RHS cannot independently be NaN.
+ bool RHSSafe =
+ RHS == LHS || isNullFPConstant(RHS) ||
+ (isa<ConstantFPSDNode>(RHS) && !cast<ConstantFPSDNode>(RHS)->isNaN());
+
+ if (RHSSafe) {
+ // Look through an AND mask to find the fp_to_sint/uint underneath.
+ SDValue Conv = GuardedVal;
+ if (Conv.getOpcode() == ISD::AND)
+ Conv = Conv.getOperand(0);
+
+ if ((Conv.getOpcode() == ISD::FP_TO_SINT ||
+ Conv.getOpcode() == ISD::FP_TO_UINT) &&
+ Conv.getOperand(0) == LHS && Conv.getValueType() == MVT::i32)
+ return GuardedVal;
+ }
+ }
+ }
+
bool isFloatingPoint = LHS.getValueType().isFloatingPoint();
bool isInteger = LHS.getValueType().isInteger();
diff --git a/llvm/test/CodeGen/AMDGPU/fptosi-nan-guard-fold.ll b/llvm/test/CodeGen/AMDGPU/fptosi-nan-guard-fold.ll
new file mode 100644
index 0000000000000..a2451450d803d
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/fptosi-nan-guard-fold.ll
@@ -0,0 +1,118 @@
+; RUN: llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx90a < %s | FileCheck %s
+
+; The AMDGPU v_cvt_i32_f32 / v_cvt_u32_f32 instructions already return 0 for
+; NaN inputs. The DAG combiner should fold away isnan guards that select 0 for
+; NaN, since the hardware already provides the desired behavior.
+
+; Basic: select (fcmp uno x, 0.0), 0, (fptosi x) -> fptosi x
+; CHECK-LABEL: nan_guard_fptosi_f32:
+; CHECK: v_cvt_i32_f32
+; CHECK-NOT: v_cmp_class
+; CHECK-NOT: v_cmp_u_f32
+; CHECK-NOT: v_cndmask
+; CHECK: s_setpc_b64
+define i32 @nan_guard_fptosi_f32(float %x) {
+ %conv = fptosi float %x to i32
+ %isnan = fcmp uno float %x, 0.0
+ %sel = select i1 %isnan, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+; Ordered form: select (fcmp ord x, 0.0), (fptosi x), 0 -> fptosi x
+; CHECK-LABEL: ord_guard_fptosi_f32:
+; CHECK: v_cvt_i32_f32
+; CHECK-NOT: v_cmp_class
+; CHECK-NOT: v_cmp_o_f32
+; CHECK-NOT: v_cndmask
+; CHECK: s_setpc_b64
+define i32 @ord_guard_fptosi_f32(float %x) {
+ %conv = fptosi float %x to i32
+ %isord = fcmp ord float %x, 0.0
+ %sel = select i1 %isord, i32 %conv, i32 0
+ ret i32 %sel
+}
+
+; With AND mask (the actual device-libs pattern):
+; select (fcmp uno x, 0.0), 0, (and (fptosi x), 3) -> and (fptosi x), 3
+; CHECK-LABEL: nan_guard_fptosi_and_mask:
+; CHECK: v_cvt_i32_f32
+; CHECK-NOT: v_cmp_class
+; CHECK-NOT: v_cmp_u_f32
+; CHECK-NOT: v_cndmask
+; CHECK: v_and_b32
+; CHECK: s_setpc_b64
+define i32 @nan_guard_fptosi_and_mask(float %x) {
+ %conv = fptosi float %x to i32
+ %and = and i32 %conv, 3
+ %isnan = fcmp uno float %x, 0.0
+ %sel = select i1 %isnan, i32 0, i32 %and
+ ret i32 %sel
+}
+
+; fptoui variant
+; CHECK-LABEL: nan_guard_fptoui_f32:
+; CHECK: v_cvt_u32_f32
+; CHECK-NOT: v_cmp_class
+; CHECK-NOT: v_cmp_u_f32
+; CHECK-NOT: v_cndmask
+; CHECK: s_setpc_b64
+define i32 @nan_guard_fptoui_f32(float %x) {
+ %conv = fptoui float %x to i32
+ %isnan = fcmp uno float %x, 0.0
+ %sel = select i1 %isnan, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+; f64 source
+; CHECK-LABEL: nan_guard_fptosi_f64:
+; CHECK: v_cvt_i32_f64
+; CHECK-NOT: v_cmp_class
+; CHECK-NOT: v_cmp_u_f64
+; CHECK-NOT: v_cndmask
+; CHECK: s_setpc_b64
+define i32 @nan_guard_fptosi_f64(double %x) {
+ %conv = fptosi double %x to i32
+ %isnan = fcmp uno double %x, 0.0
+ %sel = select i1 %isnan, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+; fcmp uno x, x form (also valid NaN check)
+; CHECK-LABEL: nan_guard_fptosi_cmp_self:
+; CHECK: v_cvt_i32_f32
+; CHECK-NOT: v_cmp_class
+; CHECK-NOT: v_cmp_u_f32
+; CHECK-NOT: v_cndmask
+; CHECK: s_setpc_b64
+define i32 @nan_guard_fptosi_cmp_self(float %x) {
+ %conv = fptosi float %x to i32
+ %isnan = fcmp uno float %x, %x
+ %sel = select i1 %isnan, i32 0, i32 %conv
+ ret i32 %sel
+}
+
+; Negative test: non-zero constant in the NaN arm should NOT fold.
+; CHECK-LABEL: nan_guard_nonzero_constant:
+; CHECK: v_cvt_i32_f32
+; CHECK: v_cmp
+; CHECK: v_cndmask
+; CHECK: s_setpc_b64
+define i32 @nan_guard_nonzero_constant(float %x) {
+ %conv = fptosi float %x to i32
+ %isnan = fcmp uno float %x, 0.0
+ %sel = select i1 %isnan, i32 42, i32 %conv
+ ret i32 %sel
+}
+
+; Negative test: mismatched operands (fcmp on x, fptosi on y) should NOT fold.
+; CHECK-LABEL: nan_guard_mismatched_operands:
+; CHECK: v_cvt_i32_f32
+; CHECK: v_cmp
+; CHECK: v_cndmask
+; CHECK: s_setpc_b64
+define i32 @nan_guard_mismatched_operands(float %x, float %y) {
+ %conv = fptosi float %y to i32
+ %isnan = fcmp uno float %x, 0.0
+ %sel = select i1 %isnan, i32 0, i32 %conv
+ ret i32 %sel
+}
>From 8a25cbe6045f11b6f419745ff45ace43f71754cd Mon Sep 17 00:00:00 2001
From: Adel Ejjeh <adel.ejjeh at amd.com>
Date: Tue, 2 Jun 2026 09:43:39 -0500
Subject: [PATCH 2/2] change to generate SAT nodes.
---
llvm/lib/Target/AMDGPU/SIISelLowering.cpp | 34 ++++++++++++++++-------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
index ec38273922459..d7ddf4992b17d 100644
--- a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
+++ b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
@@ -18412,11 +18412,9 @@ SDValue SITargetLowering::performSelectCombine(SDNode *N,
SDValue RHS = Cond.getOperand(1);
ISD::CondCode CC = cast<CondCodeSDNode>(Cond.getOperand(2))->get();
- // Fold: select (setcc X, NonNaN, SETUO), 0, (fp_to_sint X) -> fp_to_sint X
- // Fold: select (setcc X, NonNaN, SETO), (fp_to_sint X), 0 -> fp_to_sint X
+ // Fold: select (setcc X, NonNaN, SETUO), 0, (fp_to_sint X) -> fp_to_sint_sat X
+ // Fold: select (setcc X, NonNaN, SETO), (fp_to_sint X), 0 -> fp_to_sint_sat X
// Also look through an AND mask on the fp_to_sint result.
- // V_CVT_I32_F32, V_CVT_U32_F32, V_CVT_I32_F64, V_CVT_U32_F64 already
- // return 0 for NaN inputs, so the isnan guard is redundant.
{
// Identify the guarded value and the zero constant based on SETUO/SETO.
SDValue GuardedVal;
@@ -18430,19 +18428,35 @@ SDValue SITargetLowering::performSelectCombine(SDNode *N,
// (fcmp uno X, X). SETUO is true when either operand is NaN, so we
// must ensure RHS cannot independently be NaN.
bool RHSSafe =
- RHS == LHS || isNullFPConstant(RHS) ||
+ RHS == LHS ||
(isa<ConstantFPSDNode>(RHS) && !cast<ConstantFPSDNode>(RHS)->isNaN());
if (RHSSafe) {
+ // Generate the new node (ISD::FP_TO_[SU]INT_SAT) to replace the select.
+ SDValue NewNode;
+
// Look through an AND mask to find the fp_to_sint/uint underneath.
SDValue Conv = GuardedVal;
- if (Conv.getOpcode() == ISD::AND)
+ bool HasAndMask = false;
+ if (Conv.getOpcode() == ISD::AND) {
Conv = Conv.getOperand(0);
+ HasAndMask = true;
+ }
- if ((Conv.getOpcode() == ISD::FP_TO_SINT ||
- Conv.getOpcode() == ISD::FP_TO_UINT) &&
- Conv.getOperand(0) == LHS && Conv.getValueType() == MVT::i32)
- return GuardedVal;
+ if (Conv.getOperand(0) == LHS && Conv.getValueType() == MVT::i32) {
+ if (Conv.getOpcode() == ISD::FP_TO_SINT)
+ NewNode = DCI.DAG.getNode(ISD::FP_TO_SINT_SAT, SDLoc(N), MVT::i32, LHS, DCI.DAG.getValueType(MVT::i32));
+ if (Conv.getOpcode() == ISD::FP_TO_UINT)
+ NewNode = DCI.DAG.getNode(ISD::FP_TO_UINT_SAT, SDLoc(N), MVT::i32, LHS, DCI.DAG.getValueType(MVT::i32));
+ }
+
+ // Replace the node if we created a new one
+ if (NewNode) {
+ // If there was an AND mask, reapply it to the new node.
+ if (HasAndMask)
+ NewNode = DCI.DAG.getNode(ISD::AND, SDLoc(N), GuardedVal.getValueType(), NewNode, GuardedVal.getOperand(1));
+ return NewNode;
+ }
}
}
}
More information about the llvm-commits
mailing list