Skip to content

[HLSL][RootSignature] Implement validation of resource ranges for RootDescriptors #140962

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 8 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -13054,6 +13054,11 @@ def err_invalid_hlsl_resource_type: Error<
def err_hlsl_spirv_only: Error<"%0 is only available for the SPIR-V target">;
def err_hlsl_vk_literal_must_contain_constant: Error<"the argument to vk::Literal must be a vk::integral_constant">;

def err_hlsl_resource_range_overlap: Error<
"resource ranges %select{t|u|b|s}0[%1;%2] and %select{t|u|b|s}3[%4;%5] "
"overlap within space = %6 and visibility = "
"%select{All|Vertex|Hull|Domain|Geometry|Pixel|Amplification|Mesh}7">;

// Layout randomization diagnostics.
def err_non_designated_init_used : Error<
"a randomized struct can only be initialized with a designated initializer">;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Sema/SemaHLSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ class SemaHLSL : public SemaBase {
SourceLocation Loc, IdentifierInfo *DeclIdent,
SmallVector<llvm::hlsl::rootsig::RootElement> &Elements);

// Returns true when D is invalid and a diagnostic was produced
bool handleRootSignatureDecl(HLSLRootSignatureDecl *D, SourceLocation Loc);
void handleRootSignatureAttr(Decl *D, const ParsedAttr &AL);
void handleNumThreadsAttr(Decl *D, const ParsedAttr &AL);
void handleWaveSizeAttr(Decl *D, const ParsedAttr &AL);
Expand Down
131 changes: 130 additions & 1 deletion clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Frontend/HLSL/HLSLRootSignatureUtils.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/DXILABI.h"
#include "llvm/Support/ErrorHandling.h"
Expand Down Expand Up @@ -1068,10 +1069,139 @@ void SemaHLSL::ActOnFinishRootSignatureDecl(
SemaRef.getASTContext(), /*DeclContext=*/SemaRef.CurContext, Loc,
DeclIdent, Elements);

// Perform validation of constructs here
if (handleRootSignatureDecl(SignatureDecl, Loc))
return;

SignatureDecl->setImplicit();
SemaRef.PushOnScopeChains(SignatureDecl, SemaRef.getCurScope());
}

bool SemaHLSL::handleRootSignatureDecl(HLSLRootSignatureDecl *D,
SourceLocation Loc) {
auto Elements = D->getRootElements();

// The following conducts analysis on resource ranges to detect and report
// any overlaps in resource ranges.
//
// A resource range overlaps with another resource range if they have:
// - equivalent ResourceClass (SRV, UAV, CBuffer, Sampler)
// - equivalent resource space
// - overlapping visbility
//
// The following algorithm is implemented in the following steps:
//
// 1. Collect RangeInfo from relevant RootElements:
// - RangeInfo will retain the interval, ResourceClass, Space and Visibility
// 2. Sort the RangeInfo's such that they are grouped together by
// ResourceClass and Space (GroupT defined below)
// 3. Iterate through the collected RangeInfos by their groups
// - For each group we will have a ResourceRange for each visibility
// - As we iterate through we will:
// A: Insert the current RangeInfo into the corresponding Visibility
// ResourceRange
// B: Check for overlap with any overlapping Visibility ResourceRange
using RangeInfo = llvm::hlsl::rootsig::RangeInfo;
using ResourceRange = llvm::hlsl::rootsig::ResourceRange;
using GroupT = std::pair<ResourceClass, /*Space*/ uint32_t>;

// 1. Collect RangeInfos
llvm::SmallVector<RangeInfo> Infos;
for (const auto &Elem : Elements) {
if (const auto *Descriptor =
std::get_if<llvm::hlsl::rootsig::RootDescriptor>(&Elem)) {
RangeInfo Info;
Info.LowerBound = Descriptor->Reg.Number;
Info.UpperBound = Info.LowerBound; // use inclusive ranges []

Info.Class =
llvm::dxil::ResourceClass(llvm::to_underlying(Descriptor->Type));
Info.Space = Descriptor->Space;
Info.Vis = Descriptor->Visibility;
Infos.push_back(Info);
}
}

// 2. Sort the RangeInfo's by their GroupT to form groupings
std::sort(Infos.begin(), Infos.end(), [](RangeInfo A, RangeInfo B) {
return std::tie(A.Class, A.Space) < std::tie(B.Class, B.Space);
});

// 3. First we will init our state to track:
if (Infos.size() == 0)
return false; // No ranges to overlap
GroupT CurGroup = {Infos[0].Class, Infos[0].Space};
bool HadOverlap = false;

// Create a ResourceRange for each Visibility
ResourceRange::MapT::Allocator Allocator;
SmallVector<ResourceRange, 8> Ranges = {
ResourceRange(Allocator), // All
ResourceRange(Allocator), // Vertex
ResourceRange(Allocator), // Hull
ResourceRange(Allocator), // Domain
ResourceRange(Allocator), // Geometry
ResourceRange(Allocator), // Pixel
ResourceRange(Allocator), // Amplification
ResourceRange(Allocator), // Mesh
};

// Reset the ResourceRanges for when we iterate through a new group
auto ClearRanges = [&Ranges]() {
for (ResourceRange &Range : Ranges)
Range.clear();
};

// Helper to report diagnostics
auto ReportOverlap = [this, Loc, &HadOverlap](const RangeInfo *Info,
const RangeInfo *OInfo) {
HadOverlap = true;
auto CommonVis = Info->Vis == llvm::hlsl::rootsig::ShaderVisibility::All
? OInfo->Vis
: Info->Vis;
this->Diag(Loc, diag::err_hlsl_resource_range_overlap)
<< llvm::to_underlying(Info->Class) << Info->LowerBound
<< Info->UpperBound << llvm::to_underlying(OInfo->Class)
<< OInfo->LowerBound << OInfo->UpperBound << Info->Space << CommonVis;
};

// 3: Iterate throught collected RangeInfos
for (const RangeInfo &Info : Infos) {
GroupT InfoGroup = {Info.Class, Info.Space};
// Reset our ResourceRanges when we enter a new group
if (CurGroup != InfoGroup) {
ClearRanges();
CurGroup = InfoGroup;
}

// 3A: Insert range info into corresponding Visibility ResourceRange
ResourceRange &VisRange = Ranges[llvm::to_underlying(Info.Vis)];
if (auto Overlapping = VisRange.insert(Info))
ReportOverlap(&Info, Overlapping.value());

// 3B: Check for overlap in all overlapping Visibility ResourceRanges
//
// If the range that we are inserting has ShaderVisiblity::All it needs to
// check for an overlap in all other visibility types as well.
// Otherwise, the range that is inserted needs to check that it does not
// overlap with ShaderVisibility::All.
//
// Maps will be an ArrayRef to all non-all visibility RangeMaps in the
// former case and it will be an ArrayRef to just the all visiblity
// RangeMap in the latter case.
MutableArrayRef<ResourceRange> OverlapRanges =
Info.Vis == llvm::hlsl::rootsig::ShaderVisibility::All
? MutableArrayRef<ResourceRange>{Ranges}.drop_front()
: MutableArrayRef<ResourceRange>{Ranges}.take_front();

for (ResourceRange &Range : OverlapRanges)
if (auto Overlapping = Range.getOverlapping(Info))
ReportOverlap(&Info, Overlapping.value());
}

return HadOverlap;
}

void SemaHLSL::handleRootSignatureAttr(Decl *D, const ParsedAttr &AL) {
if (AL.getNumArgs() != 1) {
Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
Expand All @@ -1093,7 +1223,6 @@ void SemaHLSL::handleRootSignatureAttr(Decl *D, const ParsedAttr &AL) {
if (SemaRef.LookupQualifiedName(R, D->getDeclContext()))
if (auto *SignatureDecl =
dyn_cast<HLSLRootSignatureDecl>(R.getFoundDecl())) {
// Perform validation of constructs here
D->addAttr(::new (getASTContext()) RootSignatureAttr(
getASTContext(), AL, Ident, SignatureDecl));
}
Expand Down
26 changes: 26 additions & 0 deletions clang/test/SemaHLSL/RootSignature-resource-ranges-err.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - %s -verify

#define Overlap0 "CBV(b42), CBV(b42)"

[RootSignature(Overlap0)] // expected-error {{resource ranges b[42;42] and b[42;42] overlap within space = 0 and visibility = All}}
void bad_root_signature_0() {}

#define Overlap1 "SRV(t0, space = 3), SRV(t0, space = 3)"

[RootSignature(Overlap1)] // expected-error {{resource ranges t[0;0] and t[0;0] overlap within space = 3 and visibility = All}}
void bad_root_signature_1() {}

#define Overlap2 "UAV(u0, visibility = SHADER_VISIBILITY_PIXEL), UAV(u0, visibility = SHADER_VISIBILITY_PIXEL)"

[RootSignature(Overlap2)] // expected-error {{resource ranges u[0;0] and u[0;0] overlap within space = 0 and visibility = Pixel}}
void bad_root_signature_2() {}

#define Overlap3 "UAV(u0, visibility = SHADER_VISIBILITY_ALL), UAV(u0, visibility = SHADER_VISIBILITY_PIXEL)"

[RootSignature(Overlap3)] // expected-error {{resource ranges u[0;0] and u[0;0] overlap within space = 0 and visibility = Pixel}}
void bad_root_signature_3() {}

#define Overlap4 "UAV(u0, visibility = SHADER_VISIBILITY_PIXEL), UAV(u0, visibility = SHADER_VISIBILITY_ALL)"

[RootSignature(Overlap4)] // expected-error {{resource ranges u[0;0] and u[0;0] overlap within space = 0 and visibility = Pixel}}
void bad_root_signature_4() {}
22 changes: 22 additions & 0 deletions clang/test/SemaHLSL/RootSignature-resource-ranges.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - %s -verify
// expected-no-diagnostics

#define NoOverlap0 "CBV(b0), CBV(b1)"

[RootSignature(NoOverlap0)]
void valid_root_signature_0() {}

#define NoOverlap1 "CBV(b0, visibility = SHADER_VISIBILITY_DOMAIN), CBV(b0, visibility = SHADER_VISIBILITY_PIXEL)"

[RootSignature(NoOverlap1)]
void valid_root_signature_1() {}

#define NoOverlap2 "CBV(b0, space = 1), CBV(b0, space = 2)"

[RootSignature(NoOverlap2)]
void valid_root_signature_2() {}

#define NoOverlap3 "CBV(b0), SRV(t0)"

[RootSignature(NoOverlap3)]
void valid_root_signature_3() {}
13 changes: 10 additions & 3 deletions llvm/include/llvm/Frontend/HLSL/HLSLRootSignatureUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,17 @@ class MetadataBuilder {
SmallVector<Metadata *> GeneratedMetadata;
};

// RangeInfo holds the information to correctly construct a ResourceRange
// and retains this information to be used for displaying a better diagnostic
struct RangeInfo {
const static uint32_t Unbounded = ~0u;
const static uint32_t Unbounded = static_cast<uint32_t>(-1);

// Interval information
uint32_t LowerBound;
uint32_t UpperBound;

// Information retained for diagnostics
llvm::dxil::ResourceClass Class;
uint32_t Space;
ShaderVisibility Vis;
};

class ResourceRange {
Expand All @@ -98,6 +102,9 @@ class ResourceRange {
// Return the mapped RangeInfo at X or nullptr if no mapping exists
const RangeInfo *lookup(uint32_t X) const;

// Removes all entries of the ResourceRange
void clear();

// Insert the required (sub-)intervals such that the interval of [a;b] =
// [Info.LowerBound, Info.UpperBound] is covered and points to a valid
// RangeInfo &.
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Frontend/HLSL/HLSLRootSignatureUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@ const RangeInfo *ResourceRange::lookup(uint32_t X) const {
return Intervals.lookup(X, nullptr);
}

void ResourceRange::clear() { return Intervals.clear(); }

std::optional<const RangeInfo *> ResourceRange::insert(const RangeInfo &Info) {
uint32_t LowerBound = Info.LowerBound;
uint32_t UpperBound = Info.UpperBound;
Expand Down
Loading