llvm-project/clang/lib/Sema/HLSLExternalSemaSource.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1060 lines
42 KiB
C++
Raw Normal View History

//===--- HLSLExternalSemaSource.cpp - HLSL Sema Source --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
//
//===----------------------------------------------------------------------===//
#include "clang/Sema/HLSLExternalSemaSource.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaHLSL.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Frontend/HLSL/HLSLResource.h"
#include "llvm/Support/ErrorHandling.h"
#include <functional>
using namespace clang;
using namespace llvm::hlsl;
static FunctionDecl *lookupBuiltinFunction(Sema &S, StringRef Name);
namespace {
struct TemplateParameterListBuilder;
class BuiltinTypeDeclBuilder {
ClassTemplateDecl *Template = nullptr;
ClassTemplateDecl *PrevTemplate = nullptr;
NamespaceDecl *HLSLNamespace = nullptr;
llvm::StringMap<FieldDecl *> Fields;
public:
Sema &SemaRef;
CXXRecordDecl *Record = nullptr;
friend struct TemplateParameterListBuilder;
BuiltinTypeDeclBuilder(Sema &SemaRef, CXXRecordDecl *R)
: SemaRef(SemaRef), Record(R) {
Record->startDefinition();
Template = Record->getDescribedClassTemplate();
}
BuiltinTypeDeclBuilder(Sema &SemaRef, NamespaceDecl *Namespace,
StringRef Name)
: HLSLNamespace(Namespace), SemaRef(SemaRef) {
ASTContext &AST = SemaRef.getASTContext();
IdentifierInfo &II = AST.Idents.get(Name, tok::TokenKind::identifier);
LookupResult Result(SemaRef, &II, SourceLocation(), Sema::LookupTagName);
CXXRecordDecl *PrevDecl = nullptr;
if (SemaRef.LookupQualifiedName(Result, HLSLNamespace)) {
// Declaration already exists (from precompiled headers)
NamedDecl *Found = Result.getFoundDecl();
if (auto *TD = dyn_cast<ClassTemplateDecl>(Found)) {
PrevDecl = TD->getTemplatedDecl();
PrevTemplate = TD;
} else
PrevDecl = dyn_cast<CXXRecordDecl>(Found);
assert(PrevDecl && "Unexpected lookup result type.");
}
if (PrevDecl && PrevDecl->isCompleteDefinition()) {
Record = PrevDecl;
Template = PrevTemplate;
return;
}
Record = CXXRecordDecl::Create(AST, TagDecl::TagKind::Class, HLSLNamespace,
SourceLocation(), SourceLocation(), &II,
PrevDecl, true);
Record->setImplicit(true);
Record->setLexicalDeclContext(HLSLNamespace);
Record->setHasExternalLexicalStorage();
// Don't let anyone derive from built-in types.
Record->addAttr(FinalAttr::CreateImplicit(AST, SourceRange(),
FinalAttr::Keyword_final));
}
~BuiltinTypeDeclBuilder() {
if (HLSLNamespace && !Template && Record->getDeclContext() == HLSLNamespace)
HLSLNamespace->addDecl(Record);
}
CXXRecordDecl *finalizeForwardDeclaration() {
// Force the QualType to be generated for the record declaration. In most
// cases this will happen naturally when something uses the type the
// QualType gets lazily created. Unfortunately, with our injected types if a
// type isn't used in a translation unit the QualType may not get
// automatically generated before a PCH is generated. To resolve this we
// just force that the QualType is generated after we create a forward
// declaration.
(void)Record->getASTContext().getRecordType(Record);
return Record;
}
BuiltinTypeDeclBuilder &
addMemberVariable(StringRef Name, QualType Type, llvm::ArrayRef<Attr *> Attrs,
AccessSpecifier Access = AccessSpecifier::AS_private) {
assert(!Record->isCompleteDefinition() && "record is already complete");
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);
for (Attr *A : Attrs) {
if (A)
Field->addAttr(A);
}
Record->addDecl(Field);
Fields[Name] = Field;
return *this;
}
BuiltinTypeDeclBuilder &
addHandleMember(ResourceClass RC, ResourceKind RK, bool IsROV, bool RawBuffer,
AccessSpecifier Access = AccessSpecifier::AS_private) {
assert(!Record->isCompleteDefinition() && "record is already complete");
ASTContext &Ctx = SemaRef.getASTContext();
TypeSourceInfo *ElementTypeInfo =
Ctx.getTrivialTypeSourceInfo(getHandleElementType(), SourceLocation());
// add handle member with resource type attributes
QualType AttributedResTy = QualType();
SmallVector<const Attr *> Attrs = {
HLSLResourceClassAttr::CreateImplicit(Ctx, RC),
IsROV ? HLSLROVAttr::CreateImplicit(Ctx) : nullptr,
RawBuffer ? HLSLRawBufferAttr::CreateImplicit(Ctx) : nullptr,
ElementTypeInfo
? HLSLContainedTypeAttr::CreateImplicit(Ctx, ElementTypeInfo)
: nullptr};
Attr *ResourceAttr = HLSLResourceAttr::CreateImplicit(Ctx, RK);
if (CreateHLSLAttributedResourceType(SemaRef, Ctx.HLSLResourceTy, Attrs,
AttributedResTy))
addMemberVariable("__handle", AttributedResTy, {ResourceAttr}, Access);
return *this;
}
BuiltinTypeDeclBuilder &addDefaultHandleConstructor() {
if (Record->isCompleteDefinition())
return *this;
ASTContext &AST = Record->getASTContext();
QualType ConstructorType =
AST.getFunctionType(AST.VoidTy, {}, FunctionProtoType::ExtProtoInfo());
CanQualType CanTy = Record->getTypeForDecl()->getCanonicalTypeUnqualified();
DeclarationName Name = AST.DeclarationNames.getCXXConstructorName(CanTy);
CXXConstructorDecl *Constructor = CXXConstructorDecl::Create(
AST, Record, SourceLocation(),
DeclarationNameInfo(Name, SourceLocation()), ConstructorType,
AST.getTrivialTypeSourceInfo(ConstructorType, SourceLocation()),
ExplicitSpecifier(), false, true, false,
ConstexprSpecKind::Unspecified);
Constructor->setBody(CompoundStmt::Create(
AST, {}, FPOptionsOverride(), SourceLocation(), SourceLocation()));
Constructor->setAccess(AccessSpecifier::AS_public);
Record->addDecl(Constructor);
return *this;
}
BuiltinTypeDeclBuilder &addArraySubscriptOperators() {
ASTContext &AST = Record->getASTContext();
DeclarationName Subscript =
AST.DeclarationNames.getCXXOperatorName(OO_Subscript);
addHandleAccessFunction(Subscript, /*IsConst=*/true, /*IsRef=*/true);
addHandleAccessFunction(Subscript, /*IsConst=*/false, /*IsRef=*/true);
return *this;
}
FieldDecl *getResourceHandleField() {
auto I = Fields.find("__handle");
assert(I != Fields.end() &&
I->second->getType()->isHLSLAttributedResourceType() &&
"record does not have resource handle field");
return I->second;
}
QualType getFirstTemplateTypeParam() {
assert(Template && "record it not a template");
if (const auto *TTD = dyn_cast<TemplateTypeParmDecl>(
Template->getTemplateParameters()->getParam(0))) {
return QualType(TTD->getTypeForDecl(), 0);
}
return QualType();
}
QualType getHandleElementType() {
if (Template)
return getFirstTemplateTypeParam();
// TODO: Should we default to VoidTy? Using `i8` is arguably ambiguous.
return SemaRef.getASTContext().Char8Ty;
}
BuiltinTypeDeclBuilder &startDefinition() {
assert(!Record->isCompleteDefinition() && "record is already complete");
Record->startDefinition();
return *this;
}
BuiltinTypeDeclBuilder &completeDefinition() {
assert(!Record->isCompleteDefinition() && "record is already complete");
assert(Record->isBeingDefined() &&
"Definition must be started before completing it.");
Record->completeDefinition();
return *this;
}
Expr *getConstantIntExpr(int value) {
ASTContext &AST = SemaRef.getASTContext();
return IntegerLiteral::Create(
AST, llvm::APInt(AST.getTypeSize(AST.IntTy), value, true), AST.IntTy,
SourceLocation());
}
TemplateParameterListBuilder addTemplateArgumentList();
BuiltinTypeDeclBuilder &addSimpleTemplateParams(ArrayRef<StringRef> Names,
ConceptDecl *CD);
// Builtin types methods
BuiltinTypeDeclBuilder &addIncrementCounterMethod();
BuiltinTypeDeclBuilder &addDecrementCounterMethod();
BuiltinTypeDeclBuilder &addHandleAccessFunction(DeclarationName &Name,
bool IsConst, bool IsRef);
BuiltinTypeDeclBuilder &addAppendMethod();
BuiltinTypeDeclBuilder &addConsumeMethod();
};
struct TemplateParameterListBuilder {
BuiltinTypeDeclBuilder &Builder;
llvm::SmallVector<NamedDecl *> Params;
TemplateParameterListBuilder(BuiltinTypeDeclBuilder &RB) : Builder(RB) {}
~TemplateParameterListBuilder() { finalizeTemplateArgs(); }
TemplateParameterListBuilder &
addTypeParameter(StringRef Name, QualType DefaultValue = QualType()) {
assert(!Builder.Record->isCompleteDefinition() &&
"record is already complete");
ASTContext &AST = Builder.SemaRef.getASTContext();
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 */ true,
/* ParameterPack */ false,
/* HasTypeConstraint*/ false);
if (!DefaultValue.isNull())
Decl->setDefaultArgument(AST,
Builder.SemaRef.getTrivialTemplateArgumentLoc(
DefaultValue, QualType(), SourceLocation()));
Params.emplace_back(Decl);
return *this;
}
// The concept specialization expression (CSE) constructed in
// constructConceptSpecializationExpr is constructed so that it
// matches the CSE that is constructed when parsing the below C++ code:
//
// template<typename T>
// concept is_typed_resource_element_compatible =
// __builtin_hlsl_typed_resource_element_compatible<T>
//
// template<typename element_type> requires
// is_typed_resource_element_compatible<element_type>
// struct RWBuffer {
// element_type Val;
// };
//
// int fn() {
// RWBuffer<int> Buf;
// }
//
// When dumping the AST and filtering for "RWBuffer", the resulting AST
// structure is what we're trying to construct below, specifically the
// CSE portion.
ConceptSpecializationExpr *
constructConceptSpecializationExpr(Sema &S, ConceptDecl *CD) {
ASTContext &Context = S.getASTContext();
SourceLocation Loc = Builder.Record->getBeginLoc();
DeclarationNameInfo DNI(CD->getDeclName(), Loc);
NestedNameSpecifierLoc NNSLoc;
DeclContext *DC = Builder.Record->getDeclContext();
TemplateArgumentListInfo TALI(Loc, Loc);
// Assume that the concept decl has just one template parameter
// This parameter should have been added when CD was constructed
// in getTypedBufferConceptDecl
assert(CD->getTemplateParameters()->size() == 1 &&
"unexpected concept decl parameter count");
TemplateTypeParmDecl *ConceptTTPD = dyn_cast<TemplateTypeParmDecl>(
CD->getTemplateParameters()->getParam(0));
// this TemplateTypeParmDecl is the template for the resource, and is
// used to construct a template argumentthat will be used
// to construct the ImplicitConceptSpecializationDecl
TemplateTypeParmDecl *T = TemplateTypeParmDecl::Create(
Context, // AST context
Builder.Record->getDeclContext(), // DeclContext
SourceLocation(), SourceLocation(),
/*D=*/0, // Depth in the template parameter list
/*P=*/0, // Position in the template parameter list
/*Id=*/nullptr, // Identifier for 'T'
/*Typename=*/true, // Indicates this is a 'typename' or 'class'
/*ParameterPack=*/false, // Not a parameter pack
/*HasTypeConstraint=*/false // Has no type constraint
);
T->setDeclContext(DC);
QualType ConceptTType = Context.getTypeDeclType(ConceptTTPD);
// this is the 2nd template argument node, on which
// the concept constraint is actually being applied: 'element_type'
TemplateArgument ConceptTA = TemplateArgument(ConceptTType);
QualType CSETType = Context.getTypeDeclType(T);
// this is the 1st template argument node, which represents
// the abstract type that a concept would refer to: 'T'
TemplateArgument CSETA = TemplateArgument(CSETType);
ImplicitConceptSpecializationDecl *ImplicitCSEDecl =
ImplicitConceptSpecializationDecl::Create(
Context, Builder.Record->getDeclContext(), Loc, {CSETA});
// Constraint satisfaction is used to construct the
// ConceptSpecailizationExpr, and represents the 2nd Template Argument,
// located at the bottom of the sample AST above.
const ConstraintSatisfaction CS(CD, {ConceptTA});
TemplateArgumentLoc TAL = S.getTrivialTemplateArgumentLoc(
ConceptTA, QualType(), SourceLocation());
TALI.addArgument(TAL);
const ASTTemplateArgumentListInfo *ATALI =
ASTTemplateArgumentListInfo::Create(Context, TALI);
// In the concept reference, ATALI is what adds the extra
// TemplateArgument node underneath CSE
ConceptReference *CR =
ConceptReference::Create(Context, NNSLoc, Loc, DNI, CD, CD, ATALI);
ConceptSpecializationExpr *CSE =
ConceptSpecializationExpr::Create(Context, CR, ImplicitCSEDecl, &CS);
return CSE;
}
BuiltinTypeDeclBuilder &finalizeTemplateArgs(ConceptDecl *CD = nullptr) {
if (Params.empty())
return Builder;
ASTContext &AST = Builder.SemaRef.Context;
ConceptSpecializationExpr *CSE =
CD ? constructConceptSpecializationExpr(Builder.SemaRef, CD) : nullptr;
auto *ParamList = TemplateParameterList::Create(
AST, SourceLocation(), SourceLocation(), Params, SourceLocation(), CSE);
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());
// NOTE: setPreviousDecl before addDecl so new decl replace old decl when
// make visible.
Builder.Template->setPreviousDecl(Builder.PrevTemplate);
Builder.Record->getDeclContext()->addDecl(Builder.Template);
Params.clear();
QualType T = Builder.Template->getInjectedClassNameSpecialization();
T = AST.getInjectedClassNameType(Builder.Record, T);
return Builder;
}
};
// Builder for methods of builtin types. Allows adding methods to builtin types
// using the builder pattern like this:
//
// BuiltinTypeMethodBuilder(RecordBuilder, "MethodName", ReturnType)
// .addParam("param_name", Type, InOutModifier)
// .callBuiltin("builtin_name", BuiltinParams...)
// .finalizeMethod();
//
// The builder needs to have all of the method parameters before it can create
// a CXXMethodDecl. It collects them in addParam calls and when a first
// method that builds the body is called or when access to 'this` is needed it
// creates the CXXMethodDecl and ParmVarDecls instances. These can then be
// referenced from the body building methods. Destructor or an explicit call to
// finalizeMethod() will complete the method definition.
//
// The callBuiltin helper method accepts constants via `Expr *` or placeholder
// value arguments to indicate which function arguments to forward to the
// builtin.
//
// If the method that is being built has a non-void return type the
// finalizeMethod will create a return statent with the value of the last
// statement (unless the last statement is already a ReturnStmt).
struct BuiltinTypeMethodBuilder {
struct MethodParam {
const IdentifierInfo &NameII;
QualType Ty;
HLSLParamModifierAttr::Spelling Modifier;
MethodParam(const IdentifierInfo &NameII, QualType Ty,
HLSLParamModifierAttr::Spelling Modifier)
: NameII(NameII), Ty(Ty), Modifier(Modifier) {}
};
BuiltinTypeDeclBuilder &DeclBuilder;
DeclarationNameInfo NameInfo;
QualType ReturnTy;
CXXMethodDecl *Method;
bool IsConst;
llvm::SmallVector<MethodParam> Params;
llvm::SmallVector<Stmt *> StmtsList;
// Argument placeholders, inspired by std::placeholder. These are the indices
// of arguments to forward to `callBuiltin` and other method builder methods.
// Additional special values are:
// Handle - refers to the resource handle.
// LastStmt - refers to the last statement in the method body; referencing
// LastStmt will remove the statement from the method body since
// it will be linked from the new expression being constructed.
enum class PlaceHolder { _0, _1, _2, _3, Handle = 128, LastStmt };
Expr *convertPlaceholder(PlaceHolder PH) {
if (PH == PlaceHolder::Handle)
return getResourceHandleExpr();
if (PH == PlaceHolder::LastStmt) {
assert(!StmtsList.empty() && "no statements in the list");
Stmt *LastStmt = StmtsList.pop_back_val();
assert(isa<ValueStmt>(LastStmt) &&
"last statement does not have a value");
return cast<ValueStmt>(LastStmt)->getExprStmt();
}
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
ParmVarDecl *ParamDecl = Method->getParamDecl(static_cast<unsigned>(PH));
return DeclRefExpr::Create(
AST, NestedNameSpecifierLoc(), SourceLocation(), ParamDecl, false,
DeclarationNameInfo(ParamDecl->getDeclName(), SourceLocation()),
ParamDecl->getType(), VK_PRValue);
}
Expr *convertPlaceholder(Expr *E) { return E; }
public:
BuiltinTypeMethodBuilder(BuiltinTypeDeclBuilder &DB, DeclarationName &Name,
QualType ReturnTy, bool IsConst = false)
: DeclBuilder(DB), NameInfo(DeclarationNameInfo(Name, SourceLocation())),
ReturnTy(ReturnTy), Method(nullptr), IsConst(IsConst) {}
BuiltinTypeMethodBuilder(BuiltinTypeDeclBuilder &DB, StringRef Name,
QualType ReturnTy, bool IsConst = false)
: DeclBuilder(DB), ReturnTy(ReturnTy), Method(nullptr), IsConst(IsConst) {
const IdentifierInfo &II =
DB.SemaRef.getASTContext().Idents.get(Name, tok::TokenKind::identifier);
NameInfo = DeclarationNameInfo(DeclarationName(&II), SourceLocation());
}
BuiltinTypeMethodBuilder &addParam(StringRef Name, QualType Ty,
HLSLParamModifierAttr::Spelling Modifier =
HLSLParamModifierAttr::Keyword_in) {
assert(Method == nullptr && "Cannot add param, method already created");
const IdentifierInfo &II = DeclBuilder.SemaRef.getASTContext().Idents.get(
Name, tok::TokenKind::identifier);
Params.emplace_back(II, Ty, Modifier);
return *this;
}
private:
void createMethodDecl() {
assert(Method == nullptr && "Method already created");
// create method type
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
SmallVector<QualType> ParamTypes;
for (MethodParam &MP : Params)
ParamTypes.emplace_back(MP.Ty);
FunctionProtoType::ExtProtoInfo ExtInfo;
if (IsConst)
ExtInfo.TypeQuals.addConst();
QualType MethodTy = AST.getFunctionType(ReturnTy, ParamTypes, ExtInfo);
// create method decl
auto *TSInfo = AST.getTrivialTypeSourceInfo(MethodTy, SourceLocation());
Method =
CXXMethodDecl::Create(AST, DeclBuilder.Record, SourceLocation(),
NameInfo, MethodTy, TSInfo, SC_None, false, false,
ConstexprSpecKind::Unspecified, SourceLocation());
// create params & set them to the function prototype
SmallVector<ParmVarDecl *> ParmDecls;
auto FnProtoLoc =
Method->getTypeSourceInfo()->getTypeLoc().getAs<FunctionProtoTypeLoc>();
for (int I = 0, E = Params.size(); I != E; I++) {
MethodParam &MP = Params[I];
ParmVarDecl *Parm = ParmVarDecl::Create(
AST, Method->getDeclContext(), SourceLocation(), SourceLocation(),
&MP.NameII, MP.Ty,
AST.getTrivialTypeSourceInfo(MP.Ty, SourceLocation()), SC_None,
nullptr);
if (MP.Modifier != HLSLParamModifierAttr::Keyword_in) {
auto *Mod =
HLSLParamModifierAttr::Create(AST, SourceRange(), MP.Modifier);
Parm->addAttr(Mod);
}
ParmDecls.push_back(Parm);
FnProtoLoc.setParam(I, Parm);
}
Method->setParams({ParmDecls});
}
public:
~BuiltinTypeMethodBuilder() { finalizeMethod(); }
Expr *getResourceHandleExpr() {
// The first statement added to a method or access to 'this' creates the
// declaration.
if (!Method)
createMethodDecl();
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
CXXThisExpr *This = CXXThisExpr::Create(
AST, SourceLocation(), Method->getFunctionObjectParameterType(), true);
FieldDecl *HandleField = DeclBuilder.getResourceHandleField();
return MemberExpr::CreateImplicit(AST, This, false, HandleField,
HandleField->getType(), VK_LValue,
OK_Ordinary);
}
template <typename... Ts>
BuiltinTypeMethodBuilder &callBuiltin(StringRef BuiltinName,
QualType ReturnType, Ts... ArgSpecs) {
std::array<Expr *, sizeof...(ArgSpecs)> Args{
convertPlaceholder(std::forward<Ts>(ArgSpecs))...};
// The first statement added to a method or access to 'this` creates the
// declaration.
if (!Method)
createMethodDecl();
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
FunctionDecl *FD = lookupBuiltinFunction(DeclBuilder.SemaRef, BuiltinName);
DeclRefExpr *DRE = DeclRefExpr::Create(
AST, NestedNameSpecifierLoc(), SourceLocation(), FD, false,
FD->getNameInfo(), AST.BuiltinFnTy, VK_PRValue);
if (ReturnType.isNull())
ReturnType = FD->getReturnType();
Expr *Call = CallExpr::Create(AST, DRE, Args, ReturnType, VK_PRValue,
SourceLocation(), FPOptionsOverride());
StmtsList.push_back(Call);
return *this;
}
template <typename TLHS, typename TRHS>
BuiltinTypeMethodBuilder &assign(TLHS LHS, TRHS RHS) {
Expr *LHSExpr = convertPlaceholder(LHS);
Expr *RHSExpr = convertPlaceholder(RHS);
Stmt *AssignStmt = BinaryOperator::Create(
DeclBuilder.SemaRef.getASTContext(), LHSExpr, RHSExpr, BO_Assign,
LHSExpr->getType(), ExprValueKind::VK_PRValue,
ExprObjectKind::OK_Ordinary, SourceLocation(), FPOptionsOverride());
StmtsList.push_back(AssignStmt);
return *this;
}
template <typename T> BuiltinTypeMethodBuilder &dereference(T Ptr) {
Expr *PtrExpr = convertPlaceholder(Ptr);
Expr *Deref =
UnaryOperator::Create(DeclBuilder.SemaRef.getASTContext(), PtrExpr,
UO_Deref, PtrExpr->getType()->getPointeeType(),
VK_PRValue, OK_Ordinary, SourceLocation(),
/*CanOverflow=*/false, FPOptionsOverride());
StmtsList.push_back(Deref);
return *this;
}
BuiltinTypeDeclBuilder &finalizeMethod() {
assert(!DeclBuilder.Record->isCompleteDefinition() &&
"record is already complete");
assert(
Method != nullptr &&
"method decl not created; are you missing a call to build the body?");
if (!Method->hasBody()) {
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
assert((ReturnTy == AST.VoidTy || !StmtsList.empty()) &&
"nothing to return from non-void method");
if (ReturnTy != AST.VoidTy) {
if (Expr *LastExpr = dyn_cast<Expr>(StmtsList.back())) {
assert(AST.hasSameUnqualifiedType(LastExpr->getType(),
ReturnTy.getNonReferenceType()) &&
"Return type of the last statement must match the return type "
"of the method");
if (!isa<ReturnStmt>(LastExpr)) {
StmtsList.pop_back();
StmtsList.push_back(
ReturnStmt::Create(AST, SourceLocation(), LastExpr, nullptr));
}
}
}
Method->setBody(CompoundStmt::Create(AST, StmtsList, FPOptionsOverride(),
SourceLocation(), SourceLocation()));
Method->setLexicalDeclContext(DeclBuilder.Record);
Method->setAccess(AccessSpecifier::AS_public);
Method->addAttr(AlwaysInlineAttr::CreateImplicit(
AST, SourceRange(), AlwaysInlineAttr::CXX11_clang_always_inline));
DeclBuilder.Record->addDecl(Method);
}
return DeclBuilder;
}
};
} // namespace
TemplateParameterListBuilder BuiltinTypeDeclBuilder::addTemplateArgumentList() {
return TemplateParameterListBuilder(*this);
}
BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
ConceptDecl *CD = nullptr) {
if (Record->isCompleteDefinition()) {
assert(Template && "existing record it not a template");
assert(Template->getTemplateParameters()->size() == Names.size() &&
"template param count mismatch");
return *this;
}
TemplateParameterListBuilder Builder = this->addTemplateArgumentList();
for (StringRef Name : Names)
Builder.addTypeParameter(Name);
return Builder.finalizeTemplateArgs(CD);
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addIncrementCounterMethod() {
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
return BuiltinTypeMethodBuilder(*this, "IncrementCounter",
SemaRef.getASTContext().UnsignedIntTy)
.callBuiltin("__builtin_hlsl_buffer_update_counter", QualType(),
PH::Handle, getConstantIntExpr(1))
.finalizeMethod();
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addDecrementCounterMethod() {
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
return BuiltinTypeMethodBuilder(*this, "DecrementCounter",
SemaRef.getASTContext().UnsignedIntTy)
.callBuiltin("__builtin_hlsl_buffer_update_counter", QualType(),
PH::Handle, getConstantIntExpr(-1))
.finalizeMethod();
}
BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addHandleAccessFunction(DeclarationName &Name,
bool IsConst, bool IsRef) {
assert(!Record->isCompleteDefinition() && "record is already complete");
ASTContext &AST = SemaRef.getASTContext();
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
QualType ElemTy = getHandleElementType();
// TODO: Map to an hlsl_device address space.
QualType ElemPtrTy = AST.getPointerType(ElemTy);
QualType ReturnTy = ElemTy;
if (IsConst)
ReturnTy.addConst();
if (IsRef)
ReturnTy = AST.getLValueReferenceType(ReturnTy);
return BuiltinTypeMethodBuilder(*this, Name, ReturnTy, IsConst)
.addParam("Index", AST.UnsignedIntTy)
.callBuiltin("__builtin_hlsl_resource_getpointer", ElemPtrTy, PH::Handle,
PH::_0)
.dereference(PH::LastStmt)
.finalizeMethod();
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addAppendMethod() {
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
ASTContext &AST = SemaRef.getASTContext();
QualType ElemTy = getHandleElementType();
return BuiltinTypeMethodBuilder(*this, "Append", AST.VoidTy)
.addParam("value", ElemTy)
.callBuiltin("__builtin_hlsl_buffer_update_counter", AST.UnsignedIntTy,
PH::Handle, getConstantIntExpr(1))
.callBuiltin("__builtin_hlsl_resource_getpointer",
AST.getPointerType(ElemTy), PH::Handle, PH::LastStmt)
.dereference(PH::LastStmt)
.assign(PH::LastStmt, PH::_0)
.finalizeMethod();
}
BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addConsumeMethod() {
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
ASTContext &AST = SemaRef.getASTContext();
QualType ElemTy = getHandleElementType();
return BuiltinTypeMethodBuilder(*this, "Consume", ElemTy)
.callBuiltin("__builtin_hlsl_buffer_update_counter", AST.UnsignedIntTy,
PH::Handle, getConstantIntExpr(-1))
.callBuiltin("__builtin_hlsl_resource_getpointer",
AST.getPointerType(ElemTy), PH::Handle, PH::LastStmt)
.dereference(PH::LastStmt)
.finalizeMethod();
}
HLSLExternalSemaSource::~HLSLExternalSemaSource() {}
void HLSLExternalSemaSource::InitializeSema(Sema &S) {
SemaPtr = &S;
ASTContext &AST = SemaPtr->getASTContext();
// If the translation unit has external storage force external decls to load.
if (AST.getTranslationUnitDecl()->hasExternalLexicalStorage())
(void)AST.getTranslationUnitDecl()->decls_begin();
IdentifierInfo &HLSL = AST.Idents.get("hlsl", tok::TokenKind::identifier);
LookupResult Result(S, &HLSL, SourceLocation(), Sema::LookupNamespaceName);
NamespaceDecl *PrevDecl = nullptr;
if (S.LookupQualifiedName(Result, AST.getTranslationUnitDecl()))
PrevDecl = Result.getAsSingle<NamespaceDecl>();
HLSLNamespace = NamespaceDecl::Create(
AST, AST.getTranslationUnitDecl(), /*Inline=*/false, SourceLocation(),
SourceLocation(), &HLSL, PrevDecl, /*Nested=*/false);
HLSLNamespace->setImplicit(true);
HLSLNamespace->setHasExternalLexicalStorage();
AST.getTranslationUnitDecl()->addDecl(HLSLNamespace);
// Force external decls in the HLSL namespace to load from the PCH.
(void)HLSLNamespace->getCanonicalDecl()->decls_begin();
defineTrivialHLSLTypes();
defineHLSLTypesWithForwardDeclarations();
// 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
// the near future. In order to be source compatible older versions of HLSL
// will need to implicitly use the hlsl namespace. For now in clang everything
// will get added to the namespace, and we can remove the using directive for
// future language versions to match HLSL's evolution.
auto *UsingDecl = UsingDirectiveDecl::Create(
AST, AST.getTranslationUnitDecl(), SourceLocation(), SourceLocation(),
NestedNameSpecifierLoc(), SourceLocation(), HLSLNamespace,
AST.getTranslationUnitDecl());
AST.getTranslationUnitDecl()->addDecl(UsingDecl);
}
void HLSLExternalSemaSource::defineHLSLVectorAlias() {
ASTContext &AST = SemaPtr->getASTContext();
llvm::SmallVector<NamedDecl *> TemplateParams;
auto *TypeParam = TemplateTypeParmDecl::Create(
AST, HLSLNamespace, SourceLocation(), SourceLocation(), 0, 0,
&AST.Idents.get("element", tok::TokenKind::identifier), false, false);
TypeParam->setDefaultArgument(
AST, SemaPtr->getTrivialTemplateArgumentLoc(
TemplateArgument(AST.FloatTy), QualType(), SourceLocation()));
TemplateParams.emplace_back(TypeParam);
auto *SizeParam = NonTypeTemplateParmDecl::Create(
AST, HLSLNamespace, SourceLocation(), SourceLocation(), 0, 1,
&AST.Idents.get("element_count", tok::TokenKind::identifier), AST.IntTy,
false, AST.getTrivialTypeSourceInfo(AST.IntTy));
llvm::APInt Val(AST.getIntWidth(AST.IntTy), 4);
TemplateArgument Default(AST, llvm::APSInt(std::move(Val)), AST.IntTy,
/*IsDefaulted=*/true);
SizeParam->setDefaultArgument(
AST, SemaPtr->getTrivialTemplateArgumentLoc(Default, AST.IntTy,
SourceLocation(), SizeParam));
TemplateParams.emplace_back(SizeParam);
auto *ParamList =
TemplateParameterList::Create(AST, SourceLocation(), SourceLocation(),
TemplateParams, SourceLocation(), nullptr);
IdentifierInfo &II = AST.Idents.get("vector", tok::TokenKind::identifier);
QualType AliasType = AST.getDependentSizedExtVectorType(
AST.getTemplateTypeParmType(0, 0, false, TypeParam),
DeclRefExpr::Create(
AST, NestedNameSpecifierLoc(), SourceLocation(), SizeParam, false,
DeclarationNameInfo(SizeParam->getDeclName(), SourceLocation()),
AST.IntTy, VK_LValue),
SourceLocation());
auto *Record = TypeAliasDecl::Create(AST, HLSLNamespace, SourceLocation(),
SourceLocation(), &II,
AST.getTrivialTypeSourceInfo(AliasType));
Record->setImplicit(true);
auto *Template =
TypeAliasTemplateDecl::Create(AST, HLSLNamespace, SourceLocation(),
Record->getIdentifier(), ParamList, Record);
Record->setDescribedAliasTemplate(Template);
Template->setImplicit(true);
Template->setLexicalDeclContext(Record->getDeclContext());
HLSLNamespace->addDecl(Template);
}
void HLSLExternalSemaSource::defineTrivialHLSLTypes() {
defineHLSLVectorAlias();
}
/// Set up common members and attributes for buffer types
static BuiltinTypeDeclBuilder setupBufferType(CXXRecordDecl *Decl, Sema &S,
ResourceClass RC, ResourceKind RK,
bool IsROV, bool RawBuffer) {
return BuiltinTypeDeclBuilder(S, Decl)
.addHandleMember(RC, RK, IsROV, RawBuffer)
.addDefaultHandleConstructor();
}
static Expr *constructTypedBufferConstraintExpr(Sema &S, SourceLocation NameLoc,
TemplateTypeParmDecl *T) {
ASTContext &Context = S.getASTContext();
// Obtain the QualType for 'bool'
QualType BoolTy = Context.BoolTy;
// Create a QualType that points to this TemplateTypeParmDecl
QualType TType = Context.getTypeDeclType(T);
// Create a TypeSourceInfo for the template type parameter 'T'
TypeSourceInfo *TTypeSourceInfo =
Context.getTrivialTypeSourceInfo(TType, NameLoc);
TypeTraitExpr *TypedResExpr = TypeTraitExpr::Create(
Context, BoolTy, NameLoc, UTT_IsTypedResourceElementCompatible,
{TTypeSourceInfo}, NameLoc, true);
return TypedResExpr;
}
static ConceptDecl *constructTypedBufferConceptDecl(Sema &S,
NamespaceDecl *NSD) {
ASTContext &Context = S.getASTContext();
DeclContext *DC = NSD->getDeclContext();
SourceLocation DeclLoc = SourceLocation();
IdentifierInfo &ElementTypeII = Context.Idents.get("element_type");
TemplateTypeParmDecl *T = TemplateTypeParmDecl::Create(
Context, NSD->getDeclContext(), DeclLoc, DeclLoc,
/*D=*/0,
/*P=*/0,
/*Id=*/&ElementTypeII,
/*Typename=*/true,
/*ParameterPack=*/false);
T->setDeclContext(DC);
T->setReferenced();
// Create and Attach Template Parameter List to ConceptDecl
TemplateParameterList *ConceptParams = TemplateParameterList::Create(
Context, DeclLoc, DeclLoc, {T}, DeclLoc, nullptr);
DeclarationName DeclName = DeclarationName(
&Context.Idents.get("__is_typed_resource_element_compatible"));
Expr *ConstraintExpr = constructTypedBufferConstraintExpr(S, DeclLoc, T);
// Create a ConceptDecl
ConceptDecl *CD =
ConceptDecl::Create(Context, NSD->getDeclContext(), DeclLoc, DeclName,
ConceptParams, ConstraintExpr);
// Attach the template parameter list to the ConceptDecl
CD->setTemplateParameters(ConceptParams);
// Add the concept declaration to the Translation Unit Decl
NSD->getDeclContext()->addDecl(CD);
return CD;
}
void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
CXXRecordDecl *Decl;
ConceptDecl *TypedBufferConcept =
constructTypedBufferConceptDecl(*SemaPtr, HLSLNamespace);
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWBuffer")
.addSimpleTemplateParams({"element_type"}, TypedBufferConcept)
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV,
ResourceKind::TypedBuffer, /*IsROV=*/false,
/*RawBuffer=*/false)
.addArraySubscriptOperators()
.completeDefinition();
});
Decl =
BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RasterizerOrderedBuffer")
.addSimpleTemplateParams({"element_type"})
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV,
ResourceKind::TypedBuffer, /*IsROV=*/true,
/*RawBuffer=*/false)
.addArraySubscriptOperators()
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "StructuredBuffer")
.addSimpleTemplateParams({"element_type"})
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::SRV, ResourceKind::RawBuffer,
/*IsROV=*/false, /*RawBuffer=*/true)
.addArraySubscriptOperators()
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWStructuredBuffer")
.addSimpleTemplateParams({"element_type"})
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer,
/*IsROV=*/false, /*RawBuffer=*/true)
.addArraySubscriptOperators()
.addIncrementCounterMethod()
.addDecrementCounterMethod()
.completeDefinition();
});
Decl =
BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "AppendStructuredBuffer")
.addSimpleTemplateParams({"element_type"})
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer,
/*IsROV=*/false, /*RawBuffer=*/true)
.addAppendMethod()
.completeDefinition();
});
Decl =
BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "ConsumeStructuredBuffer")
.addSimpleTemplateParams({"element_type"})
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer,
/*IsROV=*/false, /*RawBuffer=*/true)
.addConsumeMethod()
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace,
"RasterizerOrderedStructuredBuffer")
.addSimpleTemplateParams({"element_type"})
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer,
/*IsROV=*/true, /*RawBuffer=*/true)
.addArraySubscriptOperators()
.addIncrementCounterMethod()
.addDecrementCounterMethod()
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "ByteAddressBuffer")
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::SRV, ResourceKind::RawBuffer,
/*IsROV=*/false,
/*RawBuffer=*/true)
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWByteAddressBuffer")
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer,
/*IsROV=*/false,
/*RawBuffer=*/true)
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace,
"RasterizerOrderedByteAddressBuffer")
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer,
/*IsROV=*/true,
/*RawBuffer=*/true)
.completeDefinition();
});
}
void HLSLExternalSemaSource::onCompletion(CXXRecordDecl *Record,
CompletionFunction Fn) {
if (!Record->isCompleteDefinition())
Completions.insert(std::make_pair(Record->getCanonicalDecl(), Fn));
}
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();
Record = Record->getCanonicalDecl();
auto It = Completions.find(Record);
if (It == Completions.end())
return;
It->second(Record);
}
static FunctionDecl *lookupBuiltinFunction(Sema &S, StringRef Name) {
IdentifierInfo &II =
S.getASTContext().Idents.get(Name, tok::TokenKind::identifier);
DeclarationNameInfo NameInfo =
DeclarationNameInfo(DeclarationName(&II), SourceLocation());
LookupResult R(S, NameInfo, Sema::LookupOrdinaryName);
// AllowBuiltinCreation is false but LookupDirect will create
// the builtin when searching the global scope anyways...
S.LookupName(R, S.getCurScope());
// FIXME: If the builtin function was user-declared in global scope,
// this assert *will* fail. Should this call LookupBuiltin instead?
assert(R.isSingleResult() &&
"Since this is a builtin it should always resolve!");
return cast<FunctionDecl>(R.getFoundDecl());
}