[llvm] [RISCV][GlobalISel] Fix fptosi/fptoui from half to i64 on RV32 (PR #222316)

Kane Wang via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 20:04:30 PDT 2026


https://github.com/ReVe1uv updated https://github.com/llvm/llvm-project/pull/222316

>From ff6664da43b64ea18c74c1002540fe8f2935f79b Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Wed, 9 Sep 2026 20:46:05 +0800
Subject: [PATCH 1/2] [RISCV][GlobalISel] Fix fptosi/fptoui from half to i64 on
 RV32

The fcvt.l[u].h patterns are RV64-only, so RV32 had no rule for
G_FPTOSI/G_FPTOUI with {s64, s16}. Fall back to the half-variant
conversion libcalls (__fixhfdi/__fixunshfdi), matching SelectionDAG.
---
 .../Target/RISCV/GISel/RISCVLegalizerInfo.cpp |  3 +
 .../CodeGen/RISCV/GlobalISel/half-convert.ll  | 57 +++++++++++++++++++
 2 files changed, 60 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index d0ee9bb39cc8b..a8b60bfe9b926 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -631,6 +631,9 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
       .widenScalarToNextPow2(0)
       .minScalar(0, s32)
       .libcallFor({{s32, s32}, {s64, s32}, {s32, s64}, {s64, s64}})
+      // The fcvt.l[u].h conversions are RV64-only, so RV32 falls back to the
+      // half-variant conversion libcalls.
+      .libcallFor({{s64, s16}})
       .libcallFor(ST.is64Bit(), {{s32, s128}, {s64, s128}}) // FIXME RV32.
       .libcallFor(ST.is64Bit(), {{s128, s32}, {s128, s64}, {s128, s128}});
 
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll b/llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll
new file mode 100644
index 0000000000000..d7e18681e13c0
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll
@@ -0,0 +1,57 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -global-isel -mattr=+f,+d,+zfh -target-abi=ilp32d < %s | FileCheck %s --check-prefixes=CHECK,RV32
+; RUN: llc -mtriple=riscv64 -global-isel -mattr=+f,+d,+zfh -target-abi=lp64d < %s | FileCheck %s --check-prefixes=CHECK,RV64
+
+define i64 @fptoui_i64_f16(half %x) nounwind {
+; RV32-LABEL: fptoui_i64_f16:
+; RV32:       # %bb.0:
+; RV32-NEXT:    addi sp, sp, -16
+; RV32-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
+; RV32-NEXT:    call __fixunshfdi
+; RV32-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 16
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: fptoui_i64_f16:
+; RV64:       # %bb.0:
+; RV64-NEXT:    fcvt.lu.h a0, fa0, rtz
+; RV64-NEXT:    ret
+  %a = fptoui half %x to i64
+  ret i64 %a
+}
+
+define i64 @fptosi_i64_f16(half %x) nounwind {
+; RV32-LABEL: fptosi_i64_f16:
+; RV32:       # %bb.0:
+; RV32-NEXT:    addi sp, sp, -16
+; RV32-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
+; RV32-NEXT:    call __fixhfdi
+; RV32-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
+; RV32-NEXT:    addi sp, sp, 16
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: fptosi_i64_f16:
+; RV64:       # %bb.0:
+; RV64-NEXT:    fcvt.l.h a0, fa0, rtz
+; RV64-NEXT:    ret
+  %a = fptosi half %x to i64
+  ret i64 %a
+}
+
+define i32 @fptoui_i32_f16(half %x) nounwind {
+; CHECK-LABEL: fptoui_i32_f16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    fcvt.wu.h a0, fa0, rtz
+; CHECK-NEXT:    ret
+  %a = fptoui half %x to i32
+  ret i32 %a
+}
+
+define i32 @fptosi_i32_f16(half %x) nounwind {
+; CHECK-LABEL: fptosi_i32_f16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    fcvt.w.h a0, fa0, rtz
+; CHECK-NEXT:    ret
+  %a = fptosi half %x to i32
+  ret i32 %a
+}

>From 700f231a97897bc7ad2e1d2385b0fb26a25882a6 Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Thu, 10 Sep 2026 10:59:54 +0800
Subject: [PATCH 2/2] [RISCV][GlobalISel] Use fcvt.w[u].h + extend for half to
 i64 with Zfh

The magnitude of a half is at most 65504, so with Zfh the i32 result of
fcvt.w[u].h never overflows and can simply be extended to i64.
---
 .../Target/RISCV/GISel/RISCVLegalizerInfo.cpp |  7 +-
 .../CodeGen/RISCV/GlobalISel/half-convert.ll  | 78 ++++++++++++-------
 2 files changed, 52 insertions(+), 33 deletions(-)

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index a8b60bfe9b926..09cbf455c5211 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -630,10 +630,11 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
       .customFor(ST.is64Bit() && ST.hasStdExtZfh(), {{s32, s16}})
       .widenScalarToNextPow2(0)
       .minScalar(0, s32)
+      // The magnitude of a half is at most 65504, so with Zfh use fcvt.w[u].h
+      // and extend the i32 result. Without Zfh, use the libcalls.
+      .libcallFor(!ST.hasStdExtZfh(), {{s64, s16}})
+      .narrowScalarFor({{s64, s16}}, changeTo(0, s32))
       .libcallFor({{s32, s32}, {s64, s32}, {s32, s64}, {s64, s64}})
-      // The fcvt.l[u].h conversions are RV64-only, so RV32 falls back to the
-      // half-variant conversion libcalls.
-      .libcallFor({{s64, s16}})
       .libcallFor(ST.is64Bit(), {{s32, s128}, {s64, s128}}) // FIXME RV32.
       .libcallFor(ST.is64Bit(), {{s128, s32}, {s128, s64}, {s128, s128}});
 
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll b/llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll
index d7e18681e13c0..3338be0aad09d 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/half-convert.ll
@@ -1,21 +1,42 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
-; RUN: llc -mtriple=riscv32 -global-isel -mattr=+f,+d,+zfh -target-abi=ilp32d < %s | FileCheck %s --check-prefixes=CHECK,RV32
-; RUN: llc -mtriple=riscv64 -global-isel -mattr=+f,+d,+zfh -target-abi=lp64d < %s | FileCheck %s --check-prefixes=CHECK,RV64
+; RUN: llc -mtriple=riscv32 -global-isel -mattr=+f,+d,+zfh \
+; RUN:   -target-abi=ilp32d < %s | FileCheck %s --check-prefixes=RV32
+; RUN: llc -mtriple=riscv64 -global-isel -mattr=+f,+d,+zfh \
+; RUN:   -target-abi=lp64d < %s | FileCheck %s --check-prefixes=RV64
+; RUN: llc -mtriple=riscv32 -global-isel -mattr=+f,+d \
+; RUN:   -target-abi=ilp32d < %s | FileCheck %s --check-prefixes=RV32NOZFH
+; RUN: llc -mtriple=riscv64 -global-isel -mattr=+f,+d \
+; RUN:   -target-abi=lp64d < %s | FileCheck %s --check-prefixes=RV64NOZFH
 
 define i64 @fptoui_i64_f16(half %x) nounwind {
 ; RV32-LABEL: fptoui_i64_f16:
 ; RV32:       # %bb.0:
-; RV32-NEXT:    addi sp, sp, -16
-; RV32-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT:    call __fixunshfdi
-; RV32-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT:    addi sp, sp, 16
+; RV32-NEXT:    fcvt.wu.h a0, fa0, rtz
+; RV32-NEXT:    li a1, 0
 ; RV32-NEXT:    ret
 ;
 ; RV64-LABEL: fptoui_i64_f16:
 ; RV64:       # %bb.0:
 ; RV64-NEXT:    fcvt.lu.h a0, fa0, rtz
 ; RV64-NEXT:    ret
+;
+; RV32NOZFH-LABEL: fptoui_i64_f16:
+; RV32NOZFH:       # %bb.0:
+; RV32NOZFH-NEXT:    addi sp, sp, -16
+; RV32NOZFH-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
+; RV32NOZFH-NEXT:    call __fixunshfdi
+; RV32NOZFH-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
+; RV32NOZFH-NEXT:    addi sp, sp, 16
+; RV32NOZFH-NEXT:    ret
+;
+; RV64NOZFH-LABEL: fptoui_i64_f16:
+; RV64NOZFH:       # %bb.0:
+; RV64NOZFH-NEXT:    addi sp, sp, -16
+; RV64NOZFH-NEXT:    sd ra, 8(sp) # 8-byte Folded Spill
+; RV64NOZFH-NEXT:    call __fixunshfdi
+; RV64NOZFH-NEXT:    ld ra, 8(sp) # 8-byte Folded Reload
+; RV64NOZFH-NEXT:    addi sp, sp, 16
+; RV64NOZFH-NEXT:    ret
   %a = fptoui half %x to i64
   ret i64 %a
 }
@@ -23,35 +44,32 @@ define i64 @fptoui_i64_f16(half %x) nounwind {
 define i64 @fptosi_i64_f16(half %x) nounwind {
 ; RV32-LABEL: fptosi_i64_f16:
 ; RV32:       # %bb.0:
-; RV32-NEXT:    addi sp, sp, -16
-; RV32-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
-; RV32-NEXT:    call __fixhfdi
-; RV32-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
-; RV32-NEXT:    addi sp, sp, 16
+; RV32-NEXT:    fcvt.w.h a0, fa0, rtz
+; RV32-NEXT:    srai a1, a0, 31
 ; RV32-NEXT:    ret
 ;
 ; RV64-LABEL: fptosi_i64_f16:
 ; RV64:       # %bb.0:
 ; RV64-NEXT:    fcvt.l.h a0, fa0, rtz
 ; RV64-NEXT:    ret
+;
+; RV32NOZFH-LABEL: fptosi_i64_f16:
+; RV32NOZFH:       # %bb.0:
+; RV32NOZFH-NEXT:    addi sp, sp, -16
+; RV32NOZFH-NEXT:    sw ra, 12(sp) # 4-byte Folded Spill
+; RV32NOZFH-NEXT:    call __fixhfdi
+; RV32NOZFH-NEXT:    lw ra, 12(sp) # 4-byte Folded Reload
+; RV32NOZFH-NEXT:    addi sp, sp, 16
+; RV32NOZFH-NEXT:    ret
+;
+; RV64NOZFH-LABEL: fptosi_i64_f16:
+; RV64NOZFH:       # %bb.0:
+; RV64NOZFH-NEXT:    addi sp, sp, -16
+; RV64NOZFH-NEXT:    sd ra, 8(sp) # 8-byte Folded Spill
+; RV64NOZFH-NEXT:    call __fixhfdi
+; RV64NOZFH-NEXT:    ld ra, 8(sp) # 8-byte Folded Reload
+; RV64NOZFH-NEXT:    addi sp, sp, 16
+; RV64NOZFH-NEXT:    ret
   %a = fptosi half %x to i64
   ret i64 %a
 }
-
-define i32 @fptoui_i32_f16(half %x) nounwind {
-; CHECK-LABEL: fptoui_i32_f16:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    fcvt.wu.h a0, fa0, rtz
-; CHECK-NEXT:    ret
-  %a = fptoui half %x to i32
-  ret i32 %a
-}
-
-define i32 @fptosi_i32_f16(half %x) nounwind {
-; CHECK-LABEL: fptosi_i32_f16:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    fcvt.w.h a0, fa0, rtz
-; CHECK-NEXT:    ret
-  %a = fptosi half %x to i32
-  ret i32 %a
-}



More information about the llvm-commits mailing list