[llvm] [RISCV] Don't fold vector (fp_to_sint (ceil/floor/trunc/etc X)) if the inner op has multiple uses. (PR #216137)
Craig Topper via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 13 11:07:00 PDT 2026
https://github.com/topperc created https://github.com/llvm/llvm-project/pull/216137
If the inner operation needs to write FRM and the folded operation
does too, combining them may require another pair of FRM writes.
This may be expensive on CPUs without FRM renaming support.
Assisted-by: Claude
>From 584bdf3f690d11adb5fb6bb8ec344e7aaf8ca653 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Thu, 13 Aug 2026 10:59:45 -0700
Subject: [PATCH 1/2] Pre-commit test
---
.../CodeGen/RISCV/rvv/float-round-conv.ll | 178 ++++++++++++++++++
1 file changed, 178 insertions(+)
diff --git a/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll b/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll
index dfb9445e1a68a..54bf1d6f016af 100644
--- a/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll
+++ b/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll
@@ -1027,3 +1027,181 @@ define <vscale x 4 x i64> @rint_nxv4f32_to_ui64(<vscale x 4 x float> %x) {
%b = fptoui <vscale x 4 x float> %a to <vscale x 4 x i64>
ret <vscale x 4 x i64> %b
}
+
+; ================================================================================
+; multiple uses of the rounding operation
+; ================================================================================
+
+; The rounding operation isn't removed by the fold, so folding the fptosi would
+; require a second write to FRM. Don't fold.
+define <vscale x 1 x i32> @ceil_nxv1f32_to_si32_multiple_use(<vscale x 1 x float> %x, ptr %p) {
+; RV32-LABEL: ceil_nxv1f32_to_si32_multiple_use:
+; RV32: # %bb.0:
+; RV32-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV32-NEXT: vmv1r.v v9, v8
+; RV32-NEXT: lui a1, 307200
+; RV32-NEXT: fmv.w.x fa5, a1
+; RV32-NEXT: vfabs.v v8, v8
+; RV32-NEXT: vmflt.vf v0, v8, fa5
+; RV32-NEXT: fsrmi a1, 3
+; RV32-NEXT: vfcvt.x.f.v v8, v9, v0.t
+; RV32-NEXT: fsrm a1
+; RV32-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV32-NEXT: fsrmi a1, 3
+; RV32-NEXT: vfcvt.x.f.v v8, v9
+; RV32-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV32-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV32-NEXT: fsrm a1
+; RV32-NEXT: vse32.v v9, (a0)
+; RV32-NEXT: ret
+;
+; RV64-LABEL: ceil_nxv1f32_to_si32_multiple_use:
+; RV64: # %bb.0:
+; RV64-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV64-NEXT: vmv1r.v v9, v8
+; RV64-NEXT: lui a1, 307200
+; RV64-NEXT: fmv.w.x fa5, a1
+; RV64-NEXT: vfabs.v v8, v8
+; RV64-NEXT: vmflt.vf v0, v8, fa5
+; RV64-NEXT: fsrmi a1, 3
+; RV64-NEXT: vfcvt.x.f.v v8, v9, v0.t
+; RV64-NEXT: fsrm a1
+; RV64-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV64-NEXT: fsrmi a1, 3
+; RV64-NEXT: vfcvt.x.f.v v8, v9
+; RV64-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV64-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV64-NEXT: fsrm a1
+; RV64-NEXT: vse32.v v9, (a0)
+; RV64-NEXT: ret
+ %a = call <vscale x 1 x float> @llvm.ceil.nxv1f32(<vscale x 1 x float> %x)
+ %b = fptosi <vscale x 1 x float> %a to <vscale x 1 x i32>
+ store <vscale x 1 x float> %a, ptr %p
+ ret <vscale x 1 x i32> %b
+}
+
+define <vscale x 1 x i32> @ceil_nxv1f32_to_ui32_multiple_use(<vscale x 1 x float> %x, ptr %p) {
+; RV32-LABEL: ceil_nxv1f32_to_ui32_multiple_use:
+; RV32: # %bb.0:
+; RV32-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV32-NEXT: vmv1r.v v9, v8
+; RV32-NEXT: lui a1, 307200
+; RV32-NEXT: fmv.w.x fa5, a1
+; RV32-NEXT: vfabs.v v8, v8
+; RV32-NEXT: vmflt.vf v0, v8, fa5
+; RV32-NEXT: fsrmi a1, 3
+; RV32-NEXT: vfcvt.x.f.v v8, v9, v0.t
+; RV32-NEXT: fsrm a1
+; RV32-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV32-NEXT: fsrmi a1, 3
+; RV32-NEXT: vfcvt.xu.f.v v8, v9
+; RV32-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV32-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV32-NEXT: fsrm a1
+; RV32-NEXT: vse32.v v9, (a0)
+; RV32-NEXT: ret
+;
+; RV64-LABEL: ceil_nxv1f32_to_ui32_multiple_use:
+; RV64: # %bb.0:
+; RV64-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV64-NEXT: vmv1r.v v9, v8
+; RV64-NEXT: lui a1, 307200
+; RV64-NEXT: fmv.w.x fa5, a1
+; RV64-NEXT: vfabs.v v8, v8
+; RV64-NEXT: vmflt.vf v0, v8, fa5
+; RV64-NEXT: fsrmi a1, 3
+; RV64-NEXT: vfcvt.x.f.v v8, v9, v0.t
+; RV64-NEXT: fsrm a1
+; RV64-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV64-NEXT: fsrmi a1, 3
+; RV64-NEXT: vfcvt.xu.f.v v8, v9
+; RV64-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV64-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV64-NEXT: fsrm a1
+; RV64-NEXT: vse32.v v9, (a0)
+; RV64-NEXT: ret
+ %a = call <vscale x 1 x float> @llvm.ceil.nxv1f32(<vscale x 1 x float> %x)
+ %b = fptoui <vscale x 1 x float> %a to <vscale x 1 x i32>
+ store <vscale x 1 x float> %a, ptr %p
+ ret <vscale x 1 x i32> %b
+}
+
+; RTZ has a dedicated vfcvt.rtz.x.f.v that doesn't write FRM, so we can still
+; fold even with multiple uses.
+define <vscale x 1 x i32> @trunc_nxv1f32_to_si32_multiple_use(<vscale x 1 x float> %x, ptr %p) {
+; RV32-LABEL: trunc_nxv1f32_to_si32_multiple_use:
+; RV32: # %bb.0:
+; RV32-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV32-NEXT: vmv1r.v v9, v8
+; RV32-NEXT: lui a1, 307200
+; RV32-NEXT: fmv.w.x fa5, a1
+; RV32-NEXT: vfabs.v v8, v8
+; RV32-NEXT: vmflt.vf v0, v8, fa5
+; RV32-NEXT: vfcvt.rtz.x.f.v v8, v9, v0.t
+; RV32-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV32-NEXT: vfcvt.rtz.x.f.v v8, v9
+; RV32-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV32-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV32-NEXT: vse32.v v9, (a0)
+; RV32-NEXT: ret
+;
+; RV64-LABEL: trunc_nxv1f32_to_si32_multiple_use:
+; RV64: # %bb.0:
+; RV64-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV64-NEXT: vmv1r.v v9, v8
+; RV64-NEXT: lui a1, 307200
+; RV64-NEXT: fmv.w.x fa5, a1
+; RV64-NEXT: vfabs.v v8, v8
+; RV64-NEXT: vmflt.vf v0, v8, fa5
+; RV64-NEXT: vfcvt.rtz.x.f.v v8, v9, v0.t
+; RV64-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV64-NEXT: vfcvt.rtz.x.f.v v8, v9
+; RV64-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV64-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV64-NEXT: vse32.v v9, (a0)
+; RV64-NEXT: ret
+ %a = call <vscale x 1 x float> @llvm.trunc.nxv1f32(<vscale x 1 x float> %x)
+ %b = fptosi <vscale x 1 x float> %a to <vscale x 1 x i32>
+ store <vscale x 1 x float> %a, ptr %p
+ ret <vscale x 1 x i32> %b
+}
+
+; DYN uses whatever FRM already holds and doesn't write it, so we can still fold
+; even with multiple uses.
+define <vscale x 1 x i32> @rint_nxv1f32_to_si32_multiple_use(<vscale x 1 x float> %x, ptr %p) {
+; RV32-LABEL: rint_nxv1f32_to_si32_multiple_use:
+; RV32: # %bb.0:
+; RV32-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV32-NEXT: vmv1r.v v9, v8
+; RV32-NEXT: lui a1, 307200
+; RV32-NEXT: fmv.w.x fa5, a1
+; RV32-NEXT: vfabs.v v8, v8
+; RV32-NEXT: vmflt.vf v0, v8, fa5
+; RV32-NEXT: vfcvt.x.f.v v8, v9, v0.t
+; RV32-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV32-NEXT: vfcvt.x.f.v v8, v9
+; RV32-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV32-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV32-NEXT: vse32.v v9, (a0)
+; RV32-NEXT: ret
+;
+; RV64-LABEL: rint_nxv1f32_to_si32_multiple_use:
+; RV64: # %bb.0:
+; RV64-NEXT: vsetvli a1, zero, e32, mf2, ta, ma
+; RV64-NEXT: vmv1r.v v9, v8
+; RV64-NEXT: lui a1, 307200
+; RV64-NEXT: fmv.w.x fa5, a1
+; RV64-NEXT: vfabs.v v8, v8
+; RV64-NEXT: vmflt.vf v0, v8, fa5
+; RV64-NEXT: vfcvt.x.f.v v8, v9, v0.t
+; RV64-NEXT: vfcvt.f.x.v v10, v8, v0.t
+; RV64-NEXT: vfcvt.x.f.v v8, v9
+; RV64-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
+; RV64-NEXT: vfsgnj.vv v9, v10, v9, v0.t
+; RV64-NEXT: vse32.v v9, (a0)
+; RV64-NEXT: ret
+ %a = call <vscale x 1 x float> @llvm.rint.nxv1f32(<vscale x 1 x float> %x)
+ %b = fptosi <vscale x 1 x float> %a to <vscale x 1 x i32>
+ store <vscale x 1 x float> %a, ptr %p
+ ret <vscale x 1 x i32> %b
+}
>From 84f78538fbf9af8ec9ed25b7dff5cd6cf2ad0be4 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Thu, 13 Aug 2026 11:02:59 -0700
Subject: [PATCH 2/2] [RISCV] Don't fold vector (fp_to_sint
(ceil/floor/trunc/etc X)) if the inner op has multiple uses.
If the inner operation needs to write FRM and the folded operation
does too, combining them may require another pair of FRM writes.
This may be expensive on CPUs without FRM renaming support.
Assisted-by: Claude
---
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 7 ++++
.../CodeGen/RISCV/rvv/float-round-conv.ll | 32 +++++++------------
2 files changed, 19 insertions(+), 20 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 03c000b86b2e6..f183342afa1f2 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -20433,6 +20433,13 @@ static SDValue performFP_TO_INTCombine(SDNode *N,
VT.getScalarSizeInBits() * 2 < SrcVT.getScalarSizeInBits())
return SDValue();
+ // If we'll need to write FRM, don't fold unless the src has a single use.
+ // This avoids potentially writing FRM multiple times. RTZ has a dedicated
+ // instruction and DYN uses the current FRM, so neither needs a write.
+ if (FRM != RISCVFPRndMode::RTZ && FRM != RISCVFPRndMode::DYN &&
+ !Src.hasOneUse())
+ return SDValue();
+
// Make fixed-length vectors scalable first
if (SrcVT.isFixedLengthVector()) {
SrcContainerVT = getContainerForFixedLengthVector(SrcVT, Subtarget);
diff --git a/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll b/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll
index 54bf1d6f016af..f8a3523b25e28 100644
--- a/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll
+++ b/llvm/test/CodeGen/RISCV/rvv/float-round-conv.ll
@@ -1046,12 +1046,10 @@ define <vscale x 1 x i32> @ceil_nxv1f32_to_si32_multiple_use(<vscale x 1 x float
; RV32-NEXT: fsrmi a1, 3
; RV32-NEXT: vfcvt.x.f.v v8, v9, v0.t
; RV32-NEXT: fsrm a1
-; RV32-NEXT: vfcvt.f.x.v v10, v8, v0.t
-; RV32-NEXT: fsrmi a1, 3
-; RV32-NEXT: vfcvt.x.f.v v8, v9
+; RV32-NEXT: vfcvt.f.x.v v8, v8, v0.t
; RV32-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
-; RV32-NEXT: vfsgnj.vv v9, v10, v9, v0.t
-; RV32-NEXT: fsrm a1
+; RV32-NEXT: vfsgnj.vv v9, v8, v9, v0.t
+; RV32-NEXT: vfcvt.rtz.x.f.v v8, v9
; RV32-NEXT: vse32.v v9, (a0)
; RV32-NEXT: ret
;
@@ -1066,12 +1064,10 @@ define <vscale x 1 x i32> @ceil_nxv1f32_to_si32_multiple_use(<vscale x 1 x float
; RV64-NEXT: fsrmi a1, 3
; RV64-NEXT: vfcvt.x.f.v v8, v9, v0.t
; RV64-NEXT: fsrm a1
-; RV64-NEXT: vfcvt.f.x.v v10, v8, v0.t
-; RV64-NEXT: fsrmi a1, 3
-; RV64-NEXT: vfcvt.x.f.v v8, v9
+; RV64-NEXT: vfcvt.f.x.v v8, v8, v0.t
; RV64-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
-; RV64-NEXT: vfsgnj.vv v9, v10, v9, v0.t
-; RV64-NEXT: fsrm a1
+; RV64-NEXT: vfsgnj.vv v9, v8, v9, v0.t
+; RV64-NEXT: vfcvt.rtz.x.f.v v8, v9
; RV64-NEXT: vse32.v v9, (a0)
; RV64-NEXT: ret
%a = call <vscale x 1 x float> @llvm.ceil.nxv1f32(<vscale x 1 x float> %x)
@@ -1092,12 +1088,10 @@ define <vscale x 1 x i32> @ceil_nxv1f32_to_ui32_multiple_use(<vscale x 1 x float
; RV32-NEXT: fsrmi a1, 3
; RV32-NEXT: vfcvt.x.f.v v8, v9, v0.t
; RV32-NEXT: fsrm a1
-; RV32-NEXT: vfcvt.f.x.v v10, v8, v0.t
-; RV32-NEXT: fsrmi a1, 3
-; RV32-NEXT: vfcvt.xu.f.v v8, v9
+; RV32-NEXT: vfcvt.f.x.v v8, v8, v0.t
; RV32-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
-; RV32-NEXT: vfsgnj.vv v9, v10, v9, v0.t
-; RV32-NEXT: fsrm a1
+; RV32-NEXT: vfsgnj.vv v9, v8, v9, v0.t
+; RV32-NEXT: vfcvt.rtz.xu.f.v v8, v9
; RV32-NEXT: vse32.v v9, (a0)
; RV32-NEXT: ret
;
@@ -1112,12 +1106,10 @@ define <vscale x 1 x i32> @ceil_nxv1f32_to_ui32_multiple_use(<vscale x 1 x float
; RV64-NEXT: fsrmi a1, 3
; RV64-NEXT: vfcvt.x.f.v v8, v9, v0.t
; RV64-NEXT: fsrm a1
-; RV64-NEXT: vfcvt.f.x.v v10, v8, v0.t
-; RV64-NEXT: fsrmi a1, 3
-; RV64-NEXT: vfcvt.xu.f.v v8, v9
+; RV64-NEXT: vfcvt.f.x.v v8, v8, v0.t
; RV64-NEXT: vsetvli zero, zero, e32, mf2, ta, mu
-; RV64-NEXT: vfsgnj.vv v9, v10, v9, v0.t
-; RV64-NEXT: fsrm a1
+; RV64-NEXT: vfsgnj.vv v9, v8, v9, v0.t
+; RV64-NEXT: vfcvt.rtz.xu.f.v v8, v9
; RV64-NEXT: vse32.v v9, (a0)
; RV64-NEXT: ret
%a = call <vscale x 1 x float> @llvm.ceil.nxv1f32(<vscale x 1 x float> %x)
More information about the llvm-commits
mailing list