[llvm] [AMDGPU][GISel] Add regbank-aware same_val_zero fold to AMDGPURegBankCombiner (PR #225138)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 21 09:49:51 PDT 2026
https://github.com/adeshcom14 created https://github.com/llvm/llvm-project/pull/225138
AMDGPURegBankCombiner inherits the generic same_val_zero combine (x xor x / x - x → 0), which builds the 0 into a fresh bank-less register. That's fine before RegBankSelect, but this combiner runs after it, where every vreg needs a bank, so InstructionSelect later fails.
This patch adds same_val_zero_regbank_aware, which does the same fold but builds 0 directly into $dst reusing its existing bank. It also drops plain same_val_zero from just this combiner's rule list, every other combiner keeps using it unchanged.
Fixes: LCOMPILER-2714
Assisted by: Claude Sonnet
>From a4527f5cf0f4df18ad8ab08ee15746642c3a7100 Mon Sep 17 00:00:00 2001
From: Adesh Adikane <aadikane at amd.com>
Date: Mon, 21 Sep 2026 16:26:36 +0000
Subject: [PATCH] Add regbank-aware same_val_zero fold to AMDGPURegBankCombiner
---
llvm/lib/Target/AMDGPU/AMDGPUCombine.td | 35 +++++++---
.../Target/AMDGPU/AMDGPURegBankCombiner.cpp | 22 +++++++
.../GlobalISel/same-val-zero-regbank-aware.ll | 57 ++++++++++++++++
.../same-val-zero-regbank-aware.mir | 65 +++++++++++++++++++
4 files changed, 171 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.ll
create mode 100644 llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.mir
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUCombine.td b/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
index 2f0ff66668f2b..1be7b38db94f4 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPUCombine.td
@@ -204,6 +204,17 @@ def zext_of_shift_amount_combines : GICombineGroup<[
canonicalize_zext_lshr, canonicalize_zext_ashr, canonicalize_zext_shl
]>;
+// Fold (xor x, x) -> 0 and (sub x, x) -> 0.
+//
+// Like same_val_zero, but builds the 0 straight into $dst instead of a new
+// temp reg + COPY. We're past RegBankSelect here, so a new temp reg would
+// have no register bank and fail selection later.
+def same_val_zero_regbank_aware : GICombineRule<
+ (defs root:$dst, build_fn_matchinfo:$matchinfo),
+ (match (wip_match_opcode G_XOR, G_SUB):$dst,
+ [{ return matchSameValZeroRegBankAware(*${dst}, ${matchinfo}); }]),
+ (apply [{ Helper.applyBuildFn(*${dst}, ${matchinfo}); }])>;
+
def minmax3_matchdata : GIDefMatchData<"MinMaxToMinMax3MatchInfo">;
class minmax_to_minmax3_opcodes<Instruction minmaxOpcode> : GICombineRule<
@@ -329,14 +340,22 @@ def AMDGPUPostLegalizerCombiner: GICombiner<
let CombineAllMethodName = "tryCombineAllImpl";
}
+// identity_combines, minus same_val_zero (replaced by
+// same_val_zero_regbank_aware above).
+defvar AMDGPURegBankIdentityCombines =
+ !listremove(identity_combines.Rules, [same_val_zero]);
+
def AMDGPURegBankCombiner : GICombiner<
"AMDGPURegBankCombinerImpl",
- [unmerge_merge, unmerge_cst, unmerge_undef,
- zext_trunc_fold, int_minmax_to_med3, ptr_add_immed_chain,
- fp_minmax_to_clamp, fp_minmax_to_med3, fmed3_intrinsic_to_clamp,
- identity_combines, redundant_and, constant_fold_cast_op,
- cast_of_cast_combines, sext_trunc, zext_of_shift_amount_combines,
- d16_load, smax_to_minmax3, smin_to_minmax3, umax_to_minmax3,
- umin_to_minmax3, fmax_to_minmax3, fmin_to_minmax3, fmaximum_to_minmax3,
- fminimum_to_minmax3, fmax_ieee_to_minmax3, fmin_ieee_to_minmax3]> {
+ !listconcat(
+ [unmerge_merge, unmerge_cst, unmerge_undef,
+ zext_trunc_fold, int_minmax_to_med3, ptr_add_immed_chain,
+ fp_minmax_to_clamp, fp_minmax_to_med3, fmed3_intrinsic_to_clamp,
+ same_val_zero_regbank_aware],
+ AMDGPURegBankIdentityCombines,
+ [redundant_and, constant_fold_cast_op,
+ cast_of_cast_combines, sext_trunc, zext_of_shift_amount_combines,
+ d16_load, smax_to_minmax3, smin_to_minmax3, umax_to_minmax3,
+ umin_to_minmax3, fmax_to_minmax3, fmin_to_minmax3, fmaximum_to_minmax3,
+ fminimum_to_minmax3, fmax_ieee_to_minmax3, fmin_ieee_to_minmax3])> {
}
diff --git a/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp b/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp
index 790c887950245..c533340eb971e 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp
@@ -95,6 +95,10 @@ class AMDGPURegBankCombinerImpl : public Combiner {
void applyCanonicalizeZextShiftAmt(MachineInstr &MI, MachineInstr &Ext) const;
+ bool matchSameValZeroRegBankAware(
+ MachineInstr &MI,
+ std::function<void(MachineIRBuilder &)> &MatchInfo) const;
+
bool combineD16Load(MachineInstr &MI) const;
bool applyD16Load(unsigned D16Opc, MachineInstr &DstMI,
MachineInstr *SmallLoad, Register ToOverwriteD16) const;
@@ -419,6 +423,24 @@ void AMDGPURegBankCombinerImpl::applyCanonicalizeZextShiftAmt(
MI.eraseFromParent();
}
+bool AMDGPURegBankCombinerImpl::matchSameValZeroRegBankAware(
+ MachineInstr &MI,
+ std::function<void(MachineIRBuilder &)> &MatchInfo) const {
+ assert(MI.getOpcode() == AMDGPU::G_XOR || MI.getOpcode() == AMDGPU::G_SUB);
+
+ Register Dst = MI.getOperand(0).getReg();
+ Register LHS = MI.getOperand(1).getReg();
+ Register RHS = MI.getOperand(2).getReg();
+ // Ignore copies, same as same_val_zero does.
+ if (getSrcRegIgnoringCopies(LHS, MRI) != getSrcRegIgnoringCopies(RHS, MRI))
+ return false;
+
+ // Dst already has a register bank, so build the 0 there instead of a new
+ // register.
+ MatchInfo = [Dst](MachineIRBuilder &B) { B.buildConstant(Dst, 0); };
+ return true;
+}
+
bool AMDGPURegBankCombinerImpl::combineD16Load(MachineInstr &MI) const {
Register Dst;
MachineInstr *Load, *SextLoad;
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.ll
new file mode 100644
index 0000000000000..550f13c34f120
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.ll
@@ -0,0 +1,57 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -global-isel -mtriple=amdgpu9.0a-amd-amdhsa < %s | FileCheck -check-prefix=GFX90A %s
+; RUN: llc -global-isel -mtriple=amdgpu9.50-amd-amdhsa < %s | FileCheck -check-prefix=GFX950 %s
+
+; Regression test for a crash in AMDGPURegBankCombiner. Widening the two
+; zext i1/i32 -> i64 splits both produce the same zero constant for the
+; high 32 bits (CSE'd by unmerge_merge), turning the xor into an
+; `x xor x` that same_val_zero_regbank_aware must fold without leaving
+; behind a register with no assigned register bank.
+define amdgpu_kernel void @fuzz_kernel(ptr addrspace(1) %in) {
+; GFX90A-LABEL: fuzz_kernel:
+; GFX90A: ; %bb.0:
+; GFX90A-NEXT: s_load_dwordx2 s[0:1], s[8:9], 0x0
+; GFX90A-NEXT: v_and_b32_e32 v2, 0x3ff, v0
+; GFX90A-NEXT: v_lshlrev_b32_e32 v0, 3, v2
+; GFX90A-NEXT: v_mov_b32_e32 v3, 0
+; GFX90A-NEXT: s_waitcnt lgkmcnt(0)
+; GFX90A-NEXT: global_load_dwordx2 v[0:1], v0, s[0:1]
+; GFX90A-NEXT: s_waitcnt vmcnt(0)
+; GFX90A-NEXT: v_cmp_lt_i64_e32 vcc, 0, v[0:1]
+; GFX90A-NEXT: v_cndmask_b32_e64 v0, 0, 1, vcc
+; GFX90A-NEXT: v_xor_b32_e32 v2, v0, v2
+; GFX90A-NEXT: v_pk_mov_b32 v[0:1], 0, 0
+; GFX90A-NEXT: global_store_dwordx2 v[0:1], v[2:3], off
+; GFX90A-NEXT: s_endpgm
+;
+; GFX950-LABEL: fuzz_kernel:
+; GFX950: ; %bb.0:
+; GFX950-NEXT: s_load_dwordx2 s[0:1], s[4:5], 0x0
+; GFX950-NEXT: v_and_b32_e32 v1, 0x3ff, v0
+; GFX950-NEXT: v_lshlrev_b32_e32 v1, 3, v1
+; GFX950-NEXT: s_waitcnt lgkmcnt(0)
+; GFX950-NEXT: global_load_dwordx2 v[2:3], v1, s[0:1]
+; GFX950-NEXT: s_movk_i32 s0, 0x3ff
+; GFX950-NEXT: v_mov_b32_e32 v1, 0
+; GFX950-NEXT: s_waitcnt vmcnt(0)
+; GFX950-NEXT: v_cmp_lt_i64_e32 vcc, 0, v[2:3]
+; GFX950-NEXT: s_nop 1
+; GFX950-NEXT: v_cndmask_b32_e64 v2, 0, 1, vcc
+; GFX950-NEXT: v_bitop3_b32 v0, v2, v0, s0 bitop3:0x78
+; GFX950-NEXT: v_mov_b64_e32 v[2:3], 0
+; GFX950-NEXT: global_store_dwordx2 v[2:3], v[0:1], off
+; GFX950-NEXT: s_endpgm
+ %tid = call i32 @llvm.amdgcn.workitem.id.x()
+ %in.ptr = getelementptr i64, ptr addrspace(1) %in, i32 %tid
+ %x = load i64, ptr addrspace(1) %in.ptr, align 8
+ %cmp = icmp slt i64 0, %x
+ %cmp.zext = zext i1 %cmp to i64
+ %tid.zext = zext i32 %tid to i64
+ %xor = xor i64 %cmp.zext, %tid.zext
+ store i64 %xor, ptr addrspace(1) null, align 8
+ ret void
+}
+
+declare i32 @llvm.amdgcn.workitem.id.x() #0
+
+attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.mir b/llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.mir
new file mode 100644
index 0000000000000..333736f80e062
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.mir
@@ -0,0 +1,65 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=amdgpu10.10-amd-mesa3d -run-pass=amdgpu-regbank-combiner -verify-machineinstrs %s -o - | FileCheck %s
+
+# Fold (xor x, x) / (sub x, x) -> 0 after RegBankSelect. The replacement
+# G_CONSTANT must keep x's register bank; it must not be built into a new,
+# bank-less register (which would later fail instruction selection).
+
+---
+name: xor_same_reg
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $vgpr0, $vgpr1_vgpr2
+ ; CHECK-LABEL: name: xor_same_reg
+ ; CHECK: liveins: $vgpr0, $vgpr1_vgpr2
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:vgpr(p1) = COPY $vgpr1_vgpr2
+ ; CHECK-NEXT: [[C:%[0-9]+]]:vgpr(s32) = G_CONSTANT i32 0
+ ; CHECK-NEXT: G_STORE [[C]](s32), [[COPY]](p1) :: (store (s32), addrspace 1)
+ ; CHECK-NEXT: S_ENDPGM 0
+ %0:vgpr(s32) = COPY $vgpr0
+ %1:vgpr(p1) = COPY $vgpr1_vgpr2
+ %2:vgpr(s32) = G_XOR %0, %0
+ G_STORE %2(s32), %1(p1) :: (store (s32), addrspace 1)
+ S_ENDPGM 0
+...
+---
+name: sub_same_reg
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $vgpr0, $vgpr1_vgpr2
+ ; CHECK-LABEL: name: sub_same_reg
+ ; CHECK: liveins: $vgpr0, $vgpr1_vgpr2
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:vgpr(p1) = COPY $vgpr1_vgpr2
+ ; CHECK-NEXT: [[C:%[0-9]+]]:vgpr(s32) = G_CONSTANT i32 0
+ ; CHECK-NEXT: G_STORE [[C]](s32), [[COPY]](p1) :: (store (s32), addrspace 1)
+ ; CHECK-NEXT: S_ENDPGM 0
+ %0:vgpr(s32) = COPY $vgpr0
+ %1:vgpr(p1) = COPY $vgpr1_vgpr2
+ %2:vgpr(s32) = G_SUB %0, %0
+ G_STORE %2(s32), %1(p1) :: (store (s32), addrspace 1)
+ S_ENDPGM 0
+...
+---
+name: xor_same_reg_through_copy
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $vgpr0, $vgpr1_vgpr2
+ ; CHECK-LABEL: name: xor_same_reg_through_copy
+ ; CHECK: liveins: $vgpr0, $vgpr1_vgpr2
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:vgpr(p1) = COPY $vgpr1_vgpr2
+ ; CHECK-NEXT: [[C:%[0-9]+]]:vgpr(s32) = G_CONSTANT i32 0
+ ; CHECK-NEXT: G_STORE [[C]](s32), [[COPY]](p1) :: (store (s32), addrspace 1)
+ ; CHECK-NEXT: S_ENDPGM 0
+ %0:vgpr(s32) = COPY $vgpr0
+ %1:vgpr(p1) = COPY $vgpr1_vgpr2
+ %2:vgpr(s32) = COPY %0
+ %3:vgpr(s32) = G_XOR %0, %2
+ G_STORE %3(s32), %1(p1) :: (store (s32), addrspace 1)
+ S_ENDPGM 0
+...
More information about the llvm-commits
mailing list