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 <[email protected]> Date: Thu, 6 Nov 2025 12:37:29 -0800 Subject: [PATCH 1/6] [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 <[email protected]> Date: Thu, 6 Nov 2025 17:40:22 -0800 Subject: [PATCH 2/6] [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 <[email protected]> Date: Mon, 10 Nov 2025 20:41:59 -0800 Subject: [PATCH 3/6] 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 <[email protected]> Date: Mon, 10 Nov 2025 20:47:22 -0800 Subject: [PATCH 4/6] 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 <[email protected]> Date: Mon, 17 Nov 2025 19:39:50 -0800 Subject: [PATCH 5/6] 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. >From 6e697fdf8d26fda27eb4548d3c5443017ca95216 Mon Sep 17 00:00:00 2001 From: Helena Kotas <[email protected]> Date: Wed, 19 Nov 2025 11:53:24 -0800 Subject: [PATCH 6/6] clang-format --- clang/lib/CodeGen/CGHLSLRuntime.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index fa7b9c4682371..dbcc193172d00 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -258,7 +258,7 @@ class CGHLSLRuntime { emitResourceArraySubscriptExpr(const ArraySubscriptExpr *E, CodeGenFunction &CGF); bool emitResourceArrayCopy(LValue &LHS, Expr *RHSExpr, CodeGenFunction &CGF); - + std::optional<LValue> emitBufferArraySubscriptExpr( const ArraySubscriptExpr *E, CodeGenFunction &CGF, llvm::function_ref<llvm::Value *(bool Promote)> EmitIdxAfterBase); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
