[llvm-branch-commits] [clang] [HLSL] Implement CodeGen for accessing resource members of a struct (PR #187127)

Helena Kotas via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Mar 17 13:53:15 PDT 2026


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

Depends on #184731

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes #182989

>From 34006baedc586f4e40bbe8eb7db1bdd852694aae Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 17 Mar 2026 13:45:05 -0700
Subject: [PATCH] [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;
+}



More information about the llvm-branch-commits mailing list