[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:50:29 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-globalisel

Author: adeshcom14

<details>
<summary>Changes</summary>

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

---
Full diff: https://github.com/llvm/llvm-project/pull/225138.diff


4 Files Affected:

- (modified) llvm/lib/Target/AMDGPU/AMDGPUCombine.td (+27-8) 
- (modified) llvm/lib/Target/AMDGPU/AMDGPURegBankCombiner.cpp (+22) 
- (added) llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.ll (+57) 
- (added) llvm/test/CodeGen/AMDGPU/GlobalISel/same-val-zero-regbank-aware.mir (+65) 


``````````diff
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
+...

``````````

</details>


https://github.com/llvm/llvm-project/pull/225138


More information about the llvm-commits mailing list