Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -5917,7 +5917,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
12 changes: 7 additions & 5 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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;
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