[clang] [HLSL] Static resources (PR #166880)
Helena Kotas via cfe-commits
cfe-commits at lists.llvm.org
Wed Nov 19 11:22:41 PST 2025
https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/166880
>From 883df7bce01ddaedd133813b7e79397b974e6835 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Thu, 6 Nov 2025 12:37:29 -0800
Subject: [PATCH 1/5] [HLSL] Add internal linkage attribute to resources
HLSL resources should not be externally visible from the module.
We made sure of this by marking them `static` as soon as they were declared.
However, this prevents us fixing issue #166458 because there is no way
to know if a resource has been explicitly marked `static`
by the user, and can therefore be assigned to.
This change is moves from making all resources `static` to adding Clang internal
linkage attribute to all non-static resource declarations as a global scope.
Existing tests verify that there is no change in how the resource globals are
emitted: `internal global`.
---
clang/lib/Sema/SemaHLSL.cpp | 13 ++++++++-----
clang/test/AST/HLSL/cbuffer.hlsl | 2 +-
clang/test/AST/HLSL/private.hlsl | 2 +-
3 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index a06c57b15c585..e95fe16e6cb6c 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -3910,12 +3910,15 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
if (VD->getType()->isHLSLIntangibleType())
collectResourceBindingsOnVarDecl(VD);
- if (isResourceRecordTypeOrArrayOf(VD) ||
- VD->hasAttr<HLSLVkConstantIdAttr>()) {
- // Make the variable for resources static. The global externally visible
- // storage is accessed through the handle, which is a member. The variable
- // itself is not externally visible.
+ if (VD->hasAttr<HLSLVkConstantIdAttr>())
VD->setStorageClass(StorageClass::SC_Static);
+
+ if (isResourceRecordTypeOrArrayOf(VD) &&
+ VD->getStorageClass() != SC_Static) {
+ // Add internal linkage attribute to non-static resource variables. The
+ // global externally visible storage is accessed through the handle, which
+ // is a member. The variable itself is not externally visible.
+ VD->addAttr(InternalLinkageAttr::CreateImplicit(getASTContext()));
}
// process explicit bindings
diff --git a/clang/test/AST/HLSL/cbuffer.hlsl b/clang/test/AST/HLSL/cbuffer.hlsl
index f3c6636232798..b0b5b989e36c2 100644
--- a/clang/test/AST/HLSL/cbuffer.hlsl
+++ b/clang/test/AST/HLSL/cbuffer.hlsl
@@ -153,7 +153,7 @@ cbuffer CB {
static float SV;
// CHECK: VarDecl {{.*}} s7 'EmptyStruct' callinit
EmptyStruct s7;
- // CHECK: VarDecl {{.*}} Buf 'RWBuffer<float>':'hlsl::RWBuffer<float>' static callinit
+ // CHECK: VarDecl {{.*}} Buf 'RWBuffer<float>':'hlsl::RWBuffer<float>' callinit
RWBuffer<float> Buf;
// CHECK: VarDecl {{.*}} ea 'EmptyArrayTypedef':'float[10][0]'
EmptyArrayTypedef ea;
diff --git a/clang/test/AST/HLSL/private.hlsl b/clang/test/AST/HLSL/private.hlsl
index e00afb8f5cbd8..ba7380ec3cfda 100644
--- a/clang/test/AST/HLSL/private.hlsl
+++ b/clang/test/AST/HLSL/private.hlsl
@@ -3,7 +3,7 @@
// CHECK: VarDecl {{.*}} global_scalar 'hlsl_private int' static cinit
static int global_scalar = 0;
-// CHECK: VarDecl {{.*}} global_buffer 'RWBuffer<float>':'hlsl::RWBuffer<float>' static callinit
+// CHECK: VarDecl {{.*}} global_buffer 'RWBuffer<float>':'hlsl::RWBuffer<float>' callinit
RWBuffer<float> global_buffer;
class A {
>From 58161a23f0e4241e679c4d0bcbd0c8d55cb9682d Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Thu, 6 Nov 2025 17:40:22 -0800
Subject: [PATCH 2/5] [HLSL] Fix static resources
- Enable assignment to static resource variables (fixes #166458)
- Initialize static resources and resource arrays with default constructor that sets the handle to poison
---
clang/lib/CodeGen/CGHLSLRuntime.cpp | 5 +-
clang/lib/CodeGen/CodeGenModule.cpp | 3 +-
clang/lib/Sema/SemaHLSL.cpp | 12 ++--
clang/test/SemaHLSL/static_resources.hlsl | 86 +++++++++++++++++++++++
4 files changed, 98 insertions(+), 8 deletions(-)
create mode 100644 clang/test/SemaHLSL/static_resources.hlsl
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index e392a12044a39..2cf601ca6a424 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1025,12 +1025,13 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
ArraySubsExpr->getType()->isHLSLResourceRecordArray()) &&
"expected resource array subscript expression");
- // Let clang codegen handle local resource array subscripts,
+ // 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));
- if (!ArrayDecl || !ArrayDecl->hasGlobalStorage())
+ if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
+ ArrayDecl->getStorageClass() == SC_Static)
return std::nullopt;
// get the resource array type
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 0fea57b2e1799..b1256daafcdec 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -5918,7 +5918,8 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
(D->getType()->isHLSLResourceRecord() ||
D->getType()->isHLSLResourceRecordArray())) {
Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy));
- NeedsGlobalCtor = D->getType()->isHLSLResourceRecord();
+ NeedsGlobalCtor = D->getType()->isHLSLResourceRecord() ||
+ D->getStorageClass() == SC_Static;
} else if (D->hasAttr<LoaderUninitializedAttr>()) {
Init = llvm::UndefValue::get(getTypes().ConvertTypeForMem(ASTTy));
} else if (!InitExpr) {
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index e95fe16e6cb6c..1bcc074c080b2 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -3924,7 +3924,9 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
// process explicit bindings
processExplicitBindingsOnDecl(VD);
- if (VD->getType()->isHLSLResourceRecordArray()) {
+ // Add implicit binding attribute to non-static resource arrays.
+ if (VD->getType()->isHLSLResourceRecordArray() &&
+ VD->getStorageClass() != SC_Static) {
// If the resource array does not have an explicit binding attribute,
// create an implicit one. It will be used to transfer implicit binding
// order_ID to codegen.
@@ -4118,8 +4120,8 @@ bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) {
if (VD->getType().getAddressSpace() == LangAS::hlsl_constant)
return true;
- // Initialize resources at the global scope
- if (VD->hasGlobalStorage()) {
+ // Initialize non-static resources at the global scope.
+ if (VD->hasGlobalStorage() && VD->getStorageClass() != SC_Static) {
const Type *Ty = VD->getType().getTypePtr();
if (Ty->isHLSLResourceRecord())
return initGlobalResourceDecl(VD);
@@ -4143,10 +4145,10 @@ bool SemaHLSL::CheckResourceBinOp(BinaryOperatorKind Opc, Expr *LHSExpr,
while (auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
E = ASE->getBase()->IgnoreParenImpCasts();
- // Report error if LHS is a resource declared at a global scope.
+ // Report error if LHS is a non-static resource declared at a global scope.
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParens())) {
if (VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
- if (VD->hasGlobalStorage()) {
+ if (VD->hasGlobalStorage() && VD->getStorageClass() != SC_Static) {
// assignment to global resource is not allowed
SemaRef.Diag(Loc, diag::err_hlsl_assign_to_global_resource) << VD;
SemaRef.Diag(VD->getLocation(), diag::note_var_declared_here) << VD;
diff --git a/clang/test/SemaHLSL/static_resources.hlsl b/clang/test/SemaHLSL/static_resources.hlsl
new file mode 100644
index 0000000000000..9997bdf6dca7b
--- /dev/null
+++ b/clang/test/SemaHLSL/static_resources.hlsl
@@ -0,0 +1,86 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s
+
+// CHECK: [[ONE_STR:@.*]] = private unnamed_addr constant [4 x i8] c"One\00"
+// CHECK: [[ARRAY_STR:@.*]] = private unnamed_addr constant [6 x i8] c"Array\00"
+// CHECK-NOT: private unnamed_addr constant [{{[0-9]+}} x i8] c"Static
+
+RWBuffer<float> One : register(u1, space5);
+RWBuffer<float> Array[4][2] : register(u10, space6);
+
+// Check that the non-static resource One is initialized from binding on
+// startup (register 1, space 5).
+// 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 {{.*}} @One, i32 noundef 1, i32 noundef 5, i32 noundef 1, i32 noundef 0, ptr noundef [[ONE_STR]])
+
+// Note that non-static resource arrays are not initialized on startup.
+// The individual resources from the array are initialized on access.
+
+static RWBuffer<float> StaticOne;
+static RWBuffer<float> StaticArray[2];
+
+// Check that StaticOne resource is initialized on startup with the default
+// constructor and not from binding. It will initalize the handle to poison.
+// CHECK: define internal void @__cxx_global_var_init{{.*}}
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @hlsl::RWBuffer<float>::RWBuffer()(ptr {{.*}} @StaticOne)
+
+// Check that StaticArray elements are initialized on startup with the default
+// constructor and not from binding. The initializer will loop over the array
+// elements and call the default constructor for each one, setting the handle to poison.
+// CHECK: define internal void @__cxx_global_var_init{{.*}}
+// CHECK-NEXT: entry:
+// CHECK-NEXT: br label %arrayctor.loop
+// CHECK: arrayctor.loop: ; preds = %arrayctor.loop, %entry
+// CHECK-NEXT: %arrayctor.cur = phi ptr [ @StaticArray, %entry ], [ %arrayctor.next, %arrayctor.loop ]
+// CHECK-NEXT: call void @hlsl::RWBuffer<float>::RWBuffer()(ptr {{.*}} %arrayctor.cur)
+// CHECK-NEXT: %arrayctor.next = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %arrayctor.cur, i32 1
+// CHECK-NEXT: %arrayctor.done = icmp eq ptr %arrayctor.next, getelementptr inbounds (%"class.hlsl::RWBuffer", ptr @StaticArray, i32 2)
+// CHECK-NEXT: br i1 %arrayctor.done, label %arrayctor.cont, label %arrayctor.loop
+// CHECK: arrayctor.cont: ; preds = %arrayctor.loop
+// CHECK-NEXT: ret void
+
+// No other global initialization routines should be present.
+// CHECK-NOT: define internal void @__cxx_global_var_init{{.*}}
+
+[numthreads(4,1,1)]
+void main() {
+// CHECK: define internal void @main()()
+// CHECK-NEXT: entry:
+// CHECK-NEXT: %[[TMP0:.*]] = alloca %"class.hlsl::RWBuffer"
+
+ static RWBuffer<float> StaticLocal;
+// Check that StaticLocal is initialized to by default constructor to poison and not from binding
+// call void @hlsl::RWBuffer<float>::RWBuffer()(ptr {{.*}} @main()::StaticLocal)
+
+ StaticLocal = Array[2][0];
+// A[2][0] is accessed here, so it should be initialized from binding (register 10, space 6, index 4),
+// and then assigned to StaticLocal using = operator.
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
+// CHECK-SAME: (ptr {{.*}} %[[TMP0]], i32 noundef 10, i32 noundef 6, i32 noundef 8, i32 noundef 4, ptr noundef [[ARRAY_STR]])
+// CHECK-NEXT: call {{.*}} ptr @hlsl::RWBuffer<float>::operator=({{.*}})(ptr {{.*}} @main()::StaticLocal, ptr {{.*}} %[[TMP0]])
+
+ StaticOne = One;
+// CHECK-NEXT: call {{.*}} ptr @hlsl::RWBuffer<float>::operator=({{.*}})(ptr {{.*}} @StaticOne, ptr {{.*}} @One)
+
+ StaticArray[1] = One;
+// CHECK-NEXT: call {{.*}} ptr @hlsl::RWBuffer<float>::operator=(hlsl::RWBuffer<float> const&)
+// CHECK-SAME: (ptr {{.*}} getelementptr inbounds ([2 x %"class.hlsl::RWBuffer"], ptr @StaticArray, i32 0, i32 1), ptr {{.*}} @One)
+
+ StaticLocal[0] = 123;
+// CHECK-NEXT: %[[PTR0:.*]] = call {{.*}} ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr {{.*}} @main()::StaticLocal, i32 noundef 0)
+// CHECK-NEXT: store float 1.230000e+02, ptr %[[PTR0]]
+
+ StaticOne[1] = 456;
+// CHECK-NEXT: %[[PTR1:.*]] = call {{.*}} ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr {{.*}}) @StaticOne, i32 noundef 1)
+// CHECK-NEXT: store float 4.560000e+02, ptr %[[PTR1]], align 4
+
+ StaticArray[1][2] = 789;
+// CHECK-NEXT: %[[PTR2:.*]] = call {{.*}} ptr @hlsl::RWBuffer<float>::operator[](unsigned int)
+// CHECK-SAME: (ptr {{.*}} getelementptr inbounds ([2 x %"class.hlsl::RWBuffer"], ptr @StaticArray, i32 0, i32 1), i32 noundef 2)
+// CHECK-NEXT: store float 7.890000e+02, ptr %[[PTR2]], align 4
+}
+
+// No other binding initialization calls should be present.
+// CHECK-NOT: call void @hlsl::RWBuffer<float>::__createFrom{{.*}}Binding{{.*}}
>From 17bf204735551ea62fb458cbf1cacd2d4cb5dea9 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Mon, 10 Nov 2025 20:41:59 -0800
Subject: [PATCH 3/5] Add support for assigning whole array.
---
clang/lib/CodeGen/CGExpr.cpp | 9 +++-
clang/lib/CodeGen/CGHLSLRuntime.cpp | 56 +++++++++++++++++++++--
clang/lib/CodeGen/CGHLSLRuntime.h | 2 +
clang/test/SemaHLSL/static_resources.hlsl | 21 +++++++--
4 files changed, 79 insertions(+), 9 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 01f2161f27555..998b69af86dff 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6291,8 +6291,15 @@ LValue CodeGenFunction::EmitBinaryOperatorLValue(const BinaryOperator *E) {
LValue CodeGenFunction::EmitHLSLArrayAssignLValue(const BinaryOperator *E) {
// Don't emit an LValue for the RHS because it might not be an LValue
LValue LHS = EmitLValue(E->getLHS());
+
+ // If the RHS is a global resource array, copy all individual resources
+ // into LHS.
+ if (E->getRHS()->getType()->isHLSLResourceRecordArray())
+ if (CGM.getHLSLRuntime().emitResourceArrayCopy(LHS, E->getRHS(), *this))
+ return LHS;
+
// In C the RHS of an assignment operator is an RValue.
- // EmitAggregateAssign takes anan LValue for the RHS. Instead we can call
+ // EmitAggregateAssign takes an LValue for the RHS. Instead we can call
// EmitInitializationToLValue to emit an RValue into an LValue.
EmitInitializationToLValue(E->getRHS(), LHS);
return LHS;
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 2cf601ca6a424..46e9307a2fe4d 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -21,6 +21,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
#include "clang/AST/HLSLResource.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Type.h"
@@ -91,6 +92,14 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion RootSigVer,
RootSignatureValMD->addOperand(MDVals);
}
+// 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()))
+ return DRE->getDecl();
+ return nullptr;
+}
+
// Find array variable declaration from nested array subscript AST nodes
static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) {
const Expr *E = nullptr;
@@ -100,9 +109,7 @@ static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) {
return nullptr;
ASE = dyn_cast<ArraySubscriptExpr>(E);
}
- if (const DeclRefExpr *DRE = dyn_cast_or_null<DeclRefExpr>(E))
- return DRE->getDecl();
- return nullptr;
+ return getArrayDecl(E);
}
// Get the total size of the array, or -1 if the array is unbounded.
@@ -1116,3 +1123,46 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
}
return CGF.MakeAddrLValue(TmpVar, ResultTy, AlignmentSource::Decl);
}
+
+// If RHSExpr is a global resource array, initialize all of its resources and
+// set them into LHS. Returns false if no copy has been performed and the
+// array copy should be handled by Clang codegen.
+bool CGHLSLRuntime::emitResourceArrayCopy(LValue &LHS, Expr *RHSExpr,
+ CodeGenFunction &CGF) {
+ QualType ResultTy = RHSExpr->getType();
+ 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));
+ if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
+ ArrayDecl->getStorageClass() == SC_Static)
+ return false;
+
+ // Find binding info for the resource array. For implicit binding
+ // the HLSLResourceBindingAttr should have been added by SemaHLSL.
+ ResourceBindingAttrs Binding(ArrayDecl);
+ assert((Binding.hasBinding()) &&
+ "resource array must have a binding attribute");
+
+ // Find the individual resource type.
+ ASTContext &AST = ArrayDecl->getASTContext();
+ QualType ResTy = AST.getBaseElementType(ResultTy);
+ const auto *ResArrayTy = cast<ConstantArrayType>(ResultTy.getTypePtr());
+
+ // Use the provided LHS for the result.
+ AggValueSlot ValueSlot = AggValueSlot::forAddr(
+ LHS.getAddress(), Qualifiers(), AggValueSlot::IsDestructed_t(true),
+ AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false),
+ AggValueSlot::DoesNotOverlap);
+
+ // Create Value for index and total array size (= range size).
+ int Size = getTotalArraySize(AST, ResArrayTy);
+ llvm::Value *Zero = llvm::ConstantInt::get(CGM.IntTy, 0);
+ llvm::Value *Range = llvm::ConstantInt::get(CGM.IntTy, Size);
+
+ // Initialize individual resources in the array into LHS.
+ std::optional<llvm::Value *> EndIndex = initializeLocalResourceArray(
+ CGF, ResTy->getAsCXXRecordDecl(), ResArrayTy, ValueSlot, Range, Zero,
+ ArrayDecl->getName(), Binding, {Zero}, RHSExpr->getExprLoc());
+ return EndIndex.has_value();
+}
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 9d31714ab8606..1f1bad3d5e337 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -195,6 +195,8 @@ class CGHLSLRuntime {
emitResourceArraySubscriptExpr(const ArraySubscriptExpr *E,
CodeGenFunction &CGF);
+ bool emitResourceArrayCopy(LValue &LHS, Expr *RHSExpr, CodeGenFunction &CGF);
+
private:
void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl,
llvm::GlobalVariable *BufGV);
diff --git a/clang/test/SemaHLSL/static_resources.hlsl b/clang/test/SemaHLSL/static_resources.hlsl
index 9997bdf6dca7b..cab3346ff7d7f 100644
--- a/clang/test/SemaHLSL/static_resources.hlsl
+++ b/clang/test/SemaHLSL/static_resources.hlsl
@@ -5,7 +5,7 @@
// CHECK-NOT: private unnamed_addr constant [{{[0-9]+}} x i8] c"Static
RWBuffer<float> One : register(u1, space5);
-RWBuffer<float> Array[4][2] : register(u10, space6);
+RWBuffer<float> Array[2] : register(u10, space6);
// Check that the non-static resource One is initialized from binding on
// startup (register 1, space 5).
@@ -51,20 +51,31 @@ void main() {
// CHECK-NEXT: %[[TMP0:.*]] = alloca %"class.hlsl::RWBuffer"
static RWBuffer<float> StaticLocal;
-// Check that StaticLocal is initialized to by default constructor to poison and not from binding
+// Check that StaticLocal is initialized by default constructor (handle set to poison)
+// and not from binding.
// call void @hlsl::RWBuffer<float>::RWBuffer()(ptr {{.*}} @main()::StaticLocal)
- StaticLocal = Array[2][0];
-// A[2][0] is accessed here, so it should be initialized from binding (register 10, space 6, index 4),
+ StaticLocal = Array[1];
+// A[2][0] is accessed here, so it should be initialized from binding (register 10, space 6, index 1),
// and then assigned to StaticLocal using = operator.
// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
-// CHECK-SAME: (ptr {{.*}} %[[TMP0]], i32 noundef 10, i32 noundef 6, i32 noundef 8, i32 noundef 4, ptr noundef [[ARRAY_STR]])
+// CHECK-SAME: (ptr {{.*}} %[[TMP0]], i32 noundef 10, i32 noundef 6, i32 noundef 2, i32 noundef 1, ptr noundef [[ARRAY_STR]])
// CHECK-NEXT: call {{.*}} ptr @hlsl::RWBuffer<float>::operator=({{.*}})(ptr {{.*}} @main()::StaticLocal, ptr {{.*}} %[[TMP0]])
StaticOne = One;
+// Operator = call to assign non-static One handle to static StaticOne.
// CHECK-NEXT: call {{.*}} ptr @hlsl::RWBuffer<float>::operator=({{.*}})(ptr {{.*}} @StaticOne, ptr {{.*}} @One)
+ StaticArray = Array;
+// Check that each elements of StaticArray is initialized from binding (register 10, space 6, indices 0 and 1).
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer") align 4 @StaticArray, i32 noundef 10, i32 noundef 6, i32 noundef 2, i32 noundef 0, ptr noundef [[ARRAY_STR]])
+// 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 getelementptr ([2 x %"class.hlsl::RWBuffer"], ptr @StaticArray, i32 0, i32 1),
+// CHECK-SAME: i32 noundef 10, i32 noundef 6, i32 noundef 2, i32 noundef 1, ptr noundef [[ARRAY_STR]]
+
StaticArray[1] = One;
+// Operator = call to assign non-static One handle to StaticArray element.
// CHECK-NEXT: call {{.*}} ptr @hlsl::RWBuffer<float>::operator=(hlsl::RWBuffer<float> const&)
// CHECK-SAME: (ptr {{.*}} getelementptr inbounds ([2 x %"class.hlsl::RWBuffer"], ptr @StaticArray, i32 0, i32 1), ptr {{.*}} @One)
>From d52b410909150c3444d82c9b95b1d2154f22052f Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Mon, 10 Nov 2025 20:47:22 -0800
Subject: [PATCH 4/5] tiny cleanup
---
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 46e9307a2fe4d..f16fe6d8d85cf 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1032,7 +1032,7 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
ArraySubsExpr->getType()->isHLSLResourceRecordArray()) &&
"expected resource array subscript expression");
- // Let Clang codegen handle local and static resource array subscripts,
+ // 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 =
>From 36d35371941235bb0480371df42175170385d0aa Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Mon, 17 Nov 2025 19:39:50 -0800
Subject: [PATCH 5/5] Add structured buffer resource to the test
---
clang/test/SemaHLSL/static_resources.hlsl | 45 ++++++++++++++++++++++-
1 file changed, 43 insertions(+), 2 deletions(-)
diff --git a/clang/test/SemaHLSL/static_resources.hlsl b/clang/test/SemaHLSL/static_resources.hlsl
index cab3346ff7d7f..f71e9ea98e0d9 100644
--- a/clang/test/SemaHLSL/static_resources.hlsl
+++ b/clang/test/SemaHLSL/static_resources.hlsl
@@ -1,11 +1,15 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s
-// CHECK: [[ONE_STR:@.*]] = private unnamed_addr constant [4 x i8] c"One\00"
-// CHECK: [[ARRAY_STR:@.*]] = private unnamed_addr constant [6 x i8] c"Array\00"
+// CHECK-DAG: [[ONE_STR:@.*]] = private unnamed_addr constant [4 x i8] c"One\00"
+// CHECK-DAG: [[ARRAY_STR:@.*]] = private unnamed_addr constant [6 x i8] c"Array\00"
+// CHECK-DAG: [[ONEWITHCOUNTER_STR:@.*]] = private unnamed_addr constant [15 x i8] c"OneWithCounter\00"
+// CHECK-DAG: [[ARRAYWITHCOUNTER_STR:@.*]] = private unnamed_addr constant [17 x i8] c"ArrayWithCounter\00"
// CHECK-NOT: private unnamed_addr constant [{{[0-9]+}} x i8] c"Static
RWBuffer<float> One : register(u1, space5);
RWBuffer<float> Array[2] : register(u10, space6);
+RWStructuredBuffer<int> OneWithCounter : register(u2, space4);
+RWStructuredBuffer<int> ArrayWithCounter[2] : register(u7, space4);
// Check that the non-static resource One is initialized from binding on
// startup (register 1, space 5).
@@ -14,6 +18,13 @@ RWBuffer<float> Array[2] : register(u10, space6);
// CHECK-NEXT: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
// CHECK-SAME: (ptr {{.*}} @One, i32 noundef 1, i32 noundef 5, i32 noundef 1, i32 noundef 0, ptr noundef [[ONE_STR]])
+// Check that the non-static resource OneWithCounter is initialized from binding on
+// startup (register 2, space 4).
+// CHECK: define internal void @__cxx_global_var_init{{.*}}
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @hlsl::RWStructuredBuffer<int>::__createFromBindingWithImplicitCounter(unsigned int, unsigned int, int, unsigned int, char const*, unsigned int)
+// CHECK-SAME: (ptr {{.*}} @OneWithCounter, i32 noundef 2, i32 noundef 4, i32 noundef 1, i32 noundef 0, ptr noundef [[ONEWITHCOUNTER_STR]], i32 noundef 0)
+
// Note that non-static resource arrays are not initialized on startup.
// The individual resources from the array are initialized on access.
@@ -41,6 +52,14 @@ static RWBuffer<float> StaticArray[2];
// CHECK: arrayctor.cont: ; preds = %arrayctor.loop
// CHECK-NEXT: ret void
+static RWStructuredBuffer<int> StaticOneWithCounter;
+
+// Check that StaticOneWithCounter resource is initialized on startup with the default
+// constructor and not from binding. It will initalize the handle to poison.
+// CHECK: define internal void @__cxx_global_var_init{{.*}}
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call void @hlsl::RWStructuredBuffer<int>::RWStructuredBuffer()(ptr {{.*}} @StaticOneWithCounter)
+
// No other global initialization routines should be present.
// CHECK-NOT: define internal void @__cxx_global_var_init{{.*}}
@@ -91,6 +110,28 @@ void main() {
// CHECK-NEXT: %[[PTR2:.*]] = call {{.*}} ptr @hlsl::RWBuffer<float>::operator[](unsigned int)
// CHECK-SAME: (ptr {{.*}} getelementptr inbounds ([2 x %"class.hlsl::RWBuffer"], ptr @StaticArray, i32 0, i32 1), i32 noundef 2)
// CHECK-NEXT: store float 7.890000e+02, ptr %[[PTR2]], align 4
+
+ static RWStructuredBuffer<int> StaticLocalWithCounter;
+// Check that StaticLocalWithCounter is initialized by default constructor (handle set to poison)
+// and not from binding.
+// call void @hlsl::RWStructuredBuffer<int>::RWStructuredBuffer()(ptr {{.*}} @main()::StaticLocalWithCounter)
+
+ static RWStructuredBuffer<int> StaticLocalArrayWithCounter[2];
+
+ StaticLocalWithCounter = OneWithCounter;
+// Operator = call to assign non-static OneWithCounter handles to StaticLocalWithCounter handles.
+// CHECK: call {{.*}} ptr @hlsl::RWStructuredBuffer<int>::operator=(hlsl::RWStructuredBuffer<int> const&)(ptr {{.*}} @main()::StaticLocalWithCounter, ptr {{.*}} @OneWithCounter)
+
+ StaticLocalArrayWithCounter = ArrayWithCounter;
+// Check that each elements of StaticLocalArrayWithCounter is initialized from binding
+// of ArrayWithCounter (register 7, space 4, indices 0 and 1).
+// CHECK: call void @hlsl::RWStructuredBuffer<int>::__createFromBindingWithImplicitCounter(unsigned int, unsigned int, int, unsigned int, char const*, unsigned int)
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 @main()::StaticLocalArrayWithCounter,
+// CHECK-SAME: i32 noundef 7, i32 noundef 4, i32 noundef 2, i32 noundef 0, ptr noundef [[ARRAYWITHCOUNTER_STR]], i32 noundef 1)
+
+// CHECK-NEXT: call void @hlsl::RWStructuredBuffer<int>::__createFromBindingWithImplicitCounter(unsigned int, unsigned int, int, unsigned int, char const*, unsigned int)
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 getelementptr ([2 x %"class.hlsl::RWStructuredBuffer"], ptr @main()::StaticLocalArrayWithCounter, i32 0, i32 1),
+// CHECK-SAME: i32 noundef 7, i32 noundef 4, i32 noundef 2, i32 noundef 1, ptr noundef [[ARRAYWITHCOUNTER_STR]], i32 noundef 1)
}
// No other binding initialization calls should be present.
More information about the cfe-commits
mailing list