https://github.com/hekota updated 
https://github.com/llvm/llvm-project/pull/187127

>From 1a3c736dbe5b44fb2497b29d7f4b853ab5bf280f Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Mon, 2 Mar 2026 18:07:13 -0800
Subject: [PATCH 1/8] [HLSL] Add globals for resources embedded in structs

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 #182988
---
 clang/include/clang/AST/HLSLResource.h        |  34 ++++
 clang/include/clang/Basic/Attr.td             |   8 +
 clang/include/clang/Sema/SemaHLSL.h           |  14 +-
 clang/lib/AST/CMakeLists.txt                  |   1 +
 clang/lib/AST/HLSLResource.cpp                |  46 +++++
 clang/lib/Sema/SemaExprMember.cpp             |   6 +
 clang/lib/Sema/SemaHLSL.cpp                   | 165 ++++++++++++++++-
 .../AST/HLSL/resources-in-structs-errors.hlsl |   7 +
 clang/test/AST/HLSL/resources-in-structs.hlsl | 167 ++++++++++++++++++
 9 files changed, 437 insertions(+), 11 deletions(-)
 create mode 100644 clang/lib/AST/HLSLResource.cpp
 create mode 100644 clang/test/AST/HLSL/resources-in-structs-errors.hlsl
 create mode 100644 clang/test/AST/HLSL/resources-in-structs.hlsl

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::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.1.0.Buf 
'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.1.1.Buf 
'hlsl::RWBuffer<float>' callinit
+// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+
+// CHECK: VarDecl {{.*}} gArray 'hlsl_constant G[2]'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.0.0.Buf' 
'hlsl::RWBuffer<float>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.0.1.Buf' 
'hlsl::RWBuffer<float>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.1.0.Buf' 
'hlsl::RWBuffer<float>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.1.1.Buf' 
'hlsl::RWBuffer<float>'
+// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.0.0.Buf' 
'hlsl::RWBuffer<float>'
+// 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);
+
+// Static struct with resources
+
+// CHECK-NOT: VarDecl {{.*}} a2.Buf
+// CHECK: VarDecl {{.*}} a2 'hlsl_private A' static cinit
+static A a2 = { a1 };

>From 908782fafbd03905482d239c285047d1fe2df6a7 Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Mon, 2 Mar 2026 18:50:41 -0800
Subject: [PATCH 2/8] Reorder and format

---
 clang/lib/Sema/SemaHLSL.cpp | 100 +++++++++++++++++-------------------
 1 file changed, 47 insertions(+), 53 deletions(-)

diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index df38154faeaa0..e397d32643857 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -4448,56 +4448,10 @@ void SemaHLSL::deduceAddressSpace(VarDecl *Decl) {
 
 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) {
+static void createGlobalResourceDeclForStruct(Sema &S, VarDecl *ParentVD,
+                                              SourceLocation Loc,
+                                              IdentifierInfo *Id,
+                                              QualType ResTy) {
   assert(isResourceRecordTypeOrArrayOf(ResTy) &&
          "expected resource type or array of resources");
 
@@ -4541,10 +4495,27 @@ static void createGlobalResourceDeclForStruct(
 }
 
 static void
-handleResourceFieldsInStruct(Sema &S, VarDecl *ParentVD,
-                             const CXXRecordDecl *RD,
-                             EmbeddedResourceNameBuilder &NameBuilder) {
+handleArrayOfStructWithResources(Sema &S, VarDecl *ParentVD,
+                                 const ConstantArrayType *CAT,
+                                 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
   for (const FieldDecl *FD : RD->fields()) {
     QualType FDTy = FD->getType().getCanonicalType();
     if (!FDTy->isHLSLIntangibleType())
@@ -4568,6 +4539,29 @@ handleResourceFieldsInStruct(Sema &S, VarDecl *ParentVD,
   }
 }
 
+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();
+  }
+}
+
 } // namespace
 
 void SemaHLSL::handleGlobalStructOrArrayOfWithResources(VarDecl *VD) {

>From 64141ab75a2554da799392fc4eada65d32b8c263 Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Tue, 3 Mar 2026 13:22:03 -0800
Subject: [PATCH 3/8] Add comments, update asserts

---
 clang/lib/Sema/SemaHLSL.cpp | 24 ++++++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index e397d32643857..fa24ac31ad646 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -4448,6 +4448,9 @@ void SemaHLSL::deduceAddressSpace(VarDecl *Decl) {
 
 namespace {
 
+// 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,
@@ -4499,11 +4502,14 @@ 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
+  // 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()) {
@@ -4515,7 +4521,7 @@ handleStructWithResources(Sema &S, VarDecl *ParentVD, 
const CXXRecordDecl *RD,
       NameBuilder.pop();
     }
   }
-  // process this class fields
+  // Process this class fields.
   for (const FieldDecl *FD : RD->fields()) {
     QualType FDTy = FD->getType().getCanonicalType();
     if (!FDTy->isHLSLIntangibleType())
@@ -4539,6 +4545,7 @@ handleStructWithResources(Sema &S, VarDecl *ParentVD, 
const CXXRecordDecl *RD,
   }
 }
 
+// Processes array of structs with resources.
 static void
 handleArrayOfStructWithResources(Sema &S, VarDecl *ParentVD,
                                  const ConstantArrayType *CAT,
@@ -4549,8 +4556,9 @@ handleArrayOfStructWithResources(Sema &S, VarDecl 
*ParentVD,
 
   const ConstantArrayType *SubCAT = dyn_cast<ConstantArrayType>(ElementTy);
   const CXXRecordDecl *ElementRD = ElementTy->getAsCXXRecordDecl();
-  assert((SubCAT || ElementRD) &&
-         "Expected struct type or an constant array of structs");
+
+  if (!SubCAT && !ElementRD)
+    return;
 
   for (unsigned I = 0, E = CAT->getSize().getZExtValue(); I < E; ++I) {
     NameBuilder.pushArrayIndex(I);
@@ -4564,10 +4572,18 @@ handleArrayOfStructWithResources(Sema &S, VarDecl 
*ParentVD,
 
 } // namespace
 
+// 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");
+
   const CXXRecordDecl *RD = VDTy->getAsCXXRecordDecl();
   if (RD) {
     handleStructWithResources(SemaRef, VD, RD, NameBuilder);

>From 70ca5d5d63db769943fb6fea7de41f50a2600223 Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Wed, 4 Mar 2026 18:31:11 -0800
Subject: [PATCH 4/8] [HLSL] Add binding attributes to resources in structs

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 #184281

Fixes #182992
---
 clang/lib/Sema/SemaHLSL.cpp                   | 170 +++++++++++++++---
 clang/test/AST/HLSL/resources-in-structs.hlsl | 125 ++++++++-----
 2 files changed, 224 insertions(+), 71 deletions(-)

diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index fa24ac31ad646..805b97dc858d9 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -111,6 +111,24 @@ static bool convertToRegisterType(StringRef Slot, 
RegisterType *RT) {
   }
 }
 
+static char getRegisterTypeChar(RegisterType RT) {
+  switch (RT) {
+  case RegisterType::SRV:
+    return 't';
+  case RegisterType::UAV:
+    return 'u';
+  case RegisterType::CBuffer:
+    return 'b';
+  case RegisterType::Sampler:
+    return 's';
+  case RegisterType::C:
+    return 'c';
+  case RegisterType::I:
+    return 'i';
+  }
+  llvm_unreachable("unexpected RegisterType value");
+}
+
 static ResourceClass getResourceClass(RegisterType RT) {
   switch (RT) {
   case RegisterType::SRV:
@@ -163,6 +181,15 @@ static Builtin::ID getSpecConstBuiltinId(const Type *Type) 
{
   }
 }
 
+static StringRef createRegisterString(ASTContext &AST, RegisterType RegType,
+                                      unsigned N) {
+  llvm::SmallString<16> Buffer;
+  llvm::raw_svector_ostream OS(Buffer);
+  OS << getRegisterTypeChar(RegType);
+  OS << N;
+  return AST.backupStr(OS.str());
+}
+
 DeclBindingInfo *ResourceBindings::addDeclBindingInfo(const VarDecl *VD,
                                                       ResourceClass ResClass) {
   assert(getDeclBindingInfo(VD, ResClass) == nullptr &&
@@ -4448,13 +4475,99 @@ void SemaHLSL::deduceAddressSpace(VarDecl *Decl) {
 
 namespace {
 
+// Helper class for assigning bindings to resources declared within a struct.
+// It keeps track of all binding attributes declared on a struct instance, and
+// the offsets for each register type that have been assigned so far.
+// Handles both explicit and implicit bindings.
+class StructBindingContext {
+  // Bindings and offsets per register type. We only need to support four
+  // register types - SRV (u), UAV (t), CBuffer (c), and Sampler (s).
+  HLSLResourceBindingAttr *RegBindingsAttrs[4];
+  unsigned RegBindingOffset[4];
+
+  // Vulkan binding attribute does not vary by register type.
+  HLSLVkBindingAttr *VkBindingAttr;
+  unsigned VkBindingOffset;
+
+public:
+  // Constructor: gather all binding attributes on a struct instance and
+  // initialize offsets.
+  StructBindingContext(VarDecl *VD) {
+    for (unsigned i = 0; i < 4; ++i) {
+      RegBindingsAttrs[i] = nullptr;
+      RegBindingOffset[i] = 0;
+    }
+    VkBindingAttr = nullptr;
+    VkBindingOffset = 0;
+
+    ASTContext &AST = VD->getASTContext();
+    bool IsSpirv = AST.getTargetInfo().getTriple().isSPIRV();
+
+    for (Attr *A : VD->attrs()) {
+      if (auto *RBA = dyn_cast<HLSLResourceBindingAttr>(A)) {
+        RegisterType RegType = RBA->getRegisterType();
+        unsigned RegTypeIdx = static_cast<unsigned>(RegType);
+        // Ignore unsupported register annotations, such as 'c' or 'i'.
+        if (RegTypeIdx < 4)
+          RegBindingsAttrs[RegTypeIdx] = RBA;
+        continue;
+      }
+      // Gather the Vulkan binding attributes only if the target is SPIR-V.
+      if (IsSpirv) {
+        if (auto *VBA = dyn_cast<HLSLVkBindingAttr>(A))
+          VkBindingAttr = VBA;
+      }
+    }
+  }
+
+  // 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) {
+    assert(static_cast<unsigned>(RegType) < 4 && "unexpected register type");
+
+    if (VkBindingAttr) {
+      unsigned Offset = VkBindingOffset;
+      VkBindingOffset += Range;
+      return HLSLVkBindingAttr::CreateImplicit(
+          AST, VkBindingAttr->getBinding() + Offset, VkBindingAttr->getSet(),
+          VkBindingAttr->getRange());
+    }
+
+    HLSLResourceBindingAttr *RBA =
+        RegBindingsAttrs[static_cast<unsigned>(RegType)];
+    HLSLResourceBindingAttr *NewAttr = nullptr;
+
+    if (RBA && RBA->hasRegisterSlot()) {
+      // Explicit binding - create a new attribute with offseted slot number
+      // based on the required register type.
+      unsigned Offset = RegBindingOffset[static_cast<unsigned>(RegType)];
+      RegBindingOffset[static_cast<unsigned>(RegType)] += Range;
+
+      unsigned NewSlotNumber = RBA->getSlotNumber() + Offset;
+      StringRef NewSlotNumberStr =
+          createRegisterString(AST, RBA->getRegisterType(), NewSlotNumber);
+      NewAttr = HLSLResourceBindingAttr::CreateImplicit(
+          AST, NewSlotNumberStr, RBA->getSpace(), RBA->getRange());
+      NewAttr->setBinding(RegType, NewSlotNumber, RBA->getSpaceNumber());
+    } else {
+      // No binding attribute or space-only binding - create a binding
+      // attribute for implicit binding.
+      NewAttr = HLSLResourceBindingAttr::CreateImplicit(AST, "", "0", {});
+      NewAttr->setBinding(RegType, std::nullopt,
+                          RBA ? RBA->getSpaceNumber() : 0);
+      NewAttr->setImplicitBindingOrderID(S.getNextImplicitBindingOrderID());
+    }
+    return NewAttr;
+  }
+};
+
 // 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) {
+static void createGlobalResourceDeclForStruct(
+    Sema &S, VarDecl *ParentVD, SourceLocation Loc, IdentifierInfo *Id,
+    QualType ResTy, StructBindingContext &BindingCtx) {
   assert(isResourceRecordTypeOrArrayOf(ResTy) &&
          "expected resource type or array of resources");
 
@@ -4475,13 +4588,11 @@ static void createGlobalResourceDeclForStruct(Sema &S, 
VarDecl *ParentVD,
     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);
+  // Add a binding attribute to the global resource declaration.
+  Attr *BindingAttr = BindingCtx.createBindingAttr(
+      S.HLSL(), AST, getRegisterType(ResHandleTy), Range);
+  ResDecl->addAttr(BindingAttr);
+  ResDecl->addAttr(InternalLinkageAttr::CreateImplicit(AST));
   ResDecl->setImplicit();
 
   if (Range == 1)
@@ -4497,17 +4608,17 @@ static void createGlobalResourceDeclForStruct(Sema &S, 
VarDecl *ParentVD,
   S.Consumer.HandleTopLevelDecl(DG);
 }
 
-static void
-handleArrayOfStructWithResources(Sema &S, VarDecl *ParentVD,
-                                 const ConstantArrayType *CAT,
-                                 EmbeddedResourceNameBuilder &NameBuilder);
+static void handleArrayOfStructWithResources(
+    Sema &S, VarDecl *ParentVD, const ConstantArrayType *CAT,
+    EmbeddedResourceNameBuilder &NameBuilder, StructBindingContext 
&BindingCtx);
 
 // 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) {
+static void handleStructWithResources(Sema &S, VarDecl *ParentVD,
+                                      const CXXRecordDecl *RD,
+                                      EmbeddedResourceNameBuilder &NameBuilder,
+                                      StructBindingContext &BindingCtx) {
 
   // Scan the base classes.
   assert(RD->getNumBases() <= 1 && "HLSL doesn't support multiple 
inheritance");
@@ -4517,7 +4628,7 @@ handleStructWithResources(Sema &S, VarDecl *ParentVD, 
const CXXRecordDecl *RD,
     if (QT->isHLSLIntangibleType()) {
       CXXRecordDecl *BaseRD = QT->getAsCXXRecordDecl();
       NameBuilder.pushBaseName(BaseRD->getName());
-      handleStructWithResources(S, ParentVD, BaseRD, NameBuilder);
+      handleStructWithResources(S, ParentVD, BaseRD, NameBuilder, BindingCtx);
       NameBuilder.pop();
     }
   }
@@ -4532,14 +4643,15 @@ handleStructWithResources(Sema &S, VarDecl *ParentVD, 
const CXXRecordDecl *RD,
     if (isResourceRecordTypeOrArrayOf(FDTy)) {
       IdentifierInfo *II = NameBuilder.getNameAsIdentifier(S.getASTContext());
       createGlobalResourceDeclForStruct(S, ParentVD, FD->getLocation(), II,
-                                        FDTy);
+                                        FDTy, BindingCtx);
     } else if (const auto *RD = FDTy->getAsCXXRecordDecl()) {
-      handleStructWithResources(S, ParentVD, RD, NameBuilder);
+      handleStructWithResources(S, ParentVD, RD, NameBuilder, BindingCtx);
 
     } else if (const auto *ArrayTy = dyn_cast<ConstantArrayType>(FDTy)) {
       assert(!FDTy->isHLSLResourceRecordArray() &&
              "resource arrays should have been already handled");
-      handleArrayOfStructWithResources(S, ParentVD, ArrayTy, NameBuilder);
+      handleArrayOfStructWithResources(S, ParentVD, ArrayTy, NameBuilder,
+                                       BindingCtx);
     }
     NameBuilder.pop();
   }
@@ -4549,7 +4661,8 @@ handleStructWithResources(Sema &S, VarDecl *ParentVD, 
const CXXRecordDecl *RD,
 static void
 handleArrayOfStructWithResources(Sema &S, VarDecl *ParentVD,
                                  const ConstantArrayType *CAT,
-                                 EmbeddedResourceNameBuilder &NameBuilder) {
+                                 EmbeddedResourceNameBuilder &NameBuilder,
+                                 StructBindingContext &BindingCtx) {
 
   QualType ElementTy = CAT->getElementType().getCanonicalType();
   assert(ElementTy->isHLSLIntangibleType() && "Expected HLSL intangible type");
@@ -4563,9 +4676,11 @@ handleArrayOfStructWithResources(Sema &S, VarDecl 
*ParentVD,
   for (unsigned I = 0, E = CAT->getSize().getZExtValue(); I < E; ++I) {
     NameBuilder.pushArrayIndex(I);
     if (ElementRD)
-      handleStructWithResources(S, ParentVD, ElementRD, NameBuilder);
+      handleStructWithResources(S, ParentVD, ElementRD, NameBuilder,
+                                BindingCtx);
     else
-      handleArrayOfStructWithResources(S, ParentVD, SubCAT, NameBuilder);
+      handleArrayOfStructWithResources(S, ParentVD, SubCAT, NameBuilder,
+                                       BindingCtx);
     NameBuilder.pop();
   }
 }
@@ -4578,6 +4693,7 @@ handleArrayOfStructWithResources(Sema &S, VarDecl 
*ParentVD,
 // with the parent declaration (VD) through a HLSLAssociatedResourceDeclAttr
 // attribute.
 void SemaHLSL::handleGlobalStructOrArrayOfWithResources(VarDecl *VD) {
+  StructBindingContext BindingCtx(VD);
   EmbeddedResourceNameBuilder NameBuilder(VD->getName());
 
   const Type *VDTy = VD->getType().getTypePtr();
@@ -4586,13 +4702,13 @@ void 
SemaHLSL::handleGlobalStructOrArrayOfWithResources(VarDecl *VD) {
 
   const CXXRecordDecl *RD = VDTy->getAsCXXRecordDecl();
   if (RD) {
-    handleStructWithResources(SemaRef, VD, RD, NameBuilder);
+    handleStructWithResources(SemaRef, VD, RD, NameBuilder, BindingCtx);
     return;
   }
 
   const auto *CAT = dyn_cast<ConstantArrayType>(VDTy);
   if (CAT) {
-    handleArrayOfStructWithResources(SemaRef, VD, CAT, NameBuilder);
+    handleArrayOfStructWithResources(SemaRef, VD, CAT, NameBuilder, 
BindingCtx);
     return;
   }
 }
diff --git a/clang/test/AST/HLSL/resources-in-structs.hlsl 
b/clang/test/AST/HLSL/resources-in-structs.hlsl
index f6e8b26787adc..6f16633ce8620 100644
--- a/clang/test/AST/HLSL/resources-in-structs.hlsl
+++ b/clang/test/AST/HLSL/resources-in-structs.hlsl
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -ast-dump %s | 
FileCheck %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -ast-dump %s | 
FileCheck %s -check-prefixes=CHECK,DXIL
+// RUN: %clang_cc1 -triple spirv-unknown-vulkan-compute -ast-dump %s | 
FileCheck %s -check-prefixes=CHECK,SPIRV
 
 // Single resource field in struct
 
@@ -9,11 +10,14 @@ struct A {
 };
 
 // CHECK: VarDecl {{.*}} implicit a1.Buf 'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 0 0
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u0" "space0"
 
 // CHECK: VarDecl {{.*}} a1 'hlsl_constant A'
-// CHECK: HLSLResourceBindingAttr {{.*}} "u0" "space0"
+// CHECK-NEXT: HLSLVkBindingAttr {{.*}} 0 0
+// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} "u0" "space0"
 // CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'a1.Buf' 
'hlsl::RWBuffer<float>'
+[[vk::binding(0)]]
 A a1 : register(u0);
 
 // Resource array in struct
@@ -24,13 +28,17 @@ struct B {
   RWBuffer<float> Bufs[10];
 };
 
-// CHECK: VarDecl {{.*}} implicit b1.Bufs 'hlsl::RWBuffer<float>[10]'
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// Check when the struct has only [[vk::binding]] binding attribute.
+
+// CHECK: VarDecl {{.*}} b1.Bufs 'hlsl::RWBuffer<float>[10]'
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 2 0
 
 // CHECK: VarDecl {{.*}} b1 'hlsl_constant B'
-// CHECK: HLSLResourceBindingAttr {{.*}} "u2" "space0"
+// CHECK-NEXT: HLSLVkBindingAttr {{.*}} 2 0
 // CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'b1.Bufs' 
'hlsl::RWBuffer<float>[10]'
-B b1 : register(u2);
+[[vk::binding(2)]]
+B b1;
 
 // Inheritance
 
@@ -41,15 +49,19 @@ struct C : A {
 };
 
 // CHECK: VarDecl {{.*}} implicit c1.A::Buf 'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u3" "space0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 3 0
 
 // CHECK: VarDecl {{.*}} implicit c1.Buf2 'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u4" "space0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 4 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>'
+// CHECK-NEXT: HLSLVkBindingAttr {{.*}} 3 0
+// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} "u3" "space0"
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'c1.A::Buf' 
'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'c1.Buf2' 
'hlsl::RWBuffer<float>'
+[[vk::binding(3)]]
 C c1 : register(u3);
 
 // Inheritance with same named field
@@ -66,8 +78,8 @@ struct D : A {
 // 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>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'd1.A::Buf' 
'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'd1.A.Buf' 
'hlsl::RWBuffer<float>'
 D d1;
 
 // Inheritance and Multiple Resources Kinds
@@ -89,34 +101,49 @@ class F : E {
 };
 
 // CHECK: VarDecl {{.*}} implicit f.E::SrvBuf 'hlsl::StructuredBuffer<int>' 
callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "t0" "space0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 10 0
 
 // CHECK: VarDecl {{.*}} implicit f.a.Buf 'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u20" "space0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 11 0
 
 // CHECK: VarDecl {{.*}} implicit f.SrvBuf 'hlsl::StructuredBuffer<float>' 
callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "t1" "space0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 12 0
 
 // CHECK: VarDecl {{.*}} implicit f.Samp 'hlsl::SamplerState' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "s3" "space0"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 13 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'
+// CHECK-NEXT: HLSLVkBindingAttr {{.*}} 10 0
+// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} "t0" "space0"
+// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} "u20" "space0"
+// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} "s3" "space0"
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'f.E::SrvBuf' 
'hlsl::StructuredBuffer<int>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'f.a.Buf' 
'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'f.SrvBuf' 
'hlsl::StructuredBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'f.Samp' 
'hlsl::SamplerState'
+[[vk::binding(10)]]
 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"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u0" "space1"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 0 1
 
 // CHECK: VarDecl {{.*}} implicit arrayOfA.1.Buf 'hlsl::RWBuffer<float>' 
callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u1" "space1"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 1 1
+
+// CHECK: VarDecl {{.*}} arrayOfA 'hlsl_constant A[2]'
+// CHECK-NEXT: HLSLVkBindingAttr {{.*}} 0 1
+// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} "u0" "space1"
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'arrayOfA.0.Buf' 
'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 'arrayOfA.1.Buf' 
'hlsl::RWBuffer<float>'
+[[vk::binding(0, 1)]]
 A arrayOfA[2] : register(u0, space1);
 
 // CHECK: CXXRecordDecl {{.*}} struct G
@@ -126,38 +153,48 @@ struct G {
 };
 
 // CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.0.0.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u10" "space2"
+// SPRIV: HLSLVkBindingAttr {{.*}} Implicit 10 2
 
 // CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.0.1.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u11" "space2"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 11 2
 
 // CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.1.0.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
-
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u12" "space2"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 12 2
 // CHECK: VarDecl {{.*}} implicit gArray.0.multiArray.1.1.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u13" "space2"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 13 2
 
 // CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.0.0.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u14" "space2"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 14 2
 
 // CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.0.1.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u15" "space2"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 15 2
 
 // CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.1.0.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u16" "space2"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 16 2
 
 // CHECK: VarDecl {{.*}} implicit gArray.1.multiArray.1.1.Buf 
'hlsl::RWBuffer<float>' callinit
-// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0"
+// DXIL: HLSLResourceBindingAttr {{.*}} Implicit "u17" "space2"
+// SPIRV: HLSLVkBindingAttr {{.*}} Implicit 17 2
 
 // CHECK: VarDecl {{.*}} gArray 'hlsl_constant G[2]'
-// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.0.0.Buf' 
'hlsl::RWBuffer<float>'
-// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.0.1.Buf' 
'hlsl::RWBuffer<float>'
-// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.1.0.Buf' 
'hlsl::RWBuffer<float>'
-// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.0.multiArray.1.1.Buf' 
'hlsl::RWBuffer<float>'
-// CHECK: HLSLAssociatedResourceDeclAttr {{.*}} 'gArray.1.multiArray.0.0.Buf' 
'hlsl::RWBuffer<float>'
-// 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>'
+// CHECK-NEXT: HLSLVkBindingAttr {{.*}} 10 2
+// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} "u10" "space2"
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.0.multiArray.0.0.Buf' 'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.0.multiArray.0.1.Buf' 'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.0.multiArray.1.0.Buf' 'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.0.multiArray.1.1.Buf' 'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.1.multiArray.0.0.Buf' 'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.1.multiArray.0.1.Buf' 'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.1.multiArray.1.0.Buf' 'hlsl::RWBuffer<float>'
+// CHECK-NEXT: HLSLAssociatedResourceDeclAttr {{.*}} 
'gArray.1.multiArray.1.1.Buf' 'hlsl::RWBuffer<float>'
+[[vk::binding(10, 2)]]
 G gArray[2] : register(u10, space2);
 
 // Static struct with resources

>From be5da9208e9ed5816db2298152a3aae03869c227 Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Mon, 9 Mar 2026 13:35:13 -0700
Subject: [PATCH 5/8] clang-format

---
 clang/lib/Sema/SemaHLSL.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 0d1fc9d9cb7de..2f6530b3521ac 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -4843,7 +4843,7 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
     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<HLSLGroupSharedAddressSpaceAttr>())

>From 34006baedc586f4e40bbe8eb7db1bdd852694aae Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Tue, 17 Mar 2026 13:45:05 -0700
Subject: [PATCH 6/8] [HLSL] Implement CodeGen for accessing resource members
 of a struct

Fixes #182989
---
 clang/include/clang/AST/HLSLResource.h        |   1 +
 clang/lib/AST/HLSLResource.cpp                |  14 ++
 clang/lib/CodeGen/CGExpr.cpp                  |  16 +-
 clang/lib/CodeGen/CGHLSLRuntime.cpp           | 138 +++++++++++++++++-
 clang/lib/CodeGen/CGHLSLRuntime.h             |   2 +
 .../resources/resources-in-structs-array.hlsl |  83 +++++++++++
 .../resources-in-structs-inheritance.hlsl     | 132 +++++++++++++++++
 .../resources/resources-in-structs.hlsl       |  53 +++++++
 8 files changed, 428 insertions(+), 11 deletions(-)
 create mode 100644 
clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl
 create mode 100644 
clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
 create mode 100644 clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl

diff --git a/clang/include/clang/AST/HLSLResource.h 
b/clang/include/clang/AST/HLSLResource.h
index a37acb3660d00..aeea3bdac99af 100644
--- a/clang/include/clang/AST/HLSLResource.h
+++ b/clang/include/clang/AST/HLSLResource.h
@@ -128,6 +128,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 19321625222f3..4545d94f6a5a9 100644
--- a/clang/lib/AST/HLSLResource.cpp
+++ b/clang/lib/AST/HLSLResource.cpp
@@ -42,5 +42,19 @@ void EmbeddedResourceNameBuilder::pushArrayIndex(uint64_t 
Index) {
   OS << Index;
 }
 
+void EmbeddedResourceNameBuilder::pushBaseNameHierarchy(
+    CXXRecordDecl *DerivedRD, CXXRecordDecl *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 eebb36276e0eb..c1f478b2855db 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5472,10 +5472,20 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr 
*E) {
     EmitIgnoredExpr(E->getBase());
     return EmitDeclRefLValue(DRE);
   }
-  if (getLangOpts().HLSL &&
-      E->getType().getAddressSpace() == LangAS::hlsl_constant) {
+
+  if (getLangOpts().HLSL) {
+    QualType QT = E->getType();
     // We have an HLSL buffer - emit using HLSL's layout rules.
-    return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E);
+    if (QT.getAddressSpace() == LangAS::hlsl_constant)
+      return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E);
+
+    // Resource or resource array member of a global struct/class
+    if (QT->isHLSLResourceRecord() || QT->isHLSLResourceRecordArray()) {
+      std::optional<LValue> 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 c1329ede7430f..0e8f4be33df44 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -54,6 +54,7 @@ using namespace clang::hlsl;
 using namespace llvm;
 
 using llvm::hlsl::CBufferRowSizeInBytes;
+using EmbeddedResourceNameBuilder = clang::hlsl::EmbeddedResourceNameBuilder;
 
 namespace {
 
@@ -96,16 +97,105 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion 
RootSigVer,
   RootSignatureValMD->addOperand(MDVals);
 }
 
+// Gived 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 *getStructResourceParentDeclAndBuildName(
+    const MemberExpr *ME, EmbeddedResourceNameBuilder &NameBuilder) {
+
+  SmallVector<const Expr *> WorkList;
+  const VarDecl *VD = nullptr;
+  const Expr *E = ME;
+
+  while (!VD) {
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+      assert(isa<VarDecl>(DRE->getDecl()) &&
+             "member expr base is not a var decl");
+      VD = cast<VarDecl>(DRE->getDecl());
+      NameBuilder.pushName(VD->getName());
+      break;
+    }
+
+    WorkList.push_back(E);
+    if (const auto *ME = dyn_cast<MemberExpr>(E))
+      E = ME->getBase();
+    else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E))
+      E = ICE->getSubExpr();
+    else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
+      E = ASE->getBase();
+    else
+      llvm_unreachable("unexpected expr type in resource member access");
+  }
+
+  while (!WorkList.empty()) {
+    E = WorkList.pop_back_val();
+    if (const auto *ME = dyn_cast<MemberExpr>(E)) {
+      NameBuilder.pushName(
+          ME->getMemberNameInfo().getName().getAsIdentifierInfo()->getName());
+    } else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(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<ArraySubscriptExpr>(E)) {
+      const Expr *IdxExpr = ASE->getIdx();
+      std::optional<llvm::APSInt> 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 =
+      getStructResourceParentDeclAndBuildName(ME, NameBuilder);
+
+  if (!ParentVD->hasGlobalStorage())
+    return nullptr;
+
+  IdentifierInfo *II = NameBuilder.getNameAsIdentifier(AST);
+  for (const Attr *A : ParentVD->getAttrs()) {
+    const auto *ADA = dyn_cast<HLSLAssociatedResourceDeclAttr>(A);
+    if (!ADA)
+      continue;
+    VarDecl *AssocResVD = dyn_cast<VarDecl>(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<DeclRefExpr>(E->IgnoreImpCasts()))
+static const ValueDecl *getArrayDecl(ASTContext &AST, const Expr *E) {
+  E = E->IgnoreImpCasts();
+  if (const auto *DRE = dyn_cast_or_null<DeclRefExpr>(E))
     return DRE->getDecl();
+  if (isa<MemberExpr>(E))
+    return findAssociatedResourceDeclForStruct(AST, cast<MemberExpr>(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 +203,7 @@ static const ValueDecl *getArrayDecl(const 
ArraySubscriptExpr *ASE) {
       return nullptr;
     ASE = dyn_cast<ArraySubscriptExpr>(E);
   }
-  return getArrayDecl(E);
+  return getArrayDecl(AST, E);
 }
 
 // Get the total size of the array, or -1 if the array is unbounded.
@@ -1227,8 +1317,8 @@ std::optional<LValue> 
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<VarDecl>(getArrayDecl(ArraySubsExpr));
+  const VarDecl *ArrayDecl = dyn_cast_or_null<VarDecl>(
+      getArrayDecl(CGF.CGM.getContext(), ArraySubsExpr));
   if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
       ArrayDecl->getStorageClass() == SC_Static)
     return std::nullopt;
@@ -1325,7 +1415,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<VarDecl>(getArrayDecl(RHSExpr));
+  const VarDecl *ArrayDecl =
+      dyn_cast_or_null<VarDecl>(getArrayDecl(CGF.CGM.getContext(), RHSExpr));
   if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
       ArrayDecl->getStorageClass() == SC_Static)
     return false;
@@ -1448,6 +1539,37 @@ std::optional<LValue> 
CGHLSLRuntime::emitBufferArraySubscriptExpr(
   return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo);
 }
 
+std::optional<LValue>
+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<GlobalVariable>(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 466c809fdef78..1977dad693b52 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -291,6 +291,8 @@ class CGHLSLRuntime {
                       QualType CType);
 
   LValue emitBufferMemberExpr(CodeGenFunction &CGF, const MemberExpr *E);
+  std::optional<LValue> emitResourceMemberExpr(CodeGenFunction &CGF,
+                                               const MemberExpr *E);
 
 private:
   void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl,
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..283d3616aa660
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/resources-in-structs-array.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) }
+
+// Array of structs with resources
+struct A {
+  RWBuffer<float> 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"
+
+// Make sure they are initialized from binding
+//
+// CHECK: call void @hlsl::RWBuffer<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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]])
+
+[[vk::binding(10, 2)]]
+G gArray[2] : register(u10, space2);
+
+// CHECK: define internal void @main()()
+// CHECK-NEXT: entry:
+[numthreads(1, 1, 1)]
+void main() {
+
+// CHECK-NEXT: %[[PTR1:.*]] = call {{.*}} ptr 
@hlsl::RWBuffer<float>::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<float>::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<float>::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;
+}
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<float> Buf;
+};
+
+struct C : A {
+  RWBuffer<float> 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<int> SrvBufs[2];
+};
+
+class E : B {
+};
+
+class F : E {
+  A a;
+  StructuredBuffer<float> 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<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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<float>::__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<float>::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<float>::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<float>::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<float>::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<int>::__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<int>::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<float>::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<float>::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..ed16b5be0cbb0
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
@@ -0,0 +1,53 @@
+// 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<float> Buf;
+};
+
+// Global variable for resource a.Buf
+//
+// CHECK: @a.Buf = internal global %"class.hlsl::RWBuffer" poison, align 4
+// CHECK: @[[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<float> Bufs[10];
+};
+
+// Resource arrays do not have a global, they are initialized on demand. Just 
check the string name is generated correctly.
+//
+// CHECK: @[[bBufsStr:.*]] = private unnamed_addr constant [7 x i8] 
c"b.Bufs\00", align 1
+[[vk::binding(2)]]
+B b : register(u2);
+
+// 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<float>::__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: define internal void @main()()
+// CHECK-NEXT: entry:
+// CHECK-NEXT: %[[TMP:.*]] = alloca %"class.hlsl::RWBuffer", align 4
+[numthreads(1, 1, 1)]
+void main() {
+
+// CHECK-NEXT: %[[PTR:.*]] = call {{.*}} ptr 
@hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 
dereferenceable(4) @a.Buf, i32 noundef 0) #5
+// CHECK-NEXT: store float 0x3FF3AE1480000000, ptr %[[PTR]], 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-NEXT: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned 
int, unsigned int, int, unsigned int, char const*)
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer") align 4 %[[TMP]], i32 
noundef 2, i32 noundef 0, i32 noundef 10, i32 noundef 5, ptr noundef 
@[[bBufsStr]])
+// CHECK-NEXT: %[[PTR2:.*]] = call {{.*}} ptr 
@hlsl::RWBuffer<float>::operator[](unsigned int)(ptr {{.*}} %[[TMP]], i32 
noundef 0)
+// CHECK-NEXT: store float 0x40123D70A0000000, ptr %[[PTR2]], align 4
+// CHECK-NEXT: ret void
+  b.Bufs[5][0] = 4.56f;
+}

>From e4a1dd4a0e11ad17fe3cde4de70d86fc091ddf94 Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Thu, 2 Apr 2026 18:56:44 -0700
Subject: [PATCH 7/8] code review feedback - remove comments and using, update
 loop

Also prevent crash when accessing a resource member inside a member function. 
This will be implemented later.
---
 clang/lib/CodeGen/CGExpr.cpp        |  2 --
 clang/lib/CodeGen/CGHLSLRuntime.cpp | 13 +++++++++----
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 485f458f4a659..c05ce8ea7b4ce 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5514,11 +5514,9 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr 
*E) {
 
   if (getLangOpts().HLSL) {
     QualType QT = E->getType();
-    // We have an HLSL buffer - emit using HLSL's layout rules.
     if (QT.getAddressSpace() == LangAS::hlsl_constant)
       return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E);
 
-    // Resource or resource array member of a global struct/class
     if (QT->isHLSLResourceRecord() || QT->isHLSLResourceRecordArray()) {
       std::optional<LValue> LV;
       LV = CGM.getHLSLRuntime().emitResourceMemberExpr(*this, E);
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 3965bdefa2046..1cfdb854be589 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -54,7 +54,6 @@ using namespace clang::hlsl;
 using namespace llvm;
 
 using llvm::hlsl::CBufferRowSizeInBytes;
-using EmbeddedResourceNameBuilder = clang::hlsl::EmbeddedResourceNameBuilder;
 
 namespace {
 
@@ -105,14 +104,14 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion 
RootSigVer,
 // this function will find the VarDecl of "myStructArray" and use the
 // EmbeddedResourceNameBuilder to build the resource name
 // "myStructArray.0.memberA".
-static const VarDecl *getStructResourceParentDeclAndBuildName(
+static const VarDecl *findStructResourceParentDeclAndBuildName(
     const MemberExpr *ME, EmbeddedResourceNameBuilder &NameBuilder) {
 
   SmallVector<const Expr *> WorkList;
   const VarDecl *VD = nullptr;
   const Expr *E = ME;
 
-  while (!VD) {
+  for (;;) {
     if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
       assert(isa<VarDecl>(DRE->getDecl()) &&
              "member expr base is not a var decl");
@@ -128,6 +127,10 @@ static const VarDecl 
*getStructResourceParentDeclAndBuildName(
       E = ICE->getSubExpr();
     else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
       E = ASE->getBase();
+    else if (const auto *TE = dyn_cast<CXXThisExpr>(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");
   }
@@ -166,7 +169,9 @@ findAssociatedResourceDeclForStruct(ASTContext &AST, const 
MemberExpr *ME) {
 
   EmbeddedResourceNameBuilder NameBuilder;
   const VarDecl *ParentVD =
-      getStructResourceParentDeclAndBuildName(ME, NameBuilder);
+      findStructResourceParentDeclAndBuildName(ME, NameBuilder);
+  if (!ParentVD)
+    return nullptr;
 
   if (!ParentVD->hasGlobalStorage())
     return nullptr;

>From 6b3c20638d81d001a75b092cfb64942acdc3695d Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Tue, 7 Apr 2026 17:30:04 -0700
Subject: [PATCH 8/8] Fix warning

---
 clang/lib/CodeGen/CGHLSLRuntime.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 1cfdb854be589..7197f47641ad4 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -127,7 +127,7 @@ static const VarDecl 
*findStructResourceParentDeclAndBuildName(
       E = ICE->getSubExpr();
     else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
       E = ASE->getBase();
-    else if (const auto *TE = dyn_cast<CXXThisExpr>(E))
+    else if (isa<CXXThisExpr>(E))
       // Resource member access on "this" pointer not yet implemented
       // (llvm/llvm-project#190299)
       return nullptr;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to