Skip to content

Commit 9e0186d

Browse files
authored
[HLSL][RootSignature] Implement ResourceRange as an IntervalMap (#140957)
A resource range consists of a closed interval, `[a;b]`, denoting which shader registers it is bound to. For instance: - `CBV(b1)` corresponds to the resource range of `[1;1]` - `CBV(b0, numDescriptors = 3)` likewise to `[0;2]` We want to provide an error diagnostic when there is an overlap in the required registers (an overlap in the resource ranges). The goal of this pr is to implement a structure to model a set of resource ranges and provide an api to detect any overlap over a set of resource ranges. `ResourceRange` models this by implementing an `IntervalMap` to denote a mapping from an interval of registers back to a resource range. It allows for a new `ResourceRange` to be added to the mapping and it will report if and what the first overlap is. For the context of how this will be used in validation of a `RootSignatureDecl` please see the proceeding pull request here: #140962. - Implements `ResourceRange` as an `IntervalMap` - Adds unit testing of the various `insert` scenarios Note: it was also considered to implement this as an `IntervalTree`, this would allow reporting of a diagnostic for each overlap that is encountered, as opposed to just the first. However, error generation of just reporting the first error is already rather verbose, and adding the additional diagnostics only made this worse. Part 1 of #129942
1 parent 9dd1c66 commit 9e0186d

File tree

5 files changed

+297
-0
lines changed

5 files changed

+297
-0
lines changed

llvm/include/llvm/Frontend/HLSL/HLSLRootSignature.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "llvm/Support/Compiler.h"
1818
#include "llvm/Support/DXILABI.h"
19+
#include <limits>
1920
#include <variant>
2021

2122
namespace llvm {

llvm/include/llvm/Frontend/HLSL/HLSLRootSignatureUtils.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#define LLVM_FRONTEND_HLSL_HLSLROOTSIGNATUREUTILS_H
1616

1717
#include "llvm/ADT/ArrayRef.h"
18+
#include "llvm/ADT/IntervalMap.h"
1819
#include "llvm/Frontend/HLSL/HLSLRootSignature.h"
1920
#include "llvm/Support/Compiler.h"
2021
#include "llvm/Support/raw_ostream.h"
@@ -64,6 +65,62 @@ class MetadataBuilder {
6465
SmallVector<Metadata *> GeneratedMetadata;
6566
};
6667

68+
// RangeInfo holds the information to correctly construct a ResourceRange
69+
// and retains this information to be used for displaying a better diagnostic
70+
struct RangeInfo {
71+
const static uint32_t Unbounded = ~0u;
72+
73+
uint32_t LowerBound;
74+
uint32_t UpperBound;
75+
};
76+
77+
class ResourceRange {
78+
public:
79+
using MapT = llvm::IntervalMap<uint32_t, const RangeInfo *, 16,
80+
llvm::IntervalMapInfo<uint32_t>>;
81+
82+
private:
83+
MapT Intervals;
84+
85+
public:
86+
ResourceRange(MapT::Allocator &Allocator) : Intervals(MapT(Allocator)) {}
87+
88+
// Returns a reference to the first RangeInfo that overlaps with
89+
// [Info.LowerBound;Info.UpperBound], or, std::nullopt if there is no overlap
90+
std::optional<const RangeInfo *> getOverlapping(const RangeInfo &Info) const;
91+
92+
// Return the mapped RangeInfo at X or nullptr if no mapping exists
93+
const RangeInfo *lookup(uint32_t X) const;
94+
95+
// Insert the required (sub-)intervals such that the interval of [a;b] =
96+
// [Info.LowerBound, Info.UpperBound] is covered and points to a valid
97+
// RangeInfo &.
98+
//
99+
// For instance consider the following chain of inserting RangeInfos with the
100+
// intervals denoting the Lower/Upper-bounds:
101+
//
102+
// A = [0;2]
103+
// insert(A) -> false
104+
// intervals: [0;2] -> &A
105+
// B = [5;7]
106+
// insert(B) -> false
107+
// intervals: [0;2] -> &A, [5;7] -> &B
108+
// C = [4;7]
109+
// insert(C) -> true
110+
// intervals: [0;2] -> &A, [4;7] -> &C
111+
// D = [1;5]
112+
// insert(D) -> true
113+
// intervals: [0;2] -> &A, [3;3] -> &D, [4;7] -> &C
114+
// E = [0;unbounded]
115+
// insert(E) -> true
116+
// intervals: [0;unbounded] -> E
117+
//
118+
// Returns a reference to the first RangeInfo that overlaps with
119+
// [Info.LowerBound;Info.UpperBound], or, std::nullopt if there is no overlap
120+
// (equivalent to getOverlapping)
121+
std::optional<const RangeInfo *> insert(const RangeInfo &Info);
122+
};
123+
67124
} // namespace rootsig
68125
} // namespace hlsl
69126
} // namespace llvm

llvm/lib/Frontend/HLSL/HLSLRootSignatureUtils.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,67 @@ MDNode *MetadataBuilder::BuildStaticSampler(const StaticSampler &Sampler) {
355355
return MDNode::get(Ctx, Operands);
356356
}
357357

358+
std::optional<const RangeInfo *>
359+
ResourceRange::getOverlapping(const RangeInfo &Info) const {
360+
MapT::const_iterator Interval = Intervals.find(Info.LowerBound);
361+
if (!Interval.valid() || Info.UpperBound < Interval.start())
362+
return std::nullopt;
363+
return Interval.value();
364+
}
365+
366+
const RangeInfo *ResourceRange::lookup(uint32_t X) const {
367+
return Intervals.lookup(X, nullptr);
368+
}
369+
370+
std::optional<const RangeInfo *> ResourceRange::insert(const RangeInfo &Info) {
371+
uint32_t LowerBound = Info.LowerBound;
372+
uint32_t UpperBound = Info.UpperBound;
373+
374+
std::optional<const RangeInfo *> Res = std::nullopt;
375+
MapT::iterator Interval = Intervals.begin();
376+
377+
while (true) {
378+
if (UpperBound < LowerBound)
379+
break;
380+
381+
Interval.advanceTo(LowerBound);
382+
if (!Interval.valid()) // No interval found
383+
break;
384+
385+
// Let Interval = [x;y] and [LowerBound;UpperBound] = [a;b] and note that
386+
// a <= y implicitly from Intervals.find(LowerBound)
387+
if (UpperBound < Interval.start())
388+
break; // found interval does not overlap with inserted one
389+
390+
if (!Res.has_value()) // Update to be the first found intersection
391+
Res = Interval.value();
392+
393+
if (Interval.start() <= LowerBound && UpperBound <= Interval.stop()) {
394+
// x <= a <= b <= y implies that [a;b] is covered by [x;y]
395+
// -> so we don't need to insert this, report an overlap
396+
return Res;
397+
} else if (LowerBound <= Interval.start() &&
398+
Interval.stop() <= UpperBound) {
399+
// a <= x <= y <= b implies that [x;y] is covered by [a;b]
400+
// -> so remove the existing interval that we will cover with the
401+
// overwrite
402+
Interval.erase();
403+
} else if (LowerBound < Interval.start() && UpperBound <= Interval.stop()) {
404+
// a < x <= b <= y implies that [a; x] is not covered but [x;b] is
405+
// -> so set b = x - 1 such that [a;x-1] is now the interval to insert
406+
UpperBound = Interval.start() - 1;
407+
} else if (Interval.start() <= LowerBound && Interval.stop() < UpperBound) {
408+
// a < x <= b <= y implies that [y; b] is not covered but [a;y] is
409+
// -> so set a = y + 1 such that [y+1;b] is now the interval to insert
410+
LowerBound = Interval.stop() + 1;
411+
}
412+
}
413+
414+
assert(LowerBound <= UpperBound && "Attempting to insert an empty interval");
415+
Intervals.insert(LowerBound, UpperBound, &Info);
416+
return Res;
417+
}
418+
358419
} // namespace rootsig
359420
} // namespace hlsl
360421
} // namespace llvm

llvm/unittests/Frontend/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(LLVM_LINK_COMPONENTS
1212

1313
add_llvm_unittest(LLVMFrontendTests
1414
HLSLRootSignatureDumpTest.cpp
15+
HLSLRootSignatureRangesTest.cpp
1516
OpenACCTest.cpp
1617
OpenMPContextTest.cpp
1718
OpenMPIRBuilderTest.cpp
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//===------ HLSLRootSignatureRangeTest.cpp - RootSignature Range tests ----===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/Frontend/HLSL/HLSLRootSignatureUtils.h"
10+
#include "gtest/gtest.h"
11+
12+
using namespace llvm::hlsl::rootsig;
13+
14+
namespace {
15+
16+
TEST(HLSLRootSignatureTest, NoOverlappingInsertTests) {
17+
// Ensures that there is never a reported overlap
18+
ResourceRange::MapT::Allocator Allocator;
19+
ResourceRange Range(Allocator);
20+
21+
RangeInfo A;
22+
A.LowerBound = 0;
23+
A.UpperBound = 3;
24+
EXPECT_EQ(Range.insert(A), std::nullopt);
25+
26+
RangeInfo B;
27+
B.LowerBound = 4;
28+
B.UpperBound = 7;
29+
EXPECT_EQ(Range.insert(B), std::nullopt);
30+
31+
RangeInfo C;
32+
C.LowerBound = 10;
33+
C.UpperBound = RangeInfo::Unbounded;
34+
EXPECT_EQ(Range.insert(C), std::nullopt);
35+
36+
// A = [0;3]
37+
EXPECT_EQ(Range.lookup(0), &A);
38+
EXPECT_EQ(Range.lookup(2), &A);
39+
EXPECT_EQ(Range.lookup(3), &A);
40+
41+
// B = [4;7]
42+
EXPECT_EQ(Range.lookup(4), &B);
43+
EXPECT_EQ(Range.lookup(5), &B);
44+
EXPECT_EQ(Range.lookup(7), &B);
45+
46+
EXPECT_EQ(Range.lookup(8), nullptr);
47+
EXPECT_EQ(Range.lookup(9), nullptr);
48+
49+
// C = [10;unbounded]
50+
EXPECT_EQ(Range.lookup(10), &C);
51+
EXPECT_EQ(Range.lookup(42), &C);
52+
EXPECT_EQ(Range.lookup(98237423), &C);
53+
EXPECT_EQ(Range.lookup(RangeInfo::Unbounded), &C);
54+
}
55+
56+
TEST(HLSLRootSignatureTest, SingleOverlappingInsertTests) {
57+
// Ensures that we correctly report an overlap when we insert a range that
58+
// overlaps with one other range but does not cover (replace) it
59+
ResourceRange::MapT::Allocator Allocator;
60+
ResourceRange Range(Allocator);
61+
62+
RangeInfo A;
63+
A.LowerBound = 1;
64+
A.UpperBound = 5;
65+
EXPECT_EQ(Range.insert(A), std::nullopt);
66+
67+
RangeInfo B;
68+
B.LowerBound = 0;
69+
B.UpperBound = 2;
70+
EXPECT_EQ(Range.insert(B).value(), &A);
71+
72+
RangeInfo C;
73+
C.LowerBound = 4;
74+
C.UpperBound = RangeInfo::Unbounded;
75+
EXPECT_EQ(Range.insert(C).value(), &A);
76+
77+
// A = [1;5]
78+
EXPECT_EQ(Range.lookup(1), &A);
79+
EXPECT_EQ(Range.lookup(2), &A);
80+
EXPECT_EQ(Range.lookup(3), &A);
81+
EXPECT_EQ(Range.lookup(4), &A);
82+
EXPECT_EQ(Range.lookup(5), &A);
83+
84+
// B = [0;0]
85+
EXPECT_EQ(Range.lookup(0), &B);
86+
87+
// C = [6; unbounded]
88+
EXPECT_EQ(Range.lookup(6), &C);
89+
EXPECT_EQ(Range.lookup(RangeInfo::Unbounded), &C);
90+
}
91+
92+
TEST(HLSLRootSignatureTest, MultipleOverlappingInsertTests) {
93+
// Ensures that we correctly report an overlap when inserted range
94+
// overlaps more than one range and it does not cover (replace) either
95+
// range. In this case it will just fill in the interval between the two
96+
ResourceRange::MapT::Allocator Allocator;
97+
ResourceRange Range(Allocator);
98+
99+
RangeInfo A;
100+
A.LowerBound = 0;
101+
A.UpperBound = 2;
102+
EXPECT_EQ(Range.insert(A), std::nullopt);
103+
104+
RangeInfo B;
105+
B.LowerBound = 4;
106+
B.UpperBound = 6;
107+
EXPECT_EQ(Range.insert(B), std::nullopt);
108+
109+
RangeInfo C;
110+
C.LowerBound = 1;
111+
C.UpperBound = 5;
112+
EXPECT_EQ(Range.insert(C).value(), &A);
113+
114+
// A = [0;2]
115+
EXPECT_EQ(Range.lookup(0), &A);
116+
EXPECT_EQ(Range.lookup(1), &A);
117+
EXPECT_EQ(Range.lookup(2), &A);
118+
119+
// B = [4;6]
120+
EXPECT_EQ(Range.lookup(4), &B);
121+
EXPECT_EQ(Range.lookup(5), &B);
122+
EXPECT_EQ(Range.lookup(6), &B);
123+
124+
// C = [3;3]
125+
EXPECT_EQ(Range.lookup(3), &C);
126+
}
127+
128+
TEST(HLSLRootSignatureTest, CoverInsertTests) {
129+
// Ensures that we correctly report an overlap when inserted range
130+
// covers one or more ranges
131+
ResourceRange::MapT::Allocator Allocator;
132+
ResourceRange Range(Allocator);
133+
134+
RangeInfo A;
135+
A.LowerBound = 0;
136+
A.UpperBound = 2;
137+
EXPECT_EQ(Range.insert(A), std::nullopt);
138+
139+
RangeInfo B;
140+
B.LowerBound = 4;
141+
B.UpperBound = 5;
142+
EXPECT_EQ(Range.insert(B), std::nullopt);
143+
144+
// Covers B
145+
RangeInfo C;
146+
C.LowerBound = 4;
147+
C.UpperBound = 6;
148+
EXPECT_EQ(Range.insert(C).value(), &B);
149+
150+
// A = [0;2]
151+
// C = [4;6] <- covers reference to B
152+
EXPECT_EQ(Range.lookup(0), &A);
153+
EXPECT_EQ(Range.lookup(1), &A);
154+
EXPECT_EQ(Range.lookup(2), &A);
155+
EXPECT_EQ(Range.lookup(3), nullptr);
156+
EXPECT_EQ(Range.lookup(4), &C);
157+
EXPECT_EQ(Range.lookup(5), &C);
158+
EXPECT_EQ(Range.lookup(6), &C);
159+
160+
// Covers all other ranges
161+
RangeInfo D;
162+
D.LowerBound = 0;
163+
D.UpperBound = 7;
164+
EXPECT_EQ(Range.insert(D).value(), &A);
165+
166+
// D = [0;7] <- Covers reference to A and C
167+
EXPECT_EQ(Range.lookup(0), &D);
168+
EXPECT_EQ(Range.lookup(1), &D);
169+
EXPECT_EQ(Range.lookup(2), &D);
170+
EXPECT_EQ(Range.lookup(3), &D);
171+
EXPECT_EQ(Range.lookup(4), &D);
172+
EXPECT_EQ(Range.lookup(5), &D);
173+
EXPECT_EQ(Range.lookup(6), &D);
174+
EXPECT_EQ(Range.lookup(7), &D);
175+
}
176+
177+
} // namespace

0 commit comments

Comments
 (0)