Skip to content

[AArch64][SelectionDAG] Avoid cross-bank copy for NEON vcvtfp2fx result - #210275

Merged
Lukacma merged 5 commits into
llvm:mainfrom
kieroxide:aarch64-gpr-bitcast-vcvtfp2fx
Jul 30, 2026
Merged

[AArch64][SelectionDAG] Avoid cross-bank copy for NEON vcvtfp2fx result#210275
Lukacma merged 5 commits into
llvm:mainfrom
kieroxide:aarch64-gpr-bitcast-vcvtfp2fx

Conversation

@kieroxide

@kieroxide kieroxide commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
  • 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 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.

@kieroxide kieroxide changed the title [AArch64][SelectionDAG] Avoid cross-bank copy for NEON vcvtfp2fx results [AArch64][SelectionDAG] Avoid cross-bank copy for NEON vcvtfp2fx result Jul 17, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-aarch64

Author: Kieran B (kieroxide)

Changes
  • 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


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

3 Files Affected:

  • (modified) llvm/lib/Target/AArch64/AArch64InstrInfo.td (+43)
  • (modified) llvm/test/CodeGen/AArch64/fp16_intrinsic_scalar_2op.ll (+171-44)
  • (added) llvm/test/CodeGen/AArch64/neon-scalar-vcvtfp2fx.ll (+78)
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
+}

Comment thread llvm/lib/Target/AArch64/AArch64InstrInfo.td

multiclass FPToFixedScalarPats<SDPatternOperator OpN, string INST > {
// Allow integer result to remain in GPR registers
// SelectionDAG-only as GIsel doesn't import fixedpoint_scalar_xform

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what do you mean to say about GIsel here ? I think this comment needs to be clarified if you want to keep it in the code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that the renderer for fixedpoint_scalar_xform has not been added so GIsel cannot use it. I will update the comment to be clearer

// 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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary ?

@kieroxide kieroxide Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in Review commit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are creating a class of patterns, I think it is a good idea to bundle these there as well, so they are all in the same place.

@kieroxide kieroxide Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in Review commit

Comment thread llvm/lib/Target/AArch64/AArch64InstrInfo.td
Comment thread llvm/lib/Target/AArch64/AArch64InstrInfo.td Outdated
@cofibrant
cofibrant requested a review from guy-david July 20, 2026 12:49

@CarolineConcatto CarolineConcatto left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you double check your commit message. I think it needs to be updated according to your latest change.

@kieroxide kieroxide changed the title [AArch64][SelectionDAG] Avoid cross-bank copy for NEON vcvtfp2fx result [AArch64][CodeGen] Avoid cross-bank copy for NEON vcvtfp2fx result Jul 23, 2026
@kieroxide
kieroxide force-pushed the aarch64-gpr-bitcast-vcvtfp2fx branch from e999588 to a840342 Compare July 27, 2026 08:34
@kieroxide kieroxide changed the title [AArch64][CodeGen] Avoid cross-bank copy for NEON vcvtfp2fx result [AArch64][SelectionDAG] Avoid cross-bank copy for NEON vcvtfp2fx result Jul 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these f16 tests coming from ? They don't seem to test any of the new patterns?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove them. They were from my first commit when i did have 16 bit pattern FPR

Comment thread llvm/lib/Target/AArch64/AArch64InstrInfo.td
@kieroxide
kieroxide force-pushed the aarch64-gpr-bitcast-vcvtfp2fx branch from a840342 to 41eb642 Compare July 27, 2026 13:53

@Lukacma Lukacma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the f16 tests are removed this patch LGTM

@kieroxide
kieroxide force-pushed the aarch64-gpr-bitcast-vcvtfp2fx branch from 5960974 to 6651c05 Compare July 28, 2026 15:31
…sults

- 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
- Moved the existing FPR fallbacks into the multiclass
- Removed unnecessary AddedComplexity
- Restructured comments based on review suggestions
- Acle requires f16 conversions to lower to Hd register
- Remove f16 GPR conversion patterns for vcvth_n
- Remove the bitconvert test cases for f16
I will remove the missing test cases for u64_f16 as they will be added when the f16 patterns are fixed and are unrelated to my patch.
…sults

Updated the types in test names to be correct order for llvm standard
@kieroxide
kieroxide force-pushed the aarch64-gpr-bitcast-vcvtfp2fx branch from 6651c05 to 2547417 Compare July 29, 2026 14:32
@github-actions

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 179786 tests passed
  • 3586 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

lldb-api

lldb-api.commands/process/attach/TestProcessAttach.py
Script:
--
/usr/bin/python3 /home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/./lib --env LLVM_INCLUDE_DIR=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/include --env LLVM_TOOLS_DIR=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin --libcxx-include-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/include/c++/v1 --libcxx-include-target-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/include/x86_64-unknown-linux-gnu/c++/v1 --libcxx-library-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./lib/x86_64-unknown-linux-gnu --triple x86_64-unknown-linux-gnu --build-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build --lldb-module-cache-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build/module-cache-lldb/lldb-api --clang-module-cache-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build/module-cache-clang/lldb-api --executable /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin/lldb --lldb-python-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/local/lib/python3.12/dist-packages --compiler /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin/clang --dsymutil /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin --lldb-obj-root /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lldb --lldb-libs-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./lib --cmake-build-type Release /home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/test/API/commands/process/attach -p TestProcessAttach.py
--
Exit Code: 1

Command Output (stdout):
--
Skipping the following test categories: msvcstl, dsym, pdb, gmodules, debugserver, objc

--
Command Output (stderr):
--
FAIL: LLDB (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-x86_64) :: test_attach_to_process_by_id (TestProcessAttach.ProcessAttachTestCase.test_attach_to_process_by_id)
Log Files:
 - /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build/commands/process/attach/TestProcessAttach.test_attach_to_process_by_id/Failure_test_attach_to_process_by_id.log
PASS: LLDB (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-x86_64) :: test_attach_to_process_by_id_autocontinue (TestProcessAttach.ProcessAttachTestCase.test_attach_to_process_by_id_autocontinue)
PASS: LLDB (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-x86_64) :: test_attach_to_process_by_id_correct_executable_offset (TestProcessAttach.ProcessAttachTestCase.test_attach_to_process_by_id_correct_executable_offset)
PASS: LLDB (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-x86_64) :: test_attach_to_process_by_name (TestProcessAttach.ProcessAttachTestCase.test_attach_to_process_by_name)
PASS: LLDB (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-x86_64) :: test_attach_to_process_from_different_dir_by_id (TestProcessAttach.ProcessAttachTestCase.test_attach_to_process_from_different_dir_by_id)
======================================================================
FAIL: test_attach_to_process_by_id (TestProcessAttach.ProcessAttachTestCase.test_attach_to_process_by_id)
   Test attach by process id
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/packages/Python/lldbsuite/test/decorators.py", line 220, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/test/API/commands/process/attach/TestProcessAttach.py", line 36, in test_attach_to_process_by_id
    self.runCmd("process attach -p " + str(popen.pid))
  File "/home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1114, in runCmd
    self.assertTrue(self.res.Succeeded(), msg + output)
AssertionError: False is not true : Command 'process attach -p 1654275' did not return successfully
Error output:
error: attach failed: The current value of ptrace_scope is 1, which can cause ptrace to fail to attach to a running process. To fix this, run:
	sudo sysctl -w kernel.yama.ptrace_scope=0
For more information, see: https://www.kernel.org/doc/Documentation/security/Yama.txt.

Config=x86_64-/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang
----------------------------------------------------------------------
Ran 5 tests in 2.819s

FAILED (failures=1)

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@Lukacma
Lukacma merged commit 18a4975 into llvm:main Jul 30, 2026
11 of 12 checks passed
jgreenbaum pushed a commit to jgreenbaum/llvm-project that referenced this pull request Aug 3, 2026
…lt (llvm#210275)

- 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 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants