From 10a06e36e875cbc0259d0d95700e5c4ec9721f1a Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Thu, 7 Aug 2025 00:03:37 -0700 Subject: [PATCH 01/16] [HLSL] Add `isHLSLResourceRecordArray` method to `clang::Type` --- clang/include/clang/AST/Type.h | 1 + clang/lib/AST/Type.cpp | 9 +++++++++ clang/lib/CodeGen/CGHLSLRuntime.cpp | 9 +-------- clang/lib/Sema/SemaHLSL.cpp | 15 ++++----------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 12dce309127e5..dfcf075e73312 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -2724,6 +2724,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase { bool isHLSLAttributedResourceType() const; bool isHLSLInlineSpirvType() const; bool isHLSLResourceRecord() const; + bool isHLSLResourceRecordArray() const; bool isHLSLIntangibleType() const; // Any HLSL intangible type (builtin, array, class) diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 141edc881d683..03d7413cf95b4 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5246,6 +5246,15 @@ bool Type::isHLSLResourceRecord() const { return HLSLAttributedResourceType::findHandleTypeOnResource(this) != nullptr; } +bool Type::isHLSLResourceRecordArray() const { + const Type *Ty = getUnqualifiedDesugaredType(); + if (!Ty->isArrayType()) + return false; + while (isa(Ty)) + Ty = Ty->getArrayElementTypeNoTypeQual(); + return Ty->isHLSLResourceRecord(); +} + bool Type::isHLSLIntangibleType() const { const Type *Ty = getUnqualifiedDesugaredType(); diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index f64ac20545fa3..918cb3e38448d 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -103,13 +103,6 @@ llvm::Triple::ArchType CGHLSLRuntime::getArch() { return CGM.getTarget().getTriple().getArch(); } -// Returns true if the type is an HLSL resource class or an array of them -static bool isResourceRecordTypeOrArrayOf(const clang::Type *Ty) { - while (const ConstantArrayType *CAT = dyn_cast(Ty)) - Ty = CAT->getArrayElementTypeNoTypeQual(); - return Ty->isHLSLResourceRecord(); -} - // Emits constant global variables for buffer constants declarations // and creates metadata linking the constant globals with the buffer global. void CGHLSLRuntime::emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl, @@ -146,7 +139,7 @@ void CGHLSLRuntime::emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl, if (VDTy.getAddressSpace() != LangAS::hlsl_constant) { if (VD->getStorageClass() == SC_Static || VDTy.getAddressSpace() == LangAS::hlsl_groupshared || - isResourceRecordTypeOrArrayOf(VDTy.getTypePtr())) { + VDTy->isHLSLResourceRecord() || VDTy->isHLSLResourceRecordArray()) { // Emit static and groupshared variables and resource classes inside // cbuffer as regular globals CGM.EmitGlobal(VD); diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 8536e04a129ee..873efdae38f18 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -337,16 +337,9 @@ static bool isZeroSizedArray(const ConstantArrayType *CAT) { return CAT != nullptr; } -// Returns true if the record type is an HLSL resource class or an array of -// resource classes -static bool isResourceRecordTypeOrArrayOf(const Type *Ty) { - while (const ConstantArrayType *CAT = dyn_cast(Ty)) - Ty = CAT->getArrayElementTypeNoTypeQual(); - return HLSLAttributedResourceType::findHandleTypeOnResource(Ty) != nullptr; -} - static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { - return isResourceRecordTypeOrArrayOf(VD->getType().getTypePtr()); + const Type *Ty = VD->getType().getTypePtr(); + return Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray(); } // Returns true if the type is a leaf element type that is not valid to be @@ -355,7 +348,7 @@ static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { // type or if it is a record type that needs to be inspected further. static bool isInvalidConstantBufferLeafElementType(const Type *Ty) { Ty = Ty->getUnqualifiedDesugaredType(); - if (isResourceRecordTypeOrArrayOf(Ty)) + if (Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray()) return true; if (Ty->isRecordType()) return Ty->getAsCXXRecordDecl()->isEmpty(); @@ -3597,7 +3590,7 @@ void SemaHLSL::deduceAddressSpace(VarDecl *Decl) { return; // Resource handles. - if (isResourceRecordTypeOrArrayOf(Type->getUnqualifiedDesugaredType())) + if (Type->isHLSLResourceRecord() || Type->isHLSLResourceRecordArray()) return; // Only static globals belong to the Private address space. From 4e153a4da8b990a1d07d6d1d63d2be74ed45e2eb Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Thu, 7 Aug 2025 00:37:23 -0700 Subject: [PATCH 02/16] [HLSL] Add implicit binding attribute to resource arrays without binding and make them static If a resource array does not have an explicit binding attribute, SemaHLSL will add an implicit one. The attribute will be used to transfer implicit binding order ID to the codegen, the same way as it is done for HLSLBufferDecls. This is necessary in order to generate correct initialization of resources in an array that does not have an explicit binding. This change also marks resource arrays declared at a global scope as `static`, which is what is already done for standalone resources. --- clang/lib/Sema/SemaHLSL.cpp | 57 +++++++++++++++---- .../test/AST/HLSL/resource_binding_attr.hlsl | 28 +++++++-- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 873efdae38f18..ffb996e79409c 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -71,6 +71,10 @@ static RegisterType getRegisterType(ResourceClass RC) { llvm_unreachable("unexpected ResourceClass value"); } +static RegisterType getRegisterType(const HLSLAttributedResourceType *ResTy) { + return getRegisterType(ResTy->getAttrs().ResourceClass); +} + // Converts the first letter of string Slot to RegisterType. // Returns false if the letter does not correspond to a valid register type. static bool convertToRegisterType(StringRef Slot, RegisterType *RT) { @@ -342,6 +346,17 @@ static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { return Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray(); } +static const HLSLAttributedResourceType * +getResourceArrayHandleType(VarDecl *VD) { + assert(VD->getType()->isHLSLResourceRecordArray() && + "expected array of resource records"); + const Type *Ty = VD->getType()->getUnqualifiedDesugaredType(); + while (const ConstantArrayType *CAT = dyn_cast(Ty)) { + Ty = CAT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType(); + } + return HLSLAttributedResourceType::findHandleTypeOnResource(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 @@ -568,16 +583,13 @@ void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) { BufDecl->addLayoutStruct(LS); } -static void addImplicitBindingAttrToBuffer(Sema &S, HLSLBufferDecl *BufDecl, - uint32_t ImplicitBindingOrderID) { - RegisterType RT = - BufDecl->isCBuffer() ? RegisterType::CBuffer : RegisterType::SRV; +static void addImplicitBindingAttrToDecl(Sema &S, Decl *D, RegisterType RT, + uint32_t ImplicitBindingOrderID) { auto *Attr = HLSLResourceBindingAttr::CreateImplicit(S.getASTContext(), "", "0", {}); - std::optional RegSlot; - Attr->setBinding(RT, RegSlot, 0); + Attr->setBinding(RT, std::nullopt, 0); Attr->setImplicitBindingOrderID(ImplicitBindingOrderID); - BufDecl->addAttr(Attr); + D->addAttr(Attr); } // Handle end of cbuffer/tbuffer declaration @@ -600,7 +612,10 @@ void SemaHLSL::ActOnFinishBuffer(Decl *Dcl, SourceLocation RBrace) { if (RBA) RBA->setImplicitBindingOrderID(OrderID); else - addImplicitBindingAttrToBuffer(SemaRef, BufDecl, OrderID); + addImplicitBindingAttrToDecl(SemaRef, BufDecl, + BufDecl->isCBuffer() ? RegisterType::CBuffer + : RegisterType::SRV, + OrderID); } SemaRef.PopDeclContext(); @@ -1906,7 +1921,7 @@ static bool DiagnoseLocalRegisterBinding(Sema &S, SourceLocation &ArgLoc, if (const HLSLAttributedResourceType *AttrResType = HLSLAttributedResourceType::findHandleTypeOnResource( VD->getType().getTypePtr())) { - if (RegType == getRegisterType(AttrResType->getAttrs().ResourceClass)) + if (RegType == getRegisterType(AttrResType)) return true; S.Diag(D->getLocation(), diag::err_hlsl_binding_type_mismatch) @@ -2439,8 +2454,8 @@ void SemaHLSL::ActOnEndOfTranslationUnit(TranslationUnitDecl *TU) { HLSLBufferDecl *DefaultCBuffer = HLSLBufferDecl::CreateDefaultCBuffer( SemaRef.getASTContext(), SemaRef.getCurLexicalContext(), DefaultCBufferDecls); - addImplicitBindingAttrToBuffer(SemaRef, DefaultCBuffer, - getNextImplicitBindingOrderID()); + addImplicitBindingAttrToDecl(SemaRef, DefaultCBuffer, RegisterType::CBuffer, + getNextImplicitBindingOrderID()); SemaRef.getCurLexicalContext()->addDecl(DefaultCBuffer); createHostLayoutStructForBuffer(SemaRef, DefaultCBuffer); @@ -3633,7 +3648,7 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) { const Type *VarType = VD->getType().getTypePtr(); while (VarType->isArrayType()) VarType = VarType->getArrayElementTypeNoTypeQual(); - if (VarType->isHLSLResourceRecord() || + if (isResourceRecordTypeOrArrayOf(VD) || VD->hasAttr()) { // Make the variable for resources static. The global externally visible // storage is accessed through the handle, which is a member. The variable @@ -3643,6 +3658,24 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) { // process explicit bindings processExplicitBindingsOnDecl(VD); + + if (VD->getType()->isHLSLResourceRecordArray()) { + // If the resource array does not have an explicit binding attribute, + // create an implicit one. It will be used to transfer implicit binding + // order_ID to codegen. + if (!VD->hasAttr()) { + HLSLResourceBindingAttr *RBA = VD->getAttr(); + if (!RBA || !RBA->hasRegisterSlot()) { + uint32_t OrderID = getNextImplicitBindingOrderID(); + if (RBA) + RBA->setImplicitBindingOrderID(OrderID); + else + addImplicitBindingAttrToDecl( + SemaRef, VD, getRegisterType(getResourceArrayHandleType(VD)), + OrderID); + } + } + } } deduceAddressSpace(VD); diff --git a/clang/test/AST/HLSL/resource_binding_attr.hlsl b/clang/test/AST/HLSL/resource_binding_attr.hlsl index c073cd4dc1476..e8e79e44b8f0c 100644 --- a/clang/test/AST/HLSL/resource_binding_attr.hlsl +++ b/clang/test/AST/HLSL/resource_binding_attr.hlsl @@ -20,20 +20,24 @@ export float foo() { return a + b; } -// CHECK: VarDecl {{.*}} UAV 'RWBuffer':'hlsl::RWBuffer' +// CHECK: VarDecl {{.*}} UAV 'RWBuffer':'hlsl::RWBuffer' static // CHECK: HLSLResourceBindingAttr {{.*}} "u3" "space0" RWBuffer UAV : register(u3); -// CHECK: VarDecl {{.*}} UAV1 'RWBuffer':'hlsl::RWBuffer' +// CHECK: VarDecl {{.*}} UAV1 'RWBuffer':'hlsl::RWBuffer' static // CHECK: HLSLResourceBindingAttr {{.*}} "u2" "space0" -// CHECK: VarDecl {{.*}} UAV2 'RWBuffer':'hlsl::RWBuffer' +// CHECK: VarDecl {{.*}} UAV2 'RWBuffer':'hlsl::RWBuffer' static // CHECK: HLSLResourceBindingAttr {{.*}} "u4" "space0" RWBuffer UAV1 : register(u2), UAV2 : register(u4); -// CHECK: VarDecl {{.*}} UAV3 'RWBuffer':'hlsl::RWBuffer' +// CHECK: VarDecl {{.*}} UAV3 'RWBuffer':'hlsl::RWBuffer' static // CHECK: HLSLResourceBindingAttr {{.*}} "" "space5" RWBuffer UAV3 : register(space5); +// CHECK: VarDecl {{.*}} UAV_Array 'RWBuffer[10]' static +// CHECK: HLSLResourceBindingAttr {{.*}} "u10" "space6" +RWBuffer UAV_Array[10] : register(u10, space6); + // // Default constants ($Globals) layout annotations @@ -56,3 +60,19 @@ struct S { // CHECK: VarDecl {{.*}} s 'hlsl_constant S' // CHECK: HLSLResourceBindingAttr {{.*}} "c10" "space0 S s : register(c10); + +// +// Implicit binding + +// Constant buffers should have implicit binding attribute added by SemaHLSL +// CHECK: HLSLBufferDecl {{.*}} line:[[# @LINE + 3]]:9 cbuffer CB2 +// CHECK-NEXT: HLSLResourceClassAttr {{.*}} Implicit CBuffer +// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} Implicit "" "0" +cbuffer CB2 { + float4 c; +} + +// Resource arrays should have implicit binding attribute added by SemaHLSL +// CHECK: VarDecl {{.*}} SB 'StructuredBuffer[10]' static +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" +StructuredBuffer SB[10]; From ac99f5af93d9cbb9cd93299eb5d8efae778fe21f Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Thu, 7 Aug 2025 01:06:42 -0700 Subject: [PATCH 03/16] one more instance to replace --- clang/lib/Sema/SemaHLSL.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 873efdae38f18..17f17f8114373 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -3630,10 +3630,7 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) { if (VD->getType()->isHLSLIntangibleType()) collectResourceBindingsOnVarDecl(VD); - const Type *VarType = VD->getType().getTypePtr(); - while (VarType->isArrayType()) - VarType = VarType->getArrayElementTypeNoTypeQual(); - if (VarType->isHLSLResourceRecord() || + if (isResourceRecordTypeOrArrayOf(VD) || VD->hasAttr()) { // Make the variable for resources static. The global externally visible // storage is accessed through the handle, which is a member. The variable From e12a7a97db2e4abb25c31eda5f77e86bd8b8aa03 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Thu, 7 Aug 2025 01:17:55 -0700 Subject: [PATCH 04/16] update test --- clang/test/AST/HLSL/resource_binding_attr.hlsl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/test/AST/HLSL/resource_binding_attr.hlsl b/clang/test/AST/HLSL/resource_binding_attr.hlsl index e8e79e44b8f0c..05d5eb0d619d8 100644 --- a/clang/test/AST/HLSL/resource_binding_attr.hlsl +++ b/clang/test/AST/HLSL/resource_binding_attr.hlsl @@ -20,21 +20,21 @@ export float foo() { return a + b; } -// CHECK: VarDecl {{.*}} UAV 'RWBuffer':'hlsl::RWBuffer' static +// CHECK: VarDecl {{.*}} UAV 'RWBuffer':'hlsl::RWBuffer' // CHECK: HLSLResourceBindingAttr {{.*}} "u3" "space0" RWBuffer UAV : register(u3); -// CHECK: VarDecl {{.*}} UAV1 'RWBuffer':'hlsl::RWBuffer' static +// CHECK: VarDecl {{.*}} UAV1 'RWBuffer':'hlsl::RWBuffer' // CHECK: HLSLResourceBindingAttr {{.*}} "u2" "space0" -// CHECK: VarDecl {{.*}} UAV2 'RWBuffer':'hlsl::RWBuffer' static +// CHECK: VarDecl {{.*}} UAV2 'RWBuffer':'hlsl::RWBuffer' // CHECK: HLSLResourceBindingAttr {{.*}} "u4" "space0" RWBuffer UAV1 : register(u2), UAV2 : register(u4); -// CHECK: VarDecl {{.*}} UAV3 'RWBuffer':'hlsl::RWBuffer' static +// CHECK: VarDecl {{.*}} UAV3 'RWBuffer':'hlsl::RWBuffer' // CHECK: HLSLResourceBindingAttr {{.*}} "" "space5" RWBuffer UAV3 : register(space5); -// CHECK: VarDecl {{.*}} UAV_Array 'RWBuffer[10]' static +// CHECK: VarDecl {{.*}} UAV_Array 'RWBuffer[10]' // CHECK: HLSLResourceBindingAttr {{.*}} "u10" "space6" RWBuffer UAV_Array[10] : register(u10, space6); @@ -73,6 +73,6 @@ cbuffer CB2 { } // Resource arrays should have implicit binding attribute added by SemaHLSL -// CHECK: VarDecl {{.*}} SB 'StructuredBuffer[10]' static +// CHECK: VarDecl {{.*}} SB 'StructuredBuffer[10]' // CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" StructuredBuffer SB[10]; From 86902233a96b26b710bd39c096cb581f252e09a4 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Thu, 7 Aug 2025 01:30:36 -0700 Subject: [PATCH 05/16] [HLSL] Global resource arrays element access Adds support for accessing individual resources from fixed-size resource arrays declared at global scope. When a global resource array is indexed to retrieve a specific resource, the codegen translates the `ArraySubscriptExpr` into a constructor call for the corresponding resource record type and binding. Closes #145424 --- clang/include/clang/Sema/SemaHLSL.h | 9 +- clang/lib/CodeGen/CGExpr.cpp | 10 + clang/lib/CodeGen/CGHLSLRuntime.cpp | 223 +++++++++++++++++- clang/lib/CodeGen/CGHLSLRuntime.h | 6 + clang/lib/CodeGen/CodeGenModule.cpp | 4 +- clang/lib/Sema/SemaHLSL.cpp | 93 ++++++-- .../resources/res-array-global-multi-dim.hlsl | 32 +++ .../resources/res-array-global.hlsl | 59 +++++ clang/test/CodeGenHLSL/static-local-ctor.hlsl | 5 +- 9 files changed, 401 insertions(+), 40 deletions(-) create mode 100644 clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl create mode 100644 clang/test/CodeGenHLSL/resources/res-array-global.hlsl 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 &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 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(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(E); + } + if (const DeclRefExpr *DRE = dyn_cast_or_null(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(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 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 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 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(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(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( + 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(); + HLSLResourceBindingAttr *RBA = ArrayDecl->getAttr(); + 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( + 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 + 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(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 &Args) { std::optional RegisterSlot; uint32_t SpaceNo = 0; - HLSLVkBindingAttr *VkBinding = VD->getAttr(); - HLSLResourceBindingAttr *RBA = VD->getAttr(); 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 Args; + createResourceRecordCtorArgs(VD->getType().getTypePtr(), VD->getName(), + VD->getAttr(), + VD->getAttr(), 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 Args; + QualType ResElementTy = getArrayElementType(VD->getType()); + createResourceRecordCtorArgs(ResElementTy.getTypePtr(), VD->getName(), + VD->getAttr(), + VD->getAttr(), 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. // Returns false to use default initialization. bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) { @@ -3753,17 +3803,14 @@ bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) { if (VD->getType().getAddressSpace() == LangAS::hlsl_constant) return true; - // Initialize resources - if (!isResourceRecordTypeOrArrayOf(VD)) - return false; - - // FIXME: We currectly support only simple resources - no arrays of resources - // or resources in user defined structs. - // (llvm/llvm-project#133835, llvm/llvm-project#133837) // Initialize resources at the global scope - if (VD->hasGlobalStorage() && VD->getType()->isHLSLResourceRecord()) - return initGlobalResourceDecl(VD); - + if (VD->hasGlobalStorage()) { + const Type *Ty = VD->getType().getTypePtr(); + if (Ty->isHLSLResourceRecord()) + return initGlobalResourceDecl(VD); + if (Ty->isHLSLResourceRecordArray()) + return initGlobalResourceArrayDecl(VD); + } return false; } diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl new file mode 100644 index 0000000000000..97df14769b132 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s +// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan-compute \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// CHECK: @[[BufB:.*]] = private unnamed_addr constant [2 x i8] c"B\00", align 1 +// CHECK: @[[BufC:.*]] = private unnamed_addr constant [2 x i8] c"C\00", align 1 +// CHECK: @[[BufD:.*]] = private unnamed_addr constant [2 x i8] c"D\00", align 1 + +RWBuffer B[4][4] : register(u2); +RWBuffer C[2][2][5] : register(u10, space1); +RWBuffer D[10][5]; // implicit binding -> u18, space0 + +RWStructuredBuffer Out; + +[numthreads(4,1,1)] +void main() { + // CHECK: define internal{{.*}} void @_Z4mainv() + // CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp1:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer + + // Make sure that B[2][3] is translated to a RWBuffer constructor call for explicit binding (u2, space0) with range 16 and index 14 + // CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 2, i32 noundef 0, i32 noundef 16, i32 noundef 14, ptr noundef @[[BufB]]) + + // Make sure that C[1][0][3] is translated to a RWBuffer constructor call for explicit binding (u10, space1) with range 20 and index 13 + // CHECK: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp1]], i32 noundef 10, i32 noundef 1, i32 noundef 20, i32 noundef 13, ptr noundef @[[BufC]]) + + // Make sure that D[9][2] is translated to a RWBuffer constructor call for implicit binding (u18, space0) with range 50 and index 47 + // CHECK: call void @_ZN4hlsl8RWBufferIjEC1EjijjPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 0, i32 noundef 50, i32 noundef 47, i32 noundef 0, ptr noundef @[[BufD]]) + Out[0] = B[3][2][0] + (float)C[1][0][3][0] + (float)D[9][2][0]; +} diff --git a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl new file mode 100644 index 0000000000000..26526e9a5c9d8 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl @@ -0,0 +1,59 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -check-prefixes=CHECK,DXIL +// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan-compute \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -check-prefixes=CHECK,SPV + +// CHECK: @[[BufA:.*]] = private unnamed_addr constant [2 x i8] c"A\00", align 1 +// CHECK: @[[BufB:.*]] = private unnamed_addr constant [2 x i8] c"B\00", align 1 +// CHECK: @[[BufC:.*]] = private unnamed_addr constant [2 x i8] c"C\00", align 1 +// CHECK: @[[BufD:.*]] = private unnamed_addr constant [2 x i8] c"D\00", align 1 + +// different explicit binding for DXIL and SPIR-V +[[vk::binding(12, 2)]] +RWBuffer A[4] : register(u10, space1); + +[[vk::binding(13)]] // SPIR-V explicit binding 13, set 0 +RWBuffer B[5]; // DXIL implicit binding in space0 + +// same explicit binding for both DXIL and SPIR-V +// (SPIR-V takes the binding from register annotation if there is no vk::binding attribute)) +RWBuffer C[3] : register(u2); + +// implicit binding for both DXIL and SPIR-V in space/set 0 +RWBuffer D[10]; + +RWStructuredBuffer Out; + +[numthreads(4,1,1)] +void main() { + // CHECK: define internal{{.*}} void @_Z4mainv() + // CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp1:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp3:.*]] = alloca %"class.hlsl::RWBuffer + + // Make sure A[2] is translated to a RWBuffer constructor call with range 4 and index 2 + // and DXIL explicit binding (u10, space1) + // and SPIR-V explicit binding (binding 12, set 2) + // DXIL: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 10, i32 noundef 1, i32 noundef 4, i32 noundef 2, ptr noundef @[[BufA]]) + // SPV: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 12, i32 noundef 2, i32 noundef 4, i32 noundef 2, ptr noundef @[[BufA]]) + + // Make sure B[3] is translated to a RWBuffer constructor call with range 5 and index 3 + // and DXIL for implicit binding in space0, order id 0 + // and SPIR-V explicit binding (binding 13, set 0) + // DXIL: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc(ptr {{.*}} %[[Tmp1]], i32 noundef 0, i32 noundef 5, i32 noundef 3, i32 noundef 0, ptr noundef @[[BufB]]) + // SPV: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp1]], i32 noundef 13, i32 noundef 0, i32 noundef 5, i32 noundef 3, ptr noundef @[[BufB]]) + + // Make sure C[1] is translated to a RWBuffer constructor call with range 3 and index 1 + // and DXIL explicit binding (u2, space0) + // and SPIR-V explicit binding (binding 2, set 0) + // DXIL: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 2, i32 noundef 0, i32 noundef 3, i32 noundef 1, ptr noundef @[[BufC]]) + // SPV: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 2, i32 noundef 0, i32 noundef 3, i32 noundef 1, ptr noundef @[[BufC]]) + + // Make sure D[7] is translated to a RWBuffer constructor call with range 10 and index 7 + // and DXIL for implicit binding in space0, order id 1 + // and SPIR-V explicit binding (binding 13, set 0), order id 0 + // DXIL: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 1, ptr noundef @[[BufD]]) + // SPV: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 0, ptr noundef @[[BufD]]) + Out[0] = A[2][0] + (float)B[3][0] + (float)C[1][0] + (float)D[7][0]; +} diff --git a/clang/test/CodeGenHLSL/static-local-ctor.hlsl b/clang/test/CodeGenHLSL/static-local-ctor.hlsl index 87f49b8bf7ac7..9a4bf66f030ed 100644 --- a/clang/test/CodeGenHLSL/static-local-ctor.hlsl +++ b/clang/test/CodeGenHLSL/static-local-ctor.hlsl @@ -2,7 +2,7 @@ // Verify that no per variable _Init_thread instructions are emitted for non-trivial static locals // These would normally be emitted by the MicrosoftCXXABI, but the DirectX backend should exlude them -// Instead, check for the guardvar oparations that should protect the constructor initialization should +// Instead, check for the guardvar operations that should protect the constructor initialization should // only take place once. RWBuffer buf[10]; @@ -15,13 +15,14 @@ void InitBuf(RWBuffer buf) { // CHECK-NOT: _Init_thread_epoch // CHECK: define internal void @_Z4mainv // CHECK-NEXT: entry: +// CHECK-NEXT: [[Tmp0:%.*]] = alloca %"class.hlsl::RWBuffer" // CHECK-NEXT: [[Tmp1:%.*]] = alloca %"class.hlsl::RWBuffer" // CHECK-NEXT: [[Tmp2:%.*]] = load i8, ptr @_ZGVZ4mainvE5mybuf // CHECK-NEXT: [[Tmp3:%.*]] = icmp eq i8 [[Tmp2]], 0 // CHECK-NEXT: br i1 [[Tmp3]] // CHECK-NOT: _Init_thread_header // CHECK: init.check: -// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1Ejijj +// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc( // CHECK-NEXT: store i8 1, ptr @_ZGVZ4mainvE5mybuf // CHECK-NOT: _Init_thread_footer From f08e2132a29d63a74084a0d486033961127e5b0c Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Mon, 11 Aug 2025 13:11:48 -0700 Subject: [PATCH 06/16] code review feedback, add test for dynamic indexing, typedef and identical index --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 17 +++++++----- .../resources/res-array-global-dyn-index.hlsl | 26 +++++++++++++++++++ .../resources/res-array-global-multi-dim.hlsl | 12 ++++++--- .../resources/res-array-global.hlsl | 5 ++-- 4 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index a09e540367a18..52fabe9d2983b 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -113,7 +113,8 @@ static int getTotalArraySize(const clang::Type *Ty) { if (Ty->isIncompleteArrayType()) return -1; int Size = 1; - while (const auto *CAT = dyn_cast(Ty)) { + while (const auto *CAT = + dyn_cast(Ty->getUnqualifiedDesugaredType())) { Size *= CAT->getSExtSize(); Ty = CAT->getArrayElementTypeNoTypeQual(); } @@ -121,7 +122,7 @@ static int getTotalArraySize(const clang::Type *Ty) { } // Find constructor decl for a specific resource record type and binding -// (implicit vs. explicit). The constructor has 6 parameters. +// (implicit vs. explicit). The constructor has 5 parameters. // For explicit binding the signature is: // void(unsigned, unsigned, int, unsigned, const char *). // For implicit binding the signature is: @@ -171,7 +172,7 @@ static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD, if (VkBinding) { RegisterSlot = VkBinding->getBinding(); SpaceNo = VkBinding->getSet(); - } else if (RBA) { + } else { if (RBA->hasRegisterSlot()) RegisterSlot = RBA->getSlotNumber(); SpaceNo = RBA->getSpaceNumber(); @@ -815,7 +816,7 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( ArraySubsExpr->getType()->isHLSLResourceRecordArray() && "expected resource array subscript expression"); - // let clang codegen handle local resource array subscrips + // let clang codegen handle local resource array subscripts const VarDecl *ArrayDecl = dyn_cast(getArrayDecl(ArraySubsExpr)); if (!ArrayDecl || !ArrayDecl->hasGlobalStorage()) return std::nullopt; @@ -824,12 +825,10 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( assert(!ArraySubsExpr->getType()->isArrayType() && "indexing of array subsets it not supported yet"); - // get total array size (= range size) + // get the resource array type 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- @@ -886,6 +885,10 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo( ThisAddress, CD->getThisType()->getPointeeType()); + // get total array size (= range size) + llvm::Value *Range = + llvm::ConstantInt::get(CGM.IntTy, getTotalArraySize(ResArrayTy)); + // assemble the constructor parameters CallArgList Args; createResourceCtorArgs(CGM, CD, ThisPtr, Range, Index, ArrayDecl->getName(), diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl new file mode 100644 index 0000000000000..26108aadff958 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl @@ -0,0 +1,26 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// CHECK: @[[BufA:.*]] = private unnamed_addr constant [2 x i8] c"A\00", align 1 + +RWBuffer A[4][3] : register(u2); +RWStructuredBuffer Out; + +// Make sure A[GI.x][GI.y] is translated to a RWBuffer constructor call with range 12 and dynamically calculated index + +// CHECK: define internal void @_Z4mainDv3_j(<3 x i32> noundef %GI) +// CHECK: %[[GI_alloca:.*]] = alloca <3 x i32>, align 16 +// CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer +// CHECK: store <3 x i32> %GI, ptr %[[GI_alloca]] + +// CHECK: %[[GI:.*]] = load <3 x i32>, ptr %[[GI_alloca]], align 16 +// CHECK: %[[GI_y:.*]] = extractelement <3 x i32> %[[GI]], i32 1 +// CHECK: %[[GI:.*]] = load <3 x i32>, ptr %[[GI_alloca]], align 16 +// CHECK: %[[GI_x:.*]] = extractelement <3 x i32> %[[GI]], i32 0 +// CHECK: %[[Tmp1:.*]] = mul i32 %[[GI_x]], 3 +// CHECK: %[[Index:.*]] = add i32 %[[GI_y]], %[[Tmp1]] +// CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 2, i32 noundef 0, i32 noundef 12, i32 noundef %[[Index]], ptr noundef @A.str) +[numthreads(4,1,1)] +void main(uint3 GI : SV_GroupThreadID) { + Out[0] = A[GI.x][GI.y][0]; +} diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl index 97df14769b132..5219cf5143c13 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl @@ -9,7 +9,9 @@ RWBuffer B[4][4] : register(u2); RWBuffer C[2][2][5] : register(u10, space1); -RWBuffer D[10][5]; // implicit binding -> u18, space0 + +typedef RWBuffer RWBufferArrayTenByFive[10][5]; // test typedef for the resource array type +RWBufferArrayTenByFive D; // implicit binding -> u18, space0 RWStructuredBuffer Out; @@ -19,8 +21,9 @@ void main() { // CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer // CHECK: %[[Tmp1:.*]] = alloca %"class.hlsl::RWBuffer // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp3:.*]] = alloca %"class.hlsl::RWBuffer - // Make sure that B[2][3] is translated to a RWBuffer constructor call for explicit binding (u2, space0) with range 16 and index 14 + // Make sure that B[3][2] is translated to a RWBuffer constructor call for explicit binding (u2, space0) with range 16 and index 14 // CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 2, i32 noundef 0, i32 noundef 16, i32 noundef 14, ptr noundef @[[BufB]]) // Make sure that C[1][0][3] is translated to a RWBuffer constructor call for explicit binding (u10, space1) with range 20 and index 13 @@ -28,5 +31,8 @@ void main() { // Make sure that D[9][2] is translated to a RWBuffer constructor call for implicit binding (u18, space0) with range 50 and index 47 // CHECK: call void @_ZN4hlsl8RWBufferIjEC1EjijjPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 0, i32 noundef 50, i32 noundef 47, i32 noundef 0, ptr noundef @[[BufD]]) - Out[0] = B[3][2][0] + (float)C[1][0][3][0] + (float)D[9][2][0]; + + // Make sure that the second B[3][2] is translated to the same a RWBuffer constructor call as the first B[3][2] subscript + // CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 2, i32 noundef 0, i32 noundef 16, i32 noundef 14, ptr noundef @[[BufB]]) + Out[0] = B[3][2][0] + (float)C[1][0][3][0] + (float)D[9][2][0] + B[3][2][1]; } diff --git a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl index 26526e9a5c9d8..cc0e8cca19953 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl @@ -50,9 +50,8 @@ void main() { // DXIL: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 2, i32 noundef 0, i32 noundef 3, i32 noundef 1, ptr noundef @[[BufC]]) // SPV: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 2, i32 noundef 0, i32 noundef 3, i32 noundef 1, ptr noundef @[[BufC]]) - // Make sure D[7] is translated to a RWBuffer constructor call with range 10 and index 7 - // and DXIL for implicit binding in space0, order id 1 - // and SPIR-V explicit binding (binding 13, set 0), order id 0 + // Make sure D[7] is translated to a RWBuffer constructor call with implicit binding + // for both DXIL and SPIR-V // DXIL: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 1, ptr noundef @[[BufD]]) // SPV: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 0, ptr noundef @[[BufD]]) Out[0] = A[2][0] + (float)B[3][0] + (float)C[1][0] + (float)D[7][0]; From 227f6ae97dfe128c3a2e43792a1443f6c7375673 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Mon, 11 Aug 2025 15:43:50 -0700 Subject: [PATCH 07/16] fix build break after merge --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 7ab6e335749cb..a5aafc09828a2 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -869,11 +869,9 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( // 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())) { + if (CGF.EmitLifetimeStart(TmpVar.getPointer())) { CGF.pushFullExprCleanup( - NormalEHLifetimeMarker, TmpVar, Size); + NormalEHLifetimeMarker, TmpVar); } AggValueSlot ValueSlot = AggValueSlot::forAddr( TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true), From 8bc614ae146014413dcdcee62daac226645ded15 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Tue, 12 Aug 2025 00:30:10 -0700 Subject: [PATCH 08/16] Fix bug - initialize resource arrays to poison, add test --- clang/lib/CodeGen/CodeGenModule.cpp | 17 +++++++++-------- .../resources/resource-bindings.hlsl | 14 +++++++++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 44a958a9112f4..590705508b7e8 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5752,11 +5752,16 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, (D->getType()->isCUDADeviceBuiltinSurfaceType() || D->getType()->isCUDADeviceBuiltinTextureType()); if (getLangOpts().CUDA && - (IsCUDASharedVar || IsCUDAShadowVar || IsCUDADeviceShadowVar)) + (IsCUDASharedVar || IsCUDAShadowVar || IsCUDADeviceShadowVar)) { Init = llvm::UndefValue::get(getTypes().ConvertTypeForMem(ASTTy)); - else if (D->hasAttr()) + } else if (getLangOpts().HLSL && + (D->getType()->isHLSLResourceRecord() || + D->getType()->isHLSLResourceRecordArray())) { + Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy)); + NeedsGlobalCtor = D->getType()->isHLSLResourceRecord(); + } else if (D->hasAttr()) { Init = llvm::UndefValue::get(getTypes().ConvertTypeForMem(ASTTy)); - else if (!InitExpr) { + } else if (!InitExpr) { // This is a tentative definition; tentative definitions are // implicitly initialized with { 0 }. // @@ -5777,11 +5782,7 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, if (D->getType()->isReferenceType()) T = D->getType(); - if (getLangOpts().HLSL && (D->getType()->isHLSLResourceRecord() || - D->getType()->isHLSLResourceRecordArray())) { - Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy)); - NeedsGlobalCtor = true; - } else if (getLangOpts().CPlusPlus) { + if (getLangOpts().CPlusPlus) { Init = EmitNullConstant(T); if (!IsDefinitionAvailableExternally) NeedsGlobalCtor = true; diff --git a/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl b/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl index 0a301346e55be..2d49753e546ca 100644 --- a/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl +++ b/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl @@ -4,11 +4,13 @@ // CHECK: %"class.hlsl::RWBuffer.0" = type { target("dx.TypedBuffer", float, 1, 0, 0) } // CHECK: %"class.hlsl::StructuredBuffer" = type { target("dx.RawBuffer", i32, 0, 0) } // CHECK: %"class.hlsl::RWStructuredBuffer" = type { target("dx.RawBuffer", %struct.S, 1, 0) } +// CHECK: %"class.hlsl::RWBuffer.1" = type { target("dx.TypedBuffer", double, 1, 0, 0) } // CHECK: @_ZL4U0S0 = internal global %"class.hlsl::RWBuffer" poison, align 4 // CHECK: @_ZL4U5S3 = internal global %"class.hlsl::RWBuffer.0" poison, align 4 // CHECK: @_ZL4T2S2 = internal global %"class.hlsl::StructuredBuffer" poison, align 4 // CHECK: @_ZL4T3S0 = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4 +// CHECK: @_ZL5Array = internal global [10 x %"class.hlsl::RWBuffer.1"] poison, align 4 // CHECK: %[[HANDLE:.*]] = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) // CHECK-SAME: @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_v4f32_1_0_0t( @@ -42,5 +44,15 @@ struct S { }; RWStructuredBuffer T3S0 : register(u3); +// Resource array elements are initialized on access; make sure there is not call +// to initialize RWBuffer. +// CHECK-NOT: call target("dx.TypedBuffer", double, 1, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f64_1_0_0t( +RWBuffer Array[10] : register(u4, space0); + [numthreads(4,1,1)] -void main() {} +void main() { + // Reference Array to ensure it is emitted and we can test that it is initialized + // to poison, but do not index it. + // Non-array resources are always emitted because they have a constructor initializer. + (void)Array; +} From f0d05cd65c7673ce4238fc309648cd9601f71f91 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Tue, 12 Aug 2025 12:03:03 -0700 Subject: [PATCH 09/16] Update global_array.hlsl SPIR-V convergence test to use custom struct --- clang/test/CodeGenHLSL/convergence/global_array.hlsl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/clang/test/CodeGenHLSL/convergence/global_array.hlsl b/clang/test/CodeGenHLSL/convergence/global_array.hlsl index 030ba48a5f170..c594e3a3e62ae 100644 --- a/clang/test/CodeGenHLSL/convergence/global_array.hlsl +++ b/clang/test/CodeGenHLSL/convergence/global_array.hlsl @@ -6,9 +6,15 @@ // CHECK: [[loop_entry]]: // CHECK: [[loop_token:%.*]] = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token [[entry_token]]) ] -// CHECK: call void {{.*}} [ "convergencectrl"(token [[loop_token]]) ] +// CHECK: call spir_func void {{.*}} [ "convergencectrl"(token [[loop_token]]) ] // CHECK: br i1 {{%.*}} label {{%.*}} label %[[loop_entry]] -RWBuffer e[2]; + +struct S { + int i; + S() { i = 10; } +}; + +static S s[2]; [numthreads(4,1,1)] void main() { From 3a4b7f2370faa5c59383a8e02740cf6ac5404ced Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Tue, 12 Aug 2025 01:11:56 -0700 Subject: [PATCH 10/16] Tests for local resource arrays --- .../resources/res-array-local1.hlsl | 62 +++++++++++++++++++ .../resources/res-array-local2.hlsl | 34 ++++++++++ .../resources/res-array-local3.hlsl | 60 ++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 clang/test/CodeGenHLSL/resources/res-array-local1.hlsl create mode 100644 clang/test/CodeGenHLSL/resources/res-array-local2.hlsl create mode 100644 clang/test/CodeGenHLSL/resources/res-array-local3.hlsl diff --git a/clang/test/CodeGenHLSL/resources/res-array-local1.hlsl b/clang/test/CodeGenHLSL/resources/res-array-local1.hlsl new file mode 100644 index 0000000000000..3257454484794 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-local1.hlsl @@ -0,0 +1,62 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// This test verifies local arrays of resources in HLSL. + +// CHECK: @_ZL1A = internal global %"class.hlsl::RWBuffer" poison, align 4 +// CHECK: @_ZL1B = internal global %"class.hlsl::RWBuffer" poison, align 4 +// CHECK: @_ZL1C = internal global %"class.hlsl::RWBuffer" poison, align 4 + +RWBuffer A : register(u1); +RWBuffer B : register(u2); +RWBuffer C : register(u3); +RWStructuredBuffer Out : register(u0); + +// CHECK: define internal void @_Z4mainv() +// CHECK-NEXT: entry: +[numthreads(4,1,1)] +void main() { +// CHECK-NEXT: %First = alloca [3 x %"class.hlsl::RWBuffer"], align 4 +// CHECK-NEXT: %Second = alloca [4 x %"class.hlsl::RWBuffer"], align 4 + RWBuffer First[3] = { A, B, C }; + RWBuffer Second[4]; + +// Verify initialization of First array from an initialization list +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %First, ptr align 4 @_ZL1A, i32 4, i1 false) +// CHECK-NEXT: %[[Ptr1:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %First, i32 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr1]], ptr align 4 @_ZL1B, i32 4, i1 false) +// CHECK-NEXT: %[[Ptr2:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %First, i32 2 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr2]], ptr align 4 @_ZL1C, i32 4, i1 false) + +// Verify default initialization of Second array, which means there is a loop iterating +// over the array elements and calling the default constructor for each +// CHECK-NEXT: %[[ArrayBeginPtr:.*]] = getelementptr inbounds [4 x %"class.hlsl::RWBuffer"], ptr %Second, i32 0, i32 0 +// CHECK-NEXT: %[[ArrayEndPtr:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %[[ArrayBeginPtr]], i32 4 +// CHECK-NEXT: br label %[[ArrayInitLoop:.*]] +// CHECK: [[ArrayInitLoop]]: +// CHECK-NEXT: %[[ArrayCurPtr:.*]] = phi ptr [ %[[ArrayBeginPtr]], %entry ], [ %[[ArrayNextPtr:.*]], %[[ArrayInitLoop]] ] +// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIfEC1Ev(ptr {{.*}} %[[ArrayCurPtr]]) +// CHECK-NEXT: %[[ArrayNextPtr]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %[[ArrayCurPtr]], i32 1 +// CHECK-NEXT: %[[ArrayInitDone:.*]] = icmp eq ptr %[[ArrayNextPtr]], %[[ArrayEndPtr]] +// CHECK-NEXT: br i1 %[[ArrayInitDone]], label %[[AfterArrayInit:.*]], label %[[ArrayInitLoop]] +// CHECK: [[AfterArrayInit]]: + +// Initialize First[2] with C +// CHECK: %[[Ptr3:.*]] = getelementptr inbounds [4 x %"class.hlsl::RWBuffer"], ptr %Second, i32 0, i32 2 +// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr3]], ptr align 4 @_ZL1C, i32 4, i1 false) + Second[2] = C; + +// get First[1][0] value +// CHECK: %[[First_1_Ptr:.*]] = getelementptr inbounds [3 x %"class.hlsl::RWBuffer"], ptr %First, i32 0, i32 1 +// CHECK: %[[BufPtr1:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[First_1_Ptr]], i32 noundef 0) +// CHECK: %[[Value1:.*]] = load float, ptr %[[BufPtr1]], align 4 + +// get Second[2][0] value +// CHECK: %[[Second_2_Ptr:.*]] = getelementptr inbounds [4 x %"class.hlsl::RWBuffer"], ptr %Second, i32 0, i32 2 +// CHECK: %[[BufPtr2:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[Second_2_Ptr]], i32 noundef 0) +// CHECK: %[[Value2:.*]] = load float, ptr %[[BufPtr2]], align 4 + +// add them +// CHECK: %{{.*}} = fadd {{.*}} float %[[Value1]], %[[Value2]] + Out[0] = First[1][0] + Second[2][0]; +} diff --git a/clang/test/CodeGenHLSL/resources/res-array-local2.hlsl b/clang/test/CodeGenHLSL/resources/res-array-local2.hlsl new file mode 100644 index 0000000000000..e51bf71119914 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-local2.hlsl @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// This test verifies handling of local arrays of resources when used as a function argument. + +// CHECK: @_ZL1A = internal global [3 x %"class.hlsl::RWBuffer"] poison, align 4 + +RWBuffer A[3] : register(u0); +RWStructuredBuffer Out : register(u0); + +// CHECK: define {{.*}} float @_Z3fooA3_N4hlsl8RWBufferIfEE(ptr noundef byval([3 x %"class.hlsl::RWBuffer"]) align 4 %LocalA) +// CHECK-NEXT: entry: +float foo(RWBuffer LocalA[3]) { +// CHECK-NEXT: %[[LocalA_2_Ptr:.*]] = getelementptr inbounds [3 x %"class.hlsl::RWBuffer"], ptr %LocalA, i32 0, i32 2 +// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[LocalA_2_Ptr]], i32 noundef 0) +// CHECK-NEXT: %[[Value:.*]] = load float, ptr %[[BufPtr]], align 4 +// CHECK-NEXT: ret float %[[Value]] + return LocalA[2][0]; +} + +// CHECK: define internal void @_Z4mainv() +// CHECK-NEXT: entry: +[numthreads(4,1,1)] +void main() { +// Check that the `main` function calls `foo` with a local copy of the array +// CHECK-NEXT: %[[Tmp:.*]] = alloca [3 x %"class.hlsl::RWBuffer"], align 4 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp]], ptr align 4 @_ZL1A, i32 12, i1 false) + +// CHECK-NEXT: %[[ReturnedValue:.*]] = call {{.*}} float @_Z3fooA3_N4hlsl8RWBufferIfEE(ptr noundef byval([3 x %"class.hlsl::RWBuffer"]) align 4 %[[Tmp]]) +// CHECK-NEXT: %[[OutBufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl18RWStructuredBufferIfEixEj(ptr {{.*}} @_ZL3Out, i32 noundef 0) +// CHECK-NEXT: store float %[[ReturnedValue]], ptr %[[OutBufPtr]], align 4 +// CHECK-NEXT: ret void + Out[0] = foo(A); +} diff --git a/clang/test/CodeGenHLSL/resources/res-array-local3.hlsl b/clang/test/CodeGenHLSL/resources/res-array-local3.hlsl new file mode 100644 index 0000000000000..4b7eee99b6b6f --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-local3.hlsl @@ -0,0 +1,60 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// This test verifies handling of local arrays of resources when used +// as a function argument that is modified inside the function. + +// CHECK: @_ZL1X = internal global %"class.hlsl::RWBuffer" poison, align 4 +// CHECK: @_ZL1Y = internal global %"class.hlsl::RWBuffer" poison, align 4 + +RWBuffer X : register(u0); +RWBuffer Y : register(u1); + +// CHECK: define {{.*}} @_Z6SomeFnA2_N4hlsl8RWBufferIiEEji( +// CHECK-SAME: ptr noundef byval([2 x %"class.hlsl::RWBuffer"]) align 4 %B, i32 noundef %Idx, i32 noundef %Val0) +// CHECK-NEXT: entry: +// CHECK-NEXT: %[[Idx_addr:.*]] = alloca i32, align 4 +// CHECK-NEXT: %[[Val0_addr:.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 %Idx, ptr %[[Idx_addr]], align 4 +// CHECK-NEXT: store i32 %Val0, ptr %[[Val0_addr]], align 4 +void SomeFn(RWBuffer B[2], uint Idx, int Val0) { + +// CHECK-NEXT: %[[B_0_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %B, i32 0, i32 0 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[B_0_Ptr]], ptr align 4 @_ZL1Y, i32 4, i1 false) + B[0] = Y; + +// CHECK-NEXT: %[[Val0:.*]] = load i32, ptr %[[Val0_addr]], align 4 +// CHECK-NEXT: %[[B_0_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %B, i32 0, i32 0 +// CHECK-NEXT: %[[Idx:.*]] = load i32, ptr %[[Idx_addr]], align 4 +// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIiEixEj(ptr {{.*}} %[[B_0_Ptr]], i32 noundef %[[Idx]]) +// CHECK-NEXT: store i32 %[[Val0]], ptr %[[BufPtr]], align 4 + B[0][Idx] = Val0; +} + +// CHECK: define {{.*}} void @_Z4mainj(i32 noundef %GI) +// CHECK-NEXT: entry: +// CHECK-NEXT: %[[GI_addr:.*]] = alloca i32, align 4 +[numthreads(4,1,1)] +void main(uint GI : SV_GroupIndex) { +// CHECK-NEXT: %A = alloca [2 x %"class.hlsl::RWBuffer"], align 4 +// CHECK-NEXT: %[[Tmp:.*]] = alloca [2 x %"class.hlsl::RWBuffer"], align 4 +// CHECK-NEXT: store i32 %GI, ptr %GI.addr, align 4 + +// Initialization of array A with resources X and Y +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %A, ptr align 4 @_ZL1X, i32 4, i1 false) +// CHECK-NEXT: %[[A_1_Ptr:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %A, i32 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[A_1_Ptr]], ptr align 4 @_ZL1Y, i32 4, i1 false) + RWBuffer A[2] = {X, Y}; + +// Verify that SomeFn is called with a local copy of the array A +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp]], ptr align 4 %A, i32 8, i1 false) +// CHECK-NEXT: %[[GI:.*]] = load i32, ptr %[[GI_addr]], align 4 +// CHECK-NEXT: call void @_Z6SomeFnA2_N4hlsl8RWBufferIiEEji(ptr noundef byval([2 x %"class.hlsl::RWBuffer"]) align 4 %[[Tmp]], i32 noundef %[[GI]], i32 noundef 1) + SomeFn(A, GI, 1); + +// CHECK-NEXT: %[[A_0_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %A, i32 0, i32 0 +// CHECK-NEXT: %[[GI:.*]] = load i32, ptr %[[GI_addr]], align 4 +// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIiEixEj(ptr {{.*}} %[[A_0_Ptr]], i32 noundef %[[GI]]) +// CHECK-NEXT: store i32 2, ptr %[[BufPtr]], align 4 + A[0][GI] = 2; +} From ac62921128839c3c4c1d5232d74e8c2b0606ebab Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Tue, 12 Aug 2025 14:22:28 -0700 Subject: [PATCH 11/16] code review feedback - use existing methods on Type and ASTContext, avoid alloc, use SmallVectorImpl, formatting --- clang/include/clang/Sema/SemaHLSL.h | 2 +- clang/lib/CodeGen/CGHLSLRuntime.cpp | 38 +++++++++-------------------- clang/lib/Sema/SemaHLSL.cpp | 15 +++--------- 3 files changed, 16 insertions(+), 39 deletions(-) diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 0c215c6e10013..016456f241eed 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -239,7 +239,7 @@ class SemaHLSL : public SemaBase { HLSLResourceBindingAttr *RBA, HLSLVkBindingAttr *VkBinding, uint32_t ArrayIndex, - llvm::SmallVector &Args); + llvm::SmallVectorImpl &Args); }; } // namespace clang diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index a5aafc09828a2..e962e2f06afa3 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -22,6 +22,7 @@ #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" #include "clang/Basic/TargetOptions.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Frontend/HLSL/RootSignatureMetadata.h" #include "llvm/IR/Constants.h" @@ -84,20 +85,11 @@ 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(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()); + E = ASE->getBase()->IgnoreImpCasts(); if (!E) return nullptr; ASE = dyn_cast(E); @@ -108,17 +100,12 @@ static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) { } // Get the total size of the array, or -1 if the array is unbounded. -static int getTotalArraySize(const clang::Type *Ty) { +static int getTotalArraySize(ASTContext &AST, const clang::Type *Ty) { + Ty = Ty->getUnqualifiedDesugaredType(); assert(Ty->isArrayType() && "expected array type"); if (Ty->isIncompleteArrayType()) return -1; - int Size = 1; - while (const auto *CAT = - dyn_cast(Ty->getUnqualifiedDesugaredType())) { - Size *= CAT->getSExtSize(); - Ty = CAT->getArrayElementTypeNoTypeQual(); - } - return Size; + return AST.getConstantArrayElementCount(cast(Ty)); } // Find constructor decl for a specific resource record type and binding @@ -154,9 +141,8 @@ static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST, 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(); + llvm::SmallString<64> GlobalName = { BaseName, ".str" }; + return CGM.GetAddrOfConstantCString(BaseName.str(), GlobalName.c_str()).getPointer(); } static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD, @@ -848,8 +834,7 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( } Index = Index ? CGF.Builder.CreateAdd(Index, SubIndex) : SubIndex; - ASE = dyn_cast( - getSubExprFromArrayDecayOperand(ASE->getBase())); + ASE = dyn_cast(ASE->getBase()->IgnoreParenImpCasts()); } // find binding info for the resource array @@ -862,8 +847,9 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( // lookup the resource class constructor based on the resource type and // binding + ASTContext &AST = ArrayDecl->getASTContext(); CXXConstructorDecl *CD = - findResourceConstructorDecl(ArrayDecl->getASTContext(), ResourceTy, + findResourceConstructorDecl(AST, ResourceTy, VkBinding || RBA->hasRegisterSlot()); // create a temporary variable for the resource class instance (we need to @@ -876,7 +862,7 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( AggValueSlot ValueSlot = AggValueSlot::forAddr( TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true), AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false), - AggValueSlot::MayOverlap); + AggValueSlot::DoesNotOverlap); Address ThisAddress = ValueSlot.getAddress(); llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo( @@ -884,7 +870,7 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( // get total array size (= range size) llvm::Value *Range = - llvm::ConstantInt::get(CGM.IntTy, getTotalArraySize(ResArrayTy)); + llvm::ConstantInt::get(CGM.IntTy, getTotalArraySize(AST, ResArrayTy)); // assemble the constructor parameters CallArgList Args; diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 0a0c2ba920dba..f782a497b3bb6 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -351,20 +351,11 @@ getResourceArrayHandleType(VarDecl *VD) { assert(VD->getType()->isHLSLResourceRecordArray() && "expected array of resource records"); const Type *Ty = VD->getType()->getUnqualifiedDesugaredType(); - while (const ConstantArrayType *CAT = dyn_cast(Ty)) { + while (const ConstantArrayType *CAT = dyn_cast(Ty)) Ty = CAT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType(); - } 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(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 @@ -3711,7 +3702,7 @@ void SemaHLSL::createResourceRecordCtorArgs(const Type *ResourceTy, HLSLResourceBindingAttr *RBA, HLSLVkBindingAttr *VkBinding, uint32_t ArrayIndex, - llvm::SmallVector &Args) { + llvm::SmallVectorImpl &Args) { std::optional RegisterSlot; uint32_t SpaceNo = 0; if (VkBinding) { @@ -3776,7 +3767,7 @@ bool SemaHLSL::initGlobalResourceArrayDecl(VarDecl *VD) { // for the specific resource type is instantiated, so codegen can emit a call // to it when the array element is accessed. SmallVector Args; - QualType ResElementTy = getArrayElementType(VD->getType()); + QualType ResElementTy = VD->getASTContext().getBaseElementType(VD->getType()); createResourceRecordCtorArgs(ResElementTy.getTypePtr(), VD->getName(), VD->getAttr(), VD->getAttr(), 0, Args); From 1600414290e43e1ba5790ea707014f7b66320ea9 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Tue, 12 Aug 2025 14:30:21 -0700 Subject: [PATCH 12/16] clang-format --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 10 +++++----- clang/lib/Sema/SemaHLSL.cpp | 10 ++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index e962e2f06afa3..1c947f36347ec 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -141,8 +141,9 @@ static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST, static Value *buildNameForResource(llvm::StringRef BaseName, CodeGenModule &CGM) { - llvm::SmallString<64> GlobalName = { BaseName, ".str" }; - return CGM.GetAddrOfConstantCString(BaseName.str(), GlobalName.c_str()).getPointer(); + llvm::SmallString<64> GlobalName = {BaseName, ".str"}; + return CGM.GetAddrOfConstantCString(BaseName.str(), GlobalName.c_str()) + .getPointer(); } static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD, @@ -848,9 +849,8 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( // lookup the resource class constructor based on the resource type and // binding ASTContext &AST = ArrayDecl->getASTContext(); - CXXConstructorDecl *CD = - findResourceConstructorDecl(AST, ResourceTy, - VkBinding || RBA->hasRegisterSlot()); + CXXConstructorDecl *CD = findResourceConstructorDecl( + AST, ResourceTy, VkBinding || RBA->hasRegisterSlot()); // create a temporary variable for the resource class instance (we need to // return an LValue) diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index f782a497b3bb6..f87715950c74c 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -3697,12 +3697,10 @@ static bool initVarDeclWithCtor(Sema &S, VarDecl *VD, return true; } -void SemaHLSL::createResourceRecordCtorArgs(const Type *ResourceTy, - StringRef VarName, - HLSLResourceBindingAttr *RBA, - HLSLVkBindingAttr *VkBinding, - uint32_t ArrayIndex, - llvm::SmallVectorImpl &Args) { +void SemaHLSL::createResourceRecordCtorArgs( + const Type *ResourceTy, StringRef VarName, HLSLResourceBindingAttr *RBA, + HLSLVkBindingAttr *VkBinding, uint32_t ArrayIndex, + llvm::SmallVectorImpl &Args) { std::optional RegisterSlot; uint32_t SpaceNo = 0; if (VkBinding) { From 00341fa79cb84d01ce34e73f4056d6508cb3abb0 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Tue, 12 Aug 2025 15:07:30 -0700 Subject: [PATCH 13/16] Add note to tests --- .../CodeGenHLSL/resources/res-array-global-dyn-index.hlsl | 3 +++ .../CodeGenHLSL/resources/res-array-global-multi-dim.hlsl | 8 ++++++++ clang/test/CodeGenHLSL/resources/res-array-global.hlsl | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl index 26108aadff958..5b62452ef9844 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl @@ -8,6 +8,9 @@ RWStructuredBuffer Out; // Make sure A[GI.x][GI.y] is translated to a RWBuffer constructor call with range 12 and dynamically calculated index +// NOTE: +// Constructor call for explicit binding has "jjij" in the mangled name and the arguments are (register, space, range_size, index, name). + // CHECK: define internal void @_Z4mainDv3_j(<3 x i32> noundef %GI) // CHECK: %[[GI_alloca:.*]] = alloca <3 x i32>, align 16 // CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl index 5219cf5143c13..8d664dad58153 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl @@ -23,6 +23,14 @@ void main() { // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer // CHECK: %[[Tmp3:.*]] = alloca %"class.hlsl::RWBuffer + // NOTE: + // Constructor call for explicit binding has "jjij" in the mangled name and the arguments are (register, space, range_size, index, name). + // For implicit binding the constructor has "jijj" in the mangled name and the arguments are (space, range_size, index, order_id, name). + // The range_size can be -1 for unbounded arrays, and that is the only signed int in the signature. + // The order_id argument is a sequential number that is assigned to resources with implicit binding and corresponds to the order in which + // the resources were declared. It is needed because implicit bindings are assigned later on in an LLVM pass that needs to know the order + // of the resource declarations. + // Make sure that B[3][2] is translated to a RWBuffer constructor call for explicit binding (u2, space0) with range 16 and index 14 // CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 2, i32 noundef 0, i32 noundef 16, i32 noundef 14, ptr noundef @[[BufB]]) diff --git a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl index cc0e8cca19953..98aed10cb57f0 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl @@ -32,6 +32,14 @@ void main() { // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer // CHECK: %[[Tmp3:.*]] = alloca %"class.hlsl::RWBuffer + // NOTE: + // Constructor call for explicit binding has "jjij" in the mangled name and the arguments are (register, space, range_size, index, name). + // For implicit binding the constructor has "jijj" in the mangled name and the arguments are (space, range_size, index, order_id, name). + // The range_size can be -1 for unbounded arrays, and that is the only signed int in the signature. + // The order_id argument is a sequential number that is assigned to resources with implicit binding and corresponds to the order in which + // the resources were declared. It is needed because implicit bindings are assigned later on in an LLVM pass that needs to know the order + // of the resource declarations. + // Make sure A[2] is translated to a RWBuffer constructor call with range 4 and index 2 // and DXIL explicit binding (u10, space1) // and SPIR-V explicit binding (binding 12, set 2) From 150a452d8c0ac158f0b922427fa819895e875591 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Tue, 12 Aug 2025 15:25:48 -0700 Subject: [PATCH 14/16] Add test for multi-dimensional local resource array & add notes to tests --- .../resources/res-array-local-multi-dim.hlsl | 49 +++++++++++++++++++ .../resources/res-array-local1.hlsl | 2 + .../resources/res-array-local2.hlsl | 3 ++ .../resources/res-array-local3.hlsl | 2 + 4 files changed, 56 insertions(+) create mode 100644 clang/test/CodeGenHLSL/resources/res-array-local-multi-dim.hlsl diff --git a/clang/test/CodeGenHLSL/resources/res-array-local-multi-dim.hlsl b/clang/test/CodeGenHLSL/resources/res-array-local-multi-dim.hlsl new file mode 100644 index 0000000000000..d803882fd2f72 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-local-multi-dim.hlsl @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// This test verifies handling of multi-dimensional local arrays of resources +// when used as a function argument and local variable. + +// CHECK: @_ZL1A = internal global %"class.hlsl::RWBuffer" poison, align 4 +// CHECK: @_ZL1B = internal global %"class.hlsl::RWBuffer" poison, align 4 + +RWBuffer A : register(u10); +RWBuffer B : register(u20); +RWStructuredBuffer Out; + +// NOTE: _ZN4hlsl8RWBufferIfEixEj is the subscript operator for RWBuffer and +// _ZN4hlsl18RWStructuredBufferIfEixEj is the subscript operator for RWStructuredBuffer + +// CHECK: define {{.*}} float @_Z3fooA2_A2_N4hlsl8RWBufferIfEE(ptr noundef byval([2 x [2 x %"class.hlsl::RWBuffer"]]) align 4 %Arr) +// CHECK-NEXT: entry: +float foo(RWBuffer Arr[2][2]) { +// CHECK-NEXT: %[[Arr_1_Ptr:.*]] = getelementptr inbounds [2 x [2 x %"class.hlsl::RWBuffer"]], ptr %Arr, i32 0, i32 1 +// CHECK-NEXT: %[[Arr_1_1_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %[[Arr_1_Ptr]], i32 0, i32 1 +// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[Arr_1_1_Ptr]], i32 noundef 0) +// CHECK-NEXT: %[[Value:.*]] = load float, ptr %[[BufPtr]], align 4 +// CHECK-NEXT: ret float %[[Value]] + return Arr[1][1][0]; +} + +// CHECK: define internal void @_Z4mainv() +// CHECK-NEXT: entry: +[numthreads(4,1,1)] +void main() { +// CHECK-NEXT: %L = alloca [2 x [2 x %"class.hlsl::RWBuffer"]], align 4 +// CHECK-NEXT: %[[Tmp:.*]] = alloca [2 x [2 x %"class.hlsl::RWBuffer"]], align 4 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %L, ptr align 4 @_ZL1A, i32 4, i1 false) +// CHECK-NEXT: %[[Ptr1:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %L, i32 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr1]], ptr align 4 @_ZL1B, i32 4, i1 false) +// CHECK-NEXT: %[[Ptr2:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %L, i32 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr2]], ptr align 4 @_ZL1A, i32 4, i1 false) +// CHECK-NEXT: %[[Ptr3:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %[[Ptr2]], i32 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr3]], ptr align 4 @_ZL1B, i32 4, i1 false) + RWBuffer L[2][2] = { { A, B }, { A, B } }; + +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp]], ptr align 4 %L, i32 16, i1 false) +// CHECK-NEXT: %[[ReturnedValue:.*]] = call {{.*}}float @_Z3fooA2_A2_N4hlsl8RWBufferIfEE(ptr noundef byval([2 x [2 x %"class.hlsl::RWBuffer"]]) align 4 %[[Tmp]]) +// CHECK-NEXT: %[[OutBufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl18RWStructuredBufferIfEixEj(ptr {{.*}} @_ZL3Out, i32 noundef 0) +// CHECK-NEXT: store float %[[ReturnedValue]], ptr %[[OutBufPtr]], align 4 +// CHECK-NEXT: ret void + Out[0] = foo(L); +} diff --git a/clang/test/CodeGenHLSL/resources/res-array-local1.hlsl b/clang/test/CodeGenHLSL/resources/res-array-local1.hlsl index 3257454484794..c0d508b1395c3 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-local1.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-local1.hlsl @@ -46,6 +46,8 @@ void main() { // CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr3]], ptr align 4 @_ZL1C, i32 4, i1 false) Second[2] = C; + // NOTE: _ZN4hlsl8RWBufferIfEixEj is the subscript operator for RWBuffer + // get First[1][0] value // CHECK: %[[First_1_Ptr:.*]] = getelementptr inbounds [3 x %"class.hlsl::RWBuffer"], ptr %First, i32 0, i32 1 // CHECK: %[[BufPtr1:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[First_1_Ptr]], i32 noundef 0) diff --git a/clang/test/CodeGenHLSL/resources/res-array-local2.hlsl b/clang/test/CodeGenHLSL/resources/res-array-local2.hlsl index e51bf71119914..39f3aeb66ceb5 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-local2.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-local2.hlsl @@ -8,6 +8,9 @@ RWBuffer A[3] : register(u0); RWStructuredBuffer Out : register(u0); +// NOTE: _ZN4hlsl8RWBufferIfEixEj is the subscript operator for RWBuffer and +// _ZN4hlsl18RWStructuredBufferIfEixEj is the subscript operator for RWStructuredBuffer + // CHECK: define {{.*}} float @_Z3fooA3_N4hlsl8RWBufferIfEE(ptr noundef byval([3 x %"class.hlsl::RWBuffer"]) align 4 %LocalA) // CHECK-NEXT: entry: float foo(RWBuffer LocalA[3]) { diff --git a/clang/test/CodeGenHLSL/resources/res-array-local3.hlsl b/clang/test/CodeGenHLSL/resources/res-array-local3.hlsl index 4b7eee99b6b6f..e5bcdc651254f 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-local3.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-local3.hlsl @@ -23,6 +23,8 @@ void SomeFn(RWBuffer B[2], uint Idx, int Val0) { // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[B_0_Ptr]], ptr align 4 @_ZL1Y, i32 4, i1 false) B[0] = Y; +// NOTE: _ZN4hlsl8RWBufferIiEixEj is the subscript operator for RWBuffer + // CHECK-NEXT: %[[Val0:.*]] = load i32, ptr %[[Val0_addr]], align 4 // CHECK-NEXT: %[[B_0_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %B, i32 0, i32 0 // CHECK-NEXT: %[[Idx:.*]] = load i32, ptr %[[Idx_addr]], align 4 From d1b3e3ab38814cdf3218c2347574ff8e2247ce77 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Fri, 15 Aug 2025 16:48:37 -0700 Subject: [PATCH 15/16] Update resource index calculation & fix formatting --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 1c947f36347ec..062c169d6a440 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -812,6 +812,7 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( "indexing of array subsets it not supported yet"); // get the resource array type + ASTContext &AST = ArrayDecl->getASTContext(); const Type *ResArrayTy = ArrayDecl->getType().getTypePtr(); assert(ResArrayTy->isHLSLResourceRecordArray() && "expected array of resource classes"); @@ -821,16 +822,13 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( // 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(ASE->getType().getTypePtr())) { - Value *SubMultiplier = - llvm::ConstantInt::get(CGM.IntTy, ArrayTy->getSExtSize()); - Multiplier = Multiplier ? CGF.Builder.CreateMul(Multiplier, SubMultiplier) - : SubMultiplier; + Value *Multiplier = llvm::ConstantInt::get( + CGM.IntTy, AST.getConstantArrayElementCount(ArrayTy)); SubIndex = CGF.Builder.CreateMul(SubIndex, Multiplier); } @@ -838,9 +836,8 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( ASE = dyn_cast(ASE->getBase()->IgnoreParenImpCasts()); } - // find binding info for the resource array - // (for implicit binding an HLSLResourceBindingAttr should have been added by - // SemaHLSL) + // 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(); HLSLResourceBindingAttr *RBA = ArrayDecl->getAttr(); @@ -848,17 +845,16 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( // lookup the resource class constructor based on the resource type and // binding - ASTContext &AST = ArrayDecl->getASTContext(); CXXConstructorDecl *CD = findResourceConstructorDecl( AST, 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 (CGF.EmitLifetimeStart(TmpVar.getPointer())) { + if (CGF.EmitLifetimeStart(TmpVar.getPointer())) CGF.pushFullExprCleanup( NormalEHLifetimeMarker, TmpVar); - } + AggValueSlot ValueSlot = AggValueSlot::forAddr( TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true), AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false), From 9b54bbd351281460621feb4f1fe57e7709f11739 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Mon, 18 Aug 2025 16:30:22 -0700 Subject: [PATCH 16/16] code review feedback - use std::array, llvm_unreachanble, add test, fix typo --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 13 +++++++------ .../CodeGenHLSL/resources/res-array-global.hlsl | 11 ++++++++++- .../CodeGenHLSL/resources/resource-bindings.hlsl | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 062c169d6a440..12ece217ba563 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -117,7 +117,7 @@ static int getTotalArraySize(ASTContext &AST, const clang::Type *Ty) { static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST, QualType ResTy, bool ExplicitBinding) { - SmallVector ExpParmTypes = { + std::array ExpParmTypes = { AST.UnsignedIntTy, AST.UnsignedIntTy, AST.UnsignedIntTy, AST.UnsignedIntTy, AST.getPointerType(AST.CharTy.withConst())}; ExpParmTypes[ExplicitBinding ? 2 : 1] = AST.IntTy; @@ -126,8 +126,8 @@ static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST, for (auto *Ctor : ResDecl->ctors()) { if (Ctor->getNumParams() != ExpParmTypes.size()) continue; - ParmVarDecl **ParmIt = Ctor->param_begin(); - QualType *ExpTyIt = ExpParmTypes.begin(); + auto *ParmIt = Ctor->param_begin(); + auto ExpTyIt = ExpParmTypes.begin(); for (; ParmIt != Ctor->param_end() && ExpTyIt != ExpParmTypes.end(); ++ParmIt, ++ExpTyIt) { if ((*ParmIt)->getType() != *ExpTyIt) @@ -807,9 +807,10 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( 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"); + if (ArraySubsExpr->getType()->isArrayType()) + // FIXME: this is not yet implemented (llvm/llvm-project#145426) + llvm_unreachable( + "indexing of sub-arrays of multidimensional arrays not supported yet"); // get the resource array type ASTContext &AST = ArrayDecl->getASTContext(); diff --git a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl index 98aed10cb57f0..595ea818e913b 100644 --- a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl +++ b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl @@ -7,6 +7,7 @@ // CHECK: @[[BufB:.*]] = private unnamed_addr constant [2 x i8] c"B\00", align 1 // CHECK: @[[BufC:.*]] = private unnamed_addr constant [2 x i8] c"C\00", align 1 // CHECK: @[[BufD:.*]] = private unnamed_addr constant [2 x i8] c"D\00", align 1 +// CHECK: @[[BufE:.*]] = private unnamed_addr constant [2 x i8] c"E\00", align 1 // different explicit binding for DXIL and SPIR-V [[vk::binding(12, 2)]] @@ -22,6 +23,9 @@ RWBuffer C[3] : register(u2); // implicit binding for both DXIL and SPIR-V in space/set 0 RWBuffer D[10]; +// implicit binding for both DXIL and SPIR-V with specified space/set 0 +RWBuffer E[15] : register(space2); + RWStructuredBuffer Out; [numthreads(4,1,1)] @@ -31,6 +35,7 @@ void main() { // CHECK: %[[Tmp1:.*]] = alloca %"class.hlsl::RWBuffer // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer // CHECK: %[[Tmp3:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp4:.*]] = alloca %"class.hlsl::RWBuffer // NOTE: // Constructor call for explicit binding has "jjij" in the mangled name and the arguments are (register, space, range_size, index, name). @@ -62,5 +67,9 @@ void main() { // for both DXIL and SPIR-V // DXIL: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 1, ptr noundef @[[BufD]]) // SPV: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 0, ptr noundef @[[BufD]]) - Out[0] = A[2][0] + (float)B[3][0] + (float)C[1][0] + (float)D[7][0]; + + // Make sure E[5][0] is translated to RWBuffer constructor call with implicit binding and specified space/set 2 + // DXIL: call void @_ZN4hlsl8RWBufferIjEC1EjijjPKc(ptr {{.*}} %[[Tmp4]], i32 noundef 2, i32 noundef 15, i32 noundef 5, i32 noundef 2, ptr noundef @[[BufE]]) + // SPV: call void @_ZN4hlsl8RWBufferIjEC1EjijjPKc(ptr {{.*}} %[[Tmp4]], i32 noundef 2, i32 noundef 15, i32 noundef 5, i32 noundef 1, ptr noundef @[[BufE]]) + Out[0] = A[2][0] + (float)B[3][0] + (float)C[1][0] + (float)D[7][0] + (float)E[5][0]; } diff --git a/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl b/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl index 2d49753e546ca..2a6e51d765bd1 100644 --- a/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl +++ b/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl @@ -44,7 +44,7 @@ struct S { }; RWStructuredBuffer T3S0 : register(u3); -// Resource array elements are initialized on access; make sure there is not call +// Resource array elements are initialized on access; make sure there is no call // to initialize RWBuffer. // CHECK-NOT: call target("dx.TypedBuffer", double, 1, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f64_1_0_0t( RWBuffer Array[10] : register(u4, space0);