Skip to content
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -4957,6 +4957,18 @@ def HLSLResourceNonUniformIndex : LangBuiltin<"HLSL_LANG"> {
let Prototype = "uint32_t(uint32_t)";
}

def HLSLResourceGetDimensionsX : LangBuiltin<"HLSL_LANG"> {
let Spellings = ["__builtin_hlsl_resource_getdimensions_x"];
let Attributes = [NoThrow];
let Prototype = "void(...)";
}

def HLSLResourceGetStride : LangBuiltin<"HLSL_LANG"> {
let Spellings = ["__builtin_hlsl_resource_getstride"];
let Attributes = [NoThrow];
let Prototype = "void(...)";
}

def HLSLAll : LangBuiltin<"HLSL_LANG"> {
let Spellings = ["__builtin_hlsl_all"];
let Attributes = [NoThrow, Const];
Expand Down
23 changes: 23 additions & 0 deletions clang/lib/CodeGen/CGHLSLBuiltins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ static Value *handleHlslSplitdouble(const CallExpr *E, CodeGenFunction *CGF) {
return LastInst;
}

static Value *emitBufferStride(CodeGenFunction *CGF, const Expr *HandleExpr,
LValue &Stride) {
// Figure out the stride of the buffer elements from the handle type.
auto *HandleTy =
cast<HLSLAttributedResourceType>(HandleExpr->getType().getTypePtr());
QualType ElementTy = HandleTy->getContainedType();
Value *StrideValue = CGF->getTypeSize(ElementTy);
return CGF->Builder.CreateStore(StrideValue, Stride.getAddress());
}

// Return dot product intrinsic that corresponds to the QT scalar type
static Intrinsic::ID getDotProductIntrinsic(CGHLSLRuntime &RT, QualType QT) {
if (QT->isFloatingType())
Expand Down Expand Up @@ -372,6 +382,19 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
RetTy, CGM.getHLSLRuntime().getNonUniformResourceIndexIntrinsic(),
ArrayRef<Value *>{IndexOp});
}
case Builtin::BI__builtin_hlsl_resource_getdimensions_x: {
Value *Handle = EmitScalarExpr(E->getArg(0));
LValue Dim = EmitLValue(E->getArg(1));
llvm::Type *RetTy = llvm::Type::getInt32Ty(getLLVMContext());
Value *DimValue = Builder.CreateIntrinsic(
RetTy, CGM.getHLSLRuntime().getGetDimensionsXIntrinsic(),
ArrayRef<Value *>{Handle});
return Builder.CreateStore(DimValue, Dim.getAddress());
}
case Builtin::BI__builtin_hlsl_resource_getstride: {
LValue Stride = EmitLValue(E->getArg(1));
return emitBufferStride(this, E->getArg(0), Stride);
}
case Builtin::BI__builtin_hlsl_all: {
Value *Op0 = EmitScalarExpr(E->getArg(0));
return Builder.CreateIntrinsic(
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CodeGen/CGHLSLRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class CGHLSLRuntime {
GENERATE_HLSL_INTRINSIC_FUNCTION(BufferUpdateCounter, resource_updatecounter)
GENERATE_HLSL_INTRINSIC_FUNCTION(GroupMemoryBarrierWithGroupSync,
group_memory_barrier_with_group_sync)
GENERATE_HLSL_INTRINSIC_FUNCTION(GetDimensionsX, resource_getdimensions_x)

//===----------------------------------------------------------------------===//
// End of reserved area for HLSL intrinsic getters.
Expand Down
80 changes: 76 additions & 4 deletions clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ CXXConstructorDecl *lookupCopyConstructor(QualType ResTy) {
return CD;
return nullptr;
}

ParameterABI
convertParamModifierToParamABI(HLSLParamModifierAttr::Spelling Modifier) {
assert(Modifier != HLSLParamModifierAttr::Spelling::Keyword_in &&
"HLSL 'in' parameters modifier cannot be converted to ParameterABI");
switch (Modifier) {
case HLSLParamModifierAttr::Spelling::Keyword_out:
return ParameterABI::HLSLOut;
case HLSLParamModifierAttr::Spelling::Keyword_inout:
return ParameterABI::HLSLInOut;
default:
llvm_unreachable("Invalid HLSL parameter modifier");
}
}

QualType getInoutParameterType(ASTContext &AST, QualType Ty) {
assert(!Ty->isReferenceType() &&
"Pointer and reference types cannot be inout or out parameters");
Ty = AST.getLValueReferenceType(Ty);
Ty.addRestrict();
return Ty;
}

} // namespace

// Builder for template arguments of builtin types. Used internally
Expand Down Expand Up @@ -430,19 +453,36 @@ BuiltinTypeMethodBuilder::addParam(StringRef Name, QualType Ty,
void BuiltinTypeMethodBuilder::createDecl() {
assert(Method == nullptr && "Method or constructor is already created");

// create method or constructor type
// create function prototype
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
SmallVector<QualType> ParamTypes;
for (Param &MP : Params)
SmallVector<FunctionType::ExtParameterInfo> ParamExtInfos(Params.size());
uint32_t ArgIndex = 0;

// Create function prototype.
bool UseParamExtInfo = false;
for (Param &MP : Params) {
if (MP.Modifier != HLSLParamModifierAttr::Keyword_in) {
UseParamExtInfo = true;
FunctionType::ExtParameterInfo &PI = ParamExtInfos[ArgIndex];
ParamExtInfos[ArgIndex] =
PI.withABI(convertParamModifierToParamABI(MP.Modifier));
if (!MP.Ty->isDependentType())
MP.Ty = getInoutParameterType(AST, MP.Ty);
}
ParamTypes.emplace_back(MP.Ty);
++ArgIndex;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of doing this on the params here, could put the attributes on the ParmVarDecls in the loop over Params below?

At a minimum that would ensure that the AST for these functions matches the AST for user-written code, but it may also remove the need for the convertParamModifierToParamABI function since you'll have a created ParameterABI attribute in the AST.

Copy link
Member Author

@hekota hekota Oct 15, 2025

Choose a reason for hiding this comment

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

I tried different combinations of the out attribute, ABI param and when to add restrict & on the function prototype or the method decl - this is the variation that works and does not have to 'coerce' the arguments. However, I did some testing now and found a bug in with out parameters and templates:

https://godbolt.org/z/dKn1r4v4E

I'll file an issue #163648. We'll need to fix this bug first before revisiting this.

Copy link
Member Author

@hekota hekota Oct 16, 2025

Choose a reason for hiding this comment

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

Bug fix in review: #163832

I have updated createDecl code always change the type of inout and out parameter to a reference - unless the parameter type is dependent on the template instantiation. It turns out it is needed for both the function prototype (ABI params & type) and for the param list on the decl.

I also had a bug in how the ABI params were set (or not set, actually), which is why the function prototype string did not include the out modifier in the AST dump. This is now fixed.


FunctionProtoType::ExtProtoInfo ExtInfo;
if (UseParamExtInfo)
ExtInfo.ExtParameterInfos = ParamExtInfos.data();
if (IsConst)
ExtInfo.TypeQuals.addConst();

QualType FuncTy = AST.getFunctionType(ReturnTy, ParamTypes, ExtInfo);

// create method or constructor decl
// Create method or constructor declaration.
auto *TSInfo = AST.getTrivialTypeSourceInfo(FuncTy, SourceLocation());
DeclarationNameInfo NameInfo = DeclarationNameInfo(Name, SourceLocation());
if (IsCtor)
Expand All @@ -455,7 +495,7 @@ void BuiltinTypeMethodBuilder::createDecl() {
AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo, SC,
false, false, ConstexprSpecKind::Unspecified, SourceLocation());

// create params & set them to the function prototype
// Create params & set them to the method/constructor and function prototype.
SmallVector<ParmVarDecl *> ParmDecls;
unsigned CurScopeDepth = DeclBuilder.SemaRef.getCurScope()->getDepth();
auto FnProtoLoc =
Expand Down Expand Up @@ -1258,5 +1298,37 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addConsumeMethod() {
.finalize();
}

BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addGetDimensionsMethodForBuffer() {
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
ASTContext &AST = SemaRef.getASTContext();
QualType UIntTy = AST.UnsignedIntTy;

QualType HandleTy = getResourceHandleField()->getType();
auto *AttrResTy = cast<HLSLAttributedResourceType>(HandleTy.getTypePtr());

// Structured buffers except {RW}ByteAddressBuffer have overload
// GetDimensions(out uint numStructs, out uint stride).
if (AttrResTy->getAttrs().RawBuffer &&
AttrResTy->getContainedType() != AST.Char8Ty) {
return BuiltinTypeMethodBuilder(*this, "GetDimensions", AST.VoidTy)
.addParam("numStructs", UIntTy, HLSLParamModifierAttr::Keyword_out)
.addParam("stride", UIntTy, HLSLParamModifierAttr::Keyword_out)
.callBuiltin("__builtin_hlsl_resource_getdimensions_x", QualType(),
PH::Handle, PH::_0)
.callBuiltin("__builtin_hlsl_resource_getstride", QualType(),
PH::Handle, PH::_1)
.finalize();
}

// Typed buffers and {RW}ByteAddressBuffer have overload
// GetDimensions(out uint dim).
return BuiltinTypeMethodBuilder(*this, "GetDimensions", AST.VoidTy)
.addParam("dim", UIntTy, HLSLParamModifierAttr::Keyword_out)
.callBuiltin("__builtin_hlsl_resource_getdimensions_x", QualType(),
PH::Handle, PH::_0)
.finalize();
}

} // namespace hlsl
} // namespace clang
2 changes: 2 additions & 0 deletions clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ class BuiltinTypeDeclBuilder {
BuiltinTypeDeclBuilder &addAppendMethod();
BuiltinTypeDeclBuilder &addConsumeMethod();

BuiltinTypeDeclBuilder &addGetDimensionsMethodForBuffer();

private:
BuiltinTypeDeclBuilder &addCreateFromBinding();
BuiltinTypeDeclBuilder &addCreateFromImplicitBinding();
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/Sema/HLSLExternalSemaSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
/*RawBuffer=*/false, /*HasCounter=*/false)
.addArraySubscriptOperators()
.addLoadMethods()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -392,6 +393,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
/*RawBuffer=*/false, /*HasCounter=*/false)
.addArraySubscriptOperators()
.addLoadMethods()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -404,6 +406,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
/*RawBuffer=*/false, /*HasCounter=*/false)
.addArraySubscriptOperators()
.addLoadMethods()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -415,6 +418,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
/*RawBuffer=*/true, /*HasCounter=*/false)
.addArraySubscriptOperators()
.addLoadMethods()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -428,6 +432,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
.addLoadMethods()
.addIncrementCounterMethod()
.addDecrementCounterMethod()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -439,6 +444,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, /*IsROV=*/false,
/*RawBuffer=*/true, /*HasCounter=*/true)
.addAppendMethod()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -450,6 +456,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, /*IsROV=*/false,
/*RawBuffer=*/true, /*HasCounter=*/true)
.addConsumeMethod()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -464,6 +471,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
.addLoadMethods()
.addIncrementCounterMethod()
.addDecrementCounterMethod()
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});

Expand All @@ -472,13 +480,15 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::SRV, /*IsROV=*/false,
/*RawBuffer=*/true, /*HasCounter=*/false)
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWByteAddressBuffer")
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, /*IsROV=*/false,
/*RawBuffer=*/true, /*HasCounter=*/false)
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace,
Expand All @@ -487,6 +497,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::UAV, /*IsROV=*/true,
/*RawBuffer=*/true, /*HasCounter=*/false)
.addGetDimensionsMethodForBuffer()
.completeDefinition();
});
}
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3006,6 +3006,24 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
TheCall->setType(CounterHandleTy);
break;
}
case Builtin::BI__builtin_hlsl_resource_getdimensions_x: {
ASTContext &AST = SemaRef.getASTContext();
if (SemaRef.checkArgCount(TheCall, 2) ||
CheckResourceHandle(&SemaRef, TheCall, 0) ||
CheckArgTypeMatches(&SemaRef, TheCall->getArg(1), AST.UnsignedIntTy) ||
CheckModifiableLValue(&SemaRef, TheCall, 1))
return true;
break;
}
case Builtin::BI__builtin_hlsl_resource_getstride: {
ASTContext &AST = SemaRef.getASTContext();
if (SemaRef.checkArgCount(TheCall, 2) ||
CheckResourceHandle(&SemaRef, TheCall, 0) ||
CheckArgTypeMatches(&SemaRef, TheCall->getArg(1), AST.UnsignedIntTy) ||
CheckModifiableLValue(&SemaRef, TheCall, 1))
return true;
break;
}
case Builtin::BI__builtin_hlsl_and:
case Builtin::BI__builtin_hlsl_or: {
if (SemaRef.checkArgCount(TheCall, 2))
Expand Down
14 changes: 14 additions & 0 deletions clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,19 @@ RESOURCE Buffer;
// CHECK-NEXT: DeclRefExpr {{.*}} 'hlsl::[[RESOURCE]]' lvalue Var {{.*}} 'tmp' 'hlsl::[[RESOURCE]]'
// CHECK-NEXT: AlwaysInlineAttr {{.*}} Implicit always_inline

// GetDimensions method

// CHECK-NEXT: CXXMethodDecl {{.*}} GetDimensions 'void (out unsigned int)'
// CHECK-NEXT: ParmVarDecl {{.*}} dim 'unsigned int &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
// CHECK-NEXT: CompoundStmt
// CHECK-NEXT: CallExpr {{.*}} 'void'
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(...) noexcept' <BuiltinFnToFnPtr>
// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_getdimensions_x' 'void (...) noexcept'
// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t {{.*}}' lvalue .__handle {{.*}}
// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]' lvalue implicit this
// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'dim' 'unsigned int &__restrict'
// CHECK-NEXT: AlwaysInlineAttr {{.*}} Implicit always_inline

// CHECK-NOSUBSCRIPT-NOT: CXXMethodDecl {{.*}} operator[] 'const char8_t &(unsigned int) const'
// CHECK-NOSUBSCRIPT-NOT: CXXMethodDecl {{.*}} operator[] 'char8_t &(unsigned int)'
22 changes: 22 additions & 0 deletions clang/test/AST/HLSL/StructuredBuffers-AST.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,28 @@ RESOURCE<float> Buffer;
// CHECK-CONSUME-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]<element_type>' lvalue implicit this
// CHECK-CONSUME-NEXT: IntegerLiteral {{.*}} 'int' -1

// GetDimensions method

// CHECK: CXXMethodDecl {{.*}} GetDimensions 'void (out unsigned int, out unsigned int)'
// CHECK-NEXT: ParmVarDecl {{.*}} numStructs 'unsigned int &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
// CHECK-NEXT: ParmVarDecl {{.*}} stride 'unsigned int &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
// CHECK-NEXT: CompoundStmt
// CHECK-NEXT: CallExpr {{.*}} 'void'
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(...) noexcept' <BuiltinFnToFnPtr>
// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_getdimensions_x' 'void (...) noexcept'
// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t {{.*}}' lvalue .__handle {{.*}}
// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]<element_type>' lvalue implicit this
// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'numStructs' 'unsigned int &__restrict'
// CHECK-NEXT: CallExpr {{.*}} 'void'
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(...) noexcept' <BuiltinFnToFnPtr>
// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_getstride' 'void (...) noexcept'
// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t {{.*}}' lvalue .__handle {{.*}}
// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]<element_type>' lvalue implicit this
// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'stride' 'unsigned int &__restrict'
// CHECK-NEXT: AlwaysInlineAttr {{.*}} Implicit always_inline

// CHECK: ClassTemplateSpecializationDecl {{.*}} class [[RESOURCE]] definition
// CHECK: TemplateArgument type 'float'
// CHECK-NEXT: BuiltinType {{.*}} 'float'
Expand Down
14 changes: 14 additions & 0 deletions clang/test/AST/HLSL/TypedBuffers-AST.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,20 @@ RESOURCE<float> Buffer;
// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'Index' 'unsigned int'
// CHECK-NEXT: AlwaysInlineAttr {{.*}} Implicit always_inline

// GetDimensions method

// CHECK-NEXT: CXXMethodDecl {{.*}} GetDimensions 'void (out unsigned int)'
// CHECK-NEXT: ParmVarDecl {{.*}} dim 'unsigned int &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
// CHECK-NEXT: CompoundStmt
// CHECK-NEXT: CallExpr {{.*}} 'void'
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(...) noexcept' <BuiltinFnToFnPtr>
// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_getdimensions_x' 'void (...) noexcept'
// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t {{.*}}' lvalue .__handle {{.*}}
// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]<element_type>' lvalue implicit this
// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'dim' 'unsigned int &__restrict'
// CHECK-NEXT: AlwaysInlineAttr {{.*}} Implicit always_inline

// CHECK: ClassTemplateSpecializationDecl {{.*}} class [[RESOURCE]] definition

// CHECK: TemplateArgument type 'float'
Expand Down
Loading
Loading