[llvm] [GlobalISel] Insert bitcast instead of register replacement when types don't match. (PR #177397)

Marcos Maronas via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 22 08:37:10 PST 2026


https://github.com/maarquitos14 created https://github.com/llvm/llvm-project/pull/177397

Cases like the newly added test with the vector types currently hit `Assertion `canReplaceReg(OldReg, Replacement, MRI) && \"Cannot replace register?\"' failed."` because source and destination registers have mismatching types. Apart from the assertion, it also fails when using `--verify-machineinstrs`. This PR adds a bitcast in those cases.


>From a325949edaa2cfa8c20a9662575b8c6bae1de943 Mon Sep 17 00:00:00 2001
From: Marcos Maronas <mmaronas at amd.com>
Date: Thu, 22 Jan 2026 10:09:46 -0600
Subject: [PATCH] [GlobalISel] Insert bitcast instead of register replacement
 when types don't match.

---
 llvm/include/llvm/CodeGen/GlobalISel/Utils.h  |  6 +++
 .../lib/CodeGen/GlobalISel/CombinerHelper.cpp | 31 +++++++++++-
 llvm/lib/CodeGen/GlobalISel/Utils.cpp         | 18 +++++++
 .../postlegalizer-combiner-merge.mir          | 47 +++++++++++++++++++
 4 files changed, 100 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/CodeGen/AMDGPU/GlobalISel/postlegalizer-combiner-merge.mir

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/Utils.h b/llvm/include/llvm/CodeGen/GlobalISel/Utils.h
index da2742e089f8f..6847e3751e487 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/Utils.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/Utils.h
@@ -147,6 +147,12 @@ LLVM_ABI bool constrainSelectedInstRegOperands(MachineInstr &I,
 LLVM_ABI bool canReplaceReg(Register DstReg, Register SrcReg,
                             MachineRegisterInfo &MRI);
 
+/// Check if DstReg can be replaced with SrcReg depending on the register
+/// constraints. Compared to `canReplaceReg`, this does not check types, so
+/// even for registers with different types it can return true.
+LLVM_ABI bool canReplaceRegNoTypeCheck(Register DstReg, Register SrcReg,
+                            MachineRegisterInfo &MRI);
+
 /// Check whether an instruction \p MI is dead: it only defines dead virtual
 /// registers, and doesn't have other side effects.
 LLVM_ABI bool isTriviallyDead(const MachineInstr &MI,
diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index 85d5f06b9813d..f2d649735a9bd 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -2969,8 +2969,35 @@ void CombinerHelper::replaceSingleDefInstWithReg(MachineInstr &MI,
                                                  Register Replacement) const {
   assert(MI.getNumExplicitDefs() == 1 && "Expected one explicit def?");
   Register OldReg = MI.getOperand(0).getReg();
-  assert(canReplaceReg(OldReg, Replacement, MRI) && "Cannot replace register?");
-  replaceRegWith(MRI, OldReg, Replacement);
+  bool canReplace = canReplaceReg(OldReg, Replacement, MRI);
+  if (canReplace) {
+    replaceRegWith(MRI, OldReg, Replacement);
+    MI.eraseFromParent();
+    return;
+  }
+
+  // `canReplaceReg` can fail because register types don't match, but they can
+  // be _compatible_ (e.g. <2 x s32> and s64). In fact, we expect such a
+  // scenario for some combiner opts (e.g. merge-unmerge), so we need to
+  // explicitly allow mismatched types, and manually check if they are
+  // _compatible_. `canReplaceRegNoTypeCheck` does exactly the same than
+  // `canReplaceReg`, but without type checking. If it still returns false, then
+  // it's something else, and we shouldn't proceed. If it returns true, then we
+  // go on to check type compatibility, and if that passes, we insert a bitcast.
+  assert(canReplaceRegNoTypeCheck(OldReg, Replacement, MRI) &&
+         "Cannot replace register?");
+  LLT SrcType = MRI.getType(OldReg);
+  LLT DstType = MRI.getType(Replacement);
+  // Check if G_BITCAST from SrcType to DstType is legal.
+  LLT BitcastTypes[] = {DstType, SrcType};
+  LegalityQuery Query(TargetOpcode::G_BITCAST, BitcastTypes);
+  // If types are not compatible, we shouldn't proceed.
+  assert(isLegalOrBeforeLegalizer(Query));
+
+  // Build the bitcast from OldReg to Replacement and insert it before MI.
+  Builder.setInstrAndDebugLoc(MI);
+  Builder.buildBitcast(OldReg, Replacement);
+
   MI.eraseFromParent();
 }
 
diff --git a/llvm/lib/CodeGen/GlobalISel/Utils.cpp b/llvm/lib/CodeGen/GlobalISel/Utils.cpp
index 658774ec3fcb9..41980b070ab89 100644
--- a/llvm/lib/CodeGen/GlobalISel/Utils.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/Utils.cpp
@@ -219,6 +219,24 @@ bool llvm::canReplaceReg(Register DstReg, Register SrcReg,
              *MRI.getRegClassOrNull(SrcReg));
 }
 
+bool llvm::canReplaceRegNoTypeCheck(Register DstReg, Register SrcReg,
+                                    MachineRegisterInfo &MRI) {
+  // Give up if either DstReg or SrcReg  is a physical register.
+  if (DstReg.isPhysical() || SrcReg.isPhysical())
+    return false;
+  // Replace if either DstReg has no constraints or the register
+  // constraints match.
+  const auto &DstRBC = MRI.getRegClassOrRegBank(DstReg);
+  if (!DstRBC || DstRBC == MRI.getRegClassOrRegBank(SrcReg))
+    return true;
+
+  // Otherwise match if the Src is already a regclass that is covered by the Dst
+  // RegBank.
+  return isa<const RegisterBank *>(DstRBC) && MRI.getRegClassOrNull(SrcReg) &&
+         cast<const RegisterBank *>(DstRBC)->covers(
+             *MRI.getRegClassOrNull(SrcReg));
+}
+
 bool llvm::isTriviallyDead(const MachineInstr &MI,
                            const MachineRegisterInfo &MRI) {
   // Instructions without side-effects are dead iff they only define dead regs.
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/postlegalizer-combiner-merge.mir b/llvm/test/CodeGen/AMDGPU/GlobalISel/postlegalizer-combiner-merge.mir
new file mode 100644
index 0000000000000..5df1ae681d9f0
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/postlegalizer-combiner-merge.mir
@@ -0,0 +1,47 @@
+# RUN: llc -mtriple amdgcn-amd-amdhsa -run-pass=amdgpu-postlegalizer-combiner -verify-machineinstrs %s -o - | FileCheck %s
+
+---
+name:            merge_unmerge
+alignment:       4
+legalized:       true
+liveins:
+body:             |
+  bb.1.entry:
+      liveins: $vgpr0_vgpr1
+
+        ; CHECK-LABEL: name: merge_unmerge
+        ; CHECK: liveins: $vgpr0_vgpr1
+        ; CHECK-NEXT: {{  $}}
+        ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(s64) = COPY $vgpr0_vgpr1
+        ; CHECK-NEXT: $vgpr0_vgpr1 = COPY [[COPY]](s64)
+        ; CHECK-NEXT: SI_RETURN implicit $vgpr0_vgpr1
+        %0:_(s64) = COPY $vgpr0_vgpr1
+        %a:_(s32), %b:_(s32) = G_UNMERGE_VALUES %0
+        %merge:_(s64) = G_MERGE_VALUES %a, %b
+        $vgpr0_vgpr1 = COPY %merge(s64)
+        SI_RETURN implicit $vgpr0_vgpr1
+
+...
+---
+name:            merge_unmerge_vector_type
+alignment:       4
+legalized:       true
+liveins:
+body:             |
+  bb.1.entry:
+    liveins: $vgpr0_vgpr1
+
+    ; CHECK-LABEL: name: merge_unmerge
+    ; CHECK: liveins: $vgpr0_vgpr1
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(<2 x s32>) = COPY $vgpr0_vgpr1
+    ; CHECK-NEXT: [[BITCAST:%[a-z]+]]:_(s64) = G_BITCAST [[COPY]](<2 x s32>) 
+    ; CHECK-NEXT: $vgpr0_vgpr1 = COPY [[BITCAST]](s64)
+    ; CHECK-NEXT: SI_RETURN implicit $vgpr0_vgpr1
+    %0:_(<2 x s32>) = COPY $vgpr0_vgpr1
+    %a:_(s32), %b:_(s32) = G_UNMERGE_VALUES %0:_(<2 x s32>)
+    %merge:_(s64) = G_MERGE_VALUES %a:_(s32), %b:_(s32)
+    $vgpr0_vgpr1 = COPY %merge(s64)
+    SI_RETURN implicit $vgpr0_vgpr1
+
+...



More information about the llvm-commits mailing list