Skip to content

[HLSL][DirectX] Extract HLSLBinding out of DXILResource. NFC #150633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 31, 2025

Conversation

bogner
Copy link
Contributor

@bogner bogner commented Jul 25, 2025

We extract the binding logic out of the DXILResource analysis passes into the
FrontendHLSL library. This will allow us to use this logic for resource and
root signature bindings in both the DirectX backend and the HLSL frontend.

@bogner bogner changed the base branch from users/bogner/pr150547 to main July 30, 2025 17:33
@bogner bogner force-pushed the 2025-07-25-hlslbinding branch 2 times, most recently from 09d0104 to 0c00e30 Compare July 30, 2025 19:11
@bogner bogner changed the title wip: Extract HLSLBinding out of DXILResource [HLSL][DirectX] Extract HLSLBinding out of DXILResource. NFC Jul 30, 2025
@bogner bogner marked this pull request as ready for review July 30, 2025 19:12
@llvmbot llvmbot added backend:DirectX HLSL HLSL Language Support llvm:analysis Includes value tracking, cost tables and constant folding labels Jul 30, 2025
We extract the binding logic out of the DXILResource analysis passes into the
FrontendHLSL library. This will allow us to use this logic for resource and
root signature bindings in both the DirectX backend and the HLSL frontend.
@llvmbot
Copy link
Member

llvmbot commented Jul 30, 2025

@llvm/pr-subscribers-llvm-analysis

@llvm/pr-subscribers-hlsl

Author: Justin Bogner (bogner)

Changes

We extract the binding logic out of the DXILResource analysis passes into the
FrontendHLSL library. This will allow us to use this logic for resource and
root signature bindings in both the DirectX backend and the HLSL frontend.


Patch is 44.31 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/150633.diff

9 Files Affected:

  • (modified) llvm/include/llvm/Analysis/DXILResource.h (+11-71)
  • (added) llvm/include/llvm/Frontend/HLSL/HLSLBinding.h (+162)
  • (modified) llvm/lib/Analysis/CMakeLists.txt (+1)
  • (modified) llvm/lib/Analysis/DXILResource.cpp (+6-130)
  • (modified) llvm/lib/Frontend/HLSL/CMakeLists.txt (+1)
  • (added) llvm/lib/Frontend/HLSL/HLSLBinding.cpp (+141)
  • (modified) llvm/unittests/Frontend/CMakeLists.txt (+1)
  • (added) llvm/unittests/Frontend/HLSLBindingTest.cpp (+273)
  • (modified) llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp (+7-164)
diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h
index 956dcbcc33305..93c6bfb057ef5 100644
--- a/llvm/include/llvm/Analysis/DXILResource.h
+++ b/llvm/include/llvm/Analysis/DXILResource.h
@@ -12,6 +12,7 @@
 #include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Frontend/HLSL/HLSLBinding.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/GlobalVariable.h"
 #include "llvm/IR/PassManager.h"
@@ -633,86 +634,25 @@ LLVM_ABI ModulePass *createDXILResourceWrapperPassPass();
 // register slots to resources with implicit bindings, and in a
 // post-optimization validation pass that will raise diagnostic about
 // overlapping bindings.
-//
-// For example for these resource bindings:
-//
-// RWBuffer<float> A[10] : register(u3);
-// RWBuffer<float> B[] : register(u5, space2)
-//
-// The analysis result for UAV binding type will look like this:
-//
-// UAVSpaces {
-//   ResClass = ResourceClass::UAV,
-//   Spaces = {
-//     { Space = 0, FreeRanges = {{ 0, 2 }, { 13, UINT32_MAX }} },
-//     { Space = 2, FreeRanges = {{ 0, 4 }} }
-//   }
-// }
-//
 class DXILResourceBindingInfo {
-public:
-  struct BindingRange {
-    uint32_t LowerBound;
-    uint32_t UpperBound;
-    BindingRange(uint32_t LB, uint32_t UB) : LowerBound(LB), UpperBound(UB) {}
-  };
-
-  struct RegisterSpace {
-    uint32_t Space;
-    SmallVector<BindingRange> FreeRanges;
-    RegisterSpace(uint32_t Space) : Space(Space) {
-      FreeRanges.emplace_back(0, UINT32_MAX);
-    }
-    // Size == -1 means unbounded array
-    LLVM_ABI std::optional<uint32_t> findAvailableBinding(int32_t Size);
-  };
-
-  struct BindingSpaces {
-    dxil::ResourceClass RC;
-    llvm::SmallVector<RegisterSpace> Spaces;
-    BindingSpaces(dxil::ResourceClass RC) : RC(RC) {}
-    LLVM_ABI RegisterSpace &getOrInsertSpace(uint32_t Space);
-  };
-
-private:
-  BindingSpaces SRVSpaces, UAVSpaces, CBufferSpaces, SamplerSpaces;
-  bool ImplicitBinding;
-  bool OverlappingBinding;
+  hlsl::BindingInfo Bindings;
+  bool HasImplicitBinding = false;
+  bool HasOverlappingBinding = false;
 
   // Populate the resource binding info given explicit resource binding calls
   // in the module.
   void populate(Module &M, DXILResourceTypeMap &DRTM);
 
 public:
-  DXILResourceBindingInfo()
-      : SRVSpaces(dxil::ResourceClass::SRV),
-        UAVSpaces(dxil::ResourceClass::UAV),
-        CBufferSpaces(dxil::ResourceClass::CBuffer),
-        SamplerSpaces(dxil::ResourceClass::Sampler), ImplicitBinding(false),
-        OverlappingBinding(false) {}
-
-  bool hasImplicitBinding() const { return ImplicitBinding; }
-  void setHasImplicitBinding(bool Value) { ImplicitBinding = Value; }
-  bool hasOverlappingBinding() const { return OverlappingBinding; }
-
-  BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) {
-    switch (RC) {
-    case dxil::ResourceClass::SRV:
-      return SRVSpaces;
-    case dxil::ResourceClass::UAV:
-      return UAVSpaces;
-    case dxil::ResourceClass::CBuffer:
-      return CBufferSpaces;
-    case dxil::ResourceClass::Sampler:
-      return SamplerSpaces;
-    }
+  bool hasImplicitBinding() const { return HasImplicitBinding; }
+  void setHasImplicitBinding(bool Value) { HasImplicitBinding = Value; }
+  bool hasOverlappingBinding() const { return HasOverlappingBinding; }
+  void setHasOverlappingBinding(bool Value) { HasOverlappingBinding = Value; }
 
-    llvm_unreachable("Invalid resource class");
-  }
-
-  // Size == -1 means unbounded array
   LLVM_ABI std::optional<uint32_t>
-  findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size);
+  findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size) {
+    return Bindings.findAvailableBinding(RC, Space, Size);
+  }
 
   friend class DXILResourceBindingAnalysis;
   friend class DXILResourceBindingWrapperPass;
diff --git a/llvm/include/llvm/Frontend/HLSL/HLSLBinding.h b/llvm/include/llvm/Frontend/HLSL/HLSLBinding.h
new file mode 100644
index 0000000000000..1591bc9ae1f3a
--- /dev/null
+++ b/llvm/include/llvm/Frontend/HLSL/HLSLBinding.h
@@ -0,0 +1,162 @@
+//===- HLSLBinding.h - Representation for resource bindings in HLSL -------===//
+//
+// 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 contains objects to represent resource bindings.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_FRONTEND_HLSL_HLSLBINDING_H
+#define LLVM_FRONTEND_HLSL_HLSLBINDING_H
+
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/DXILABI.h"
+#include "llvm/Support/ErrorHandling.h"
+
+namespace llvm {
+namespace hlsl {
+
+/// BindingInfo represents the ranges of bindings and free space for each
+/// `dxil::ResourceClass`. This can represent HLSL-level bindings as well as
+/// bindings described in root signatures, and can be used for analysis of
+/// overlapping or missing bindings as well as for finding space for implicit
+/// bindings.
+///
+/// As an example, given these resource bindings:
+///
+/// RWBuffer<float> A[10] : register(u3);
+/// RWBuffer<float> B[] : register(u5, space2)
+///
+/// The binding info for UAV bindings should look like this:
+///
+/// UAVSpaces {
+///   ResClass = ResourceClass::UAV,
+///   Spaces = {
+///     { Space = 0, FreeRanges = {{ 0, 2 }, { 13, UINT32_MAX }} },
+///     { Space = 2, FreeRanges = {{ 0, 4 }} }
+///   }
+/// }
+class BindingInfo {
+public:
+  struct BindingRange {
+    uint32_t LowerBound;
+    uint32_t UpperBound;
+    BindingRange(uint32_t LB, uint32_t UB) : LowerBound(LB), UpperBound(UB) {}
+  };
+
+  struct RegisterSpace {
+    uint32_t Space;
+    SmallVector<BindingRange> FreeRanges;
+    RegisterSpace(uint32_t Space) : Space(Space) {
+      FreeRanges.emplace_back(0, UINT32_MAX);
+    }
+    // Size == -1 means unbounded array
+    LLVM_ABI std::optional<uint32_t> findAvailableBinding(int32_t Size);
+  };
+
+  struct BindingSpaces {
+    dxil::ResourceClass RC;
+    llvm::SmallVector<RegisterSpace> Spaces;
+    BindingSpaces(dxil::ResourceClass RC) : RC(RC) {}
+    LLVM_ABI RegisterSpace &getOrInsertSpace(uint32_t Space);
+  };
+
+private:
+  BindingSpaces SRVSpaces{dxil::ResourceClass::SRV};
+  BindingSpaces UAVSpaces{dxil::ResourceClass::UAV};
+  BindingSpaces CBufferSpaces{dxil::ResourceClass::CBuffer};
+  BindingSpaces SamplerSpaces{dxil::ResourceClass::Sampler};
+
+public:
+  BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) {
+    switch (RC) {
+    case dxil::ResourceClass::SRV:
+      return SRVSpaces;
+    case dxil::ResourceClass::UAV:
+      return UAVSpaces;
+    case dxil::ResourceClass::CBuffer:
+      return CBufferSpaces;
+    case dxil::ResourceClass::Sampler:
+      return SamplerSpaces;
+    }
+
+    llvm_unreachable("Invalid resource class");
+  }
+  const BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) const {
+    return const_cast<BindingInfo *>(this)->getBindingSpaces(RC);
+  }
+
+  // Size == -1 means unbounded array
+  LLVM_ABI std::optional<uint32_t>
+  findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size);
+
+  friend class BindingInfoBuilder;
+};
+
+/// Builder class for creating a /c BindingInfo.
+class BindingInfoBuilder {
+public:
+  struct Binding {
+    dxil::ResourceClass RC;
+    uint32_t Space;
+    uint32_t LowerBound;
+    uint32_t UpperBound;
+    const void *Cookie;
+
+    Binding(dxil::ResourceClass RC, uint32_t Space, uint32_t LowerBound,
+            uint32_t UpperBound, const void *Cookie)
+        : RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound),
+          Cookie(Cookie) {}
+
+    bool isUnbounded() const { return UpperBound == ~0U; }
+
+    bool operator==(const Binding &RHS) const {
+      return std::tie(RC, Space, LowerBound, UpperBound, Cookie) ==
+             std::tie(RHS.RC, RHS.Space, RHS.LowerBound, RHS.UpperBound,
+                      RHS.Cookie);
+    }
+    bool operator!=(const Binding &RHS) const { return !(*this == RHS); }
+
+    bool operator<(const Binding &RHS) const {
+      return std::tie(RC, Space, LowerBound) <
+             std::tie(RHS.RC, RHS.Space, RHS.LowerBound);
+    }
+  };
+
+private:
+  SmallVector<Binding> Bindings;
+
+public:
+  void trackBinding(dxil::ResourceClass RC, uint32_t Space, uint32_t LowerBound,
+                    uint32_t UpperBound, const void *Cookie) {
+    Bindings.emplace_back(RC, Space, LowerBound, UpperBound, Cookie);
+  }
+  /// Calculate the binding info - \c ReportOverlap will be called once for each
+  /// overlapping binding.
+  BindingInfo calculateBindingInfo(
+      llvm::function_ref<void(const BindingInfoBuilder &Builder,
+                              const Binding &Overlapping)>
+          ReportOverlap);
+
+  /// Calculate the binding info - \c HasOverlap will be set to indicate whether
+  /// there are any overlapping bindings.
+  BindingInfo calculateBindingInfo(bool &HasOverlap) {
+    HasOverlap = false;
+    return calculateBindingInfo(
+        [&HasOverlap](auto, auto) { HasOverlap = true; });
+  }
+
+  /// For use in the \c ReportOverlap callback of \c calculateBindingInfo -
+  /// finds a binding that the \c ReportedBinding overlaps with.
+  const Binding &findOverlapping(const Binding &ReportedBinding) const;
+};
+
+} // namespace hlsl
+} // namespace llvm
+
+#endif // LLVM_FRONTEND_HLSL_HLSLBINDING_H
diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt
index cfde787b17790..16dd6f8b86006 100644
--- a/llvm/lib/Analysis/CMakeLists.txt
+++ b/llvm/lib/Analysis/CMakeLists.txt
@@ -175,6 +175,7 @@ add_llvm_component_library(LLVMAnalysis
   LINK_COMPONENTS
   BinaryFormat
   Core
+  FrontendHLSL
   Object
   ProfileData
   Support
diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp
index 1959ab6e510a3..629fa7cddb9d4 100644
--- a/llvm/lib/Analysis/DXILResource.cpp
+++ b/llvm/lib/Analysis/DXILResource.cpp
@@ -995,18 +995,7 @@ SmallVector<dxil::ResourceInfo *> DXILResourceMap::findByUse(const Value *Key) {
 //===----------------------------------------------------------------------===//
 
 void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {
-  struct Binding {
-    ResourceClass RC;
-    uint32_t Space;
-    uint32_t LowerBound;
-    uint32_t UpperBound;
-    Value *Name;
-    Binding(ResourceClass RC, uint32_t Space, uint32_t LowerBound,
-            uint32_t UpperBound, Value *Name)
-        : RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound),
-          Name(Name) {}
-  };
-  SmallVector<Binding> Bindings;
+  hlsl::BindingInfoBuilder Builder;
 
   // collect all of the llvm.dx.resource.handlefrombinding calls;
   // make a note if there is llvm.dx.resource.handlefromimplicitbinding
@@ -1036,133 +1025,20 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {
           assert((Size < 0 || (unsigned)LowerBound + Size - 1 <= UINT32_MAX) &&
                  "upper bound register overflow");
           uint32_t UpperBound = Size < 0 ? UINT32_MAX : LowerBound + Size - 1;
-          Bindings.emplace_back(RTI.getResourceClass(), Space, LowerBound,
-                                UpperBound, Name);
+          Builder.trackBinding(RTI.getResourceClass(), Space, LowerBound,
+                               UpperBound, Name);
         }
       break;
     }
     case Intrinsic::dx_resource_handlefromimplicitbinding: {
-      ImplicitBinding = true;
+      HasImplicitBinding = true;
       break;
     }
     }
   }
 
-  // sort all the collected bindings
-  llvm::stable_sort(Bindings, [](auto &LHS, auto &RHS) {
-    return std::tie(LHS.RC, LHS.Space, LHS.LowerBound) <
-           std::tie(RHS.RC, RHS.Space, RHS.LowerBound);
-  });
-
-  // remove duplicates
-  Binding *NewEnd = llvm::unique(Bindings, [](auto &LHS, auto &RHS) {
-    return std::tie(LHS.RC, LHS.Space, LHS.LowerBound, LHS.UpperBound,
-                    LHS.Name) == std::tie(RHS.RC, RHS.Space, RHS.LowerBound,
-                                          RHS.UpperBound, RHS.Name);
-  });
-  if (NewEnd != Bindings.end())
-    Bindings.erase(NewEnd);
-
-  // Go over the sorted bindings and build up lists of free register ranges
-  // for each binding type and used spaces. Bindings are sorted by resource
-  // class, space, and lower bound register slot.
-  BindingSpaces *BS = &SRVSpaces;
-  for (const Binding &B : Bindings) {
-    if (BS->RC != B.RC)
-      // move to the next resource class spaces
-      BS = &getBindingSpaces(B.RC);
-
-    RegisterSpace *S = BS->Spaces.empty() ? &BS->Spaces.emplace_back(B.Space)
-                                          : &BS->Spaces.back();
-    assert(S->Space <= B.Space && "bindings not sorted correctly?");
-    if (B.Space != S->Space)
-      // add new space
-      S = &BS->Spaces.emplace_back(B.Space);
-
-    // The space is full - there are no free slots left, or the rest of the
-    // slots are taken by an unbounded array. Set flag to report overlapping
-    // binding later.
-    if (S->FreeRanges.empty() || S->FreeRanges.back().UpperBound < UINT32_MAX) {
-      OverlappingBinding = true;
-      continue;
-    }
-
-    // adjust the last free range lower bound, split it in two, or remove it
-    BindingRange &LastFreeRange = S->FreeRanges.back();
-    if (LastFreeRange.LowerBound == B.LowerBound) {
-      if (B.UpperBound < UINT32_MAX)
-        LastFreeRange.LowerBound = B.UpperBound + 1;
-      else
-        S->FreeRanges.pop_back();
-    } else if (LastFreeRange.LowerBound < B.LowerBound) {
-      LastFreeRange.UpperBound = B.LowerBound - 1;
-      if (B.UpperBound < UINT32_MAX)
-        S->FreeRanges.emplace_back(B.UpperBound + 1, UINT32_MAX);
-    } else {
-      OverlappingBinding = true;
-      if (B.UpperBound < UINT32_MAX)
-        LastFreeRange.LowerBound =
-            std::max(LastFreeRange.LowerBound, B.UpperBound + 1);
-      else
-        S->FreeRanges.pop_back();
-    }
-  }
-}
-
-// returns std::nulopt if binding could not be found in given space
-std::optional<uint32_t>
-DXILResourceBindingInfo::findAvailableBinding(dxil::ResourceClass RC,
-                                              uint32_t Space, int32_t Size) {
-  BindingSpaces &BS = getBindingSpaces(RC);
-  RegisterSpace &RS = BS.getOrInsertSpace(Space);
-  return RS.findAvailableBinding(Size);
-}
-
-DXILResourceBindingInfo::RegisterSpace &
-DXILResourceBindingInfo::BindingSpaces::getOrInsertSpace(uint32_t Space) {
-  for (auto *I = Spaces.begin(); I != Spaces.end(); ++I) {
-    if (I->Space == Space)
-      return *I;
-    if (I->Space < Space)
-      continue;
-    return *Spaces.insert(I, Space);
-  }
-  return Spaces.emplace_back(Space);
-}
-
-std::optional<uint32_t>
-DXILResourceBindingInfo::RegisterSpace::findAvailableBinding(int32_t Size) {
-  assert((Size == -1 || Size > 0) && "invalid size");
-
-  if (FreeRanges.empty())
-    return std::nullopt;
-
-  // unbounded array
-  if (Size == -1) {
-    BindingRange &Last = FreeRanges.back();
-    if (Last.UpperBound != UINT32_MAX)
-      // this space is already occupied by an unbounded array
-      return std::nullopt;
-    uint32_t RegSlot = Last.LowerBound;
-    FreeRanges.pop_back();
-    return RegSlot;
-  }
-
-  // single resource or fixed-size array
-  for (BindingRange &R : FreeRanges) {
-    // compare the size as uint64_t to prevent overflow for range (0,
-    // UINT32_MAX)
-    if ((uint64_t)R.UpperBound - R.LowerBound + 1 < (uint64_t)Size)
-      continue;
-    uint32_t RegSlot = R.LowerBound;
-    // This might create a range where (LowerBound == UpperBound + 1). When
-    // that happens, the next time this function is called the range will
-    // skipped over by the check above (at this point Size is always > 0).
-    R.LowerBound += Size;
-    return RegSlot;
-  }
-
-  return std::nullopt;
+  Bindings = Builder.calculateBindingInfo(
+      [this](auto, auto) { this->HasOverlappingBinding = true; });
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Frontend/HLSL/CMakeLists.txt b/llvm/lib/Frontend/HLSL/CMakeLists.txt
index 534346920ff19..3d225770e8d5b 100644
--- a/llvm/lib/Frontend/HLSL/CMakeLists.txt
+++ b/llvm/lib/Frontend/HLSL/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_llvm_component_library(LLVMFrontendHLSL
   CBuffer.cpp
+  HLSLBinding.cpp
   HLSLResource.cpp
   HLSLRootSignature.cpp
   RootSignatureMetadata.cpp
diff --git a/llvm/lib/Frontend/HLSL/HLSLBinding.cpp b/llvm/lib/Frontend/HLSL/HLSLBinding.cpp
new file mode 100644
index 0000000000000..c2ae4a44bb5d0
--- /dev/null
+++ b/llvm/lib/Frontend/HLSL/HLSLBinding.cpp
@@ -0,0 +1,141 @@
+//===- HLSLBinding.cpp - Representation for resource bindings in HLSL -----===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Frontend/HLSL/HLSLBinding.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace llvm;
+using namespace hlsl;
+
+std::optional<uint32_t>
+BindingInfo::findAvailableBinding(dxil::ResourceClass RC, uint32_t Space,
+                                  int32_t Size) {
+  BindingSpaces &BS = getBindingSpaces(RC);
+  RegisterSpace &RS = BS.getOrInsertSpace(Space);
+  return RS.findAvailableBinding(Size);
+}
+
+BindingInfo::RegisterSpace &
+BindingInfo::BindingSpaces::getOrInsertSpace(uint32_t Space) {
+  for (auto *I = Spaces.begin(); I != Spaces.end(); ++I) {
+    if (I->Space == Space)
+      return *I;
+    if (I->Space < Space)
+      continue;
+    return *Spaces.insert(I, Space);
+  }
+  return Spaces.emplace_back(Space);
+}
+
+std::optional<uint32_t>
+BindingInfo::RegisterSpace::findAvailableBinding(int32_t Size) {
+  assert((Size == -1 || Size > 0) && "invalid size");
+
+  if (FreeRanges.empty())
+    return std::nullopt;
+
+  // unbounded array
+  if (Size == -1) {
+    BindingRange &Last = FreeRanges.back();
+    if (Last.UpperBound != UINT32_MAX)
+      // this space is already occupied by an unbounded array
+      return std::nullopt;
+    uint32_t RegSlot = Last.LowerBound;
+    FreeRanges.pop_back();
+    return RegSlot;
+  }
+
+  // single resource or fixed-size array
+  for (BindingRange &R : FreeRanges) {
+    // compare the size as uint64_t to prevent overflow for range (0,
+    // UINT32_MAX)
+    if ((uint64_t)R.UpperBound - R.LowerBound + 1 < (uint64_t)Size)
+      continue;
+    uint32_t RegSlot = R.LowerBound;
+    // This might create a range where (LowerBound == UpperBound + 1). When
+    // that happens, the next time this function is called the range will
+    // skipped over by the check above (at this point Size is always > 0).
+    R.LowerBound += Size;
+    return RegSlot;
+  }
+
+  return std::nullopt;
+}
+
+BindingInfo BindingInfoBuilder::calculateBindingInfo(
+    llvm::function_ref<void(const BindingInfoBuilder &Builder,
+                            const Binding &Overlapping)>
+        ReportOverlap) {
+  // sort all the collected bindings
+  llvm::stable_sort(Bindings);
+
+  // remove duplicates
+  Binding *NewEnd = llvm::unique(Bindings);
+  if (NewEnd != Bindings.end())
+    Bindings.erase(NewEnd);
+
+  BindingInfo Info;
+
+  // Go over the sorted bindings and build up lists of free register ranges
+  // for each binding type and used spaces. Bindings are sorted by resource
+  // class, space, and lower bound register slot.
+  BindingInfo::BindingSpaces *BS =
+      &Info.getBindingSpaces(dxil::ResourceClass::SRV);
+  for (const Binding &B : Bindings) {
+    if (BS->RC != B.RC)
+      // move to the next resource class spaces
+      BS = &Info.getBindingSpaces(B.RC);
+
+    BindingInfo::RegisterSpace *S = BS->Spaces.empty()
+                                        ? &BS->Spaces.emplace_back(B.Space)
+                                        : &BS->Spaces.back();
+    assert(S->Space <= B.Space && "bindings not sorted correctly?");
+    if (B.Space != S->Space)
+      // add new space
+      S = &BS->Spaces.emplace_b...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jul 30, 2025

@llvm/pr-subscribers-backend-directx

Author: Justin Bogner (bogner)

Changes

We extract the binding logic out of the DXILResource analysis passes into the
FrontendHLSL library. This will allow us to use this logic for resource and
root signature bindings in both the DirectX backend and the HLSL frontend.


Patch is 44.31 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/150633.diff

9 Files Affected:

  • (modified) llvm/include/llvm/Analysis/DXILResource.h (+11-71)
  • (added) llvm/include/llvm/Frontend/HLSL/HLSLBinding.h (+162)
  • (modified) llvm/lib/Analysis/CMakeLists.txt (+1)
  • (modified) llvm/lib/Analysis/DXILResource.cpp (+6-130)
  • (modified) llvm/lib/Frontend/HLSL/CMakeLists.txt (+1)
  • (added) llvm/lib/Frontend/HLSL/HLSLBinding.cpp (+141)
  • (modified) llvm/unittests/Frontend/CMakeLists.txt (+1)
  • (added) llvm/unittests/Frontend/HLSLBindingTest.cpp (+273)
  • (modified) llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp (+7-164)
diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h
index 956dcbcc33305..93c6bfb057ef5 100644
--- a/llvm/include/llvm/Analysis/DXILResource.h
+++ b/llvm/include/llvm/Analysis/DXILResource.h
@@ -12,6 +12,7 @@
 #include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Frontend/HLSL/HLSLBinding.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/GlobalVariable.h"
 #include "llvm/IR/PassManager.h"
@@ -633,86 +634,25 @@ LLVM_ABI ModulePass *createDXILResourceWrapperPassPass();
 // register slots to resources with implicit bindings, and in a
 // post-optimization validation pass that will raise diagnostic about
 // overlapping bindings.
-//
-// For example for these resource bindings:
-//
-// RWBuffer<float> A[10] : register(u3);
-// RWBuffer<float> B[] : register(u5, space2)
-//
-// The analysis result for UAV binding type will look like this:
-//
-// UAVSpaces {
-//   ResClass = ResourceClass::UAV,
-//   Spaces = {
-//     { Space = 0, FreeRanges = {{ 0, 2 }, { 13, UINT32_MAX }} },
-//     { Space = 2, FreeRanges = {{ 0, 4 }} }
-//   }
-// }
-//
 class DXILResourceBindingInfo {
-public:
-  struct BindingRange {
-    uint32_t LowerBound;
-    uint32_t UpperBound;
-    BindingRange(uint32_t LB, uint32_t UB) : LowerBound(LB), UpperBound(UB) {}
-  };
-
-  struct RegisterSpace {
-    uint32_t Space;
-    SmallVector<BindingRange> FreeRanges;
-    RegisterSpace(uint32_t Space) : Space(Space) {
-      FreeRanges.emplace_back(0, UINT32_MAX);
-    }
-    // Size == -1 means unbounded array
-    LLVM_ABI std::optional<uint32_t> findAvailableBinding(int32_t Size);
-  };
-
-  struct BindingSpaces {
-    dxil::ResourceClass RC;
-    llvm::SmallVector<RegisterSpace> Spaces;
-    BindingSpaces(dxil::ResourceClass RC) : RC(RC) {}
-    LLVM_ABI RegisterSpace &getOrInsertSpace(uint32_t Space);
-  };
-
-private:
-  BindingSpaces SRVSpaces, UAVSpaces, CBufferSpaces, SamplerSpaces;
-  bool ImplicitBinding;
-  bool OverlappingBinding;
+  hlsl::BindingInfo Bindings;
+  bool HasImplicitBinding = false;
+  bool HasOverlappingBinding = false;
 
   // Populate the resource binding info given explicit resource binding calls
   // in the module.
   void populate(Module &M, DXILResourceTypeMap &DRTM);
 
 public:
-  DXILResourceBindingInfo()
-      : SRVSpaces(dxil::ResourceClass::SRV),
-        UAVSpaces(dxil::ResourceClass::UAV),
-        CBufferSpaces(dxil::ResourceClass::CBuffer),
-        SamplerSpaces(dxil::ResourceClass::Sampler), ImplicitBinding(false),
-        OverlappingBinding(false) {}
-
-  bool hasImplicitBinding() const { return ImplicitBinding; }
-  void setHasImplicitBinding(bool Value) { ImplicitBinding = Value; }
-  bool hasOverlappingBinding() const { return OverlappingBinding; }
-
-  BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) {
-    switch (RC) {
-    case dxil::ResourceClass::SRV:
-      return SRVSpaces;
-    case dxil::ResourceClass::UAV:
-      return UAVSpaces;
-    case dxil::ResourceClass::CBuffer:
-      return CBufferSpaces;
-    case dxil::ResourceClass::Sampler:
-      return SamplerSpaces;
-    }
+  bool hasImplicitBinding() const { return HasImplicitBinding; }
+  void setHasImplicitBinding(bool Value) { HasImplicitBinding = Value; }
+  bool hasOverlappingBinding() const { return HasOverlappingBinding; }
+  void setHasOverlappingBinding(bool Value) { HasOverlappingBinding = Value; }
 
-    llvm_unreachable("Invalid resource class");
-  }
-
-  // Size == -1 means unbounded array
   LLVM_ABI std::optional<uint32_t>
-  findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size);
+  findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size) {
+    return Bindings.findAvailableBinding(RC, Space, Size);
+  }
 
   friend class DXILResourceBindingAnalysis;
   friend class DXILResourceBindingWrapperPass;
diff --git a/llvm/include/llvm/Frontend/HLSL/HLSLBinding.h b/llvm/include/llvm/Frontend/HLSL/HLSLBinding.h
new file mode 100644
index 0000000000000..1591bc9ae1f3a
--- /dev/null
+++ b/llvm/include/llvm/Frontend/HLSL/HLSLBinding.h
@@ -0,0 +1,162 @@
+//===- HLSLBinding.h - Representation for resource bindings in HLSL -------===//
+//
+// 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 contains objects to represent resource bindings.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_FRONTEND_HLSL_HLSLBINDING_H
+#define LLVM_FRONTEND_HLSL_HLSLBINDING_H
+
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/DXILABI.h"
+#include "llvm/Support/ErrorHandling.h"
+
+namespace llvm {
+namespace hlsl {
+
+/// BindingInfo represents the ranges of bindings and free space for each
+/// `dxil::ResourceClass`. This can represent HLSL-level bindings as well as
+/// bindings described in root signatures, and can be used for analysis of
+/// overlapping or missing bindings as well as for finding space for implicit
+/// bindings.
+///
+/// As an example, given these resource bindings:
+///
+/// RWBuffer<float> A[10] : register(u3);
+/// RWBuffer<float> B[] : register(u5, space2)
+///
+/// The binding info for UAV bindings should look like this:
+///
+/// UAVSpaces {
+///   ResClass = ResourceClass::UAV,
+///   Spaces = {
+///     { Space = 0, FreeRanges = {{ 0, 2 }, { 13, UINT32_MAX }} },
+///     { Space = 2, FreeRanges = {{ 0, 4 }} }
+///   }
+/// }
+class BindingInfo {
+public:
+  struct BindingRange {
+    uint32_t LowerBound;
+    uint32_t UpperBound;
+    BindingRange(uint32_t LB, uint32_t UB) : LowerBound(LB), UpperBound(UB) {}
+  };
+
+  struct RegisterSpace {
+    uint32_t Space;
+    SmallVector<BindingRange> FreeRanges;
+    RegisterSpace(uint32_t Space) : Space(Space) {
+      FreeRanges.emplace_back(0, UINT32_MAX);
+    }
+    // Size == -1 means unbounded array
+    LLVM_ABI std::optional<uint32_t> findAvailableBinding(int32_t Size);
+  };
+
+  struct BindingSpaces {
+    dxil::ResourceClass RC;
+    llvm::SmallVector<RegisterSpace> Spaces;
+    BindingSpaces(dxil::ResourceClass RC) : RC(RC) {}
+    LLVM_ABI RegisterSpace &getOrInsertSpace(uint32_t Space);
+  };
+
+private:
+  BindingSpaces SRVSpaces{dxil::ResourceClass::SRV};
+  BindingSpaces UAVSpaces{dxil::ResourceClass::UAV};
+  BindingSpaces CBufferSpaces{dxil::ResourceClass::CBuffer};
+  BindingSpaces SamplerSpaces{dxil::ResourceClass::Sampler};
+
+public:
+  BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) {
+    switch (RC) {
+    case dxil::ResourceClass::SRV:
+      return SRVSpaces;
+    case dxil::ResourceClass::UAV:
+      return UAVSpaces;
+    case dxil::ResourceClass::CBuffer:
+      return CBufferSpaces;
+    case dxil::ResourceClass::Sampler:
+      return SamplerSpaces;
+    }
+
+    llvm_unreachable("Invalid resource class");
+  }
+  const BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) const {
+    return const_cast<BindingInfo *>(this)->getBindingSpaces(RC);
+  }
+
+  // Size == -1 means unbounded array
+  LLVM_ABI std::optional<uint32_t>
+  findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size);
+
+  friend class BindingInfoBuilder;
+};
+
+/// Builder class for creating a /c BindingInfo.
+class BindingInfoBuilder {
+public:
+  struct Binding {
+    dxil::ResourceClass RC;
+    uint32_t Space;
+    uint32_t LowerBound;
+    uint32_t UpperBound;
+    const void *Cookie;
+
+    Binding(dxil::ResourceClass RC, uint32_t Space, uint32_t LowerBound,
+            uint32_t UpperBound, const void *Cookie)
+        : RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound),
+          Cookie(Cookie) {}
+
+    bool isUnbounded() const { return UpperBound == ~0U; }
+
+    bool operator==(const Binding &RHS) const {
+      return std::tie(RC, Space, LowerBound, UpperBound, Cookie) ==
+             std::tie(RHS.RC, RHS.Space, RHS.LowerBound, RHS.UpperBound,
+                      RHS.Cookie);
+    }
+    bool operator!=(const Binding &RHS) const { return !(*this == RHS); }
+
+    bool operator<(const Binding &RHS) const {
+      return std::tie(RC, Space, LowerBound) <
+             std::tie(RHS.RC, RHS.Space, RHS.LowerBound);
+    }
+  };
+
+private:
+  SmallVector<Binding> Bindings;
+
+public:
+  void trackBinding(dxil::ResourceClass RC, uint32_t Space, uint32_t LowerBound,
+                    uint32_t UpperBound, const void *Cookie) {
+    Bindings.emplace_back(RC, Space, LowerBound, UpperBound, Cookie);
+  }
+  /// Calculate the binding info - \c ReportOverlap will be called once for each
+  /// overlapping binding.
+  BindingInfo calculateBindingInfo(
+      llvm::function_ref<void(const BindingInfoBuilder &Builder,
+                              const Binding &Overlapping)>
+          ReportOverlap);
+
+  /// Calculate the binding info - \c HasOverlap will be set to indicate whether
+  /// there are any overlapping bindings.
+  BindingInfo calculateBindingInfo(bool &HasOverlap) {
+    HasOverlap = false;
+    return calculateBindingInfo(
+        [&HasOverlap](auto, auto) { HasOverlap = true; });
+  }
+
+  /// For use in the \c ReportOverlap callback of \c calculateBindingInfo -
+  /// finds a binding that the \c ReportedBinding overlaps with.
+  const Binding &findOverlapping(const Binding &ReportedBinding) const;
+};
+
+} // namespace hlsl
+} // namespace llvm
+
+#endif // LLVM_FRONTEND_HLSL_HLSLBINDING_H
diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt
index cfde787b17790..16dd6f8b86006 100644
--- a/llvm/lib/Analysis/CMakeLists.txt
+++ b/llvm/lib/Analysis/CMakeLists.txt
@@ -175,6 +175,7 @@ add_llvm_component_library(LLVMAnalysis
   LINK_COMPONENTS
   BinaryFormat
   Core
+  FrontendHLSL
   Object
   ProfileData
   Support
diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp
index 1959ab6e510a3..629fa7cddb9d4 100644
--- a/llvm/lib/Analysis/DXILResource.cpp
+++ b/llvm/lib/Analysis/DXILResource.cpp
@@ -995,18 +995,7 @@ SmallVector<dxil::ResourceInfo *> DXILResourceMap::findByUse(const Value *Key) {
 //===----------------------------------------------------------------------===//
 
 void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {
-  struct Binding {
-    ResourceClass RC;
-    uint32_t Space;
-    uint32_t LowerBound;
-    uint32_t UpperBound;
-    Value *Name;
-    Binding(ResourceClass RC, uint32_t Space, uint32_t LowerBound,
-            uint32_t UpperBound, Value *Name)
-        : RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound),
-          Name(Name) {}
-  };
-  SmallVector<Binding> Bindings;
+  hlsl::BindingInfoBuilder Builder;
 
   // collect all of the llvm.dx.resource.handlefrombinding calls;
   // make a note if there is llvm.dx.resource.handlefromimplicitbinding
@@ -1036,133 +1025,20 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {
           assert((Size < 0 || (unsigned)LowerBound + Size - 1 <= UINT32_MAX) &&
                  "upper bound register overflow");
           uint32_t UpperBound = Size < 0 ? UINT32_MAX : LowerBound + Size - 1;
-          Bindings.emplace_back(RTI.getResourceClass(), Space, LowerBound,
-                                UpperBound, Name);
+          Builder.trackBinding(RTI.getResourceClass(), Space, LowerBound,
+                               UpperBound, Name);
         }
       break;
     }
     case Intrinsic::dx_resource_handlefromimplicitbinding: {
-      ImplicitBinding = true;
+      HasImplicitBinding = true;
       break;
     }
     }
   }
 
-  // sort all the collected bindings
-  llvm::stable_sort(Bindings, [](auto &LHS, auto &RHS) {
-    return std::tie(LHS.RC, LHS.Space, LHS.LowerBound) <
-           std::tie(RHS.RC, RHS.Space, RHS.LowerBound);
-  });
-
-  // remove duplicates
-  Binding *NewEnd = llvm::unique(Bindings, [](auto &LHS, auto &RHS) {
-    return std::tie(LHS.RC, LHS.Space, LHS.LowerBound, LHS.UpperBound,
-                    LHS.Name) == std::tie(RHS.RC, RHS.Space, RHS.LowerBound,
-                                          RHS.UpperBound, RHS.Name);
-  });
-  if (NewEnd != Bindings.end())
-    Bindings.erase(NewEnd);
-
-  // Go over the sorted bindings and build up lists of free register ranges
-  // for each binding type and used spaces. Bindings are sorted by resource
-  // class, space, and lower bound register slot.
-  BindingSpaces *BS = &SRVSpaces;
-  for (const Binding &B : Bindings) {
-    if (BS->RC != B.RC)
-      // move to the next resource class spaces
-      BS = &getBindingSpaces(B.RC);
-
-    RegisterSpace *S = BS->Spaces.empty() ? &BS->Spaces.emplace_back(B.Space)
-                                          : &BS->Spaces.back();
-    assert(S->Space <= B.Space && "bindings not sorted correctly?");
-    if (B.Space != S->Space)
-      // add new space
-      S = &BS->Spaces.emplace_back(B.Space);
-
-    // The space is full - there are no free slots left, or the rest of the
-    // slots are taken by an unbounded array. Set flag to report overlapping
-    // binding later.
-    if (S->FreeRanges.empty() || S->FreeRanges.back().UpperBound < UINT32_MAX) {
-      OverlappingBinding = true;
-      continue;
-    }
-
-    // adjust the last free range lower bound, split it in two, or remove it
-    BindingRange &LastFreeRange = S->FreeRanges.back();
-    if (LastFreeRange.LowerBound == B.LowerBound) {
-      if (B.UpperBound < UINT32_MAX)
-        LastFreeRange.LowerBound = B.UpperBound + 1;
-      else
-        S->FreeRanges.pop_back();
-    } else if (LastFreeRange.LowerBound < B.LowerBound) {
-      LastFreeRange.UpperBound = B.LowerBound - 1;
-      if (B.UpperBound < UINT32_MAX)
-        S->FreeRanges.emplace_back(B.UpperBound + 1, UINT32_MAX);
-    } else {
-      OverlappingBinding = true;
-      if (B.UpperBound < UINT32_MAX)
-        LastFreeRange.LowerBound =
-            std::max(LastFreeRange.LowerBound, B.UpperBound + 1);
-      else
-        S->FreeRanges.pop_back();
-    }
-  }
-}
-
-// returns std::nulopt if binding could not be found in given space
-std::optional<uint32_t>
-DXILResourceBindingInfo::findAvailableBinding(dxil::ResourceClass RC,
-                                              uint32_t Space, int32_t Size) {
-  BindingSpaces &BS = getBindingSpaces(RC);
-  RegisterSpace &RS = BS.getOrInsertSpace(Space);
-  return RS.findAvailableBinding(Size);
-}
-
-DXILResourceBindingInfo::RegisterSpace &
-DXILResourceBindingInfo::BindingSpaces::getOrInsertSpace(uint32_t Space) {
-  for (auto *I = Spaces.begin(); I != Spaces.end(); ++I) {
-    if (I->Space == Space)
-      return *I;
-    if (I->Space < Space)
-      continue;
-    return *Spaces.insert(I, Space);
-  }
-  return Spaces.emplace_back(Space);
-}
-
-std::optional<uint32_t>
-DXILResourceBindingInfo::RegisterSpace::findAvailableBinding(int32_t Size) {
-  assert((Size == -1 || Size > 0) && "invalid size");
-
-  if (FreeRanges.empty())
-    return std::nullopt;
-
-  // unbounded array
-  if (Size == -1) {
-    BindingRange &Last = FreeRanges.back();
-    if (Last.UpperBound != UINT32_MAX)
-      // this space is already occupied by an unbounded array
-      return std::nullopt;
-    uint32_t RegSlot = Last.LowerBound;
-    FreeRanges.pop_back();
-    return RegSlot;
-  }
-
-  // single resource or fixed-size array
-  for (BindingRange &R : FreeRanges) {
-    // compare the size as uint64_t to prevent overflow for range (0,
-    // UINT32_MAX)
-    if ((uint64_t)R.UpperBound - R.LowerBound + 1 < (uint64_t)Size)
-      continue;
-    uint32_t RegSlot = R.LowerBound;
-    // This might create a range where (LowerBound == UpperBound + 1). When
-    // that happens, the next time this function is called the range will
-    // skipped over by the check above (at this point Size is always > 0).
-    R.LowerBound += Size;
-    return RegSlot;
-  }
-
-  return std::nullopt;
+  Bindings = Builder.calculateBindingInfo(
+      [this](auto, auto) { this->HasOverlappingBinding = true; });
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Frontend/HLSL/CMakeLists.txt b/llvm/lib/Frontend/HLSL/CMakeLists.txt
index 534346920ff19..3d225770e8d5b 100644
--- a/llvm/lib/Frontend/HLSL/CMakeLists.txt
+++ b/llvm/lib/Frontend/HLSL/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_llvm_component_library(LLVMFrontendHLSL
   CBuffer.cpp
+  HLSLBinding.cpp
   HLSLResource.cpp
   HLSLRootSignature.cpp
   RootSignatureMetadata.cpp
diff --git a/llvm/lib/Frontend/HLSL/HLSLBinding.cpp b/llvm/lib/Frontend/HLSL/HLSLBinding.cpp
new file mode 100644
index 0000000000000..c2ae4a44bb5d0
--- /dev/null
+++ b/llvm/lib/Frontend/HLSL/HLSLBinding.cpp
@@ -0,0 +1,141 @@
+//===- HLSLBinding.cpp - Representation for resource bindings in HLSL -----===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Frontend/HLSL/HLSLBinding.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace llvm;
+using namespace hlsl;
+
+std::optional<uint32_t>
+BindingInfo::findAvailableBinding(dxil::ResourceClass RC, uint32_t Space,
+                                  int32_t Size) {
+  BindingSpaces &BS = getBindingSpaces(RC);
+  RegisterSpace &RS = BS.getOrInsertSpace(Space);
+  return RS.findAvailableBinding(Size);
+}
+
+BindingInfo::RegisterSpace &
+BindingInfo::BindingSpaces::getOrInsertSpace(uint32_t Space) {
+  for (auto *I = Spaces.begin(); I != Spaces.end(); ++I) {
+    if (I->Space == Space)
+      return *I;
+    if (I->Space < Space)
+      continue;
+    return *Spaces.insert(I, Space);
+  }
+  return Spaces.emplace_back(Space);
+}
+
+std::optional<uint32_t>
+BindingInfo::RegisterSpace::findAvailableBinding(int32_t Size) {
+  assert((Size == -1 || Size > 0) && "invalid size");
+
+  if (FreeRanges.empty())
+    return std::nullopt;
+
+  // unbounded array
+  if (Size == -1) {
+    BindingRange &Last = FreeRanges.back();
+    if (Last.UpperBound != UINT32_MAX)
+      // this space is already occupied by an unbounded array
+      return std::nullopt;
+    uint32_t RegSlot = Last.LowerBound;
+    FreeRanges.pop_back();
+    return RegSlot;
+  }
+
+  // single resource or fixed-size array
+  for (BindingRange &R : FreeRanges) {
+    // compare the size as uint64_t to prevent overflow for range (0,
+    // UINT32_MAX)
+    if ((uint64_t)R.UpperBound - R.LowerBound + 1 < (uint64_t)Size)
+      continue;
+    uint32_t RegSlot = R.LowerBound;
+    // This might create a range where (LowerBound == UpperBound + 1). When
+    // that happens, the next time this function is called the range will
+    // skipped over by the check above (at this point Size is always > 0).
+    R.LowerBound += Size;
+    return RegSlot;
+  }
+
+  return std::nullopt;
+}
+
+BindingInfo BindingInfoBuilder::calculateBindingInfo(
+    llvm::function_ref<void(const BindingInfoBuilder &Builder,
+                            const Binding &Overlapping)>
+        ReportOverlap) {
+  // sort all the collected bindings
+  llvm::stable_sort(Bindings);
+
+  // remove duplicates
+  Binding *NewEnd = llvm::unique(Bindings);
+  if (NewEnd != Bindings.end())
+    Bindings.erase(NewEnd);
+
+  BindingInfo Info;
+
+  // Go over the sorted bindings and build up lists of free register ranges
+  // for each binding type and used spaces. Bindings are sorted by resource
+  // class, space, and lower bound register slot.
+  BindingInfo::BindingSpaces *BS =
+      &Info.getBindingSpaces(dxil::ResourceClass::SRV);
+  for (const Binding &B : Bindings) {
+    if (BS->RC != B.RC)
+      // move to the next resource class spaces
+      BS = &Info.getBindingSpaces(B.RC);
+
+    BindingInfo::RegisterSpace *S = BS->Spaces.empty()
+                                        ? &BS->Spaces.emplace_back(B.Space)
+                                        : &BS->Spaces.back();
+    assert(S->Space <= B.Space && "bindings not sorted correctly?");
+    if (B.Space != S->Space)
+      // add new space
+      S = &BS->Spaces.emplace_b...
[truncated]

@bogner bogner force-pushed the 2025-07-25-hlslbinding branch from 0c00e30 to 55981ec Compare July 30, 2025 19:13
Copy link

github-actions bot commented Jul 30, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@joaosaffran joaosaffran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit, LGTM

Copy link
Member

@hekota hekota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

bogner added 4 commits July 30, 2025 14:39
Use ~0u for the end-of-range sigil, and numeric_limits for math involving the
limits of uint32_t and int32_t.
@bogner bogner merged commit 3f066f5 into llvm:main Jul 31, 2025
10 checks passed
rupprecht added a commit to rupprecht/llvm-project that referenced this pull request Jul 31, 2025
rupprecht added a commit that referenced this pull request Jul 31, 2025
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 31, 2025

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-sie-win running on sie-win-worker while building llvm at step 7 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/46/builds/21000

Here is the relevant piece of the build log for the reference
Step 7 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'lld :: COFF/import_weak_alias.test' FAILED ********************
Exit Code: 3221225477

Command Output (stdout):
--
# RUN: at line 3
split-file Z:\b\llvm-clang-x86_64-sie-win\llvm-project\lld\test\COFF\import_weak_alias.test Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir
# executed command: split-file 'Z:\b\llvm-clang-x86_64-sie-win\llvm-project\lld\test\COFF\import_weak_alias.test' 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir'
# note: command had no output on stdout or stderr
# RUN: at line 4
z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe --filetype=obj -triple=x86_64-windows-msvc Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/foo.s -o Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj
# executed command: 'z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe' --filetype=obj -triple=x86_64-windows-msvc 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/foo.s' -o 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj'
# note: command had no output on stdout or stderr
# RUN: at line 5
z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe --filetype=obj -triple=x86_64-windows-msvc Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/qux.s -o Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj
# executed command: 'z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe' --filetype=obj -triple=x86_64-windows-msvc 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/qux.s' -o 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj'
# note: command had no output on stdout or stderr
# RUN: at line 6
z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj -out:Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dll -dll
# executed command: 'z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe' 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj' 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj' '-out:Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dll' -dll
# .---command stderr------------
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
# | Stack dump:
# | 0.	Program arguments: z:\\b\\llvm-clang-x86_64-sie-win\\build\\bin\\lld-link.exe Z:\\b\\llvm-clang-x86_64-sie-win\\build\\tools\\lld\\test\\COFF\\Output\\import_weak_alias.test.tmp.qux.obj Z:\\b\\llvm-clang-x86_64-sie-win\\build\\tools\\lld\\test\\COFF\\Output\\import_weak_alias.test.tmp.foo.obj -out:Z:\\b\\llvm-clang-x86_64-sie-win\\build\\tools\\lld\\test\\COFF\\Output\\import_weak_alias.test.tmp.dll -dll
# | Exception Code: 0xC0000005
# | #0 0x00007ff8e5361b39 (C:\Windows\System32\KERNELBASE.dll+0x41b39)
# | #1 0x00007ff62da5b8f8 (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0xcb8f8)
# | #2 0x00007ff62dae270b (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0x15270b)
# | #3 0x00007ff62da3d78a (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0xad78a)
# | #4 0x00007ff62da3d7f4 (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0xad7f4)
# | #5 0x00007ff6301ada84 (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0x281da84)
# | #6 0x00007ff8e7947ac4 (C:\Windows\System32\KERNEL32.DLL+0x17ac4)
# | #7 0x00007ff8e879a8c1 (C:\Windows\SYSTEM32\ntdll.dll+0x5a8c1)
# `-----------------------------
# error: command failed with exit status: 0xc0000005

--

********************


@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 31, 2025

LLVM Buildbot has detected a new failure on builder ppc64le-lld-multistage-test running on ppc64le-lld-multistage-test while building llvm at step 12 "build-stage2-unified-tree".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/168/builds/14779

Here is the relevant piece of the build log for the reference
Step 12 (build-stage2-unified-tree) failure: build (failure)
...
14.456 [1/8/16] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/ClangASTPropertiesEmitter.cpp.o
17.424 [1/7/17] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/ClangOptionDocEmitter.cpp.o
31.286 [1/6/18] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/RISCVVEmitter.cpp.o
34.606 [1/5/19] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/SveEmitter.cpp.o
36.203 [1/4/20] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/ClangDiagnosticsEmitter.cpp.o
48.294 [1/3/21] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/MveEmitter.cpp.o
51.741 [1/2/22] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/NeonEmitter.cpp.o
96.997 [1/1/23] Building CXX object tools/clang/utils/TableGen/CMakeFiles/clang-tblgen.dir/ClangAttrEmitter.cpp.o
97.447 [0/1/24] Linking CXX executable bin/clang-tblgen
80.486 [4082/959/1591] Building CXX object unittests/Frontend/CMakeFiles/LLVMFrontendTests.dir/HLSLBindingTest.cpp.o
FAILED: unittests/Frontend/CMakeFiles/LLVMFrontendTests.dir/HLSLBindingTest.cpp.o 
ccache /home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/install/stage1/bin/clang++ -DEXPERIMENTAL_KEY_INSTRUCTIONS -DGTEST_HAS_RTTI=0 -DLLVM_BUILD_STATIC -D_DEBUG -D_GLIBCXX_ASSERTIONS -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -D__SHORT_FILE__=\"HLSLBindingTest.cpp\" -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/build/stage2/unittests/Frontend -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/llvm/unittests/Frontend -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/build/stage2/include -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/llvm/include -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/third-party/unittest/googletest/include -I/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/third-party/unittest/googlemock/include -fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -pedantic -Wno-long-long -Wc++98-compat-extra-semi -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion -Wmisleading-indentation -Wctad-maybe-unsupported -fdiagnostics-color -ffunction-sections -fdata-sections -O3 -DNDEBUG -std=c++17  -Wno-variadic-macros -Wno-gnu-zero-variadic-macro-arguments -fno-exceptions -funwind-tables -fno-rtti -UNDEBUG -Wno-suggest-override -MD -MT unittests/Frontend/CMakeFiles/LLVMFrontendTests.dir/HLSLBindingTest.cpp.o -MF unittests/Frontend/CMakeFiles/LLVMFrontendTests.dir/HLSLBindingTest.cpp.o.d -o unittests/Frontend/CMakeFiles/LLVMFrontendTests.dir/HLSLBindingTest.cpp.o -c /home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/llvm/unittests/Frontend/HLSLBindingTest.cpp
/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/llvm/unittests/Frontend/HLSLBindingTest.cpp:164:54: error: variable 'ID1' is uninitialized when passed as a const pointer argument here [-Werror,-Wuninitialized-const-pointer]
  164 |                        /*UpperBound=*/5, /*Cookie=*/&ID1);
      |                                                      ^~~
/home/buildbots/llvm-external-buildbots/workers/ppc64le-lld-multistage-test/ppc64le-lld-multistage-test/llvm-project/llvm/unittests/Frontend/HLSLBindingTest.cpp:166:54: error: variable 'ID2' is uninitialized when passed as a const pointer argument here [-Werror,-Wuninitialized-const-pointer]
  166 |                        /*UpperBound=*/5, /*Cookie=*/&ID2);
      |                                                      ^~~
2 errors generated.
137.239 [4082/32/2518] Building CXX object lib/Analysis/CMakeFiles/LLVMAnalysis.dir/ScalarEvolution.cpp.o
139.642 [4082/30/2520] Building CXX object lib/CodeGen/CMakeFiles/LLVMCodeGen.dir/CodeGenPrepare.cpp.o
140.333 [4082/29/2521] Building CXX object lib/Transforms/Vectorize/CMakeFiles/LLVMVectorize.dir/VPlanTransforms.cpp.o
140.399 [4082/28/2522] Building CXX object lib/Transforms/Scalar/CMakeFiles/LLVMScalarOpts.dir/LoopStrengthReduce.cpp.o
140.674 [4082/27/2523] Building CXX object lib/CodeGen/AsmPrinter/CMakeFiles/LLVMAsmPrinter.dir/DwarfDebug.cpp.o
140.980 [4082/26/2524] Building CXX object lib/Passes/CMakeFiles/LLVMPasses.dir/PassBuilderPipelines.cpp.o
142.665 [4082/25/2525] Building CXX object lib/CodeGen/CMakeFiles/LLVMCodeGen.dir/MachinePipeliner.cpp.o
143.142 [4082/24/2526] Building CXX object unittests/ProfileData/CMakeFiles/ProfileDataTests.dir/MemProfTest.cpp.o
144.416 [4082/23/2527] Building CXX object unittests/Transforms/Vectorize/CMakeFiles/VectorizeTests.dir/VPlanTest.cpp.o
147.882 [4082/22/2528] Building CXX object lib/Transforms/IPO/CMakeFiles/LLVMipo.dir/Attributor.cpp.o
148.314 [4082/21/2529] Building CXX object lib/Transforms/Utils/CMakeFiles/LLVMTransformUtils.dir/SimplifyCFG.cpp.o
148.651 [4082/20/2530] Building CXX object unittests/IR/CMakeFiles/IRTests.dir/MetadataTest.cpp.o
148.679 [4082/19/2531] Building CXX object tools/llvm-profdata/CMakeFiles/llvm-profdata.dir/llvm-profdata.cpp.o
148.862 [4082/18/2532] Building CXX object lib/CodeGen/CMakeFiles/LLVMCodeGen.dir/LiveDebugValues/InstrRefBasedImpl.cpp.o
153.486 [4082/17/2533] Building CXX object lib/Analysis/CMakeFiles/LLVMAnalysis.dir/TargetLibraryInfo.cpp.o
156.285 [4082/16/2534] Building CXX object unittests/ProfileData/CMakeFiles/ProfileDataTests.dir/InstrProfTest.cpp.o
169.992 [4082/15/2535] Building CXX object unittests/Frontend/CMakeFiles/LLVMFrontendTests.dir/OpenMPDecompositionTest.cpp.o
173.654 [4082/14/2536] Building CXX object unittests/SandboxIR/CMakeFiles/SandboxIRTests.dir/SandboxIRTest.cpp.o
175.636 [4082/13/2537] Building CXX object unittests/Transforms/Scalar/CMakeFiles/ScalarTests.dir/LoopPassManagerTest.cpp.o
176.796 [4082/12/2538] Building CXX object unittests/IR/CMakeFiles/IRTests.dir/PassBuilderCallbacksTest.cpp.o
177.150 [4082/11/2539] Building CXX object unittests/ADT/CMakeFiles/ADTTests.dir/DenseMapTest.cpp.o
179.676 [4082/10/2540] Building CXX object lib/Transforms/Vectorize/CMakeFiles/LLVMVectorize.dir/LoopVectorize.cpp.o
183.237 [4082/9/2541] Building CXX object tools/obj2yaml/CMakeFiles/obj2yaml.dir/elf2yaml.cpp.o
186.070 [4082/8/2542] Building CXX object unittests/Frontend/CMakeFiles/LLVMFrontendTests.dir/OpenMPIRBuilderTest.cpp.o
191.147 [4082/7/2543] Building CXX object lib/Transforms/IPO/CMakeFiles/LLVMipo.dir/AttributorAttributes.cpp.o
192.790 [4082/6/2544] Building CXX object lib/CodeGen/SelectionDAG/CMakeFiles/LLVMSelectionDAG.dir/DAGCombiner.cpp.o
198.822 [4082/5/2545] Building CXX object lib/Transforms/IPO/CMakeFiles/LLVMipo.dir/OpenMPOpt.cpp.o
213.653 [4082/4/2546] Building CXX object lib/Transforms/IPO/CMakeFiles/LLVMipo.dir/MemProfContextDisambiguation.cpp.o
269.430 [4082/3/2547] Building CXX object lib/Passes/CMakeFiles/LLVMPasses.dir/PassBuilder.cpp.o
311.123 [4082/2/2548] Building CXX object lib/Transforms/Vectorize/CMakeFiles/LLVMVectorize.dir/SLPVectorizer.cpp.o

krishna2803 pushed a commit to krishna2803/llvm-project that referenced this pull request Aug 12, 2025
…0633)

We extract the binding logic out of the DXILResource analysis passes into the
FrontendHLSL library. This will allow us to use this logic for resource and
root signature bindings in both the DirectX backend and the HLSL frontend.
krishna2803 pushed a commit to krishna2803/llvm-project that referenced this pull request Aug 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:DirectX HLSL HLSL Language Support llvm:analysis Includes value tracking, cost tables and constant folding
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants