Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7a0d3ca
[HLSL] Reorder the arguments of handle initialization builtins
hekota Aug 28, 2025
dcbbda4
[HLSL] Add static methods for resource initialization and a construct…
hekota Aug 28, 2025
02342c9
[HLSL] Use static create methods to initialize individual resources
hekota Sep 2, 2025
b13a530
cleanup
hekota Sep 3, 2025
a0cfc72
Merge branch 'main' of https://github.com/llvm/llvm-project into res-…
hekota Sep 3, 2025
1e6f4de
Merge branch 'users/hekota/pr155861-res-create-0-reorder-builtin-args…
hekota Sep 3, 2025
dfc07bd
Remove handle constructor, update create methods body and expected AS…
hekota Sep 4, 2025
a41cf8a
Merge branch 'main' of https://github.com/llvm/llvm-project into res-…
hekota Sep 4, 2025
e7641e1
cleanup
hekota Sep 4, 2025
6028194
clang-format
hekota Sep 4, 2025
ea93f39
Merge branch 'users/hekota/pr155866-res-create-1-add-methods' into re…
hekota Sep 4, 2025
e7d6ee6
Update tests after create methods body change
hekota Sep 4, 2025
7e9d4c5
[HLSL] Use static create methods to initialize resources in arrays
hekota Sep 4, 2025
84883bc
Merge branch 'main' of https://github.com/llvm/llvm-project into res-…
hekota Sep 4, 2025
7782eea
Update tests to use llvm-cxxfilt to have unmangled names in the basel…
hekota Sep 4, 2025
f64525e
Merge branch 'users/hekota/pr155866-res-create-1-add-methods' of http…
hekota Sep 4, 2025
b7e5126
Merge branch 'users/hekota/pr156544-res-create-2-use-methods-single-r…
hekota Sep 4, 2025
6669bd7
Update tests to use llvm-cxxfilt to have names without mangling
hekota Sep 5, 2025
0d1bc12
fix newlines
hekota Sep 5, 2025
75e6b0f
Remove unused function
hekota Sep 5, 2025
dfa4144
Merge branch 'main' of https://github.com/llvm/llvm-project into res-…
hekota Sep 10, 2025
c7b35b9
update PR after merge from main that introduced explicit copy constru…
hekota Sep 11, 2025
fd7b37a
Merge branch 'users/hekota/pr156544-res-create-2-use-methods-single-r…
hekota Sep 11, 2025
152471e
update unbounded resources test after merge from main
hekota Sep 11, 2025
886ab3c
add comment
hekota Sep 16, 2025
3e85c31
Merge branch 'main' of https://github.com/llvm/llvm-project into res-…
hekota Sep 16, 2025
acd45af
cleanup after merge, add one more comment
hekota Sep 16, 2025
363d463
clang-format
hekota Sep 16, 2025
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
5 changes: 0 additions & 5 deletions clang/include/clang/Sema/SemaHLSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,6 @@ class SemaHLSL : public SemaBase {

bool initGlobalResourceDecl(VarDecl *VD);
bool initGlobalResourceArrayDecl(VarDecl *VD);
void createResourceRecordCtorArgs(const Type *ResourceTy, StringRef VarName,
HLSLResourceBindingAttr *RBA,
HLSLVkBindingAttr *VkBinding,
uint32_t ArrayIndex,
llvm::SmallVectorImpl<Expr *> &Args);
};

} // namespace clang
Expand Down
163 changes: 80 additions & 83 deletions clang/lib/CodeGen/CGHLSLRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//

#include "CGHLSLRuntime.h"
#include "Address.h"
#include "CGDebugInfo.h"
#include "CodeGenFunction.h"
#include "CodeGenModule.h"
Expand All @@ -39,6 +40,7 @@
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include <cstdint>
#include <optional>

using namespace clang;
using namespace CodeGen;
Expand Down Expand Up @@ -111,52 +113,29 @@ static int getTotalArraySize(ASTContext &AST, const clang::Type *Ty) {
return AST.getConstantArrayElementCount(cast<ConstantArrayType>(Ty));
}

// Find constructor decl for a specific resource record type and binding
// (implicit vs. explicit). The constructor has 5 parameters.
// For explicit binding the signature is:
// void(unsigned, unsigned, int, unsigned, const char *).
// For implicit binding the signature is:
// void(unsigned, int, unsigned, unsigned, const char *).
static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST,
QualType ResTy,
bool ExplicitBinding) {
std::array<QualType, 5> ExpParmTypes = {
AST.UnsignedIntTy, AST.UnsignedIntTy, AST.UnsignedIntTy,
AST.UnsignedIntTy, AST.getPointerType(AST.CharTy.withConst())};
ExpParmTypes[ExplicitBinding ? 2 : 1] = AST.IntTy;

CXXRecordDecl *ResDecl = ResTy->getAsCXXRecordDecl();
for (auto *Ctor : ResDecl->ctors()) {
if (Ctor->getNumParams() != ExpParmTypes.size())
continue;
auto *ParmIt = Ctor->param_begin();
auto ExpTyIt = ExpParmTypes.begin();
for (; ParmIt != Ctor->param_end() && ExpTyIt != ExpParmTypes.end();
++ParmIt, ++ExpTyIt) {
if ((*ParmIt)->getType() != *ExpTyIt)
break;
}
if (ParmIt == Ctor->param_end())
return Ctor;
}
llvm_unreachable("did not find constructor for resource class");
}

static Value *buildNameForResource(llvm::StringRef BaseName,
CodeGenModule &CGM) {
llvm::SmallString<64> GlobalName = {BaseName, ".str"};
return CGM.GetAddrOfConstantCString(BaseName.str(), GlobalName.c_str())
.getPointer();
}

static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD,
llvm::Value *ThisPtr, llvm::Value *Range,
llvm::Value *Index, StringRef Name,
HLSLResourceBindingAttr *RBA,
HLSLVkBindingAttr *VkBinding,
CallArgList &Args) {
static CXXMethodDecl *lookupMethod(CXXRecordDecl *Record, StringRef Name,
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should probably do this with Sema::LookupSingleName.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately, this is in CodeGen and we do not have access to Sema anymore.

I can use Sema lookup in my previous PR though (#156544), so I will update it at least there.

StorageClass SC = SC_None) {
for (auto *Method : Record->methods()) {
if (Method->getStorageClass() == SC && Method->getName() == Name)
return Method;
}
return nullptr;
}

static CXXMethodDecl *lookupResourceInitMethodAndSetupArgs(
CodeGenModule &CGM, CXXRecordDecl *ResourceDecl, llvm::Value *Range,
llvm::Value *Index, StringRef Name, HLSLResourceBindingAttr *RBA,
HLSLVkBindingAttr *VkBinding, CallArgList &Args) {
assert((VkBinding || RBA) && "at least one a binding attribute expected");

ASTContext &AST = CGM.getContext();
std::optional<uint32_t> RegisterSlot;
uint32_t SpaceNo = 0;
if (VkBinding) {
Expand All @@ -168,44 +147,57 @@ static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD,
SpaceNo = RBA->getSpaceNumber();
}

ASTContext &AST = CD->getASTContext();
CXXMethodDecl *CreateMethod = nullptr;
Value *NameStr = buildNameForResource(Name, CGM);
Value *Space = llvm::ConstantInt::get(CGM.IntTy, SpaceNo);

Args.add(RValue::get(ThisPtr), CD->getThisType());
if (RegisterSlot.has_value()) {
// explicit binding
auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, RegisterSlot.value());
Args.add(RValue::get(RegSlot), AST.UnsignedIntTy);
Args.add(RValue::get(Space), AST.UnsignedIntTy);
Args.add(RValue::get(Range), AST.IntTy);
Args.add(RValue::get(Index), AST.UnsignedIntTy);

CreateMethod = lookupMethod(ResourceDecl, "__createFromBinding", SC_Static);
} else {
// implicit binding
assert(RBA && "missing implicit binding attribute");
auto *OrderID =
llvm::ConstantInt::get(CGM.IntTy, RBA->getImplicitBindingOrderID());
Args.add(RValue::get(Space), AST.UnsignedIntTy);
Args.add(RValue::get(Range), AST.IntTy);
Args.add(RValue::get(Index), AST.UnsignedIntTy);
Args.add(RValue::get(OrderID), AST.UnsignedIntTy);
CreateMethod =
lookupMethod(ResourceDecl, "__createFromImplicitBinding", SC_Static);
}
Args.add(RValue::get(Space), AST.UnsignedIntTy);
Args.add(RValue::get(Range), AST.IntTy);
Args.add(RValue::get(Index), AST.UnsignedIntTy);
Args.add(RValue::get(NameStr), AST.getPointerType(AST.CharTy.withConst()));

return CreateMethod;
}

static void callResourceInitMethod(CodeGenFunction &CGF,
CXXMethodDecl *CreateMethod,
CallArgList &Args, Address ReturnAddress) {
llvm::Constant *CalleeFn = CGF.CGM.GetAddrOfFunction(CreateMethod);
const FunctionProtoType *Proto =
CreateMethod->getType()->getAs<FunctionProtoType>();
const CGFunctionInfo &FnInfo =
CGF.CGM.getTypes().arrangeFreeFunctionCall(Args, Proto, false);
ReturnValueSlot ReturnValue(ReturnAddress, false);
CGCallee Callee(CGCalleeInfo(Proto), CalleeFn);
CGF.EmitCall(FnInfo, Callee, ReturnValue, Args, nullptr);
}

// Initializes local resource array variable. For multi-dimensional arrays it
// calls itself recursively to initialize its sub-arrays. The Index used in the
// resource constructor calls will begin at StartIndex and will be incremented
// for each array element. The last used resource Index is returned to the
// caller.
static Value *initializeLocalResourceArray(
CodeGenFunction &CGF, AggValueSlot &ValueSlot,
const ConstantArrayType *ArrayTy, CXXConstructorDecl *CD,
// caller. If the function returns std::nullopt, it indicates an error.
static std::optional<llvm::Value *> initializeLocalResourceArray(
CodeGenFunction &CGF, CXXRecordDecl *ResourceDecl,
const ConstantArrayType *ArrayTy, AggValueSlot &ValueSlot,
llvm::Value *Range, llvm::Value *StartIndex, StringRef ResourceName,
HLSLResourceBindingAttr *RBA, HLSLVkBindingAttr *VkBinding,
ArrayRef<llvm::Value *> PrevGEPIndices, SourceLocation ArraySubsExprLoc) {

ASTContext &AST = CGF.getContext();
llvm::IntegerType *IntTy = CGF.CGM.IntTy;
llvm::Value *Index = StartIndex;
llvm::Value *One = llvm::ConstantInt::get(IntTy, 1);
Expand All @@ -226,16 +218,19 @@ static Value *initializeLocalResourceArray(
Index = CGF.Builder.CreateAdd(Index, One);
GEPIndices.back() = llvm::ConstantInt::get(IntTy, I);
}
Index = initializeLocalResourceArray(
CGF, ValueSlot, SubArrayTy, CD, Range, Index, ResourceName, RBA,
VkBinding, GEPIndices, ArraySubsExprLoc);
std::optional<llvm::Value *> MaybeIndex = initializeLocalResourceArray(
CGF, ResourceDecl, SubArrayTy, ValueSlot, Range, Index, ResourceName,
RBA, VkBinding, GEPIndices, ArraySubsExprLoc);
if (!MaybeIndex)
return std::nullopt;
Index = *MaybeIndex;
}
return Index;
}

// For array of resources, initialize each resource in the array.
llvm::Type *Ty = CGF.ConvertTypeForMem(ElemType);
CharUnits ElemSize = CD->getASTContext().getTypeSizeInChars(ElemType);
CharUnits ElemSize = AST.getTypeSizeInChars(ElemType);
CharUnits Align =
TmpArrayAddr.getAlignment().alignmentOfArrayElement(ElemSize);

Expand All @@ -244,16 +239,21 @@ static Value *initializeLocalResourceArray(
Index = CGF.Builder.CreateAdd(Index, One);
GEPIndices.back() = llvm::ConstantInt::get(IntTy, I);
}
Address ThisAddress =
Address ReturnAddress =
CGF.Builder.CreateGEP(TmpArrayAddr, GEPIndices, Ty, Align);
llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo(ThisAddress, ElemType);

CallArgList Args;
createResourceCtorArgs(CGF.CGM, CD, ThisPtr, Range, Index, ResourceName,
RBA, VkBinding, Args);
CGF.EmitCXXConstructorCall(CD, Ctor_Complete, false, false, ThisAddress,
Args, ValueSlot.mayOverlap(), ArraySubsExprLoc,
ValueSlot.isSanitizerChecked());
CXXMethodDecl *CreateMethod = lookupResourceInitMethodAndSetupArgs(
CGF.CGM, ResourceDecl, Range, Index, ResourceName, RBA, VkBinding,
Args);

if (!CreateMethod)
// This can happen if someone creates an array of structs that looks like
// an HLSL resource record array but it does not have the required static
// create method. No binding will be generated for it.
return std::nullopt;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to surface an error if this happens? Can this happen?

Copy link
Member Author

@hekota hekota Sep 16, 2025

Choose a reason for hiding this comment

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

This can happen when someone has an array of fake/custom(?) resource classes - array of structs with a __hlsl_resource_t member - and the struct does not have the create method.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Chatted with @hekota offline. Seems like this happens for manually written structs containing handles, and the outcome here is that they just don't get bindings generated. I think not supporting automatically binding handles for people writing their own types against internal compiler details is completely reasonable.


callResourceInitMethod(CGF, CreateMethod, Args, ReturnAddress);
}
return Index;
}
Expand Down Expand Up @@ -969,11 +969,6 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
QualType ResourceTy =
ResultTy->isArrayType() ? AST.getBaseElementType(ResultTy) : ResultTy;

// Lookup the resource class constructor based on the resource type and
// binding.
CXXConstructorDecl *CD = findResourceConstructorDecl(
AST, ResourceTy, VkBinding || RBA->hasRegisterSlot());

// Create a temporary variable for the result, which is either going
// to be a single resource instance or a local array of resources (we need to
// return an LValue).
Expand All @@ -986,7 +981,6 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true),
AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false),
AggValueSlot::DoesNotOverlap);
Address TmpVarAddress = ValueSlot.getAddress();

// Calculate total array size (= range size).
llvm::Value *Range =
Expand All @@ -995,27 +989,30 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
// If the result of the subscript operation is a single resource, call the
// constructor.
if (ResultTy == ResourceTy) {
QualType ThisType = CD->getThisType()->getPointeeType();
llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo(TmpVarAddress, ThisType);

// Assemble the constructor parameters.
CallArgList Args;
createResourceCtorArgs(CGM, CD, ThisPtr, Range, Index, ArrayDecl->getName(),
RBA, VkBinding, Args);
// Call the constructor.
CGF.EmitCXXConstructorCall(CD, Ctor_Complete, false, false, TmpVarAddress,
Args, ValueSlot.mayOverlap(),
ArraySubsExpr->getExprLoc(),
ValueSlot.isSanitizerChecked());
CXXMethodDecl *CreateMethod = lookupResourceInitMethodAndSetupArgs(
CGF.CGM, ResourceTy->getAsCXXRecordDecl(), Range, Index,
ArrayDecl->getName(), RBA, VkBinding, Args);

if (!CreateMethod)
// This can happen if someone creates an array of structs that looks like
// an HLSL resource record array but it does not have the required static
// create method. No binding will be generated for it.
return std::nullopt;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above, what happens if this happens?

Copy link
Member Author

Choose a reason for hiding this comment

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

See above.


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

} else {
// The result of the subscript operation is a local resource array which
// needs to be initialized.
const ConstantArrayType *ArrayTy =
cast<ConstantArrayType>(ResultTy.getTypePtr());
initializeLocalResourceArray(CGF, ValueSlot, ArrayTy, CD, Range, Index,
ArrayDecl->getName(), RBA, VkBinding,
{llvm::ConstantInt::get(CGM.IntTy, 0)},
ArraySubsExpr->getExprLoc());
std::optional<llvm::Value *> EndIndex = initializeLocalResourceArray(
CGF, ResourceTy->getAsCXXRecordDecl(), ArrayTy, ValueSlot, Range, Index,
ArrayDecl->getName(), RBA, VkBinding,
{llvm::ConstantInt::get(CGM.IntTy, 0)}, ArraySubsExpr->getExprLoc());
if (!EndIndex)
return std::nullopt;
}
return CGF.MakeAddrLValue(TmpVar, ResultTy, AlignmentSource::Decl);
}
94 changes: 28 additions & 66 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3793,55 +3793,6 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
deduceAddressSpace(VD);
}

void SemaHLSL::createResourceRecordCtorArgs(
const Type *ResourceTy, StringRef VarName, HLSLResourceBindingAttr *RBA,
HLSLVkBindingAttr *VkBinding, uint32_t ArrayIndex,
llvm::SmallVectorImpl<Expr *> &Args) {
std::optional<uint32_t> RegisterSlot;
uint32_t SpaceNo = 0;
if (VkBinding) {
RegisterSlot = VkBinding->getBinding();
SpaceNo = VkBinding->getSet();
} else if (RBA) {
if (RBA->hasRegisterSlot())
RegisterSlot = RBA->getSlotNumber();
SpaceNo = RBA->getSpaceNumber();
}

ASTContext &AST = SemaRef.getASTContext();
uint64_t UIntTySize = AST.getTypeSize(AST.UnsignedIntTy);
uint64_t IntTySize = AST.getTypeSize(AST.IntTy);
IntegerLiteral *RangeSize = IntegerLiteral::Create(
AST, llvm::APInt(IntTySize, 1), AST.IntTy, SourceLocation());
IntegerLiteral *Index =
IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, ArrayIndex),
AST.UnsignedIntTy, SourceLocation());
IntegerLiteral *Space =
IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, SpaceNo),
AST.UnsignedIntTy, SourceLocation());
StringLiteral *Name = StringLiteral::Create(
AST, VarName, StringLiteralKind::Ordinary, false,
AST.getStringLiteralArrayType(AST.CharTy.withConst(), VarName.size()),
SourceLocation());

// resource with explicit binding
if (RegisterSlot.has_value()) {
IntegerLiteral *RegSlot = IntegerLiteral::Create(
AST, llvm::APInt(UIntTySize, RegisterSlot.value()), AST.UnsignedIntTy,
SourceLocation());
Args.append({RegSlot, Space, RangeSize, Index, Name});
} else {
// resource with implicit binding
uint32_t OrderID = (RBA && RBA->hasImplicitBindingOrderID())
? RBA->getImplicitBindingOrderID()
: getNextImplicitBindingOrderID();
IntegerLiteral *OrderId =
IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, OrderID),
AST.UnsignedIntTy, SourceLocation());
Args.append({Space, RangeSize, Index, OrderId, Name});
}
}

bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) {
assert(VD->getType()->isHLSLResourceRecord() &&
"expected resource record type");
Expand Down Expand Up @@ -3951,28 +3902,39 @@ bool SemaHLSL::initGlobalResourceArrayDecl(VarDecl *VD) {

// Individual resources in a resource array are not initialized here. They
// are initialized later on during codegen when the individual resources are
// accessed. Codegen will emit a call to the resource constructor with the
// specified array index. We need to make sure though that the constructor
// accessed. Codegen will emit a call to the resource initialization method
// with the specified array index. We need to make sure though that the method
// for the specific resource type is instantiated, so codegen can emit a call
// to it when the array element is accessed.
SmallVector<Expr *> Args;
QualType ResElementTy = VD->getASTContext().getBaseElementType(VD->getType());
createResourceRecordCtorArgs(ResElementTy.getTypePtr(), VD->getName(),
VD->getAttr<HLSLResourceBindingAttr>(),
VD->getAttr<HLSLVkBindingAttr>(), 0, Args);

SourceLocation Loc = VD->getLocation();
InitializedEntity Entity =
InitializedEntity::InitializeTemporary(ResElementTy);
InitializationKind Kind = InitializationKind::CreateDirect(Loc, Loc, Loc);
InitializationSequence InitSeq(SemaRef, Entity, Kind, Args);
if (InitSeq.Failed())
// Find correct initialization method based on the resource binding
// information.
ASTContext &AST = SemaRef.getASTContext();
QualType ResElementTy = AST.getBaseElementType(VD->getType());
CXXRecordDecl *ResourceDecl = ResElementTy->getAsCXXRecordDecl();

HLSLResourceBindingAttr *RBA = VD->getAttr<HLSLResourceBindingAttr>();
HLSLVkBindingAttr *VkBinding = VD->getAttr<HLSLVkBindingAttr>();
CXXMethodDecl *CreateMethod = nullptr;

if (VkBinding || (RBA && RBA->hasRegisterSlot()))
// Resource has explicit binding.
CreateMethod = lookupMethod(SemaRef, ResourceDecl, "__createFromBinding",
VD->getLocation());
else
// Resource has implicit binding.
CreateMethod =
lookupMethod(SemaRef, ResourceDecl, "__createFromImplicitBinding",
VD->getLocation());

if (!CreateMethod)
return false;

// This takes care of instantiating and emitting of the constructor that will
// be called from codegen when the array is accessed.
ExprResult OneResInit = InitSeq.Perform(SemaRef, Entity, Kind, Args);
return !OneResInit.isInvalid();
// Make sure the create method template is instantiated and emitted.
if (!CreateMethod->isDefined() && CreateMethod->isTemplateInstantiation())
SemaRef.InstantiateFunctionDefinition(VD->getLocation(), CreateMethod,
true);
return true;
}

// Returns true if the initialization has been handled.
Expand Down
Loading