[llvm] [AArch64][SelectionDAG] Fold late FCMP+CSEL patterns to FMAXNM/FMINNM during AArch64 DAG-to-DAG preprocessing. (PR #197429)
Usha Gupta via llvm-commits
llvm-commits at lists.llvm.org
Mon May 18 05:00:35 PDT 2026
https://github.com/usha1830 updated https://github.com/llvm/llvm-project/pull/197429
>From 27f478cd4269364effcab3cb500fe8a60334f356 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Wed, 13 May 2026 10:55:48 +0000
Subject: [PATCH 1/4] [AArch64][SelectionDAG] Add tests for folding ordered
FCMP+CSEL patterns to FMAXNM/FMINNM
---
.../AArch64/fmaximumnum-fminimumnum-select.ll | 178 ++++++++++++++++++
1 file changed, 178 insertions(+)
create mode 100644 llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll
diff --git a/llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll b/llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll
new file mode 100644
index 0000000000000..6bc65a1f32189
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll
@@ -0,0 +1,178 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=aarch64 < %s | FileCheck %s
+
+; Test late AArch64 folding of ordered FCMP+CSEL patterns to fmaxnm/fminnm.
+; Pattern: max(mul, 0.0) then min(result, 1.0)
+; First fcmp/fcsel folds to fmaxnm, outer one does NOT fold as operands cannot
+; be proven as non-sNaN.
+define float @max_after_fmul(float %a, float %b) {
+; CHECK-LABEL: max_after_fmul:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fmul s0, s0, s1
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: fcsel s0, s0, s1, gt
+; CHECK-NEXT: fmov s1, #1.00000000
+; CHECK-NEXT: fcmp s0, s1
+; CHECK-NEXT: fcsel s0, s0, s1, mi
+; CHECK-NEXT: ret
+entry:
+ %mul = fmul float %a, %b
+ %cmp1 = fcmp nsz ogt float %mul, 0.000000e+00
+ %max = select i1 %cmp1, float %mul, float 0.000000e+00
+ %cmp2 = fcmp nsz olt float %max, 1.000000e+00
+ %min = select i1 %cmp2, float %max, float 1.000000e+00
+ ret float %min
+}
+
+; fadd input
+define float @max_after_fadd(float %a, float %b) {
+; CHECK-LABEL: max_after_fadd:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fadd s0, s0, s1
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: fcsel s0, s0, s1, gt
+; CHECK-NEXT: ret
+entry:
+ %add = fadd float %a, %b
+ %cmp = fcmp nsz ogt float %add, 0.000000e+00
+ %max = select i1 %cmp, float %add, float 0.000000e+00
+ ret float %max
+}
+
+; Double precision
+define double @max_after_fmul_f64(double %a, double %b) {
+; CHECK-LABEL: max_after_fmul_f64:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fmul d0, d0, d1
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp d0, #0.0
+; CHECK-NEXT: fcsel d0, d0, d1, gt
+; CHECK-NEXT: ret
+entry:
+ %mul = fmul double %a, %b
+ %cmp = fcmp nsz ogt double %mul, 0.000000e+00
+ %max = select i1 %cmp, double %mul, double 0.000000e+00
+ ret double %max
+}
+
+; Function argument: isKnownNeverSNaN returns false, so the fold is skipped.
+define float @max_arg(float %x) {
+; CHECK-LABEL: max_arg:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: fcsel s0, s0, s1, gt
+; CHECK-NEXT: ret
+entry:
+ %cmp = fcmp nsz ogt float %x, 0.000000e+00
+ %max = select i1 %cmp, float %x, float 0.000000e+00
+ ret float %max
+}
+
+; fmin with arg: isKnownNeverSNaN returns false, so the fold is skipped.
+define float @min_arg(float %x) {
+; CHECK-LABEL: min_arg:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fmov s1, #1.00000000
+; CHECK-NEXT: fcmp s0, s1
+; CHECK-NEXT: fcsel s0, s0, s1, mi
+; CHECK-NEXT: ret
+entry:
+ %cmp = fcmp nsz olt float %x, 1.000000e+00
+ %min = select i1 %cmp, float %x, float 1.000000e+00
+ ret float %min
+}
+
+; fmin with arithmetic input
+define float @min_after_fmul(float %a, float %b) {
+; CHECK-LABEL: min_after_fmul:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fmul s0, s0, s1
+; CHECK-NEXT: fmov s1, #1.00000000
+; CHECK-NEXT: fcmp s0, s1
+; CHECK-NEXT: fcsel s0, s0, s1, mi
+; CHECK-NEXT: ret
+entry:
+ %mul = fmul float %a, %b
+ %cmp = fcmp nsz olt float %mul, 1.000000e+00
+ %min = select i1 %cmp, float %mul, float 1.000000e+00
+ ret float %min
+}
+
+; Negative: no nsz flag - cannot transform (signed zero mismatch)
+define float @no_nsz(float %x) {
+; CHECK-LABEL: no_nsz:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: fcsel s0, s0, s1, gt
+; CHECK-NEXT: ret
+entry:
+ %cmp = fcmp ogt float %x, 0.000000e+00
+ %max = select i1 %cmp, float %x, float 0.000000e+00
+ ret float %max
+}
+
+; Negative: unordered comparison - fold is NOT correct, skip.
+define float @unordered_cmp(float %x) {
+; CHECK-LABEL: unordered_cmp:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: fcsel s0, s0, s1, hi
+; CHECK-NEXT: ret
+entry:
+ %cmp = fcmp nsz ugt float %x, 0.000000e+00
+ %max = select i1 %cmp, float %x, float 0.000000e+00
+ ret float %max
+}
+
+; nsz on the setcc only (not the select) - should still work
+define float @nsz_on_setcc_only(float %a, float %b) {
+; CHECK-LABEL: nsz_on_setcc_only:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fmul s0, s0, s1
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: fcsel s0, s0, s1, gt
+; CHECK-NEXT: ret
+entry:
+ %mul = fmul float %a, %b
+ %cmp = fcmp nsz ogt float %mul, 0.000000e+00
+ %max = select i1 %cmp, float %mul, float 0.000000e+00
+ ret float %max
+}
+
+; GE (>=) with arithmetic input: folds to fmaxnm (same semantics as GT with nsz)
+define float @max_oge(float %a, float %b) {
+; CHECK-LABEL: max_oge:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fmul s0, s0, s1
+; CHECK-NEXT: movi d1, #0000000000000000
+; CHECK-NEXT: fcmp s0, #0.0
+; CHECK-NEXT: fcsel s0, s0, s1, ge
+; CHECK-NEXT: ret
+entry:
+ %mul = fmul float %a, %b
+ %cmp = fcmp nsz oge float %mul, 0.000000e+00
+ %max = select i1 %cmp, float %mul, float 0.000000e+00
+ ret float %max
+}
+
+; LE (<=) with arithmetic input: folds to fminnm (same semantics as MI with nsz)
+define float @min_ole(float %a, float %b) {
+; CHECK-LABEL: min_ole:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: fmul s0, s0, s1
+; CHECK-NEXT: fmov s1, #1.00000000
+; CHECK-NEXT: fcmp s0, s1
+; CHECK-NEXT: fcsel s0, s0, s1, ls
+; CHECK-NEXT: ret
+entry:
+ %mul = fmul float %a, %b
+ %cmp = fcmp nsz ole float %mul, 1.000000e+00
+ %min = select i1 %cmp, float %mul, float 1.000000e+00
+ ret float %min
+}
>From 665746209aa793ed4f3ad9003de28ad309701c25 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Wed, 13 May 2026 11:35:04 +0000
Subject: [PATCH 2/4] [AArch64][SelectionDAG] Fold late FCMP+CSEL patterns to
FMAXNM/FMINNM during AArch64 DAG-to-DAG preprocessing.
---
.../Target/AArch64/AArch64ISelDAGToDAG.cpp | 97 +++++++++++++++++++
.../Target/AArch64/AArch64ISelLowering.cpp | 6 +-
.../AArch64/fmaximumnum-fminimumnum-select.ll | 31 +++---
3 files changed, 114 insertions(+), 20 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
index 64ccf606b4ef7..c5ac49913f9ba 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
@@ -447,6 +447,8 @@ class AArch64DAGToDAGISel : public SelectionDAGISel {
bool trySelectXAR(SDNode *N);
+ SDValue tryFoldCselToFMaxMin(SDNode &N);
+
// Include the pieces autogenerated from the target description.
#include "AArch64GenDAGISel.inc"
@@ -7994,6 +7996,98 @@ bool AArch64DAGToDAGISel::SelectCmpBranchExtOperand(SDValue N, SDValue &Reg,
return false;
}
+/// Try to fold AArch64 CSEL/FCMP patterns to FMAXNUM_IEEE/FMINNUM_IEEE.
+///
+/// This is intentionally done in PreprocessISelDAG rather than DAGCombine:
+/// doing this earlier based on the defining operation of X can be invalidated
+/// by later DAG combines. At this point the DAG is being prepared for
+/// instruction selection, so the use of isKnownNeverSNaN(X) applies to the
+/// final SDValue being selected.
+///
+/// Only handles FCMP(X, C) with scalar FP types, where C is a non-NaN constant
+/// and the CSEL has nsz. The nsz requirement avoids signed-zero mismatches.
+/// The never-sNaN check is required because AArch64 FMAXNM/FMINNM differ from
+/// fcmp+fcsel for signaling NaN inputs.
+SDValue AArch64DAGToDAGISel::tryFoldCselToFMaxMin(SDNode &N) {
+ EVT VT = N.getValueType(0);
+
+ // Scalar FP only.
+ if (!VT.isFloatingPoint() || VT.isVector())
+ return SDValue();
+
+ // nsz required: without it, fmaxnm(+0,-0)=+0 but fcmp+fcsel returns -0.
+ if (!N.getFlags().hasNoSignedZeros())
+ return SDValue();
+
+ SDValue TVal = N.getOperand(0);
+ SDValue FVal = N.getOperand(1);
+ SDValue CCVal = N.getOperand(2);
+ SDValue Cmp = N.getOperand(3);
+
+ if (Cmp.getOpcode() != AArch64ISD::FCMP)
+ return SDValue();
+
+ auto *CC = dyn_cast<ConstantSDNode>(CCVal);
+ if (!CC)
+ return SDValue();
+
+ SDValue CmpLHS = Cmp.getOperand(0);
+ SDValue CmpRHS = Cmp.getOperand(1);
+ unsigned CondCode = CC->getZExtValue();
+
+ // Match GT, GE, MI, LS conditions.
+ // When nsz is present, GE and LS have the same semantics as GT and MI
+ // respectively:
+ unsigned IEEEOpc;
+ SDValue Operand, Constant;
+
+ if (CondCode == AArch64CC::GT || CondCode == AArch64CC::GE) {
+ if (TVal == CmpLHS && FVal == CmpRHS) {
+ IEEEOpc = ISD::FMAXNUM_IEEE;
+ Operand = CmpLHS;
+ Constant = CmpRHS;
+ } else if (TVal == CmpRHS && FVal == CmpLHS) {
+ IEEEOpc = ISD::FMINNUM_IEEE;
+ Operand = CmpLHS;
+ Constant = CmpRHS;
+ } else {
+ return SDValue();
+ }
+ } else if (CondCode == AArch64CC::MI || CondCode == AArch64CC::LS) {
+ if (TVal == CmpLHS && FVal == CmpRHS) {
+ IEEEOpc = ISD::FMINNUM_IEEE;
+ Operand = CmpLHS;
+ Constant = CmpRHS;
+ } else if (TVal == CmpRHS && FVal == CmpLHS) {
+ IEEEOpc = ISD::FMAXNUM_IEEE;
+ Operand = CmpLHS;
+ Constant = CmpRHS;
+ } else {
+ return SDValue();
+ }
+ } else {
+ return SDValue();
+ }
+
+ const TargetLowering *TLI = getTargetLowering();
+ if (!TLI->isOperationLegalOrCustom(IEEEOpc, VT))
+ return SDValue();
+
+ // Constant must be non-NaN.
+ auto *CFP = dyn_cast<ConstantFPSDNode>(Constant);
+ if (!CFP || CFP->getValueAPF().isNaN())
+ return SDValue();
+
+ // Only fold if variable operand is never sNaN.
+ // This runs after DAG combines, so later combines cannot remove a defining
+ // operation used by isKnownNeverSNaN().
+ if (!CurDAG->isKnownNeverSNaN(Operand))
+ return SDValue();
+
+ SDLoc DL(&N);
+ return CurDAG->getNode(IEEEOpc, DL, VT, Operand, Constant);
+}
+
void AArch64DAGToDAGISel::PreprocessISelDAG() {
bool MadeChange = false;
for (SDNode &N : llvm::make_early_inc_range(CurDAG->allnodes())) {
@@ -8010,6 +8104,9 @@ void AArch64DAGToDAGISel::PreprocessISelDAG() {
break;
}
+ case AArch64ISD::CSEL:
+ Result = tryFoldCselToFMaxMin(N);
+ break;
default:
break;
}
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 9de0b461a216c..a3aa11dbf59a4 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -12473,8 +12473,12 @@ SDValue AArch64TargetLowering::LowerSELECT_CC(
}
// Emit first, and possibly only, CSEL.
+ // Propagate nsz to the CSEL so downstream passes can use it.
+ SDNodeFlags CSELFlags;
+ CSELFlags.setNoSignedZeros(Flags.hasNoSignedZeros());
SDValue CC1Val = getCondCode(DAG, CC1);
- SDValue CS1 = DAG.getNode(AArch64ISD::CSEL, DL, VT, TVal, FVal, CC1Val, Cmp);
+ SDValue CS1 = DAG.getNode(AArch64ISD::CSEL, DL, VT, {TVal, FVal, CC1Val, Cmp},
+ CSELFlags);
// If we need a second CSEL, emit it, using the output of the first as the
// RHS. We're effectively OR'ing the two CC's together.
diff --git a/llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll b/llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll
index 6bc65a1f32189..5e21c25215887 100644
--- a/llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll
+++ b/llvm/test/CodeGen/AArch64/fmaximumnum-fminimumnum-select.ll
@@ -8,11 +8,10 @@
define float @max_after_fmul(float %a, float %b) {
; CHECK-LABEL: max_after_fmul:
; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d2, #0000000000000000
; CHECK-NEXT: fmul s0, s0, s1
-; CHECK-NEXT: movi d1, #0000000000000000
-; CHECK-NEXT: fcmp s0, #0.0
-; CHECK-NEXT: fcsel s0, s0, s1, gt
; CHECK-NEXT: fmov s1, #1.00000000
+; CHECK-NEXT: fmaxnm s0, s0, s2
; CHECK-NEXT: fcmp s0, s1
; CHECK-NEXT: fcsel s0, s0, s1, mi
; CHECK-NEXT: ret
@@ -29,10 +28,9 @@ entry:
define float @max_after_fadd(float %a, float %b) {
; CHECK-LABEL: max_after_fadd:
; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d2, #0000000000000000
; CHECK-NEXT: fadd s0, s0, s1
-; CHECK-NEXT: movi d1, #0000000000000000
-; CHECK-NEXT: fcmp s0, #0.0
-; CHECK-NEXT: fcsel s0, s0, s1, gt
+; CHECK-NEXT: fmaxnm s0, s0, s2
; CHECK-NEXT: ret
entry:
%add = fadd float %a, %b
@@ -45,10 +43,9 @@ entry:
define double @max_after_fmul_f64(double %a, double %b) {
; CHECK-LABEL: max_after_fmul_f64:
; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d2, #0000000000000000
; CHECK-NEXT: fmul d0, d0, d1
-; CHECK-NEXT: movi d1, #0000000000000000
-; CHECK-NEXT: fcmp d0, #0.0
-; CHECK-NEXT: fcsel d0, d0, d1, gt
+; CHECK-NEXT: fmaxnm d0, d0, d2
; CHECK-NEXT: ret
entry:
%mul = fmul double %a, %b
@@ -91,8 +88,7 @@ define float @min_after_fmul(float %a, float %b) {
; CHECK: // %bb.0: // %entry
; CHECK-NEXT: fmul s0, s0, s1
; CHECK-NEXT: fmov s1, #1.00000000
-; CHECK-NEXT: fcmp s0, s1
-; CHECK-NEXT: fcsel s0, s0, s1, mi
+; CHECK-NEXT: fminnm s0, s0, s1
; CHECK-NEXT: ret
entry:
%mul = fmul float %a, %b
@@ -133,10 +129,9 @@ entry:
define float @nsz_on_setcc_only(float %a, float %b) {
; CHECK-LABEL: nsz_on_setcc_only:
; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d2, #0000000000000000
; CHECK-NEXT: fmul s0, s0, s1
-; CHECK-NEXT: movi d1, #0000000000000000
-; CHECK-NEXT: fcmp s0, #0.0
-; CHECK-NEXT: fcsel s0, s0, s1, gt
+; CHECK-NEXT: fmaxnm s0, s0, s2
; CHECK-NEXT: ret
entry:
%mul = fmul float %a, %b
@@ -149,10 +144,9 @@ entry:
define float @max_oge(float %a, float %b) {
; CHECK-LABEL: max_oge:
; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: movi d2, #0000000000000000
; CHECK-NEXT: fmul s0, s0, s1
-; CHECK-NEXT: movi d1, #0000000000000000
-; CHECK-NEXT: fcmp s0, #0.0
-; CHECK-NEXT: fcsel s0, s0, s1, ge
+; CHECK-NEXT: fmaxnm s0, s0, s2
; CHECK-NEXT: ret
entry:
%mul = fmul float %a, %b
@@ -167,8 +161,7 @@ define float @min_ole(float %a, float %b) {
; CHECK: // %bb.0: // %entry
; CHECK-NEXT: fmul s0, s0, s1
; CHECK-NEXT: fmov s1, #1.00000000
-; CHECK-NEXT: fcmp s0, s1
-; CHECK-NEXT: fcsel s0, s0, s1, ls
+; CHECK-NEXT: fminnm s0, s0, s1
; CHECK-NEXT: ret
entry:
%mul = fmul float %a, %b
>From 99a820f2dba749f0a82b69d5ca44c59549a5aca5 Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Fri, 15 May 2026 12:16:37 +0000
Subject: [PATCH 3/4] Address review comments: Generate machine node directly,
impose nsz requirement only when constant is 0
---
.../Target/AArch64/AArch64ISelDAGToDAG.cpp | 80 ++++++++++---------
.../Target/AArch64/AArch64ISelLowering.cpp | 4 +-
2 files changed, 44 insertions(+), 40 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
index c5ac49913f9ba..edd0b45afd5f1 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
@@ -7996,7 +7996,7 @@ bool AArch64DAGToDAGISel::SelectCmpBranchExtOperand(SDValue N, SDValue &Reg,
return false;
}
-/// Try to fold AArch64 CSEL/FCMP patterns to FMAXNUM_IEEE/FMINNUM_IEEE.
+/// Try to fold AArch64 CSEL/FCMP patterns to FMAXNM/FMINNM.
///
/// This is intentionally done in PreprocessISelDAG rather than DAGCombine:
/// doing this earlier based on the defining operation of X can be invalidated
@@ -8004,10 +8004,10 @@ bool AArch64DAGToDAGISel::SelectCmpBranchExtOperand(SDValue N, SDValue &Reg,
/// instruction selection, so the use of isKnownNeverSNaN(X) applies to the
/// final SDValue being selected.
///
-/// Only handles FCMP(X, C) with scalar FP types, where C is a non-NaN constant
-/// and the CSEL has nsz. The nsz requirement avoids signed-zero mismatches.
-/// The never-sNaN check is required because AArch64 FMAXNM/FMINNM differ from
-/// fcmp+fcsel for signaling NaN inputs.
+/// Only handles FCMP(X, C) with scalar FP types, where C is a non-NaN constant.
+/// The nsz requirement is needed only when C is zero, to avoid signed-zero
+/// mismatches. The never-sNaN check is required because AArch64 FMAXNM/FMINNM
+/// differ from fcmp+fcsel for signaling NaN inputs.
SDValue AArch64DAGToDAGISel::tryFoldCselToFMaxMin(SDNode &N) {
EVT VT = N.getValueType(0);
@@ -8015,10 +8015,6 @@ SDValue AArch64DAGToDAGISel::tryFoldCselToFMaxMin(SDNode &N) {
if (!VT.isFloatingPoint() || VT.isVector())
return SDValue();
- // nsz required: without it, fmaxnm(+0,-0)=+0 but fcmp+fcsel returns -0.
- if (!N.getFlags().hasNoSignedZeros())
- return SDValue();
-
SDValue TVal = N.getOperand(0);
SDValue FVal = N.getOperand(1);
SDValue CCVal = N.getOperand(2);
@@ -8035,57 +8031,63 @@ SDValue AArch64DAGToDAGISel::tryFoldCselToFMaxMin(SDNode &N) {
SDValue CmpRHS = Cmp.getOperand(1);
unsigned CondCode = CC->getZExtValue();
- // Match GT, GE, MI, LS conditions.
- // When nsz is present, GE and LS have the same semantics as GT and MI
- // respectively:
- unsigned IEEEOpc;
- SDValue Operand, Constant;
+ // Map VT and operation (max/min) to machine opcode.
+ auto getOpc = [](EVT VT, bool isMax) -> unsigned {
+ if (VT == MVT::f16)
+ return isMax ? AArch64::FMAXNMHrr : AArch64::FMINNMHrr;
+ else if (VT == MVT::f32)
+ return isMax ? AArch64::FMAXNMSrr : AArch64::FMINNMSrr;
+ else if (VT == MVT::f64)
+ return isMax ? AArch64::FMAXNMDrr : AArch64::FMINNMDrr;
+ else
+ return 0; // unsupported
+ };
+ // Determine whether to use max or min based on condition code and operands.
+ bool isMax;
if (CondCode == AArch64CC::GT || CondCode == AArch64CC::GE) {
- if (TVal == CmpLHS && FVal == CmpRHS) {
- IEEEOpc = ISD::FMAXNUM_IEEE;
- Operand = CmpLHS;
- Constant = CmpRHS;
- } else if (TVal == CmpRHS && FVal == CmpLHS) {
- IEEEOpc = ISD::FMINNUM_IEEE;
- Operand = CmpLHS;
- Constant = CmpRHS;
- } else {
+ if (TVal == CmpLHS && FVal == CmpRHS)
+ isMax = true;
+ else if (TVal == CmpRHS && FVal == CmpLHS)
+ isMax = false;
+ else
return SDValue();
- }
} else if (CondCode == AArch64CC::MI || CondCode == AArch64CC::LS) {
- if (TVal == CmpLHS && FVal == CmpRHS) {
- IEEEOpc = ISD::FMINNUM_IEEE;
- Operand = CmpLHS;
- Constant = CmpRHS;
- } else if (TVal == CmpRHS && FVal == CmpLHS) {
- IEEEOpc = ISD::FMAXNUM_IEEE;
- Operand = CmpLHS;
- Constant = CmpRHS;
- } else {
+ if (TVal == CmpLHS && FVal == CmpRHS)
+ isMax = false;
+ else if (TVal == CmpRHS && FVal == CmpLHS)
+ isMax = true;
+ else
return SDValue();
- }
} else {
return SDValue();
}
- const TargetLowering *TLI = getTargetLowering();
- if (!TLI->isOperationLegalOrCustom(IEEEOpc, VT))
+ // Get the machine opcode for this VT and operation.
+ unsigned Opc = getOpc(VT, isMax);
+ if (!Opc)
return SDValue();
// Constant must be non-NaN.
- auto *CFP = dyn_cast<ConstantFPSDNode>(Constant);
+ auto *CFP = dyn_cast<ConstantFPSDNode>(CmpRHS);
if (!CFP || CFP->getValueAPF().isNaN())
return SDValue();
+ // nsz flag required only when constant is zero: fmaxnm(+0,-0)=+0 differs from
+ // fcmp+select's -0. For non-zero constants, semantics are identical.
+ if (CFP->isZero() && !N.getFlags().hasNoSignedZeros())
+ return SDValue();
+
// Only fold if variable operand is never sNaN.
// This runs after DAG combines, so later combines cannot remove a defining
// operation used by isKnownNeverSNaN().
- if (!CurDAG->isKnownNeverSNaN(Operand))
+ if (!CurDAG->isKnownNeverSNaN(CmpLHS))
return SDValue();
SDLoc DL(&N);
- return CurDAG->getNode(IEEEOpc, DL, VT, Operand, Constant);
+
+ // Directly emit the machine node
+ return SDValue(CurDAG->getMachineNode(Opc, DL, VT, CmpLHS, CmpRHS), 0);
}
void AArch64DAGToDAGISel::PreprocessISelDAG() {
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index a3aa11dbf59a4..1a1a2a9ca9e84 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -12473,7 +12473,9 @@ SDValue AArch64TargetLowering::LowerSELECT_CC(
}
// Emit first, and possibly only, CSEL.
- // Propagate nsz to the CSEL so downstream passes can use it.
+ // Propagate only nsz flag as it directly affects CSEL semantics by ignoring
+ // signed-zero differences and relaxing equivalence.
+ // Other flags (reassoc, contract, arcp, etc.) are not applicable to CSEL.
SDNodeFlags CSELFlags;
CSELFlags.setNoSignedZeros(Flags.hasNoSignedZeros());
SDValue CC1Val = getCondCode(DAG, CC1);
>From 257471b5b4090ed0d43816bcd72f64044520c47f Mon Sep 17 00:00:00 2001
From: Usha Gupta <usha.gupta at arm.com>
Date: Mon, 18 May 2026 09:20:20 +0000
Subject: [PATCH 4/4] Pass all flags for the CSEL node instead of only nsz
---
llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp | 1 -
llvm/lib/Target/AArch64/AArch64ISelLowering.cpp | 10 +++-------
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
index edd0b45afd5f1..6002ee4c7b7f7 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelDAGToDAG.cpp
@@ -8003,7 +8003,6 @@ bool AArch64DAGToDAGISel::SelectCmpBranchExtOperand(SDValue N, SDValue &Reg,
/// by later DAG combines. At this point the DAG is being prepared for
/// instruction selection, so the use of isKnownNeverSNaN(X) applies to the
/// final SDValue being selected.
-///
/// Only handles FCMP(X, C) with scalar FP types, where C is a non-NaN constant.
/// The nsz requirement is needed only when C is zero, to avoid signed-zero
/// mismatches. The never-sNaN check is required because AArch64 FMAXNM/FMINNM
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 1a1a2a9ca9e84..ab9362f7fc5b8 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -12473,14 +12473,10 @@ SDValue AArch64TargetLowering::LowerSELECT_CC(
}
// Emit first, and possibly only, CSEL.
- // Propagate only nsz flag as it directly affects CSEL semantics by ignoring
- // signed-zero differences and relaxing equivalence.
- // Other flags (reassoc, contract, arcp, etc.) are not applicable to CSEL.
- SDNodeFlags CSELFlags;
- CSELFlags.setNoSignedZeros(Flags.hasNoSignedZeros());
+ // Propagate all flags to the CSEL node for downstream optimization passes.
SDValue CC1Val = getCondCode(DAG, CC1);
- SDValue CS1 = DAG.getNode(AArch64ISD::CSEL, DL, VT, {TVal, FVal, CC1Val, Cmp},
- CSELFlags);
+ SDValue CS1 =
+ DAG.getNode(AArch64ISD::CSEL, DL, VT, {TVal, FVal, CC1Val, Cmp}, Flags);
// If we need a second CSEL, emit it, using the output of the first as the
// RHS. We're effectively OR'ing the two CC's together.
More information about the llvm-commits
mailing list