[llvm] [AArch64][SelectionDAG] Avoid cross-bank copy for NEON vcvtfp2fx result (PR #210275)

Kieran B via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 23 03:55:12 PDT 2026


https://github.com/kieroxide updated https://github.com/llvm/llvm-project/pull/210275

>From 9a3f11cf375476c98cc35cb38dc72a48805c7b69 Mon Sep 17 00:00:00 2001
From: Kieran Bailey <kieran.bailey at arm.com>
Date: Thu, 16 Jul 2026 15:15:56 +0000
Subject: [PATCH 1/4] [AArch64][SelectionDAG] Avoid cross-bank copies for NEON
 vcvtfp2fx results

- Add  SelectionDAG-only patterns for NEON vcvtfp2fx so integer result select GPR instead of FPR, avoiding the need for cross-bank copies.
- Bitcast uses still select the FP/SIMD-register forms where available.
- Add complexity used to prioritize the new patterns over the generic FP/SIMD patterns which GlobalIsel still uses

- Add testing for GPR-resulting and FPR-resulting patterns
- Add missing tests in fp16_intrinsic_scalar_2op.ll: test_vcvth_n_u64_f16_1 and test_vcvth_n_u64_f16_16

A follow-up patch will also include the fix for GlobalIsel
---
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   |  43 ++++
 .../AArch64/fp16_intrinsic_scalar_2op.ll      | 215 ++++++++++++++----
 .../CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll  |  78 +++++++
 3 files changed, 292 insertions(+), 44 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 51fb2e1d1f9f0..f0e7e020c05e4 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -9121,6 +9121,49 @@ defm FCVTZS : SIMDFPScalarRShift<0, 0b11111, "fcvtzs">;
 defm FCVTZU : SIMDFPScalarRShift<1, 0b11111, "fcvtzu">;
 defm SCVTF  : SIMDFPScalarRShift<0, 0b11100, "scvtf">;
 defm UCVTF  : SIMDFPScalarRShift<1, 0b11100, "ucvtf">;
+
+// Transformation for SIMD shift imm to fixed point imm for FPR-to-GPR result
+def fixedpoint_scalar_xform : SDNodeXForm<timm, [{
+  (void)N;
+  return V;
+}]>;
+
+multiclass FPToFixedScalarPats<SDPatternOperator OpN, string INST > {
+// Allow integer result to remain in GPR registers
+// SelectionDAG-only as GIsel doesn't import fixedpoint_scalar_xform
+// Give priority over generic FPR fallback
+let AddedComplexity = 1 in {
+  def : Pat<(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)),
+          (!cast<Instruction>(INST # "SWSri") FPR32:$Rn, (fixedpoint_scalar_xform vecshiftR32:$imm))>;
+  def : Pat<(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)),
+          (!cast<Instruction>(INST # "SXDri") FPR64:$Rn, (fixedpoint_scalar_xform vecshiftR64:$imm))>;
+  
+  def : Pat<(i32 (OpN (f16 FPR16:$Rn), vecshiftR32:$imm)),
+          (!cast<Instruction>(INST # "SWHri") FPR16:$Rn, (fixedpoint_scalar_xform vecshiftR32:$imm))>;
+  def : Pat<(i64 (OpN (f16 FPR16:$Rn), vecshiftR64:$imm)),
+          (!cast<Instruction>(INST # "SXHri") FPR16:$Rn, (fixedpoint_scalar_xform vecshiftR64:$imm))>;
+  }
+
+  // Bitcast results kept in FP/SIMD registers.
+  def : Pat<(f32 (bitconvert(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)))),
+          (!cast<Instruction>(INST # "s") FPR32:$Rn, vecshiftR32:$imm)>;
+  def : Pat<(f64 (bitconvert(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)))),
+          (!cast<Instruction>(INST # "d") FPR64:$Rn, vecshiftR64:$imm)>;
+
+  def : Pat<(f32(bitconvert (i32 (OpN (f16 FPR16:$Rn), vecshiftR32:$imm)))),
+          (f32 (INSERT_SUBREG 
+            (f32 (IMPLICIT_DEF)),
+            (!cast<Instruction>(INST # "h") FPR16:$Rn, vecshiftR32:$imm), 
+              hsub))>;
+  def : Pat<(f64 (bitconvert (i64 (OpN (f16 FPR16:$Rn), vecshiftR64:$imm)))),
+          (f64 (INSERT_SUBREG
+            (f64 (IMPLICIT_DEF)),
+            (!cast<Instruction>(INST # "h") FPR16:$Rn, vecshiftR64:$imm),
+              hsub))>;
+}
+defm : FPToFixedScalarPats<int_aarch64_neon_vcvtfp2fxs, "FCVTZS">;
+defm : FPToFixedScalarPats<int_aarch64_neon_vcvtfp2fxu, "FCVTZU">;
+
 // Codegen patterns for the above. We don't put these directly on the
 // instructions because TableGen's type inference can't handle the truth.
 // Having the same base pattern for fp <--> int totally freaks it out.
diff --git a/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll b/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll
index da70599483a63..f44e1a6fa9970 100644
--- a/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll
+++ b/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll
@@ -185,6 +185,7 @@ declare i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half, i32) #1
 declare i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f16(half, i32) #1
 declare half @llvm.aarch64.neon.vcvtfxu2fp.f16.i32(i32, i32) #1
 declare i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half, i32) #1
+declare i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f16(half, i32) #1
 
 define dso_local half @test_vcvth_n_f16_s16_1(i16 %a) {
 ; CHECK-SD-LABEL: test_vcvth_n_f16_s16_1:
@@ -247,11 +248,16 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_s16_f16_1(half %a) {
-; CHECK-LABEL: test_vcvth_n_s16_f16_1:
-; CHECK:       // %bb.0: // %entry
-; CHECK-NEXT:    fcvtzs h0, h0, #1
-; CHECK-NEXT:    fmov w0, s0
-; CHECK-NEXT:    ret
+; CHECK-SD-LABEL: test_vcvth_n_s16_f16_1:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzs w0, h0, #1
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_s16_f16_1:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzs h0, h0, #1
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 1)
   %0 = trunc i32 %fcvth_n to i16
@@ -259,11 +265,16 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_s16_f16_16(half %a) {
-; CHECK-LABEL: test_vcvth_n_s16_f16_16:
-; CHECK:       // %bb.0: // %entry
-; CHECK-NEXT:    fcvtzs h0, h0, #16
-; CHECK-NEXT:    fmov w0, s0
-; CHECK-NEXT:    ret
+; CHECK-SD-LABEL: test_vcvth_n_s16_f16_16:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzs w0, h0, #16
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_s16_f16_16:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzs h0, h0, #16
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 16)
   %0 = trunc i32 %fcvth_n to i16
@@ -271,47 +282,89 @@ entry:
 }
 
 define dso_local i32 @test_vcvth_n_s32_f16_1(half %a) {
-; CHECK-LABEL: test_vcvth_n_s32_f16_1:
-; CHECK:       // %bb.0: // %entry
-; CHECK-NEXT:    fcvtzs h0, h0, #1
-; CHECK-NEXT:    fmov w0, s0
-; CHECK-NEXT:    ret
+; CHECK-SD-LABEL: test_vcvth_n_s32_f16_1:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzs w0, h0, #1
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_s32_f16_1:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzs h0, h0, #1
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
 entry:
   %vcvth_n_s32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 1)
   ret i32 %vcvth_n_s32_f16
 }
 
 define dso_local i32 @test_vcvth_n_s32_f16_16(half %a) {
-; CHECK-LABEL: test_vcvth_n_s32_f16_16:
+; CHECK-SD-LABEL: test_vcvth_n_s32_f16_16:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzs w0, h0, #16
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_s32_f16_16:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzs h0, h0, #16
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
+entry:
+  %vcvth_n_s32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 16)
+  ret i32 %vcvth_n_s32_f16
+}
+
+define dso_local float @test_vcvth_n_s32_f16_fpr(half %a) {
+; CHECK-LABEL: test_vcvth_n_s32_f16_fpr:
 ; CHECK:       // %bb.0: // %entry
 ; CHECK-NEXT:    fcvtzs h0, h0, #16
-; CHECK-NEXT:    fmov w0, s0
 ; CHECK-NEXT:    ret
 entry:
   %vcvth_n_s32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 16)
-  ret i32 %vcvth_n_s32_f16
+  %bc = bitcast i32 %vcvth_n_s32_f16 to float
+  ret float %bc
 }
 
 define dso_local i64 @test_vcvth_n_s64_f16_1(half %a) {
-; CHECK-LABEL: test_vcvth_n_s64_f16_1:
-; CHECK:       // %bb.0: // %entry
-; CHECK-NEXT:    fcvtzs h0, h0, #1
-; CHECK-NEXT:    fmov x0, d0
-; CHECK-NEXT:    ret
+; CHECK-SD-LABEL: test_vcvth_n_s64_f16_1:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzs x0, h0, #1
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_s64_f16_1:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzs h0, h0, #1
+; CHECK-GI-NEXT:    fmov x0, d0
+; CHECK-GI-NEXT:    ret
 entry:
   %vcvth_n_s64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f16(half %a, i32 1)
   ret i64 %vcvth_n_s64_f16
 }
 
 define dso_local i64 @test_vcvth_n_s64_f16_32(half %a) {
-; CHECK-LABEL: test_vcvth_n_s64_f16_32:
+; CHECK-SD-LABEL: test_vcvth_n_s64_f16_32:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzs x0, h0, #32
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_s64_f16_32:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzs h0, h0, #32
+; CHECK-GI-NEXT:    fmov x0, d0
+; CHECK-GI-NEXT:    ret
+entry:
+  %vcvth_n_s64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f16(half %a, i32 32)
+  ret i64 %vcvth_n_s64_f16
+}
+
+define dso_local double @test_vcvth_n_s64_f16_fpr(half %a) {
+; CHECK-LABEL: test_vcvth_n_s64_f16_fpr:
 ; CHECK:       // %bb.0: // %entry
 ; CHECK-NEXT:    fcvtzs h0, h0, #32
-; CHECK-NEXT:    fmov x0, d0
 ; CHECK-NEXT:    ret
 entry:
   %vcvth_n_s64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f16(half %a, i32 32)
-  ret i64 %vcvth_n_s64_f16
+  %bc = bitcast i64 %vcvth_n_s64_f16 to double
+  ret double %bc
 }
 
 define dso_local half @test_vcvth_n_f16_u16_1(i16 %a) {
@@ -375,11 +428,16 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_u16_f16_1(half %a) {
-; CHECK-LABEL: test_vcvth_n_u16_f16_1:
-; CHECK:       // %bb.0: // %entry
-; CHECK-NEXT:    fcvtzu h0, h0, #1
-; CHECK-NEXT:    fmov w0, s0
-; CHECK-NEXT:    ret
+; CHECK-SD-LABEL: test_vcvth_n_u16_f16_1:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzu w0, h0, #1
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_u16_f16_1:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzu h0, h0, #1
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 1)
   %0 = trunc i32 %fcvth_n to i16
@@ -387,11 +445,16 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_u16_f16_16(half %a) {
-; CHECK-LABEL: test_vcvth_n_u16_f16_16:
-; CHECK:       // %bb.0: // %entry
-; CHECK-NEXT:    fcvtzu h0, h0, #16
-; CHECK-NEXT:    fmov w0, s0
-; CHECK-NEXT:    ret
+; CHECK-SD-LABEL: test_vcvth_n_u16_f16_16:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzu w0, h0, #16
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_u16_f16_16:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzu h0, h0, #16
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 16)
   %0 = trunc i32 %fcvth_n to i16
@@ -399,25 +462,89 @@ entry:
 }
 
 define dso_local i32 @test_vcvth_n_u32_f16_1(half %a) {
-; CHECK-LABEL: test_vcvth_n_u32_f16_1:
-; CHECK:       // %bb.0: // %entry
-; CHECK-NEXT:    fcvtzu h0, h0, #1
-; CHECK-NEXT:    fmov w0, s0
-; CHECK-NEXT:    ret
+; CHECK-SD-LABEL: test_vcvth_n_u32_f16_1:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzu w0, h0, #1
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_u32_f16_1:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzu h0, h0, #1
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
 entry:
   %vcvth_n_u32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 1)
   ret i32 %vcvth_n_u32_f16
 }
 
 define dso_local i32 @test_vcvth_n_u32_f16_16(half %a) {
-; CHECK-LABEL: test_vcvth_n_u32_f16_16:
+; CHECK-SD-LABEL: test_vcvth_n_u32_f16_16:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzu w0, h0, #16
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_u32_f16_16:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzu h0, h0, #16
+; CHECK-GI-NEXT:    fmov w0, s0
+; CHECK-GI-NEXT:    ret
+entry:
+  %vcvth_n_u32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 16)
+  ret i32 %vcvth_n_u32_f16
+}
+
+define dso_local float @test_vcvth_n_u32_f16_fpr(half %a) {
+; CHECK-LABEL: test_vcvth_n_u32_f16_fpr:
 ; CHECK:       // %bb.0: // %entry
 ; CHECK-NEXT:    fcvtzu h0, h0, #16
-; CHECK-NEXT:    fmov w0, s0
 ; CHECK-NEXT:    ret
 entry:
   %vcvth_n_u32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 16)
-  ret i32 %vcvth_n_u32_f16
+  %bc = bitcast i32 %vcvth_n_u32_f16 to float
+  ret float %bc
+}
+
+define dso_local i64 @test_vcvth_n_u64_f16_1(half %a) {
+; CHECK-SD-LABEL: test_vcvth_n_u64_f16_1:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzu x0, h0, #1
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_u64_f16_1:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzu h0, h0, #1
+; CHECK-GI-NEXT:    fmov x0, d0
+; CHECK-GI-NEXT:    ret
+entry:
+  %vcvth_n_u64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f16(half %a, i32 1)
+  ret i64 %vcvth_n_u64_f16
+}
+
+define dso_local i64 @test_vcvth_n_u64_f16_16(half %a) {
+; CHECK-SD-LABEL: test_vcvth_n_u64_f16_16:
+; CHECK-SD:       // %bb.0: // %entry
+; CHECK-SD-NEXT:    fcvtzu x0, h0, #16
+; CHECK-SD-NEXT:    ret
+;
+; CHECK-GI-LABEL: test_vcvth_n_u64_f16_16:
+; CHECK-GI:       // %bb.0: // %entry
+; CHECK-GI-NEXT:    fcvtzu h0, h0, #16
+; CHECK-GI-NEXT:    fmov x0, d0
+; CHECK-GI-NEXT:    ret
+entry:
+  %vcvth_n_u64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f16(half %a, i32 16)
+  ret i64 %vcvth_n_u64_f16
+}
+
+define dso_local double @test_vcvth_n_u64_f16_fpr(half %a) {
+; CHECK-LABEL: test_vcvth_n_u64_f16_fpr:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzu h0, h0, #16
+; CHECK-NEXT:    ret
+entry:
+  %vcvth_n_u64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f16(half %a, i32 16)
+  %bc = bitcast i64 %vcvth_n_u64_f16 to double
+  ret double %bc
 }
 
 define dso_local i16 @vcageh_f16_test(half %a, half %b) {
diff --git a/llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll b/llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll
new file mode 100644
index 0000000000000..5ca1d8c44cf58
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll
@@ -0,0 +1,78 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=aarch64 -global-isel=0 < %s | FileCheck %s
+
+define i32 @vcvtfp2fxs_f32_i32(float %a) {
+; CHECK-LABEL: vcvtfp2fxs_f32_i32:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzs w0, s0, #16
+; CHECK-NEXT:    ret
+  %r = call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f32(float %a, i32 16)
+  ret i32 %r
+}
+
+define float @vcvtfp2fxs_f32_i32_bitcast(float %a) {
+; CHECK-LABEL: vcvtfp2fxs_f32_i32_bitcast:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzs s0, s0, #16
+; CHECK-NEXT:    ret
+  %r = call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f32(float %a, i32 16)
+  %b = bitcast i32 %r to float
+  ret float %b
+}
+
+define i32 @vcvtfp2fxu_f32_i32(float %a) {
+; CHECK-LABEL: vcvtfp2fxu_f32_i32:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzu w0, s0, #16
+; CHECK-NEXT:    ret
+  %r = call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f32(float %a, i32 16)
+  ret i32 %r
+}
+
+define float @vcvtfp2fxu_f32_i32_bitcast(float %a) {
+; CHECK-LABEL: vcvtfp2fxu_f32_i32_bitcast:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzu s0, s0, #16
+; CHECK-NEXT:    ret
+  %r = call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f32(float %a, i32 16)
+  %b = bitcast i32 %r to float
+  ret float %b
+}
+
+define i64 @vcvtfp2fxs_f64_i64(double %a) {
+; CHECK-LABEL: vcvtfp2fxs_f64_i64:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzs x0, d0, #16
+; CHECK-NEXT:    ret
+  %r = call i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f64(double %a, i32 16)
+  ret i64 %r
+}
+
+define double @vcvtfp2fxs_f64_i64_bitcast(double %a) {
+; CHECK-LABEL: vcvtfp2fxs_f64_i64_bitcast:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzs d0, d0, #16
+; CHECK-NEXT:    ret
+  %r = call i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f64(double %a, i32 16)
+  %b = bitcast i64 %r to double
+  ret double %b
+}
+
+define i64 @vcvtfp2fxu_f64_i64(double %a) {
+; CHECK-LABEL: vcvtfp2fxu_f64_i64:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzu x0, d0, #16
+; CHECK-NEXT:    ret
+  %r = call i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f64(double %a, i32 16)
+  ret i64 %r
+}
+
+define double @vcvtfp2fxu_f64_i64_bitcast(double %a) {
+; CHECK-LABEL: vcvtfp2fxu_f64_i64_bitcast:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    fcvtzu d0, d0, #16
+; CHECK-NEXT:    ret
+  %r = call i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f64(double %a, i32 16)
+  %b = bitcast i64 %r to double
+  ret double %b
+}

>From 6be28ae62f2f4f3cfb056c9dbd29894430ba125b Mon Sep 17 00:00:00 2001
From: Kieran Bailey <kieran.bailey at arm.com>
Date: Mon, 20 Jul 2026 13:54:46 +0000
Subject: [PATCH 2/4] Addressing Review Comments

- Moved the existing FPR fallbacks into the multiclass
- Removed unnecessary AddedComplexity
- Restructured comments based on review suggestions
---
 llvm/lib/Target/AArch64/AArch64InstrInfo.td | 64 +++++++--------------
 1 file changed, 21 insertions(+), 43 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index f0e7e020c05e4..9295074439000 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -9129,27 +9129,21 @@ def fixedpoint_scalar_xform : SDNodeXForm<timm, [{
 }]>;
 
 multiclass FPToFixedScalarPats<SDPatternOperator OpN, string INST > {
-// Allow integer result to remain in GPR registers
-// SelectionDAG-only as GIsel doesn't import fixedpoint_scalar_xform
-// Give priority over generic FPR fallback
-let AddedComplexity = 1 in {
+  // Allow integer result to remain in GPR register
   def : Pat<(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)),
           (!cast<Instruction>(INST # "SWSri") FPR32:$Rn, (fixedpoint_scalar_xform vecshiftR32:$imm))>;
   def : Pat<(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)),
           (!cast<Instruction>(INST # "SXDri") FPR64:$Rn, (fixedpoint_scalar_xform vecshiftR64:$imm))>;
-  
   def : Pat<(i32 (OpN (f16 FPR16:$Rn), vecshiftR32:$imm)),
           (!cast<Instruction>(INST # "SWHri") FPR16:$Rn, (fixedpoint_scalar_xform vecshiftR32:$imm))>;
   def : Pat<(i64 (OpN (f16 FPR16:$Rn), vecshiftR64:$imm)),
           (!cast<Instruction>(INST # "SXHri") FPR16:$Rn, (fixedpoint_scalar_xform vecshiftR64:$imm))>;
-  }
 
-  // Bitcast results kept in FP/SIMD registers.
+  // Explicit Bitcast results kept in FP/SIMD registers.
   def : Pat<(f32 (bitconvert(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)))),
           (!cast<Instruction>(INST # "s") FPR32:$Rn, vecshiftR32:$imm)>;
   def : Pat<(f64 (bitconvert(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)))),
           (!cast<Instruction>(INST # "d") FPR64:$Rn, vecshiftR64:$imm)>;
-
   def : Pat<(f32(bitconvert (i32 (OpN (f16 FPR16:$Rn), vecshiftR32:$imm)))),
           (f32 (INSERT_SUBREG 
             (f32 (IMPLICIT_DEF)),
@@ -9160,27 +9154,31 @@ let AddedComplexity = 1 in {
             (f64 (IMPLICIT_DEF)),
             (!cast<Instruction>(INST # "h") FPR16:$Rn, vecshiftR64:$imm),
               hsub))>;
+
+  // FPR fallback patterns
+  def : Pat<(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)),
+          (!cast<Instruction>(INST # "s") FPR32:$Rn, vecshiftR32:$imm)>;
+  def : Pat<(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)),
+          (!cast<Instruction>(INST # "d") FPR64:$Rn, vecshiftR64:$imm)>;
+  def : Pat<(v1i64 (OpN (v1f64 FPR64:$Rn), vecshiftR64:$imm)),
+          (!cast<Instruction>(INST # "d") FPR64:$Rn, vecshiftR64:$imm)>;
+  def : Pat<(i32 (OpN (f16 FPR16:$Rn), vecshiftR32:$imm)),
+          (i32 (INSERT_SUBREG
+            (i32 (IMPLICIT_DEF)),
+            (!cast<Instruction>(INST # "h") FPR16:$Rn, vecshiftR32:$imm),
+            hsub))>;
+  def : Pat<(i64 (OpN (f16 FPR16:$Rn), vecshiftR64:$imm)),
+            (i64 (INSERT_SUBREG
+              (i64 (IMPLICIT_DEF)),
+              (!cast<Instruction>(INST # "h") FPR16:$Rn, vecshiftR64:$imm),
+              hsub))>;
 }
 defm : FPToFixedScalarPats<int_aarch64_neon_vcvtfp2fxs, "FCVTZS">;
 defm : FPToFixedScalarPats<int_aarch64_neon_vcvtfp2fxu, "FCVTZU">;
 
-// Codegen patterns for the above. We don't put these directly on the
+// Codegen patterns for SCVTF and UCVTF. We don't put these directly on the
 // instructions because TableGen's type inference can't handle the truth.
 // Having the same base pattern for fp <--> int totally freaks it out.
-def : Pat<(int_aarch64_neon_vcvtfp2fxs FPR32:$Rn, vecshiftR32:$imm),
-          (FCVTZSs FPR32:$Rn, vecshiftR32:$imm)>;
-def : Pat<(int_aarch64_neon_vcvtfp2fxu FPR32:$Rn, vecshiftR32:$imm),
-          (FCVTZUs FPR32:$Rn, vecshiftR32:$imm)>;
-def : Pat<(i64 (int_aarch64_neon_vcvtfp2fxs (f64 FPR64:$Rn), vecshiftR64:$imm)),
-          (FCVTZSd FPR64:$Rn, vecshiftR64:$imm)>;
-def : Pat<(i64 (int_aarch64_neon_vcvtfp2fxu (f64 FPR64:$Rn), vecshiftR64:$imm)),
-          (FCVTZUd FPR64:$Rn, vecshiftR64:$imm)>;
-def : Pat<(v1i64 (int_aarch64_neon_vcvtfp2fxs (v1f64 FPR64:$Rn),
-                                            vecshiftR64:$imm)),
-          (FCVTZSd FPR64:$Rn, vecshiftR64:$imm)>;
-def : Pat<(v1i64 (int_aarch64_neon_vcvtfp2fxu (v1f64 FPR64:$Rn),
-                                            vecshiftR64:$imm)),
-          (FCVTZUd FPR64:$Rn, vecshiftR64:$imm)>;
 def : Pat<(int_aarch64_neon_vcvtfxu2fp FPR32:$Rn, vecshiftR32:$imm),
           (UCVTFs FPR32:$Rn, vecshiftR32:$imm)>;
 def : Pat<(f64 (int_aarch64_neon_vcvtfxu2fp (i64 FPR64:$Rn), vecshiftR64:$imm)),
@@ -9212,26 +9210,6 @@ def : Pat<(f16 (int_aarch64_neon_vcvtfxu2fp FPR32:$Rn, vecshiftR16:$imm)),
           (UCVTFh (f16 (EXTRACT_SUBREG FPR32:$Rn, hsub)), vecshiftR16:$imm)>;
 def : Pat<(f16 (int_aarch64_neon_vcvtfxu2fp (i64 FPR64:$Rn), vecshiftR16:$imm)),
           (UCVTFh (f16 (EXTRACT_SUBREG FPR64:$Rn, hsub)), vecshiftR16:$imm)>;
-def : Pat<(i32 (int_aarch64_neon_vcvtfp2fxs (f16 FPR16:$Rn), vecshiftR32:$imm)),
-          (i32 (INSERT_SUBREG
-            (i32 (IMPLICIT_DEF)),
-            (FCVTZSh FPR16:$Rn, vecshiftR32:$imm),
-            hsub))>;
-def : Pat<(i64 (int_aarch64_neon_vcvtfp2fxs (f16 FPR16:$Rn), vecshiftR64:$imm)),
-          (i64 (INSERT_SUBREG
-            (i64 (IMPLICIT_DEF)),
-            (FCVTZSh FPR16:$Rn, vecshiftR64:$imm),
-            hsub))>;
-def : Pat<(i32 (int_aarch64_neon_vcvtfp2fxu (f16 FPR16:$Rn), vecshiftR32:$imm)),
-          (i32 (INSERT_SUBREG
-            (i32 (IMPLICIT_DEF)),
-            (FCVTZUh FPR16:$Rn, vecshiftR32:$imm),
-            hsub))>;
-def : Pat<(i64 (int_aarch64_neon_vcvtfp2fxu (f16 FPR16:$Rn), vecshiftR64:$imm)),
-          (i64 (INSERT_SUBREG
-            (i64 (IMPLICIT_DEF)),
-            (FCVTZUh FPR16:$Rn, vecshiftR64:$imm),
-            hsub))>;
 def : Pat<(i32 (int_aarch64_neon_facge (f16 FPR16:$Rn), (f16 FPR16:$Rm))),
           (i32 (INSERT_SUBREG
             (i32 (IMPLICIT_DEF)),

>From a8403429e124db321172ef0b79ef37ad239183a2 Mon Sep 17 00:00:00 2001
From: Kieran Bailey <kieran.bailey at arm.com>
Date: Tue, 21 Jul 2026 11:13:14 +0000
Subject: [PATCH 3/4] Remove f16 GPR result conversions. - Acle requires f16
 conversions to lower to Hd register - Remove f16 GPR conversion patterns for
 vcvth_n

---
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   |  20 +-
 .../AArch64/fp16_intrinsic_scalar_2op.ll      | 180 ++++++------------
 2 files changed, 63 insertions(+), 137 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 9295074439000..20c6b3b7b3635 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -9122,40 +9122,26 @@ defm FCVTZU : SIMDFPScalarRShift<1, 0b11111, "fcvtzu">;
 defm SCVTF  : SIMDFPScalarRShift<0, 0b11100, "scvtf">;
 defm UCVTF  : SIMDFPScalarRShift<1, 0b11100, "ucvtf">;
 
-// Transformation for SIMD shift imm to fixed point imm for FPR-to-GPR result
+// Transformation for SIMD shift imm to fixed point imm for FPR-to-GPR result.
 def fixedpoint_scalar_xform : SDNodeXForm<timm, [{
   (void)N;
   return V;
 }]>;
 
 multiclass FPToFixedScalarPats<SDPatternOperator OpN, string INST > {
-  // Allow integer result to remain in GPR register
+  // Allow integer result to remain in GPR register.
   def : Pat<(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)),
           (!cast<Instruction>(INST # "SWSri") FPR32:$Rn, (fixedpoint_scalar_xform vecshiftR32:$imm))>;
   def : Pat<(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)),
           (!cast<Instruction>(INST # "SXDri") FPR64:$Rn, (fixedpoint_scalar_xform vecshiftR64:$imm))>;
-  def : Pat<(i32 (OpN (f16 FPR16:$Rn), vecshiftR32:$imm)),
-          (!cast<Instruction>(INST # "SWHri") FPR16:$Rn, (fixedpoint_scalar_xform vecshiftR32:$imm))>;
-  def : Pat<(i64 (OpN (f16 FPR16:$Rn), vecshiftR64:$imm)),
-          (!cast<Instruction>(INST # "SXHri") FPR16:$Rn, (fixedpoint_scalar_xform vecshiftR64:$imm))>;
 
   // Explicit Bitcast results kept in FP/SIMD registers.
   def : Pat<(f32 (bitconvert(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)))),
           (!cast<Instruction>(INST # "s") FPR32:$Rn, vecshiftR32:$imm)>;
   def : Pat<(f64 (bitconvert(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)))),
           (!cast<Instruction>(INST # "d") FPR64:$Rn, vecshiftR64:$imm)>;
-  def : Pat<(f32(bitconvert (i32 (OpN (f16 FPR16:$Rn), vecshiftR32:$imm)))),
-          (f32 (INSERT_SUBREG 
-            (f32 (IMPLICIT_DEF)),
-            (!cast<Instruction>(INST # "h") FPR16:$Rn, vecshiftR32:$imm), 
-              hsub))>;
-  def : Pat<(f64 (bitconvert (i64 (OpN (f16 FPR16:$Rn), vecshiftR64:$imm)))),
-          (f64 (INSERT_SUBREG
-            (f64 (IMPLICIT_DEF)),
-            (!cast<Instruction>(INST # "h") FPR16:$Rn, vecshiftR64:$imm),
-              hsub))>;
 
-  // FPR fallback patterns
+  // FPR fallback patterns.
   def : Pat<(i32 (OpN FPR32:$Rn, vecshiftR32:$imm)),
           (!cast<Instruction>(INST # "s") FPR32:$Rn, vecshiftR32:$imm)>;
   def : Pat<(i64 (OpN (f64 FPR64:$Rn), vecshiftR64:$imm)),
diff --git a/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll b/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll
index f44e1a6fa9970..0e3e6d7bcaf80 100644
--- a/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll
+++ b/llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll
@@ -248,16 +248,11 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_s16_f16_1(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_s16_f16_1:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzs w0, h0, #1
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_s16_f16_1:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzs h0, h0, #1
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_s16_f16_1:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzs h0, h0, #1
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 1)
   %0 = trunc i32 %fcvth_n to i16
@@ -265,16 +260,11 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_s16_f16_16(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_s16_f16_16:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzs w0, h0, #16
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_s16_f16_16:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzs h0, h0, #16
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_s16_f16_16:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzs h0, h0, #16
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 16)
   %0 = trunc i32 %fcvth_n to i16
@@ -282,32 +272,22 @@ entry:
 }
 
 define dso_local i32 @test_vcvth_n_s32_f16_1(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_s32_f16_1:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzs w0, h0, #1
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_s32_f16_1:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzs h0, h0, #1
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_s32_f16_1:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzs h0, h0, #1
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_s32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 1)
   ret i32 %vcvth_n_s32_f16
 }
 
 define dso_local i32 @test_vcvth_n_s32_f16_16(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_s32_f16_16:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzs w0, h0, #16
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_s32_f16_16:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzs h0, h0, #16
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_s32_f16_16:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzs h0, h0, #16
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_s32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxs.i32.f16(half %a, i32 16)
   ret i32 %vcvth_n_s32_f16
@@ -325,32 +305,22 @@ entry:
 }
 
 define dso_local i64 @test_vcvth_n_s64_f16_1(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_s64_f16_1:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzs x0, h0, #1
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_s64_f16_1:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzs h0, h0, #1
-; CHECK-GI-NEXT:    fmov x0, d0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_s64_f16_1:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzs h0, h0, #1
+; CHECK-NEXT:    fmov x0, d0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_s64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f16(half %a, i32 1)
   ret i64 %vcvth_n_s64_f16
 }
 
 define dso_local i64 @test_vcvth_n_s64_f16_32(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_s64_f16_32:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzs x0, h0, #32
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_s64_f16_32:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzs h0, h0, #32
-; CHECK-GI-NEXT:    fmov x0, d0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_s64_f16_32:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzs h0, h0, #32
+; CHECK-NEXT:    fmov x0, d0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_s64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxs.i64.f16(half %a, i32 32)
   ret i64 %vcvth_n_s64_f16
@@ -428,16 +398,11 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_u16_f16_1(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_u16_f16_1:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzu w0, h0, #1
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_u16_f16_1:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzu h0, h0, #1
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_u16_f16_1:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzu h0, h0, #1
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 1)
   %0 = trunc i32 %fcvth_n to i16
@@ -445,16 +410,11 @@ entry:
 }
 
 define dso_local i16 @test_vcvth_n_u16_f16_16(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_u16_f16_16:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzu w0, h0, #16
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_u16_f16_16:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzu h0, h0, #16
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_u16_f16_16:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzu h0, h0, #16
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %fcvth_n = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 16)
   %0 = trunc i32 %fcvth_n to i16
@@ -462,32 +422,22 @@ entry:
 }
 
 define dso_local i32 @test_vcvth_n_u32_f16_1(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_u32_f16_1:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzu w0, h0, #1
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_u32_f16_1:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzu h0, h0, #1
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_u32_f16_1:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzu h0, h0, #1
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_u32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 1)
   ret i32 %vcvth_n_u32_f16
 }
 
 define dso_local i32 @test_vcvth_n_u32_f16_16(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_u32_f16_16:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzu w0, h0, #16
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_u32_f16_16:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzu h0, h0, #16
-; CHECK-GI-NEXT:    fmov w0, s0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_u32_f16_16:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzu h0, h0, #16
+; CHECK-NEXT:    fmov w0, s0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_u32_f16 = tail call i32 @llvm.aarch64.neon.vcvtfp2fxu.i32.f16(half %a, i32 16)
   ret i32 %vcvth_n_u32_f16
@@ -505,32 +455,22 @@ entry:
 }
 
 define dso_local i64 @test_vcvth_n_u64_f16_1(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_u64_f16_1:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzu x0, h0, #1
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_u64_f16_1:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzu h0, h0, #1
-; CHECK-GI-NEXT:    fmov x0, d0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_u64_f16_1:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzu h0, h0, #1
+; CHECK-NEXT:    fmov x0, d0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_u64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f16(half %a, i32 1)
   ret i64 %vcvth_n_u64_f16
 }
 
 define dso_local i64 @test_vcvth_n_u64_f16_16(half %a) {
-; CHECK-SD-LABEL: test_vcvth_n_u64_f16_16:
-; CHECK-SD:       // %bb.0: // %entry
-; CHECK-SD-NEXT:    fcvtzu x0, h0, #16
-; CHECK-SD-NEXT:    ret
-;
-; CHECK-GI-LABEL: test_vcvth_n_u64_f16_16:
-; CHECK-GI:       // %bb.0: // %entry
-; CHECK-GI-NEXT:    fcvtzu h0, h0, #16
-; CHECK-GI-NEXT:    fmov x0, d0
-; CHECK-GI-NEXT:    ret
+; CHECK-LABEL: test_vcvth_n_u64_f16_16:
+; CHECK:       // %bb.0: // %entry
+; CHECK-NEXT:    fcvtzu h0, h0, #16
+; CHECK-NEXT:    fmov x0, d0
+; CHECK-NEXT:    ret
 entry:
   %vcvth_n_u64_f16 = tail call i64 @llvm.aarch64.neon.vcvtfp2fxu.i64.f16(half %a, i32 16)
   ret i64 %vcvth_n_u64_f16

>From e999588dd6cd2a89764bc0340e766dc889a62944 Mon Sep 17 00:00:00 2001
From: Kieran Bailey <kieran.bailey at arm.com>
Date: Thu, 23 Jul 2026 10:48:25 +0000
Subject: [PATCH 4/4] [AArch64][GlobalIsel] Avoid cross-bank copy for NEON
 vcvtfp2fx result

- Add GINodeXFormEquiv for fixedpoint_scalar_xform with Custom Renderer which is an identity function to transfer immediate value of vecshift to fixedpoint for GPR versions of fvcvt
-  Add register bank case for instrinic choosing correct RegBank on conditions.
- Update test to include Gisel run command
---
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   |  3 ++
 .../GISel/AArch64InstructionSelector.cpp      | 10 ++++++-
 .../AArch64/GISel/AArch64RegisterBankInfo.cpp | 28 +++++++++++++++++--
 .../CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll  |  6 +++-
 4 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 20c6b3b7b3635..65c7280834e56 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -9127,6 +9127,9 @@ def fixedpoint_scalar_xform : SDNodeXForm<timm, [{
   (void)N;
   return V;
 }]>;
+def gi_fixedpoint_scalar_xform
+    : GICustomOperandRenderer<"renderFixedPointScalarXForm">,
+      GISDNodeXFormEquiv<fixedpoint_scalar_xform>;
 
 multiclass FPToFixedScalarPats<SDPatternOperator OpN, string INST > {
   // Allow integer result to remain in GPR register.
diff --git a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
index f32e4f910ac58..5d3bfff8fb5d1 100644
--- a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
+++ b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
@@ -488,11 +488,12 @@ class AArch64InstructionSelector : public InstructionSelector {
   ComplexRendererFns
   selectCVTFixedPointVecBase(const MachineOperand &Root,
                              bool isReciprocal = false) const;
+  void renderFixedPointScalarXForm(MachineInstrBuilder &MIB,
+                                   const MachineInstr &MI, int OpIdx) const;
   void renderFixedPointXForm(MachineInstrBuilder &MIB, const MachineInstr &MI,
                              int OpIdx = -1) const;
   void renderFixedPointRecipXForm(MachineInstrBuilder &MIB,
                                   const MachineInstr &MI, int OpIdx = -1) const;
-
   void renderTruncImm(MachineInstrBuilder &MIB, const MachineInstr &MI,
                       int OpIdx = -1) const;
   void renderLogicalImm32(MachineInstrBuilder &MIB, const MachineInstr &I,
@@ -7979,6 +7980,13 @@ AArch64InstructionSelector::selectCVTFixedPosRecipOperandVec(
   return selectCVTFixedPointVecBase(Root, /*isReciprocal*/ true);
 }
 
+void AArch64InstructionSelector::renderFixedPointScalarXForm(
+    MachineInstrBuilder &MIB, const MachineInstr &MI, int OpIdx) const {
+  assert(OpIdx == 3 && MI.getOperand(OpIdx).isImm() &&
+         "Expected vecshift immediate operand");
+  MIB.addImm(MI.getOperand(OpIdx).getImm());
+}
+
 void AArch64InstructionSelector::renderFixedPointXForm(MachineInstrBuilder &MIB,
                                                        const MachineInstr &MI,
                                                        int OpIdx) const {
diff --git a/llvm/lib/Target/AArch64/GISel/AArch64RegisterBankInfo.cpp b/llvm/lib/Target/AArch64/GISel/AArch64RegisterBankInfo.cpp
index 90b5992acf598..79b85d6968be4 100644
--- a/llvm/lib/Target/AArch64/GISel/AArch64RegisterBankInfo.cpp
+++ b/llvm/lib/Target/AArch64/GISel/AArch64RegisterBankInfo.cpp
@@ -1432,8 +1432,6 @@ AArch64RegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
     }
     case Intrinsic::aarch64_neon_vcvtfxs2fp:
     case Intrinsic::aarch64_neon_vcvtfxu2fp:
-    case Intrinsic::aarch64_neon_vcvtfp2fxs:
-    case Intrinsic::aarch64_neon_vcvtfp2fxu:
       // Override these intrinsics, because they would have a partial
       // mapping. This is needed for 'half' types, which otherwise don't
       // get legalised correctly.
@@ -1442,6 +1440,32 @@ AArch64RegisterBankInfo::getInstrMapping(const MachineInstr &MI) const {
       // OpRegBankIdx[1] is the intrinsic ID.
       // OpRegBankIdx[3] is an integer immediate.
       break;
+    case Intrinsic::aarch64_neon_vcvtfp2fxs:
+    case Intrinsic::aarch64_neon_vcvtfp2fxu: {
+      OpRegBankIdx[2] = PMI_FirstFPR;
+      if (MRI.getType(MI.getOperand(0).getReg()).isVector()) {
+        OpRegBankIdx[0] = PMI_FirstFPR;
+        break;
+      }
+
+      TypeSize DstSize = getSizeInBits(MI.getOperand(0).getReg(), MRI, TRI);
+      TypeSize SrcSize = getSizeInBits(MI.getOperand(2).getReg(), MRI, TRI);
+
+      // Half-precision fixed-point FP-to-int scalar intrinsics are specified as
+      // producing an H-register result. The LLVM intrinsic may still have an
+      // i32/i64 result type, so check the source size for 16 bit
+      if (SrcSize == 16 ||
+          ((DstSize == SrcSize) &&
+           all_of(MRI.use_nodbg_instructions(MI.getOperand(0).getReg()),
+                  [&](const MachineInstr &UseMI) {
+                    return onlyUsesFP(UseMI, MRI, TRI) ||
+                           prefersFPUse(UseMI, MRI, TRI);
+                  })))
+        OpRegBankIdx[0] = PMI_FirstFPR;
+      else
+        OpRegBankIdx[0] = PMI_FirstGPR;
+      break;
+    }
     default: {
       // Check if we know that the intrinsic has any constraints on its register
       // banks. If it does, then update the mapping accordingly.
diff --git a/llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll b/llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll
index 5ca1d8c44cf58..ba6d4509e9981 100644
--- a/llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll
+++ b/llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll
@@ -1,5 +1,6 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
-; RUN: llc -mtriple=aarch64 -global-isel=0 < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64 -global-isel=0 < %s | FileCheck %s --check-prefixes=CHECK,CHECK-SD
+; RUN: llc -mtriple=aarch64 -global-isel=1 < %s | FileCheck %s --check-prefixes=CHECK,CHECK-GI
 
 define i32 @vcvtfp2fxs_f32_i32(float %a) {
 ; CHECK-LABEL: vcvtfp2fxs_f32_i32:
@@ -76,3 +77,6 @@ define double @vcvtfp2fxu_f64_i64_bitcast(double %a) {
   %b = bitcast i64 %r to double
   ret double %b
 }
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; CHECK-GI: {{.*}}
+; CHECK-SD: {{.*}}



More information about the llvm-commits mailing list