Skip to content

Commit eb3d884

Browse files
authored
[HLSL] Global resource arrays element access (llvm#152454)
Adds support for accessing individual resources from fixed-size global resource arrays. Design proposal: https://github.com/llvm/wg-hlsl/blob/main/proposals/0028-resource-arrays.md Enables indexing into globally scoped, fixed-size resource arrays to retrieve individual resources. The initialization logic is primarily handled during codegen. When a global resource array is indexed, the codegen translates the `ArraySubscriptExpr` AST node into a constructor call for the corresponding resource record type and binding. To support this behavior, Sema needs to ensure that: - The constructor for the specific resource type is instantiated. - An implicit binding attribute is added to resource arrays that lack explicit bindings (llvm#152452). Closes llvm#145424
1 parent 0c512f7 commit eb3d884

File tree

12 files changed

+462
-49
lines changed

12 files changed

+462
-49
lines changed

clang/include/clang/Sema/SemaHLSL.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,17 @@ class SemaHLSL : public SemaBase {
229229

230230
void diagnoseAvailabilityViolations(TranslationUnitDecl *TU);
231231

232-
bool initGlobalResourceDecl(VarDecl *VD);
233232
uint32_t getNextImplicitBindingOrderID() {
234233
return ImplicitBindingNextOrderID++;
235234
}
235+
236+
bool initGlobalResourceDecl(VarDecl *VD);
237+
bool initGlobalResourceArrayDecl(VarDecl *VD);
238+
void createResourceRecordCtorArgs(const Type *ResourceTy, StringRef VarName,
239+
HLSLResourceBindingAttr *RBA,
240+
HLSLVkBindingAttr *VkBinding,
241+
uint32_t ArrayIndex,
242+
llvm::SmallVectorImpl<Expr *> &Args);
236243
};
237244

238245
} // namespace clang

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "CGCall.h"
1717
#include "CGCleanup.h"
1818
#include "CGDebugInfo.h"
19+
#include "CGHLSLRuntime.h"
1920
#include "CGObjCRuntime.h"
2021
#include "CGOpenMPRuntime.h"
2122
#include "CGRecordLayout.h"
@@ -4534,6 +4535,15 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
45344535
LHS.getBaseInfo(), TBAAAccessInfo());
45354536
}
45364537

4538+
// The HLSL runtime handle the subscript expression on global resource arrays.
4539+
if (getLangOpts().HLSL && (E->getType()->isHLSLResourceRecord() ||
4540+
E->getType()->isHLSLResourceRecordArray())) {
4541+
std::optional<LValue> LV =
4542+
CGM.getHLSLRuntime().emitResourceArraySubscriptExpr(E, *this);
4543+
if (LV.has_value())
4544+
return *LV;
4545+
}
4546+
45374547
// All the other cases basically behave like simple offsetting.
45384548

45394549
// Handle the extvector case we ignored above.

clang/lib/CodeGen/CGHLSLRuntime.cpp

Lines changed: 195 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "clang/AST/RecursiveASTVisitor.h"
2323
#include "clang/AST/Type.h"
2424
#include "clang/Basic/TargetOptions.h"
25+
#include "llvm/ADT/SmallString.h"
2526
#include "llvm/ADT/SmallVector.h"
2627
#include "llvm/Frontend/HLSL/RootSignatureMetadata.h"
2728
#include "llvm/IR/Constants.h"
@@ -84,6 +85,111 @@ void addRootSignature(llvm::dxbc::RootSignatureVersion RootSigVer,
8485
RootSignatureValMD->addOperand(MDVals);
8586
}
8687

88+
// Find array variable declaration from nested array subscript AST nodes
89+
static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) {
90+
const Expr *E = nullptr;
91+
while (ASE != nullptr) {
92+
E = ASE->getBase()->IgnoreImpCasts();
93+
if (!E)
94+
return nullptr;
95+
ASE = dyn_cast<ArraySubscriptExpr>(E);
96+
}
97+
if (const DeclRefExpr *DRE = dyn_cast_or_null<DeclRefExpr>(E))
98+
return DRE->getDecl();
99+
return nullptr;
100+
}
101+
102+
// Get the total size of the array, or -1 if the array is unbounded.
103+
static int getTotalArraySize(ASTContext &AST, const clang::Type *Ty) {
104+
Ty = Ty->getUnqualifiedDesugaredType();
105+
assert(Ty->isArrayType() && "expected array type");
106+
if (Ty->isIncompleteArrayType())
107+
return -1;
108+
return AST.getConstantArrayElementCount(cast<ConstantArrayType>(Ty));
109+
}
110+
111+
// Find constructor decl for a specific resource record type and binding
112+
// (implicit vs. explicit). The constructor has 5 parameters.
113+
// For explicit binding the signature is:
114+
// void(unsigned, unsigned, int, unsigned, const char *).
115+
// For implicit binding the signature is:
116+
// void(unsigned, int, unsigned, unsigned, const char *).
117+
static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST,
118+
QualType ResTy,
119+
bool ExplicitBinding) {
120+
std::array<QualType, 5> ExpParmTypes = {
121+
AST.UnsignedIntTy, AST.UnsignedIntTy, AST.UnsignedIntTy,
122+
AST.UnsignedIntTy, AST.getPointerType(AST.CharTy.withConst())};
123+
ExpParmTypes[ExplicitBinding ? 2 : 1] = AST.IntTy;
124+
125+
CXXRecordDecl *ResDecl = ResTy->getAsCXXRecordDecl();
126+
for (auto *Ctor : ResDecl->ctors()) {
127+
if (Ctor->getNumParams() != ExpParmTypes.size())
128+
continue;
129+
auto *ParmIt = Ctor->param_begin();
130+
auto ExpTyIt = ExpParmTypes.begin();
131+
for (; ParmIt != Ctor->param_end() && ExpTyIt != ExpParmTypes.end();
132+
++ParmIt, ++ExpTyIt) {
133+
if ((*ParmIt)->getType() != *ExpTyIt)
134+
break;
135+
}
136+
if (ParmIt == Ctor->param_end())
137+
return Ctor;
138+
}
139+
llvm_unreachable("did not find constructor for resource class");
140+
}
141+
142+
static Value *buildNameForResource(llvm::StringRef BaseName,
143+
CodeGenModule &CGM) {
144+
llvm::SmallString<64> GlobalName = {BaseName, ".str"};
145+
return CGM.GetAddrOfConstantCString(BaseName.str(), GlobalName.c_str())
146+
.getPointer();
147+
}
148+
149+
static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD,
150+
llvm::Value *ThisPtr, llvm::Value *Range,
151+
llvm::Value *Index, StringRef Name,
152+
HLSLResourceBindingAttr *RBA,
153+
HLSLVkBindingAttr *VkBinding,
154+
CallArgList &Args) {
155+
assert((VkBinding || RBA) && "at least one a binding attribute expected");
156+
157+
std::optional<uint32_t> RegisterSlot;
158+
uint32_t SpaceNo = 0;
159+
if (VkBinding) {
160+
RegisterSlot = VkBinding->getBinding();
161+
SpaceNo = VkBinding->getSet();
162+
} else {
163+
if (RBA->hasRegisterSlot())
164+
RegisterSlot = RBA->getSlotNumber();
165+
SpaceNo = RBA->getSpaceNumber();
166+
}
167+
168+
ASTContext &AST = CD->getASTContext();
169+
Value *NameStr = buildNameForResource(Name, CGM);
170+
Value *Space = llvm::ConstantInt::get(CGM.IntTy, SpaceNo);
171+
172+
Args.add(RValue::get(ThisPtr), CD->getThisType());
173+
if (RegisterSlot.has_value()) {
174+
// explicit binding
175+
auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, RegisterSlot.value());
176+
Args.add(RValue::get(RegSlot), AST.UnsignedIntTy);
177+
Args.add(RValue::get(Space), AST.UnsignedIntTy);
178+
Args.add(RValue::get(Range), AST.IntTy);
179+
Args.add(RValue::get(Index), AST.UnsignedIntTy);
180+
181+
} else {
182+
// implicit binding
183+
auto *OrderID =
184+
llvm::ConstantInt::get(CGM.IntTy, RBA->getImplicitBindingOrderID());
185+
Args.add(RValue::get(Space), AST.UnsignedIntTy);
186+
Args.add(RValue::get(Range), AST.IntTy);
187+
Args.add(RValue::get(Index), AST.UnsignedIntTy);
188+
Args.add(RValue::get(OrderID), AST.UnsignedIntTy);
189+
}
190+
Args.add(RValue::get(NameStr), AST.getPointerType(AST.CharTy.withConst()));
191+
}
192+
87193
} // namespace
88194

89195
llvm::Type *
@@ -589,13 +695,6 @@ static void initializeBuffer(CodeGenModule &CGM, llvm::GlobalVariable *GV,
589695
CGM.AddCXXGlobalInit(InitResFunc);
590696
}
591697

592-
static Value *buildNameForResource(llvm::StringRef BaseName,
593-
CodeGenModule &CGM) {
594-
std::string Str(BaseName);
595-
std::string GlobalName(Str + ".str");
596-
return CGM.GetAddrOfConstantCString(Str, GlobalName.c_str()).getPointer();
597-
}
598-
599698
void CGHLSLRuntime::initializeBufferFromBinding(const HLSLBufferDecl *BufDecl,
600699
llvm::GlobalVariable *GV,
601700
HLSLVkBindingAttr *VkBinding) {
@@ -623,17 +722,13 @@ void CGHLSLRuntime::initializeBufferFromBinding(const HLSLBufferDecl *BufDecl,
623722
auto *Index = llvm::ConstantInt::get(CGM.IntTy, 0);
624723
auto *RangeSize = llvm::ConstantInt::get(CGM.IntTy, 1);
625724
auto *Space = llvm::ConstantInt::get(CGM.IntTy, RBA->getSpaceNumber());
626-
Value *Name = nullptr;
725+
Value *Name = buildNameForResource(BufDecl->getName(), CGM);
627726

628727
llvm::Intrinsic::ID IntrinsicID =
629728
RBA->hasRegisterSlot()
630729
? CGM.getHLSLRuntime().getCreateHandleFromBindingIntrinsic()
631730
: CGM.getHLSLRuntime().getCreateHandleFromImplicitBindingIntrinsic();
632731

633-
std::string Str(BufDecl->getName());
634-
std::string GlobalName(Str + ".str");
635-
Name = CGM.GetAddrOfConstantCString(Str, GlobalName.c_str()).getPointer();
636-
637732
// buffer with explicit binding
638733
if (RBA->hasRegisterSlot()) {
639734
auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, RBA->getSlotNumber());
@@ -700,3 +795,91 @@ void CGHLSLRuntime::emitInitListOpaqueValues(CodeGenFunction &CGF,
700795
}
701796
}
702797
}
798+
799+
std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
800+
const ArraySubscriptExpr *ArraySubsExpr, CodeGenFunction &CGF) {
801+
assert(ArraySubsExpr->getType()->isHLSLResourceRecord() ||
802+
ArraySubsExpr->getType()->isHLSLResourceRecordArray() &&
803+
"expected resource array subscript expression");
804+
805+
// let clang codegen handle local resource array subscripts
806+
const VarDecl *ArrayDecl = dyn_cast<VarDecl>(getArrayDecl(ArraySubsExpr));
807+
if (!ArrayDecl || !ArrayDecl->hasGlobalStorage())
808+
return std::nullopt;
809+
810+
if (ArraySubsExpr->getType()->isArrayType())
811+
// FIXME: this is not yet implemented (llvm/llvm-project#145426)
812+
llvm_unreachable(
813+
"indexing of sub-arrays of multidimensional arrays not supported yet");
814+
815+
// get the resource array type
816+
ASTContext &AST = ArrayDecl->getASTContext();
817+
const Type *ResArrayTy = ArrayDecl->getType().getTypePtr();
818+
assert(ResArrayTy->isHLSLResourceRecordArray() &&
819+
"expected array of resource classes");
820+
821+
// Iterate through all nested array subscript expressions to calculate
822+
// the index in the flattened resource array (if this is a multi-
823+
// dimensional array). The index is calculated as a sum of all indices
824+
// multiplied by the total size of the array at that level.
825+
Value *Index = nullptr;
826+
const ArraySubscriptExpr *ASE = ArraySubsExpr;
827+
while (ASE != nullptr) {
828+
Value *SubIndex = CGF.EmitScalarExpr(ASE->getIdx());
829+
if (const auto *ArrayTy =
830+
dyn_cast<ConstantArrayType>(ASE->getType().getTypePtr())) {
831+
Value *Multiplier = llvm::ConstantInt::get(
832+
CGM.IntTy, AST.getConstantArrayElementCount(ArrayTy));
833+
SubIndex = CGF.Builder.CreateMul(SubIndex, Multiplier);
834+
}
835+
836+
Index = Index ? CGF.Builder.CreateAdd(Index, SubIndex) : SubIndex;
837+
ASE = dyn_cast<ArraySubscriptExpr>(ASE->getBase()->IgnoreParenImpCasts());
838+
}
839+
840+
// find binding info for the resource array (for implicit binding
841+
// an HLSLResourceBindingAttr should have been added by SemaHLSL)
842+
QualType ResourceTy = ArraySubsExpr->getType();
843+
HLSLVkBindingAttr *VkBinding = ArrayDecl->getAttr<HLSLVkBindingAttr>();
844+
HLSLResourceBindingAttr *RBA = ArrayDecl->getAttr<HLSLResourceBindingAttr>();
845+
assert((VkBinding || RBA) && "resource array must have a binding attribute");
846+
847+
// lookup the resource class constructor based on the resource type and
848+
// binding
849+
CXXConstructorDecl *CD = findResourceConstructorDecl(
850+
AST, ResourceTy, VkBinding || RBA->hasRegisterSlot());
851+
852+
// create a temporary variable for the resource class instance (we need to
853+
// return an LValue)
854+
RawAddress TmpVar = CGF.CreateMemTemp(ResourceTy);
855+
if (CGF.EmitLifetimeStart(TmpVar.getPointer()))
856+
CGF.pushFullExprCleanup<CodeGenFunction::CallLifetimeEnd>(
857+
NormalEHLifetimeMarker, TmpVar);
858+
859+
AggValueSlot ValueSlot = AggValueSlot::forAddr(
860+
TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true),
861+
AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false),
862+
AggValueSlot::DoesNotOverlap);
863+
864+
Address ThisAddress = ValueSlot.getAddress();
865+
llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo(
866+
ThisAddress, CD->getThisType()->getPointeeType());
867+
868+
// get total array size (= range size)
869+
llvm::Value *Range =
870+
llvm::ConstantInt::get(CGM.IntTy, getTotalArraySize(AST, ResArrayTy));
871+
872+
// assemble the constructor parameters
873+
CallArgList Args;
874+
createResourceCtorArgs(CGM, CD, ThisPtr, Range, Index, ArrayDecl->getName(),
875+
RBA, VkBinding, Args);
876+
877+
// call the constructor
878+
CGF.EmitCXXConstructorCall(CD, Ctor_Complete, false, false, ThisAddress, Args,
879+
ValueSlot.mayOverlap(),
880+
ArraySubsExpr->getExprLoc(),
881+
ValueSlot.isSanitizerChecked());
882+
883+
return CGF.MakeAddrLValue(TmpVar, ArraySubsExpr->getType(),
884+
AlignmentSource::Decl);
885+
}

clang/lib/CodeGen/CGHLSLRuntime.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ class Type;
6868
class RecordType;
6969
class DeclContext;
7070
class HLSLPackOffsetAttr;
71+
class ArraySubscriptExpr;
7172

7273
class FunctionDecl;
7374

7475
namespace CodeGen {
7576

7677
class CodeGenModule;
7778
class CodeGenFunction;
79+
class LValue;
7880

7981
class CGHLSLRuntime {
8082
public:
@@ -164,6 +166,10 @@ class CGHLSLRuntime {
164166
llvm::TargetExtType *LayoutTy);
165167
void emitInitListOpaqueValues(CodeGenFunction &CGF, InitListExpr *E);
166168

169+
std::optional<LValue>
170+
emitResourceArraySubscriptExpr(const ArraySubscriptExpr *E,
171+
CodeGenFunction &CGF);
172+
167173
private:
168174
void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl,
169175
llvm::GlobalVariable *BufGV);

clang/lib/CodeGen/CodeGenModule.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5796,11 +5796,16 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
57965796
(D->getType()->isCUDADeviceBuiltinSurfaceType() ||
57975797
D->getType()->isCUDADeviceBuiltinTextureType());
57985798
if (getLangOpts().CUDA &&
5799-
(IsCUDASharedVar || IsCUDAShadowVar || IsCUDADeviceShadowVar))
5799+
(IsCUDASharedVar || IsCUDAShadowVar || IsCUDADeviceShadowVar)) {
58005800
Init = llvm::UndefValue::get(getTypes().ConvertTypeForMem(ASTTy));
5801-
else if (D->hasAttr<LoaderUninitializedAttr>())
5801+
} else if (getLangOpts().HLSL &&
5802+
(D->getType()->isHLSLResourceRecord() ||
5803+
D->getType()->isHLSLResourceRecordArray())) {
5804+
Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy));
5805+
NeedsGlobalCtor = D->getType()->isHLSLResourceRecord();
5806+
} else if (D->hasAttr<LoaderUninitializedAttr>()) {
58025807
Init = llvm::UndefValue::get(getTypes().ConvertTypeForMem(ASTTy));
5803-
else if (!InitExpr) {
5808+
} else if (!InitExpr) {
58045809
// This is a tentative definition; tentative definitions are
58055810
// implicitly initialized with { 0 }.
58065811
//
@@ -5821,11 +5826,7 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
58215826
if (D->getType()->isReferenceType())
58225827
T = D->getType();
58235828

5824-
if (getLangOpts().HLSL &&
5825-
D->getType().getTypePtr()->isHLSLResourceRecord()) {
5826-
Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy));
5827-
NeedsGlobalCtor = true;
5828-
} else if (getLangOpts().CPlusPlus) {
5829+
if (getLangOpts().CPlusPlus) {
58295830
Init = EmitNullConstant(T);
58305831
if (!IsDefinitionAvailableExternally)
58315832
NeedsGlobalCtor = true;

0 commit comments

Comments
 (0)