[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