diff --git a/clang/include/clang/AST/HLSLResource.h b/clang/include/clang/AST/HLSLResource.h index a37acb3660d00..585435ec1dd33 100644 --- a/clang/include/clang/AST/HLSLResource.h +++ b/clang/include/clang/AST/HLSLResource.h @@ -109,6 +109,21 @@ inline uint32_t getResourceDimensions(llvm::dxil::ResourceDimension Dim) { llvm_unreachable("Unhandled llvm::dxil::ResourceDimension enum."); } +// Returns true if the second field of the record is a counter resource handle +inline bool hasCounterHandle(const CXXRecordDecl *RD) { + if (RD->field_empty()) + return false; + auto It = std::next(RD->field_begin()); + if (It == RD->field_end()) + return false; + const FieldDecl *SecondField = *It; + if (const auto *ResTy = + SecondField->getType()->getAs()) { + return ResTy->getAttrs().IsCounter; + } + return false; +} + // Helper class for building a name of a global resource variable that // gets created for a resource embedded in a struct or class. This will // also be used from CodeGen to build a name that matches the resource @@ -128,6 +143,7 @@ class EmbeddedResourceNameBuilder { void pushName(llvm::StringRef N) { pushName(N, FieldDelim); } void pushBaseName(llvm::StringRef N); void pushArrayIndex(uint64_t Index); + void pushBaseNameHierarchy(CXXRecordDecl *DerivedRD, CXXRecordDecl *BaseRD); void pop() { assert(!Offsets.empty() && "no name to pop"); diff --git a/clang/lib/AST/HLSLResource.cpp b/clang/lib/AST/HLSLResource.cpp index 9b311c005df5c..204acfa22b8b7 100644 --- a/clang/lib/AST/HLSLResource.cpp +++ b/clang/lib/AST/HLSLResource.cpp @@ -41,5 +41,20 @@ void EmbeddedResourceNameBuilder::pushArrayIndex(uint64_t Index) { OS << Index; } +void EmbeddedResourceNameBuilder::pushBaseNameHierarchy( + CXXRecordDecl *DerivedRD, CXXRecordDecl *BaseRD) { + assert(BaseRD != DerivedRD && DerivedRD->isDerivedFrom(BaseRD)); + Offsets.push_back(Name.size()); + Name.append(FieldDelim); + while (BaseRD != DerivedRD) { + assert(DerivedRD->getNumBases() == 1 && + "HLSL does not support multiple inheritance"); + DerivedRD = DerivedRD->bases_begin()->getType()->getAsCXXRecordDecl(); + assert(DerivedRD && "base class not found"); + Name.append(DerivedRD->getName()); + Name.append(BaseClassDelim); + } +} + } // namespace hlsl } // namespace clang diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 2ef399a7e7309..0b97f3028285d 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -5511,10 +5511,18 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) { EmitIgnoredExpr(E->getBase()); return EmitDeclRefLValue(DRE); } - if (getLangOpts().HLSL && - E->getType().getAddressSpace() == LangAS::hlsl_constant) { - // We have an HLSL buffer - emit using HLSL's layout rules. - return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E); + + if (getLangOpts().HLSL) { + QualType QT = E->getType(); + if (QT.getAddressSpace() == LangAS::hlsl_constant) + return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E); + + if (QT->isHLSLResourceRecord() || QT->isHLSLResourceRecordArray()) { + std::optional LV; + LV = CGM.getHLSLRuntime().emitResourceMemberExpr(*this, E); + if (LV.has_value()) + return *LV; + } } Expr *BaseExpr = E->getBase(); diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 6182663111f5a..e7b4088361461 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -96,16 +96,112 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion RootSigVer, RootSignatureValMD->addOperand(MDVals); } +// Given a MemberExpr of a resource or resource array type, find the parent +// VarDecl of the struct or class instance that contains this resource and +// build the full resource name based on the member access path. +// +// For example, for a member access like "myStructArray[0].memberA", +// this function will find the VarDecl of "myStructArray" and use the +// EmbeddedResourceNameBuilder to build the resource name +// "myStructArray.0.memberA". +static const VarDecl *findStructResourceParentDeclAndBuildName( + const MemberExpr *ME, EmbeddedResourceNameBuilder &NameBuilder) { + + SmallVector WorkList; + const VarDecl *VD = nullptr; + const Expr *E = ME; + + for (;;) { + if (const auto *DRE = dyn_cast(E)) { + assert(isa(DRE->getDecl()) && + "member expr base is not a var decl"); + VD = cast(DRE->getDecl()); + NameBuilder.pushName(VD->getName()); + break; + } + + WorkList.push_back(E); + if (const auto *MExp = dyn_cast(E)) + E = MExp->getBase(); + else if (const auto *ICE = dyn_cast(E)) + E = ICE->getSubExpr(); + else if (const auto *ASE = dyn_cast(E)) + E = ASE->getBase(); + else if (isa(E)) + // Resource member access on "this" pointer not yet implemented + // (llvm/llvm-project#190299) + return nullptr; + else + llvm_unreachable("unexpected expr type in resource member access"); + + assert(E && "expected valid expression"); + } + + while (!WorkList.empty()) { + E = WorkList.pop_back_val(); + if (const auto *ME = dyn_cast(E)) { + NameBuilder.pushName( + ME->getMemberNameInfo().getName().getAsIdentifierInfo()->getName()); + } else if (const auto *ICE = dyn_cast(E)) { + if (ICE->getCastKind() == CK_UncheckedDerivedToBase) { + CXXRecordDecl *DerivedRD = + ICE->getSubExpr()->getType()->getAsCXXRecordDecl(); + CXXRecordDecl *BaseRD = ICE->getType()->getAsCXXRecordDecl(); + NameBuilder.pushBaseNameHierarchy(DerivedRD, BaseRD); + } + } else if (const auto *ASE = dyn_cast(E)) { + const Expr *IdxExpr = ASE->getIdx(); + std::optional Value = + IdxExpr->getIntegerConstantExpr(VD->getASTContext()); + assert(Value && + "expected constant index in struct with resource array access"); + NameBuilder.pushArrayIndex(Value->getZExtValue()); + } else { + llvm_unreachable("unexpected expr type in resource member access"); + } + } + return VD; +} + +// Given a MemberExpr of a resource or resource array type, find the +// corresponding global resource declaration associated with the owning struct +// or class instance via HLSLAssociatedResourceDeclAttr. +static const VarDecl * +findAssociatedResourceDeclForStruct(ASTContext &AST, const MemberExpr *ME) { + + EmbeddedResourceNameBuilder NameBuilder; + const VarDecl *ParentVD = + findStructResourceParentDeclAndBuildName(ME, NameBuilder); + if (!ParentVD) + return nullptr; + + if (!ParentVD->hasGlobalStorage()) + return nullptr; + + IdentifierInfo *II = NameBuilder.getNameAsIdentifier(AST); + for (const Attr *A : ParentVD->getAttrs()) { + if (const auto *ADA = dyn_cast(A)) { + VarDecl *AssocResVD = ADA->getResDecl(); + if (AssocResVD->getIdentifier() == II) + return AssocResVD; + } + } + return nullptr; +} + // Find array variable declaration from DeclRef expression -static const ValueDecl *getArrayDecl(const Expr *E) { - if (const DeclRefExpr *DRE = - dyn_cast_or_null(E->IgnoreImpCasts())) +static const ValueDecl *getArrayDecl(ASTContext &AST, const Expr *E) { + E = E->IgnoreImpCasts(); + if (const auto *DRE = dyn_cast_or_null(E)) return DRE->getDecl(); + if (isa(E)) + return findAssociatedResourceDeclForStruct(AST, cast(E)); return nullptr; } // Find array variable declaration from nested array subscript AST nodes -static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) { +static const ValueDecl *getArrayDecl(ASTContext &AST, + const ArraySubscriptExpr *ASE) { const Expr *E = nullptr; while (ASE != nullptr) { E = ASE->getBase()->IgnoreImpCasts(); @@ -113,7 +209,7 @@ static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) { return nullptr; ASE = dyn_cast(E); } - return getArrayDecl(E); + return getArrayDecl(AST, E); } // Get the total size of the array, or 0 if the array is unbounded. @@ -152,6 +248,10 @@ static CXXMethodDecl *lookupResourceInitMethodAndSetupArgs( Value *NameStr = buildNameForResource(Name, CGM); Value *Space = llvm::ConstantInt::get(CGM.IntTy, Binding.getSpace()); + bool HasCounter = hasCounterHandle(ResourceDecl); + assert((!HasCounter || Binding.hasCounterImplicitOrderID()) && + "resources with counter handle must have a binding with counter " + "implicit order ID"); if (Binding.isExplicit()) { // explicit binding auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, Binding.getSlot()); @@ -174,7 +274,7 @@ static CXXMethodDecl *lookupResourceInitMethodAndSetupArgs( Args.add(RValue::get(Range), AST.IntTy); Args.add(RValue::get(Index), AST.UnsignedIntTy); Args.add(RValue::get(NameStr), AST.getPointerType(AST.CharTy.withConst())); - if (Binding.hasCounterImplicitOrderID()) { + if (HasCounter) { uint32_t CounterBinding = Binding.getCounterImplicitOrderID(); auto *CounterOrderID = llvm::ConstantInt::get(CGM.IntTy, CounterBinding); Args.add(RValue::get(CounterOrderID), AST.UnsignedIntTy); @@ -1236,8 +1336,8 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( // Let clang codegen handle local and static resource array subscripts, // or when the subscript references on opaque expression (as part of // ArrayInitLoopExpr AST node). - const VarDecl *ArrayDecl = - dyn_cast_or_null(getArrayDecl(ArraySubsExpr)); + const VarDecl *ArrayDecl = dyn_cast_or_null( + getArrayDecl(CGF.CGM.getContext(), ArraySubsExpr)); if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() || ArrayDecl->getStorageClass() == SC_Static) return std::nullopt; @@ -1302,11 +1402,15 @@ std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( CGF.CGM, ResourceTy->getAsCXXRecordDecl(), Range, Index, ArrayDecl->getName(), Binding, Args); - if (!CreateMethod) + if (!CreateMethod) { // This can happen if someone creates an array of structs that looks like // an HLSL resource record array but it does not have the required static // create method. No binding will be generated for it. + assert(!ResourceTy->getAsCXXRecordDecl()->isImplicit() && + "create method lookup should always succeed for built-in resource " + "records"); return std::nullopt; + } callResourceInitMethod(CGF, CreateMethod, Args, ValueSlot.getAddress()); @@ -1334,7 +1438,8 @@ bool CGHLSLRuntime::emitResourceArrayCopy(LValue &LHS, Expr *RHSExpr, assert(ResultTy->isHLSLResourceRecordArray() && "expected resource array"); // Let Clang codegen handle local and static resource array copies. - const VarDecl *ArrayDecl = dyn_cast_or_null(getArrayDecl(RHSExpr)); + const VarDecl *ArrayDecl = + dyn_cast_or_null(getArrayDecl(CGF.CGM.getContext(), RHSExpr)); if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() || ArrayDecl->getStorageClass() == SC_Static) return false; @@ -1457,6 +1562,37 @@ std::optional CGHLSLRuntime::emitBufferArraySubscriptExpr( return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo); } +std::optional +CGHLSLRuntime::emitResourceMemberExpr(CodeGenFunction &CGF, + const MemberExpr *ME) { + assert((ME->getType()->isHLSLResourceRecord() || + ME->getType()->isHLSLResourceRecordArray()) && + "expected resource member expression"); + + if (ME->getType()->isHLSLResourceRecordArray()) { + // FIXME: Handle member access of the whole array of resources + // (llvm/llvm-project#187087). Access to individual resource array elements + // is already handled in emitResourceArraySubscriptExpr. + return std::nullopt; + } + + const VarDecl *ResourceVD = + findAssociatedResourceDeclForStruct(CGF.CGM.getContext(), ME); + if (!ResourceVD) + return std::nullopt; + + GlobalVariable *ResGV = + cast(CGM.GetAddrOfGlobalVar(ResourceVD)); + const DataLayout &DL = CGM.getDataLayout(); + llvm::Type *Ty = ResGV->getValueType(); + CharUnits Align = CharUnits::fromQuantity(DL.getABITypeAlign(Ty)); + Address Addr = Address(ResGV, Ty, Align); + LValue LV = LValue::MakeAddr(Addr, ME->getType(), CGM.getContext(), + LValueBaseInfo(AlignmentSource::Type), + CGM.getTBAAAccessInfo(ME->getType())); + return LV; +} + namespace { /// Utility for emitting copies following the HLSL buffer layout rules (ie, /// copying out of a cbuffer). diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index b1c5b3318a11e..2e6ec22097d36 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -304,6 +304,8 @@ class CGHLSLRuntime { QualType CType); LValue emitBufferMemberExpr(CodeGenFunction &CGF, const MemberExpr *E); + std::optional emitResourceMemberExpr(CodeGenFunction &CGF, + const MemberExpr *E); private: void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl, diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index a943303149931..c80925486106b 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -1489,20 +1489,6 @@ static CXXMethodDecl *lookupMethod(Sema &S, CXXRecordDecl *RecordDecl, } // end anonymous namespace -static bool hasCounterHandle(const CXXRecordDecl *RD) { - if (RD->field_empty()) - return false; - auto It = std::next(RD->field_begin()); - if (It == RD->field_end()) - return false; - const FieldDecl *SecondField = *It; - if (const auto *ResTy = - SecondField->getType()->getAs()) { - return ResTy->getAttrs().IsCounter; - } - return false; -} - bool SemaHLSL::handleRootSignatureElements( ArrayRef Elements) { // Define some common error handling functions @@ -5059,7 +5045,7 @@ class StructBindingContext { // Creates a binding attribute for a resource based on the gathered attributes // and the required register type and range. Attr *createBindingAttr(SemaHLSL &S, ASTContext &AST, RegisterType RegType, - unsigned Range) { + unsigned Range, bool HasCounter) { assert(static_cast(RegType) < 4 && "unexpected register type"); if (VkBindingAttr) { @@ -5094,6 +5080,9 @@ class StructBindingContext { RBA ? RBA->getSpaceNumber() : 0); NewAttr->setImplicitBindingOrderID(S.getNextImplicitBindingOrderID()); } + if (HasCounter) + NewAttr->setImplicitCounterBindingOrderID( + S.getNextImplicitBindingOrderID()); return NewAttr; } }; @@ -5115,18 +5104,20 @@ static void createGlobalResourceDeclForStruct( VarDecl::Create(AST, DC, Loc, Loc, Id, ResTy, nullptr, SC_None); unsigned Range = 1; - const HLSLAttributedResourceType *ResHandleTy = nullptr; - if (const auto *AT = dyn_cast(ResTy.getTypePtr())) { + const Type *SingleResTy = ResTy.getTypePtr()->getUnqualifiedDesugaredType(); + while (const auto *AT = dyn_cast(SingleResTy)) { const auto *CAT = dyn_cast(AT); - Range = CAT ? CAT->getSize().getZExtValue() : 0; - ResHandleTy = getResourceArrayHandleType(ResTy); - } else { - ResHandleTy = HLSLAttributedResourceType::findHandleTypeOnResource( - ResTy.getTypePtr()); + Range = CAT ? (Range * CAT->getSize().getZExtValue()) : 0; + SingleResTy = + AT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType(); } + const HLSLAttributedResourceType *ResHandleTy = + HLSLAttributedResourceType::findHandleTypeOnResource(SingleResTy); + // Add a binding attribute to the global resource declaration. + bool HasCounter = hasCounterHandle(SingleResTy->getAsCXXRecordDecl()); Attr *BindingAttr = BindingCtx.createBindingAttr( - S.HLSL(), AST, getRegisterType(ResHandleTy), Range); + S.HLSL(), AST, getRegisterType(ResHandleTy), Range, HasCounter); ResDecl->addAttr(BindingAttr); ResDecl->addAttr(InternalLinkageAttr::CreateImplicit(AST)); ResDecl->setImplicit(); @@ -5365,11 +5356,15 @@ bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { CreateMethod = lookupMethod(SemaRef, ResourceDecl, CreateMethodName, VD->getLocation()); - if (!CreateMethod) + if (!CreateMethod) { // This can happen if someone creates a struct that looks like an HLSL // resource record but does not have the required static create method. // No binding will be generated for it. + assert(!ResourceDecl->isImplicit() && + "create method lookup should always succeed for built-in resource " + "records"); return false; + } if (Binding.isExplicit()) { IntegerLiteral *RegSlot = diff --git a/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl b/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl new file mode 100644 index 0000000000000..e57cceaaf2a01 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl @@ -0,0 +1,100 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s + +// CHECK: %"class.hlsl::RWBuffer" = type { target("dx.TypedBuffer", float, 1, 0, 0) } + +// Array of structs with resources +struct A { + RWBuffer Buf; +}; + +// CHECK: @arrayOfA.0.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[arrayOfA0BufStr:.*]] = private unnamed_addr constant [15 x i8] c"arrayOfA.0.Buf\00" +// CHECK: @arrayOfA.1.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[arrayOfA1BufStr:.*]] = private unnamed_addr constant [15 x i8] c"arrayOfA.1.Buf\00" + +[[vk::binding(0, 1)]] +A arrayOfA[2] : register(u0, space1); + +// Nested struct arrays with resources +struct G { + A multiArray[2][2]; +}; + +// CHECK: @gArray.0.multiArray.0.0.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray0MultiArray00BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.0.0.Buf\00" +// CHECK: @gArray.0.multiArray.0.1.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray0MultiArray01BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.0.1.Buf\00" +// CHECK: @gArray.0.multiArray.1.0.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray0MultiArray10BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.1.0.Buf\00" +// CHECK: @gArray.0.multiArray.1.1.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray0MultiArray11BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.1.1.Buf\00" +// CHECK: @gArray.1.multiArray.0.0.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray1MultiArray00BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.0.0.Buf\00" +// CHECK: @gArray.1.multiArray.0.1.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray1MultiArray01BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.0.1.Buf\00" +// CHECK: @gArray.1.multiArray.1.0.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray1MultiArray10BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.1.0.Buf\00" +// CHECK: @gArray.1.multiArray.1.1.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[gArray1MultiArray11BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.1.1.Buf\00" + +[[vk::binding(10, 2)]] +G gArray[2] : register(u10, space2); + +// Array of structs that contain a resource array +struct B { + RWStructuredBuffer ManyBufs[2]; +}; + +B bArray[2]; + +// CHECK: @[[bArray1ManyBufsStr:.*]] = private unnamed_addr constant [18 x i8] c"bArray.1.ManyBufs\00", align 1 + +// Make sure the global single resources are initialized from binding; resource arrays are initialized on access. +// +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @arrayOfA.0.Buf, +// CHECK-SAME: i32 noundef 0, i32 noundef 1, i32 noundef 1, i32 noundef 0, ptr noundef @[[arrayOfA0BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @arrayOfA.1.Buf, +// CHECK-SAME: i32 noundef 1, i32 noundef 1, i32 noundef 1, i32 noundef 0, ptr noundef @[[arrayOfA1BufStr]]) + +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.0.0.Buf, +// CHECK-SAME: i32 noundef 10, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray00BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.0.1.Buf, +// CHECK-SAME: i32 noundef 11, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray01BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.1.0.Buf, +// CHECK-SAME: i32 noundef 12, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray10BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.1.1.Buf, +// CHECK-SAME: i32 noundef 13, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray11BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.0.0.Buf, +// CHECK-SAME: i32 noundef 14, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray00BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.0.1.Buf, +// CHECK-SAME: i32 noundef 15, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray01BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.1.0.Buf, +// CHECK-SAME: i32 noundef 16, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray10BufStr]]) +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.1.1.Buf, +// CHECK-SAME: i32 noundef 17, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray11BufStr]]) + +// CHECK: define internal void @main()() +// CHECK-NEXT: entry: +[numthreads(1, 1, 1)] +void main() { +// CHECK-NEXT: %[[TMP:.*]] = alloca %"class.hlsl::RWStructuredBuffer", align 4 + +// CHECK-NEXT: %[[PTR1:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @arrayOfA.1.Buf, i32 noundef 0) +// CHECK-NEXT: store float 1.000000e+00, ptr %[[PTR1]] + arrayOfA[1].Buf[0] = 1.0f; + +// CHECK-NEXT: %[[PTR2:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @gArray.1.multiArray.1.0.Buf, i32 noundef 0) +// CHECK-NEXT: store float 2.000000e+00, ptr %[[PTR2]] + gArray[1].multiArray[1][0].Buf[0] = 2.0f; + +// CHECK-NEXT: %[[PTR3:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @gArray.0.multiArray.0.1.Buf, i32 noundef 0) +// CHECK-NEXT: store float 3.000000e+00, ptr %[[PTR3]] + gArray[0].multiArray[0][1].Buf[0] = 3.0f; + +// Resource array access - first create the resource from binding, then access the element and store to it. +// CHECK-NEXT: call void @hlsl::RWStructuredBuffer::__createFromImplicitBindingWithImplicitCounter(unsigned int, unsigned int, int, unsigned int, char const*, unsigned int) +// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 %[[TMP]], i32 noundef 2, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr noundef @bArray.1.ManyBufs.str, i32 noundef 3) +// CHECK-NEXT: %[[PTR4:.*]] = call {{.*}} ptr @hlsl::RWStructuredBuffer::operator[](unsigned int)(ptr {{.*}} %[[TMP]], i32 noundef 0) +// CHECK-NEXT: store float 4.000000e+00, ptr %[[PTR4]], align 4 + bArray[1].ManyBufs[1][0] = 4.0f; +} diff --git a/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl b/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl new file mode 100644 index 0000000000000..5b5b8270f8e86 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl @@ -0,0 +1,132 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s + +// CHECK: %"class.hlsl::RWBuffer" = type { target("dx.TypedBuffer", float, 1, 0, 0) } +// CHECK: %"class.hlsl::StructuredBuffer" = type { target("dx.RawBuffer", float, 0, 0) } +// CHECK: %"class.hlsl::SamplerState" = type { target("dx.Sampler", 0) } +// CHECK: %"class.hlsl::StructuredBuffer.0" = type { target("dx.RawBuffer", i32, 0, 0) } + +// Simple inheritance +struct A { + RWBuffer Buf; +}; + +struct C : A { + RWBuffer Buf2; +}; + +// Global variables for resources c.A::Buf and c.Buf2 +// (Looks like llvm-cxxfilt doesn't demangle names with `::`.) +// +// CHECK: @"_ZL8c.A::Buf" = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[cABufStr:.*]] = private unnamed_addr constant [9 x i8] c"c.A::Buf\00" +// CHECK: @c.Buf2 = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[cBuf2Str:.*]] = private unnamed_addr constant [7 x i8] c"c.Buf2\00" + +[[vk::binding(3)]] +C c : register(u3); + +// Global variables for resources d.A::Buf and d.A.Buf +// +// CHECK: @"_ZL8d.A::Buf" = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[dABufStr1:.*]] = private unnamed_addr constant [9 x i8] c"d.A::Buf\00" +// CHECK: @d.A.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[dABufStr2:.*]] = private unnamed_addr constant [8 x i8] c"d.A.Buf\00" + +// Inheritance with same named field +struct D : A { + A A; +}; +D d; + +// Multiple resources kinds and inheritance +class B { + StructuredBuffer SrvBufs[2]; +}; + +class E : B { +}; + +class F : E { + A a; + StructuredBuffer SrvBuf; + SamplerState Samp; +}; + +// Global variables for resources f.a.Buf, f.SrvBuf and f.Samp. +// Resource array f.E::B::SrvBufs does not have a global, it is initialized on demand. +// +// CHECK: @f.a.Buf = internal global %"class.hlsl::RWBuffer" poison +// CHECK: @[[fABufStr:.*]] = private unnamed_addr constant [8 x i8] c"f.a.Buf\00" +// CHECK: @f.SrvBuf = internal global %"class.hlsl::StructuredBuffer" poison +// CHECK: @[[fSrvBufStr:.*]] = private unnamed_addr constant [9 x i8] c"f.SrvBuf\00" +// CHECK: @f.Samp = internal global %"class.hlsl::SamplerState" poison +// CHECK: @[[fSampStr:.*]] = private unnamed_addr constant [7 x i8] c"f.Samp\00" +// CHECK: @[[fEBSrvBufStr:.*]] = private unnamed_addr constant [16 x i8] c"f.E::B::SrvBufs\00" + +[[vk::binding(10)]] +F f : register(t0) : register(u20) : register(s3); + +// Make sure they are initialized from binding +// +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @"_ZL8c.A::Buf", +// CHECK-SAME: i32 noundef 3, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[cABufStr]]) + +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @c.Buf2, +// CHECK-SAME: i32 noundef 4, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[cBuf2Str]]) + +// CHECK: call void @hlsl::RWBuffer::__createFromImplicitBinding({{.*}})(ptr {{.*}} @"_ZL8d.A::Buf", +// CHECK-SAME: i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[dABufStr1]]) + +// CHECK: call void @hlsl::RWBuffer::__createFromImplicitBinding({{.*}})(ptr {{.*}} @d.A.Buf, +// CHECK-SAME: i32 noundef 1, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[dABufStr2]]) + +// CHECK: call void @hlsl::RWBuffer::__createFromBinding({{.*}})(ptr {{.*}} @f.a.Buf, +// CHECK-SAME: i32 noundef 20, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[fABufStr]]) + +// CHECK: call void @hlsl::StructuredBuffer::__createFromBinding({{.*}})(ptr {{.*}} @f.SrvBuf, +// CHECK-SAME: i32 noundef 2, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[fSrvBufStr]]) + +// CHECK: call void @hlsl::SamplerState::__createFromBinding({{.*}})(ptr {{.*}} @f.Samp, +// CHECK-SAME: i32 noundef 3, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[fSampStr]]) + +// CHECK: define internal void @main()() +// CHECK-NEXT: entry: +[numthreads(1, 1, 1)] +void main() { +// CHECK-NEXT: %i = alloca i32 +// CHECK-NEXT: %[[TMP:.*]] = alloca %"class.hlsl::StructuredBuffer.0" +// CHECK-NEXT: %a = alloca float + +// CHECK-NEXT: %[[PTR1:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @"_ZL8c.A::Buf", i32 noundef 0) +// CHECK-NEXT: store float 0x3FF3AE1480000000, ptr %[[PTR1:]] + c.Buf[0] = 1.230f; + +// CHECK-NEXT: %[[PTR2:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @c.Buf2, i32 noundef 0) +// CHECK-NEXT: store float 0x4002B851E0000000, ptr %[[PTR2:]] + c.Buf2[0] = 2.340f; + +// CHECK-NEXT: %[[PTR3:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @"_ZL8d.A::Buf", i32 noundef 0) +// CHECK-NEXT: store float 0x400B9999A0000000, ptr %[[PTR3:]] + d.Buf[0] = 3.450f; + +// CHECK-NEXT: %[[PTR4:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @d.A.Buf, i32 noundef 0) +// CHECK-NEXT: store float 0x40123D70A0000000, ptr %[[PTR4:]] + d.A.Buf[0] = 4.560f; + +// Resource array access - initilized on demand: +// CHECK-NEXT: call void @hlsl::StructuredBuffer::__createFromBinding({{.*}})(ptr {{.*}} %[[TMP]], +// CHECK-SAME: i32 noundef 0, i32 noundef 0, i32 noundef 2, i32 noundef 0, ptr noundef @[[fEBSrvBufStr]]) +// CHECK-NEXT: %[[PTR5:.*]] = call {{.*}} ptr @hlsl::StructuredBuffer::operator[](unsigned int) const(ptr {{.*}} %[[TMP]], i32 noundef 1) +// CHECK-NEXT: %[[VAL1:.*]] = load i32, ptr %[[PTR5]] +// CHECK-NEXT: store i32 %[[VAL1]], ptr %i + int i = f.SrvBufs[0][1]; + +// CHECK-NEXT: %[[PTR6:.*]] = call {{.*}} ptr @hlsl::StructuredBuffer::operator[](unsigned int) const({{.*}} @f.SrvBuf, i32 noundef 0) +// CHECK-NEXT: %[[VAL2:.*]] = load float, ptr %[[PTR6]] +// CHECK-NEXT: store float %[[VAL2]], ptr %a + float a = f.SrvBuf[0]; + +// CHECK: [[PTR7:.*]]= call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} @f.a.Buf, i32 noundef 0) +// CHECK: store float %{{.*}}, ptr %call6 + f.a.Buf[0] = (float)i + a; +} diff --git a/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl b/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl new file mode 100644 index 0000000000000..3502f1d7eca66 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl @@ -0,0 +1,83 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s + +// CHECK: %"class.hlsl::RWBuffer" = type { target("dx.TypedBuffer", float, 1, 0, 0) } + +// Single resource field in struct. +struct A { + RWBuffer Buf; +}; + +// Global variable for resource a.Buf +// +// CHECK-DAG: @a.Buf = internal global %"class.hlsl::RWBuffer" poison, align 4 +// CHECK-DAG: @[[aBufStr:.*]] = private unnamed_addr constant [6 x i8] c"a.Buf\00", align 1 +[[vk::binding(0)]] +A a : register(u0); + +// Resource array in struct. +struct B { + RWBuffer Bufs[10]; +}; + +// Resource arrays do not have a global, they are initialized on demand. Just check the string name is generated correctly. +// +// CHECK-DAG: @[[bBufsStr:.*]] = private unnamed_addr constant [7 x i8] c"b.Bufs\00", align 1 +[[vk::binding(2)]] +B b : register(u2); + +// Resources with counters +struct C { + StructuredBuffer BufMany[3][2]; + StructuredBuffer BufOne; +}; + +// CHECK-DAG: @[[cBufOne:.*]] = private unnamed_addr constant [9 x i8] c"c.BufOne\00", align 1 +// CHECK-DAG: @[[cBufMany:.*]] = private unnamed_addr constant [10 x i8] c"c.BufMany\00", align 1 + +[[vk::binding(10)]] +C c : register(t10); + +// Check that a.Buf is initialized from binding +// +// CHECK: define internal void @__cxx_global_var_init() +// CHECK-NEXT: entry: +// CHECK-NEXT: call void @hlsl::RWBuffer::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*) +// CHECK-SAME: (ptr {{.*}}(%"class.hlsl::RWBuffer") align 4 @a.Buf, i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[aBufStr]]) +// CHECK-NEXT: ret void + +// Check that c.BufOne is initialized from binding with counter +// +// CHECK: define internal void @__cxx_global_var_init.3() +// CHECK-NEXT: entry: +// CHECK-NEXT: call void @hlsl::StructuredBuffer::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*) +// CHECK-SAME: (ptr dead_on_unwind writable sret(%"class.hlsl::StructuredBuffer") align 4 @c.BufOne, i32 noundef 16, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[cBufOne]]) +// CHECK-NEXT: ret void + +// CHECK: define internal void @main()() +// CHECK: %[[TMP1:.*]] = alloca %"class.hlsl::RWBuffer", align 4 +// CHECK: %[[TMP2:.*]] = alloca %"class.hlsl::StructuredBuffer", align 4 +[numthreads(1, 1, 1)] +void main() { + +// CHECK: %[[PTR1:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @a.Buf, i32 noundef 0) +// CHECK-NEXT: store float 0x3FF3AE1480000000, ptr %[[PTR1]], align 4 + a.Buf[0] = 1.230f; + +// Resource array access - first create the resource from binding, then access the element and store to it. +// CHECK: call void @hlsl::RWBuffer::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*) +// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer") align 4 %[[TMP1]], i32 noundef 2, i32 noundef 0, i32 noundef 10, i32 noundef 5, ptr noundef @[[bBufsStr]]) +// CHECK-NEXT: %[[PTR2:.*]] = call {{.*}} ptr @hlsl::RWBuffer::operator[](unsigned int)(ptr {{.*}} %[[TMP1]], i32 noundef 0) +// CHECK-NEXT: store float 0x40123D70A0000000, ptr %[[PTR2]], align 4 + b.Bufs[5][0] = 4.56f; + +// CHECK: %[[PTR3:.*]] = call {{.*}} ptr @hlsl::StructuredBuffer::operator[](unsigned int) const(ptr noundef nonnull align 4 dereferenceable(4) @c.BufOne, i32 noundef 0) +// CHECK-NEXT: load float, ptr %[[PTR3]], align 4 + float x = c.BufOne[0]; + +// Resource with counter array access - first create the resource from binding, then access the element and store to it. +// CHECK: call void @hlsl::StructuredBuffer::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*) +// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::StructuredBuffer") align 4 %[[TMP2]], i32 noundef 10, i32 noundef 0, i32 noundef 6, i32 noundef 5, ptr noundef @c.BufMany.str) +// CHECK-NEXT: %[[PTR4:.*]] = call {{.*}} ptr @hlsl::StructuredBuffer::operator[](unsigned int) const(ptr noundef nonnull align 4 dereferenceable(4) %[[TMP2]], i32 noundef 0) +// CHECK-NEXT: load float, ptr %[[PTR4]], align 4 + float f = c.BufMany[2][1][0]; +}