[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