Start support for HLSL RWBuffer

Most of the change here is fleshing out the HLSLExternalSemaSource with
builder implementations to build the builtin types. Eventually, I may
move some of this code into tablegen or a more managable declarative
file but I want to get the AST generation logic ready first.

This code adds two new types into the HLSL AST, `hlsl::Resource` and
`hlsl::RWBuffer`. The `Resource` type is just a wrapper around a handle
identifier, and is largely unused in source. It will morph a bit over
time as I work on getting the source compatability correct, but for now
it is a reasonable stand-in. The `RWBuffer` type is not ready for use.
I'm posting this change for review because it adds a lot of
infrastructure code and is testable.

There is one change to clang code outside the HLSL-specific logic here,
which addresses a behavior change introduced a long time ago in
967d438439ac. That change resulted in unintentionally breaking
situations where an incomplete template declaration was provided from
an AST source, and needed to be completed later by the external AST.
That situation doesn't happen in the normal AST importer flow, but can
happen when an AST source provides incomplete declarations of
templates. The solution is to annotate template specializations of
incomplete types with the HasExternalLexicalSource bit from the base
template.

Depends on D128012.

Differential Revision: https://reviews.llvm.org/D128569
This commit is contained in:
Chris Bieneman 2022-07-06 13:29:48 -05:00
parent bd08f413c0
commit 6e56d0dbe3
6 changed files with 290 additions and 1 deletions

View File

@ -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 Sema;
class HLSLExternalSemaSource : public ExternalSemaSource {
Sema *SemaPtr = nullptr;
NamespaceDecl *HLSLNamespace;
CXXRecordDecl *ResourceDecl;
using CompletionFunction = std::function<void(CXXRecordDecl *)>;
llvm::DenseMap<CXXRecordDecl *, CompletionFunction> Completions;
void defineHLSLVectorAlias();
void defineTrivialHLSLTypes();
void forwardDeclareHLSLTypes();
void completeBufferType(CXXRecordDecl *Record);
public:
~HLSLExternalSemaSource() override;
@ -34,6 +44,9 @@ public:
/// 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

View File

@ -930,6 +930,14 @@ ClassTemplateSpecializationDecl::Create(ASTContext &Context, TagKind TK,
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;
}

View File

@ -15,8 +15,161 @@
#include "clang/Basic/AttrKinds.h"
#include "clang/Sema/Sema.h"
#include <functional>
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<NamedDecl *> 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<NamedDecl *> Params;
TemplateParameterListBuilder(BuiltinTypeDeclBuilder &RB)
: Builder(RB), AST(RB.Record->getASTContext()) {}
~TemplateParameterListBuilder() { finalizeTemplateArgs(); }
TemplateParameterListBuilder &
addTypeParameter(StringRef Name, QualType DefaultValue = QualType()) {
unsigned Position = static_cast<unsigned>(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 @@ void HLSLExternalSemaSource::InitializeSema(Sema &S) {
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 @@ void HLSLExternalSemaSource::defineHLSLVectorAlias() {
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<CXXRecordDecl>(Tag))
return;
auto Record = cast<CXXRecordDecl>(Tag);
// If this is a specialization, we need to get the underlying templated
// declaration and complete that.
if (auto TDecl = dyn_cast<ClassTemplateSpecializationDecl>(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();
}

View File

@ -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]+}} <<invalid sloc>> <invalid sloc> implicit RWBuffer
// EMPTY-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> 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]+}} <<invalid sloc>> <invalid sloc> implicit <undeserialized declarations> class RWBuffer
// EMPTY-NEXT: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final
// There should be no more occurrances of RWBuffer
// EMPTY-NOT: RWBuffer
#ifndef EMPTY
RWBuffer<float> Buffer;
#endif
// CHECK: CXXRecordDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit <undeserialized declarations> class Resource definition
// CHECK: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final
// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit h 'void *'
// CHECK: ClassTemplateDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit RWBuffer
// CHECK-NEXT: TemplateTypeParmDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> 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]+}} <<invalid sloc>> <invalid sloc> implicit class RWBuffer definition
// CHECK: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final
// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit h 'void *'
// CHECK: ClassTemplateSpecializationDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> class RWBuffer definition
// CHECK: TemplateArgument type 'float'
// CHECK-NEXT: BuiltinType 0x{{[0-9A-Fa-f]+}} 'float'
// CHECK-NEXT: FinalAttr 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> Implicit final
// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc> implicit h 'void *'

View File

@ -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]+}} <<invalid sloc>> <invalid sloc> implicit <undeserialized declarations> 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]+}} <<invalid sloc>> Implicit final
// CHECK-NEXT: FieldDecl 0x{{[0-9A-Fa-f]+}} <<invalid sloc>> <invalid sloc>
// implicit h 'void *'

View File

@ -0,0 +1,12 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -fsyntax-only -verify %s
Resource ResourceDescriptorHeap[5];
typedef vector<float, 3> float3;
RWBuffer<float3> Buffer;
[numthreads(1,1,1)]
void main() {
(void)Buffer.h; // expected-error {{'h' is a private member of 'hlsl::RWBuffer<float __attribute__((ext_vector_type(3)))>'}}
// expected-note@* {{implicitly declared private here}}
}