Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 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 @@ -243,11 +243,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
157 changes: 74 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 @@ -38,6 +39,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 @@ -110,52 +112,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 @@ -167,44 +146,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 @@ -225,16 +217,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 @@ -243,16 +238,18 @@ 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)
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 @@ -935,11 +932,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 @@ -952,7 +944,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 @@ -961,27 +952,27 @@ 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)
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);
}
46 changes: 45 additions & 1 deletion clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
Expand Down Expand Up @@ -48,6 +49,14 @@ static FunctionDecl *lookupBuiltinFunction(Sema &S, StringRef Name) {
"Since this is a builtin it should always resolve!");
return cast<FunctionDecl>(R.getFoundDecl());
}

CXXConstructorDecl *lookupCopyConstructor(QualType ResTy) {
assert(ResTy->isRecordType() && "not a CXXRecord type");
for (auto *CD : ResTy->getAsCXXRecordDecl()->ctors())
if (CD->isCopyConstructor())
return CD;
return nullptr;
}
} // namespace

// Builder for template arguments of builtin types. Used internally
Expand Down Expand Up @@ -121,6 +130,7 @@ struct BuiltinTypeMethodBuilder {
StorageClass SC;
llvm::SmallVector<Param> Params;
llvm::SmallVector<Stmt *> StmtsList;
llvm::SmallVector<VarDecl *> LocalVars;

// Argument placeholders, inspired by std::placeholder. These are the indices
// of arguments to forward to `callBuiltin` and other method builder methods.
Expand All @@ -129,7 +139,16 @@ struct BuiltinTypeMethodBuilder {
// LastStmt - refers to the last statement in the method body; referencing
// LastStmt will remove the statement from the method body since
// it will be linked from the new expression being constructed.
enum class PlaceHolder { _0, _1, _2, _3, _4, Handle = 128, LastStmt };
enum class PlaceHolder {
_0,
_1,
_2,
_3,
_4,
LocalVar_0 = 64,
Handle = 128,
LastStmt
};

Expr *convertPlaceholder(PlaceHolder PH);
Expr *convertPlaceholder(LocalVar &Var);
Expand Down Expand Up @@ -346,6 +365,17 @@ Expr *BuiltinTypeMethodBuilder::convertPlaceholder(PlaceHolder PH) {
}

ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
if (PH >= PlaceHolder::LocalVar_0) {
unsigned Index = static_cast<unsigned>(PH) -
static_cast<unsigned>(PlaceHolder::LocalVar_0);
assert(Index < LocalVars.size() && "local var index out of range");
VarDecl *VD = LocalVars[Index];
return DeclRefExpr::Create(
AST, NestedNameSpecifierLoc(), SourceLocation(), VD, false,
DeclarationNameInfo(VD->getDeclName(), SourceLocation()), VD->getType(),
VK_LValue);
}

ParmVarDecl *ParamDecl = Method->getParamDecl(static_cast<unsigned>(PH));
return DeclRefExpr::Create(
AST, NestedNameSpecifierLoc(), SourceLocation(), ParamDecl, false,
Expand Down Expand Up @@ -580,6 +610,20 @@ BuiltinTypeMethodBuilder &BuiltinTypeMethodBuilder::returnValue(T ReturnValue) {

Expr *ReturnValueExpr = convertPlaceholder(ReturnValue);
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();

QualType Ty = ReturnValueExpr->getType();
if (Ty->isRecordType()) {
// For record types, create a call to copy constructor to ensure proper copy
// semantics.
auto *ICE =
ImplicitCastExpr::Create(AST, Ty.withConst(), CK_NoOp, ReturnValueExpr,
nullptr, VK_XValue, FPOptionsOverride());
CXXConstructorDecl *CD = lookupCopyConstructor(Ty);
assert(CD && "no copy constructor found");
ReturnValueExpr = CXXConstructExpr::Create(
AST, Ty, SourceLocation(), CD, false, {ICE}, false, false, false, false,
CXXConstructionKind::Complete, SourceRange());
}
StmtsList.push_back(
ReturnStmt::Create(AST, SourceLocation(), ReturnValueExpr, nullptr));
return *this;
Expand Down
Loading