[llvm] [AMDGPU] Introduce Next-Use Analysis for SSA-based Register Allocation (PR #156079)

Chris Jackson via llvm-commits llvm-commits at lists.llvm.org
Mon Dec 8 07:09:28 PST 2025


================
@@ -0,0 +1,457 @@
+//===-- AMDGPUNextUseAnalysis.h - Next Use Analysis ------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// This file defines the Next Use Analysis for AMDGPU targets, which computes
+/// distances to next uses of virtual registers to guide register allocation
+/// and spilling decisions.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_AMDGPU_NEXT_USE_ANALYSIS_H
+#define LLVM_LIB_TARGET_AMDGPU_NEXT_USE_ANALYSIS_H
+
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/CodeGen/MachineLoopInfo.h"
+#include "llvm/CodeGen/SlotIndexes.h"
+
+#include "AMDGPUSSARAUtils.h"
+#include "GCNSubtarget.h"
+#include "SIRegisterInfo.h"
+#include "VRegMaskPair.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+using namespace llvm;
+
+// namespace {
+
+// Helper function for rebasing successor distances into current block frame
+static inline int64_t rebaseFromSucc(int64_t SuccStored, unsigned SuccEntryOff,
+                                     int64_t EdgeWeight /*0 or LoopTag*/) {
+  // Move succ-relative value into "current block end" frame.
+  return (int64_t)SuccStored + (int64_t)SuccEntryOff + (int64_t)EdgeWeight;
+}
+
+class NextUseResult {
+  friend class AMDGPUNextUseAnalysisWrapper;
+  SlotIndexes *Indexes;
+  const MachineRegisterInfo *MRI;
+  const SIRegisterInfo *TRI;
+  MachineLoopInfo *LI;
+
+  class VRegDistances {
+
+    using Record = std::pair<LaneBitmask, int64_t>;
+    struct CompareByDist {
+      bool operator()(const Record &LHS, const Record &RHS) const {
+        if (LHS.second != RHS.second)     // Different distances
+          return LHS.second < RHS.second; // Closest first
+        return LHS.first.getAsInteger() <
+               RHS.first.getAsInteger(); // Tiebreaker
+      }
+    };
+
+  public:
+    using SortedRecords = std::set<Record, CompareByDist>;
+
+  private:
+    DenseMap<unsigned, SortedRecords> NextUseMap;
+
+  public:
+    auto begin() { return NextUseMap.begin(); }
+    auto end() { return NextUseMap.end(); }
+
+    auto begin() const { return NextUseMap.begin(); }
+    auto end() const { return NextUseMap.end(); }
+
+    size_t size() const { return NextUseMap.size(); }
+    std::pair<bool, SortedRecords> get(unsigned Key) const {
+      if (NextUseMap.contains(Key))
+        return {true, NextUseMap.find(Key)->second};
+      return {false, SortedRecords()};
+    }
+
+    SortedRecords &operator[](unsigned Key) { return NextUseMap[Key]; }
+
+    SmallVector<unsigned> keys() {
+      SmallVector<unsigned> Keys;
+      for (auto P : NextUseMap)
+        Keys.push_back(P.first);
+      return Keys;
+    }
+
+    bool contains(unsigned Key) { return NextUseMap.contains(Key); }
+
+    // Compare two stored distances: returns true if A is closer or equal to B.
+    // Handles mixed-sign values correctly:
+    // - Negative stored values (finite distances): larger (less negative) =
+    // closer
+    // - Non-negative stored values (LoopTag distances): smaller = closer
+    // - Mixed: negative (finite) is always closer than non-negative
+    // (loop-tagged)
+    // TODO: Investigate making LoopTag/DeadTag negative for consistent sign
+    // convention
+    static bool isCloserOrEqual(int64_t A, int64_t B) {
+      // Both negative (finite): larger = closer
+      if (A < 0 && B < 0)
+        return A >= B;
+      // Both non-negative (loop-tagged): smaller = closer
+      if (A >= 0 && B >= 0)
+        return A <= B;
+      // Mixed: negative (finite) is always closer than non-negative
+      // (loop-tagged)
+      return A < 0;
+    }
+
+    bool insert(VRegMaskPair VMP, int64_t Dist) {
+      Record R(VMP.getLaneMask(), Dist);
+      if (NextUseMap.contains(VMP.getVReg())) {
+        SortedRecords &Dists = NextUseMap[VMP.getVReg()];
+
+        if (Dists.find(R) == Dists.end()) {
+          SmallVector<SortedRecords::iterator, 4> ToErase;
+
+          for (auto It = Dists.begin(); It != Dists.end(); ++It) {
+            const Record &D = *It;
+
+            // Check if existing use covers the new use
+            if ((R.first & D.first) == R.first) {
+              // Existing use covers new use - keep if existing is closer
+              if (isCloserOrEqual(D.second, R.second)) {
+                // Existing use is closer or equal → reject new use
+                return false;
+              }
+              // Existing use is further → continue (might replace it)
+            }
+
+            // Check if new use covers existing use
+            if ((D.first & R.first) == D.first) {
+              // New use covers existing use - evict if new is closer
+              if (isCloserOrEqual(R.second, D.second)) {
+                // New use is closer → mark existing for removal
+                ToErase.push_back(It);
+              } else {
+                // New use is further → reject it
+                return false;
+              }
+            }
+          }
+
+          // Remove all records that the new use supersedes
+          for (auto It : ToErase) {
+            Dists.erase(It);
+          }
+
+          // Add new record
+          return Dists.insert(R).second;
+        }
+        // Record already exists!
+        return false;
+      }
+      return NextUseMap[VMP.getVReg()].insert(R).second;
+    }
+
+    void clear(VRegMaskPair VMP) {
+      if (NextUseMap.contains(VMP.getVReg())) {
+        auto &Dists = NextUseMap[VMP.getVReg()];
+        for (auto It = Dists.begin(); It != Dists.end();) {
+          LaneBitmask Masked = It->first & ~VMP.getLaneMask();
+          if (Masked.none()) {
+            It = Dists.erase(It);
+          } else {
+            ++It;
+          }
+        }
+        if (Dists.empty())
+          NextUseMap.erase(VMP.getVReg());
+      }
+    }
+
+    bool operator==(const VRegDistances Other) const {
+
+      if (Other.size() != size())
+        return false;
+
+      for (auto P : NextUseMap) {
+
+        std::pair<bool, SortedRecords> OtherDists = Other.get(P.getFirst());
+        if (!OtherDists.first)
+          return false;
+        SortedRecords &Dists = P.getSecond();
+
+        if (Dists.size() != OtherDists.second.size())
+          return false;
+
+        for (auto R : OtherDists.second) {
+          SortedRecords::iterator I = Dists.find(R);
+          if (I == Dists.end())
+            return false;
+          if (R.second != I->second)
+            return false;
+        }
+      }
+
+      return true;
+    }
+
+    bool operator!=(const VRegDistances &Other) const {
+      return !operator==(Other);
+    }
+
+    // Adjust 'Other' (which is in successor's frame) into *this* frame,
+    // then merge using insert's coverage logic.
+    void merge(const VRegDistances &Other, unsigned SuccEntryOff,
+               int64_t EdgeWeight = 0) {
+      for (const auto &P : Other) {
+        unsigned Key = P.getFirst();
+        const auto &OtherDists = P.getSecond();
+
+        for (const auto &D : OtherDists) {
+          int64_t Rebased = rebaseFromSucc(D.second, SuccEntryOff, EdgeWeight);
+          // Use insert's coverage logic for consistent handling
+          insert(VRegMaskPair(Register(Key), D.first), Rebased);
+        }
+      }
+    }
+  };
+  class NextUseInfo {
+    // FIXME: need to elaborate proper class interface!
+  public:
+    VRegDistances Bottom;
+    DenseMap<const MachineInstr *, VRegDistances> InstrDist;
+    DenseMap<const MachineInstr *, unsigned>
+        InstrOffset; // offset at that MI snapshot
+  };
+
+  DenseMap<unsigned, NextUseInfo> NextUseMap;
+  // Map MBB number to the maximal offset in given by the bottm-up walk
+  DenseMap<unsigned, unsigned> EntryOff;
+
+public:
+private:
+  DenseMap<unsigned, VRegMaskPairSet> UsedInBlock;
+  DenseMap<int, int> LoopExits;
+  // Signed tag used to mark "outside current loop" in stored values.
+  // Must be >> any finite distance you can accumulate in one function.
+  static constexpr int64_t LoopTag = (int64_t)1 << 40; // ~1e12 headroom
+  static constexpr int64_t DeadTag = (int64_t)1 << 60; // ~1e18, >> LoopTag
+
+  // Unsigned Infinity for external API/DAG users who want a sentinel.
+  static constexpr unsigned PrintedInfinity =
+      std::numeric_limits<unsigned>::max();
+
+  const uint16_t Infinity = std::numeric_limits<unsigned short>::max();
----------------
chrisjbris wrote:

INFINITY(!)?

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


More information about the llvm-commits mailing list