diff --git a/clang/include/clang/Sema/HLSLExternalSemaSource.h b/clang/include/clang/Sema/HLSLExternalSemaSource.h --- a/clang/include/clang/Sema/HLSLExternalSemaSource.h +++ b/clang/include/clang/Sema/HLSLExternalSemaSource.h @@ -12,6 +12,8 @@ #ifndef CLANG_SEMA_HLSLEXTERNALSEMASOURCE_H #define CLANG_SEMA_HLSLEXTERNALSEMASOURCE_H +#include "llvm/ADT/DenseMap.h" + #include "clang/Sema/ExternalSemaSource.h" namespace clang { @@ -21,8 +23,16 @@ class HLSLExternalSemaSource : public ExternalSemaSource { Sema *SemaPtr = nullptr; NamespaceDecl *HLSLNamespace; + CXXRecordDecl *ResourceDecl; + + using CompletionFunction = std::function; + llvm::DenseMap Completions; void defineHLSLVectorAlias(); + void defineTrivialHLSLTypes(); + void forwardDeclareHLSLTypes(); + + void completeBufferType(CXXRecordDecl *Record); public: ~HLSLExternalSemaSource() override; @@ -34,6 +44,9 @@ /// Inform the semantic consumer that Sema is no longer available. void ForgetSema() override { SemaPtr = nullptr; } + + /// Complete an incomplete HLSL builtin type + void CompleteType(TagDecl *Tag) override; }; } // namespace clang diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp --- a/clang/lib/AST/DeclTemplate.cpp +++ b/clang/lib/AST/DeclTemplate.cpp @@ -930,6 +930,14 @@ SpecializedTemplate, Args, PrevDecl); Result->setMayHaveOutOfDateDef(false); + // If the template decl is incomplete, copy the external lexical storage from + // the base template. This allows instantiations of incomplete types to + // complete using the external AST if the template's declaration came from an + // external AST. + if (!SpecializedTemplate->getTemplatedDecl()->isCompleteDefinition()) + Result->setHasExternalLexicalStorage( + SpecializedTemplate->getTemplatedDecl()->hasExternalLexicalStorage()); + Context.getTypeDeclType(Result, PrevDecl); return Result; } diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp --- a/clang/lib/Sema/HLSLExternalSemaSource.cpp +++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp @@ -15,8 +15,161 @@ #include "clang/Basic/AttrKinds.h" #include "clang/Sema/Sema.h" +#include + using namespace clang; +namespace { + +struct TemplateParameterListBuilder; + +struct BuiltinTypeDeclBuilder { + CXXRecordDecl *Record = nullptr; + ClassTemplateDecl *Template = nullptr; + NamespaceDecl *HLSLNamespace = nullptr; + + BuiltinTypeDeclBuilder(CXXRecordDecl *R) : Record(R) { + Record->startDefinition(); + Template = Record->getDescribedClassTemplate(); + } + + BuiltinTypeDeclBuilder(Sema &S, NamespaceDecl *Namespace, StringRef Name) + : HLSLNamespace(Namespace) { + ASTContext &AST = S.getASTContext(); + IdentifierInfo &II = AST.Idents.get(Name, tok::TokenKind::identifier); + + Record = CXXRecordDecl::Create(AST, TagDecl::TagKind::TTK_Class, + HLSLNamespace, SourceLocation(), + SourceLocation(), &II, nullptr, true); + Record->setImplicit(true); + Record->setLexicalDeclContext(HLSLNamespace); + Record->setHasExternalLexicalStorage(); + + // Don't let anyone derive from built-in types + Record->addAttr(FinalAttr::CreateImplicit(AST, SourceRange(), + AttributeCommonInfo::AS_Keyword, + FinalAttr::Keyword_final)); + } + + ~BuiltinTypeDeclBuilder() { + if (HLSLNamespace && !Template) + HLSLNamespace->addDecl(Record); + } + + BuiltinTypeDeclBuilder & + addTemplateArgumentList(llvm::ArrayRef TemplateArgs) { + ASTContext &AST = Record->getASTContext(); + + auto *ParamList = + TemplateParameterList::Create(AST, SourceLocation(), SourceLocation(), + TemplateArgs, SourceLocation(), nullptr); + Template = ClassTemplateDecl::Create( + AST, Record->getDeclContext(), SourceLocation(), + DeclarationName(Record->getIdentifier()), ParamList, Record); + Record->setDescribedClassTemplate(Template); + Template->setImplicit(true); + Template->setLexicalDeclContext(Record->getDeclContext()); + Record->getDeclContext()->addDecl(Template); + + // Requesting the class name specialization will fault in required types. + QualType T = Template->getInjectedClassNameSpecialization(); + T = AST.getInjectedClassNameType(Record, T); + return *this; + } + + BuiltinTypeDeclBuilder & + addMemberVariable(StringRef Name, QualType Type, + AccessSpecifier Access = AccessSpecifier::AS_private) { + assert(Record->isBeingDefined() && + "Definition must be started before adding members!"); + ASTContext &AST = Record->getASTContext(); + + IdentifierInfo &II = AST.Idents.get(Name, tok::TokenKind::identifier); + TypeSourceInfo *MemTySource = + AST.getTrivialTypeSourceInfo(Type, SourceLocation()); + auto *Field = FieldDecl::Create( + AST, Record, SourceLocation(), SourceLocation(), &II, Type, MemTySource, + nullptr, false, InClassInitStyle::ICIS_NoInit); + Field->setAccess(Access); + Field->setImplicit(true); + Record->addDecl(Field); + return *this; + } + + BuiltinTypeDeclBuilder & + addHandleMember(AccessSpecifier Access = AccessSpecifier::AS_private) { + return addMemberVariable("h", Record->getASTContext().VoidPtrTy, Access); + } + + BuiltinTypeDeclBuilder &startDefinition() { + Record->startDefinition(); + return *this; + } + + BuiltinTypeDeclBuilder &completeDefinition() { + assert(Record->isBeingDefined() && + "Definition must be started before completing it."); + + Record->completeDefinition(); + return *this; + } + + TemplateParameterListBuilder addTemplateArgumentList(); +}; + +struct TemplateParameterListBuilder { + BuiltinTypeDeclBuilder &Builder; + ASTContext &AST; + llvm::SmallVector Params; + + TemplateParameterListBuilder(BuiltinTypeDeclBuilder &RB) + : Builder(RB), AST(RB.Record->getASTContext()) {} + + ~TemplateParameterListBuilder() { finalizeTemplateArgs(); } + + TemplateParameterListBuilder & + addTypeParameter(StringRef Name, QualType DefaultValue = QualType()) { + unsigned Position = static_cast(Params.size()); + auto *Decl = TemplateTypeParmDecl::Create( + AST, Builder.Record->getDeclContext(), SourceLocation(), + SourceLocation(), /* TemplateDepth */ 0, Position, + &AST.Idents.get(Name, tok::TokenKind::identifier), /* Typename */ false, + /* ParameterPack */ false); + if (!DefaultValue.isNull()) + Decl->setDefaultArgument(AST.getTrivialTypeSourceInfo(DefaultValue)); + + Params.emplace_back(Decl); + return *this; + } + + BuiltinTypeDeclBuilder &finalizeTemplateArgs() { + if (Params.empty()) + return Builder; + auto *ParamList = + TemplateParameterList::Create(AST, SourceLocation(), SourceLocation(), + Params, SourceLocation(), nullptr); + Builder.Template = ClassTemplateDecl::Create( + AST, Builder.Record->getDeclContext(), SourceLocation(), + DeclarationName(Builder.Record->getIdentifier()), ParamList, + Builder.Record); + Builder.Record->setDescribedClassTemplate(Builder.Template); + Builder.Template->setImplicit(true); + Builder.Template->setLexicalDeclContext(Builder.Record->getDeclContext()); + Builder.Record->getDeclContext()->addDecl(Builder.Template); + Params.clear(); + + QualType T = Builder.Template->getInjectedClassNameSpecialization(); + T = AST.getInjectedClassNameType(Builder.Record, T); + + return Builder; + } +}; + +TemplateParameterListBuilder BuiltinTypeDeclBuilder::addTemplateArgumentList() { + return TemplateParameterListBuilder(*this); +} +} // namespace + HLSLExternalSemaSource::~HLSLExternalSemaSource() {} void HLSLExternalSemaSource::InitializeSema(Sema &S) { @@ -28,7 +181,8 @@ SourceLocation(), SourceLocation(), &HLSL, nullptr); HLSLNamespace->setImplicit(true); AST.getTranslationUnitDecl()->addDecl(HLSLNamespace); - defineHLSLVectorAlias(); + defineTrivialHLSLTypes(); + forwardDeclareHLSLTypes(); // This adds a `using namespace hlsl` directive. In DXC, we don't put HLSL's // built in types inside a namespace, but we are planning to change that in @@ -94,3 +248,44 @@ Template->setLexicalDeclContext(Record->getDeclContext()); HLSLNamespace->addDecl(Template); } + +void HLSLExternalSemaSource::defineTrivialHLSLTypes() { + defineHLSLVectorAlias(); + + ResourceDecl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "Resource") + .startDefinition() + .addHandleMember(AccessSpecifier::AS_public) + .completeDefinition() + .Record; +} + +void HLSLExternalSemaSource::forwardDeclareHLSLTypes() { + CXXRecordDecl *Decl; + Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWBuffer") + .addTemplateArgumentList() + .addTypeParameter("element_type", SemaPtr->getASTContext().FloatTy) + .finalizeTemplateArgs() + .Record; + Completions.insert(std::make_pair( + Decl, std::bind(&HLSLExternalSemaSource::completeBufferType, this, + std::placeholders::_1))); +} + +void HLSLExternalSemaSource::CompleteType(TagDecl *Tag) { + if (!isa(Tag)) + return; + auto Record = cast(Tag); + + // If this is a specialization, we need to get the underlying templated + // declaration and complete that. + if (auto TDecl = dyn_cast(Record)) + Record = TDecl->getSpecializedTemplate()->getTemplatedDecl(); + auto It = Completions.find(Record); + if (It == Completions.end()) + return; + It->second(Record); +} + +void HLSLExternalSemaSource::completeBufferType(CXXRecordDecl *Record) { + BuiltinTypeDeclBuilder(Record).addHandleMember().completeDefinition(); +} diff --git a/clang/test/AST/HLSL/RWBuffer-AST.hlsl b/clang/test/AST/HLSL/RWBuffer-AST.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/AST/HLSL/RWBuffer-AST.hlsl @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -fsyntax-only -ast-dump -DEMPTY %s | FileCheck -check-prefix=EMPTY %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -fsyntax-only -ast-dump %s | FileCheck %s + + +// This test tests two different AST generations. The "EMPTY" test mode verifies +// the AST generated by forward declaration of the HLSL types which happens on +// initializing the HLSL external AST with an AST Context. + +// The non-empty mode has a use that requires the RWBuffer type be complete, +// which results in the AST being populated by the external AST source. That +// case covers the full implementation of the template declaration and the +// instantiated specialization. + +// EMPTY: ClassTemplateDecl 0x{{[0-9A-Fa-f]+}} <> implicit RWBuffer +// EMPTY-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <> class depth 0 index 0 element_type +// EMPTY-NEXT: TemplateArgument type 'float' +// EMPTY-NEXT: BuiltinType 0x{{[0-9A-Fa-f]+}} 'float' +// EMPTY-NEXT: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <> implicit class RWBuffer +// EMPTY-NEXT: FinalAttr 0x{{[0-9A-Fa-f]+}} <> Implicit final + +// There should be no more occurrances of RWBuffer +// EMPTY-NOT: RWBuffer + +#ifndef EMPTY + +RWBuffer Buffer; + +#endif + +// CHECK: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <> implicit class Resource definition +// CHECK: FinalAttr 0x{{[0-9A-Fa-f]+}} <> Implicit final +// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <> implicit h 'void *' + +// CHECK: ClassTemplateDecl 0x{{[0-9A-Fa-f]+}} <> implicit RWBuffer +// CHECK-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <> class depth 0 index 0 element_type +// CHECK-NEXT: TemplateArgument type 'float' +// CHECK-NEXT: BuiltinType 0x{{[0-9A-Fa-f]+}} 'float' +// CHECK-NEXT: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <> implicit class RWBuffer definition + +// CHECK: FinalAttr 0x{{[0-9A-Fa-f]+}} <> Implicit final +// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <> implicit h 'void *' +// CHECK: ClassTemplateSpecializationDecl 0x{{[0-9A-Fa-f]+}} <> class RWBuffer definition + +// CHECK: TemplateArgument type 'float' +// CHECK-NEXT: BuiltinType 0x{{[0-9A-Fa-f]+}} 'float' +// CHECK-NEXT: FinalAttr 0x{{[0-9A-Fa-f]+}} <> Implicit final +// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <> implicit h 'void *' diff --git a/clang/test/AST/HLSL/ResourceStruct.hlsl b/clang/test/AST/HLSL/ResourceStruct.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/AST/HLSL/ResourceStruct.hlsl @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -fsyntax-only -ast-dump %s | FileCheck %s + +// CHECK: NamespaceDecl {{.*}} implicit hlsl +// CHECK: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <> implicit class Resource definition +// CHECK-NEXT: DefinitionData +// CHECK-NEXT: DefaultConstructor exists trivial needs_implicit +// CHECK-NEXT: CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param +// CHECK-NEXT: MoveConstructor exists simple trivial needs_implicit +// CHECK-NEXT: CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param +// CHECK-NEXT: MoveAssignment exists simple trivial needs_implicit +// CHECK-NEXT: Destructor simple irrelevant trivial needs_implicit +// CHECK-NEXT: FinalAttr 0x{{[0-9A-Fa-f]+}} <> Implicit final +// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <> +// implicit h 'void *' diff --git a/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -fsyntax-only -verify %s + +Resource ResourceDescriptorHeap[5]; +typedef vector float3; + +RWBuffer Buffer; + +[numthreads(1,1,1)] +void main() { + (void)Buffer.h; // expected-error {{'h' is a private member of 'hlsl::RWBuffer'}} + // expected-note@* {{implicitly declared private here}} +}