[llvm] [SelectionDAG] Fix fcmp fold for new min/max semantics (PR #223655)
Mitch Briles via llvm-commits
llvm-commits at lists.llvm.org
Fri Sep 25 16:52:12 PDT 2026
https://github.com/MitchBriles updated https://github.com/llvm/llvm-project/pull/223655
>From 3e084e0be81ac01d37c0f3c09c8f81eb9ba6fba7 Mon Sep 17 00:00:00 2001
From: Mitch <mitchbriles at gmail.com>
Date: Tue, 15 Sep 2026 04:31:35 -0600
Subject: [PATCH 1/4] [SelectionDAG] Fix fcmp fold for new min/max semantics
---
llvm/include/llvm/CodeGen/ISDOpcodes.h | 62 ++++++------
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 95 ++++++++++---------
.../AArch64/combine_andor_with_cmps.ll | 46 ++++++++-
.../AMDGPU/combine_andor_with_cmps_nnan.ll | 13 +--
llvm/test/CodeGen/AMDGPU/or.r600.ll | 25 +++--
.../CodeGen/RISCV/combine-andor-with-cmps.ll | 83 ++++++++++++++++
6 files changed, 231 insertions(+), 93 deletions(-)
create mode 100644 llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll
diff --git a/llvm/include/llvm/CodeGen/ISDOpcodes.h b/llvm/include/llvm/CodeGen/ISDOpcodes.h
index 9c3757203299ed..5e38c7ccfbb3d5 100644
--- a/llvm/include/llvm/CodeGen/ISDOpcodes.h
+++ b/llvm/include/llvm/CodeGen/ISDOpcodes.h
@@ -1085,47 +1085,53 @@ enum NodeType {
LRINT,
LLRINT,
- /// FMINNUM/FMAXNUM - Perform floating-point minimum maximum on two values,
- /// following IEEE-754 definitions except for signed zero behavior.
+ /// FMINNUM/FMAXNUM - NaN-discarding minimum/maximum: if one operand is a
+ /// quiet NaN and the other is a number, returns the number.
///
- /// If one input is a signaling NaN, returns a quiet NaN. This matches
- /// IEEE-754 2008's minNum/maxNum behavior for signaling NaNs (which differs
- /// from 2019).
+ /// If an operand is a signaling NaN, this will non-deterministically either:
+ /// - Return a NaN.
+ /// - Or treat the signaling NaN as a quiet NaN.
///
/// These treat -0 as ordered less than +0, matching the behavior of IEEE-754
- /// 2019's minimumNumber/maximumNumber.
- ///
- /// Note that that arithmetic on an sNaN doesn't consistently produce a qNaN,
- /// so arithmetic feeding into a minnum/maxnum can produce inconsistent
- /// results. FMAXIMUN/FMINIMUM or FMAXIMUMNUM/FMINIMUMNUM may be better choice
- /// for non-distinction of sNaN/qNaN handling.
+ /// 2019's minimumNumber/maximumNumber. With the nsz flag, one +0.0 and one
+ /// -0.0 operand may non-deterministically return either operand; contrary to
+ /// normal nsz semantics, if both operands have the same sign, so must the
+ /// result. Note that not all backends respect this ordering yet.
FMINNUM,
FMAXNUM,
- /// FMINNUM_IEEE/FMAXNUM_IEEE - Perform floating-point minimumNumber or
- /// maximumNumber on two values, following IEEE-754 definitions. This differs
- /// from FMINNUM/FMAXNUM in the handling of signaling NaNs, and signed zero.
- ///
- /// If one input is a signaling NaN, returns a quiet NaN. This matches
- /// IEEE-754 2008's minnum/maxnum behavior for signaling NaNs (which differs
- /// from 2019).
- ///
- /// These treat -0 as ordered less than +0, matching the behavior of IEEE-754
- /// 2019's minimumNumber/maximumNumber.
+ /// FMINNUM_IEEE/FMAXNUM_IEEE - Same as FMINNUM/FMAXNUM, except that a
+ /// signaling NaN operand deterministically returns a quiet NaN, matching/for
+ /// IEEE-754 2008's minNum/maxNum. Signed zeros are ordered identically to
+ /// FMINNUM/FMAXNUM: -0 is less than +0, relaxed by the nsz flag.
///
- /// Deprecated, and will be removed soon, as FMINNUM/FMAXNUM have the same
- /// semantics now.
+ /// Deprecated, and will be removed soon: this is a legal implementation of
+ /// FMINNUM/FMAXNUM, so targets should select those instead.
FMINNUM_IEEE,
FMAXNUM_IEEE,
- /// FMINIMUM/FMAXIMUM - NaN-propagating minimum/maximum that also treat -0.0
- /// as less than 0.0. While FMINNUM_IEEE/FMAXNUM_IEEE follow IEEE 754-2008
- /// semantics, FMINIMUM/FMAXIMUM follow IEEE 754-2019 semantics.
+ /// FMINIMUM/FMAXIMUM - NaN-propagating minimum/maximum: if either operand is
+ /// a NaN, returns a NaN. Follows C23's fminimum/fmaximum and IEEE-754 2019's
+ /// minimum/maximum, except that a signaling NaN operand is not guaranteed to
+ /// be quieted.
+ ///
+ /// These treat -0 as ordered less than +0. With the nsz flag, one +0.0 and
+ /// one -0.0 operand may non-deterministically return either operand;
+ /// contrary to normal nsz semantics, if both operands have the same sign, so
+ /// must the result.
FMINIMUM,
FMAXIMUM,
- /// FMINIMUMNUM/FMAXIMUMNUM - minimumnum/maximumnum that is same with
- /// FMINNUM_IEEE and FMAXNUM_IEEE besides if either operand is sNaN.
+ /// FMINIMUMNUM/FMAXIMUMNUM - NaN-discarding minimum/maximum: if one operand
+ /// is a NaN and the other is a number, returns the number. Follows C23's
+ /// fminimum_num/fmaximum_num and IEEE-754 2019's minimumNumber/maximumNumber,
+ /// except that a signaling NaN operand is not guaranteed to be quieted.
+ /// Same as FMINNUM/FMAXNUM, but treats signaling NaNs as quiet NaNs.
+ ///
+ /// These treat -0 as ordered less than +0. With the nsz flag, one +0.0 and
+ /// one -0.0 operand may non-deterministically return either operand;
+ /// contrary to normal nsz semantics, if both operands have the same sign, so
+ /// must the result.
FMINIMUMNUM,
FMAXIMUMNUM,
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index a829d7a34d5a6b..a84127e453b11f 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6935,61 +6935,57 @@ static unsigned getMinMaxOpcodeForClamp(bool IsMin, SDValue Operand1,
return ISD::DELETED_NODE;
}
-// FIXME: use FMINIMUMNUM if possible, such as for RISC-V.
static unsigned getMinMaxOpcodeForCompareFold(
SDValue Operand1, SDValue Operand2, bool SetCCNoNaNs, ISD::CondCode CC,
unsigned OrAndOpcode, SelectionDAG &DAG, bool isFMAXNUMFMINNUM_IEEE,
- bool isFMAXNUMFMINNUM) {
+ bool isFMAXNUMFMINNUM, bool isFMAXIMUMNUMFMINIMUMNUM) {
// The optimization cannot be applied for all the predicates because
- // of the way FMINNUM/FMAXNUM and FMINNUM_IEEE/FMAXNUM_IEEE handle
- // NaNs. For FMINNUM_IEEE/FMAXNUM_IEEE, the optimization cannot be
- // applied at all if one of the operands is a signaling NaN.
+ // of the way the min/max opcodes handle NaNs.
- // It is safe to use FMINNUM_IEEE/FMAXNUM_IEEE if all the operands
- // are non NaN values.
+ // It is safe to use FMINIMUMNUM/FMAXIMUMNUM or FMINNUM_IEEE/FMAXNUM_IEEE if
+ // all the operands are non NaN values.
if (((CC == ISD::SETLT || CC == ISD::SETLE) && (OrAndOpcode == ISD::OR)) ||
((CC == ISD::SETGT || CC == ISD::SETGE) && (OrAndOpcode == ISD::AND))) {
- return (SetCCNoNaNs || arebothOperandsNotNan(Operand1, Operand2, DAG)) &&
- isFMAXNUMFMINNUM_IEEE
- ? ISD::FMINNUM_IEEE
- : ISD::DELETED_NODE;
+ if (!SetCCNoNaNs && !arebothOperandsNotNan(Operand1, Operand2, DAG))
+ return ISD::DELETED_NODE;
+ return isFMAXIMUMNUMFMINIMUMNUM ? ISD::FMINIMUMNUM
+ : isFMAXNUMFMINNUM_IEEE ? ISD::FMINNUM_IEEE
+ : ISD::DELETED_NODE;
}
if (((CC == ISD::SETGT || CC == ISD::SETGE) && (OrAndOpcode == ISD::OR)) ||
((CC == ISD::SETLT || CC == ISD::SETLE) && (OrAndOpcode == ISD::AND))) {
- return (SetCCNoNaNs || arebothOperandsNotNan(Operand1, Operand2, DAG)) &&
- isFMAXNUMFMINNUM_IEEE
- ? ISD::FMAXNUM_IEEE
- : ISD::DELETED_NODE;
- }
-
- // Both FMINNUM/FMAXNUM and FMINNUM_IEEE/FMAXNUM_IEEE handle quiet
- // NaNs in the same way. But, FMINNUM/FMAXNUM and FMINNUM_IEEE/
- // FMAXNUM_IEEE handle signaling NaNs differently. If we cannot prove
- // that there are not any sNaNs, then the optimization is not valid
- // for FMINNUM_IEEE/FMAXNUM_IEEE. In the presence of sNaNs, we apply
- // the optimization using FMINNUM/FMAXNUM for the following cases. If
- // we can prove that we do not have any sNaNs, then we can do the
- // optimization using FMINNUM_IEEE/FMAXNUM_IEEE for the following
- // cases.
- if (((CC == ISD::SETOLT || CC == ISD::SETOLE) && (OrAndOpcode == ISD::OR)) ||
- ((CC == ISD::SETUGT || CC == ISD::SETUGE) && (OrAndOpcode == ISD::AND))) {
- return isFMAXNUMFMINNUM ? ISD::FMINNUM
- : arebothOperandsNotSNan(Operand1, Operand2, DAG) &&
- isFMAXNUMFMINNUM_IEEE
- ? ISD::FMINNUM_IEEE
- : ISD::DELETED_NODE;
- }
-
- if (((CC == ISD::SETOGT || CC == ISD::SETOGE) && (OrAndOpcode == ISD::OR)) ||
- ((CC == ISD::SETULT || CC == ISD::SETULE) && (OrAndOpcode == ISD::AND))) {
- return isFMAXNUMFMINNUM ? ISD::FMAXNUM
- : arebothOperandsNotSNan(Operand1, Operand2, DAG) &&
- isFMAXNUMFMINNUM_IEEE
- ? ISD::FMAXNUM_IEEE
- : ISD::DELETED_NODE;
+ if (!SetCCNoNaNs && !arebothOperandsNotNan(Operand1, Operand2, DAG))
+ return ISD::DELETED_NODE;
+ return isFMAXIMUMNUMFMINIMUMNUM ? ISD::FMAXIMUMNUM
+ : isFMAXNUMFMINNUM_IEEE ? ISD::FMAXNUM_IEEE
+ : ISD::DELETED_NODE;
}
+ bool IsMin;
+ if (((CC == ISD::SETOLT || CC == ISD::SETOLE) && (OrAndOpcode == ISD::OR)) ||
+ ((CC == ISD::SETUGT || CC == ISD::SETUGE) && (OrAndOpcode == ISD::AND)))
+ IsMin = true;
+ else if (((CC == ISD::SETOGT || CC == ISD::SETOGE) &&
+ (OrAndOpcode == ISD::OR)) ||
+ ((CC == ISD::SETULT || CC == ISD::SETULE) &&
+ (OrAndOpcode == ISD::AND)))
+ IsMin = false;
+ else
+ return ISD::DELETED_NODE;
+
+ // For the above predicates, the optimization is valid if a NaN operand is
+ // discarded. FMINIMUMNUM/FMAXIMUMNUM always do so. FMINNUM/FMAXNUM and
+ // FMINNUM_IEEE/FMAXNUM_IEEE only do so for quiet NaNs, as a signaling NaN
+ // operand may instead produce a NaN.
+ if (isFMAXIMUMNUMFMINIMUMNUM)
+ return IsMin ? ISD::FMINIMUMNUM : ISD::FMAXIMUMNUM;
+ if (!arebothOperandsNotSNan(Operand1, Operand2, DAG))
+ return ISD::DELETED_NODE;
+ if (isFMAXNUMFMINNUM)
+ return IsMin ? ISD::FMINNUM : ISD::FMAXNUM;
+ if (isFMAXNUMFMINNUM_IEEE)
+ return IsMin ? ISD::FMINNUM_IEEE : ISD::FMAXNUM_IEEE;
return ISD::DELETED_NODE;
}
@@ -7040,12 +7036,20 @@ static SDValue foldAndOrOfSETCC(SDNode *LogicOp, SelectionDAG &DAG) {
TLI.isOperationLegal(ISD::FMINNUM_IEEE, OpVT);
bool isFMAXNUMFMINNUM = TLI.isOperationLegalOrCustom(ISD::FMAXNUM, OpVT) &&
TLI.isOperationLegalOrCustom(ISD::FMINNUM, OpVT);
+ // A custom FMINIMUMNUM/FMAXIMUMNUM may be more expensive than the compares,
+ // so only use one if the target considers forming it profitable.
+ bool isFMAXIMUMNUMFMINIMUMNUM =
+ (TLI.isOperationLegal(ISD::FMAXIMUMNUM, OpVT) &&
+ TLI.isOperationLegal(ISD::FMINIMUMNUM, OpVT)) ||
+ (TLI.isOperationLegalOrCustom(ISD::FMAXIMUMNUM, OpVT) &&
+ TLI.isOperationLegalOrCustom(ISD::FMINIMUMNUM, OpVT) &&
+ TLI.isProfitableToCombineMinNumMaxNum(OpVT));
if (((OpVT.isInteger() && TLI.isOperationLegal(ISD::UMAX, OpVT) &&
TLI.isOperationLegal(ISD::SMAX, OpVT) &&
TLI.isOperationLegal(ISD::UMIN, OpVT) &&
TLI.isOperationLegal(ISD::SMIN, OpVT)) ||
- (OpVT.isFloatingPoint() &&
- (isFMAXNUMFMINNUM_IEEE || isFMAXNUMFMINNUM))) &&
+ (OpVT.isFloatingPoint() && (isFMAXNUMFMINNUM_IEEE || isFMAXNUMFMINNUM ||
+ isFMAXIMUMNUMFMINIMUMNUM))) &&
!ISD::isIntEqualitySetCC(CCL) && !ISD::isFPEqualitySetCC(CCL) &&
CCL != ISD::SETFALSE && CCL != ISD::SETO && CCL != ISD::SETUO &&
CCL != ISD::SETTRUE &&
@@ -7102,7 +7106,8 @@ static SDValue foldAndOrOfSETCC(SDNode *LogicOp, SelectionDAG &DAG) {
NewOpcode = getMinMaxOpcodeForCompareFold(
Operand1, Operand2,
LHSSetCCFlags.hasNoNaNs() && RHSSetCCFlags.hasNoNaNs(), CC,
- LogicOp->getOpcode(), DAG, isFMAXNUMFMINNUM_IEEE, isFMAXNUMFMINNUM);
+ LogicOp->getOpcode(), DAG, isFMAXNUMFMINNUM_IEEE, isFMAXNUMFMINNUM,
+ isFMAXIMUMNUMFMINIMUMNUM);
if (NewOpcode != ISD::DELETED_NODE) {
// Propagate fast-math flags from setcc.
diff --git a/llvm/test/CodeGen/AArch64/combine_andor_with_cmps.ll b/llvm/test/CodeGen/AArch64/combine_andor_with_cmps.ll
index 89cb25f3d9d756..a1eaac062e6b49 100644
--- a/llvm/test/CodeGen/AArch64/combine_andor_with_cmps.ll
+++ b/llvm/test/CodeGen/AArch64/combine_andor_with_cmps.ll
@@ -5,12 +5,14 @@
; CMP(A,C)||CMP(B,C) => CMP(MIN/MAX(A,B), C)
; CMP(A,C)&&CMP(B,C) => CMP(MIN/MAX(A,B), C)
+; Not folded: A or B may be a signaling NaN, for which MIN/MAX may return NaN.
define i1 @test1(float %arg1, float %arg2, float %arg3) #0 {
; CHECK-LABEL: test1:
; CHECK: // %bb.0:
-; CHECK-NEXT: fminnm s0, s0, s1
; CHECK-NEXT: fcmp s0, s2
-; CHECK-NEXT: cset w0, mi
+; CHECK-NEXT: cset w8, mi
+; CHECK-NEXT: fcmp s1, s2
+; CHECK-NEXT: csinc w0, w8, wzr, pl
; CHECK-NEXT: ret
%cmp1 = fcmp olt float %arg1, %arg3
%cmp2 = fcmp olt float %arg2, %arg3
@@ -21,9 +23,10 @@ define i1 @test1(float %arg1, float %arg2, float %arg3) #0 {
define i1 @test2(double %arg1, double %arg2, double %arg3) #0 {
; CHECK-LABEL: test2:
; CHECK: // %bb.0:
-; CHECK-NEXT: fmaxnm d0, d0, d1
; CHECK-NEXT: fcmp d0, d2
-; CHECK-NEXT: cset w0, gt
+; CHECK-NEXT: cset w8, gt
+; CHECK-NEXT: fcmp d1, d2
+; CHECK-NEXT: csinc w0, w8, wzr, le
; CHECK-NEXT: ret
%cmp1 = fcmp ogt double %arg1, %arg3
%cmp2 = fcmp ogt double %arg2, %arg3
@@ -69,3 +72,38 @@ define i1 @test4(float %arg1, float %arg2, float %arg3) {
ret i1 %or1
}
+; A and B cannot be signaling NaNs, so the folds happen.
+define i1 @test5(i32 %arg1, i32 %arg2, float %arg3) {
+; CHECK-LABEL: test5:
+; CHECK: // %bb.0:
+; CHECK-NEXT: scvtf s1, w0
+; CHECK-NEXT: scvtf s2, w1
+; CHECK-NEXT: fminnm s1, s1, s2
+; CHECK-NEXT: fcmp s1, s0
+; CHECK-NEXT: cset w0, mi
+; CHECK-NEXT: ret
+ %conv1 = sitofp i32 %arg1 to float
+ %conv2 = sitofp i32 %arg2 to float
+ %cmp1 = fcmp olt float %conv1, %arg3
+ %cmp2 = fcmp olt float %conv2, %arg3
+ %or1 = or i1 %cmp1, %cmp2
+ ret i1 %or1
+}
+
+define i1 @test6(i32 %arg1, i32 %arg2, double %arg3) {
+; CHECK-LABEL: test6:
+; CHECK: // %bb.0:
+; CHECK-NEXT: scvtf d1, w0
+; CHECK-NEXT: scvtf d2, w1
+; CHECK-NEXT: fmaxnm d1, d1, d2
+; CHECK-NEXT: fcmp d1, d0
+; CHECK-NEXT: cset w0, lt
+; CHECK-NEXT: ret
+ %conv1 = sitofp i32 %arg1 to double
+ %conv2 = sitofp i32 %arg2 to double
+ %cmp1 = fcmp ult double %conv1, %arg3
+ %cmp2 = fcmp ult double %conv2, %arg3
+ %and1 = and i1 %cmp1, %cmp2
+ ret i1 %and1
+}
+
diff --git a/llvm/test/CodeGen/AMDGPU/combine_andor_with_cmps_nnan.ll b/llvm/test/CodeGen/AMDGPU/combine_andor_with_cmps_nnan.ll
index 550cfec413d79a..87ac3b4c73d6d6 100644
--- a/llvm/test/CodeGen/AMDGPU/combine_andor_with_cmps_nnan.ll
+++ b/llvm/test/CodeGen/AMDGPU/combine_andor_with_cmps_nnan.ll
@@ -954,15 +954,16 @@ define i1 @test117(float %arg1, float %arg2, float %arg3, float %arg4, float %ar
; GCN-LABEL: test117:
; GCN: ; %bb.0:
; GCN-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
-; GCN-NEXT: v_min3_f32 v4, v4, v5, v6
; GCN-NEXT: v_min_f32_e32 v0, v0, v1
-; GCN-NEXT: v_min3_f32 v1, v8, v9, v10
-; GCN-NEXT: v_min_f32_e32 v2, v2, v3
-; GCN-NEXT: v_min_f32_e32 v3, v4, v7
+; GCN-NEXT: v_min3_f32 v1, v4, v5, v6
+; GCN-NEXT: v_max_f32_e32 v4, v7, v7
+; GCN-NEXT: v_min3_f32 v5, v8, v9, v10
+; GCN-NEXT: v_max_f32_e32 v6, v11, v11
+; GCN-NEXT: v_dual_min_f32 v2, v2, v3 :: v_dual_min_f32 v1, v1, v4
; GCN-NEXT: v_cmp_lt_f32_e32 vcc_lo, v0, v12
-; GCN-NEXT: v_min_f32_e32 v0, v1, v11
+; GCN-NEXT: v_min_f32_e32 v0, v5, v6
; GCN-NEXT: v_cmp_lt_f32_e64 s0, v2, v13
-; GCN-NEXT: v_cmp_lt_f32_e64 s1, v3, v13
+; GCN-NEXT: v_cmp_lt_f32_e64 s1, v1, v13
; GCN-NEXT: v_cmp_lt_f32_e64 s2, v0, v12
; GCN-NEXT: s_or_b32 s0, vcc_lo, s0
; GCN-NEXT: s_or_b32 s0, s0, s1
diff --git a/llvm/test/CodeGen/AMDGPU/or.r600.ll b/llvm/test/CodeGen/AMDGPU/or.r600.ll
index ed9d0085fd82a3..2b06ffaaf387cd 100644
--- a/llvm/test/CodeGen/AMDGPU/or.r600.ll
+++ b/llvm/test/CodeGen/AMDGPU/or.r600.ll
@@ -456,21 +456,26 @@ define amdgpu_kernel void @trunc_i64_or_to_i32(ptr addrspace(1) %out, [8 x i32],
define amdgpu_kernel void @or_i1(ptr addrspace(1) %out, ptr addrspace(1) %in0, ptr addrspace(1) %in1) {
; EG-LABEL: or_i1:
; EG: ; %bb.0:
-; EG-NEXT: ALU 1, @10, KC0[CB0:0-32], KC1[]
-; EG-NEXT: TEX 1 @6
-; EG-NEXT: ALU 4, @12, KC0[CB0:0-32], KC1[]
+; EG-NEXT: ALU 0, @12, KC0[CB0:0-32], KC1[]
+; EG-NEXT: TEX 0 @8
+; EG-NEXT: ALU 0, @13, KC0[CB0:0-32], KC1[]
+; EG-NEXT: TEX 0 @10
+; EG-NEXT: ALU 5, @14, KC0[CB0:0-32], KC1[]
; EG-NEXT: MEM_RAT_CACHELESS STORE_RAW T0.X, T1.X, 1
; EG-NEXT: CF_END
; EG-NEXT: PAD
-; EG-NEXT: Fetch clause starting at 6:
-; EG-NEXT: VTX_READ_32 T1.X, T1.X, 0, #1
+; EG-NEXT: Fetch clause starting at 8:
; EG-NEXT: VTX_READ_32 T0.X, T0.X, 0, #1
-; EG-NEXT: ALU clause starting at 10:
-; EG-NEXT: MOV T0.X, KC0[2].Z,
-; EG-NEXT: MOV * T1.X, KC0[2].W,
+; EG-NEXT: Fetch clause starting at 10:
+; EG-NEXT: VTX_READ_32 T1.X, T1.X, 0, #1
; EG-NEXT: ALU clause starting at 12:
-; EG-NEXT: MAX_DX10 * T0.W, T0.X, T1.X,
-; EG-NEXT: SETGE_DX10 * T0.W, PV.W, 0.0,
+; EG-NEXT: MOV * T0.X, KC0[2].W,
+; EG-NEXT: ALU clause starting at 13:
+; EG-NEXT: MOV * T1.X, KC0[2].Z,
+; EG-NEXT: ALU clause starting at 14:
+; EG-NEXT: SETGE_DX10 T0.W, T0.X, 0.0,
+; EG-NEXT: SETGE_DX10 * T1.W, T1.X, 0.0,
+; EG-NEXT: OR_INT * T0.W, PS, PV.W,
; EG-NEXT: AND_INT T0.X, PV.W, 1,
; EG-NEXT: LSHR * T1.X, KC0[2].Y, literal.x,
; EG-NEXT: 2(2.802597e-45), 0(0.000000e+00)
diff --git a/llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll b/llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll
new file mode 100644
index 00000000000000..99a3788b6fa8f3
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll
@@ -0,0 +1,83 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv64 -mattr=+d -verify-machineinstrs < %s | FileCheck %s
+
+; The tests check the following optimization of DAGCombiner:
+; CMP(A,C)||CMP(B,C) => CMP(MIN/MAX(A,B), C)
+; CMP(A,C)&&CMP(B,C) => CMP(MIN/MAX(A,B), C)
+; FMINIMUMNUM/FMAXIMUMNUM discard signaling NaNs, so A and B may be sNaNs.
+
+define i1 @olt_or(float %arg1, float %arg2, float %arg3) {
+; CHECK-LABEL: olt_or:
+; CHECK: # %bb.0:
+; CHECK-NEXT: fmin.s fa5, fa0, fa1
+; CHECK-NEXT: flt.s a0, fa5, fa2
+; CHECK-NEXT: ret
+ %cmp1 = fcmp olt float %arg1, %arg3
+ %cmp2 = fcmp olt float %arg2, %arg3
+ %or1 = or i1 %cmp1, %cmp2
+ ret i1 %or1
+}
+
+define i1 @ogt_or(double %arg1, double %arg2, double %arg3) {
+; CHECK-LABEL: ogt_or:
+; CHECK: # %bb.0:
+; CHECK-NEXT: fmax.d fa5, fa0, fa1
+; CHECK-NEXT: flt.d a0, fa2, fa5
+; CHECK-NEXT: ret
+ %cmp1 = fcmp ogt double %arg1, %arg3
+ %cmp2 = fcmp ogt double %arg2, %arg3
+ %or1 = or i1 %cmp1, %cmp2
+ ret i1 %or1
+}
+
+define i1 @ugt_and(float %arg1, float %arg2, float %arg3) {
+; CHECK-LABEL: ugt_and:
+; CHECK: # %bb.0:
+; CHECK-NEXT: fmin.s fa5, fa0, fa1
+; CHECK-NEXT: fle.s a0, fa5, fa2
+; CHECK-NEXT: xori a0, a0, 1
+; CHECK-NEXT: ret
+ %cmp1 = fcmp ugt float %arg1, %arg3
+ %cmp2 = fcmp ugt float %arg2, %arg3
+ %and1 = and i1 %cmp1, %cmp2
+ ret i1 %and1
+}
+
+define i1 @ult_and(double %arg1, double %arg2, double %arg3) {
+; CHECK-LABEL: ult_and:
+; CHECK: # %bb.0:
+; CHECK-NEXT: fmax.d fa5, fa0, fa1
+; CHECK-NEXT: fle.d a0, fa2, fa5
+; CHECK-NEXT: xori a0, a0, 1
+; CHECK-NEXT: ret
+ %cmp1 = fcmp ult double %arg1, %arg3
+ %cmp2 = fcmp ult double %arg2, %arg3
+ %and1 = and i1 %cmp1, %cmp2
+ ret i1 %and1
+}
+
+define i1 @nnan_olt_and(float %arg1, float %arg2, float %arg3) {
+; CHECK-LABEL: nnan_olt_and:
+; CHECK: # %bb.0:
+; CHECK-NEXT: fmax.s fa5, fa0, fa1
+; CHECK-NEXT: flt.s a0, fa5, fa2
+; CHECK-NEXT: ret
+ %cmp1 = fcmp nnan olt float %arg1, %arg3
+ %cmp2 = fcmp nnan olt float %arg2, %arg3
+ %and1 = and i1 %cmp1, %cmp2
+ ret i1 %and1
+}
+
+; Negative test: a NaN operand makes its compare false, but MAX discards it.
+define i1 @olt_and(float %arg1, float %arg2, float %arg3) {
+; CHECK-LABEL: olt_and:
+; CHECK: # %bb.0:
+; CHECK-NEXT: flt.s a0, fa1, fa2
+; CHECK-NEXT: flt.s a1, fa0, fa2
+; CHECK-NEXT: and a0, a1, a0
+; CHECK-NEXT: ret
+ %cmp1 = fcmp olt float %arg1, %arg3
+ %cmp2 = fcmp olt float %arg2, %arg3
+ %and1 = and i1 %cmp1, %cmp2
+ ret i1 %and1
+}
>From 747380e4332070984ca35aae4297ee4c904acb6d Mon Sep 17 00:00:00 2001
From: Mitch <mitchbriles at gmail.com>
Date: Wed, 16 Sep 2026 18:47:28 -0600
Subject: [PATCH 2/4] Prefer PSEUDO_FMIN/PSEUDO_FMAX, add X86 test
---
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 57 +++++---
.../CodeGen/X86/combine-andor-with-cmps.ll | 133 ++++++++++++++++++
llvm/test/CodeGen/X86/fcmp-logic.ll | 25 ++--
3 files changed, 177 insertions(+), 38 deletions(-)
create mode 100644 llvm/test/CodeGen/X86/combine-andor-with-cmps.ll
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index a84127e453b11f..39eda34e3b8a0f 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6938,28 +6938,31 @@ static unsigned getMinMaxOpcodeForClamp(bool IsMin, SDValue Operand1,
static unsigned getMinMaxOpcodeForCompareFold(
SDValue Operand1, SDValue Operand2, bool SetCCNoNaNs, ISD::CondCode CC,
unsigned OrAndOpcode, SelectionDAG &DAG, bool isFMAXNUMFMINNUM_IEEE,
- bool isFMAXNUMFMINNUM, bool isFMAXIMUMNUMFMINIMUMNUM) {
+ bool isFMAXNUMFMINNUM, bool isFMAXIMUMNUMFMINIMUMNUM,
+ bool isPSEUDOFMAXFMIN) {
// The optimization cannot be applied for all the predicates because
// of the way the min/max opcodes handle NaNs.
- // It is safe to use FMINIMUMNUM/FMAXIMUMNUM or FMINNUM_IEEE/FMAXNUM_IEEE if
- // all the operands are non NaN values.
+ // It is safe to use PSEUDO_FMIN/PSEUDO_FMAX, FMINIMUMNUM/FMAXIMUMNUM or
+ // FMINNUM_IEEE/FMAXNUM_IEEE if all the operands are non NaN values.
if (((CC == ISD::SETLT || CC == ISD::SETLE) && (OrAndOpcode == ISD::OR)) ||
((CC == ISD::SETGT || CC == ISD::SETGE) && (OrAndOpcode == ISD::AND))) {
if (!SetCCNoNaNs && !arebothOperandsNotNan(Operand1, Operand2, DAG))
return ISD::DELETED_NODE;
- return isFMAXIMUMNUMFMINIMUMNUM ? ISD::FMINIMUMNUM
- : isFMAXNUMFMINNUM_IEEE ? ISD::FMINNUM_IEEE
- : ISD::DELETED_NODE;
+ return isPSEUDOFMAXFMIN ? ISD::PSEUDO_FMIN
+ : isFMAXIMUMNUMFMINIMUMNUM ? ISD::FMINIMUMNUM
+ : isFMAXNUMFMINNUM_IEEE ? ISD::FMINNUM_IEEE
+ : ISD::DELETED_NODE;
}
if (((CC == ISD::SETGT || CC == ISD::SETGE) && (OrAndOpcode == ISD::OR)) ||
((CC == ISD::SETLT || CC == ISD::SETLE) && (OrAndOpcode == ISD::AND))) {
if (!SetCCNoNaNs && !arebothOperandsNotNan(Operand1, Operand2, DAG))
return ISD::DELETED_NODE;
- return isFMAXIMUMNUMFMINIMUMNUM ? ISD::FMAXIMUMNUM
- : isFMAXNUMFMINNUM_IEEE ? ISD::FMAXNUM_IEEE
- : ISD::DELETED_NODE;
+ return isPSEUDOFMAXFMIN ? ISD::PSEUDO_FMAX
+ : isFMAXIMUMNUMFMINIMUMNUM ? ISD::FMAXIMUMNUM
+ : isFMAXNUMFMINNUM_IEEE ? ISD::FMAXNUM_IEEE
+ : ISD::DELETED_NODE;
}
bool IsMin;
@@ -6975,9 +6978,12 @@ static unsigned getMinMaxOpcodeForCompareFold(
return ISD::DELETED_NODE;
// For the above predicates, the optimization is valid if a NaN operand is
- // discarded. FMINIMUMNUM/FMAXIMUMNUM always do so. FMINNUM/FMAXNUM and
- // FMINNUM_IEEE/FMAXNUM_IEEE only do so for quiet NaNs, as a signaling NaN
+ // discarded. PSEUDO_FMIN/PSEUDO_FMAX return the second operand if either
+ // operand is a NaN. FMINIMUMNUM/FMAXIMUMNUM always discard. FMINNUM/FMAXNUM
+ // and FMINNUM_IEEE/FMAXNUM_IEEE only do so for quiet NaNs, as a signaling NaN
// operand may instead produce a NaN.
+ if (isPSEUDOFMAXFMIN && DAG.isKnownNeverNaN(Operand2))
+ return IsMin ? ISD::PSEUDO_FMIN : ISD::PSEUDO_FMAX;
if (isFMAXIMUMNUMFMINIMUMNUM)
return IsMin ? ISD::FMINIMUMNUM : ISD::FMAXIMUMNUM;
if (!arebothOperandsNotSNan(Operand1, Operand2, DAG))
@@ -7036,20 +7042,21 @@ static SDValue foldAndOrOfSETCC(SDNode *LogicOp, SelectionDAG &DAG) {
TLI.isOperationLegal(ISD::FMINNUM_IEEE, OpVT);
bool isFMAXNUMFMINNUM = TLI.isOperationLegalOrCustom(ISD::FMAXNUM, OpVT) &&
TLI.isOperationLegalOrCustom(ISD::FMINNUM, OpVT);
- // A custom FMINIMUMNUM/FMAXIMUMNUM may be more expensive than the compares,
- // so only use one if the target considers forming it profitable.
+ // A custom FMINIMUMNUM/FMAXIMUMNUM may be more expensive than the compares.
bool isFMAXIMUMNUMFMINIMUMNUM =
- (TLI.isOperationLegal(ISD::FMAXIMUMNUM, OpVT) &&
- TLI.isOperationLegal(ISD::FMINIMUMNUM, OpVT)) ||
- (TLI.isOperationLegalOrCustom(ISD::FMAXIMUMNUM, OpVT) &&
- TLI.isOperationLegalOrCustom(ISD::FMINIMUMNUM, OpVT) &&
- TLI.isProfitableToCombineMinNumMaxNum(OpVT));
+ TLI.isOperationLegalOrCustom(ISD::FMAXIMUMNUM, OpVT) &&
+ TLI.isOperationLegalOrCustom(ISD::FMINIMUMNUM, OpVT) &&
+ TLI.isProfitableToCombineMinNumMaxNum(OpVT);
+ bool isPSEUDOFMAXFMIN =
+ TLI.isOperationLegalOrCustom(ISD::PSEUDO_FMAX, OpVT) &&
+ TLI.isOperationLegalOrCustom(ISD::PSEUDO_FMIN, OpVT);
if (((OpVT.isInteger() && TLI.isOperationLegal(ISD::UMAX, OpVT) &&
TLI.isOperationLegal(ISD::SMAX, OpVT) &&
TLI.isOperationLegal(ISD::UMIN, OpVT) &&
TLI.isOperationLegal(ISD::SMIN, OpVT)) ||
- (OpVT.isFloatingPoint() && (isFMAXNUMFMINNUM_IEEE || isFMAXNUMFMINNUM ||
- isFMAXIMUMNUMFMINIMUMNUM))) &&
+ (OpVT.isFloatingPoint() &&
+ (isFMAXNUMFMINNUM_IEEE || isFMAXNUMFMINNUM ||
+ isFMAXIMUMNUMFMINIMUMNUM || isPSEUDOFMAXFMIN))) &&
!ISD::isIntEqualitySetCC(CCL) && !ISD::isFPEqualitySetCC(CCL) &&
CCL != ISD::SETFALSE && CCL != ISD::SETO && CCL != ISD::SETUO &&
CCL != ISD::SETTRUE &&
@@ -7102,12 +7109,18 @@ static SDValue foldAndOrOfSETCC(SDNode *LogicOp, SelectionDAG &DAG) {
NewOpcode = IsSigned ? ISD::SMIN : ISD::UMIN;
else
NewOpcode = IsSigned ? ISD::SMAX : ISD::UMAX;
- } else if (OpVT.isFloatingPoint())
+ } else if (OpVT.isFloatingPoint()) {
+ // The min/max opcodes below are commutative, so move a known non-NaN
+ // operand second, where PSEUDO_FMIN/PSEUDO_FMAX can make use of it.
+ if (isPSEUDOFMAXFMIN && !DAG.isKnownNeverNaN(Operand2) &&
+ DAG.isKnownNeverNaN(Operand1))
+ std::swap(Operand1, Operand2);
NewOpcode = getMinMaxOpcodeForCompareFold(
Operand1, Operand2,
LHSSetCCFlags.hasNoNaNs() && RHSSetCCFlags.hasNoNaNs(), CC,
LogicOp->getOpcode(), DAG, isFMAXNUMFMINNUM_IEEE, isFMAXNUMFMINNUM,
- isFMAXIMUMNUMFMINIMUMNUM);
+ isFMAXIMUMNUMFMINIMUMNUM, isPSEUDOFMAXFMIN);
+ }
if (NewOpcode != ISD::DELETED_NODE) {
// Propagate fast-math flags from setcc.
diff --git a/llvm/test/CodeGen/X86/combine-andor-with-cmps.ll b/llvm/test/CodeGen/X86/combine-andor-with-cmps.ll
new file mode 100644
index 00000000000000..e340b9b3e3ff5d
--- /dev/null
+++ b/llvm/test/CodeGen/X86/combine-andor-with-cmps.ll
@@ -0,0 +1,133 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=x86_64-- -mattr=sse2 | FileCheck %s --check-prefixes=SSE2
+; RUN: llc < %s -mtriple=x86_64-- -mattr=avx | FileCheck %s --check-prefixes=AVX,AVX1
+; RUN: llc < %s -mtriple=x86_64-- -mattr=avx512f | FileCheck %s --check-prefixes=AVX,AVX512
+
+; The tests check the following optimization of DAGCombiner:
+; CMP(A,C)||CMP(B,C) => CMP(MIN/MAX(A,B), C)
+; CMP(A,C)&&CMP(B,C) => CMP(MIN/MAX(A,B), C)
+; PSEUDO_FMIN/PSEUDO_FMAX return their second operand if either operand is NaN,
+; so they can only be used if one of A or B cannot be NaN.
+
+; Negative test: A and B may both be NaN.
+define i1 @olt_or_maybe_nan(float %a, float %b, float %c) {
+; SSE2-LABEL: olt_or_maybe_nan:
+; SSE2: # %bb.0:
+; SSE2-NEXT: cmpltss %xmm2, %xmm1
+; SSE2-NEXT: cmpltss %xmm2, %xmm0
+; SSE2-NEXT: orps %xmm1, %xmm0
+; SSE2-NEXT: movd %xmm0, %eax
+; SSE2-NEXT: # kill: def $al killed $al killed $eax
+; SSE2-NEXT: retq
+;
+; AVX1-LABEL: olt_or_maybe_nan:
+; AVX1: # %bb.0:
+; AVX1-NEXT: vcmpltss %xmm2, %xmm1, %xmm1
+; AVX1-NEXT: vcmpltss %xmm2, %xmm0, %xmm0
+; AVX1-NEXT: vorps %xmm1, %xmm0, %xmm0
+; AVX1-NEXT: vmovd %xmm0, %eax
+; AVX1-NEXT: # kill: def $al killed $al killed $eax
+; AVX1-NEXT: retq
+;
+; AVX512-LABEL: olt_or_maybe_nan:
+; AVX512: # %bb.0:
+; AVX512-NEXT: vcmpltss %xmm2, %xmm1, %k0
+; AVX512-NEXT: vcmpltss %xmm2, %xmm0, %k1
+; AVX512-NEXT: korw %k0, %k1, %k0
+; AVX512-NEXT: kmovw %k0, %eax
+; AVX512-NEXT: # kill: def $al killed $al killed $eax
+; AVX512-NEXT: retq
+ %cmp1 = fcmp olt float %a, %c
+ %cmp2 = fcmp olt float %b, %c
+ %or = or i1 %cmp1, %cmp2
+ ret i1 %or
+}
+
+define i1 @olt_or_second_not_nan(float %a, i32 %b, float %c) {
+; SSE2-LABEL: olt_or_second_not_nan:
+; SSE2: # %bb.0:
+; SSE2-NEXT: cvtsi2ss %edi, %xmm2
+; SSE2-NEXT: minss %xmm2, %xmm0
+; SSE2-NEXT: ucomiss %xmm0, %xmm1
+; SSE2-NEXT: seta %al
+; SSE2-NEXT: retq
+;
+; AVX-LABEL: olt_or_second_not_nan:
+; AVX: # %bb.0:
+; AVX-NEXT: vcvtsi2ss %edi, %xmm15, %xmm2
+; AVX-NEXT: vminss %xmm2, %xmm0, %xmm0
+; AVX-NEXT: vucomiss %xmm0, %xmm1
+; AVX-NEXT: seta %al
+; AVX-NEXT: retq
+ %conv = sitofp i32 %b to float
+ %cmp1 = fcmp olt float %a, %c
+ %cmp2 = fcmp olt float %conv, %c
+ %or = or i1 %cmp1, %cmp2
+ ret i1 %or
+}
+
+define i1 @ogt_or_first_not_nan(i32 %a, double %b, double %c) {
+; SSE2-LABEL: ogt_or_first_not_nan:
+; SSE2: # %bb.0:
+; SSE2-NEXT: cvtsi2sd %edi, %xmm2
+; SSE2-NEXT: maxsd %xmm2, %xmm0
+; SSE2-NEXT: ucomisd %xmm1, %xmm0
+; SSE2-NEXT: seta %al
+; SSE2-NEXT: retq
+;
+; AVX-LABEL: ogt_or_first_not_nan:
+; AVX: # %bb.0:
+; AVX-NEXT: vcvtsi2sd %edi, %xmm15, %xmm2
+; AVX-NEXT: vmaxsd %xmm2, %xmm0, %xmm0
+; AVX-NEXT: vucomisd %xmm1, %xmm0
+; AVX-NEXT: seta %al
+; AVX-NEXT: retq
+ %conv = sitofp i32 %a to double
+ %cmp1 = fcmp ogt double %conv, %c
+ %cmp2 = fcmp ogt double %b, %c
+ %or = or i1 %cmp1, %cmp2
+ ret i1 %or
+}
+
+define i1 @ugt_and_first_not_nan(i32 %a, float %b, float %c) {
+; SSE2-LABEL: ugt_and_first_not_nan:
+; SSE2: # %bb.0:
+; SSE2-NEXT: cvtsi2ss %edi, %xmm2
+; SSE2-NEXT: minss %xmm2, %xmm0
+; SSE2-NEXT: ucomiss %xmm0, %xmm1
+; SSE2-NEXT: setb %al
+; SSE2-NEXT: retq
+;
+; AVX-LABEL: ugt_and_first_not_nan:
+; AVX: # %bb.0:
+; AVX-NEXT: vcvtsi2ss %edi, %xmm15, %xmm2
+; AVX-NEXT: vminss %xmm2, %xmm0, %xmm0
+; AVX-NEXT: vucomiss %xmm0, %xmm1
+; AVX-NEXT: setb %al
+; AVX-NEXT: retq
+ %conv = sitofp i32 %a to float
+ %cmp1 = fcmp ugt float %conv, %c
+ %cmp2 = fcmp ugt float %b, %c
+ %and = and i1 %cmp1, %cmp2
+ ret i1 %and
+}
+
+define i1 @nnan_olt_and(float %a, float %b, float %c) {
+; SSE2-LABEL: nnan_olt_and:
+; SSE2: # %bb.0:
+; SSE2-NEXT: maxss %xmm1, %xmm0
+; SSE2-NEXT: ucomiss %xmm2, %xmm0
+; SSE2-NEXT: setb %al
+; SSE2-NEXT: retq
+;
+; AVX-LABEL: nnan_olt_and:
+; AVX: # %bb.0:
+; AVX-NEXT: vmaxss %xmm1, %xmm0, %xmm0
+; AVX-NEXT: vucomiss %xmm2, %xmm0
+; AVX-NEXT: setb %al
+; AVX-NEXT: retq
+ %cmp1 = fcmp nnan olt float %a, %c
+ %cmp2 = fcmp nnan olt float %b, %c
+ %and = and i1 %cmp1, %cmp2
+ ret i1 %and
+}
diff --git a/llvm/test/CodeGen/X86/fcmp-logic.ll b/llvm/test/CodeGen/X86/fcmp-logic.ll
index 98fa725b2ea3a1..66cb5daa8f71cb 100644
--- a/llvm/test/CodeGen/X86/fcmp-logic.ll
+++ b/llvm/test/CodeGen/X86/fcmp-logic.ll
@@ -387,13 +387,10 @@ define i1 @PR140534(i32 %a0, i32 %a1, i32 %a2) {
; SSE2-NEXT: cvtsi2sd %rax, %xmm1
; SSE2-NEXT: movl %edx, %eax
; SSE2-NEXT: cvtsi2sd %rax, %xmm2
+; SSE2-NEXT: maxsd %xmm2, %xmm0
; SSE2-NEXT: mulsd {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm1
-; SSE2-NEXT: movapd %xmm1, %xmm3
-; SSE2-NEXT: cmpltsd %xmm2, %xmm3
-; SSE2-NEXT: cmpltsd %xmm0, %xmm1
-; SSE2-NEXT: orpd %xmm3, %xmm1
-; SSE2-NEXT: movd %xmm1, %eax
-; SSE2-NEXT: # kill: def $al killed $al killed $eax
+; SSE2-NEXT: ucomisd %xmm1, %xmm0
+; SSE2-NEXT: seta %al
; SSE2-NEXT: retq
;
; AVX1-LABEL: PR140534:
@@ -404,12 +401,10 @@ define i1 @PR140534(i32 %a0, i32 %a1, i32 %a2) {
; AVX1-NEXT: vcvtsi2sd %rax, %xmm15, %xmm1
; AVX1-NEXT: movl %edx, %eax
; AVX1-NEXT: vcvtsi2sd %rax, %xmm15, %xmm2
+; AVX1-NEXT: vmaxsd %xmm2, %xmm0, %xmm0
; AVX1-NEXT: vmulsd {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm1, %xmm1
-; AVX1-NEXT: vcmpltsd %xmm2, %xmm1, %xmm2
-; AVX1-NEXT: vcmpltsd %xmm0, %xmm1, %xmm0
-; AVX1-NEXT: vorpd %xmm2, %xmm0, %xmm0
-; AVX1-NEXT: vmovd %xmm0, %eax
-; AVX1-NEXT: # kill: def $al killed $al killed $eax
+; AVX1-NEXT: vucomisd %xmm1, %xmm0
+; AVX1-NEXT: seta %al
; AVX1-NEXT: retq
;
; AVX512-LABEL: PR140534:
@@ -417,12 +412,10 @@ define i1 @PR140534(i32 %a0, i32 %a1, i32 %a2) {
; AVX512-NEXT: vcvtusi2sd %edi, %xmm15, %xmm0
; AVX512-NEXT: vcvtusi2sd %esi, %xmm15, %xmm1
; AVX512-NEXT: vcvtusi2sd %edx, %xmm15, %xmm2
+; AVX512-NEXT: vmaxsd %xmm2, %xmm0, %xmm0
; AVX512-NEXT: vmulsd {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm1, %xmm1
-; AVX512-NEXT: vcmpltsd %xmm2, %xmm1, %k0
-; AVX512-NEXT: vcmpltsd %xmm0, %xmm1, %k1
-; AVX512-NEXT: korw %k0, %k1, %k0
-; AVX512-NEXT: kmovw %k0, %eax
-; AVX512-NEXT: # kill: def $al killed $al killed $eax
+; AVX512-NEXT: vucomisd %xmm1, %xmm0
+; AVX512-NEXT: seta %al
; AVX512-NEXT: retq
%conv0 = uitofp i32 %a0 to double
%conv1 = uitofp i32 %a1 to double
>From 357fe4acd58eff32803493afe175ac59c456f264 Mon Sep 17 00:00:00 2001
From: Mitch <mitchbriles at gmail.com>
Date: Wed, 16 Sep 2026 18:47:56 -0600
Subject: [PATCH 3/4] Don't verify-machineinstrs
---
llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll b/llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll
index 99a3788b6fa8f3..8e730a3328b345 100644
--- a/llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll
+++ b/llvm/test/CodeGen/RISCV/combine-andor-with-cmps.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
-; RUN: llc -mtriple=riscv64 -mattr=+d -verify-machineinstrs < %s | FileCheck %s
+; RUN: llc -mtriple=riscv64 -mattr=+d < %s | FileCheck %s
; The tests check the following optimization of DAGCombiner:
; CMP(A,C)||CMP(B,C) => CMP(MIN/MAX(A,B), C)
>From 1b92944d87baf29338338bb53fe0d10ed59efccb Mon Sep 17 00:00:00 2001
From: Mitch <mitchbriles at gmail.com>
Date: Fri, 25 Sep 2026 17:51:35 -0600
Subject: [PATCH 4/4] [NFC] Trim down comments in ISDOpcodes.h
---
llvm/include/llvm/CodeGen/ISDOpcodes.h | 40 ++++----------------------
1 file changed, 6 insertions(+), 34 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/ISDOpcodes.h b/llvm/include/llvm/CodeGen/ISDOpcodes.h
index 5e38c7ccfbb3d5..67a4375a693690 100644
--- a/llvm/include/llvm/CodeGen/ISDOpcodes.h
+++ b/llvm/include/llvm/CodeGen/ISDOpcodes.h
@@ -1085,53 +1085,25 @@ enum NodeType {
LRINT,
LLRINT,
- /// FMINNUM/FMAXNUM - NaN-discarding minimum/maximum: if one operand is a
- /// quiet NaN and the other is a number, returns the number.
- ///
- /// If an operand is a signaling NaN, this will non-deterministically either:
- /// - Return a NaN.
- /// - Or treat the signaling NaN as a quiet NaN.
- ///
- /// These treat -0 as ordered less than +0, matching the behavior of IEEE-754
- /// 2019's minimumNumber/maximumNumber. With the nsz flag, one +0.0 and one
- /// -0.0 operand may non-deterministically return either operand; contrary to
- /// normal nsz semantics, if both operands have the same sign, so must the
- /// result. Note that not all backends respect this ordering yet.
+ /// FMINNUM/FMAXNUM - Same semantics as the llvm.minnum/maxnum intrinsics.
FMINNUM,
FMAXNUM,
/// FMINNUM_IEEE/FMAXNUM_IEEE - Same as FMINNUM/FMAXNUM, except that a
- /// signaling NaN operand deterministically returns a quiet NaN, matching/for
- /// IEEE-754 2008's minNum/maxNum. Signed zeros are ordered identically to
- /// FMINNUM/FMAXNUM: -0 is less than +0, relaxed by the nsz flag.
+ /// signaling NaN operand deterministically returns a quiet NaN, like IEEE-754
+ /// 2008's minNum/maxNum. -0 is ordered less than +0, relaxed by the nsz flag.
///
/// Deprecated, and will be removed soon: this is a legal implementation of
/// FMINNUM/FMAXNUM, so targets should select those instead.
FMINNUM_IEEE,
FMAXNUM_IEEE,
- /// FMINIMUM/FMAXIMUM - NaN-propagating minimum/maximum: if either operand is
- /// a NaN, returns a NaN. Follows C23's fminimum/fmaximum and IEEE-754 2019's
- /// minimum/maximum, except that a signaling NaN operand is not guaranteed to
- /// be quieted.
- ///
- /// These treat -0 as ordered less than +0. With the nsz flag, one +0.0 and
- /// one -0.0 operand may non-deterministically return either operand;
- /// contrary to normal nsz semantics, if both operands have the same sign, so
- /// must the result.
+ /// FMINIMUM/FMAXIMUM - Same semantics as the llvm.minimum/maximum intrinsics.
FMINIMUM,
FMAXIMUM,
- /// FMINIMUMNUM/FMAXIMUMNUM - NaN-discarding minimum/maximum: if one operand
- /// is a NaN and the other is a number, returns the number. Follows C23's
- /// fminimum_num/fmaximum_num and IEEE-754 2019's minimumNumber/maximumNumber,
- /// except that a signaling NaN operand is not guaranteed to be quieted.
- /// Same as FMINNUM/FMAXNUM, but treats signaling NaNs as quiet NaNs.
- ///
- /// These treat -0 as ordered less than +0. With the nsz flag, one +0.0 and
- /// one -0.0 operand may non-deterministically return either operand;
- /// contrary to normal nsz semantics, if both operands have the same sign, so
- /// must the result.
+ /// FMINIMUMNUM/FMAXIMUMNUM - Same semantics as the llvm.minimumnum/maximumnum
+ /// intrinsics.
FMINIMUMNUM,
FMAXIMUMNUM,
More information about the llvm-commits
mailing list