//===--- 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 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 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(Found)) { PrevDecl = TD->getTemplatedDecl(); PrevTemplate = TD; } else PrevDecl = dyn_cast(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 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 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; } BuiltinTypeDeclBuilder &addLoadMethods() { if (Record->isCompleteDefinition()) return *this; ASTContext &AST = Record->getASTContext(); IdentifierInfo &II = AST.Idents.get("Load", tok::TokenKind::identifier); DeclarationName Load(&II); // TODO: We also need versions with status for CheckAccessFullyMapped. addHandleAccessFunction(Load, /*IsConst=*/false, /*IsRef=*/false); 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( 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 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 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(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 // concept is_typed_resource_element_compatible = // __builtin_hlsl_typed_resource_element_compatible // // template requires // is_typed_resource_element_compatible // struct RWBuffer { // element_type Val; // }; // // int fn() { // RWBuffer 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( 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 Params; llvm::SmallVector 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(LastStmt) && "last statement does not have a value"); return cast(LastStmt)->getExprStmt(); } ASTContext &AST = DeclBuilder.SemaRef.getASTContext(); ParmVarDecl *ParamDecl = Method->getParamDecl(static_cast(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 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 ParmDecls; auto FnProtoLoc = Method->getTypeSourceInfo()->getTypeLoc().getAs(); 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(); } BuiltinTypeMethodBuilder(const BuiltinTypeMethodBuilder &Other) = delete; BuiltinTypeMethodBuilder & operator=(const BuiltinTypeMethodBuilder &Other) = delete; 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 BuiltinTypeMethodBuilder &callBuiltin(StringRef BuiltinName, QualType ReturnType, Ts... ArgSpecs) { std::array Args{ convertPlaceholder(std::forward(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 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 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(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(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 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(); 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 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(); } // This function is responsible for constructing the constraint expression for // this concept: // template concept is_typed_resource_element_compatible = // __is_typed_resource_element_compatible; 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; } // This function is responsible for constructing the constraint expression for // this concept: // template concept is_structured_resource_element_compatible = // !__is_intangible && sizeof(T) >= 1; static Expr *constructStructuredBufferConstraintExpr(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 *IsIntangibleExpr = TypeTraitExpr::Create(Context, BoolTy, NameLoc, UTT_IsIntangibleType, {TTypeSourceInfo}, NameLoc, true); // negate IsIntangibleExpr UnaryOperator *NotIntangibleExpr = UnaryOperator::Create( Context, IsIntangibleExpr, UO_LNot, BoolTy, VK_LValue, OK_Ordinary, NameLoc, false, FPOptionsOverride()); // element types also may not be of 0 size UnaryExprOrTypeTraitExpr *SizeOfExpr = new (Context) UnaryExprOrTypeTraitExpr( UETT_SizeOf, TTypeSourceInfo, BoolTy, NameLoc, NameLoc); // Create a BinaryOperator that checks if the size of the type is not equal to // 1 Empty structs have a size of 1 in HLSL, so we need to check for that IntegerLiteral *rhs = IntegerLiteral::Create( Context, llvm::APInt(Context.getTypeSize(Context.getSizeType()), 1, true), Context.getSizeType(), NameLoc); BinaryOperator *SizeGEQOneExpr = BinaryOperator::Create(Context, SizeOfExpr, rhs, BO_GE, BoolTy, VK_LValue, OK_Ordinary, NameLoc, FPOptionsOverride()); // Combine the two constraints BinaryOperator *CombinedExpr = BinaryOperator::Create( Context, NotIntangibleExpr, SizeGEQOneExpr, BO_LAnd, BoolTy, VK_LValue, OK_Ordinary, NameLoc, FPOptionsOverride()); return CombinedExpr; } static ConceptDecl *constructBufferConceptDecl(Sema &S, NamespaceDecl *NSD, bool isTypedBuffer) { 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; Expr *ConstraintExpr = nullptr; if (isTypedBuffer) { DeclName = DeclarationName( &Context.Idents.get("__is_typed_resource_element_compatible")); ConstraintExpr = constructTypedBufferConstraintExpr(S, DeclLoc, T); } else { DeclName = DeclarationName( &Context.Idents.get("__is_structured_resource_element_compatible")); ConstraintExpr = constructStructuredBufferConstraintExpr(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 = constructBufferConceptDecl( *SemaPtr, HLSLNamespace, /*isTypedBuffer*/ true); ConceptDecl *StructuredBufferConcept = constructBufferConceptDecl( *SemaPtr, HLSLNamespace, /*isTypedBuffer*/ false); 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() .addLoadMethods() .completeDefinition(); }); Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RasterizerOrderedBuffer") .addSimpleTemplateParams({"element_type"}, StructuredBufferConcept) .finalizeForwardDeclaration(); onCompletion(Decl, [this](CXXRecordDecl *Decl) { setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::TypedBuffer, /*IsROV=*/true, /*RawBuffer=*/false) .addArraySubscriptOperators() .addLoadMethods() .completeDefinition(); }); Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "StructuredBuffer") .addSimpleTemplateParams({"element_type"}, StructuredBufferConcept) .finalizeForwardDeclaration(); onCompletion(Decl, [this](CXXRecordDecl *Decl) { setupBufferType(Decl, *SemaPtr, ResourceClass::SRV, ResourceKind::RawBuffer, /*IsROV=*/false, /*RawBuffer=*/true) .addArraySubscriptOperators() .addLoadMethods() .completeDefinition(); }); Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWStructuredBuffer") .addSimpleTemplateParams({"element_type"}, StructuredBufferConcept) .finalizeForwardDeclaration(); onCompletion(Decl, [this](CXXRecordDecl *Decl) { setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer, /*IsROV=*/false, /*RawBuffer=*/true) .addArraySubscriptOperators() .addLoadMethods() .addIncrementCounterMethod() .addDecrementCounterMethod() .completeDefinition(); }); Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "AppendStructuredBuffer") .addSimpleTemplateParams({"element_type"}, StructuredBufferConcept) .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"}, StructuredBufferConcept) .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"}, StructuredBufferConcept) .finalizeForwardDeclaration(); onCompletion(Decl, [this](CXXRecordDecl *Decl) { setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, ResourceKind::RawBuffer, /*IsROV=*/true, /*RawBuffer=*/true) .addArraySubscriptOperators() .addLoadMethods() .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(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(); 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(R.getFoundDecl()); }