[llvm] [RISCV] Simplify float-point boundary comparisons with FCLASS (PR #228984)

Julian Zhu via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 4 23:37:22 PDT 2026


https://github.com/HeliC829 created https://github.com/llvm/llvm-project/pull/228984

Simplify float-point boundary comparisons with FCLASS instruction.

>From 96883812e72d4a65237cc051e84887e44d5313bd Mon Sep 17 00:00:00 2001
From: Julian Zhu <jz531210 at gmail.com>
Date: Mon, 28 Sep 2026 01:44:02 +0800
Subject: [PATCH 1/2] [RISCV] Fold maximum-finite FP boundary comparisons to
 FCLASS

Compares of f32/f64 against the largest finite value are equivalent to
infinity checks, so lower them as IS_FPCLASS instead of materializing
the constant.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  40 ++
 .../CodeGen/RISCV/fcmp-boundary-fclass.ll     | 400 ++++++++++++++++++
 llvm/test/CodeGen/RISCV/rv64-float-convert.ll |  13 +-
 llvm/test/CodeGen/RISCV/rv64-half-convert.ll  |  13 +-
 4 files changed, 452 insertions(+), 14 deletions(-)
 create mode 100644 llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 4b9175b9aad27..05d213c50b7b4 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -20852,6 +20852,42 @@ combineVectorSizedSetCCEquality(EVT VT, SDValue X, SDValue Y, ISD::CondCode CC,
                       DAG.getConstant(0, DL, XLenVT), CC);
 }
 
+static SDValue combineFPBoundarySetCC(SDNode *N, SelectionDAG &DAG,
+                                      const RISCVSubtarget &Subtarget) {
+  SDValue X = N->getOperand(0);
+  SDValue Bound = N->getOperand(1);
+  EVT FPVT = X.getValueType();
+  if ((FPVT != MVT::f32 && FPVT != MVT::f64) ||
+      (FPVT == MVT::f32 && !Subtarget.hasStdExtFOrZfinx()) ||
+      (FPVT == MVT::f64 && !Subtarget.hasStdExtDOrZdinx()) ||
+      !isa<ConstantFPSDNode>(Bound))
+    return SDValue();
+
+  const APFloat &C = cast<ConstantFPSDNode>(Bound)->getValueAPF();
+  ISD::CondCode CC = cast<CondCodeSDNode>(N->getOperand(2))->get();
+  FPClassTest Mask = fcNone;
+  FPClassTest NaNs = fcSNan | fcQNan;
+  if (C.bitwiseIsEqual(APFloat::getLargest(C.getSemantics(), true))) {
+    if (CC == ISD::SETOLT || CC == ISD::SETULT)
+      Mask = fcNegInf;
+    else if (CC == ISD::SETOGE || CC == ISD::SETUGE)
+      Mask = fcAllFlags & ~(fcNegInf | NaNs);
+  } else if (C.bitwiseIsEqual(APFloat::getLargest(C.getSemantics()))) {
+    if (CC == ISD::SETOGT || CC == ISD::SETUGT)
+      Mask = fcPosInf;
+    else if (CC == ISD::SETOLE || CC == ISD::SETULE)
+      Mask = fcAllFlags & ~(fcPosInf | NaNs);
+  }
+  if (Mask == fcNone)
+    return SDValue();
+  if (CC == ISD::SETULT || CC == ISD::SETUGE || CC == ISD::SETUGT ||
+      CC == ISD::SETULE)
+    Mask |= NaNs;
+  SDLoc DL(N);
+  return DAG.getNode(ISD::IS_FPCLASS, DL, N->getValueType(0), X,
+                     DAG.getTargetConstant(Mask, DL, MVT::i32));
+}
+
 static SDValue performSETCCCombine(SDNode *N,
                                    TargetLowering::DAGCombinerInfo &DCI,
                                    const RISCVSubtarget &Subtarget) {
@@ -20862,6 +20898,10 @@ static SDValue performSETCCCombine(SDNode *N,
   EVT VT = N->getValueType(0);
   EVT OpVT = N0.getValueType();
 
+  if (DCI.isBeforeLegalizeOps() && OpVT.isFloatingPoint())
+    if (SDValue V = combineFPBoundarySetCC(N, DAG, Subtarget))
+      return V;
+
   ISD::CondCode Cond = cast<CondCodeSDNode>(N->getOperand(2))->get();
   if (SDValue V =
           canonicalizeMaskForVLPredicate(VT, N0, N1, Cond, dl, DAG, DCI))
diff --git a/llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll b/llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll
new file mode 100644
index 0000000000000..fb0f15229b65f
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll
@@ -0,0 +1,400 @@
+; 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 \
+; RUN:   | FileCheck %s --check-prefix=RV64D
+; RUN: llc -mtriple=riscv64 -mattr=+d,+zfa -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s --check-prefix=RV64ZFA
+; RUN: llc -mtriple=riscv32 -mattr=+d -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s --check-prefix=RV32D
+; RUN: llc -mtriple=riscv64 -mattr=+zdinx -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s --check-prefix=RV64ZDINX
+
+define i1 @below_negative_max_float(float %x) {
+; RV64D-LABEL: below_negative_max_float:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.s a0, fa0
+; RV64D-NEXT:    andi a0, a0, 1
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: below_negative_max_float:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.s a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 1
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: below_negative_max_float:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.s a0, fa0
+; RV32D-NEXT:    andi a0, a0, 1
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: below_negative_max_float:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.s a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 1
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp olt float %x, 0xC7EFFFFFE0000000
+  ret i1 %r
+}
+
+define i1 @above_positive_max_float(float %x) {
+; RV64D-LABEL: above_positive_max_float:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.s a0, fa0
+; RV64D-NEXT:    slli a0, a0, 56
+; RV64D-NEXT:    srli a0, a0, 63
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: above_positive_max_float:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.s a0, fa0
+; RV64ZFA-NEXT:    slli a0, a0, 56
+; RV64ZFA-NEXT:    srli a0, a0, 63
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: above_positive_max_float:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.s a0, fa0
+; RV32D-NEXT:    slli a0, a0, 24
+; RV32D-NEXT:    srli a0, a0, 31
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: above_positive_max_float:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.s a0, a0
+; RV64ZDINX-NEXT:    slli a0, a0, 56
+; RV64ZDINX-NEXT:    srli a0, a0, 63
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ogt float %x, 0x47EFFFFFE0000000
+  ret i1 %r
+}
+
+define i1 @at_least_negative_max_float_unordered(float %x) {
+; RV64D-LABEL: at_least_negative_max_float_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.s a0, fa0
+; RV64D-NEXT:    andi a0, a0, 1022
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_least_negative_max_float_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.s a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 1022
+; RV64ZFA-NEXT:    snez a0, a0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_least_negative_max_float_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.s a0, fa0
+; RV32D-NEXT:    andi a0, a0, 1022
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_least_negative_max_float_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.s a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 1022
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp uge float %x, 0xC7EFFFFFE0000000
+  ret i1 %r
+}
+
+define i1 @below_negative_max(double %x) {
+; RV64D-LABEL: below_negative_max:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 1
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: below_negative_max:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 1
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: below_negative_max:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 1
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: below_negative_max:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 1
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp olt double %x, 0xFFEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @above_positive_max(double %x) {
+; RV64D-LABEL: above_positive_max:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    slli a0, a0, 56
+; RV64D-NEXT:    srli a0, a0, 63
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: above_positive_max:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    slli a0, a0, 56
+; RV64ZFA-NEXT:    srli a0, a0, 63
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: above_positive_max:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    slli a0, a0, 24
+; RV32D-NEXT:    srli a0, a0, 31
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: above_positive_max:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    slli a0, a0, 56
+; RV64ZDINX-NEXT:    srli a0, a0, 63
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ogt double %x, 0x7FEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @below_negative_max_unordered(double %x) {
+; RV64D-LABEL: below_negative_max_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 769
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: below_negative_max_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 769
+; RV64ZFA-NEXT:    snez a0, a0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: below_negative_max_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 769
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: below_negative_max_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 769
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ult double %x, 0xFFEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @at_most_positive_max(double %x) {
+; RV64D-LABEL: at_most_positive_max:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 127
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_most_positive_max:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 127
+; RV64ZFA-NEXT:    snez a0, a0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_most_positive_max:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 127
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_most_positive_max:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 127
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ole double %x, 0x7FEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @at_least_negative_max(double %x) {
+; RV64D-LABEL: at_least_negative_max:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 254
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_least_negative_max:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 254
+; RV64ZFA-NEXT:    snez a0, a0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_least_negative_max:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 254
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_least_negative_max:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 254
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp oge double %x, 0xFFEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @at_least_negative_max_unordered(double %x) {
+; RV64D-LABEL: at_least_negative_max_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 1022
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_least_negative_max_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 1022
+; RV64ZFA-NEXT:    snez a0, a0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_least_negative_max_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 1022
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_least_negative_max_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 1022
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp uge double %x, 0xFFEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @above_positive_max_unordered(double %x) {
+; RV64D-LABEL: above_positive_max_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 896
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: above_positive_max_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 896
+; RV64ZFA-NEXT:    snez a0, a0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: above_positive_max_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 896
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: above_positive_max_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 896
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ugt double %x, 0x7FEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @at_most_positive_max_unordered(double %x) {
+; RV64D-LABEL: at_most_positive_max_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 895
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_most_positive_max_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fclass.d a0, fa0
+; RV64ZFA-NEXT:    andi a0, a0, 895
+; RV64ZFA-NEXT:    snez a0, a0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_most_positive_max_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 895
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_most_positive_max_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 895
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ule double %x, 0x7FEFFFFFFFFFFFFF
+  ret i1 %r
+}
+
+define i1 @strict_below_negative_max(double %x) strictfp {
+; RV64D-LABEL: strict_below_negative_max:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    lui a0, %hi(.LCPI11_0)
+; RV64D-NEXT:    fld fa5, %lo(.LCPI11_0)(a0)
+; RV64D-NEXT:    frflags a1
+; RV64D-NEXT:    flt.d a0, fa0, fa5
+; RV64D-NEXT:    fsflags a1
+; RV64D-NEXT:    feq.d zero, fa0, fa5
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: strict_below_negative_max:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    lui a0, %hi(.LCPI11_0)
+; RV64ZFA-NEXT:    fld fa5, %lo(.LCPI11_0)(a0)
+; RV64ZFA-NEXT:    fltq.d a0, fa0, fa5
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: strict_below_negative_max:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    lui a0, %hi(.LCPI11_0)
+; RV32D-NEXT:    fld fa5, %lo(.LCPI11_0)(a0)
+; RV32D-NEXT:    frflags a1
+; RV32D-NEXT:    flt.d a0, fa0, fa5
+; RV32D-NEXT:    fsflags a1
+; RV32D-NEXT:    feq.d zero, fa0, fa5
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: strict_below_negative_max:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    mv a1, a0
+; RV64ZDINX-NEXT:    li a0, -1
+; RV64ZDINX-NEXT:    slli a0, a0, 52
+; RV64ZDINX-NEXT:    addi a2, a0, -1
+; RV64ZDINX-NEXT:    frflags a3
+; RV64ZDINX-NEXT:    flt.d a0, a1, a2
+; RV64ZDINX-NEXT:    fsflags a3
+; RV64ZDINX-NEXT:    feq.d zero, a1, a2
+; RV64ZDINX-NEXT:    ret
+  %r = call i1 @llvm.experimental.constrained.fcmp.f64(
+      double %x, double 0xFFEFFFFFFFFFFFFF,
+      metadata !"olt", metadata !"fpexcept.strict") strictfp
+  ret i1 %r
+}
+
+declare i1 @llvm.experimental.constrained.fcmp.f64(double, double, metadata, metadata)
diff --git a/llvm/test/CodeGen/RISCV/rv64-float-convert.ll b/llvm/test/CodeGen/RISCV/rv64-float-convert.ll
index df9a3da095729..30e1b8a648adf 100644
--- a/llvm/test/CodeGen/RISCV/rv64-float-convert.ll
+++ b/llvm/test/CodeGen/RISCV/rv64-float-convert.ll
@@ -235,11 +235,10 @@ define i128 @fptoui_sat_f32_to_i128(float %a) nounwind {
 ; RV64IF-NEXT:    fle.s a0, fa5, fa0
 ; RV64IF-NEXT:    neg s0, a0
 ; RV64IF-NEXT:    call __fixunssfti
-; RV64IF-NEXT:    lui a2, 522240
-; RV64IF-NEXT:    addi a2, a2, -1
-; RV64IF-NEXT:    fmv.w.x fa5, a2
-; RV64IF-NEXT:    flt.s a2, fa5, fs0
+; RV64IF-NEXT:    fclass.s a2, fs0
+; RV64IF-NEXT:    slli a2, a2, 56
 ; RV64IF-NEXT:    and a0, s0, a0
+; RV64IF-NEXT:    srli a2, a2, 63
 ; RV64IF-NEXT:    neg a2, a2
 ; RV64IF-NEXT:    and a1, s0, a1
 ; RV64IF-NEXT:    or a0, a2, a0
@@ -261,10 +260,10 @@ define i128 @fptoui_sat_f32_to_i128(float %a) nounwind {
 ; RV64IZFINX-NEXT:    neg s1, a0
 ; RV64IZFINX-NEXT:    mv a0, s0
 ; RV64IZFINX-NEXT:    call __fixunssfti
-; RV64IZFINX-NEXT:    lui a2, 522240
-; RV64IZFINX-NEXT:    addi a2, a2, -1
-; RV64IZFINX-NEXT:    flt.s a2, a2, s0
+; RV64IZFINX-NEXT:    fclass.s a2, s0
+; RV64IZFINX-NEXT:    slli a2, a2, 56
 ; RV64IZFINX-NEXT:    and a0, s1, a0
+; RV64IZFINX-NEXT:    srli a2, a2, 63
 ; RV64IZFINX-NEXT:    neg a2, a2
 ; RV64IZFINX-NEXT:    and a1, s1, a1
 ; RV64IZFINX-NEXT:    or a0, a2, a0
diff --git a/llvm/test/CodeGen/RISCV/rv64-half-convert.ll b/llvm/test/CodeGen/RISCV/rv64-half-convert.ll
index 8f101741375be..811f0fa06bbdd 100644
--- a/llvm/test/CodeGen/RISCV/rv64-half-convert.ll
+++ b/llvm/test/CodeGen/RISCV/rv64-half-convert.ll
@@ -315,11 +315,10 @@ define i128 @fptoui_sat_f16_to_i128(half %a) nounwind {
 ; RV64IZFH-NEXT:    neg s0, a0
 ; RV64IZFH-NEXT:    fmv.s fa0, fs0
 ; RV64IZFH-NEXT:    call __fixunssfti
-; RV64IZFH-NEXT:    lui a2, 522240
-; RV64IZFH-NEXT:    addi a2, a2, -1
-; RV64IZFH-NEXT:    fmv.w.x fa5, a2
-; RV64IZFH-NEXT:    flt.s a2, fa5, fs0
+; RV64IZFH-NEXT:    fclass.s a2, fs0
+; RV64IZFH-NEXT:    slli a2, a2, 56
 ; RV64IZFH-NEXT:    and a0, s0, a0
+; RV64IZFH-NEXT:    srli a2, a2, 63
 ; RV64IZFH-NEXT:    neg a2, a2
 ; RV64IZFH-NEXT:    and a1, s0, a1
 ; RV64IZFH-NEXT:    or a0, a2, a0
@@ -341,10 +340,10 @@ define i128 @fptoui_sat_f16_to_i128(half %a) nounwind {
 ; RV64IZHINX-NEXT:    neg s1, a0
 ; RV64IZHINX-NEXT:    mv a0, s0
 ; RV64IZHINX-NEXT:    call __fixunssfti
-; RV64IZHINX-NEXT:    lui a2, 522240
-; RV64IZHINX-NEXT:    addi a2, a2, -1
-; RV64IZHINX-NEXT:    flt.s a2, a2, s0
+; RV64IZHINX-NEXT:    fclass.s a2, s0
+; RV64IZHINX-NEXT:    slli a2, a2, 56
 ; RV64IZHINX-NEXT:    and a0, s1, a0
+; RV64IZHINX-NEXT:    srli a2, a2, 63
 ; RV64IZHINX-NEXT:    neg a2, a2
 ; RV64IZHINX-NEXT:    and a1, s1, a1
 ; RV64IZHINX-NEXT:    or a0, a2, a0

>From 386d8fe45970cbe768dce84811c2e1b63b449f56 Mon Sep 17 00:00:00 2001
From: Julian Zhu <jz531210 at gmail.com>
Date: Mon, 28 Sep 2026 01:45:03 +0800
Subject: [PATCH 2/2] [RISCV] Fold minimum-normal f64 boundary comparisons to
 FCLASS

Without Zfa, FCLASS.D is cheaper than materializing the smallest
normal double. Only fold in IEEE input denormal mode.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  17 +
 .../CodeGen/RISCV/fcmp-boundary-fclass.ll     | 302 +++++++++++++++++-
 2 files changed, 313 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 05d213c50b7b4..5789248f66b20 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -20877,6 +20877,23 @@ static SDValue combineFPBoundarySetCC(SDNode *N, SelectionDAG &DAG,
       Mask = fcPosInf;
     else if (CC == ISD::SETOLE || CC == ISD::SETULE)
       Mask = fcAllFlags & ~(fcPosInf | NaNs);
+  } else if (!Subtarget.hasStdExtZfa() && FPVT == MVT::f64 &&
+             DAG.getDenormalMode(FPVT).Input == DenormalMode::IEEE) {
+    // Without FLI.D, materializing this bound costs more than FCLASS.D.
+    FPClassTest Below = fcNegInf | fcNegNormal | fcNegSubnormal | fcNegZero |
+                        fcPosZero | fcPosSubnormal;
+    if (C.bitwiseIsEqual(APFloat::getSmallestNormalized(C.getSemantics()))) {
+      if (CC == ISD::SETOLT || CC == ISD::SETULT)
+        Mask = Below;
+      else if (CC == ISD::SETOGE || CC == ISD::SETUGE)
+        Mask = fcPosNormal | fcPosInf;
+    } else if (C.bitwiseIsEqual(
+                   APFloat::getSmallestNormalized(C.getSemantics(), true))) {
+      if (CC == ISD::SETOGT || CC == ISD::SETUGT)
+        Mask = fcAllFlags & ~(fcNegInf | fcNegNormal | NaNs);
+      else if (CC == ISD::SETOLE || CC == ISD::SETULE)
+        Mask = fcNegInf | fcNegNormal;
+    }
   }
   if (Mask == fcNone)
     return SDValue();
diff --git a/llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll b/llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll
index fb0f15229b65f..ee3673a473103 100644
--- a/llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll
+++ b/llvm/test/CodeGen/RISCV/fcmp-boundary-fclass.ll
@@ -352,11 +352,299 @@ define i1 @at_most_positive_max_unordered(double %x) {
   ret i1 %r
 }
 
+define i1 @below_min_normal(double %x) {
+; RV64D-LABEL: below_min_normal:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 63
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: below_min_normal:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    flt.d a0, fa0, fa5
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: below_min_normal:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 63
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: below_min_normal:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 63
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp olt double %x, 0x0010000000000000
+  ret i1 %r
+}
+
+define i1 @at_least_min_normal(double %x) {
+; RV64D-LABEL: at_least_min_normal:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 192
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_least_min_normal:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    fle.d a0, fa5, fa0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_least_min_normal:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 192
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_least_min_normal:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 192
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp oge double %x, 0x0010000000000000
+  ret i1 %r
+}
+
+define i1 @above_negative_min_normal(double %x) {
+; RV64D-LABEL: above_negative_min_normal:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 252
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: above_negative_min_normal:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    fneg.d fa5, fa5
+; RV64ZFA-NEXT:    flt.d a0, fa5, fa0
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: above_negative_min_normal:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 252
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: above_negative_min_normal:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 252
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ogt double %x, 0x8010000000000000
+  ret i1 %r
+}
+
+define i1 @at_most_negative_min_normal(double %x) {
+; RV64D-LABEL: at_most_negative_min_normal:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 3
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_most_negative_min_normal:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    fneg.d fa5, fa5
+; RV64ZFA-NEXT:    fle.d a0, fa0, fa5
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_most_negative_min_normal:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 3
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_most_negative_min_normal:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 3
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ole double %x, 0x8010000000000000
+  ret i1 %r
+}
+
+define i1 @below_min_normal_unordered(double %x) {
+; RV64D-LABEL: below_min_normal_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 831
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: below_min_normal_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    fle.d a0, fa5, fa0
+; RV64ZFA-NEXT:    xori a0, a0, 1
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: below_min_normal_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 831
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: below_min_normal_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 831
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ult double %x, 0x0010000000000000
+  ret i1 %r
+}
+
+define i1 @at_least_min_normal_unordered(double %x) {
+; RV64D-LABEL: at_least_min_normal_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 960
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_least_min_normal_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    flt.d a0, fa0, fa5
+; RV64ZFA-NEXT:    xori a0, a0, 1
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_least_min_normal_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 960
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_least_min_normal_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 960
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp uge double %x, 0x0010000000000000
+  ret i1 %r
+}
+
+define i1 @above_negative_min_normal_unordered(double %x) {
+; RV64D-LABEL: above_negative_min_normal_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 1020
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: above_negative_min_normal_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    fneg.d fa5, fa5
+; RV64ZFA-NEXT:    fle.d a0, fa0, fa5
+; RV64ZFA-NEXT:    xori a0, a0, 1
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: above_negative_min_normal_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 1020
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: above_negative_min_normal_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 1020
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ugt double %x, 0x8010000000000000
+  ret i1 %r
+}
+
+define i1 @at_most_negative_min_normal_unordered(double %x) {
+; RV64D-LABEL: at_most_negative_min_normal_unordered:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    fclass.d a0, fa0
+; RV64D-NEXT:    andi a0, a0, 771
+; RV64D-NEXT:    snez a0, a0
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: at_most_negative_min_normal_unordered:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    fneg.d fa5, fa5
+; RV64ZFA-NEXT:    flt.d a0, fa5, fa0
+; RV64ZFA-NEXT:    xori a0, a0, 1
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: at_most_negative_min_normal_unordered:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    fclass.d a0, fa0
+; RV32D-NEXT:    andi a0, a0, 771
+; RV32D-NEXT:    snez a0, a0
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: at_most_negative_min_normal_unordered:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    fclass.d a0, a0
+; RV64ZDINX-NEXT:    andi a0, a0, 771
+; RV64ZDINX-NEXT:    snez a0, a0
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp ule double %x, 0x8010000000000000
+  ret i1 %r
+}
+
+define i1 @below_min_normal_daz(double %x) #0 {
+; RV64D-LABEL: below_min_normal_daz:
+; RV64D:       # %bb.0:
+; RV64D-NEXT:    li a0, 1
+; RV64D-NEXT:    slli a0, a0, 52
+; RV64D-NEXT:    fmv.d.x fa5, a0
+; RV64D-NEXT:    flt.d a0, fa0, fa5
+; RV64D-NEXT:    ret
+;
+; RV64ZFA-LABEL: below_min_normal_daz:
+; RV64ZFA:       # %bb.0:
+; RV64ZFA-NEXT:    fli.d fa5, min
+; RV64ZFA-NEXT:    flt.d a0, fa0, fa5
+; RV64ZFA-NEXT:    ret
+;
+; RV32D-LABEL: below_min_normal_daz:
+; RV32D:       # %bb.0:
+; RV32D-NEXT:    lui a0, %hi(.LCPI19_0)
+; RV32D-NEXT:    fld fa5, %lo(.LCPI19_0)(a0)
+; RV32D-NEXT:    flt.d a0, fa0, fa5
+; RV32D-NEXT:    ret
+;
+; RV64ZDINX-LABEL: below_min_normal_daz:
+; RV64ZDINX:       # %bb.0:
+; RV64ZDINX-NEXT:    li a1, 1
+; RV64ZDINX-NEXT:    slli a1, a1, 52
+; RV64ZDINX-NEXT:    flt.d a0, a0, a1
+; RV64ZDINX-NEXT:    ret
+  %r = fcmp olt double %x, 0x0010000000000000
+  ret i1 %r
+}
+
 define i1 @strict_below_negative_max(double %x) strictfp {
 ; RV64D-LABEL: strict_below_negative_max:
 ; RV64D:       # %bb.0:
-; RV64D-NEXT:    lui a0, %hi(.LCPI11_0)
-; RV64D-NEXT:    fld fa5, %lo(.LCPI11_0)(a0)
+; RV64D-NEXT:    lui a0, %hi(.LCPI20_0)
+; RV64D-NEXT:    fld fa5, %lo(.LCPI20_0)(a0)
 ; RV64D-NEXT:    frflags a1
 ; RV64D-NEXT:    flt.d a0, fa0, fa5
 ; RV64D-NEXT:    fsflags a1
@@ -365,15 +653,15 @@ define i1 @strict_below_negative_max(double %x) strictfp {
 ;
 ; RV64ZFA-LABEL: strict_below_negative_max:
 ; RV64ZFA:       # %bb.0:
-; RV64ZFA-NEXT:    lui a0, %hi(.LCPI11_0)
-; RV64ZFA-NEXT:    fld fa5, %lo(.LCPI11_0)(a0)
+; RV64ZFA-NEXT:    lui a0, %hi(.LCPI20_0)
+; RV64ZFA-NEXT:    fld fa5, %lo(.LCPI20_0)(a0)
 ; RV64ZFA-NEXT:    fltq.d a0, fa0, fa5
 ; RV64ZFA-NEXT:    ret
 ;
 ; RV32D-LABEL: strict_below_negative_max:
 ; RV32D:       # %bb.0:
-; RV32D-NEXT:    lui a0, %hi(.LCPI11_0)
-; RV32D-NEXT:    fld fa5, %lo(.LCPI11_0)(a0)
+; RV32D-NEXT:    lui a0, %hi(.LCPI20_0)
+; RV32D-NEXT:    fld fa5, %lo(.LCPI20_0)(a0)
 ; RV32D-NEXT:    frflags a1
 ; RV32D-NEXT:    flt.d a0, fa0, fa5
 ; RV32D-NEXT:    fsflags a1
@@ -398,3 +686,5 @@ define i1 @strict_below_negative_max(double %x) strictfp {
 }
 
 declare i1 @llvm.experimental.constrained.fcmp.f64(double, double, metadata, metadata)
+
+attributes #0 = { denormal_fpenv(positivezero) }



More information about the llvm-commits mailing list