Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
61 changes: 56 additions & 5 deletions clang/lib/CodeGen/CGHLSLRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -1025,12 +1032,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
Expand Down Expand Up @@ -1115,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();
}
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CGHLSLRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
25 changes: 15 additions & 10 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3910,18 +3910,23 @@ 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
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.
Expand Down Expand Up @@ -4115,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);
Expand All @@ -4140,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;
Expand Down
2 changes: 1 addition & 1 deletion clang/test/AST/HLSL/cbuffer.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion clang/test/AST/HLSL/private.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
97 changes: 97 additions & 0 deletions clang/test/SemaHLSL/static_resources.hlsl
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add tests with a RWStructuredBuffer? I'd like to see that the counter variable is properly copied as well.

Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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[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 by default constructor (handle set to poison)
// and not from binding.
// call void @hlsl::RWBuffer<float>::RWBuffer()(ptr {{.*}} @main()::StaticLocal)

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 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)

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{{.*}}
Loading