Skip to content

Commit 825e712

Browse files
authored
[HLSL] cbuffer: create host layout structs (#122820)
Creates layout struct for `cbuffer` in Sema which will contains only declarations contributing to the constant buffer layout. Anything else will be filtered out, such as static variables decls, struct and function definitions, resources, or empty struct and zero-sized arrays. If the constant buffer includes a struct that contains any of the above undesirable declarations, a new version of this struct should be created with these declarations filtered out as well. The definition of buffer layout struct will be added to the HLSLBufferDecl AST node as the last node. Any layout structs for embedded structures will be added there as well. Fixes #122553
1 parent e4009ed commit 825e712

File tree

6 files changed

+592
-58
lines changed

6 files changed

+592
-58
lines changed

clang/lib/Sema/SemaHLSL.cpp

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
#include "clang/AST/Decl.h"
1616
#include "clang/AST/DeclBase.h"
1717
#include "clang/AST/DeclCXX.h"
18+
#include "clang/AST/DeclarationName.h"
1819
#include "clang/AST/DynamicRecursiveASTVisitor.h"
1920
#include "clang/AST/Expr.h"
2021
#include "clang/AST/Type.h"
2122
#include "clang/AST/TypeLoc.h"
2223
#include "clang/Basic/Builtins.h"
2324
#include "clang/Basic/DiagnosticSema.h"
25+
#include "clang/Basic/IdentifierTable.h"
2426
#include "clang/Basic/LLVM.h"
2527
#include "clang/Basic/SourceLocation.h"
2628
#include "clang/Basic/TargetInfo.h"
@@ -32,16 +34,21 @@
3234
#include "llvm/ADT/SmallVector.h"
3335
#include "llvm/ADT/StringExtras.h"
3436
#include "llvm/ADT/StringRef.h"
37+
#include "llvm/ADT/Twine.h"
3538
#include "llvm/Support/Casting.h"
3639
#include "llvm/Support/DXILABI.h"
3740
#include "llvm/Support/ErrorHandling.h"
3841
#include "llvm/TargetParser/Triple.h"
42+
#include <cstddef>
3943
#include <iterator>
4044
#include <utility>
4145

4246
using namespace clang;
4347
using RegisterType = HLSLResourceBindingAttr::RegisterType;
4448

49+
static CXXRecordDecl *createHostLayoutStruct(Sema &S,
50+
CXXRecordDecl *StructDecl);
51+
4552
static RegisterType getRegisterType(ResourceClass RC) {
4653
switch (RC) {
4754
case ResourceClass::SRV:
@@ -253,12 +260,244 @@ static void validatePackoffset(Sema &S, HLSLBufferDecl *BufDecl) {
253260
}
254261
}
255262

263+
// Returns true if the array has a zero size = if any of the dimensions is 0
264+
static bool isZeroSizedArray(const ConstantArrayType *CAT) {
265+
while (CAT && !CAT->isZeroSize())
266+
CAT = dyn_cast<ConstantArrayType>(
267+
CAT->getElementType()->getUnqualifiedDesugaredType());
268+
return CAT != nullptr;
269+
}
270+
271+
// Returns true if the record type is an HLSL resource class
272+
static bool isResourceRecordType(const Type *Ty) {
273+
return HLSLAttributedResourceType::findHandleTypeOnResource(Ty) != nullptr;
274+
}
275+
276+
// Returns true if the type is a leaf element type that is not valid to be
277+
// included in HLSL Buffer, such as a resource class, empty struct, zero-sized
278+
// array, or a builtin intangible type. Returns false it is a valid leaf element
279+
// type or if it is a record type that needs to be inspected further.
280+
static bool isInvalidConstantBufferLeafElementType(const Type *Ty) {
281+
if (Ty->isRecordType()) {
282+
if (isResourceRecordType(Ty) || Ty->getAsCXXRecordDecl()->isEmpty())
283+
return true;
284+
return false;
285+
}
286+
if (Ty->isConstantArrayType() &&
287+
isZeroSizedArray(cast<ConstantArrayType>(Ty)))
288+
return true;
289+
if (Ty->isHLSLBuiltinIntangibleType())
290+
return true;
291+
return false;
292+
}
293+
294+
// Returns true if the struct contains at least one element that prevents it
295+
// from being included inside HLSL Buffer as is, such as an intangible type,
296+
// empty struct, or zero-sized array. If it does, a new implicit layout struct
297+
// needs to be created for HLSL Buffer use that will exclude these unwanted
298+
// declarations (see createHostLayoutStruct function).
299+
static bool requiresImplicitBufferLayoutStructure(const CXXRecordDecl *RD) {
300+
if (RD->getTypeForDecl()->isHLSLIntangibleType() || RD->isEmpty())
301+
return true;
302+
// check fields
303+
for (const FieldDecl *Field : RD->fields()) {
304+
QualType Ty = Field->getType();
305+
if (isInvalidConstantBufferLeafElementType(Ty.getTypePtr()))
306+
return true;
307+
if (Ty->isRecordType() &&
308+
requiresImplicitBufferLayoutStructure(Ty->getAsCXXRecordDecl()))
309+
return true;
310+
}
311+
// check bases
312+
for (const CXXBaseSpecifier &Base : RD->bases())
313+
if (requiresImplicitBufferLayoutStructure(
314+
Base.getType()->getAsCXXRecordDecl()))
315+
return true;
316+
return false;
317+
}
318+
319+
static CXXRecordDecl *findRecordDeclInContext(IdentifierInfo *II,
320+
DeclContext *DC) {
321+
CXXRecordDecl *RD = nullptr;
322+
for (NamedDecl *Decl :
323+
DC->getNonTransparentContext()->lookup(DeclarationName(II))) {
324+
if (CXXRecordDecl *FoundRD = dyn_cast<CXXRecordDecl>(Decl)) {
325+
assert(RD == nullptr &&
326+
"there should be at most 1 record by a given name in a scope");
327+
RD = FoundRD;
328+
}
329+
}
330+
return RD;
331+
}
332+
333+
// Creates a name for buffer layout struct using the provide name base.
334+
// If the name must be unique (not previously defined), a suffix is added
335+
// until a unique name is found.
336+
static IdentifierInfo *getHostLayoutStructName(Sema &S, NamedDecl *BaseDecl,
337+
bool MustBeUnique) {
338+
ASTContext &AST = S.getASTContext();
339+
340+
IdentifierInfo *NameBaseII = BaseDecl->getIdentifier();
341+
llvm::SmallString<64> Name("__layout_");
342+
if (NameBaseII) {
343+
Name.append(NameBaseII->getName());
344+
} else {
345+
// anonymous struct
346+
Name.append("anon");
347+
MustBeUnique = true;
348+
}
349+
350+
size_t NameLength = Name.size();
351+
IdentifierInfo *II = &AST.Idents.get(Name, tok::TokenKind::identifier);
352+
if (!MustBeUnique)
353+
return II;
354+
355+
unsigned suffix = 0;
356+
while (true) {
357+
if (suffix != 0) {
358+
Name.append("_");
359+
Name.append(llvm::Twine(suffix).str());
360+
II = &AST.Idents.get(Name, tok::TokenKind::identifier);
361+
}
362+
if (!findRecordDeclInContext(II, BaseDecl->getDeclContext()))
363+
return II;
364+
// declaration with that name already exists - increment suffix and try
365+
// again until unique name is found
366+
suffix++;
367+
Name.truncate(NameLength);
368+
};
369+
}
370+
371+
// Creates a field declaration of given name and type for HLSL buffer layout
372+
// struct. Returns nullptr if the type cannot be use in HLSL Buffer layout.
373+
static FieldDecl *createFieldForHostLayoutStruct(Sema &S, const Type *Ty,
374+
IdentifierInfo *II,
375+
CXXRecordDecl *LayoutStruct) {
376+
if (isInvalidConstantBufferLeafElementType(Ty))
377+
return nullptr;
378+
379+
if (Ty->isRecordType()) {
380+
CXXRecordDecl *RD = Ty->getAsCXXRecordDecl();
381+
if (requiresImplicitBufferLayoutStructure(RD)) {
382+
RD = createHostLayoutStruct(S, RD);
383+
if (!RD)
384+
return nullptr;
385+
Ty = RD->getTypeForDecl();
386+
}
387+
}
388+
389+
QualType QT = QualType(Ty, 0);
390+
ASTContext &AST = S.getASTContext();
391+
TypeSourceInfo *TSI = AST.getTrivialTypeSourceInfo(QT, SourceLocation());
392+
auto *Field = FieldDecl::Create(AST, LayoutStruct, SourceLocation(),
393+
SourceLocation(), II, QT, TSI, nullptr, false,
394+
InClassInitStyle::ICIS_NoInit);
395+
Field->setAccess(AccessSpecifier::AS_private);
396+
return Field;
397+
}
398+
399+
// Creates host layout struct for a struct included in HLSL Buffer.
400+
// The layout struct will include only fields that are allowed in HLSL buffer.
401+
// These fields will be filtered out:
402+
// - resource classes
403+
// - empty structs
404+
// - zero-sized arrays
405+
// Returns nullptr if the resulting layout struct would be empty.
406+
static CXXRecordDecl *createHostLayoutStruct(Sema &S,
407+
CXXRecordDecl *StructDecl) {
408+
assert(requiresImplicitBufferLayoutStructure(StructDecl) &&
409+
"struct is already HLSL buffer compatible");
410+
411+
ASTContext &AST = S.getASTContext();
412+
DeclContext *DC = StructDecl->getDeclContext();
413+
IdentifierInfo *II = getHostLayoutStructName(S, StructDecl, false);
414+
415+
// reuse existing if the layout struct if it already exists
416+
if (CXXRecordDecl *RD = findRecordDeclInContext(II, DC))
417+
return RD;
418+
419+
CXXRecordDecl *LS = CXXRecordDecl::Create(
420+
AST, TagDecl::TagKind::Class, DC, SourceLocation(), SourceLocation(), II);
421+
LS->setImplicit(true);
422+
LS->startDefinition();
423+
424+
// copy base struct, create HLSL Buffer compatible version if needed
425+
if (unsigned NumBases = StructDecl->getNumBases()) {
426+
assert(NumBases == 1 && "HLSL supports only one base type");
427+
CXXBaseSpecifier Base = *StructDecl->bases_begin();
428+
CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl();
429+
if (requiresImplicitBufferLayoutStructure(BaseDecl)) {
430+
BaseDecl = createHostLayoutStruct(S, BaseDecl);
431+
if (BaseDecl) {
432+
TypeSourceInfo *TSI = AST.getTrivialTypeSourceInfo(
433+
QualType(BaseDecl->getTypeForDecl(), 0));
434+
Base = CXXBaseSpecifier(SourceRange(), false, StructDecl->isClass(),
435+
AS_none, TSI, SourceLocation());
436+
}
437+
}
438+
if (BaseDecl) {
439+
const CXXBaseSpecifier *BasesArray[1] = {&Base};
440+
LS->setBases(BasesArray, 1);
441+
}
442+
}
443+
444+
// filter struct fields
445+
for (const FieldDecl *FD : StructDecl->fields()) {
446+
const Type *Ty = FD->getType()->getUnqualifiedDesugaredType();
447+
if (FieldDecl *NewFD =
448+
createFieldForHostLayoutStruct(S, Ty, FD->getIdentifier(), LS))
449+
LS->addDecl(NewFD);
450+
}
451+
LS->completeDefinition();
452+
453+
if (LS->field_empty() && LS->getNumBases() == 0)
454+
return nullptr;
455+
456+
DC->addDecl(LS);
457+
return LS;
458+
}
459+
460+
// Creates host layout struct for HLSL Buffer. The struct will include only
461+
// fields of types that are allowed in HLSL buffer and it will filter out:
462+
// - static variable declarations
463+
// - resource classes
464+
// - empty structs
465+
// - zero-sized arrays
466+
// - non-variable declarations
467+
// The layour struct will be added to the HLSLBufferDecl declarations.
468+
void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) {
469+
ASTContext &AST = S.getASTContext();
470+
IdentifierInfo *II = getHostLayoutStructName(S, BufDecl, true);
471+
472+
CXXRecordDecl *LS =
473+
CXXRecordDecl::Create(AST, TagDecl::TagKind::Class, BufDecl,
474+
SourceLocation(), SourceLocation(), II);
475+
LS->setImplicit(true);
476+
LS->startDefinition();
477+
478+
for (const Decl *D : BufDecl->decls()) {
479+
const VarDecl *VD = dyn_cast<VarDecl>(D);
480+
if (!VD || VD->getStorageClass() == SC_Static)
481+
continue;
482+
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
483+
if (FieldDecl *FD =
484+
createFieldForHostLayoutStruct(S, Ty, VD->getIdentifier(), LS))
485+
LS->addDecl(FD);
486+
}
487+
LS->completeDefinition();
488+
BufDecl->addDecl(LS);
489+
}
490+
491+
// Handle end of cbuffer/tbuffer declaration
256492
void SemaHLSL::ActOnFinishBuffer(Decl *Dcl, SourceLocation RBrace) {
257493
auto *BufDecl = cast<HLSLBufferDecl>(Dcl);
258494
BufDecl->setRBraceLoc(RBrace);
259495

260496
validatePackoffset(SemaRef, BufDecl);
261497

498+
// create buffer layout struct
499+
createHostLayoutStructForBuffer(SemaRef, BufDecl);
500+
262501
SemaRef.PopDeclContext();
263502
}
264503

clang/test/AST/HLSL/ast-dump-comment-cbuffe-tbufferr.hlsl renamed to clang/test/AST/HLSL/ast-dump-comment-cbuffer-tbuffer.hlsl

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,26 @@ tbuffer B {
3737
int d;
3838
}
3939

40-
// AST:HLSLBufferDecl {{.*}}:11:1, line:20:1> line:11:9 cbuffer A
41-
// AST-NEXT:-HLSLResourceClassAttr {{.*}} <<invalid sloc>> Implicit CBuffer
42-
// AST-NEXT:-HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit CBuffer
43-
// AST-NEXT:FullComment {{.*}}<line:10:4, col:17>
44-
// AST-NEXT:`-ParagraphComment {{.*}}<col:4, col:17>
45-
// AST-NEXT:`-TextComment {{.*}}<col:4, col:17> Text=" CBuffer decl."
46-
// AST-NEXT:-VarDecl {{.*}}<line:15:5, col:11> col:11 a 'float'
47-
// AST-NEXT:`-VarDecl {{.*}}<line:19:5, col:9> col:9 b 'int'
48-
// AST-NEXT:HLSLBufferDecl {{.*}}<line:29:1, line:38:1> line:29:9 tbuffer B
49-
// AST-NEXT:-HLSLResourceClassAttr {{.*}} <<invalid sloc>> Implicit SRV
50-
// AST-NEXT:-HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit TBuffer
51-
// AST-NEXT:-FullComment {{.*}}<line:28:4, col:17>
52-
// AST-NEXT: `-ParagraphComment {{.*}}<col:4, col:17>
53-
// AST-NEXT: `-TextComment {{.*}}<col:4, col:17> Text=" TBuffer decl."
54-
// AST-NEXT:-VarDecl {{.*}}<line:33:5, col:11> col:11 c 'float'
55-
// AST-NEXT:`-VarDecl {{.*}} <line:37:5, col:9> col:9 d 'int'
40+
// AST: HLSLBufferDecl {{.*}} line:11:9 cbuffer A
41+
// AST-NEXT: HLSLResourceClassAttr {{.*}} Implicit CBuffer
42+
// AST-NEXT: HLSLResourceAttr {{.*}} Implicit CBuffer
43+
// AST-NEXT: FullComment
44+
// AST-NEXT: ParagraphComment
45+
// AST-NEXT: TextComment {{.*}} Text=" CBuffer decl."
46+
// AST-NEXT: VarDecl {{.*}} a 'float'
47+
// AST-NEXT: VarDecl {{.*}} b 'int'
48+
// AST-NEXT: CXXRecordDecl {{.*}} implicit class __layout_A definition
49+
// AST: FieldDecl {{.*}} a 'float'
50+
// AST-NEXT: FieldDecl {{.*}} b 'int'
51+
52+
// AST-NEXT: HLSLBufferDecl {{.*}} line:29:9 tbuffer B
53+
// AST-NEXT: HLSLResourceClassAttr {{.*}} Implicit SRV
54+
// AST-NEXT: HLSLResourceAttr {{.*}} Implicit TBuffer
55+
// AST-NEXT: FullComment
56+
// AST-NEXT: ParagraphComment
57+
// AST-NEXT: TextComment {{.*}} Text=" TBuffer decl."
58+
// AST-NEXT: VarDecl {{.*}} c 'float'
59+
// AST-NEXT: VarDecl {{.*}} d 'int'
60+
// AST-NEXT: CXXRecordDecl {{.*}} implicit class __layout_B definition
61+
// AST: FieldDecl {{.*}} c 'float'
62+
// AST-NEXT: FieldDecl {{.*}} d 'int'

0 commit comments

Comments
 (0)