diff --git a/clang/include/clang/AST/HLSLResource.h b/clang/include/clang/AST/HLSLResource.h index 131aebf5f14f2..a37acb3660d00 100644 --- a/clang/include/clang/AST/HLSLResource.h +++ b/clang/include/clang/AST/HLSLResource.h @@ -17,6 +17,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclBase.h" +#include "clang/Basic/IdentifierTable.h" #include "clang/Basic/TargetInfo.h" #include "clang/Support/Compiler.h" #include "llvm/Frontend/HLSL/HLSLResource.h" @@ -108,6 +109,39 @@ inline uint32_t getResourceDimensions(llvm::dxil::ResourceDimension Dim) { llvm_unreachable("Unhandled llvm::dxil::ResourceDimension enum."); } +// 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 +// access with the corresponding declaration. +class EmbeddedResourceNameBuilder { + llvm::SmallString<64> Name; + llvm::SmallVector Offsets; + + inline static constexpr std::string_view BaseClassDelim = "::"; + inline static constexpr std::string_view FieldDelim = "."; + inline static constexpr std::string_view ArrayIndexDelim = FieldDelim; + +public: + EmbeddedResourceNameBuilder(llvm::StringRef BaseName) : Name(BaseName) {} + EmbeddedResourceNameBuilder() : Name("") {} + + void pushName(llvm::StringRef N) { pushName(N, FieldDelim); } + void pushBaseName(llvm::StringRef N); + void pushArrayIndex(uint64_t Index); + + void pop() { + assert(!Offsets.empty() && "no name to pop"); + Name.resize(Offsets.pop_back_val()); + } + + IdentifierInfo *getNameAsIdentifier(ASTContext &AST) const { + return &AST.Idents.get(Name); + } + +private: + void pushName(llvm::StringRef N, llvm::StringRef Delim); +}; + } // namespace hlsl } // namespace clang diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 61194e3c2c940..5023011a68147 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -5053,6 +5053,14 @@ def HLSLResourceBinding: InheritableAttr { }]; } +def HLSLAssociatedResourceDecl : InheritableAttr { + let Spellings = []; + let Args = [DeclArgument]; + let Subjects = SubjectList<[ExternalGlobalVar], ErrorDiag>; + let LangOpts = [HLSL]; + let Documentation = [InternalOnly]; +} + def HLSLUnparsedSemantic : HLSLAnnotationAttr { let Spellings = []; let Args = [DefaultIntArgument<"Index", 0>, diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 6341d3cd1cc00..cf9b8c39b109e 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -225,6 +225,13 @@ class SemaHLSL : public SemaBase { const IdentifierInfo *CompName, SourceLocation CompLoc); + uint32_t getNextImplicitBindingOrderID() { + return ImplicitBindingNextOrderID++; + } + + bool initGlobalResourceDecl(VarDecl *VD); + bool initGlobalResourceArrayDecl(VarDecl *VD); + private: // HLSL resource type attributes need to be processed all at once. // This is a list to collect them. @@ -318,12 +325,7 @@ class SemaHLSL : public SemaBase { const Attr *A, llvm::Triple::EnvironmentType Stage, IOType CurrentIOType, std::initializer_list AllowedStages); - uint32_t getNextImplicitBindingOrderID() { - return ImplicitBindingNextOrderID++; - } - - bool initGlobalResourceDecl(VarDecl *VD); - bool initGlobalResourceArrayDecl(VarDecl *VD); + void handleGlobalStructOrArrayOfWithResources(VarDecl *VD); // Infer a common global binding info for an Expr // diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index a2eac60844ef8..e33250dc005e4 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -95,6 +95,7 @@ add_clang_library(clangAST ByteCode/State.cpp ByteCode/MemberPointer.cpp ByteCode/InterpShared.cpp + HLSLResource.cpp ItaniumCXXABI.cpp ItaniumMangle.cpp JSONNodeDumper.cpp diff --git a/clang/lib/AST/HLSLResource.cpp b/clang/lib/AST/HLSLResource.cpp new file mode 100644 index 0000000000000..9b311c005df5c --- /dev/null +++ b/clang/lib/AST/HLSLResource.cpp @@ -0,0 +1,45 @@ +//===--- HLSLResource.cpp - Helper routines for HLSL resources -----------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides shared routines to help analyze HLSL resources and +// their bindings during Sema and CodeGen. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/HLSLResource.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Type.h" + +using namespace clang; + +namespace clang { +namespace hlsl { + +void EmbeddedResourceNameBuilder::pushBaseName(llvm::StringRef N) { + pushName(N, FieldDelim); + Name.append(BaseClassDelim); +} + +void EmbeddedResourceNameBuilder::pushName(llvm::StringRef N, + llvm::StringRef Delim) { + Offsets.push_back(Name.size()); + if (!Name.empty() && !Name.ends_with(BaseClassDelim)) + Name.append(Delim); + Name.append(N); +} + +void EmbeddedResourceNameBuilder::pushArrayIndex(uint64_t Index) { + llvm::raw_svector_ostream OS(Name); + Offsets.push_back(Name.size()); + OS << ArrayIndexDelim; + OS << Index; +} + +} // namespace hlsl +} // namespace clang diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp index e2f26ef5aa2b2..ff7c844f66123 100644 --- a/clang/lib/Sema/SemaExprMember.cpp +++ b/clang/lib/Sema/SemaExprMember.cpp @@ -1812,6 +1812,12 @@ Sema::BuildFieldReferenceExpr(Expr *BaseExpr, bool IsArrow, // except that 'mutable' members don't pick up 'const'. if (Field->isMutable()) BaseQuals.removeConst(); + // HLSL resource types do not pick up address space qualifiers from the + // base. + if (getLangOpts().HLSL && (MemberType->isHLSLResourceRecord() || + MemberType->isHLSLResourceRecordArray())) + BaseQuals.removeAddressSpace(); + Qualifiers MemberQuals = Context.getCanonicalType(MemberType).getQualifiers(); diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 11a856f637be7..0619295cd2fbb 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -342,21 +342,29 @@ static bool isZeroSizedArray(const ConstantArrayType *CAT) { return CAT != nullptr; } -static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { - const Type *Ty = VD->getType().getTypePtr(); +static bool isResourceRecordTypeOrArrayOf(QualType Ty) { return Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray(); } +static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { + return isResourceRecordTypeOrArrayOf(VD->getType()); +} + static const HLSLAttributedResourceType * -getResourceArrayHandleType(VarDecl *VD) { - assert(VD->getType()->isHLSLResourceRecordArray() && +getResourceArrayHandleType(QualType QT) { + assert(QT->isHLSLResourceRecordArray() && "expected array of resource records"); - const Type *Ty = VD->getType()->getUnqualifiedDesugaredType(); + const Type *Ty = QT->getUnqualifiedDesugaredType(); while (const ArrayType *AT = dyn_cast(Ty)) Ty = AT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType(); return HLSLAttributedResourceType::findHandleTypeOnResource(Ty); } +static const HLSLAttributedResourceType * +getResourceArrayHandleType(VarDecl *VD) { + return getResourceArrayHandleType(VD->getType()); +} + // 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 @@ -4811,6 +4819,151 @@ void SemaHLSL::deduceAddressSpace(VarDecl *Decl) { Decl->setType(Type); } +// Creates a global variable declaration for a resource field embedded in a +// struct, assigns it a binding, initializes it, and associates it with the +// struct declaration via an HLSLAssociatedResourceDeclAttr. +static void createGlobalResourceDeclForStruct(Sema &S, VarDecl *ParentVD, + SourceLocation Loc, + IdentifierInfo *Id, + QualType ResTy) { + assert(isResourceRecordTypeOrArrayOf(ResTy) && + "expected resource type or array of resources"); + + DeclContext *DC = ParentVD->getNonTransparentDeclContext(); + assert(DC->isTranslationUnit() && "expected translation unit decl context"); + + ASTContext &AST = S.getASTContext(); + VarDecl *ResDecl = + 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 auto *CAT = dyn_cast(AT); + Range = CAT ? CAT->getSize().getZExtValue() : 0; + ResHandleTy = getResourceArrayHandleType(ResTy); + } else { + ResHandleTy = HLSLAttributedResourceType::findHandleTypeOnResource( + ResTy.getTypePtr()); + } + // FIXME: Explicit bindings will be handled in a follow-up change. For now + // just add an implicit binding attribute. + auto *Attr = + HLSLResourceBindingAttr::CreateImplicit(S.getASTContext(), "", "0", {}); + Attr->setBinding(getRegisterType(ResHandleTy), std::nullopt, 0); + Attr->setImplicitBindingOrderID(S.HLSL().getNextImplicitBindingOrderID()); + ResDecl->addAttr(Attr); + ResDecl->setImplicit(); + + if (Range == 1) + S.HLSL().initGlobalResourceDecl(ResDecl); + else + S.HLSL().initGlobalResourceArrayDecl(ResDecl); + + ParentVD->addAttr( + HLSLAssociatedResourceDeclAttr::CreateImplicit(AST, ResDecl)); + DC->addDecl(ResDecl); + + DeclGroupRef DG(ResDecl); + S.Consumer.HandleTopLevelDecl(DG); +} + +static void +handleArrayOfStructWithResources(Sema &S, VarDecl *ParentVD, + const ConstantArrayType *CAT, + EmbeddedResourceNameBuilder &NameBuilder); + +// Scans base and all fields of a struct/class type to find all embedded +// resources or resource arrays. Creates a global variable for each resource +// found. +static void +handleStructWithResources(Sema &S, VarDecl *ParentVD, const CXXRecordDecl *RD, + EmbeddedResourceNameBuilder &NameBuilder) { + + // Scan the base classes. + assert(RD->getNumBases() <= 1 && "HLSL doesn't support multiple inheritance"); + const auto *BasesIt = RD->bases_begin(); + if (BasesIt != RD->bases_end()) { + QualType QT = BasesIt->getType(); + if (QT->isHLSLIntangibleType()) { + CXXRecordDecl *BaseRD = QT->getAsCXXRecordDecl(); + NameBuilder.pushBaseName(BaseRD->getName()); + handleStructWithResources(S, ParentVD, BaseRD, NameBuilder); + NameBuilder.pop(); + } + } + // Process this class fields. + for (const FieldDecl *FD : RD->fields()) { + QualType FDTy = FD->getType().getCanonicalType(); + if (!FDTy->isHLSLIntangibleType()) + continue; + + NameBuilder.pushName(FD->getName()); + + if (isResourceRecordTypeOrArrayOf(FDTy)) { + IdentifierInfo *II = NameBuilder.getNameAsIdentifier(S.getASTContext()); + createGlobalResourceDeclForStruct(S, ParentVD, FD->getLocation(), II, + FDTy); + } else if (const auto *RD = FDTy->getAsCXXRecordDecl()) { + handleStructWithResources(S, ParentVD, RD, NameBuilder); + + } else if (const auto *ArrayTy = dyn_cast(FDTy)) { + assert(!FDTy->isHLSLResourceRecordArray() && + "resource arrays should have been already handled"); + handleArrayOfStructWithResources(S, ParentVD, ArrayTy, NameBuilder); + } + NameBuilder.pop(); + } +} + +// Processes array of structs with resources. +static void +handleArrayOfStructWithResources(Sema &S, VarDecl *ParentVD, + const ConstantArrayType *CAT, + EmbeddedResourceNameBuilder &NameBuilder) { + + QualType ElementTy = CAT->getElementType().getCanonicalType(); + assert(ElementTy->isHLSLIntangibleType() && "Expected HLSL intangible type"); + + const ConstantArrayType *SubCAT = dyn_cast(ElementTy); + const CXXRecordDecl *ElementRD = ElementTy->getAsCXXRecordDecl(); + + if (!SubCAT && !ElementRD) + return; + + for (unsigned I = 0, E = CAT->getSize().getZExtValue(); I < E; ++I) { + NameBuilder.pushArrayIndex(I); + if (ElementRD) + handleStructWithResources(S, ParentVD, ElementRD, NameBuilder); + else + handleArrayOfStructWithResources(S, ParentVD, SubCAT, NameBuilder); + NameBuilder.pop(); + } +} + +// Scans all fields of a user-defined struct (or array of structs) +// to find all embedded resources or resource arrays. For each resource +// a global variable of the resource type is created and associated +// with the parent declaration (VD) through a HLSLAssociatedResourceDeclAttr +// attribute. +void SemaHLSL::handleGlobalStructOrArrayOfWithResources(VarDecl *VD) { + EmbeddedResourceNameBuilder NameBuilder(VD->getName()); + + const Type *VDTy = VD->getType().getTypePtr(); + assert(VDTy->isHLSLIntangibleType() && !isResourceRecordTypeOrArrayOf(VD) && + "Expected non-resource struct or array type"); + + if (const CXXRecordDecl *RD = VDTy->getAsCXXRecordDecl()) { + handleStructWithResources(SemaRef, VD, RD, NameBuilder); + return; + } + + if (const auto *CAT = dyn_cast(VDTy)) { + handleArrayOfStructWithResources(SemaRef, VD, CAT, NameBuilder); + return; + } +} + void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) { if (VD->hasGlobalStorage()) { // make sure the declaration has a complete type @@ -4884,6 +5037,12 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) { } } + // Process resources in user-defined structs, or arrays of such structs. + const Type *VDTy = VD->getType().getTypePtr(); + if (VD->getStorageClass() != SC_Static && VDTy->isHLSLIntangibleType() && + !isResourceRecordTypeOrArrayOf(VD)) + handleGlobalStructOrArrayOfWithResources(VD); + // Mark groupshared variables as extern so they will have // external storage and won't be default initialized if (VD->hasAttr()) diff --git a/clang/test/AST/HLSL/resources-in-structs-errors.hlsl b/clang/test/AST/HLSL/resources-in-structs-errors.hlsl new file mode 100644 index 0000000000000..56baef7957fb1 --- /dev/null +++ b/clang/test/AST/HLSL/resources-in-structs-errors.hlsl @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -verify %s + +struct A { + RWBuffer Buf; +}; + +A incompleteArray[]; // expected-error {{definition of variable with array type needs an explicit size or an initializer}} diff --git a/clang/test/AST/HLSL/resources-in-structs.hlsl b/clang/test/AST/HLSL/resources-in-structs.hlsl new file mode 100644 index 0000000000000..08aafcd278332 --- /dev/null +++ b/clang/test/AST/HLSL/resources-in-structs.hlsl @@ -0,0 +1,167 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -ast-dump %s | FileCheck %s + +// Single resource field in struct + +// CHECK: CXXRecordDecl {{.*}} struct A +// CHECK: FieldDecl {{.*}} Buf 'RWBuffer':'hlsl::RWBuffer' +struct A { + RWBuffer Buf; +}; + +// CHECK: VarDecl {{.*}} implicit a1.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} a1 'A' +// CHECK: HLSLResourceBindingAttr {{.*}} "u0" "space0" +// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'a1.Buf' 'hlsl::RWBuffer' +A a1 : register(u0); + +// Resource array in struct + +// CHECK: CXXRecordDecl {{.*}} struct B +// CHECK: FieldDecl {{.*}} Bufs 'RWBuffer[10]' +struct B { + RWBuffer Bufs[10]; +}; + +// CHECK: VarDecl {{.*}} implicit b1.Bufs 'hlsl::RWBuffer[10]' +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} b1 'B' +// CHECK: HLSLResourceBindingAttr {{.*}} "u2" "space0" +// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'b1.Bufs' 'hlsl::RWBuffer[10]' +B b1 : register(u2); + +// Inheritance + +// CHECK: CXXRecordDecl {{.*}} struct C +// CHECK: FieldDecl {{.*}} Buf2 'RWBuffer':'hlsl::RWBuffer' +struct C : A { + RWBuffer Buf2; +}; + +// CHECK: VarDecl {{.*}} implicit c1.A::Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit c1.Buf2 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} c1 'C' +// CHECK: HLSLResourceBindingAttr {{.*}} "u3" "space0" +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'c1.A::Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'c1.Buf2' 'hlsl::RWBuffer' +C c1 : register(u3); + +// Inheritance with same named field +// CHECK: CXXRecordDecl {{.*}} struct D +// CHECK: FieldDecl {{.*}} A 'A' +struct D : A { + A A; +}; + +// CHECK: VarDecl {{.*}} implicit d1.A::Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit d1.A.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} d1 'D' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'd1.A::Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'd1.A.Buf' 'hlsl::RWBuffer' +D d1; + +// Inheritance and Multiple Resources Kinds + +// CHECK: CXXRecordDecl {{.*}} class E +// CHECK: FieldDecl {{.*}} SrvBuf 'StructuredBuffer':'hlsl::StructuredBuffer' +class E { + StructuredBuffer SrvBuf; +}; + +// CHECK: CXXRecordDecl {{.*}} class F +// CHECK: FieldDecl {{.*}} a 'A' +// CHECK: FieldDecl {{.*}} SrvBuf 'StructuredBuffer':'hlsl::StructuredBuffer' +// CHECK: FieldDecl {{.*}} Samp 'SamplerState' +class F : E { + A a; + StructuredBuffer SrvBuf; + SamplerState Samp; +}; + +// CHECK: VarDecl {{.*}} implicit f.E::SrvBuf 'hlsl::StructuredBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit f.a.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit f.SrvBuf 'hlsl::StructuredBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit f.Samp 'hlsl::SamplerState' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} f 'F' +// CHECK: HLSLResourceBindingAttr {{.*}} "t0" "space0" +// CHECK: HLSLResourceBindingAttr {{.*}} "u20" "space0" +// CHECK: HLSLResourceBindingAttr {{.*}} "s3" "space0" +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'f.E::SrvBuf' 'hlsl::StructuredBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'f.a.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'f.SrvBuf' 'hlsl::StructuredBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'f.Samp' 'hlsl::SamplerState' +F f : register(t0) : register(u20) : register(s3); + +// Array of structs with resources + +// CHECK: VarDecl {{.*}} implicit arrayOfA.0.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit arrayOfA.1.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" +A arrayOfA[2] : register(u0, space1); + +// CHECK: CXXRecordDecl {{.*}} struct G +// CHECK: FieldDecl {{.*}} multiArray 'A[2][2]' +struct G { + A multiArray[2][2]; +}; + +// CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.0.0.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.0.1.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.1.0.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.1.1.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.0.0.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.0.1.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.1.0.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.1.1.Buf 'hlsl::RWBuffer' callinit +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" + +// CHECK: VarDecl {{.*}} gArray 'G[2]' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.0.0.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.0.1.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.1.0.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.1.1.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.0.0.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.0.1.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.1.0.Buf' 'hlsl::RWBuffer' +// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.1.1.Buf' 'hlsl::RWBuffer' +G gArray[2] : register(u10, space2); + +// Static struct with resources + +// CHECK-NOT: VarDecl {{.*}} a2.Buf +// CHECK: VarDecl {{.*}} a2 'hlsl_private A' static cinit +static A a2 = { a1 };