[llvm] [RISCV][ISelLowering] Use Zicond for FP selects on Zfinx/Zdinx (PR #169299)

via llvm-commits llvm-commits at lists.llvm.org
Mon Nov 24 11:07:16 PST 2025


https://github.com/fennecJ updated https://github.com/llvm/llvm-project/pull/169299

>From 329a5d04d9ac50c02e2631adbec119a50425972e Mon Sep 17 00:00:00 2001
From: fennecJ <hwahwa649 at gmail.com>
Date: Mon, 24 Nov 2025 16:25:36 +0800
Subject: [PATCH 1/5] [RISCV][ISelLowering] Use Zicond for FP selects on
 Zfinx/Zdinx

When Zfinx or Zdinx is enabled, FP values live in the integer register
file, so there is no GPR<->FPR move cost. In this configuration we can
profitably lower floating-point `select` nodes through an integer
`ISD::SELECT` so that the existing Zicond patterns generate branchless
czero/cmov sequences instead of control-flow branches.

This covers patterns such as:

  // Case 1: integer condition
  float sel(int cond, float a, float b) {
    return cond ? a : b;
  }

  // Case 2: floating-point condition
  float cmp_sel(float a, float b, float c, float d) {
    return (a > b) ? c : d;
  }

In Case 1 we bitcast the FP operands to an XLen integer type, form an
integer `select` with the original integer condition `cond`, and then
bitcast the result back to float, allowing it to be lowered to a
Zicond-based cmov/czero sequence.

In Case 2 the floating-point compare is already lowered to an integer
0/1 value in a GPR (e.g. `flt.s` / `fle.s` / `feq.s`), so we can reuse
that integer result as the Zicond condition and apply the same scheme.

The transformation is gated on both Zicond and Zfinx/Zdinx, so classic
F/D implementations (where FP values live in a separate FP register
file) continue to use the existing branch-based lowering and do not pay
for extra GPR<->FPR moves. FP semantics are unchanged: we only
reinterpret the FP operands as integers; no numeric conversions are
introduced.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 30 +++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 19a16197272fe..9183a523226bc 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -9555,6 +9555,36 @@ SDValue RISCVTargetLowering::lowerSELECT(SDValue Op, SelectionDAG &DAG) const {
   if (SDValue V = lowerSelectToBinOp(Op.getNode(), DAG, Subtarget))
     return V;
 
+  // When there is no cost for GPR <-> FGPR, we can use zicond select for
+  // floating value when CondV is int type
+  bool FPinGPR = Subtarget.hasStdExtZfinx() || Subtarget.hasStdExtZdinx();
+  bool UseZicondForFPSel = Subtarget.hasStdExtZicond() && FPinGPR &&
+                           VT.isFloatingPoint() &&
+                           CondV.getValueType().isInteger();
+  if (UseZicondForFPSel) {
+    MVT XLenIntVT = Subtarget.getXLenVT();
+
+    auto CastToInt = [&](SDValue V) -> SDValue {
+      if (VT == MVT::f32 && Subtarget.is64Bit()) {
+        return DAG.getNode(RISCVISD::FMV_X_ANYEXTW_RV64, DL, XLenIntVT, V);
+      }
+      return DAG.getBitcast(XLenIntVT, V);
+    };
+
+    SDValue TrueVInt = CastToInt(TrueV);
+    SDValue FalseVInt = CastToInt(FalseV);
+
+    // Emit integer SELECT (lowers to Zicond)
+    SDValue ResultInt =
+        DAG.getNode(ISD::SELECT, DL, XLenIntVT, CondV, TrueVInt, FalseVInt);
+
+    // Convert back to floating VT
+    if (VT == MVT::f32 && Subtarget.is64Bit()) {
+      return DAG.getNode(RISCVISD::FMV_W_X_RV64, DL, VT, ResultInt);
+    }
+    return DAG.getBitcast(VT, ResultInt);
+  }
+
   // When Zicond or XVentanaCondOps is present, emit CZERO_EQZ and CZERO_NEZ
   // nodes to implement the SELECT. Performing the lowering here allows for
   // greater control over when CZERO_{EQZ/NEZ} are used vs another branchless

>From fe46a46e0c21dbc2e415d5bfd6d05027f61bfb60 Mon Sep 17 00:00:00 2001
From: fennecJ <hwahwa649 at gmail.com>
Date: Mon, 24 Nov 2025 16:38:11 +0800
Subject: [PATCH 2/5] Add test for zicond zf/dinx select lowering

* Floating-point `select` is lowered through Zicond only when both
  Zicond and Zfinx/Zdinx extensions are enabled.
* When both Zicond and Zfinx/Zdinx are enabled, branch-based lowering
  is replaced by a Zicond-backed integer `select` (no control-flow
  branches are emitted for these cases).
---
 .../CodeGen/RISCV/zicond-fp-select-zfinx.ll   | 115 ++++++++++++++++++
 1 file changed, 115 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll

diff --git a/llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll b/llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll
new file mode 100644
index 0000000000000..ddce3752584c4
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll
@@ -0,0 +1,115 @@
+; RUN: llc -mtriple=riscv64 -mattr=+zfinx,+zicond -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64ZFINX_ZICOND
+; RUN: llc -mtriple=riscv64 -mattr=+zfinx           -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64ZFINX_NOZICOND
+; RUN: llc -mtriple=riscv64 -mattr=+zdinx,+zicond -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64ZDINX_ZICOND
+; RUN: llc -mtriple=riscv64 -mattr=+zdinx           -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64ZDINX_NOZICOND
+; RUN: llc -mtriple=riscv64 -mattr=+f -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64F
+; RUN: llc -mtriple=riscv64 -mattr=+d -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV64D
+
+; RUN: llc -mtriple=riscv32 -mattr=+zfinx,+zicond -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV32ZFINX_ZICOND
+; RUN: llc -mtriple=riscv32 -mattr=+zfinx           -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV32ZFINX_NOZICOND
+; RUN: llc -mtriple=riscv32 -mattr=+f -verify-machineinstrs < %s | FileCheck %s --check-prefix=RV32F
+
+
+; This test checks that floating-point SELECT is lowered through integer
+; SELECT (and thus to Zicond czero.* sequence) when FP values live in GPRs
+; (Zfinx/Zdinx) and Zicond is enabled. When Zicond is disabled, we expect
+; a branch-based lowering instead.
+
+; -----------------------------------------------------------------------------
+; float select with i1 condition (Zfinx)
+; -----------------------------------------------------------------------------
+
+define float @select_f32_i1(i1 %cond, float %t, float %f) nounwind {
+; RV64ZFINX_ZICOND-LABEL: select_f32_i1:
+; RV64ZFINX_ZICOND: czero
+; RV64ZFINX_ZICOND: czero
+; RV64ZFINX_ZICOND: or
+; RV64ZFINX_ZICOND-NOT: b{{(eq|ne)z?}}
+; RV64ZFINX_ZICOND: ret
+
+; RV64ZFINX_NOZICOND-LABEL: select_f32_i1:
+; RV64ZFINX_NOZICOND: b{{(eq|ne)z?}}
+; RV64ZFINX_NOZICOND-NOT: czero.eqz
+; RV64ZFINX_NOZICOND-NOT: czero.nez
+
+; RV64F-LABEL: select_f32_i1:
+; RV64F: b{{(eq|ne)z?}}
+; RV64F-NOT: czero.eqz
+; RV64F-NOT: czero.nez
+
+; RV32ZFINX_ZICOND-LABEL: select_f32_i1:
+; RV32ZFINX_ZICOND: czero
+; RV32ZFINX_ZICOND: czero
+; RV32ZFINX_ZICOND: or
+; RV32ZFINX_ZICOND-NOT: b{{(eq|ne)z?}}
+; RV32ZFINX_ZICOND: ret
+
+; RV32ZFINX_NOZICOND-LABEL: select_f32_i1:
+; RV32ZFINX_NOZICOND: b{{(eq|ne)z?}}
+; RV32ZFINX_NOZICOND-NOT: czero.eqz
+; RV32ZFINX_NOZICOND-NOT: czero.nez
+
+; RV32F-LABEL: select_f32_i1:
+; RV32F: b{{(eq|ne)z?}}
+; RV32F-NOT: czero.eqz
+; RV32F-NOT: czero.nez
+
+entry:
+  %sel = select i1 %cond, float %t, float %f
+  ret float %sel
+}
+
+; -----------------------------------------------------------------------------
+; double select with i1 condition (Zdinx)
+; -----------------------------------------------------------------------------
+
+define double @select_f64_i1(i1 %cond, double %t, double %f) nounwind {
+; RV64ZDINX_ZICOND-LABEL: select_f64_i1:
+; RV64ZDINX_ZICOND: czero
+; RV64ZDINX_ZICOND: czero
+; RV64ZDINX_ZICOND: or
+; RV64ZDINX_ZICOND-NOT: b{{(eq|ne)z?}}
+; RV64ZDINX_ZICOND: ret
+
+; RV64ZDINX_NOZICOND-LABEL: select_f64_i1:
+; RV64ZDINX_NOZICOND: b{{(eq|ne)z?}}
+; RV64ZDINX_NOZICOND-NOT: czero.eqz
+; RV64ZDINX_NOZICOND-NOT: czero.nez
+
+; RV64D-LABEL: select_f64_i1:
+; RV64D: b{{(eq|ne)z?}}
+; RV64D-NOT: czero.eqz
+; RV64D-NOT: czero.nez
+
+entry:
+  %sel = select i1 %cond, double %t, double %f
+  ret double %sel
+}
+
+; -----------------------------------------------------------------------------
+; double select with floating-point compare condition (a > b ? c : d), Zdinx
+; -----------------------------------------------------------------------------
+
+define double @select_f64_fcmp(double %a, double %b, double %c, double %d) nounwind {
+; RV64ZDINX_ZICOND-LABEL: select_f64_fcmp:
+; RV64ZDINX_ZICOND: czero
+; RV64ZDINX_ZICOND: czero
+; RV64ZDINX_ZICOND: or
+; RV64ZDINX_ZICOND-NOT: b{{(eq|ne)z?}}
+; RV64ZDINX_ZICOND: ret
+
+; RV64ZDINX_NOZICOND-LABEL: select_f64_fcmp:
+; RV64ZDINX_NOZICOND: b{{(eq|ne)z?}}
+; RV64ZDINX_NOZICOND-NOT: czero.eqz
+; RV64ZDINX_NOZICOND-NOT: czero.nez
+
+; RV64D-LABEL: select_f64_fcmp:
+; RV64D: b{{(eq|ne)z?}}
+; RV64D-NOT: czero.eqz
+; RV64D-NOT: czero.nez
+
+entry:
+  %cmp = fcmp ogt double %a, %b
+  %sel = select i1 %cmp, double %c, double %d
+  ret double %sel
+}
\ No newline at end of file

>From f49320f3744c1f5e985c76156d3c7f387b746e0f Mon Sep 17 00:00:00 2001
From: fennecJ <hwahwa649 at gmail.com>
Date: Tue, 25 Nov 2025 01:23:21 +0800
Subject: [PATCH 3/5] Refine FPinGPR checking logic

Zdinx implies Zfinx so it's sufficient to check only Zfinx here.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 9183a523226bc..0ce5c493c88b6 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -9557,7 +9557,7 @@ SDValue RISCVTargetLowering::lowerSELECT(SDValue Op, SelectionDAG &DAG) const {
 
   // When there is no cost for GPR <-> FGPR, we can use zicond select for
   // floating value when CondV is int type
-  bool FPinGPR = Subtarget.hasStdExtZfinx() || Subtarget.hasStdExtZdinx();
+  bool FPinGPR = Subtarget.hasStdExtZfinx();
   bool UseZicondForFPSel = Subtarget.hasStdExtZicond() && FPinGPR &&
                            VT.isFloatingPoint() &&
                            CondV.getValueType().isInteger();

>From e235ce434649971e3b9526638ccc9eda0beeba9f Mon Sep 17 00:00:00 2001
From: fennecJ <hwahwa649 at gmail.com>
Date: Tue, 25 Nov 2025 02:53:01 +0800
Subject: [PATCH 4/5] Fix f16 Zicond SELECT lowering with Zhinx

---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 0ce5c493c88b6..a66ba8ff61ce2 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -9565,6 +9565,9 @@ SDValue RISCVTargetLowering::lowerSELECT(SDValue Op, SelectionDAG &DAG) const {
     MVT XLenIntVT = Subtarget.getXLenVT();
 
     auto CastToInt = [&](SDValue V) -> SDValue {
+      if (VT == MVT::f16) {
+        return DAG.getNode(RISCVISD::FMV_X_ANYEXTH, DL, XLenIntVT, V);
+      }
       if (VT == MVT::f32 && Subtarget.is64Bit()) {
         return DAG.getNode(RISCVISD::FMV_X_ANYEXTW_RV64, DL, XLenIntVT, V);
       }
@@ -9582,6 +9585,9 @@ SDValue RISCVTargetLowering::lowerSELECT(SDValue Op, SelectionDAG &DAG) const {
     if (VT == MVT::f32 && Subtarget.is64Bit()) {
       return DAG.getNode(RISCVISD::FMV_W_X_RV64, DL, VT, ResultInt);
     }
+    if (VT == MVT::f16) {
+      return DAG.getNode(RISCVISD::FMV_H_X, DL, VT, ResultInt);
+    }
     return DAG.getBitcast(VT, ResultInt);
   }
 

>From 905e17ba1cd32f3aae74ac568c61a7777b0e6d7b Mon Sep 17 00:00:00 2001
From: fennecJ <hwahwa649 at gmail.com>
Date: Tue, 25 Nov 2025 03:06:56 +0800
Subject: [PATCH 5/5] Add unit test for half zicond lowering

---
 .../CodeGen/RISCV/zicond-fp-select-zfinx.ll   | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll b/llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll
index ddce3752584c4..a8e10a028404f 100644
--- a/llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll
+++ b/llvm/test/CodeGen/RISCV/zicond-fp-select-zfinx.ll
@@ -112,4 +112,48 @@ entry:
   %cmp = fcmp ogt double %a, %b
   %sel = select i1 %cmp, double %c, double %d
   ret double %sel
+}
+
+; -----------------------------------------------------------------------------
+; half select with  i1 condition (cond ? a : b), Zfinx
+; -----------------------------------------------------------------------------
+
+define dso_local noundef half @select_half_i1(i1 %cond, half %a, half %b) nounwind {
+; RV64ZFINX_ZICOND-LABEL: select_half_i1:
+; RV64ZFINX_ZICOND: czero
+; RV64ZFINX_ZICOND: czero
+; RV64ZFINX_ZICOND: or
+; RV64ZFINX_ZICOND-NOT: b{{(eq|ne)z?}}
+; RV64ZFINX_ZICOND: ret
+
+; RV64ZFINX_NOZICOND-LABEL: select_half_i1:
+; RV64ZFINX_NOZICOND: b{{(eq|ne)z?}}
+; RV64ZFINX_NOZICOND-NOT: czero.eqz
+; RV64ZFINX_NOZICOND-NOT: czero.nez
+
+; RV64F-LABEL: select_half_i1:
+; RV64F: b{{(eq|ne)z?}}
+; RV64F-NOT: czero.eqz
+; RV64F-NOT: czero.nez
+
+; RV32ZFINX_ZICOND-LABEL: select_half_i1:
+; RV32ZFINX_ZICOND: czero
+; RV32ZFINX_ZICOND: czero
+; RV32ZFINX_ZICOND: or
+; RV32ZFINX_ZICOND-NOT: b{{(eq|ne)z?}}
+; RV32ZFINX_ZICOND: ret
+
+; RV32ZFINX_NOZICOND-LABEL: select_half_i1:
+; RV32ZFINX_NOZICOND: b{{(eq|ne)z?}}
+; RV32ZFINX_NOZICOND-NOT: czero.eqz
+; RV32ZFINX_NOZICOND-NOT: czero.nez
+
+; RV32F-LABEL: select_half_i1:
+; RV32F: b{{(eq|ne)z?}}
+; RV32F-NOT: czero.eqz
+; RV32F-NOT: czero.nez
+
+entry:
+  %sel = select i1 %cond, half %a, half %b
+  ret half %sel
 }
\ No newline at end of file



More information about the llvm-commits mailing list