[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