[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