[llvm] AMDGPU: Add NextUseAnalysis Pass (PR #178873)

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 16 10:23:01 PDT 2026


================
@@ -0,0 +1,2614 @@
+//===---------------------- AMDGPUNextUseAnalysis.cpp ---------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the AMDGPUNextUseAnalysis pass, a machine-level analysis
+// that computes the distance from each instruction to the "nearest" next use of
+// every live virtual register. These distances guide register spilling
+// decisions by identifying which live values are furthest from their next use
+// and are therefore the best candidates to spill.
+//
+// The analysis is based on the Braun & Hack CC'09 paper "Register Spilling and
+// Live-Range Splitting for SSA-Form Programs."
+//
+// Key concepts:
+//
+//   NextUseDistance     A loop-depth-weighted instruction count representing
+//                       how far away a register's next use is. Distances
+//                       through deeper loops are scaled by fromLoopDepth() so
+//                       that uses inside hot loops appear closer.
+//
+//   Inter-block         Pre-computed shortest weighted distances between all
+//   distances           pairs of basic blocks, used to efficiently answer
+//                       cross-block next-use queries. Each intermediate block
+//                       is weighted by fromLoopDepth() applied once per loop
+//                       boundary crossing relative to the destination.
+//
+// Configuration flags (see Config struct in the header):
+//
+//   CountPhis           Count PHI instructions toward distance and block size.
+//   ForwardOnly         Restrict inter-block distances to forward-reachable
+//                       paths.
+//   PreciseUseModeling  Model PHI uses at their incoming edge block and filter
+//                       uses with intermediate redefinitions.
+//   PromoteToPreheader  Route loop-entry and inner-loop uses to the preheader.
+//
+// This file contains:
+//
+//   - Command-line options for configuration and debug output
+//   - LiveRegUse / JSON helpers
+//   - AMDGPUNextUseAnalysisImpl (the main analysis implementation)
+//       - Instruction ID assignment and block size computation
+//       - CFG path pre-computation (reachability, loop depth, back-edges)
+//       - Inter-block distance computation
+//       - Per-register next-use distance queries and caching
+//   - AMDGPUNextUseAnalysis (public facade, pimpl)
+//   - Legacy and new pass manager wrappers
+//
+//===----------------------------------------------------------------------===//
+
+#include "AMDGPUNextUseAnalysis.h"
+#include "AMDGPU.h"
+#include "GCNRegPressure.h"
+#include "GCNSubtarget.h"
+
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineInstr.h"
+#include "llvm/CodeGen/MachineLoopInfo.h"
+#include "llvm/IR/ModuleSlotTracker.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Timer.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+using namespace llvm;
+
+#define DEBUG_TYPE "amdgpu-next-use-analysis"
+
+//==============================================================================
+// Options etc
+//==============================================================================
+namespace {
+
+cl::opt<bool>
+    DistanceCacheEnabled("amdgpu-next-use-analysis-distance-cache",
+                         cl::init(true), cl::Hidden,
+                         cl::desc("Enable live-reg-use distance cache"));
+
+cl::opt<std::string>
+    DumpNextUseDistanceAsJson("amdgpu-next-use-analysis-dump-distance-as-json",
+                              cl::Hidden);
+
+cl::opt<bool> DumpNextUseDistanceDefToUse(
+    "amdgpu-next-use-analysis-dump-distance-def-to-use", cl::init(false),
+    cl::Hidden);
+
+cl::opt<bool>
+    DumpNextUseDistanceVerbose("amdgpu-next-use-analysis-dump-distance-verbose",
+                               cl::init(false), cl::Hidden);
+
+// 'graphics' and 'compute' modes arose due to initial competing implementations
+// of next-use analysis that emphasized different types of workloads. This
+// implementation is a compromise that combines aspects of both. Over time, the
+// hope is we will be able to remove some of these differences and settle on a
+// more unified implementation.
+cl::opt<std::string>
+    ConfigPresetOpt("amdgpu-next-use-analysis-config", cl::Hidden,
+                    cl::init("graphics"),
+                    cl::desc("Config preset: 'graphics' or 'compute'"));
+
+cl::opt<bool> ConfigCountPhisOpt(
+    "amdgpu-next-use-analysis-count-phis", cl::Hidden,
+    cl::desc("Count PHI instructions toward distance and block size"));
+cl::opt<bool> ConfigForwardOnlyOpt(
+    "amdgpu-next-use-analysis-forward-only", cl::Hidden,
+    cl::desc("Restrict inter-block distances to forward-reachable paths"));
+cl::opt<bool> ConfigPreciseUseModelingOpt(
+    "amdgpu-next-use-analysis-precise-use-modeling", cl::Hidden,
+    cl::desc("Model PHI uses via incoming edge block with loop-aware "
+             "reachability filtering"));
+cl::opt<bool> ConfigPromoteToPreheaderOpt(
+    "amdgpu-next-use-analysis-use-preheader-model", cl::Hidden,
+    cl::desc("Promote loop-entry and inner-loop uses to the loop preheader"));
+} // namespace
+
+//==============================================================================
+// LiveRegUse - Represents a live register use with its distance. Used for
+// tracking and sorting register uses by distance.
+//==============================================================================
+namespace {
+using UseDistancePair = AMDGPUNextUseAnalysis::UseDistancePair;
+struct LiveRegUse : public UseDistancePair {
+  // 'nullptr' indicates an unset/invalid state.
+  LiveRegUse() : UseDistancePair(nullptr, 0) {}
+  LiveRegUse(const MachineOperand *Use, NextUseDistance Dist)
+      : UseDistancePair(Use, Dist) {}
+  LiveRegUse(const UseDistancePair &P) : UseDistancePair(P) {}
+
+  bool isUnset() const { return Use == nullptr; }
+
+  Register getReg() const { return Use->getReg(); }
+  unsigned getSubReg() const { return Use->getSubReg(); }
+  LaneBitmask getLaneMask(const SIRegisterInfo *TRI) const {
+    return TRI->getSubRegIndexLaneMask(Use->getSubReg());
+  }
+
+  bool isCloserThan(const LiveRegUse &X) const {
+    if (Dist < X.Dist)
+      return true;
+
+    if (Dist > X.Dist)
+      return false;
+
+    if (Use == X.Use)
+      return false;
+
+    // Ugh. When !CountPhis, PHIs and the first non-PHI instruction have id
+    // 0. In this case, consider PHIs as less than the first non-PHI
+    // instruction.
+    const MachineInstr *ThisMI = Use->getParent();
+    const MachineInstr *XMI = X.Use->getParent();
+    const MachineBasicBlock *ThisMBB = ThisMI->getParent();
+    if (ThisMBB == XMI->getParent()) {
+      if (ThisMI->isPHI() && !XMI->isPHI() &&
+          XMI == &(*ThisMBB->getFirstNonPHI()))
+        return true;
+    }
+
+    // Ensure deterministic results
+    return X.getReg() < getReg();
+  }
+
+  void print(raw_ostream &OS, const TargetRegisterInfo *TRI = nullptr,
+             const MachineRegisterInfo *MRI = nullptr) const {
+    if (isUnset()) {
+      OS << "<unset>";
+      return;
+    }
+    Dist.print(OS);
+    OS << " [" << printReg(getReg(), TRI, getSubReg(), MRI) << "]";
+  }
+
+  LLVM_DUMP_METHOD void dump() const {
+    print(dbgs());
+    dbgs() << '\n';
+  }
+};
+
+inline bool updateClosest(LiveRegUse &Closest, const LiveRegUse &X) {
+  if (!Closest.Use || X.isCloserThan(Closest)) {
+    Closest = X;
+    return true;
+  }
+  return false;
+}
+
+inline bool updateFurthest(LiveRegUse &Furthest, const LiveRegUse &X) {
+  if (!Furthest.Use || Furthest.isCloserThan(X)) {
+    Furthest = X;
+    return true;
+  }
+  return false;
+}
+} // namespace
+
+//==============================================================================
+// JSON helpers
+//==============================================================================
+namespace {
+template <typename Lambda>
+void printStringAttr(json::OStream &J, const char *Name, Lambda L) {
+  J.attributeBegin(Name);
+  raw_ostream &OS = J.rawValueBegin();
+  OS << '"';
+  L(OS);
+  OS << '"';
+  J.rawValueEnd();
+  J.attributeEnd();
+}
+void printStringAttr(json::OStream &J, const char *Name, Printable P) {
+  printStringAttr(J, Name, [&](raw_ostream &OS) { OS << P; });
+}
+
+void printStringAttr(json::OStream &J, const char *Name, const MachineInstr &MI,
+                     ModuleSlotTracker &MST) {
+  printStringAttr(J, Name, [&](raw_ostream &OS) {
+    MI.print(OS, MST,
+             /* IsStandalone    */ false,
+             /* SkipOpers       */ false,
+             /* SkipDebugLoc    */ false,
+             /* AddNewLine ---> */ false,
+             /* TargetInstrInfo */ nullptr);
+  });
+}
+
+void printMBBNameAttr(json::OStream &J, const char *Name,
+                      const MachineBasicBlock &MBB, ModuleSlotTracker &MST) {
+  printStringAttr(J, Name, [&](raw_ostream &OS) {
+    MBB.printName(OS, MachineBasicBlock::PrintNameIr, &MST);
+  });
+}
+
+template <typename NameLambda, typename ValueT>
+void printAttr(json::OStream &J, NameLambda NL, ValueT V) {
+  std::string Name;
+  raw_string_ostream NameOS(Name);
+  NL(NameOS);
+  J.attribute(NameOS.str(), V);
+}
+
+template <typename ValueT>
+void printAttr(json::OStream &J, const Printable &P, ValueT V) {
+  printAttr(J, [&](raw_ostream &OS) { OS << P; }, V);
+}
+
+} // namespace
+
+//==============================================================================
+// AMDGPUNextUseAnalysisImpl
+//==============================================================================
+class llvm::AMDGPUNextUseAnalysisImpl {
+public:
+  struct CacheableNextUseDistance {
+    bool IsInstrRelative;
+    NextUseDistance Distance;
+  };
+  static constexpr bool InstrRelative = true;
+  static constexpr bool InstrInvariant = false;
+
+private:
+  const MachineFunction *MF = nullptr;
+  const SIRegisterInfo *TRI = nullptr;
+  const SIInstrInfo *TII = nullptr;
+  const MachineLoopInfo *MLI = nullptr;
+  const MachineRegisterInfo *MRI = nullptr;
+
+  using InstrIdTy = unsigned;
+  using InstrToIdMap = DenseMap<const MachineInstr *, InstrIdTy>;
+  InstrToIdMap InstrToId;
+  AMDGPUNextUseAnalysis::Config Cfg;
+
+  void initializeTables() {
+    for (const MachineBasicBlock &BB : *MF)
+      calcInstrIds(&BB, InstrToId);
+    initializeCfgPaths();
+    initializeInterBlockDistances();
+  }
+
+  void clearTables() {
+    InstrToId.clear();
+    RegUseMap.clear();
+    Paths.clear();
+
+    resetDistanceCache();
+  }
+
+  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // Instruction Ids
+  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+private:
+  unsigned sizeOf(const MachineInstr &MI) const {
+    // When !Cfg.CountPhis, PHIs do not contribute to distances/sizes since they
+    // generally don't result in the generation of a machine instruction.
+    // FIXME: Consider using MI.isPseudo() or maybe MI.isMetaInstruction().
+    return Cfg.CountPhis ? 1 : !MI.isPHI();
+  }
+
+  void calcInstrIds(const MachineBasicBlock *BB,
+                    InstrToIdMap &MutableInstrToId) const {
+    InstrIdTy Id = 0;
+    for (auto &MI : BB->instrs()) {
+      MutableInstrToId[&MI] = Id;
+      Id += sizeOf(MI);
+    }
+  }
+
+  /// Returns MI's instruction Id. It renumbers (part of) the BB if MI is not
+  /// found in the map.
+  InstrIdTy getInstrId(const MachineInstr *MI) const {
+    auto It = InstrToId.find(MI);
+    if (It != InstrToId.end())
+      return It->second;
+
+    // Renumber the MBB.
+    // TODO: Renumber from MI onwards.
+    auto &MutableInstrToId = const_cast<InstrToIdMap &>(InstrToId);
+    calcInstrIds(MI->getParent(), MutableInstrToId);
+    return InstrToId.find(MI)->second;
+  }
+
+  // Length of the segment from MI (inclusive) to the first instruction of the
+  // basic block.
+  InstrIdTy getHeadLen(const MachineInstr *MI) const {
+    const MachineBasicBlock *MBB = MI->getParent();
+    return getInstrId(MI) + getInstrId(&MBB->instr_front()) + 1;
+  }
+
+  // Length of the segment from MI (exclusive) to the last instruction of the
+  // basic block.
+  InstrIdTy getTailLen(const MachineInstr *MI) const {
+    const MachineBasicBlock *MBB = MI->getParent();
+    return getInstrId(&MBB->instr_back()) - getInstrId(MI);
+  }
+
+  // Length of the segment from 'From' to 'To' (exclusive). Both instructions
+  // must be in the same basic block.
+  InstrIdTy getDistance(const MachineInstr *From,
----------------
arsenm wrote:

const references 

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


More information about the llvm-commits mailing list