Skip to content

[HLSL] Add globals for resources embedded in structs#184281

Merged
hekota merged 11 commits into
llvm:mainfrom
hekota:res-in-structs-sema
Mar 19, 2026
Merged

[HLSL] Add globals for resources embedded in structs#184281
hekota merged 11 commits into
llvm:mainfrom
hekota:res-in-structs-sema

Conversation

@hekota

@hekota hekota commented Mar 3, 2026

Copy link
Copy Markdown
Member

For each resource or resource array member of a struct declared at global scope or inside a cbuffer, create an implicit global variable of the same resource type. The variable name will be derived from the struct instance name and the member name and will be associated with the struct declaration using a new attribute HLSLAssociatedResourceDeclAttr.

Closes #182988

For each resource or resource array member of a struct declared
at global scope or inside a cbuffer, create an implicit global
variable of the same resource type. The variable name will be
derived from the struct instance name and the member name.

The new global is associated with the struct declaration using
a new attribute HLSLAssociatedResourceDeclAttr.

Closes llvm#182988
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support labels Mar 3, 2026
@llvmbot

llvmbot commented Mar 3, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-hlsl

@llvm/pr-subscribers-clang

Author: Helena Kotas (hekota)

Changes

For each resource or resource array member of a struct declared at global scope or inside a cbuffer, create an implicit global variable of the same resource type. The variable name will be derived from the struct instance name and the member name and will be associated with the struct declaration using a new attribute HLSLAssociatedResourceDeclAttr.

Closes #182988


Patch is 21.43 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/184281.diff

9 Files Affected:

  • (modified) clang/include/clang/AST/HLSLResource.h (+34)
  • (modified) clang/include/clang/Basic/Attr.td (+8)
  • (modified) clang/include/clang/Sema/SemaHLSL.h (+8-6)
  • (modified) clang/lib/AST/CMakeLists.txt (+1)
  • (added) clang/lib/AST/HLSLResource.cpp (+46)
  • (modified) clang/lib/Sema/SemaExprMember.cpp (+6)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+160-5)
  • (added) clang/test/AST/HLSL/resources-in-structs-errors.hlsl (+7)
  • (added) clang/test/AST/HLSL/resources-in-structs.hlsl (+167)
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<unsigned> 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 a5641e2e008cd..298ac535c8a9b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -5046,6 +5046,14 @@ def HLSLResourceBinding: InheritableAttr {
   }];
 }
 
+def HLSLAssociatedResourceDecl : InheritableAttr {
+  let Spellings = [];
+  let Args = [DeclArgument<Var, "ResDecl">];
+  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 a6a38531ac284..7aa97aa878e7b 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -222,6 +222,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.
@@ -315,12 +322,7 @@ class SemaHLSL : public SemaBase {
       const Attr *A, llvm::Triple::EnvironmentType Stage, IOType CurrentIOType,
       std::initializer_list<SemanticStageInfo> 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 f9a5f4f0e7ecd..11d3638e98bc6 100644
--- a/clang/lib/AST/CMakeLists.txt
+++ b/clang/lib/AST/CMakeLists.txt
@@ -96,6 +96,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..19321625222f3
--- /dev/null
+++ b/clang/lib/AST/HLSLResource.cpp
@@ -0,0 +1,46 @@
+//===--- HLSLResource.cpp - Routines for HLSL resources and bindings
+//-------------===//
+//
+// 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
+// theirs 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 804ea70aaddce..df38154faeaa0 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<ArrayType>(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
@@ -4438,6 +4446,147 @@ void SemaHLSL::deduceAddressSpace(VarDecl *Decl) {
   Decl->setType(Type);
 }
 
+namespace {
+
+static void handleResourceFieldsInStruct(
+    Sema &S, VarDecl *ParentVD, const CXXRecordDecl *RD,
+    EmbeddedResourceNameBuilder &NameBuilder);
+
+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
+  handleResourceFieldsInStruct(S, ParentVD, RD, NameBuilder);
+}
+
+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<ConstantArrayType>(ElementTy);
+  const CXXRecordDecl *ElementRD = ElementTy->getAsCXXRecordDecl();
+  assert((SubCAT || ElementRD) &&
+         "Expected struct type or an constant array of structs");
+
+  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();
+  }
+}
+
+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<ArrayType>(ResTy.getTypePtr())) {
+    const auto *CAT = dyn_cast<ConstantArrayType>(AT);
+    Range = CAT ? CAT->getSize().getZExtValue() : -1;
+    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
+handleResourceFieldsInStruct(Sema &S, VarDecl *ParentVD,
+                             const CXXRecordDecl *RD,
+                             EmbeddedResourceNameBuilder &NameBuilder) {
+
+  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<ConstantArrayType>(FDTy)) {
+      assert(!FDTy->isHLSLResourceRecordArray() &&
+             "resource arrays should have been already handled");
+      handleArrayOfStructWithResources(S, ParentVD, ArrayTy, NameBuilder);
+    }
+    NameBuilder.pop();
+  }
+}
+
+} // namespace
+
+void SemaHLSL::handleGlobalStructOrArrayOfWithResources(VarDecl *VD) {
+  EmbeddedResourceNameBuilder NameBuilder(VD->getName());
+
+  const Type *VDTy = VD->getType().getTypePtr();
+  const CXXRecordDecl *RD = VDTy->getAsCXXRecordDecl();
+  if (RD) {
+    handleStructWithResources(SemaRef, VD, RD, NameBuilder);
+    return;
+  }
+
+  const auto *CAT = dyn_cast<ConstantArrayType>(VDTy);
+  if (CAT) {
+    handleArrayOfStructWithResources(SemaRef, VD, CAT, NameBuilder);
+    return;
+  }
+}
+
 void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
   if (VD->hasGlobalStorage()) {
     // make sure the declaration has a complete type
@@ -4510,6 +4659,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);
   }
 
   deduceAddressSpace(VD);
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<float> 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..f6e8b26787adc
--- /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<float>':'hlsl::RWBuffer<float>'
+struct A {
+  RWBuffer<float> Buf;
+};
+
+// CHECK: VarDecl {{.*}} implicit a1.Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} a1 'hlsl_constant A'
+// CHECK: HLSLResourceBindingAttr {{.*}} "u0" "space0"
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'a1.Buf' 'hlsl::RWBuffer<float>'
+A a1 : register(u0);
+
+// Resource array in struct
+
+// CHECK: CXXRecordDecl {{.*}} struct B
+// CHECK: FieldDecl {{.*}} Bufs 'RWBuffer<float>[10]'
+struct B {
+  RWBuffer<float> Bufs[10];
+};
+
+// CHECK: VarDecl {{.*}} implicit b1.Bufs 'hlsl::RWBuffer<float>[10]'
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} b1 'hlsl_constant B'
+// CHECK: HLSLResourceBindingAttr {{.*}} "u2" "space0"
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'b1.Bufs' 'hlsl::RWBuffer<float>[10]'
+B b1 : register(u2);
+
+// Inheritance
+
+// CHECK: CXXRecordDecl {{.*}} struct C
+// CHECK: FieldDecl {{.*}} Buf2 'RWBuffer<float>':'hlsl::RWBuffer<float>'
+struct C : A {
+  RWBuffer<float> Buf2;
+};
+
+// CHECK: VarDecl {{.*}} implicit c1.A::Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit c1.Buf2 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} c1 'hlsl_constant C'
+// CHECK: HLSLResourceBindingAttr {{.*}} "u3" "space0"
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'c1.A::Buf' 'hlsl::RWBuffer<float>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'c1.Buf2' 'hlsl::RWBuffer<float>'
+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<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit d1.A.Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} d1 'hlsl_constant D'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'd1.A::Buf' 'hlsl::RWBuffer<float>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'd1.A.Buf' 'hlsl::RWBuffer<float>'
+D d1;
+
+// Inheritance and Multiple Resources Kinds
+
+// CHECK: CXXRecordDecl {{.*}} class E
+// CHECK: FieldDecl {{.*}} SrvBuf 'StructuredBuffer<int>':'hlsl::StructuredBuffer<int>'
+class E {
+  StructuredBuffer<int> SrvBuf;
+};
+
+// CHECK: CXXRecordDecl {{.*}} class F
+// CHECK: FieldDecl {{.*}} a 'A'
+// CHECK: FieldDecl {{.*}} SrvBuf 'StructuredBuffer<float>':'hlsl::StructuredBuffer<float>'
+// CHECK: FieldDecl {{.*}} Samp 'SamplerState'
+class F : E {
+  A a;
+  StructuredBuffer<float> SrvBuf;
+  SamplerState Samp;
+};
+
+// CHECK: VarDecl {{.*}} implicit f.E::SrvBuf 'hlsl::StructuredBuffer<int>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit f.a.Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit f.SrvBuf 'hlsl::StructuredBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit f.Samp 'hlsl::SamplerState' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} f 'hlsl_constant F'
+// CHECK: HLSLResourceBindingAttr {{.*}} "t0" "space0"
+// CHECK: HLSLResourceBindingAttr {{.*}} "u20" "space0"
+// CHECK: HLSLResourceBindingAttr {{.*}} "s3" "space0"
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'f.E::SrvBuf' 'hlsl::StructuredBuffer<int>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'f.a.Buf' 'hlsl::RWBuffer<float>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'f.SrvBuf' 'hlsl::StructuredBuffer<float>'
+// 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<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit arrayOfA.1.Buf 'hlsl::RWBuffer<float>' 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<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.0.1.Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.1.0.Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.1.1.Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.0.0.Buf 'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.0.1.Buf 'hlsl::RWBuf...
[truncated]

@github-actions

github-actions Bot commented Mar 3, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@github-actions

github-actions Bot commented Mar 3, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 115033 tests passed
  • 4501 tests skipped

✅ The build succeeded and all tests passed.

hekota added a commit to hekota/llvm-project that referenced this pull request Mar 5, 2026
Add binding attributes to global variables that were created for resources embedded in structs.
The binding values are based on `register` annotations and `[[vk::binding]]` attribute on the struct instance.

Depends on llvm#184281

Fixes llvm#182992
Comment thread clang/lib/AST/HLSLResource.cpp Outdated
Comment thread clang/lib/AST/HLSLResource.cpp Outdated
Comment thread clang/lib/Sema/SemaHLSL.cpp
Comment thread clang/lib/Sema/SemaHLSL.cpp Outdated
Comment thread clang/lib/Sema/SemaHLSL.cpp Outdated
Comment thread clang/test/AST/HLSL/resources-in-structs.hlsl
// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.0.1.Buf' 'hlsl::RWBuffer<float>'
// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.1.0.Buf' 'hlsl::RWBuffer<float>'
// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.1.1.Buf' 'hlsl::RWBuffer<float>'
G gArray[2] : register(u10, space2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the case where I think we could do better than expanding each outer struct array element containing a resource decl. We could instead collect the array sizes as we recurse into the type, and create a single, multi-dimensional resource array corresponding to a single field. The resulting resource array in this case would be like:
gArray.multiArray.Buf or gArray.[].multiArray.[].[].Buf or something similar, and it would have a dimensionality of [2][2][2]. Array field types would add those dimensions to the end for one set of array dimensions on one resource range decl.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. If the user intended these to be handled as a single array, they should have written them as an array. What benefit would merging them provide? From a user’s perspective, that behavior would likely be unexpected.

What if the struct contains multiple resource types? Or a mix of single resources and arrays? I firmly believe resources should be treated as resource ranges only when they are explicitly defined as arrays in the source code.

@github-actions

github-actions Bot commented Mar 11, 2026

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 54939 tests passed
  • 2365 tests skipped

✅ The build succeeded and all tests passed.

@bob80905 bob80905 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bob80905 bob80905 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, looks like resources-in-structs.hlsl is somehow failing.

@hekota

hekota commented Mar 11, 2026

Copy link
Copy Markdown
Member Author

Never mind, looks like resources-in-structs.hlsl is somehow failing.

That's a fallback from my previous PR. The test is updated now.

@bob80905 bob80905 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread clang/lib/Sema/SemaHLSL.cpp Outdated
Comment thread clang/lib/Sema/SemaHLSL.cpp Outdated
Comment on lines +4839 to +4840
if (!SubCAT && !ElementRD)
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we have if it's neither a ConstantArrayType or a RecordDecl? Do we really want to just return here or should we have some kind of assert?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally had an assert here and it was getting triggered on our test cases that use the resource handle type __hlsl_resource_t directly in an array. This case is safe to ignore, so I removed the assert.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still worries me a bit that some code change somewhere else could lead to new types being possible here and that silently ignoring them might not be correct. I guess if there isn't something reasonable to assert on then we just have to live with that though...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that should be a problem. We are scanning the array of structs for resource records. I believe it is safe to ignore anything else that is not a struct or an array of structs here.

Comment thread clang/lib/Sema/SemaHLSL.cpp Outdated
Comment thread clang/lib/Sema/SemaHLSL.cpp Outdated

@bob80905 bob80905 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +4839 to +4840
if (!SubCAT && !ElementRD)
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still worries me a bit that some code change somewhere else could lead to new types being possible here and that silently ignoring them might not be correct. I guess if there isn't something reasonable to assert on then we just have to live with that though...

@hekota hekota merged commit 39b6a4d into llvm:main Mar 19, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[HLSL] Create globals for resources in structs

5 participants