[llvm] [GlobalISel] Emit G_BITCAST for same-size scalar reassembly (PR #200946)

Michal Paszkowski via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 15 09:47:03 PDT 2026


https://github.com/michalpaszkowski updated https://github.com/llvm/llvm-project/pull/200946

>From 52a9409a93669a91608443319c8012096adab0c3 Mon Sep 17 00:00:00 2001
From: Michal Paszkowski <michal at michalpaszkowski.com>
Date: Mon, 1 Jun 2026 14:14:56 -0700
Subject: [PATCH 1/2] [GlobalISel] Emit G_BITCAST for same-size scalar
 reassembly

In LegalizationArtifactCombiner, when a G_MERGE_VALUES reassembles all
results from a G_UNMERGE_VALUES back into a same-sized scalar of a
different LLT flavor (for example i32 -> f32 with extended LLT), fold to
G_BITCAST instead of falling through and creating a degenerate
single-result G_UNMERGE_VALUES.

This fixes an artifact-combiner miscompile that is observable on targets
using extended LLT where same-width integer and floating scalar types
are distinct.
---
 .../GlobalISel/LegalizationArtifactCombiner.h | 26 ++++++++++
 .../GlobalISel/bitcast-v32i1-to-float.mir     | 50 +++++++++++++++++++
 2 files changed, 76 insertions(+)
 create mode 100644 llvm/test/CodeGen/AArch64/GlobalISel/bitcast-v32i1-to-float.mir

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h b/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
index d8d7ccc0bd7a7..2c94363947897 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
@@ -1005,6 +1005,32 @@ class LegalizationArtifactCombiner {
         return true;
       }
 
+      // With extended LLT, same-sized scalar types like f32 and i32 are
+      // distinct (unlike the default where both are s32). When the merge
+      // reassembles all unmerge outputs back into a same-sized scalar of a
+      // different type flavor, emit a G_BITCAST instead of falling through
+      // to the next case which would create a degenerate single-result
+      // G_UNMERGE_VALUES.
+      //
+      // %0:_(i1), %1, ... = G_UNMERGE_VALUES %UnmergeSrc:_(i32)
+      // %Dst:_(f32) = G_MERGE_VALUES %0:_(i1), %1, ...
+      //
+      // %Dst:_(f32) = G_BITCAST %UnmergeSrc:_(i32)
+      if ((DstTy != UnmergeSrcTy) &&
+          (DstTy.getSizeInBits() == UnmergeSrcTy.getSizeInBits()) &&
+          !DstTy.isVector() && !UnmergeSrcTy.isVector() &&
+          (Elt0UnmergeIdx == 0)) {
+        if (!isSequenceFromUnmerge(MI, 0, Unmerge, 0, NumMIElts, EltSize,
+                                   /*AllowUndef=*/false))
+          return false;
+
+        MIB.setInstrAndDebugLoc(MI);
+        MIB.buildBitcast(Dst, UnmergeSrc);
+        UpdatedDefs.push_back(Dst);
+        DeadInsts.push_back(&MI);
+        return true;
+      }
+
       // Recognize UnmergeSrc that can be unmerged to DstTy directly.
       // Types have to be either both vector or both non-vector types.
       // In case of vector types, the scalar elements need to match.
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/bitcast-v32i1-to-float.mir b/llvm/test/CodeGen/AArch64/GlobalISel/bitcast-v32i1-to-float.mir
new file mode 100644
index 0000000000000..094253da3616f
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/bitcast-v32i1-to-float.mir
@@ -0,0 +1,50 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -o - -mtriple=aarch64 -run-pass=legalizer %s | FileCheck %s
+
+# A G_MERGE_VALUES that reassembles all outputs of a G_UNMERGE_VALUES into
+# a same-sized scalar of a different LLT flavor (here, f32 from an i32
+# source) used to produce a degenerate single-result G_UNMERGE_VALUES in
+# the artifact combiner.  With extended LLT enabled on this target, i32
+# and f32 are distinct LLTs, so this miscompile is observable in MIR.
+# The fix in LegalizationArtifactCombiner.h emits a G_BITCAST instead.
+
+---
+name:            test_bitcast_v32i1_to_float
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $w0
+    ; CHECK-LABEL: name: test_bitcast_v32i1_to_float
+    ; CHECK: liveins: $w0
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(i32) = COPY $w0
+    ; CHECK-NEXT: [[BITCAST:%[0-9]+]]:_(f32) = G_BITCAST [[COPY]](i32)
+    ; CHECK-NEXT: $s0 = COPY [[BITCAST]](f32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $s0
+    %0:_(i32) = COPY $w0
+    %1:_(i1), %2:_(i1), %3:_(i1), %4:_(i1), %5:_(i1), %6:_(i1), %7:_(i1), %8:_(i1), %9:_(i1), %10:_(i1), %11:_(i1), %12:_(i1), %13:_(i1), %14:_(i1), %15:_(i1), %16:_(i1), %17:_(i1), %18:_(i1), %19:_(i1), %20:_(i1), %21:_(i1), %22:_(i1), %23:_(i1), %24:_(i1), %25:_(i1), %26:_(i1), %27:_(i1), %28:_(i1), %29:_(i1), %30:_(i1), %31:_(i1), %32:_(i1) = G_UNMERGE_VALUES %0(i32)
+    %33:_(f32) = G_MERGE_VALUES %1(i1), %2(i1), %3(i1), %4(i1), %5(i1), %6(i1), %7(i1), %8(i1), %9(i1), %10(i1), %11(i1), %12(i1), %13(i1), %14(i1), %15(i1), %16(i1), %17(i1), %18(i1), %19(i1), %20(i1), %21(i1), %22(i1), %23(i1), %24(i1), %25(i1), %26(i1), %27(i1), %28(i1), %29(i1), %30(i1), %31(i1), %32(i1)
+    $s0 = COPY %33(f32)
+    RET_ReallyLR implicit $s0
+...
+
+# Same shape but with the i64/<64 x i1>/double variant.
+---
+name:            test_bitcast_v64i1_to_double
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $x0
+    ; CHECK-LABEL: name: test_bitcast_v64i1_to_double
+    ; CHECK: liveins: $x0
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(i64) = COPY $x0
+    ; CHECK-NEXT: [[BITCAST:%[0-9]+]]:_(f64) = G_BITCAST [[COPY]](i64)
+    ; CHECK-NEXT: $d0 = COPY [[BITCAST]](f64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $d0
+    %0:_(i64) = COPY $x0
+    %1:_(i1), %2:_(i1), %3:_(i1), %4:_(i1), %5:_(i1), %6:_(i1), %7:_(i1), %8:_(i1), %9:_(i1), %10:_(i1), %11:_(i1), %12:_(i1), %13:_(i1), %14:_(i1), %15:_(i1), %16:_(i1), %17:_(i1), %18:_(i1), %19:_(i1), %20:_(i1), %21:_(i1), %22:_(i1), %23:_(i1), %24:_(i1), %25:_(i1), %26:_(i1), %27:_(i1), %28:_(i1), %29:_(i1), %30:_(i1), %31:_(i1), %32:_(i1), %33:_(i1), %34:_(i1), %35:_(i1), %36:_(i1), %37:_(i1), %38:_(i1), %39:_(i1), %40:_(i1), %41:_(i1), %42:_(i1), %43:_(i1), %44:_(i1), %45:_(i1), %46:_(i1), %47:_(i1), %48:_(i1), %49:_(i1), %50:_(i1), %51:_(i1), %52:_(i1), %53:_(i1), %54:_(i1), %55:_(i1), %56:_(i1), %57:_(i1), %58:_(i1), %59:_(i1), %60:_(i1), %61:_(i1), %62:_(i1), %63:_(i1), %64:_(i1) = G_UNMERGE_VALUES %0(i64)
+    %65:_(f64) = G_MERGE_VALUES %1(i1), %2(i1), %3(i1), %4(i1), %5(i1), %6(i1), %7(i1), %8(i1), %9(i1), %10(i1), %11(i1), %12(i1), %13(i1), %14(i1), %15(i1), %16(i1), %17(i1), %18(i1), %19(i1), %20(i1), %21(i1), %22(i1), %23(i1), %24(i1), %25(i1), %26(i1), %27(i1), %28(i1), %29(i1), %30(i1), %31(i1), %32(i1), %33(i1), %34(i1), %35(i1), %36(i1), %37(i1), %38(i1), %39(i1), %40(i1), %41(i1), %42(i1), %43(i1), %44(i1), %45(i1), %46(i1), %47(i1), %48(i1), %49(i1), %50(i1), %51(i1), %52(i1), %53(i1), %54(i1), %55(i1), %56(i1), %57(i1), %58(i1), %59(i1), %60(i1), %61(i1), %62(i1), %63(i1), %64(i1)
+    $d0 = COPY %65(f64)
+    RET_ReallyLR implicit $d0
+...

>From b281f37430dbe9a6b7ae42121148936a4ad18286 Mon Sep 17 00:00:00 2001
From: Michal Paszkowski <michal at michalpaszkowski.com>
Date: Mon, 13 Jul 2026 12:59:08 -0700
Subject: [PATCH 2/2] Add TODO

---
 .../llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h      | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h b/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
index 2c94363947897..b3eb0b9e9373a 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
@@ -1011,6 +1011,8 @@ class LegalizationArtifactCombiner {
       // different type flavor, emit a G_BITCAST instead of falling through
       // to the next case which would create a degenerate single-result
       // G_UNMERGE_VALUES.
+      // TODO: Remove this once generic G_MERGE_VALUES reassembly is tightened
+      // to require an explicit G_BITCAST for same-size scalar type changes.
       //
       // %0:_(i1), %1, ... = G_UNMERGE_VALUES %UnmergeSrc:_(i32)
       // %Dst:_(f32) = G_MERGE_VALUES %0:_(i1), %1, ...



More information about the llvm-commits mailing list