Skip to content

Commit 73e12de

Browse files
authored
[HLSL] Implement explicit layout for default constant buffer ($Globals) (#128991)
Processes `HLSLResourceBindingAttr` attributes that represent `register(c#)` annotations on default constant buffer declarations and applies its value to the buffer layout. Any default buffer declarations without an explicit `register(c#)` annotation are placed after the elements with explicit layout. This PR also adds a test case for a `cbuffer` that does not have `packoffset` on all declarations. Same layout rules apply here as well. Fixes #126791
1 parent bc4b2c7 commit 73e12de

File tree

11 files changed

+183
-45
lines changed

11 files changed

+183
-45
lines changed

clang/lib/CodeGen/CGHLSLRuntime.cpp

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ using namespace CodeGen;
4040
using namespace clang::hlsl;
4141
using namespace llvm;
4242

43+
using llvm::hlsl::CBufferRowSizeInBytes;
44+
4345
static void createResourceInitFn(CodeGenModule &CGM, llvm::GlobalVariable *GV,
4446
unsigned Slot, unsigned Space);
4547

@@ -70,7 +72,7 @@ void addDxilValVersion(StringRef ValVersionStr, llvm::Module &M) {
7072

7173
llvm::Type *
7274
CGHLSLRuntime::convertHLSLSpecificType(const Type *T,
73-
SmallVector<unsigned> *Packoffsets) {
75+
SmallVector<int32_t> *Packoffsets) {
7476
assert(T->isHLSLSpecificType() && "Not an HLSL specific type!");
7577

7678
// Check if the target has a specific translation for this type first.
@@ -174,21 +176,50 @@ createBufferHandleType(const HLSLBufferDecl *BufDecl) {
174176
return cast<HLSLAttributedResourceType>(QT.getTypePtr());
175177
}
176178

179+
// Iterates over all declarations in the HLSL buffer and based on the
180+
// packoffset or register(c#) annotations it fills outs the Layout
181+
// vector with the user-specified layout offsets.
182+
// The buffer offsets can be specified 2 ways:
183+
// 1. declarations in cbuffer {} block can have a packoffset annotation
184+
// (translates to HLSLPackOffsetAttr)
185+
// 2. default constant buffer declarations at global scope can have
186+
// register(c#) annotations (translates to HLSLResourceBindingAttr with
187+
// RegisterType::C)
188+
// It is not guaranteed that all declarations in a buffer have an annotation.
189+
// For those where it is not specified a -1 value is added to the Layout
190+
// vector. In the final layout these declarations will be placed at the end
191+
// of the HLSL buffer after all of the elements with specified offset.
177192
static void fillPackoffsetLayout(const HLSLBufferDecl *BufDecl,
178-
SmallVector<unsigned> &Layout) {
193+
SmallVector<int32_t> &Layout) {
179194
assert(Layout.empty() && "expected empty vector for layout");
180195
assert(BufDecl->hasValidPackoffset());
181196

182-
for (Decl *D : BufDecl->decls()) {
197+
for (Decl *D : BufDecl->buffer_decls()) {
183198
if (isa<CXXRecordDecl, EmptyDecl>(D) || isa<FunctionDecl>(D)) {
184199
continue;
185200
}
186201
VarDecl *VD = dyn_cast<VarDecl>(D);
187202
if (!VD || VD->getType().getAddressSpace() != LangAS::hlsl_constant)
188203
continue;
189-
assert(VD->hasAttr<HLSLPackOffsetAttr>() &&
190-
"expected packoffset attribute on every declaration");
191-
size_t Offset = VD->getAttr<HLSLPackOffsetAttr>()->getOffsetInBytes();
204+
205+
if (!VD->hasAttrs()) {
206+
Layout.push_back(-1);
207+
continue;
208+
}
209+
210+
int32_t Offset = -1;
211+
for (auto *Attr : VD->getAttrs()) {
212+
if (auto *POA = dyn_cast<HLSLPackOffsetAttr>(Attr)) {
213+
Offset = POA->getOffsetInBytes();
214+
break;
215+
}
216+
auto *RBA = dyn_cast<HLSLResourceBindingAttr>(Attr);
217+
if (RBA &&
218+
RBA->getRegisterType() == HLSLResourceBindingAttr::RegisterType::C) {
219+
Offset = RBA->getSlotNumber() * CBufferRowSizeInBytes;
220+
break;
221+
}
222+
}
192223
Layout.push_back(Offset);
193224
}
194225
}
@@ -207,7 +238,7 @@ void CGHLSLRuntime::addBuffer(const HLSLBufferDecl *BufDecl) {
207238
return;
208239

209240
// create global variable for the constant buffer
210-
SmallVector<unsigned> Layout;
241+
SmallVector<int32_t> Layout;
211242
if (BufDecl->hasValidPackoffset())
212243
fillPackoffsetLayout(BufDecl, Layout);
213244

clang/lib/CodeGen/CGHLSLRuntime.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ class CGHLSLRuntime {
146146

147147
llvm::Type *
148148
convertHLSLSpecificType(const Type *T,
149-
SmallVector<unsigned> *Packoffsets = nullptr);
149+
SmallVector<int32_t> *Packoffsets = nullptr);
150150

151151
void annotateHLSLResource(const VarDecl *D, llvm::GlobalVariable *GV);
152152
void generateGlobalCtorDtorCalls();

clang/lib/CodeGen/HLSLBufferLayoutBuilder.cpp

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "CGHLSLRuntime.h"
1111
#include "CodeGenModule.h"
1212
#include "clang/AST/Type.h"
13+
#include <climits>
1314

1415
//===----------------------------------------------------------------------===//
1516
// Implementation of constant buffer layout common between DirectX and
@@ -18,6 +19,7 @@
1819

1920
using namespace clang;
2021
using namespace clang::CodeGen;
22+
using llvm::hlsl::CBufferRowSizeInBytes;
2123

2224
namespace {
2325

@@ -51,16 +53,22 @@ namespace clang {
5153
namespace CodeGen {
5254

5355
// Creates a layout type for given struct with HLSL constant buffer layout
54-
// taking into account Packoffsets, if provided.
56+
// taking into account PackOffsets, if provided.
5557
// Previously created layout types are cached by CGHLSLRuntime.
5658
//
5759
// The function iterates over all fields of the StructType (including base
5860
// classes) and calls layoutField to converts each field to its corresponding
5961
// LLVM type and to calculate its HLSL constant buffer layout. Any embedded
6062
// structs (or arrays of structs) are converted to target layout types as well.
63+
//
64+
// When PackOffsets are specified the elements will be placed based on the
65+
// user-specified offsets. Not all elements must have a packoffset/register(c#)
66+
// annotation though. For those that don't, the PackOffsets array will contain
67+
// -1 value instead. These elements must be placed at the end of the layout
68+
// after all of the elements with specific offset.
6169
llvm::TargetExtType *HLSLBufferLayoutBuilder::createLayoutType(
6270
const RecordType *StructType,
63-
const llvm::SmallVector<unsigned> *Packoffsets) {
71+
const llvm::SmallVector<int32_t> *PackOffsets) {
6472

6573
// check if we already have the layout type for this struct
6674
if (llvm::TargetExtType *Ty =
@@ -72,6 +80,8 @@ llvm::TargetExtType *HLSLBufferLayoutBuilder::createLayoutType(
7280
unsigned Index = 0; // packoffset index
7381
unsigned EndOffset = 0;
7482

83+
SmallVector<std::pair<const FieldDecl *, unsigned>> DelayLayoutFields;
84+
7585
// reserve first spot in the layout vector for buffer size
7686
Layout.push_back(0);
7787

@@ -84,22 +94,55 @@ llvm::TargetExtType *HLSLBufferLayoutBuilder::createLayoutType(
8494
"HLSL doesn't support multiple inheritance");
8595
RecordTypes.push_back(D->bases_begin()->getType()->getAs<RecordType>());
8696
}
97+
98+
unsigned FieldOffset;
99+
llvm::Type *FieldType;
100+
87101
while (!RecordTypes.empty()) {
88102
const RecordType *RT = RecordTypes.back();
89103
RecordTypes.pop_back();
90104

91105
for (const auto *FD : RT->getDecl()->fields()) {
92-
assert((!Packoffsets || Index < Packoffsets->size()) &&
93-
"number of elements in layout struct does not "
94-
"match number of packoffset annotations");
95-
96-
if (!layoutField(FD, EndOffset, Layout, LayoutElements,
97-
Packoffsets ? (*Packoffsets)[Index] : -1))
98-
return nullptr;
99-
Index++;
106+
assert((!PackOffsets || Index < PackOffsets->size()) &&
107+
"number of elements in layout struct does not match number of "
108+
"packoffset annotations");
109+
110+
// No PackOffset info at all, or have a valid packoffset/register(c#)
111+
// annotations value -> layout the field.
112+
const int PO = PackOffsets ? (*PackOffsets)[Index++] : -1;
113+
if (!PackOffsets || PO != -1) {
114+
if (!layoutField(FD, EndOffset, FieldOffset, FieldType, PO))
115+
return nullptr;
116+
Layout.push_back(FieldOffset);
117+
LayoutElements.push_back(FieldType);
118+
continue;
119+
}
120+
// Have PackOffset info, but there is no packoffset/register(cX)
121+
// annotation on this field. Delay the layout until after all of the
122+
// other elements with packoffsets/register(cX) are processed.
123+
DelayLayoutFields.emplace_back(FD, LayoutElements.size());
124+
// reserve space for this field in the layout vector and elements list
125+
Layout.push_back(UINT_MAX);
126+
LayoutElements.push_back(nullptr);
100127
}
101128
}
102129

130+
// process delayed layouts
131+
for (auto I : DelayLayoutFields) {
132+
const FieldDecl *FD = I.first;
133+
const unsigned IndexInLayoutElements = I.second;
134+
// the first item in layout vector is size, so we need to offset the index
135+
// by 1
136+
const unsigned IndexInLayout = IndexInLayoutElements + 1;
137+
assert(Layout[IndexInLayout] == UINT_MAX &&
138+
LayoutElements[IndexInLayoutElements] == nullptr);
139+
140+
if (!layoutField(FD, EndOffset, FieldOffset, FieldType))
141+
return nullptr;
142+
Layout[IndexInLayout] = FieldOffset;
143+
LayoutElements[IndexInLayoutElements] = FieldType;
144+
}
145+
103146
// set the size of the buffer
104147
Layout[0] = EndOffset;
105148

@@ -122,16 +165,19 @@ llvm::TargetExtType *HLSLBufferLayoutBuilder::createLayoutType(
122165
// The function converts a single field of HLSL Buffer to its corresponding
123166
// LLVM type and calculates it's layout. Any embedded structs (or
124167
// arrays of structs) are converted to target layout types as well.
125-
// The converted type is appended to the LayoutElements list, the element
126-
// offset is added to the Layout list and the EndOffset updated to the offset
127-
// just after the lay-ed out element (which is basically the size of the
128-
// buffer).
168+
// The converted type is set to the FieldType parameter, the element
169+
// offset is set to the FieldOffset parameter. The EndOffset (=size of the
170+
// buffer) is also updated accordingly to the offset just after the placed
171+
// element, unless the incoming EndOffset already larger (may happen in case
172+
// of unsorted packoffset annotations).
129173
// Returns true if the conversion was successful.
130174
// The packoffset parameter contains the field's layout offset provided by the
131175
// user or -1 if there was no packoffset (or register(cX)) annotation.
132-
bool HLSLBufferLayoutBuilder::layoutField(
133-
const FieldDecl *FD, unsigned &EndOffset, SmallVector<unsigned> &Layout,
134-
SmallVector<llvm::Type *> &LayoutElements, int Packoffset) {
176+
bool HLSLBufferLayoutBuilder::layoutField(const FieldDecl *FD,
177+
unsigned &EndOffset,
178+
unsigned &FieldOffset,
179+
llvm::Type *&FieldType,
180+
int Packoffset) {
135181

136182
// Size of element; for arrays this is a size of a single element in the
137183
// array. Total array size of calculated as (ArrayCount-1) * ArrayStride +
@@ -141,8 +187,7 @@ bool HLSLBufferLayoutBuilder::layoutField(
141187
unsigned ArrayCount = 1;
142188
unsigned ArrayStride = 0;
143189

144-
const unsigned BufferRowAlign = 16U;
145-
unsigned NextRowOffset = llvm::alignTo(EndOffset, BufferRowAlign);
190+
unsigned NextRowOffset = llvm::alignTo(EndOffset, CBufferRowSizeInBytes);
146191

147192
llvm::Type *ElemLayoutTy = nullptr;
148193
QualType FieldTy = FD->getType();
@@ -172,7 +217,7 @@ bool HLSLBufferLayoutBuilder::layoutField(
172217
getScalarOrVectorSizeInBytes(CGM.getTypes().ConvertTypeForMem(Ty));
173218
ElemLayoutTy = CGM.getTypes().ConvertTypeForMem(FieldTy);
174219
}
175-
ArrayStride = llvm::alignTo(ElemSize, BufferRowAlign);
220+
ArrayStride = llvm::alignTo(ElemSize, CBufferRowSizeInBytes);
176221
ElemOffset = (Packoffset != -1) ? Packoffset : NextRowOffset;
177222

178223
} else if (FieldTy->isStructureType()) {
@@ -220,8 +265,8 @@ bool HLSLBufferLayoutBuilder::layoutField(
220265
EndOffset = std::max<unsigned>(EndOffset, NewEndOffset);
221266

222267
// add the layout element and offset to the lists
223-
Layout.push_back(ElemOffset);
224-
LayoutElements.push_back(ElemLayoutTy);
268+
FieldOffset = ElemOffset;
269+
FieldType = ElemLayoutTy;
225270
return true;
226271
}
227272

clang/lib/CodeGen/HLSLBufferLayoutBuilder.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,12 @@ class HLSLBufferLayoutBuilder {
3535
// the Layout is the size followed by offsets for each struct element.
3636
llvm::TargetExtType *
3737
createLayoutType(const RecordType *StructType,
38-
const llvm::SmallVector<unsigned> *Packoffsets = nullptr);
38+
const llvm::SmallVector<int32_t> *Packoffsets = nullptr);
3939

4040
private:
4141
bool layoutField(const clang::FieldDecl *FD, unsigned &EndOffset,
42-
llvm::SmallVector<unsigned> &Layout,
43-
llvm::SmallVector<llvm::Type *> &LayoutElements,
44-
int Packoffset);
42+
unsigned &FieldOffset, llvm::Type *&FieldType,
43+
int Packoffset = -1);
4544
};
4645

4746
} // namespace CodeGen

clang/lib/CodeGen/TargetInfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ class TargetCodeGenInfo {
441441
/// Return an LLVM type that corresponds to a HLSL type
442442
virtual llvm::Type *
443443
getHLSLType(CodeGenModule &CGM, const Type *T,
444-
const SmallVector<unsigned> *Packoffsets = nullptr) const {
444+
const SmallVector<int32_t> *Packoffsets = nullptr) const {
445445
return nullptr;
446446
}
447447

clang/lib/CodeGen/Targets/DirectX.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ class DirectXTargetCodeGenInfo : public TargetCodeGenInfo {
2929
DirectXTargetCodeGenInfo(CodeGen::CodeGenTypes &CGT)
3030
: TargetCodeGenInfo(std::make_unique<DefaultABIInfo>(CGT)) {}
3131

32-
llvm::Type *getHLSLType(
33-
CodeGenModule &CGM, const Type *T,
34-
const SmallVector<unsigned> *Packoffsets = nullptr) const override;
32+
llvm::Type *
33+
getHLSLType(CodeGenModule &CGM, const Type *T,
34+
const SmallVector<int32_t> *Packoffsets = nullptr) const override;
3535
};
3636

3737
llvm::Type *DirectXTargetCodeGenInfo::getHLSLType(
3838
CodeGenModule &CGM, const Type *Ty,
39-
const SmallVector<unsigned> *Packoffsets) const {
39+
const SmallVector<int32_t> *Packoffsets) const {
4040
auto *ResType = dyn_cast<HLSLAttributedResourceType>(Ty);
4141
if (!ResType)
4242
return nullptr;

clang/lib/CodeGen/Targets/SPIR.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ class CommonSPIRTargetCodeGenInfo : public TargetCodeGenInfo {
5252

5353
unsigned getOpenCLKernelCallingConv() const override;
5454
llvm::Type *getOpenCLType(CodeGenModule &CGM, const Type *T) const override;
55-
llvm::Type *getHLSLType(
56-
CodeGenModule &CGM, const Type *Ty,
57-
const SmallVector<unsigned> *Packoffsets = nullptr) const override;
55+
llvm::Type *
56+
getHLSLType(CodeGenModule &CGM, const Type *Ty,
57+
const SmallVector<int32_t> *Packoffsets = nullptr) const override;
5858
llvm::Type *getSPIRVImageTypeFromHLSLResource(
5959
const HLSLAttributedResourceType::Attributes &attributes,
6060
llvm::Type *ElementType, llvm::LLVMContext &Ctx) const;
@@ -371,7 +371,7 @@ llvm::Type *CommonSPIRTargetCodeGenInfo::getOpenCLType(CodeGenModule &CGM,
371371

372372
llvm::Type *CommonSPIRTargetCodeGenInfo::getHLSLType(
373373
CodeGenModule &CGM, const Type *Ty,
374-
const SmallVector<unsigned> *Packoffsets) const {
374+
const SmallVector<int32_t> *Packoffsets) const {
375375
auto *ResType = dyn_cast<HLSLAttributedResourceType>(Ty);
376376
if (!ResType)
377377
return nullptr;

clang/lib/Sema/SemaHLSL.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,17 @@ void SemaHLSL::ActOnEndOfTranslationUnit(TranslationUnitDecl *TU) {
19611961
SemaRef.getCurLexicalContext()->addDecl(DefaultCBuffer);
19621962
createHostLayoutStructForBuffer(SemaRef, DefaultCBuffer);
19631963

1964+
// Set HasValidPackoffset if any of the decls has a register(c#) annotation;
1965+
for (const Decl *VD : DefaultCBufferDecls) {
1966+
const HLSLResourceBindingAttr *RBA =
1967+
VD->getAttr<HLSLResourceBindingAttr>();
1968+
if (RBA &&
1969+
RBA->getRegisterType() == HLSLResourceBindingAttr::RegisterType::C) {
1970+
DefaultCBuffer->setHasValidPackoffset(true);
1971+
break;
1972+
}
1973+
}
1974+
19641975
DeclGroupRef DG(DefaultCBuffer);
19651976
SemaRef.Consumer.HandleTopLevelDecl(DG);
19661977
}

clang/test/CodeGenHLSL/cbuffer_with_packoffset.hlsl

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s
44

55
// CHECK: %__cblayout_CB = type <{ float, double, <2 x i32> }>
6+
// CHECK: %__cblayout_CB_1 = type <{ float, <2 x float> }>
67

78
// CHECK: @CB.cb = external constant target("dx.CBuffer", target("dx.Layout", %__cblayout_CB, 176, 16, 168, 88))
89
// CHECK: @a = external addrspace(2) global float, align 4
@@ -15,6 +16,17 @@ cbuffer CB : register(b1, space3) {
1516
int2 c : packoffset(c5.z);
1617
}
1718

19+
// CHECK: @CB.cb.1 = external constant target("dx.CBuffer", target("dx.Layout", %__cblayout_CB_1, 92, 88, 80))
20+
// CHECK: @x = external addrspace(2) global float, align 4
21+
// CHECK: @y = external addrspace(2) global <2 x float>, align 8
22+
23+
// Missing packoffset annotation will produce a warning.
24+
// Element x will be placed after the element y that has an explicit packoffset.
25+
cbuffer CB : register(b0) {
26+
float x;
27+
float2 y : packoffset(c5);
28+
}
29+
1830
// CHECK: define internal void @_init_resource_CB.cb()
1931
// CHECK-NEXT: entry:
2032
// CHECK-NEXT: %CB.cb_h = call target("dx.CBuffer", target("dx.Layout", %__cblayout_CB, 176, 16, 168, 88))
@@ -34,5 +46,6 @@ void main() {
3446
foo();
3547
}
3648

37-
// CHECK: !hlsl.cbs = !{![[CB:[0-9]+]]}
38-
// CHECK: ![[CB]] = !{ptr @CB.cb, ptr addrspace(2) @a, ptr addrspace(2) @b, ptr addrspace(2) @c}
49+
// CHECK: !hlsl.cbs = !{![[CB1:[0-9]+]], ![[CB2:[0-9]+]]}
50+
// CHECK: ![[CB1]] = !{ptr @CB.cb, ptr addrspace(2) @a, ptr addrspace(2) @b, ptr addrspace(2) @c}
51+
// CHECK: ![[CB2]] = !{ptr @CB.cb.1, ptr addrspace(2) @x, ptr addrspace(2) @y}

0 commit comments

Comments
 (0)