[llvm] [RISCV] Optimize bitcast to use dedicated fneg instruction (PR #216659)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 17 01:21:18 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-selectiondag

Author: shalom (milkHongYe)

<details>
<summary>Changes</summary>

Currently, RISC-V generates inefficient code for `bitcast(fneg(f64/f32))`:
```
fmv.x.d  a0, fa0
li       a1, -1
slli     a1, a1, 63
xor      a0, a0, a1
```
This can actually be optimized to:
```
fneg.d   fa5, fa0
fmv.x.d  a0, fa5
```
  The main reason for this issue is that the RISC-V backend lacks a proper implementation of `isFNegFree()`, causing the `bitcast(fneg(x)) `pattern to be expanded into bitwise operations during the DAG optimization stage, instead of preserving the efficient floating-point negation instruction.
  The solution is to implement the `isFNegFree()` function for RISC-V in this patch to preserve the bitcast optimization. RISC-V needs `isFNegFree=true` to prevent the `fneg` pattern from being unnecessarily expanded. At the same time, to avoid breaking FMA negation optimization, the patch introduces a new `shouldAlwaysPerformFMANegOpt()` interface to separate these concerns. For the standard floating-point configuration, RISC-V returns `true` for both functions.
  Through LLVM's standard optimization pipeline, the middle-end optimization passes can recognize the bitwise operation pattern and reconstruct it into an efficient `fneg` form, after which the backend prevents these patterns from being expanded again.
  As a result, the number of instructions is reduced from 4 to 2, while FMA optimization continues to work correctly.

---
Full diff: https://github.com/llvm/llvm-project/pull/216659.diff


7 Files Affected:

- (modified) llvm/include/llvm/CodeGen/TargetLowering.h (+8) 
- (modified) llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp (+2-1) 
- (modified) llvm/lib/Target/RISCV/RISCVISelLowering.cpp (+18) 
- (modified) llvm/lib/Target/RISCV/RISCVISelLowering.h (+2) 
- (modified) llvm/test/CodeGen/RISCV/double-bitmanip-dagcombines.ll (+6-7) 
- (modified) llvm/test/CodeGen/RISCV/float-bit-preserving-dagcombines.ll (+6-14) 
- (modified) llvm/test/CodeGen/RISCV/float-bitmanip-dagcombines.ll (+6-6) 


``````````diff
diff --git a/llvm/include/llvm/CodeGen/TargetLowering.h b/llvm/include/llvm/CodeGen/TargetLowering.h
index c8aa38afc5dd0..c30233536b5ba 100644
--- a/llvm/include/llvm/CodeGen/TargetLowering.h
+++ b/llvm/include/llvm/CodeGen/TargetLowering.h
@@ -3438,6 +3438,14 @@ class LLVM_ABI TargetLoweringBase {
     return false;
   }
 
+  /// Return true if the target should always perform FMA negation optimizations
+  /// even when isFNegFree returns true. This is used to handle target-specific
+  /// cases where FMA negation should be performed regardless of the general 
+  /// fneg cost model. 
+  virtual bool shouldAlwaysPerformFMANegOpt(EVT VT) const {
+    return false;
+  }
+
   /// Return true if an fabs operation is free to the point where it is never
   /// worthwhile to replace it with a bitwise operation.
   virtual bool isFAbsFree(EVT VT) const {
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 863d99e61a4aa..fc839ccd78c7d 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -19933,7 +19933,8 @@ SDValue DAGCombiner::visitFMA(SDNode *N) {
 
   // fold ((fma (fneg X), Y, (fneg Z)) -> fneg (fma X, Y, Z))
   // fold ((fma X, (fneg Y), (fneg Z)) -> fneg (fma X, Y, Z))
-  if (!TLI.isFNegFree(VT))
+bool shouldPerformFMANegOpt = !TLI.isFNegFree(VT) || TLI.shouldAlwaysPerformFMANegOpt(VT);
+  if (shouldPerformFMANegOpt)
     if (SDValue Neg = TLI.getCheaperNegatedExpression(
             SDValue(N, 0), DAG, LegalOperations, ForCodeSize))
       return DAG.getNode(ISD::FNEG, DL, VT, Neg);
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 6202a12c7025b..fa5d577b70550 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -2649,6 +2649,24 @@ bool RISCVTargetLowering::isSExtCheaperThanZExt(EVT SrcVT, EVT DstVT) const {
 bool RISCVTargetLowering::signExtendConstant(const ConstantInt *CI) const {
   return Subtarget.is64Bit() && CI->getType()->isIntegerTy(32);
 }
+bool RISCVTargetLowering::isFNegFree(EVT VT) const {
+  if (!VT.isFloatingPoint())
+    return false;
+  return (VT == MVT::f64 && Subtarget.is64Bit() &&
+          Subtarget.hasStdExtD() && !Subtarget.hasStdExtZdinx()) ||
+         (VT == MVT::f32 && Subtarget.hasStdExtF() && 
+          !Subtarget.hasStdExtZfinx());
+}
+
+bool RISCVTargetLowering::shouldAlwaysPerformFMANegOpt(EVT VT) const {
+  if (!VT.isFloatingPoint())
+    return false;
+  return (VT == MVT::f64 && Subtarget.is64Bit() &&
+          Subtarget.hasStdExtD() && !Subtarget.hasStdExtZdinx()) ||
+         (VT == MVT::f32 && Subtarget.hasStdExtF() && 
+          !Subtarget.hasStdExtZfinx());
+}
+
 
 bool RISCVTargetLowering::isCheapToSpeculateCttz(Type *Ty) const {
   return Subtarget.hasCTZLike();
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.h b/llvm/lib/Target/RISCV/RISCVISelLowering.h
index 929bcc91c83ac..eae95bf85884f 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.h
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.h
@@ -47,6 +47,8 @@ class RISCVTargetLowering : public TargetLowering {
   bool isTruncateFree(EVT SrcVT, EVT DstVT) const override;
   bool isTruncateFree(SDValue Val, EVT VT2) const override;
   bool isZExtFree(SDValue Val, EVT VT2) const override;
+  bool isFNegFree(EVT VT) const override;
+  bool shouldAlwaysPerformFMANegOpt(EVT VT) const override;
   bool isSExtCheaperThanZExt(EVT SrcVT, EVT DstVT) const override;
   bool signExtendConstant(const ConstantInt *CI) const override;
   bool isCheapToSpeculateCttz(Type *Ty) const override;
diff --git a/llvm/test/CodeGen/RISCV/double-bitmanip-dagcombines.ll b/llvm/test/CodeGen/RISCV/double-bitmanip-dagcombines.ll
index 93b8161e27d02..4ce06a13af253 100644
--- a/llvm/test/CodeGen/RISCV/double-bitmanip-dagcombines.ll
+++ b/llvm/test/CodeGen/RISCV/double-bitmanip-dagcombines.ll
@@ -48,9 +48,9 @@ define double @fneg(double %a) nounwind {
 ;
 ; RV64IFD-LABEL: fneg:
 ; RV64IFD:       # %bb.0:
-; RV64IFD-NEXT:    li a1, -1
-; RV64IFD-NEXT:    slli a1, a1, 63
-; RV64IFD-NEXT:    xor a0, a0, a1
+; RV64IFD-NEXT:    fmv.d.x fa5, a0
+; RV64IFD-NEXT:    fneg.d fa5, fa5
+; RV64IFD-NEXT:    fmv.x.d a0, fa5
 ; RV64IFD-NEXT:    ret
 ;
 ; RV64IZFINXZDINX-LABEL: fneg:
@@ -147,10 +147,9 @@ define double @fcopysign_fneg(double %a, double %b) nounwind {
 ;
 ; RV64IFD-LABEL: fcopysign_fneg:
 ; RV64IFD:       # %bb.0:
-; RV64IFD-NEXT:    not a1, a1
-; RV64IFD-NEXT:    fmv.d.x fa5, a0
-; RV64IFD-NEXT:    fmv.d.x fa4, a1
-; RV64IFD-NEXT:    fsgnj.d fa5, fa5, fa4
+; RV64IFD-NEXT:    fmv.d.x fa5, a1
+; RV64IFD-NEXT:    fmv.d.x fa4, a0
+; RV64IFD-NEXT:    fsgnjn.d fa5, fa4, fa5
 ; RV64IFD-NEXT:    fmv.x.d a0, fa5
 ; RV64IFD-NEXT:    ret
 ;
diff --git a/llvm/test/CodeGen/RISCV/float-bit-preserving-dagcombines.ll b/llvm/test/CodeGen/RISCV/float-bit-preserving-dagcombines.ll
index 4649ca8642ec6..8307ba15b6d71 100644
--- a/llvm/test/CodeGen/RISCV/float-bit-preserving-dagcombines.ll
+++ b/llvm/test/CodeGen/RISCV/float-bit-preserving-dagcombines.ll
@@ -198,9 +198,8 @@ define float @bitcast_xor(float %a1, float %a2) nounwind {
 ; RV32F-NEXT:    fmv.w.x fa4, a0
 ; RV32F-NEXT:    fmul.s fa5, fa4, fa5
 ; RV32F-NEXT:    fmul.s fa5, fa4, fa5
+; RV32F-NEXT:    fneg.s fa5, fa5
 ; RV32F-NEXT:    fmv.x.w a0, fa5
-; RV32F-NEXT:    lui a1, 524288
-; RV32F-NEXT:    xor a0, a0, a1
 ; RV32F-NEXT:    ret
 ;
 ; RV32ZFINX-LABEL: bitcast_xor:
@@ -216,9 +215,8 @@ define float @bitcast_xor(float %a1, float %a2) nounwind {
 ; RV32FD-NEXT:    fmv.w.x fa4, a0
 ; RV32FD-NEXT:    fmul.s fa5, fa4, fa5
 ; RV32FD-NEXT:    fmul.s fa5, fa4, fa5
+; RV32FD-NEXT:    fneg.s fa5, fa5
 ; RV32FD-NEXT:    fmv.x.w a0, fa5
-; RV32FD-NEXT:    lui a1, 524288
-; RV32FD-NEXT:    xor a0, a0, a1
 ; RV32FD-NEXT:    ret
 ;
 ; RV64F-LABEL: bitcast_xor:
@@ -363,10 +361,8 @@ define double @bitcast_double_xor(double %a1, double %a2) nounwind {
 ; RV64FD-NEXT:    fmv.d.x fa4, a0
 ; RV64FD-NEXT:    fmul.d fa5, fa4, fa5
 ; RV64FD-NEXT:    fmul.d fa5, fa4, fa5
+; RV64FD-NEXT:    fneg.d fa5, fa5
 ; RV64FD-NEXT:    fmv.x.d a0, fa5
-; RV64FD-NEXT:    li a1, -1
-; RV64FD-NEXT:    slli a1, a1, 63
-; RV64FD-NEXT:    xor a0, a0, a1
 ; RV64FD-NEXT:    ret
   %a3 = fmul double %a1, %a2
   %bc1 = bitcast double %a3 to i64
@@ -384,9 +380,8 @@ define float @bitcast_or(float %a1, float %a2) nounwind {
 ; RV32F-NEXT:    fmul.s fa5, fa4, fa5
 ; RV32F-NEXT:    fabs.s fa5, fa5
 ; RV32F-NEXT:    fmul.s fa5, fa4, fa5
+; RV32F-NEXT:    fneg.s fa5, fa5
 ; RV32F-NEXT:    fmv.x.w a0, fa5
-; RV32F-NEXT:    lui a1, 524288
-; RV32F-NEXT:    xor a0, a0, a1
 ; RV32F-NEXT:    ret
 ;
 ; RV32ZFINX-LABEL: bitcast_or:
@@ -404,9 +399,8 @@ define float @bitcast_or(float %a1, float %a2) nounwind {
 ; RV32FD-NEXT:    fmul.s fa5, fa4, fa5
 ; RV32FD-NEXT:    fabs.s fa5, fa5
 ; RV32FD-NEXT:    fmul.s fa5, fa4, fa5
+; RV32FD-NEXT:    fneg.s fa5, fa5
 ; RV32FD-NEXT:    fmv.x.w a0, fa5
-; RV32FD-NEXT:    lui a1, 524288
-; RV32FD-NEXT:    xor a0, a0, a1
 ; RV32FD-NEXT:    ret
 ;
 ; RV64F-LABEL: bitcast_or:
@@ -560,10 +554,8 @@ define double @bitcast_double_or(double %a1, double %a2) nounwind {
 ; RV64FD-NEXT:    fmul.d fa5, fa4, fa5
 ; RV64FD-NEXT:    fabs.d fa5, fa5
 ; RV64FD-NEXT:    fmul.d fa5, fa4, fa5
+; RV64FD-NEXT:    fneg.d fa5, fa5
 ; RV64FD-NEXT:    fmv.x.d a0, fa5
-; RV64FD-NEXT:    li a1, -1
-; RV64FD-NEXT:    slli a1, a1, 63
-; RV64FD-NEXT:    xor a0, a0, a1
 ; RV64FD-NEXT:    ret
   %a3 = fmul double %a1, %a2
   %bc1 = bitcast double %a3 to i64
diff --git a/llvm/test/CodeGen/RISCV/float-bitmanip-dagcombines.ll b/llvm/test/CodeGen/RISCV/float-bitmanip-dagcombines.ll
index 9dad6857890b0..16d0e48f17e54 100644
--- a/llvm/test/CodeGen/RISCV/float-bitmanip-dagcombines.ll
+++ b/llvm/test/CodeGen/RISCV/float-bitmanip-dagcombines.ll
@@ -27,8 +27,9 @@ define float @fneg(float %a) nounwind {
 ;
 ; RV32IF-LABEL: fneg:
 ; RV32IF:       # %bb.0:
-; RV32IF-NEXT:    lui a1, 524288
-; RV32IF-NEXT:    xor a0, a0, a1
+; RV32IF-NEXT:    fmv.w.x fa5, a0
+; RV32IF-NEXT:    fneg.s fa5, fa5
+; RV32IF-NEXT:    fmv.x.w a0, fa5
 ; RV32IF-NEXT:    ret
 ;
 ; RV32IZFINX-LABEL: fneg:
@@ -111,10 +112,9 @@ define float @fcopysign_fneg(float %a, float %b) nounwind {
 ;
 ; RV32IF-LABEL: fcopysign_fneg:
 ; RV32IF:       # %bb.0:
-; RV32IF-NEXT:    not a1, a1
-; RV32IF-NEXT:    fmv.w.x fa5, a0
-; RV32IF-NEXT:    fmv.w.x fa4, a1
-; RV32IF-NEXT:    fsgnj.s fa5, fa5, fa4
+; RV32IF-NEXT:    fmv.w.x fa5, a1
+; RV32IF-NEXT:    fmv.w.x fa4, a0
+; RV32IF-NEXT:    fsgnjn.s fa5, fa4, fa5
 ; RV32IF-NEXT:    fmv.x.w a0, fa5
 ; RV32IF-NEXT:    ret
 ;

``````````

</details>


https://github.com/llvm/llvm-project/pull/216659


More information about the llvm-commits mailing list