[llvm] [NFC][AMDGPU] Separate VOPD candidate selection (PR #223518)

Shilei Tian via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 16 16:54:12 PDT 2026


https://github.com/shiltian updated https://github.com/llvm/llvm-project/pull/223518

>From 3590b629385f545c7bb3ffeab2a8f2ec51954278 Mon Sep 17 00:00:00 2001
From: Shilei Tian <i at tianshilei.me>
Date: Mon, 14 Sep 2026 15:38:39 -0400
Subject: [PATCH] [NFC][AMDGPU] Separate VOPD candidate selection

This PR separates VOPD candidate matching from selection without changing
generated code.

GCNCreateVOPD used to choose a pair as soon as tryMatchVOPDPair found one.
That made matching and selection one operation: the first match consumed both
instructions before later work could decide whether the pair was usable.

We now record matches in program order, collect every adjacent candidate, and
select the same greedy non-overlapping set in a separate step.

This gives later changes a place to reject or rank a candidate without hiding
an overlapping pair that uses its second instruction.
---
 llvm/lib/Target/AMDGPU/GCNCreateVOPD.cpp | 75 ++++++++++++------------
 llvm/lib/Target/AMDGPU/GCNVOPDUtils.cpp  |  4 +-
 llvm/lib/Target/AMDGPU/GCNVOPDUtils.h    | 19 +++---
 3 files changed, 50 insertions(+), 48 deletions(-)

diff --git a/llvm/lib/Target/AMDGPU/GCNCreateVOPD.cpp b/llvm/lib/Target/AMDGPU/GCNCreateVOPD.cpp
index aedf71e7bfac1..015e862bab776 100644
--- a/llvm/lib/Target/AMDGPU/GCNCreateVOPD.cpp
+++ b/llvm/lib/Target/AMDGPU/GCNCreateVOPD.cpp
@@ -37,43 +37,30 @@ using namespace llvm;
 namespace {
 
 class GCNCreateVOPD {
-private:
-  class VOPDCombineInfo {
-  public:
-    VOPDCombineInfo() = default;
-    VOPDCombineInfo(MachineInstr *First, MachineInstr *Second,
-                    bool VOPD3 = false)
-        : FirstMI(First), SecondMI(Second), IsVOPD3(VOPD3) {}
-
-    MachineInstr *FirstMI;
-    MachineInstr *SecondMI;
-    bool IsVOPD3;
-  };
-
 public:
   const GCNSubtarget *ST = nullptr;
 
-  bool doReplace(const SIInstrInfo *SII, VOPDCombineInfo &CI) {
-    auto *FirstMI = CI.FirstMI;
-    auto *SecondMI = CI.SecondMI;
-    unsigned Opc1 = FirstMI->getOpcode();
-    unsigned Opc2 = SecondMI->getOpcode();
+  bool doReplace(const SIInstrInfo *SII, VOPDMatchInfo &Match) {
+    MachineInstr *MIX = Match.getMIX();
+    MachineInstr *MIY = Match.getMIY();
+    unsigned Opc1 = MIX->getOpcode();
+    unsigned Opc2 = MIY->getOpcode();
     unsigned EncodingFamily =
         AMDGPU::getVOPDEncodingFamily(SII->getSubtarget());
-    int NewOpcode = AMDGPU::getVOPDFull(AMDGPU::getVOPDOpcode(Opc1, CI.IsVOPD3),
-                                        AMDGPU::getVOPDOpcode(Opc2, CI.IsVOPD3),
-                                        EncodingFamily, CI.IsVOPD3);
+    int NewOpcode =
+        AMDGPU::getVOPDFull(AMDGPU::getVOPDOpcode(Opc1, Match.IsVOPD3),
+                            AMDGPU::getVOPDOpcode(Opc2, Match.IsVOPD3),
+                            EncodingFamily, Match.IsVOPD3);
     assert(NewOpcode != -1 &&
            "Should have previously determined this as a possible VOPD\n");
 
-    auto VOPDInst = BuildMI(*FirstMI->getParent(), FirstMI,
-                            FirstMI->getDebugLoc(), SII->get(NewOpcode))
-                        .setMIFlags(FirstMI->getFlags() | SecondMI->getFlags());
+    auto VOPDInst =
+        BuildMI(*MIX->getParent(), MIX, MIX->getDebugLoc(), SII->get(NewOpcode))
+            .setMIFlags(MIX->getFlags() | MIY->getFlags());
 
     namespace VOPD = AMDGPU::VOPD;
-    MachineInstr *MI[] = {FirstMI, SecondMI};
-    auto InstInfo =
-        AMDGPU::getVOPDInstInfo(FirstMI->getDesc(), SecondMI->getDesc());
+    MachineInstr *MI[] = {MIX, MIY};
+    auto InstInfo = AMDGPU::getVOPDInstInfo(MIX->getDesc(), MIY->getDesc());
 
     for (auto CompIdx : VOPD::COMPONENTS) {
       auto MCOprIdx = InstInfo[CompIdx].getIndexOfDstInMCOperands();
@@ -103,11 +90,12 @@ class GCNCreateVOPD {
             InstInfo[CompIdx].getIndexOfSrcInMCOperands(CompSrcIdx, IsVOP3);
         VOPDInst.add(MI[CompIdx]->getOperand(MCOprIdx));
       }
-      if (MI[CompIdx]->getOpcode() == AMDGPU::V_CNDMASK_B32_e32 && CI.IsVOPD3)
+      if (MI[CompIdx]->getOpcode() == AMDGPU::V_CNDMASK_B32_e32 &&
+          Match.IsVOPD3)
         VOPDInst.addReg(AMDGPU::VCC_LO);
     }
 
-    if (CI.IsVOPD3) {
+    if (Match.IsVOPD3) {
       if (unsigned BitOp2 = AMDGPU::getBitOp2(Opc2))
         VOPDInst.addImm(BitOp2);
     }
@@ -116,8 +104,8 @@ class GCNCreateVOPD {
     for (auto CompIdx : VOPD::COMPONENTS)
       VOPDInst.copyImplicitOps(*MI[CompIdx]);
 
-    LLVM_DEBUG(dbgs() << "VOPD Fused: " << *VOPDInst << " from\tX: "
-                      << *CI.FirstMI << "\tY: " << *CI.SecondMI << "\n");
+    LLVM_DEBUG(dbgs() << "VOPD Fused: " << *VOPDInst << " from\tX: " << *MIX
+                      << "\tY: " << *MIY << "\n");
 
     for (auto CompIdx : VOPD::COMPONENTS)
       MI[CompIdx]->eraseFromParent();
@@ -135,7 +123,7 @@ class GCNCreateVOPD {
     const SIInstrInfo *SII = ST->getInstrInfo();
     bool Changed = false;
 
-    SmallVector<VOPDCombineInfo> ReplaceCandidates;
+    SmallVector<VOPDMatchInfo> ReplaceCandidates;
 
     for (auto &MBB : MF) {
       auto MII = MBB.begin(), E = MBB.end();
@@ -148,16 +136,25 @@ class GCNCreateVOPD {
           continue;
         auto *SecondMI = &*MII;
 
-        if (auto Match = tryMatchVOPDPair(*SII, *FirstMI, *SecondMI)) {
-          ReplaceCandidates.push_back(
-              VOPDCombineInfo(Match->MIX, Match->MIY, Match->IsVOPD3));
-          ++MII;
-        }
+        if (std::optional<VOPDMatchInfo> Match =
+                tryMatchVOPDPair(*SII, *FirstMI, *SecondMI))
+          ReplaceCandidates.push_back(std::move(*Match));
       }
     }
-    for (auto &CI : ReplaceCandidates) {
-      Changed |= doReplace(SII, CI);
+
+    SmallVector<VOPDMatchInfo *> Selected;
+    // An instruction can be the second MI of one candidate and the first MI of
+    // the next. Remember the second MI of the last selected candidate to reject
+    // that overlap.
+    MachineInstr *LastSelectedSecond = nullptr;
+    for (VOPDMatchInfo &Match : ReplaceCandidates) {
+      if (Match.InOrder[0] == LastSelectedSecond)
+        continue;
+      Selected.push_back(&Match);
+      LastSelectedSecond = Match.InOrder[1];
     }
+    for (VOPDMatchInfo *Match : Selected)
+      Changed |= doReplace(SII, *Match);
 
     return Changed;
   }
diff --git a/llvm/lib/Target/AMDGPU/GCNVOPDUtils.cpp b/llvm/lib/Target/AMDGPU/GCNVOPDUtils.cpp
index 9dee923bfaa2d..4f0e84584988d 100644
--- a/llvm/lib/Target/AMDGPU/GCNVOPDUtils.cpp
+++ b/llvm/lib/Target/AMDGPU/GCNVOPDUtils.cpp
@@ -259,7 +259,7 @@ tryMatchVOPDPairVariant(const SIInstrInfo &TII, unsigned EncodingFamily,
 
   if (FirstCanBeVOPD.X && SecondCanBeVOPD.Y) {
     if (checkVOPDRegConstraints(TII, FirstMI, SecondMI, IsVOPD3, AllowSameVGPR))
-      return VOPDMatchInfo{&FirstMI, &SecondMI, IsVOPD3};
+      return VOPDMatchInfo{{&FirstMI, &SecondMI}, 0, IsVOPD3};
   }
 
   if (FirstCanBeVOPD.Y && SecondCanBeVOPD.X) {
@@ -270,7 +270,7 @@ tryMatchVOPDPairVariant(const SIInstrInfo &TII, unsigned EncodingFamily,
     if (IsAntiDep && !TII.isVOPDAntidependencyAllowed(SecondMI))
       return std::nullopt;
     if (checkVOPDRegConstraints(TII, SecondMI, FirstMI, IsVOPD3, AllowSameVGPR))
-      return VOPDMatchInfo{&SecondMI, &FirstMI, IsVOPD3};
+      return VOPDMatchInfo{{&FirstMI, &SecondMI}, 1, IsVOPD3};
   }
 
   return std::nullopt;
diff --git a/llvm/lib/Target/AMDGPU/GCNVOPDUtils.h b/llvm/lib/Target/AMDGPU/GCNVOPDUtils.h
index b46d2852f5942..9d815e7d13a7e 100644
--- a/llvm/lib/Target/AMDGPU/GCNVOPDUtils.h
+++ b/llvm/lib/Target/AMDGPU/GCNVOPDUtils.h
@@ -28,17 +28,22 @@ bool checkVOPDRegConstraints(const SIInstrInfo &TII,
                              const MachineInstr &SecondMI, bool IsVOPD3,
                              bool AllowSameVGPR);
 
-/// Describes a matched VOPD pair: which instruction is the X component and
-/// which is the Y component, and whether this is a VOPD3 encoding.
+/// Describes a matched VOPD pair.
 struct VOPDMatchInfo {
-  MachineInstr *MIX;
-  MachineInstr *MIY;
+  /// The component instructions in program order.
+  MachineInstr *InOrder[2];
+  /// Which entry in \p InOrder is the X component.
+  unsigned XIdx;
   bool IsVOPD3;
+
+  MachineInstr *getMIX() const { return InOrder[XIdx]; }
+  MachineInstr *getMIY() const { return InOrder[1 - XIdx]; }
 };
 
-/// Check whether FirstMI and SecondMI can be
-/// combined into a VOPD instruction.  Returns the match info (X/Y assignment
-/// and encoding variant) on success, or std::nullopt if they cannot be paired.
+/// Check whether \p FirstMI and \p SecondMI, which are next to each other in
+/// program order, can be combined into a VOPD instruction. Returns the match
+/// info (program order, X/Y assignment, and encoding variant) on success, or
+/// std::nullopt if they cannot be paired.
 std::optional<VOPDMatchInfo> tryMatchVOPDPair(const SIInstrInfo &TII,
                                               MachineInstr &FirstMI,
                                               MachineInstr &SecondMI);



More information about the llvm-commits mailing list