[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