llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-codegen Author: Helena Kotas (hekota) <details> <summary>Changes</summary> Adds support for accessing individual resources from fixed-size global resource arrays. Design proposal: https://github.com/llvm/wg-hlsl/blob/main/proposals/0028-resource-arrays.md Enables indexing into globally scoped, fixed-size resource arrays to retrieve individual resources. The initialization logic is primarily handled during codegen. When a global resource array is indexed, the codegen translates the `ArraySubscriptExpr` AST node into a constructor call for the corresponding resource record type and binding. To support this behavior, Sema needs to ensure that: - The constructor for the specific resource type is instantiated. - An implicit binding attribute is added to resource arrays that lack explicit bindings (#<!-- -->152452). Closes #<!-- -->145424 Depends on #<!-- -->152450 and #<!-- -->152452. --- Patch is 28.60 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/152454.diff 9 Files Affected: - (modified) clang/include/clang/Sema/SemaHLSL.h (+8-1) - (modified) clang/lib/CodeGen/CGExpr.cpp (+10) - (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+211-12) - (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+6) - (modified) clang/lib/CodeGen/CodeGenModule.cpp (+2-2) - (modified) clang/lib/Sema/SemaHLSL.cpp (+70-23) - (added) clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl (+32) - (added) clang/test/CodeGenHLSL/resources/res-array-global.hlsl (+59) - (modified) clang/test/CodeGenHLSL/static-local-ctor.hlsl (+3-2) ``````````diff diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 085c9ed9f3ebd..0c215c6e10013 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -229,10 +229,17 @@ class SemaHLSL : public SemaBase { void diagnoseAvailabilityViolations(TranslationUnitDecl *TU); - bool initGlobalResourceDecl(VarDecl *VD); uint32_t getNextImplicitBindingOrderID() { return ImplicitBindingNextOrderID++; } + + bool initGlobalResourceDecl(VarDecl *VD); + bool initGlobalResourceArrayDecl(VarDecl *VD); + void createResourceRecordCtorArgs(const Type *ResourceTy, StringRef VarName, + HLSLResourceBindingAttr *RBA, + HLSLVkBindingAttr *VkBinding, + uint32_t ArrayIndex, + llvm::SmallVector<Expr *> &Args); }; } // namespace clang diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index ed35a055d8a7f..8c34fb501a3b8 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -16,6 +16,7 @@ #include "CGCall.h" #include "CGCleanup.h" #include "CGDebugInfo.h" +#include "CGHLSLRuntime.h" #include "CGObjCRuntime.h" #include "CGOpenMPRuntime.h" #include "CGRecordLayout.h" @@ -4532,6 +4533,15 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, LHS.getBaseInfo(), TBAAAccessInfo()); } + // The HLSL runtime handle the subscript expression on global resource arrays. + if (getLangOpts().HLSL && (E->getType()->isHLSLResourceRecord() || + E->getType()->isHLSLResourceRecordArray())) { + std::optional<LValue> LV = + CGM.getHLSLRuntime().emitResourceArraySubscriptExpr(E, *this); + if (LV.has_value()) + return *LV; + } + // All the other cases basically behave like simple offsetting. // Handle the extvector case we ignored above. diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 918cb3e38448d..a09e540367a18 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -84,6 +84,124 @@ void addRootSignature(llvm::dxbc::RootSignatureVersion RootSigVer, RootSignatureValMD->addOperand(MDVals); } +// If the specified expr is a simple decay from an array to pointer, +// return the array subexpression. Otherwise, return nullptr. +static const Expr *getSubExprFromArrayDecayOperand(const Expr *E) { + const auto *CE = dyn_cast<CastExpr>(E); + if (!CE || CE->getCastKind() != CK_ArrayToPointerDecay) + return nullptr; + return CE->getSubExpr(); +} + +// Find array variable declaration from nested array subscript AST nodes +static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) { + const Expr *E = nullptr; + while (ASE != nullptr) { + E = getSubExprFromArrayDecayOperand(ASE->getBase()); + if (!E) + return nullptr; + ASE = dyn_cast<ArraySubscriptExpr>(E); + } + if (const DeclRefExpr *DRE = dyn_cast_or_null<DeclRefExpr>(E)) + return DRE->getDecl(); + return nullptr; +} + +// Get the total size of the array, or -1 if the array is unbounded. +static int getTotalArraySize(const clang::Type *Ty) { + assert(Ty->isArrayType() && "expected array type"); + if (Ty->isIncompleteArrayType()) + return -1; + int Size = 1; + while (const auto *CAT = dyn_cast<ConstantArrayType>(Ty)) { + Size *= CAT->getSExtSize(); + Ty = CAT->getArrayElementTypeNoTypeQual(); + } + return Size; +} + +// Find constructor decl for a specific resource record type and binding +// (implicit vs. explicit). The constructor has 6 parameters. +// For explicit binding the signature is: +// void(unsigned, unsigned, int, unsigned, const char *). +// For implicit binding the signature is: +// void(unsigned, int, unsigned, unsigned, const char *). +static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST, + QualType ResTy, + bool ExplicitBinding) { + SmallVector<QualType> ExpParmTypes = { + AST.UnsignedIntTy, AST.UnsignedIntTy, AST.UnsignedIntTy, + AST.UnsignedIntTy, AST.getPointerType(AST.CharTy.withConst())}; + ExpParmTypes[ExplicitBinding ? 2 : 1] = AST.IntTy; + + CXXRecordDecl *ResDecl = ResTy->getAsCXXRecordDecl(); + for (auto *Ctor : ResDecl->ctors()) { + if (Ctor->getNumParams() != ExpParmTypes.size()) + continue; + ParmVarDecl **ParmIt = Ctor->param_begin(); + QualType *ExpTyIt = ExpParmTypes.begin(); + for (; ParmIt != Ctor->param_end() && ExpTyIt != ExpParmTypes.end(); + ++ParmIt, ++ExpTyIt) { + if ((*ParmIt)->getType() != *ExpTyIt) + break; + } + if (ParmIt == Ctor->param_end()) + return Ctor; + } + llvm_unreachable("did not find constructor for resource class"); +} + +static Value *buildNameForResource(llvm::StringRef BaseName, + CodeGenModule &CGM) { + std::string Str(BaseName); + std::string GlobalName(Str + ".str"); + return CGM.GetAddrOfConstantCString(Str, GlobalName.c_str()).getPointer(); +} + +static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD, + llvm::Value *ThisPtr, llvm::Value *Range, + llvm::Value *Index, StringRef Name, + HLSLResourceBindingAttr *RBA, + HLSLVkBindingAttr *VkBinding, + CallArgList &Args) { + assert((VkBinding || RBA) && "at least one a binding attribute expected"); + + std::optional<uint32_t> RegisterSlot; + uint32_t SpaceNo = 0; + if (VkBinding) { + RegisterSlot = VkBinding->getBinding(); + SpaceNo = VkBinding->getSet(); + } else if (RBA) { + if (RBA->hasRegisterSlot()) + RegisterSlot = RBA->getSlotNumber(); + SpaceNo = RBA->getSpaceNumber(); + } + + ASTContext &AST = CD->getASTContext(); + Value *NameStr = buildNameForResource(Name, CGM); + Value *Space = llvm::ConstantInt::get(CGM.IntTy, SpaceNo); + + Args.add(RValue::get(ThisPtr), CD->getThisType()); + if (RegisterSlot.has_value()) { + // explicit binding + auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, RegisterSlot.value()); + Args.add(RValue::get(RegSlot), AST.UnsignedIntTy); + Args.add(RValue::get(Space), AST.UnsignedIntTy); + Args.add(RValue::get(Range), AST.IntTy); + Args.add(RValue::get(Index), AST.UnsignedIntTy); + + } else { + // implicit binding + auto *OrderID = + llvm::ConstantInt::get(CGM.IntTy, RBA->getImplicitBindingOrderID()); + Args.add(RValue::get(Space), AST.UnsignedIntTy); + Args.add(RValue::get(Range), AST.IntTy); + Args.add(RValue::get(Index), AST.UnsignedIntTy); + Args.add(RValue::get(OrderID), AST.UnsignedIntTy); + } + Args.add(RValue::get(NameStr), AST.getPointerType(AST.CharTy.withConst())); +} + } // namespace llvm::Type * @@ -590,13 +708,6 @@ static void initializeBuffer(CodeGenModule &CGM, llvm::GlobalVariable *GV, CGM.AddCXXGlobalInit(InitResFunc); } -static Value *buildNameForResource(llvm::StringRef BaseName, - CodeGenModule &CGM) { - std::string Str(BaseName); - std::string GlobalName(Str + ".str"); - return CGM.GetAddrOfConstantCString(Str, GlobalName.c_str()).getPointer(); -} - void CGHLSLRuntime::initializeBufferFromBinding(const HLSLBufferDecl *BufDecl, llvm::GlobalVariable *GV, HLSLVkBindingAttr *VkBinding) { @@ -624,17 +735,13 @@ void CGHLSLRuntime::initializeBufferFromBinding(const HLSLBufferDecl *BufDecl, auto *Index = llvm::ConstantInt::get(CGM.IntTy, 0); auto *RangeSize = llvm::ConstantInt::get(CGM.IntTy, 1); auto *Space = llvm::ConstantInt::get(CGM.IntTy, RBA->getSpaceNumber()); - Value *Name = nullptr; + Value *Name = buildNameForResource(BufDecl->getName(), CGM); llvm::Intrinsic::ID IntrinsicID = RBA->hasRegisterSlot() ? CGM.getHLSLRuntime().getCreateHandleFromBindingIntrinsic() : CGM.getHLSLRuntime().getCreateHandleFromImplicitBindingIntrinsic(); - std::string Str(BufDecl->getName()); - std::string GlobalName(Str + ".str"); - Name = CGM.GetAddrOfConstantCString(Str, GlobalName.c_str()).getPointer(); - // buffer with explicit binding if (RBA->hasRegisterSlot()) { auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, RBA->getSlotNumber()); @@ -701,3 +808,95 @@ void CGHLSLRuntime::emitInitListOpaqueValues(CodeGenFunction &CGF, } } } + +std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr( + const ArraySubscriptExpr *ArraySubsExpr, CodeGenFunction &CGF) { + assert(ArraySubsExpr->getType()->isHLSLResourceRecord() || + ArraySubsExpr->getType()->isHLSLResourceRecordArray() && + "expected resource array subscript expression"); + + // let clang codegen handle local resource array subscrips + const VarDecl *ArrayDecl = dyn_cast<VarDecl>(getArrayDecl(ArraySubsExpr)); + if (!ArrayDecl || !ArrayDecl->hasGlobalStorage()) + return std::nullopt; + + // FIXME: this is not yet implemented (llvm/llvm-project#145426) + assert(!ArraySubsExpr->getType()->isArrayType() && + "indexing of array subsets it not supported yet"); + + // get total array size (= range size) + const Type *ResArrayTy = ArrayDecl->getType().getTypePtr(); + assert(ResArrayTy->isHLSLResourceRecordArray() && + "expected array of resource classes"); + llvm::Value *Range = + llvm::ConstantInt::get(CGM.IntTy, getTotalArraySize(ResArrayTy)); + + // Iterate through all nested array subscript expressions to calculate + // the index in the flattened resource array (if this is a multi- + // dimensional array). The index is calculated as a sum of all indices + // multiplied by the total size of the array at that level. + Value *Index = nullptr; + Value *Multiplier = nullptr; + const ArraySubscriptExpr *ASE = ArraySubsExpr; + while (ASE != nullptr) { + Value *SubIndex = CGF.EmitScalarExpr(ASE->getIdx()); + if (const auto *ArrayTy = + dyn_cast<ConstantArrayType>(ASE->getType().getTypePtr())) { + Value *SubMultiplier = + llvm::ConstantInt::get(CGM.IntTy, ArrayTy->getSExtSize()); + Multiplier = Multiplier ? CGF.Builder.CreateMul(Multiplier, SubMultiplier) + : SubMultiplier; + SubIndex = CGF.Builder.CreateMul(SubIndex, Multiplier); + } + + Index = Index ? CGF.Builder.CreateAdd(Index, SubIndex) : SubIndex; + ASE = dyn_cast<ArraySubscriptExpr>( + getSubExprFromArrayDecayOperand(ASE->getBase())); + } + + // find binding info for the resource array + // (for implicit binding an HLSLResourceBindingAttr should have been added by + // SemaHLSL) + QualType ResourceTy = ArraySubsExpr->getType(); + HLSLVkBindingAttr *VkBinding = ArrayDecl->getAttr<HLSLVkBindingAttr>(); + HLSLResourceBindingAttr *RBA = ArrayDecl->getAttr<HLSLResourceBindingAttr>(); + assert((VkBinding || RBA) && "resource array must have a binding attribute"); + + // lookup the resource class constructor based on the resource type and + // binding + CXXConstructorDecl *CD = + findResourceConstructorDecl(ArrayDecl->getASTContext(), ResourceTy, + VkBinding || RBA->hasRegisterSlot()); + + // create a temporary variable for the resource class instance (we need to + // return an LValue) + RawAddress TmpVar = CGF.CreateMemTemp(ResourceTy); + if (auto *Size = CGF.EmitLifetimeStart( + CGM.getDataLayout().getTypeAllocSize(TmpVar.getElementType()), + TmpVar.getPointer())) { + CGF.pushFullExprCleanup<CodeGenFunction::CallLifetimeEnd>( + NormalEHLifetimeMarker, TmpVar, Size); + } + AggValueSlot ValueSlot = AggValueSlot::forAddr( + TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true), + AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false), + AggValueSlot::MayOverlap); + + Address ThisAddress = ValueSlot.getAddress(); + llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo( + ThisAddress, CD->getThisType()->getPointeeType()); + + // assemble the constructor parameters + CallArgList Args; + createResourceCtorArgs(CGM, CD, ThisPtr, Range, Index, ArrayDecl->getName(), + RBA, VkBinding, Args); + + // call the constructor + CGF.EmitCXXConstructorCall(CD, Ctor_Complete, false, false, ThisAddress, Args, + ValueSlot.mayOverlap(), + ArraySubsExpr->getExprLoc(), + ValueSlot.isSanitizerChecked()); + + return CGF.MakeAddrLValue(TmpVar, ArraySubsExpr->getType(), + AlignmentSource::Decl); +} diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index 31d1728da9c56..b872f9ef0e9b6 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -68,6 +68,7 @@ class Type; class RecordType; class DeclContext; class HLSLPackOffsetAttr; +class ArraySubscriptExpr; class FunctionDecl; @@ -75,6 +76,7 @@ namespace CodeGen { class CodeGenModule; class CodeGenFunction; +class LValue; class CGHLSLRuntime { public: @@ -164,6 +166,10 @@ class CGHLSLRuntime { llvm::TargetExtType *LayoutTy); void emitInitListOpaqueValues(CodeGenFunction &CGF, InitListExpr *E); + std::optional<LValue> + emitResourceArraySubscriptExpr(const ArraySubscriptExpr *E, + CodeGenFunction &CGF); + private: void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl, llvm::GlobalVariable *BufGV); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 834b1c067d84c..1498c5d75fa53 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5775,8 +5775,8 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, if (D->getType()->isReferenceType()) T = D->getType(); - if (getLangOpts().HLSL && - D->getType().getTypePtr()->isHLSLResourceRecord()) { + if (getLangOpts().HLSL && (D->getType()->isHLSLResourceRecord() || + D->getType()->isHLSLResourceRecordArray())) { Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy)); NeedsGlobalCtor = true; } else if (getLangOpts().CPlusPlus) { diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 6811f3f27603b..7de529fc898ad 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -357,6 +357,14 @@ getResourceArrayHandleType(VarDecl *VD) { return HLSLAttributedResourceType::findHandleTypeOnResource(Ty); } +// returns the element type of an array (including multi-dimensional array) +static QualType getArrayElementType(QualType Ty) { + assert(Ty->isArrayType() && "expected array type"); + while (const ArrayType *AT = dyn_cast<ArrayType>(Ty.getTypePtr())) + Ty = AT->getElementType(); + return Ty; +} + // Returns true if the type is a leaf element type that is not valid to be // included in HLSL Buffer, such as a resource class, empty struct, zero-sized // array, or a builtin intangible type. Returns false it is a valid leaf element @@ -3698,11 +3706,14 @@ static bool initVarDeclWithCtor(Sema &S, VarDecl *VD, return true; } -bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { +void SemaHLSL::createResourceRecordCtorArgs(const Type *ResourceTy, + StringRef VarName, + HLSLResourceBindingAttr *RBA, + HLSLVkBindingAttr *VkBinding, + uint32_t ArrayIndex, + llvm::SmallVector<Expr *> &Args) { std::optional<uint32_t> RegisterSlot; uint32_t SpaceNo = 0; - HLSLVkBindingAttr *VkBinding = VD->getAttr<HLSLVkBindingAttr>(); - HLSLResourceBindingAttr *RBA = VD->getAttr<HLSLResourceBindingAttr>(); if (VkBinding) { RegisterSlot = VkBinding->getBinding(); SpaceNo = VkBinding->getSet(); @@ -3717,12 +3728,12 @@ bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { uint64_t IntTySize = AST.getTypeSize(AST.IntTy); IntegerLiteral *RangeSize = IntegerLiteral::Create( AST, llvm::APInt(IntTySize, 1), AST.IntTy, SourceLocation()); - IntegerLiteral *Index = IntegerLiteral::Create( - AST, llvm::APInt(UIntTySize, 0), AST.UnsignedIntTy, SourceLocation()); + IntegerLiteral *Index = + IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, ArrayIndex), + AST.UnsignedIntTy, SourceLocation()); IntegerLiteral *Space = IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, SpaceNo), AST.UnsignedIntTy, SourceLocation()); - StringRef VarName = VD->getName(); StringLiteral *Name = StringLiteral::Create( AST, VarName, StringLiteralKind::Ordinary, false, AST.getStringLiteralArrayType(AST.CharTy.withConst(), VarName.size()), @@ -3733,18 +3744,57 @@ bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { IntegerLiteral *RegSlot = IntegerLiteral::Create( AST, llvm::APInt(UIntTySize, RegisterSlot.value()), AST.UnsignedIntTy, SourceLocation()); - Expr *Args[] = {RegSlot, Space, RangeSize, Index, Name}; - return initVarDeclWithCtor(SemaRef, VD, Args); + Args.append({RegSlot, Space, RangeSize, Index, Name}); + } else { + // resource with implicit binding + uint32_t OrderID = (RBA && RBA->hasImplicitBindingOrderID()) + ? RBA->getImplicitBindingOrderID() + : getNextImplicitBindingOrderID(); + IntegerLiteral *OrderId = + IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, OrderID), + AST.UnsignedIntTy, SourceLocation()); + Args.append({Space, RangeSize, Index, OrderId, Name}); } +} - // resource with implicit binding - IntegerLiteral *OrderId = IntegerLiteral::Create( - AST, llvm::APInt(UIntTySize, getNextImplicitBindingOrderID()), - AST.UnsignedIntTy, SourceLocation()); - Expr *Args[] = {Space, RangeSize, Index, OrderId, Name}; +bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { + SmallVector<Expr *> Args; + createResourceRecordCtorArgs(VD->getType().getTypePtr(), VD->getName(), + VD->getAttr<HLSLResourceBindingAttr>(), + VD->getAttr<HLSLVkBindingAttr>(), 0, Args); return initVarDeclWithCtor(SemaRef, VD, Args); } +bool SemaHLSL::initGlobalResourceArrayDecl(VarDecl *VD) { + assert(VD->getType()->isHLSLResourceRecordArray() && + "expected array of resource records"); + + // Individual resources in a resource array are not initialized here. They + // are initialized later on during codegen when the individual resources are + // accessed. Codegen will emit a call to the resource constructor with the + // specified array index. We need to make sure though that the constructor + // for the specific resource type is instantiated, so codegen can emit a call + // to it when the array element is accessed. + SmallVector<Expr *> Args; + QualType ResElementTy = getArrayElementType(VD->getType()); + createResourceRecordCtorArgs(ResElementTy.getTypePtr(), VD->getName(), + VD->getAttr<HLSLResourceBindingAttr>(), + VD->getAttr<HLSLVkBindingAttr>(), 0, Args); + + SourceLocation Loc = VD->getLocation(); + InitializedEntity Entity = + InitializedEntity::InitializeTemporary(ResElementTy); + InitializationKind Kind = InitializationKind::CreateDirect(Loc, Loc, Loc); + InitializationSequence InitSeq(SemaRef, Entity, Kind, Args); + if (InitSeq.Failed()) + return false; + + // This takes care of instantiating and emitting of the constructor that will + // be called from codegen when the array is accessed. + ExprResult OneResInit = InitSeq.Perform(SemaRef, Entity, Kind, Args); + return !OneResInit.isInvalid(); +} + // Returns true if the initialization has been handled. ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/152454 _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits