Skip to content

Commit 87a8d40

Browse files
authored
[HLSL] Add codegen for accessing resource members of a struct (2nd merge attempt) (llvm#193584)
Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable. When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access. Fixes llvm#182989 This is the second try to land this. The [first one](llvm#187127 with llvm#188792 and both PRs had to be reverted. No updates needed to this change. I synced with @inbelic and we agreed that this one should go in first.
1 parent 0459273 commit 87a8d40

9 files changed

Lines changed: 525 additions & 38 deletions

File tree

clang/include/clang/AST/HLSLResource.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ inline uint32_t getResourceDimensions(llvm::dxil::ResourceDimension Dim) {
109109
llvm_unreachable("Unhandled llvm::dxil::ResourceDimension enum.");
110110
}
111111

112+
// Returns true if the second field of the record is a counter resource handle
113+
inline bool hasCounterHandle(const CXXRecordDecl *RD) {
114+
if (RD->field_empty())
115+
return false;
116+
auto It = std::next(RD->field_begin());
117+
if (It == RD->field_end())
118+
return false;
119+
const FieldDecl *SecondField = *It;
120+
if (const auto *ResTy =
121+
SecondField->getType()->getAs<HLSLAttributedResourceType>()) {
122+
return ResTy->getAttrs().IsCounter;
123+
}
124+
return false;
125+
}
126+
112127
// Helper class for building a name of a global resource variable that
113128
// gets created for a resource embedded in a struct or class. This will
114129
// also be used from CodeGen to build a name that matches the resource
@@ -128,6 +143,7 @@ class EmbeddedResourceNameBuilder {
128143
void pushName(llvm::StringRef N) { pushName(N, FieldDelim); }
129144
void pushBaseName(llvm::StringRef N);
130145
void pushArrayIndex(uint64_t Index);
146+
void pushBaseNameHierarchy(CXXRecordDecl *DerivedRD, CXXRecordDecl *BaseRD);
131147

132148
void pop() {
133149
assert(!Offsets.empty() && "no name to pop");

clang/lib/AST/HLSLResource.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,20 @@ void EmbeddedResourceNameBuilder::pushArrayIndex(uint64_t Index) {
4141
OS << Index;
4242
}
4343

44+
void EmbeddedResourceNameBuilder::pushBaseNameHierarchy(
45+
CXXRecordDecl *DerivedRD, CXXRecordDecl *BaseRD) {
46+
assert(BaseRD != DerivedRD && DerivedRD->isDerivedFrom(BaseRD));
47+
Offsets.push_back(Name.size());
48+
Name.append(FieldDelim);
49+
while (BaseRD != DerivedRD) {
50+
assert(DerivedRD->getNumBases() == 1 &&
51+
"HLSL does not support multiple inheritance");
52+
DerivedRD = DerivedRD->bases_begin()->getType()->getAsCXXRecordDecl();
53+
assert(DerivedRD && "base class not found");
54+
Name.append(DerivedRD->getName());
55+
Name.append(BaseClassDelim);
56+
}
57+
}
58+
4459
} // namespace hlsl
4560
} // namespace clang

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5545,10 +5545,18 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
55455545
EmitIgnoredExpr(E->getBase());
55465546
return EmitDeclRefLValue(DRE);
55475547
}
5548-
if (getLangOpts().HLSL &&
5549-
E->getType().getAddressSpace() == LangAS::hlsl_constant) {
5550-
// We have an HLSL buffer - emit using HLSL's layout rules.
5551-
return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E);
5548+
5549+
if (getLangOpts().HLSL) {
5550+
QualType QT = E->getType();
5551+
if (QT.getAddressSpace() == LangAS::hlsl_constant)
5552+
return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E);
5553+
5554+
if (QT->isHLSLResourceRecord() || QT->isHLSLResourceRecordArray()) {
5555+
std::optional<LValue> LV;
5556+
LV = CGM.getHLSLRuntime().emitResourceMemberExpr(*this, E);
5557+
if (LV.has_value())
5558+
return *LV;
5559+
}
55525560
}
55535561

55545562
Expr *BaseExpr = E->getBase();

clang/lib/CodeGen/CGHLSLRuntime.cpp

Lines changed: 146 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,24 +96,120 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion RootSigVer,
9696
RootSignatureValMD->addOperand(MDVals);
9797
}
9898

99+
// Given a MemberExpr of a resource or resource array type, find the parent
100+
// VarDecl of the struct or class instance that contains this resource and
101+
// build the full resource name based on the member access path.
102+
//
103+
// For example, for a member access like "myStructArray[0].memberA",
104+
// this function will find the VarDecl of "myStructArray" and use the
105+
// EmbeddedResourceNameBuilder to build the resource name
106+
// "myStructArray.0.memberA".
107+
static const VarDecl *findStructResourceParentDeclAndBuildName(
108+
const MemberExpr *ME, EmbeddedResourceNameBuilder &NameBuilder) {
109+
110+
SmallVector<const Expr *> WorkList;
111+
const VarDecl *VD = nullptr;
112+
const Expr *E = ME;
113+
114+
for (;;) {
115+
if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
116+
assert(isa<VarDecl>(DRE->getDecl()) &&
117+
"member expr base is not a var decl");
118+
VD = cast<VarDecl>(DRE->getDecl());
119+
NameBuilder.pushName(VD->getName());
120+
break;
121+
}
122+
123+
WorkList.push_back(E);
124+
if (const auto *MExp = dyn_cast<MemberExpr>(E))
125+
E = MExp->getBase();
126+
else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E))
127+
E = ICE->getSubExpr();
128+
else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
129+
E = ASE->getBase();
130+
else if (isa<CXXThisExpr>(E))
131+
// Resource member access on "this" pointer not yet implemented
132+
// (llvm/llvm-project#190299)
133+
return nullptr;
134+
else
135+
llvm_unreachable("unexpected expr type in resource member access");
136+
137+
assert(E && "expected valid expression");
138+
}
139+
140+
while (!WorkList.empty()) {
141+
E = WorkList.pop_back_val();
142+
if (const auto *ME = dyn_cast<MemberExpr>(E)) {
143+
NameBuilder.pushName(
144+
ME->getMemberNameInfo().getName().getAsIdentifierInfo()->getName());
145+
} else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E)) {
146+
if (ICE->getCastKind() == CK_UncheckedDerivedToBase) {
147+
CXXRecordDecl *DerivedRD =
148+
ICE->getSubExpr()->getType()->getAsCXXRecordDecl();
149+
CXXRecordDecl *BaseRD = ICE->getType()->getAsCXXRecordDecl();
150+
NameBuilder.pushBaseNameHierarchy(DerivedRD, BaseRD);
151+
}
152+
} else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
153+
const Expr *IdxExpr = ASE->getIdx();
154+
std::optional<llvm::APSInt> Value =
155+
IdxExpr->getIntegerConstantExpr(VD->getASTContext());
156+
assert(Value &&
157+
"expected constant index in struct with resource array access");
158+
NameBuilder.pushArrayIndex(Value->getZExtValue());
159+
} else {
160+
llvm_unreachable("unexpected expr type in resource member access");
161+
}
162+
}
163+
return VD;
164+
}
165+
166+
// Given a MemberExpr of a resource or resource array type, find the
167+
// corresponding global resource declaration associated with the owning struct
168+
// or class instance via HLSLAssociatedResourceDeclAttr.
169+
static const VarDecl *
170+
findAssociatedResourceDeclForStruct(ASTContext &AST, const MemberExpr *ME) {
171+
172+
EmbeddedResourceNameBuilder NameBuilder;
173+
const VarDecl *ParentVD =
174+
findStructResourceParentDeclAndBuildName(ME, NameBuilder);
175+
if (!ParentVD)
176+
return nullptr;
177+
178+
if (!ParentVD->hasGlobalStorage())
179+
return nullptr;
180+
181+
IdentifierInfo *II = NameBuilder.getNameAsIdentifier(AST);
182+
for (const Attr *A : ParentVD->getAttrs()) {
183+
if (const auto *ADA = dyn_cast<HLSLAssociatedResourceDeclAttr>(A)) {
184+
VarDecl *AssocResVD = ADA->getResDecl();
185+
if (AssocResVD->getIdentifier() == II)
186+
return AssocResVD;
187+
}
188+
}
189+
return nullptr;
190+
}
191+
99192
// Find array variable declaration from DeclRef expression
100-
static const ValueDecl *getArrayDecl(const Expr *E) {
101-
if (const DeclRefExpr *DRE =
102-
dyn_cast_or_null<DeclRefExpr>(E->IgnoreImpCasts()))
193+
static const ValueDecl *getArrayDecl(ASTContext &AST, const Expr *E) {
194+
E = E->IgnoreImpCasts();
195+
if (const auto *DRE = dyn_cast_or_null<DeclRefExpr>(E))
103196
return DRE->getDecl();
197+
if (isa<MemberExpr>(E))
198+
return findAssociatedResourceDeclForStruct(AST, cast<MemberExpr>(E));
104199
return nullptr;
105200
}
106201

107202
// Find array variable declaration from nested array subscript AST nodes
108-
static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) {
203+
static const ValueDecl *getArrayDecl(ASTContext &AST,
204+
const ArraySubscriptExpr *ASE) {
109205
const Expr *E = nullptr;
110206
while (ASE != nullptr) {
111207
E = ASE->getBase()->IgnoreImpCasts();
112208
if (!E)
113209
return nullptr;
114210
ASE = dyn_cast<ArraySubscriptExpr>(E);
115211
}
116-
return getArrayDecl(E);
212+
return getArrayDecl(AST, E);
117213
}
118214

119215
// Get the total size of the array, or 0 if the array is unbounded.
@@ -152,6 +248,10 @@ static CXXMethodDecl *lookupResourceInitMethodAndSetupArgs(
152248
Value *NameStr = buildNameForResource(Name, CGM);
153249
Value *Space = llvm::ConstantInt::get(CGM.IntTy, Binding.getSpace());
154250

251+
bool HasCounter = hasCounterHandle(ResourceDecl);
252+
assert((!HasCounter || Binding.hasCounterImplicitOrderID()) &&
253+
"resources with counter handle must have a binding with counter "
254+
"implicit order ID");
155255
if (Binding.isExplicit()) {
156256
// explicit binding
157257
auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, Binding.getSlot());
@@ -174,7 +274,7 @@ static CXXMethodDecl *lookupResourceInitMethodAndSetupArgs(
174274
Args.add(RValue::get(Range), AST.IntTy);
175275
Args.add(RValue::get(Index), AST.UnsignedIntTy);
176276
Args.add(RValue::get(NameStr), AST.getPointerType(AST.CharTy.withConst()));
177-
if (Binding.hasCounterImplicitOrderID()) {
277+
if (HasCounter) {
178278
uint32_t CounterBinding = Binding.getCounterImplicitOrderID();
179279
auto *CounterOrderID = llvm::ConstantInt::get(CGM.IntTy, CounterBinding);
180280
Args.add(RValue::get(CounterOrderID), AST.UnsignedIntTy);
@@ -1243,8 +1343,8 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
12431343
// Let clang codegen handle local and static resource array subscripts,
12441344
// or when the subscript references on opaque expression (as part of
12451345
// ArrayInitLoopExpr AST node).
1246-
const VarDecl *ArrayDecl =
1247-
dyn_cast_or_null<VarDecl>(getArrayDecl(ArraySubsExpr));
1346+
const VarDecl *ArrayDecl = dyn_cast_or_null<VarDecl>(
1347+
getArrayDecl(CGF.CGM.getContext(), ArraySubsExpr));
12481348
if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
12491349
ArrayDecl->getStorageClass() == SC_Static)
12501350
return std::nullopt;
@@ -1309,11 +1409,15 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
13091409
CGF.CGM, ResourceTy->getAsCXXRecordDecl(), Range, Index,
13101410
ArrayDecl->getName(), Binding, Args);
13111411

1312-
if (!CreateMethod)
1412+
if (!CreateMethod) {
13131413
// This can happen if someone creates an array of structs that looks like
13141414
// an HLSL resource record array but it does not have the required static
13151415
// create method. No binding will be generated for it.
1416+
assert(!ResourceTy->getAsCXXRecordDecl()->isImplicit() &&
1417+
"create method lookup should always succeed for built-in resource "
1418+
"records");
13161419
return std::nullopt;
1420+
}
13171421

13181422
callResourceInitMethod(CGF, CreateMethod, Args, ValueSlot.getAddress());
13191423

@@ -1341,7 +1445,8 @@ bool CGHLSLRuntime::emitResourceArrayCopy(LValue &LHS, Expr *RHSExpr,
13411445
assert(ResultTy->isHLSLResourceRecordArray() && "expected resource array");
13421446

13431447
// Let Clang codegen handle local and static resource array copies.
1344-
const VarDecl *ArrayDecl = dyn_cast_or_null<VarDecl>(getArrayDecl(RHSExpr));
1448+
const VarDecl *ArrayDecl =
1449+
dyn_cast_or_null<VarDecl>(getArrayDecl(CGF.CGM.getContext(), RHSExpr));
13451450
if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
13461451
ArrayDecl->getStorageClass() == SC_Static)
13471452
return false;
@@ -1464,6 +1569,37 @@ std::optional<LValue> CGHLSLRuntime::emitBufferArraySubscriptExpr(
14641569
return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo);
14651570
}
14661571

1572+
std::optional<LValue>
1573+
CGHLSLRuntime::emitResourceMemberExpr(CodeGenFunction &CGF,
1574+
const MemberExpr *ME) {
1575+
assert((ME->getType()->isHLSLResourceRecord() ||
1576+
ME->getType()->isHLSLResourceRecordArray()) &&
1577+
"expected resource member expression");
1578+
1579+
if (ME->getType()->isHLSLResourceRecordArray()) {
1580+
// FIXME: Handle member access of the whole array of resources
1581+
// (llvm/llvm-project#187087). Access to individual resource array elements
1582+
// is already handled in emitResourceArraySubscriptExpr.
1583+
return std::nullopt;
1584+
}
1585+
1586+
const VarDecl *ResourceVD =
1587+
findAssociatedResourceDeclForStruct(CGF.CGM.getContext(), ME);
1588+
if (!ResourceVD)
1589+
return std::nullopt;
1590+
1591+
GlobalVariable *ResGV =
1592+
cast<GlobalVariable>(CGM.GetAddrOfGlobalVar(ResourceVD));
1593+
const DataLayout &DL = CGM.getDataLayout();
1594+
llvm::Type *Ty = ResGV->getValueType();
1595+
CharUnits Align = CharUnits::fromQuantity(DL.getABITypeAlign(Ty));
1596+
Address Addr = Address(ResGV, Ty, Align);
1597+
LValue LV = LValue::MakeAddr(Addr, ME->getType(), CGM.getContext(),
1598+
LValueBaseInfo(AlignmentSource::Type),
1599+
CGM.getTBAAAccessInfo(ME->getType()));
1600+
return LV;
1601+
}
1602+
14671603
namespace {
14681604
/// Utility for emitting copies following the HLSL buffer layout rules (ie,
14691605
/// copying out of a cbuffer).

clang/lib/CodeGen/CGHLSLRuntime.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ class CGHLSLRuntime {
304304
QualType CType);
305305

306306
LValue emitBufferMemberExpr(CodeGenFunction &CGF, const MemberExpr *E);
307+
std::optional<LValue> emitResourceMemberExpr(CodeGenFunction &CGF,
308+
const MemberExpr *E);
307309

308310
private:
309311
void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl,

clang/lib/Sema/SemaHLSL.cpp

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,20 +1489,6 @@ static CXXMethodDecl *lookupMethod(Sema &S, CXXRecordDecl *RecordDecl,
14891489

14901490
} // end anonymous namespace
14911491

1492-
static bool hasCounterHandle(const CXXRecordDecl *RD) {
1493-
if (RD->field_empty())
1494-
return false;
1495-
auto It = std::next(RD->field_begin());
1496-
if (It == RD->field_end())
1497-
return false;
1498-
const FieldDecl *SecondField = *It;
1499-
if (const auto *ResTy =
1500-
SecondField->getType()->getAs<HLSLAttributedResourceType>()) {
1501-
return ResTy->getAttrs().IsCounter;
1502-
}
1503-
return false;
1504-
}
1505-
15061492
bool SemaHLSL::handleRootSignatureElements(
15071493
ArrayRef<hlsl::RootSignatureElement> Elements) {
15081494
// Define some common error handling functions
@@ -5060,7 +5046,7 @@ class StructBindingContext {
50605046
// Creates a binding attribute for a resource based on the gathered attributes
50615047
// and the required register type and range.
50625048
Attr *createBindingAttr(SemaHLSL &S, ASTContext &AST, RegisterType RegType,
5063-
unsigned Range) {
5049+
unsigned Range, bool HasCounter) {
50645050
assert(static_cast<unsigned>(RegType) < 4 && "unexpected register type");
50655051

50665052
if (VkBindingAttr) {
@@ -5095,6 +5081,9 @@ class StructBindingContext {
50955081
RBA ? RBA->getSpaceNumber() : 0);
50965082
NewAttr->setImplicitBindingOrderID(S.getNextImplicitBindingOrderID());
50975083
}
5084+
if (HasCounter)
5085+
NewAttr->setImplicitCounterBindingOrderID(
5086+
S.getNextImplicitBindingOrderID());
50985087
return NewAttr;
50995088
}
51005089
};
@@ -5116,18 +5105,20 @@ static void createGlobalResourceDeclForStruct(
51165105
VarDecl::Create(AST, DC, Loc, Loc, Id, ResTy, nullptr, SC_None);
51175106

51185107
unsigned Range = 1;
5119-
const HLSLAttributedResourceType *ResHandleTy = nullptr;
5120-
if (const auto *AT = dyn_cast<ArrayType>(ResTy.getTypePtr())) {
5108+
const Type *SingleResTy = ResTy.getTypePtr()->getUnqualifiedDesugaredType();
5109+
while (const auto *AT = dyn_cast<ArrayType>(SingleResTy)) {
51215110
const auto *CAT = dyn_cast<ConstantArrayType>(AT);
5122-
Range = CAT ? CAT->getSize().getZExtValue() : 0;
5123-
ResHandleTy = getResourceArrayHandleType(ResTy);
5124-
} else {
5125-
ResHandleTy = HLSLAttributedResourceType::findHandleTypeOnResource(
5126-
ResTy.getTypePtr());
5111+
Range = CAT ? (Range * CAT->getSize().getZExtValue()) : 0;
5112+
SingleResTy =
5113+
AT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType();
51275114
}
5115+
const HLSLAttributedResourceType *ResHandleTy =
5116+
HLSLAttributedResourceType::findHandleTypeOnResource(SingleResTy);
5117+
51285118
// Add a binding attribute to the global resource declaration.
5119+
bool HasCounter = hasCounterHandle(SingleResTy->getAsCXXRecordDecl());
51295120
Attr *BindingAttr = BindingCtx.createBindingAttr(
5130-
S.HLSL(), AST, getRegisterType(ResHandleTy), Range);
5121+
S.HLSL(), AST, getRegisterType(ResHandleTy), Range, HasCounter);
51315122
ResDecl->addAttr(BindingAttr);
51325123
ResDecl->addAttr(InternalLinkageAttr::CreateImplicit(AST));
51335124
ResDecl->setImplicit();
@@ -5366,11 +5357,15 @@ bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) {
53665357
CreateMethod =
53675358
lookupMethod(SemaRef, ResourceDecl, CreateMethodName, VD->getLocation());
53685359

5369-
if (!CreateMethod)
5360+
if (!CreateMethod) {
53705361
// This can happen if someone creates a struct that looks like an HLSL
53715362
// resource record but does not have the required static create method.
53725363
// No binding will be generated for it.
5364+
assert(!ResourceDecl->isImplicit() &&
5365+
"create method lookup should always succeed for built-in resource "
5366+
"records");
53735367
return false;
5368+
}
53745369

53755370
if (Binding.isExplicit()) {
53765371
IntegerLiteral *RegSlot =

0 commit comments

Comments
 (0)