[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