Skip to content

Commit 478c24b

Browse files
authored
[HLSL] Add implicit resource element type concepts to AST (#112600)
This PR is step one on the journey to implement resource element type validation via C++20 concepts. The PR sets up the infrastructure for injecting implicit concept decls / concept specialization expressions into the AST, which will then be evaluated after template arguments are instantiated. This is not meant to be a complete implementation of the desired validation for HLSL, there are a couple of missing elements: 1. We need the __builtin_hlsl_is_typed_resource_element_compatible builtin to be implemented. 2. We need other constraints, like is_intangible 3. We need to put the first 2 points together, and construct a finalized constraint expression, which should differ between typed and raw buffers This is just an initial PR that puts some of the core infrastructure in place.
1 parent 2f55de4 commit 478c24b

File tree

6 files changed

+250
-20
lines changed

6 files changed

+250
-20
lines changed

clang/lib/Sema/HLSLExternalSemaSource.cpp

Lines changed: 205 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,9 @@ struct BuiltinTypeDeclBuilder {
289289
}
290290

291291
TemplateParameterListBuilder addTemplateArgumentList(Sema &S);
292-
BuiltinTypeDeclBuilder &addSimpleTemplateParams(Sema &S,
293-
ArrayRef<StringRef> Names);
292+
BuiltinTypeDeclBuilder &
293+
addSimpleTemplateParams(Sema &S, ArrayRef<StringRef> Names, ConceptDecl *CD);
294+
BuiltinTypeDeclBuilder &addConceptSpecializationExpr(Sema &S);
294295
};
295296

296297
struct TemplateParameterListBuilder {
@@ -312,30 +313,129 @@ struct TemplateParameterListBuilder {
312313
S.Context, Builder.Record->getDeclContext(), SourceLocation(),
313314
SourceLocation(), /* TemplateDepth */ 0, Position,
314315
&S.Context.Idents.get(Name, tok::TokenKind::identifier),
315-
/* Typename */ false,
316-
/* ParameterPack */ false);
316+
/* Typename */ true,
317+
/* ParameterPack */ false,
318+
/* HasTypeConstraint*/ false);
317319
if (!DefaultValue.isNull())
318320
Decl->setDefaultArgument(
319321
S.Context, S.getTrivialTemplateArgumentLoc(DefaultValue, QualType(),
320322
SourceLocation()));
321-
322323
Params.emplace_back(Decl);
323324
return *this;
324325
}
325326

326-
BuiltinTypeDeclBuilder &finalizeTemplateArgs() {
327+
/*
328+
The concept specialization expression (CSE) constructed in
329+
constructConceptSpecializationExpr is constructed so that it
330+
matches the CSE that is constructed when parsing the below C++ code:
331+
332+
template<typename T>
333+
concept is_typed_resource_element_compatible = sizeof(T) <= 16;
334+
335+
template<typename element_type> requires
336+
is_typed_resource_element_compatible<element_type>
337+
struct RWBuffer {
338+
element_type Val;
339+
};
340+
341+
int fn() {
342+
RWBuffer<int> Buf;
343+
}
344+
345+
When dumping the AST and filtering for "RWBuffer", the resulting AST
346+
structure is what we're trying to construct below, specifically the
347+
CSE portion.
348+
*/
349+
ConceptSpecializationExpr *
350+
constructConceptSpecializationExpr(Sema &S, ConceptDecl *CD) {
351+
ASTContext &Context = S.getASTContext();
352+
SourceLocation Loc = Builder.Record->getBeginLoc();
353+
DeclarationNameInfo DNI(CD->getDeclName(), Loc);
354+
NestedNameSpecifierLoc NNSLoc;
355+
DeclContext *DC = Builder.Record->getDeclContext();
356+
TemplateArgumentListInfo TALI(Loc, Loc);
357+
358+
// Assume that the concept decl has just one template parameter
359+
// This parameter should have been added when CD was constructed
360+
// in getTypedBufferConceptDecl
361+
assert(CD->getTemplateParameters()->size() == 1 &&
362+
"unexpected concept decl parameter count");
363+
TemplateTypeParmDecl *ConceptTTPD = dyn_cast<TemplateTypeParmDecl>(
364+
CD->getTemplateParameters()->getParam(0));
365+
366+
// this TemplateTypeParmDecl is the template for the resource, and is
367+
// used to construct a template argumentthat will be used
368+
// to construct the ImplicitConceptSpecializationDecl
369+
TemplateTypeParmDecl *T = TemplateTypeParmDecl::Create(
370+
Context, // AST context
371+
Builder.Record->getDeclContext(), // DeclContext
372+
SourceLocation(), SourceLocation(),
373+
/*depth=*/0, // Depth in the template parameter list
374+
/*position=*/0, // Position in the template parameter list
375+
/*id=*/nullptr, // Identifier for 'T'
376+
/*Typename=*/true, // Indicates this is a 'typename' or 'class'
377+
/*ParameterPack=*/false, // Not a parameter pack
378+
/*HasTypeConstraint=*/false // Has no type constraint
379+
);
380+
381+
T->setDeclContext(DC);
382+
383+
QualType ConceptTType = Context.getTypeDeclType(ConceptTTPD);
384+
385+
// this is the 2nd template argument node, on which
386+
// the concept constraint is actually being applied: 'element_type'
387+
TemplateArgument ConceptTA = TemplateArgument(ConceptTType);
388+
389+
QualType CSETType = Context.getTypeDeclType(T);
390+
391+
// this is the 1st template argument node, which represents
392+
// the abstract type that a concept would refer to: 'T'
393+
TemplateArgument CSETA = TemplateArgument(CSETType);
394+
395+
ImplicitConceptSpecializationDecl *ImplicitCSEDecl =
396+
ImplicitConceptSpecializationDecl::Create(
397+
Context, Builder.Record->getDeclContext(), Loc, {CSETA});
398+
399+
// Constraint satisfaction is used to construct the
400+
// ConceptSpecailizationExpr, and represents the 2nd Template Argument,
401+
// located at the bottom of the sample AST above.
402+
const ConstraintSatisfaction CS(CD, {ConceptTA});
403+
TemplateArgumentLoc TAL = S.getTrivialTemplateArgumentLoc(
404+
ConceptTA, QualType(), SourceLocation());
405+
406+
TALI.addArgument(TAL);
407+
const ASTTemplateArgumentListInfo *ATALI =
408+
ASTTemplateArgumentListInfo::Create(Context, TALI);
409+
410+
// In the concept reference, ATALI is what adds the extra
411+
// TemplateArgument node underneath CSE
412+
ConceptReference *CR =
413+
ConceptReference::Create(Context, NNSLoc, Loc, DNI, CD, CD, ATALI);
414+
415+
ConceptSpecializationExpr *CSE =
416+
ConceptSpecializationExpr::Create(Context, CR, ImplicitCSEDecl, &CS);
417+
418+
return CSE;
419+
}
420+
421+
BuiltinTypeDeclBuilder &finalizeTemplateArgs(ConceptDecl *CD = nullptr) {
327422
if (Params.empty())
328423
return Builder;
424+
ConceptSpecializationExpr *CSE =
425+
CD ? constructConceptSpecializationExpr(S, CD) : nullptr;
426+
329427
auto *ParamList = TemplateParameterList::Create(S.Context, SourceLocation(),
330428
SourceLocation(), Params,
331-
SourceLocation(), nullptr);
429+
SourceLocation(), CSE);
332430
Builder.Template = ClassTemplateDecl::Create(
333431
S.Context, Builder.Record->getDeclContext(), SourceLocation(),
334432
DeclarationName(Builder.Record->getIdentifier()), ParamList,
335433
Builder.Record);
434+
336435
Builder.Record->setDescribedClassTemplate(Builder.Template);
337436
Builder.Template->setImplicit(true);
338437
Builder.Template->setLexicalDeclContext(Builder.Record->getDeclContext());
438+
339439
// NOTE: setPreviousDecl before addDecl so new decl replace old decl when
340440
// make visible.
341441
Builder.Template->setPreviousDecl(Builder.PrevTemplate);
@@ -355,13 +455,13 @@ BuiltinTypeDeclBuilder::addTemplateArgumentList(Sema &S) {
355455
return TemplateParameterListBuilder(S, *this);
356456
}
357457

358-
BuiltinTypeDeclBuilder &
359-
BuiltinTypeDeclBuilder::addSimpleTemplateParams(Sema &S,
360-
ArrayRef<StringRef> Names) {
458+
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addSimpleTemplateParams(
459+
Sema &S, ArrayRef<StringRef> Names, ConceptDecl *CD = nullptr) {
361460
TemplateParameterListBuilder Builder = this->addTemplateArgumentList(S);
362461
for (StringRef Name : Names)
363462
Builder.addTypeParameter(Name);
364-
return Builder.finalizeTemplateArgs();
463+
464+
return Builder.finalizeTemplateArgs(CD);
365465
}
366466

367467
HLSLExternalSemaSource::~HLSLExternalSemaSource() {}
@@ -472,10 +572,103 @@ static BuiltinTypeDeclBuilder setupBufferType(CXXRecordDecl *Decl, Sema &S,
472572
.addDefaultHandleConstructor(S);
473573
}
474574

575+
BinaryOperator *constructSizeOfLEQ16Expr(ASTContext &Context,
576+
SourceLocation NameLoc,
577+
TemplateTypeParmDecl *T) {
578+
// Obtain the QualType for 'unsigned long'
579+
QualType UnsignedLongType = Context.UnsignedLongTy;
580+
581+
// Create a QualType that points to this TemplateTypeParmDecl
582+
QualType TType = Context.getTypeDeclType(T);
583+
584+
// Create a TypeSourceInfo for the template type parameter 'T'
585+
TypeSourceInfo *TTypeSourceInfo =
586+
Context.getTrivialTypeSourceInfo(TType, NameLoc);
587+
588+
UnaryExprOrTypeTraitExpr *sizeOfExpr = new (Context) UnaryExprOrTypeTraitExpr(
589+
UETT_SizeOf, TTypeSourceInfo, UnsignedLongType, NameLoc, NameLoc);
590+
591+
// Create an IntegerLiteral for the value '16' with size type
592+
QualType SizeType = Context.getSizeType();
593+
llvm::APInt SizeValue = llvm::APInt(Context.getTypeSize(SizeType), 16);
594+
IntegerLiteral *SizeLiteral =
595+
new (Context) IntegerLiteral(Context, SizeValue, SizeType, NameLoc);
596+
597+
QualType BoolTy = Context.BoolTy;
598+
599+
BinaryOperator *binaryOperator =
600+
BinaryOperator::Create(Context, sizeOfExpr, // Left-hand side expression
601+
SizeLiteral, // Right-hand side expression
602+
BO_LE, // Binary operator kind (<=)
603+
BoolTy, // Result type (bool)
604+
VK_LValue, // Value kind
605+
OK_Ordinary, // Object kind
606+
NameLoc, // Source location of operator
607+
FPOptionsOverride());
608+
609+
return binaryOperator;
610+
}
611+
612+
Expr *constructTypedBufferConstraintExpr(Sema &S, SourceLocation NameLoc,
613+
TemplateTypeParmDecl *T) {
614+
ASTContext &Context = S.getASTContext();
615+
616+
// first get the "sizeof(T) <= 16" expression, as a binary operator
617+
BinaryOperator *SizeOfLEQ16 = constructSizeOfLEQ16Expr(Context, NameLoc, T);
618+
// TODO: add the 'builtin_hlsl_is_typed_resource_element_compatible' builtin
619+
// and return a binary operator that evaluates the builtin on the given
620+
// template type parameter 'T'.
621+
// Defined in issue https://github.com/llvm/llvm-project/issues/113223
622+
return SizeOfLEQ16;
623+
}
624+
625+
ConceptDecl *constructTypedBufferConceptDecl(Sema &S, NamespaceDecl *NSD) {
626+
ASTContext &Context = S.getASTContext();
627+
DeclContext *DC = NSD->getDeclContext();
628+
SourceLocation DeclLoc = SourceLocation();
629+
630+
IdentifierInfo &ElementTypeII = Context.Idents.get("element_type");
631+
TemplateTypeParmDecl *T = TemplateTypeParmDecl::Create(
632+
Context, NSD->getDeclContext(), DeclLoc, DeclLoc,
633+
/*depth=*/0,
634+
/*position=*/0,
635+
/*id=*/&ElementTypeII,
636+
/*Typename=*/true,
637+
/*ParameterPack=*/false);
638+
639+
T->setDeclContext(DC);
640+
T->setReferenced();
641+
642+
// Create and Attach Template Parameter List to ConceptDecl
643+
TemplateParameterList *ConceptParams = TemplateParameterList::Create(
644+
Context, DeclLoc, DeclLoc, {T}, DeclLoc, nullptr);
645+
646+
DeclarationName DeclName = DeclarationName(
647+
&Context.Idents.get("__is_typed_resource_element_compatible"));
648+
Expr *ConstraintExpr = constructTypedBufferConstraintExpr(S, DeclLoc, T);
649+
650+
// Create a ConceptDecl
651+
ConceptDecl *CD =
652+
ConceptDecl::Create(Context, NSD->getDeclContext(), DeclLoc, DeclName,
653+
ConceptParams, ConstraintExpr);
654+
655+
// Attach the template parameter list to the ConceptDecl
656+
CD->setTemplateParameters(ConceptParams);
657+
658+
// Add the concept declaration to the Translation Unit Decl
659+
NSD->getDeclContext()->addDecl(CD);
660+
661+
return CD;
662+
}
663+
475664
void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
476665
CXXRecordDecl *Decl;
666+
ConceptDecl *TypedBufferConcept =
667+
constructTypedBufferConceptDecl(*SemaPtr, HLSLNamespace);
668+
477669
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWBuffer")
478-
.addSimpleTemplateParams(*SemaPtr, {"element_type"})
670+
.addSimpleTemplateParams(*SemaPtr, {"element_type"},
671+
TypedBufferConcept)
479672
.Record;
480673

481674
onCompletion(Decl, [this](CXXRecordDecl *Decl) {

clang/test/AST/HLSL/RWBuffer-AST.hlsl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@
1111
// instantiated specialization.
1212

1313
// EMPTY: ClassTemplateDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit RWBuffer
14-
// EMPTY-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> class depth 0 index 0 element_type
14+
// EMPTY-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> typename depth 0 index 0 element_type
15+
// EMPTY-NEXT: ConceptSpecializationExpr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> 'bool' Concept 0x{{[0-9A-Fa-f]+}} '__is_typed_resource_element_compatible'
16+
// EMPTY-NEXT: ImplicitConceptSpecializationDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc>
17+
// EMPTY-NEXT: TemplateArgument type 'type-parameter-0-0'
18+
// EMPTY-NEXT: TemplateTypeParmType 0x{{[0-9A-Fa-f]+}} 'type-parameter-0-0' dependent depth 0 index 0
19+
// EMPTY-NEXT: TemplateTypeParm 0x{{[0-9A-Fa-f]+}} ''
20+
// EMPTY-NEXT: TemplateArgument type 'element_type':'type-parameter-0-0'
21+
// EMPTY-NEXT: TemplateTypeParmType 0x{{[0-9A-Fa-f]+}} 'element_type' dependent depth 0 index 0
22+
// EMPTY-NEXT: TemplateTypeParm 0x{{[0-9A-Fa-f]+}} 'element_type'
1523
// EMPTY-NEXT: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit <undeserialized declarations> class RWBuffer
1624
// EMPTY-NEXT: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final
1725

@@ -25,7 +33,15 @@ RWBuffer<float> Buffer;
2533
#endif
2634

2735
// CHECK: ClassTemplateDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit RWBuffer
28-
// CHECK-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> class depth 0 index 0 element_type
36+
// CHECK-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> typename depth 0 index 0 element_type
37+
// CHECK-NEXT: ConceptSpecializationExpr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> 'bool' Concept 0x{{[0-9A-Fa-f]+}} '__is_typed_resource_element_compatible'
38+
// CHECK-NEXT: ImplicitConceptSpecializationDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc>
39+
// CHECK-NEXT: TemplateArgument type 'type-parameter-0-0'
40+
// CHECK-NEXT: TemplateTypeParmType 0x{{[0-9A-Fa-f]+}} 'type-parameter-0-0' dependent depth 0 index 0
41+
// CHECK-NEXT: TemplateTypeParm 0x{{[0-9A-Fa-f]+}} ''
42+
// CHECK-NEXT: TemplateArgument type 'element_type':'type-parameter-0-0'
43+
// CHECK-NEXT: TemplateTypeParmType 0x{{[0-9A-Fa-f]+}} 'element_type' dependent depth 0 index 0
44+
// CHECK-NEXT: TemplateTypeParm 0x{{[0-9A-Fa-f]+}} 'element_type'
2945
// CHECK-NEXT: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit class RWBuffer definition
3046

3147
// CHECK: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final

clang/test/AST/HLSL/StructuredBuffer-AST.hlsl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// instantiated specialization.
1313

1414
// EMPTY: ClassTemplateDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit StructuredBuffer
15-
// EMPTY-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> class depth 0 index 0 element_type
15+
// EMPTY-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> typename depth 0 index 0 element_type
1616
// EMPTY-NEXT: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit <undeserialized declarations> class StructuredBuffer
1717
// EMPTY-NEXT: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final
1818

@@ -26,7 +26,7 @@ StructuredBuffer<float> Buffer;
2626
#endif
2727

2828
// CHECK: ClassTemplateDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit StructuredBuffer
29-
// CHECK-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> class depth 0 index 0 element_type
29+
// CHECK-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> typename depth 0 index 0 element_type
3030
// CHECK-NEXT: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit class StructuredBuffer definition
3131

3232
// CHECK: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -ast-dump -ast-dump-filter=__is_typed_resource_element_compatible %s | FileCheck %s
2+
3+
// CHECK: ConceptDecl 0x{{[0-9a-f]+}} <<invalid sloc>> <invalid sloc> __is_typed_resource_element_compatible
4+
// CHECK: |-TemplateTypeParmDecl 0x{{[0-9a-f]+}} <<invalid sloc>> <invalid sloc> referenced typename depth 0 index 0 element_type
5+
// CHECK: `-BinaryOperator 0x{{[0-9a-f]+}} <<invalid sloc>> 'bool' lvalue '<='
6+
// CHECK: |-UnaryExprOrTypeTraitExpr 0x{{[0-9a-f]+}} <<invalid sloc>> 'unsigned long' sizeof 'element_type'
7+
// CHECK: `-IntegerLiteral 0x{{[0-9a-f]+}} <<invalid sloc>> 'unsigned long' 16
8+
9+
10+
RWBuffer<float> Buffer;

clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,24 @@ typedef vector<float, 3> float3;
55
RWBuffer<float3> Buffer;
66

77
// expected-error@+2 {{class template 'RWBuffer' requires template arguments}}
8-
// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer}}
8+
// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class RWBuffer {}}}
99
RWBuffer BufferErr1;
1010

1111
// expected-error@+2 {{too few template arguments for class template 'RWBuffer'}}
12-
// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer}}
12+
// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class RWBuffer {}}}
1313
RWBuffer<> BufferErr2;
1414

15+
struct threeDoubles {
16+
double a;
17+
double b;
18+
double c;
19+
};
20+
21+
// expected-error@+3 {{constraints not satisfied for class template 'RWBuffer'}}
22+
// expected-note@*:* {{because 'threeDoubles' does not satisfy '__is_typed_resource_element_compatible'}}
23+
// expected-note@*:* {{because 'sizeof(threeDoubles) <= 16UL' (24 <= 16) evaluated to false}}
24+
RWBuffer<threeDoubles> BufferErr3;
25+
1526
[numthreads(1,1,1)]
1627
void main() {
1728
(void)Buffer.h; // expected-error {{'h' is a private member of 'hlsl::RWBuffer<vector<float, 3>>'}}

clang/test/SemaHLSL/BuiltIns/StructuredBuffers.hlsl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ typedef vector<float, 3> float3;
55
StructuredBuffer<float3> Buffer;
66

77
// expected-error@+2 {{class template 'StructuredBuffer' requires template arguments}}
8-
// expected-note@*:* {{template declaration from hidden source: template <class element_type> class StructuredBuffer}}
8+
// expected-note@*:* {{template declaration from hidden source: template <typename element_type> class StructuredBuffer {}}}
99
StructuredBuffer BufferErr1;
1010

1111
// expected-error@+2 {{too few template arguments for class template 'StructuredBuffer'}}
12-
// expected-note@*:* {{template declaration from hidden source: template <class element_type> class StructuredBuffer}}
12+
// expected-note@*:* {{template declaration from hidden source: template <typename element_type> class StructuredBuffer {}}}
1313
StructuredBuffer<> BufferErr2;
1414

1515
[numthreads(1,1,1)]

0 commit comments

Comments
 (0)