[llvm] [AMDGPU] Fix si-pre-emit-peephole to preserve S_AND when SCC is alive (PR #215829)

Frederik Harwath via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 13 02:14:49 PDT 2026


https://github.com/frederik-h updated https://github.com/llvm/llvm-project/pull/215829

>From 4b018dd1053722c9cc056ec91da77d8b500ac4b3 Mon Sep 17 00:00:00 2001
From: Frederik Harwath <fharwath at amd.com>
Date: Wed, 12 Aug 2026 09:08:09 -0400
Subject: [PATCH 1/3] [AMDGPU] Add test showing SCC liveness bug in
 si-pre-emit-peephole

si-pre-emit-peephole incorrectly removes S_AND_B64 instructions in VCC
branch optimization, without considering that the SCC value may be
needed by subsequent instructions like S_CSELECT.
---
 .../CodeGen/AMDGPU/insert-skip-from-vcc.mir   | 19 ++++
 ...eemit-peephole-scc-liveness-issue215745.ll | 86 +++++++++++++++++++
 2 files changed, 105 insertions(+)
 create mode 100644 llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll

diff --git a/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir b/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir
index 27f84ca8e3b5c..ac731c283e610 100644
--- a/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir
+++ b/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir
@@ -708,3 +708,22 @@ body:             |
     S_CBRANCH_VCCZ %bb.1, implicit killed $vcc
     S_ENDPGM 0
 ...
+
+---
+# GCN-LABEL: name: and_cmp_vccnz_live_scc
+# GCN: V_CMP_GT_I64_e32 $sgpr0_sgpr1, $vgpr0_vgpr1, implicit-def $vcc, implicit $exec
+# GCN-NOT: S_AND_
+# GCN-NEXT: S_CBRANCH_VCCNZ %bb.1, implicit $vcc
+# GCN: $sgpr0 = S_CSELECT_B32 $sgpr2, $sgpr3, implicit $scc
+name:            and_cmp_vccnz_live_scc
+body:             |
+  bb.0:
+    liveins: $sgpr0_sgpr1, $sgpr2, $sgpr3, $vgpr0_vgpr1
+    V_CMP_GT_I64_e32 $sgpr0_sgpr1, $vgpr0_vgpr1, implicit-def $vcc, implicit $exec
+    $vcc = S_AND_B64 $exec, $vcc, implicit-def $scc
+    S_CBRANCH_VCCNZ %bb.1, implicit $vcc
+
+  bb.1:
+    $sgpr0 = S_CSELECT_B32 $sgpr2, $sgpr3, implicit $scc
+    S_ENDPGM 0
+...
diff --git a/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll b/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
new file mode 100644
index 0000000000000..7336b944ecfa4
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
@@ -0,0 +1,86 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx942 < %s | FileCheck -check-prefix=GFX942 %s
+; RUN: llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx1100 < %s | FileCheck -check-prefix=GFX1100 %s
+
+; Test that si-pre-emit-peephole does not remove S_AND_B64 when its implicit
+; SCC definition has a user. This is a reduced version of the reproducer from issue
+; #215745.
+
+define amdgpu_kernel void @triangular_loop_scc_liveness(i64 %n) {
+; GFX942-LABEL: triangular_loop_scc_liveness:
+; GFX942:       ; %bb.0: ; %entry
+; GFX942-NEXT:    s_load_dwordx2 s[2:3], s[4:5], 0x0
+; GFX942-NEXT:    s_mov_b64 s[4:5], 1
+; GFX942-NEXT:    s_and_b64 s[0:1], exec, 0
+; GFX942-NEXT:  .LBB0_1: ; %outer
+; GFX942-NEXT:    ; =>This Loop Header: Depth=1
+; GFX942-NEXT:    ; Child Loop BB0_3 Depth 2
+; GFX942-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX942-NEXT:    v_mov_b64_e32 v[0:1], s[2:3]
+; GFX942-NEXT:    v_cmp_gt_i64_e32 vcc, s[4:5], v[0:1]
+; GFX942-NEXT:    s_mov_b64 s[4:5], 0
+; GFX942-NEXT:    s_cbranch_vccnz .LBB0_1
+; GFX942-NEXT:  ; %bb.2: ; %inner.preheader
+; GFX942-NEXT:    ; in Loop: Header=BB0_1 Depth=1
+; GFX942-NEXT:    s_cselect_b32 s5, 0, s3
+; GFX942-NEXT:    s_cselect_b32 s4, 0, s2
+; GFX942-NEXT:    s_cmp_eq_u64 s[4:5], 0
+; GFX942-NEXT:    s_cselect_b64 s[4:5], -1, 0
+; GFX942-NEXT:    s_and_b64 vcc, exec, s[4:5]
+; GFX942-NEXT:  .LBB0_3: ; %TransitionBlock
+; GFX942-NEXT:    ; Parent Loop BB0_1 Depth=1
+; GFX942-NEXT:    ; => This Inner Loop Header: Depth=2
+; GFX942-NEXT:    s_mov_b64 vcc, vcc
+; GFX942-NEXT:    s_cbranch_vccz .LBB0_3
+; GFX942-NEXT:  ; %bb.4: ; %loop.exit.guard
+; GFX942-NEXT:    ; in Loop: Header=BB0_1 Depth=1
+; GFX942-NEXT:    s_mov_b64 s[4:5], 0
+; GFX942-NEXT:    s_mov_b64 vcc, s[0:1]
+; GFX942-NEXT:    s_cbranch_vccz .LBB0_1
+; GFX942-NEXT:  ; %bb.5: ; %DummyReturnBlock
+; GFX942-NEXT:    s_endpgm
+;
+; GFX1100-LABEL: triangular_loop_scc_liveness:
+; GFX1100:       ; %bb.0: ; %entry
+; GFX1100-NEXT:    s_load_b64 s[0:1], s[4:5], 0x0
+; GFX1100-NEXT:    s_mov_b64 s[2:3], 1
+; GFX1100-NEXT:  .LBB0_1: ; %outer
+; GFX1100-NEXT:    ; =>This Loop Header: Depth=1
+; GFX1100-NEXT:    ; Child Loop BB0_3 Depth 2
+; GFX1100-NEXT:    s_waitcnt lgkmcnt(0)
+; GFX1100-NEXT:    v_cmp_gt_i64_e64 s2, s[2:3], s[0:1]
+; GFX1100-NEXT:    s_and_b32 vcc_lo, exec_lo, s2
+; GFX1100-NEXT:    s_mov_b64 s[2:3], 0
+; GFX1100-NEXT:    s_cbranch_vccnz .LBB0_1
+; GFX1100-NEXT:  ; %bb.2: ; %inner.preheader
+; GFX1100-NEXT:    ; in Loop: Header=BB0_1 Depth=1
+; GFX1100-NEXT:    s_cselect_b32 s3, 0, s1
+; GFX1100-NEXT:    s_cselect_b32 s2, 0, s0
+; GFX1100-NEXT:    s_delay_alu instid0(SALU_CYCLE_1) | instskip(SKIP_1) | instid1(SALU_CYCLE_1)
+; GFX1100-NEXT:    s_cmp_eq_u64 s[2:3], 0
+; GFX1100-NEXT:    s_cselect_b32 s2, -1, 0
+; GFX1100-NEXT:    s_and_b32 vcc_lo, exec_lo, s2
+; GFX1100-NEXT:  .LBB0_3: ; %TransitionBlock
+; GFX1100-NEXT:    ; Parent Loop BB0_1 Depth=1
+; GFX1100-NEXT:    ; => This Inner Loop Header: Depth=2
+; GFX1100-NEXT:    s_cbranch_vccz .LBB0_3
+; GFX1100-NEXT:  ; %bb.4: ; %loop.exit.guard
+; GFX1100-NEXT:    ; in Loop: Header=BB0_1 Depth=1
+; GFX1100-NEXT:    s_mov_b64 s[2:3], 0
+; GFX1100-NEXT:    s_mov_b32 vcc_lo, 0
+; GFX1100-NEXT:    s_cbranch_vccz .LBB0_1
+; GFX1100-NEXT:  ; %bb.5: ; %DummyReturnBlock
+; GFX1100-NEXT:    s_endpgm
+entry:
+  br label %outer
+
+outer:                                            ; preds = %inner, %outer, %entry
+  %k = phi i64 [ 1, %entry ], [ 0, %inner ], [ 0, %outer ]
+  %kgtn = icmp sgt i64 %k, %n
+  %bound = select i1 %kgtn, i64 0, i64 %n
+  br i1 %kgtn, label %outer, label %inner
+
+inner:                                            ; preds = %inner, %outer
+  %done = icmp eq i64 0, %bound
+  br i1 %done, label %outer, label %inner
+}

>From 6333dc9f8cb40f4eb5d75675df8d222c8897375f Mon Sep 17 00:00:00 2001
From: Frederik Harwath <fharwath at amd.com>
Date: Wed, 12 Aug 2026 09:50:06 -0400
Subject: [PATCH 2/3] [AMDGPU] Fix si-pre-emit-peephole to preserve S_AND when
 SCC is alive

The optimization in si-pre-emit-peephole that removes S_AND_B64 after
V_CMP instructions was not checking if the implicit SCC def was alive.
This caused miscompiles when the SCC value was used by subsequent instructions.

Fixes #215745
---
 llvm/lib/Target/AMDGPU/SIPreEmitPeephole.cpp                | 6 ++++--
 llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir           | 2 +-
 .../AMDGPU/preemit-peephole-scc-liveness-issue215745.ll     | 1 +
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/SIPreEmitPeephole.cpp b/llvm/lib/Target/AMDGPU/SIPreEmitPeephole.cpp
index e43e753e1314d..45b9dd3df39b9 100644
--- a/llvm/lib/Target/AMDGPU/SIPreEmitPeephole.cpp
+++ b/llvm/lib/Target/AMDGPU/SIPreEmitPeephole.cpp
@@ -237,14 +237,16 @@ bool SIPreEmitPeephole::optimizeVccBranch(MachineInstr &MI) const {
     if (M == E)
       return Changed;
     // If SReg is VCC and SReg definition is a VALU comparison.
-    // This means S_AND with EXEC is not required.
+    // This means S_AND with EXEC is not required, unless
+    // the implicit def of SCC is alive.
     // Erase the S_AND and return.
     // Note: isVOPC is used instead of isCompare to catch V_CMP_CLASS
     if (A->getOpcode() == And && SReg == CondReg && !ModifiesExec &&
-        TII->isVOPC(*M)) {
+        TII->isVOPC(*M) && A->registerDefIsDead(AMDGPU::SCC, /*TRI=*/nullptr)) {
       A->eraseFromParent();
       return true;
     }
+
     if (!M->isMoveImmediate() || !M->getOperand(1).isImm() ||
         (M->getOperand(1).getImm() != -1 && M->getOperand(1).getImm() != 0))
       return Changed;
diff --git a/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir b/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir
index ac731c283e610..3081a2c1d1fe1 100644
--- a/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir
+++ b/llvm/test/CodeGen/AMDGPU/insert-skip-from-vcc.mir
@@ -712,7 +712,7 @@ body:             |
 ---
 # GCN-LABEL: name: and_cmp_vccnz_live_scc
 # GCN: V_CMP_GT_I64_e32 $sgpr0_sgpr1, $vgpr0_vgpr1, implicit-def $vcc, implicit $exec
-# GCN-NOT: S_AND_
+# GCN-NEXT: $vcc = S_AND_B64 $exec, $vcc, implicit-def $scc
 # GCN-NEXT: S_CBRANCH_VCCNZ %bb.1, implicit $vcc
 # GCN: $sgpr0 = S_CSELECT_B32 $sgpr2, $sgpr3, implicit $scc
 name:            and_cmp_vccnz_live_scc
diff --git a/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll b/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
index 7336b944ecfa4..7a24ee933c173 100644
--- a/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
+++ b/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
@@ -19,6 +19,7 @@ define amdgpu_kernel void @triangular_loop_scc_liveness(i64 %n) {
 ; GFX942-NEXT:    v_mov_b64_e32 v[0:1], s[2:3]
 ; GFX942-NEXT:    v_cmp_gt_i64_e32 vcc, s[4:5], v[0:1]
 ; GFX942-NEXT:    s_mov_b64 s[4:5], 0
+; GFX942-NEXT:    s_and_b64 vcc, exec, vcc
 ; GFX942-NEXT:    s_cbranch_vccnz .LBB0_1
 ; GFX942-NEXT:  ; %bb.2: ; %inner.preheader
 ; GFX942-NEXT:    ; in Loop: Header=BB0_1 Depth=1

>From 77f369fc362bfd5ef0809a5444e786f9cde52057 Mon Sep 17 00:00:00 2001
From: Frederik Harwath <fharwath at amd.com>
Date: Thu, 13 Aug 2026 05:13:55 -0400
Subject: [PATCH 3/3] Use new subarch triples

---
 .../AMDGPU/preemit-peephole-scc-liveness-issue215745.ll       | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll b/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
index 7a24ee933c173..09174fcfc3f35 100644
--- a/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
+++ b/llvm/test/CodeGen/AMDGPU/preemit-peephole-scc-liveness-issue215745.ll
@@ -1,6 +1,6 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
-; RUN: llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx942 < %s | FileCheck -check-prefix=GFX942 %s
-; RUN: llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx1100 < %s | FileCheck -check-prefix=GFX1100 %s
+; RUN: llc -mtriple=amdgpu9.42-amd-amdhsa < %s | FileCheck -check-prefix=GFX942 %s
+; RUN: llc -mtriple=amdgpu11.00-amd-amdhsa < %s | FileCheck -check-prefix=GFX1100 %s
 
 ; Test that si-pre-emit-peephole does not remove S_AND_B64 when its implicit
 ; SCC definition has a user. This is a reduced version of the reproducer from issue



More information about the llvm-commits mailing list