[clang] [llvm] [HLSL] Add type traits for ConstantBuffers templates (PR #195154)
Steven Perron via cfe-commits
cfe-commits at lists.llvm.org
Thu May 7 14:00:13 PDT 2026
https://github.com/s-perron updated https://github.com/llvm/llvm-project/pull/195154
>From e9a060b390e43adae9dd2ad491379647b148af96 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Wed, 8 Apr 2026 15:00:16 -0400
Subject: [PATCH 1/6] [HLSL] Allow __builtin_hlsl_resource_getpointer to take
no indices
In preperation for adding ConstnatBuffer<T>, we will need to be able to
access the base pointer for the data constat buffer resource handle is pointingto
to. This is done by:
1. Making the index operand in __builtin_hlsl_resource_getpointer
optional.
2. Modifing the codegen for __builtin_hlsl_resource_getpointer to emit a
call to resource.getbasepointer when no index is provided.
3. Add the resource.getbasepointer for the dx and spv targets.
Another issue is that the address space for the pointer returned by
__builtin_hlsl_resource_getpointer is not always hlsl_device any more.
Changes are made to get the correct address space based on the resource
class of the handle.
Note that we cannot implement codegen for
__builtin_hlsl_resource_getpointer directly. The tests for the codegen
changes will be in a follow up PR that add ConstnatBuffer<T>.
Assisted-by: Gemini
---
clang/lib/CodeGen/CGHLSLBuiltins.cpp | 14 +++++++--
clang/lib/CodeGen/CGHLSLRuntime.h | 2 ++
clang/lib/Sema/SemaHLSL.cpp | 30 ++++++++++++++-----
.../BuiltIns/resource_getpointer-errors.hlsl | 10 +++----
llvm/include/llvm/IR/IntrinsicsDirectX.td | 4 +++
llvm/include/llvm/IR/IntrinsicsSPIRV.td | 4 +++
6 files changed, 49 insertions(+), 15 deletions(-)
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index b82a237ecefca..82b03d7d5f069 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -571,12 +571,20 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
case Builtin::BI__builtin_hlsl_resource_getpointer:
case Builtin::BI__builtin_hlsl_resource_getpointer_typed: {
Value *HandleOp = EmitScalarExpr(E->getArg(0));
- Value *IndexOp = EmitScalarExpr(E->getArg(1));
+ bool IsIndexed =
+ BuiltinID == Builtin::BI__builtin_hlsl_resource_getpointer_typed ||
+ E->getNumArgs() > 1;
llvm::Type *RetTy = ConvertType(E->getType());
+ if (IsIndexed) {
+ Value *IndexOp = EmitScalarExpr(E->getArg(1));
+ return Builder.CreateIntrinsic(
+ RetTy, CGM.getHLSLRuntime().getCreateResourceGetPointerIntrinsic(),
+ ArrayRef<Value *>{HandleOp, IndexOp});
+ }
return Builder.CreateIntrinsic(
- RetTy, CGM.getHLSLRuntime().getCreateResourceGetPointerIntrinsic(),
- ArrayRef<Value *>{HandleOp, IndexOp});
+ RetTy, CGM.getHLSLRuntime().getCreateResourceGetBasePointerIntrinsic(),
+ ArrayRef<Value *>{HandleOp});
}
case Builtin::BI__builtin_hlsl_resource_sample: {
Value *HandleOp = EmitScalarExpr(E->getArg(0));
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index b54cbab906056..d7ac2346f2428 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -167,6 +167,8 @@ class CGHLSLRuntime {
GENERATE_HLSL_INTRINSIC_FUNCTION(SClamp, sclamp)
GENERATE_HLSL_INTRINSIC_FUNCTION(UClamp, uclamp)
+ GENERATE_HLSL_INTRINSIC_FUNCTION(CreateResourceGetBasePointer,
+ resource_getbasepointer)
GENERATE_HLSL_INTRINSIC_FUNCTION(CreateResourceGetPointer,
resource_getpointer)
GENERATE_HLSL_INTRINSIC_FUNCTION(Sample, resource_sample)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index aba1c5072a5fc..7788d777edf1c 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -77,6 +77,19 @@ static RegisterType getRegisterType(const HLSLAttributedResourceType *ResTy) {
return getRegisterType(ResTy->getAttrs().ResourceClass);
}
+static LangAS getLangASFromResourceClass(ResourceClass RC) {
+ switch (RC) {
+ case ResourceClass::SRV:
+ case ResourceClass::UAV:
+ return LangAS::hlsl_device;
+ case ResourceClass::CBuffer:
+ return LangAS::hlsl_constant;
+ case ResourceClass::Sampler:
+ return LangAS::hlsl_device;
+ }
+ llvm_unreachable("unexpected ResourceClass value");
+}
+
// Converts the first letter of string Slot to RegisterType.
// Returns false if the letter does not correspond to a valid register type.
static bool convertToRegisterType(StringRef Slot, RegisterType *RT) {
@@ -3891,19 +3904,19 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
break;
}
case Builtin::BI__builtin_hlsl_resource_getpointer: {
- if (SemaRef.checkArgCount(TheCall, 2) ||
+ if (SemaRef.checkArgCountRange(TheCall, 1, 2) ||
CheckResourceHandle(&SemaRef, TheCall, 0) ||
- CheckIndexType(&SemaRef, TheCall, 1))
+ (TheCall->getNumArgs() == 2 && CheckIndexType(&SemaRef, TheCall, 1)))
return true;
auto *ResourceTy =
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
QualType ContainedTy = ResourceTy->getContainedType();
- auto ReturnType =
- SemaRef.Context.getAddrSpaceQualType(ContainedTy, LangAS::hlsl_device);
+ auto ReturnType = SemaRef.Context.getAddrSpaceQualType(
+ ContainedTy,
+ getLangASFromResourceClass(ResourceTy->getAttrs().ResourceClass));
ReturnType = SemaRef.Context.getPointerType(ReturnType);
TheCall->setType(ReturnType);
- TheCall->setValueKind(VK_LValue);
break;
}
@@ -3924,8 +3937,11 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
cast<FunctionDecl>(SemaRef.CurContext)->getPointOfInstantiation(),
diag::err_invalid_use_of_array_type);
- auto ReturnType =
- SemaRef.Context.getAddrSpaceQualType(ElementTy, LangAS::hlsl_device);
+ auto *ResourceTy =
+ TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
+ auto ReturnType = SemaRef.Context.getAddrSpaceQualType(
+ ElementTy,
+ getLangASFromResourceClass(ResourceTy->getAttrs().ResourceClass));
ReturnType = SemaRef.Context.getPointerType(ReturnType);
TheCall->setType(ReturnType);
diff --git a/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl
index 20de0773a1742..7dde4dcf6c149 100644
--- a/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl
@@ -5,17 +5,17 @@ using handle_t = __hlsl_resource_t
[[hlsl::resource_class(UAV)]] [[hlsl::contained_type(int)]];
void test_args(unsigned int x) {
- // expected-error at +1 {{too few arguments to function call, expected 2, have 1}}
+ // expected-error at +1 {{used type 'unsigned int' where __hlsl_resource_t is required}}
__builtin_hlsl_resource_getpointer(x);
- // expected-error at +1 {{too many arguments to function call, expected 2, have 3}}
+ // expected-error at +1 {{too many arguments to function call, expected at most 2, have 3}}
__builtin_hlsl_resource_getpointer(x, x, x);
- // expected-error at +1 {{used type 'unsigned int' where __hlsl_resource_t is required}}
- __builtin_hlsl_resource_getpointer(x, x);
-
handle_t res;
+ // no error
+ __builtin_hlsl_resource_getpointer(res);
+
// expected-error at +1 {{used type 'const char *' where integer is required}}
__builtin_hlsl_resource_getpointer(res, "1");
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index f37180ce9084a..f55996234aea5 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -40,6 +40,10 @@ def int_dx_resource_getpointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_any_ty],
[IntrReadMem, IntrInaccessibleMemOnly]>;
+def int_dx_resource_getbasepointer
+ : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty],
+ [IntrReadMem, IntrInaccessibleMemOnly]>;
+
def int_dx_resource_nonuniformindex
: DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty], [IntrNoMem]>;
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 44e31a1410523..6157947ad2318 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -318,6 +318,10 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_any_ty],
[IntrNoMem]>;
+ def int_spv_resource_getbasepointer
+ : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty],
+ [IntrNoMem]>;
+
def int_spv_pushconstant_getpointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty], [IntrNoMem]>;
>From 1c1f1a5c185935682dbf87723723dcaafc741e8b Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Wed, 29 Apr 2026 16:19:35 -0400
Subject: [PATCH 2/6] [SPIRV] Implement spv_resource_getbasepointer for Vulkan
buffers
The new intrinsic spv_resource_getbasepointer allows retrieving the base
pointer of a resource without an index. This is necessary for resources
like ConstantBuffer<T> where you can access the top level struct of type
T.
Assisted-by: Gemini
---
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 19 ++++---
.../Target/SPIRV/SPIRVInstructionSelector.cpp | 30 +++++++----
.../SPIRV/hlsl-resources/getbasepointer.ll | 54 +++++++++++++++++++
3 files changed, 86 insertions(+), 17 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/getbasepointer.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 180a0f45874e1..cfded9596c094 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -411,6 +411,7 @@ bool isConvergenceIntrinsic(const Instruction *I) {
bool expectIgnoredInIRTranslation(const Instruction *I) {
return match(I, m_AnyIntrinsic<Intrinsic::invariant_start,
Intrinsic::spv_resource_handlefrombinding,
+ Intrinsic::spv_resource_getbasepointer,
Intrinsic::spv_resource_getpointer>());
}
@@ -1022,7 +1023,8 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
// TODO: maybe improve performance by caching demangled names
auto *II = dyn_cast<IntrinsicInst>(I);
- if (II && II->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+ if (II && (II->getIntrinsicID() == Intrinsic::spv_resource_getbasepointer ||
+ II->getIntrinsicID() == Intrinsic::spv_resource_getpointer)) {
auto *HandleType = cast<TargetExtType>(II->getOperand(0)->getType());
if (HandleType->getTargetExtName() == "spirv.Image" ||
HandleType->getTargetExtName() == "spirv.SignedImage") {
@@ -1034,12 +1036,15 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
} else if (HandleType->getTargetExtName() == "spirv.VulkanBuffer") {
// This call is supposed to index into an array
Ty = HandleType->getTypeParameter(0);
- if (Ty->isArrayTy())
- Ty = Ty->getArrayElementType();
- else {
- assert(Ty && Ty->isStructTy());
- uint32_t Index = cast<ConstantInt>(II->getOperand(1))->getZExtValue();
- Ty = cast<StructType>(Ty)->getElementType(Index);
+ if (II->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+ if (Ty->isArrayTy())
+ Ty = Ty->getArrayElementType();
+ else {
+ assert(Ty && Ty->isStructTy());
+ uint32_t Index =
+ cast<ConstantInt>(II->getOperand(1))->getZExtValue();
+ Ty = cast<StructType>(Ty)->getElementType(Index);
+ }
}
Ty = reconstitutePeeledArrayType(Ty);
} else {
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index aee3a29c6e42b..5b0204d5a2e8b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -700,6 +700,7 @@ static bool intrinsicHasSideEffects(Intrinsic::ID ID) {
case Intrinsic::spv_radians:
case Intrinsic::spv_reflect:
case Intrinsic::spv_refract:
+ case Intrinsic::spv_resource_getbasepointer:
case Intrinsic::spv_resource_getpointer:
case Intrinsic::spv_resource_handlefrombinding:
case Intrinsic::spv_resource_handlefromimplicitbinding:
@@ -1887,7 +1888,9 @@ bool SPIRVInstructionSelector::selectLoad(Register ResVReg,
auto *PtrDef = getVRegDef(*MRI, Ptr);
auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
if (IntPtrDef &&
- IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+ (IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getbasepointer ||
+ IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer)) {
+
Register HandleReg = IntPtrDef->getOperand(2).getReg();
SPIRVTypeInst HandleType = GR.getSPIRVTypeForVReg(HandleReg);
if (HandleType->getOpcode() == SPIRV::OpTypeImage) {
@@ -1979,7 +1982,9 @@ bool SPIRVInstructionSelector::selectStore(MachineInstr &I) const {
auto *PtrDef = getVRegDef(*MRI, Ptr);
auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
if (IntPtrDef &&
- IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+ (IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getbasepointer ||
+ IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer)) {
+
Register HandleReg = IntPtrDef->getOperand(2).getReg();
Register NewHandleReg =
MRI->createVirtualRegister(MRI->getRegClass(HandleReg));
@@ -5094,6 +5099,7 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
case Intrinsic::spv_resource_gather:
case Intrinsic::spv_resource_gather_cmp:
return selectGatherIntrinsic(ResVReg, ResType, I);
+ case Intrinsic::spv_resource_getbasepointer:
case Intrinsic::spv_resource_getpointer: {
return selectResourceGetPointer(ResVReg, ResType, I);
}
@@ -5890,16 +5896,20 @@ bool SPIRVInstructionSelector::selectResourceGetPointer(Register &ResVReg,
assert(ResType->getOpcode() == SPIRV::OpTypePointer);
MachineIRBuilder MIRBuilder(I);
- Register IndexReg = I.getOperand(3).getReg();
Register ZeroReg =
buildZerosVal(GR.getOrCreateSPIRVIntegerType(32, I, TII), I);
- BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpAccessChain))
- .addDef(ResVReg)
- .addUse(GR.getSPIRVTypeID(ResType))
- .addUse(ResourcePtr)
- .addUse(ZeroReg)
- .addUse(IndexReg)
- .constrainAllUses(TII, TRI, RBI);
+ auto MIB =
+ BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpAccessChain))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(ResourcePtr)
+ .addUse(ZeroReg);
+
+ if (I.getNumExplicitOperands() > 3) {
+ Register IndexReg = I.getOperand(3).getReg();
+ MIB.addUse(IndexReg);
+ }
+ MIB.constrainAllUses(TII, TRI, RBI);
return true;
}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/getbasepointer.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/getbasepointer.ll
new file mode 100644
index 0000000000000..aefeea0981871
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/getbasepointer.ll
@@ -0,0 +1,54 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+
+ at .str.1 = private unnamed_addr constant [2 x i8] c"B\00", align 1
+ at .str.2 = private unnamed_addr constant [2 x i8] c"S\00", align 1
+
+; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[int]] 0
+
+; CHECK-DAG: [[ArrayType:%.+]] = OpTypeRuntimeArray [[int]]
+; CHECK-DAG: [[BufferType:%.+]] = OpTypeStruct [[ArrayType]]
+; CHECK-DAG: [[BufferPtrType:%.+]] = OpTypePointer StorageBuffer [[BufferType]]
+; CHECK-DAG: [[BufferVar:%.+]] = OpVariable [[BufferPtrType]] StorageBuffer
+
+; CHECK-DAG: [[StructType:%.+]] = OpTypeStruct [[int]] [[int]]
+; CHECK-DAG: [[StructWrapper:%.+]] = OpTypeStruct [[StructType]]
+; CHECK-DAG: [[StructPtrType:%.+]] = OpTypePointer StorageBuffer [[StructWrapper]]
+; CHECK-DAG: [[StructVar:%.+]] = OpVariable [[StructPtrType]] StorageBuffer
+
+define i32 @main() local_unnamed_addr {
+entry:
+; CHECK-DAG: [[BufferHandle:%.+]] = OpCopyObject [[BufferPtrType]] [[BufferVar]]
+; CHECK-DAG: [[StructHandle:%.+]] = OpCopyObject [[StructPtrType]] [[StructVar]]
+
+ %BufferHandle = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str.1)
+ %StructHandle = tail call target("spirv.VulkanBuffer", {i32, i32}, 12, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBufferStruct(i32 0, i32 2, i32 1, i32 0, ptr nonnull @.str.2)
+
+; CHECK: [[AC2:%.+]] = OpAccessChain {{.*}} [[BufferHandle]] [[zero]]
+; CHECK-NOT: [[AC2]] = OpAccessChain {{.*}} [[BufferHandle]] [[zero]] %
+ %2 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getbasepointer.p11.tspirv.VulkanBuffer(target("spirv.VulkanBuffer", [0 x i32], 12, 0) %BufferHandle)
+ %load2 = load i32, ptr addrspace(11) %2
+
+; CHECK: [[AC3:%.+]] = OpAccessChain {{.*}} [[BufferHandle]] [[zero]] [[index:%[0-9]+]]
+ %3 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer(target("spirv.VulkanBuffer", [0 x i32], 12, 0) %BufferHandle, i32 42)
+ %load3 = load i32, ptr addrspace(11) %3
+
+; CHECK: [[AC4:%.+]] = OpAccessChain {{.*}} [[StructHandle]] [[zero]]
+; CHECK-NOT: [[AC4]] = OpAccessChain {{.*}} [[StructHandle]] [[zero]] %
+ %4 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getbasepointer.p11.tspirv.VulkanBufferStruct(target("spirv.VulkanBuffer", {i32, i32}, 12, 0) %StructHandle)
+ %load4 = load i32, ptr addrspace(11) %4
+
+ %res1 = add i32 %load2, %load3
+ %res2 = add i32 %res1, %load4
+ ret i32 %res2
+}
+
+declare target("spirv.VulkanBuffer", [0 x i32], 12, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer(i32, i32, i32, i32, ptr)
+declare target("spirv.VulkanBuffer", {i32, i32}, 12, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBufferStruct(i32, i32, i32, i32, ptr)
+
+declare ptr addrspace(11) @llvm.spv.resource.getbasepointer.p11.tspirv.VulkanBuffer(target("spirv.VulkanBuffer", [0 x i32], 12, 0))
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer(target("spirv.VulkanBuffer", [0 x i32], 12, 0), i32)
+declare ptr addrspace(11) @llvm.spv.resource.getbasepointer.p11.tspirv.VulkanBufferStruct(target("spirv.VulkanBuffer", {i32, i32}, 12, 0))
>From c3ea7c0d18932788c0b092c71564923218b0c10c Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Wed, 29 Apr 2026 17:00:12 -0400
Subject: [PATCH 3/6] [HLSL] Add ConstantBuffer<T>
The ConstantBuffer<T> is a standard resource type in HLSL. This commit
is following the design in wg-hlsl proposal [0046](https://github.com/llvm/wg-hlsl/blob/main/proposals/0046-constantbuffer-t.md).
The type constraints will be left to a follow up pr.
Assisted-by: Gemini
---
clang/include/clang/Sema/SemaHLSL.h | 11 ++
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 30 ++++-
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 3 +-
clang/lib/Sema/HLSLExternalSemaSource.cpp | 11 ++
clang/lib/Sema/SemaExprMember.cpp | 14 ++
clang/lib/Sema/SemaHLSL.cpp | 47 +++++++
.../AST/HLSL/ConstantBuffers-AST-error.hlsl | 24 ++++
clang/test/AST/HLSL/ConstantBuffers-AST.hlsl | 120 ++++++++++++++++++
.../builtins/ConstantBuffer-layout.hlsl | 68 ++++++++++
.../CodeGenHLSL/builtins/ConstantBuffer.hlsl | 65 ++++++++++
.../test/CodeGenHLSL/cbuffer_copy_layout.hlsl | 24 ++++
.../BuiltIns/ConstantBuffer-member-funcs.hlsl | 28 ++++
.../SemaHLSL/BuiltIns/ConstantBuffers.hlsl | 35 +++++
13 files changed, 477 insertions(+), 3 deletions(-)
create mode 100644 clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl
create mode 100644 clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
create mode 100644 clang/test/CodeGenHLSL/builtins/ConstantBuffer-layout.hlsl
create mode 100644 clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
create mode 100644 clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl
create mode 100644 clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
create mode 100644 clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 1ba9bfed9918d..7a8fb5492f8df 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -143,6 +143,17 @@ class SemaHLSL : public SemaBase {
bool IsCompAssign);
void emitLogicalOperatorFixIt(Expr *LHS, Expr *RHS, BinaryOperatorKind Opc);
+ // Returns the result of converting ConstantBuffer<T> to
+ // `const hlsl_constant T&`. If `BaseExpr`'s type is not ConstantBuffer<T>
+ // then the return value is `std::nullopt`.
+ std::optional<ExprResult>
+ performConstantBufferConversion(ExprResult &BaseExpr);
+
+ // Returns the conversion operator to convert `RD` to `const hlsl_constant
+ // Type&`. Returns `nullptr` if it could not be found.
+ NamedDecl *getConstantBufferConversionFunction(QualType Type,
+ CXXRecordDecl *RD);
+
/// Computes the unique Root Signature identifier from the given signature,
/// then lookup if there is a previousy created Root Signature decl.
///
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index ba8e63f01527a..a4c1c70ebf31b 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -554,6 +554,11 @@ void BuiltinTypeMethodBuilder::createDecl() {
AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo,
ExplicitSpecifier(), false, /*IsInline=*/true, false,
ConstexprSpecKind::Unspecified);
+ else if (Name.getNameKind() == DeclarationName::CXXConversionFunctionName)
+ Method = CXXConversionDecl::Create(
+ AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo,
+ false, /*isInline=*/true, ExplicitSpecifier(),
+ ConstexprSpecKind::Unspecified, SourceLocation());
else
Method = CXXMethodDecl::Create(
AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo, SC,
@@ -879,7 +884,7 @@ BuiltinTypeMethodBuilder &BuiltinTypeMethodBuilder::returnValue(T ReturnValue) {
ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
QualType Ty = ReturnValueExpr->getType();
- if (Ty->isRecordType()) {
+ if (Ty->isRecordType() && !Method->getReturnType()->isReferenceType()) {
// For record types, create a call to copy constructor to ensure proper copy
// semantics.
auto *ICE =
@@ -1055,6 +1060,27 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addSamplerHandle() {
return *this;
}
+BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addConversionToType() {
+ assert(!Record->isCompleteDefinition() && "record is already complete");
+ ASTContext &AST = SemaRef.getASTContext();
+ using PH = BuiltinTypeMethodBuilder::PlaceHolder;
+
+ QualType ElemTy = getHandleElementType();
+ QualType AddrSpaceElemTy = AST.getCanonicalType(
+ AST.getAddrSpaceQualType(ElemTy.withConst(), LangAS::hlsl_constant));
+ QualType ReturnTy =
+ AST.getCanonicalType(AST.getLValueReferenceType(AddrSpaceElemTy));
+
+ DeclarationName Name = AST.DeclarationNames.getCXXConversionFunctionName(
+ AST.getCanonicalType(ReturnTy));
+
+ return BuiltinTypeMethodBuilder(*this, Name, ReturnTy, /*IsConst=*/true)
+ .callBuiltin("__builtin_hlsl_resource_getpointer",
+ AST.getPointerType(AddrSpaceElemTy), PH::Handle)
+ .dereference(PH::LastStmt)
+ .finalize();
+}
+
BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addFriend(CXXRecordDecl *Friend) {
assert(!Record->isCompleteDefinition() && "record is already complete");
@@ -2159,7 +2185,7 @@ Expr *BuiltinTypeDeclBuilder::getConstantUnsignedIntExpr(unsigned value) {
BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
- ConceptDecl *CD = nullptr) {
+ ConceptDecl *CD) {
return addSimpleTemplateParams(Names, {}, CD);
}
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index e69afd67b2618..72e7bed2b991d 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -64,7 +64,7 @@ class BuiltinTypeDeclBuilder {
~BuiltinTypeDeclBuilder();
BuiltinTypeDeclBuilder &addSimpleTemplateParams(ArrayRef<StringRef> Names,
- ConceptDecl *CD);
+ ConceptDecl *CD = nullptr);
BuiltinTypeDeclBuilder &
addSimpleTemplateParams(ArrayRef<StringRef> Names,
ArrayRef<QualType> DefaultTypes, ConceptDecl *CD);
@@ -83,6 +83,7 @@ class BuiltinTypeDeclBuilder {
addTextureHandle(ResourceClass RC, bool IsROV, ResourceDimension RD,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &addSamplerHandle();
+ BuiltinTypeDeclBuilder &addConversionToType();
BuiltinTypeDeclBuilder &addArraySubscriptOperators(
ResourceDimension Dim = ResourceDimension::Unknown);
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index 235ede8eb0bf0..10ffa7d6ab370 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -472,6 +472,17 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
ConceptDecl *StructuredBufferConcept = constructBufferConceptDecl(
*SemaPtr, HLSLNamespace, /*isTypedBuffer*/ false);
+ Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "ConstantBuffer")
+ .addSimpleTemplateParams({"element_type"})
+ .finalizeForwardDeclaration();
+
+ onCompletion(Decl, [this](CXXRecordDecl *Decl) {
+ setupBufferType(Decl, *SemaPtr, ResourceClass::CBuffer, /*IsROV=*/false,
+ /*RawBuffer=*/false, /*HasCounter=*/false)
+ .addConversionToType()
+ .completeDefinition();
+ });
+
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "Buffer")
.addSimpleTemplateParams({"element_type"}, TypedBufferConcept)
.finalizeForwardDeclaration();
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index a4504410cae28..1e8ed5f678259 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1292,6 +1292,20 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
BaseExpr.get()->getValueKind(), FPOptionsOverride());
}
+ // In HLSL, the member access on a ConstantBuffer<T> access the members of
+ // through the handle in the ConstantBuffer<T>. If BaseType is a
+ // ConstantBuffer, the conversion function to type T is called before trying
+ // to access the member.
+ if (S.getLangOpts().HLSL) {
+ if (std::optional<ExprResult> ConvBase =
+ S.HLSL().performConstantBufferConversion(BaseExpr)) {
+ assert(!ConvBase->isInvalid());
+ BaseExpr = *ConvBase;
+ BaseType = BaseExpr.get()->getType();
+ IsArrow = false;
+ }
+ }
+
// Handle field access to simple records.
if (BaseType->getAsRecordDecl()) {
if (LookupMemberExprInRecord(S, R, BaseExpr.get(), BaseType, OpLoc, IsArrow,
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 7788d777edf1c..3280ea2338bb6 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -3175,6 +3175,53 @@ bool SemaHLSL::ActOnResourceMemberAccessExpr(MemberExpr *ME) {
return true;
}
+NamedDecl *SemaHLSL::getConstantBufferConversionFunction(QualType Type,
+ CXXRecordDecl *RD) {
+ QualType AddrSpaceType =
+ SemaRef.Context.getCanonicalType(SemaRef.Context.getAddrSpaceQualType(
+ Type.withConst(), LangAS::hlsl_constant));
+ QualType ReturnTy = SemaRef.Context.getCanonicalType(
+ SemaRef.Context.getLValueReferenceType(AddrSpaceType));
+
+ DeclarationName ConvName =
+ SemaRef.Context.DeclarationNames.getCXXConversionFunctionName(
+ CanQualType::CreateUnsafe(ReturnTy));
+ LookupResult ConvR(SemaRef, ConvName, SourceLocation(),
+ Sema::LookupOrdinaryName);
+ bool LookupSucceeded = SemaRef.LookupQualifiedName(ConvR, RD);
+ assert(LookupSucceeded);
+
+ for (NamedDecl *D : ConvR) {
+ if (isa<CXXConversionDecl>(D->getUnderlyingDecl()))
+ return D;
+ }
+ return nullptr;
+}
+
+std::optional<ExprResult>
+SemaHLSL::performConstantBufferConversion(ExprResult &BaseExpr) {
+ QualType BaseType = BaseExpr.get()->getType();
+ const HLSLAttributedResourceType *ResTy =
+ HLSLAttributedResourceType::findHandleTypeOnResource(
+ BaseType.getTypePtr());
+ if (!ResTy ||
+ ResTy->getAttrs().ResourceClass != llvm::dxil::ResourceClass::CBuffer)
+ return std::nullopt;
+
+ QualType TemplateType = ResTy->getContainedType();
+
+ NamedDecl *NamedConversionDecl = getConstantBufferConversionFunction(
+ TemplateType, BaseType->getAsCXXRecordDecl());
+ assert(NamedConversionDecl &&
+ "Could not find conversion function for ConstantBuffer.");
+ auto *ConversionDecl =
+ cast<CXXConversionDecl>(NamedConversionDecl->getUnderlyingDecl());
+
+ return SemaRef.BuildCXXMemberCallExpr(BaseExpr.get(), NamedConversionDecl,
+ ConversionDecl,
+ /*HadMultipleCandidates=*/false);
+}
+
void SemaHLSL::diagnoseAvailabilityViolations(TranslationUnitDecl *TU) {
// Skip running the diagnostics scan if the diagnostic mode is
// strict (-fhlsl-strict-availability) and the target shader stage is known
diff --git a/clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl b/clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl
new file mode 100644
index 0000000000000..3e2d8075a6569
--- /dev/null
+++ b/clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl
@@ -0,0 +1,24 @@
+// RUN: not %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -ast-dump -finclude-default-header -o - %s 2>&1 | FileCheck %s
+
+// Unimplemented: https://github.com/llvm/llvm-project/issues/195093
+// Once fixed, these tests should work and we should check the AST.
+
+struct S {
+ float a;
+};
+ConstantBuffer<S> cb;
+
+void takes_s(S s) {}
+
+void main() {
+ S s;
+
+ // CHECK: error: no viable constructor copying parameter of type 'const hlsl_constant S'
+ takes_s(cb);
+
+ // CHECK: error: no viable constructor copying variable of type 'const hlsl_constant S'
+ S s2 = cb;
+
+ // CHECK: error: no viable conversion from 'ConstantBuffer<S>' to 'const S'
+ s = cb;
+}
diff --git a/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
new file mode 100644
index 0000000000000..6a880c437db8f
--- /dev/null
+++ b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
@@ -0,0 +1,120 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -ast-dump -disable-llvm-passes -finclude-default-header -o - %s | FileCheck %s
+
+// CHECK: ClassTemplateDecl {{.*}} ConstantBuffer
+// CHECK: TemplateTypeParmDecl {{.*}} element_type
+// CHECK: CXXRecordDecl {{.*}} ConstantBuffer definition
+// CHECK: FinalAttr {{.*}} Implicit final
+// CHECK-NEXT: FieldDecl {{.*}} implicit __handle '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+
+// CHECK: CXXConstructorDecl {{.*}} ConstantBuffer<element_type> 'void ()' inline
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: BinaryOperator {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' '='
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+// CHECK-NEXT: CStyleCastExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' <Dependent>
+// CHECK-NEXT: CallExpr {{.*}} '<dependent type>'
+
+// CHECK: CXXConstructorDecl {{.*}} ConstantBuffer<element_type> 'void (const hlsl::ConstantBuffer<element_type> &)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} other 'const hlsl::ConstantBuffer<element_type> &'
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: BinaryOperator {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' '='
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: DeclRefExpr {{.*}} 'const hlsl::ConstantBuffer<element_type>' lvalue ParmVar {{.*}} 'other' 'const hlsl::ConstantBuffer<element_type> &'
+
+// CHECK: CXXMethodDecl {{.*}} operator= 'hlsl::ConstantBuffer<element_type> &(const hlsl::ConstantBuffer<element_type> &)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} other 'const hlsl::ConstantBuffer<element_type> &'
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: BinaryOperator {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' '='
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: DeclRefExpr {{.*}} 'const hlsl::ConstantBuffer<element_type>' lvalue ParmVar {{.*}} 'other' 'const hlsl::ConstantBuffer<element_type> &'
+// CHECK-NEXT: ReturnStmt
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+
+struct S {
+ float a;
+};
+ConstantBuffer<S> cb;
+
+struct Nested {
+ S s;
+ float b;
+};
+ConstantBuffer<Nested> cb_nested;
+
+void takes_s(S s) {}
+void takes_cb(ConstantBuffer<S> c) {}
+void takes_inout_cb(inout ConstantBuffer<S> c) {}
+
+float main() {
+ // CHECK: FunctionDecl {{.*}} main
+ // CHECK: MemberExpr {{.*}} 'const hlsl_constant float' lvalue .a
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'const hlsl_constant S' lvalue
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator const hlsl_constant S &
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+ float f1 = cb.a;
+
+ // CHECK: MemberExpr {{.*}} 'const hlsl_constant float' lvalue .b
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'const hlsl_constant Nested' lvalue
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator const hlsl_constant Nested &
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<Nested>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>' lvalue Var {{.*}} 'cb_nested' 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>'
+ float f2 = cb_nested.b;
+
+ // CHECK: MemberExpr {{.*}} 'const hlsl_constant float' lvalue .a
+ // CHECK-NEXT: MemberExpr {{.*}} 'const hlsl_constant S' lvalue .s
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'const hlsl_constant Nested' lvalue
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator const hlsl_constant Nested &
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<Nested>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>' lvalue Var {{.*}} 'cb_nested' 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>'
+ float f3 = cb_nested.s.a;
+
+ // CHECK: CallExpr {{.*}} 'void'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(ConstantBuffer<S>)' <FunctionToPointerDecay>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'void (ConstantBuffer<S>)' lvalue Function {{.*}} 'takes_cb' 'void (ConstantBuffer<S>)'
+ // CHECK-NEXT: CXXConstructExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' 'void (const hlsl::ConstantBuffer<S> &)'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+ takes_cb(cb);
+
+ // CHECK: CallExpr {{.*}} 'void'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout ConstantBuffer<S>)' <FunctionToPointerDecay>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'void (inout ConstantBuffer<S>)' lvalue Function {{.*}} 'takes_inout_cb' 'void (inout ConstantBuffer<S>)'
+ // CHECK-NEXT: HLSLOutArgExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue inout
+ takes_inout_cb(cb);
+
+ return f1 + f2 + f3;
+}
diff --git a/clang/test/CodeGenHLSL/builtins/ConstantBuffer-layout.hlsl b/clang/test/CodeGenHLSL/builtins/ConstantBuffer-layout.hlsl
new file mode 100644
index 0000000000000..1cf65b83ab501
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ConstantBuffer-layout.hlsl
@@ -0,0 +1,68 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.3-library %s \
+// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s --check-prefixes=CHECK,CHECK-DXIL
+// RUN: %clang_cc1 -finclude-default-header -triple spirv-vulkan-library %s \
+// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV
+
+// Scenario 1: Basic Padding (No row crossing).
+struct Basic {
+ float3 a;
+ float b;
+};
+// CHECK-DAG: %Basic = type <{ <3 x float>, float }>
+// CHECK-DXIL-DAG: %"class.hlsl::ConstantBuffer" = type { target("dx.CBuffer", %Basic) }
+// CHECK-SPIRV-DAG: %"class.hlsl::ConstantBuffer" = type { target("spirv.VulkanBuffer", %Basic, 2, 0) }
+ConstantBuffer<Basic> cb_basic;
+
+// Scenario 2: Row Boundary Crossing.
+struct RowCrossing {
+ float2 a;
+ float3 b;
+};
+// CHECK-DXIL-DAG: %RowCrossing = type <{ <2 x float>, target("dx.Padding", 8), <3 x float> }>
+// CHECK-SPIRV-DAG: %RowCrossing = type <{ <2 x float>, target("spirv.Padding", 8), <3 x float> }>
+ConstantBuffer<RowCrossing> cb_row_crossing;
+
+// Scenario 3: Arrays.
+struct ArrayPadding {
+ float a[2];
+ float b;
+};
+// CHECK-DXIL-DAG: %ArrayPadding = type <{ <{ [1 x <{ float, target("dx.Padding", 12) }>], float }>, float }>
+// CHECK-SPIRV-DAG: %ArrayPadding = type <{ <{ [1 x <{ float, target("spirv.Padding", 12) }>], float }>, float }>
+ConstantBuffer<ArrayPadding> cb_array;
+
+// Scenario 4: Nested Structs.
+struct Inner {
+ float a;
+};
+struct Outer {
+ Inner i;
+ float3 b;
+};
+// CHECK-DAG: %Inner = type <{ float }>
+// CHECK-DAG: %Outer = type <{ %Inner, <3 x float> }>
+ConstantBuffer<Outer> cb_nested;
+
+[numthreads(1,1,1)]
+void main() {
+ // Scenario 1
+ // CHECK-LABEL: define {{.*}} void @_Z4mainv()
+ // CHECK: %[[CB_BASIC:.*]] = call {{.*}} ptr addrspace({{.*}}) @_ZNK4hlsl14ConstantBufferI5BasicEcvRU{{.*}}S1_Ev
+ // CHECK: getelementptr inbounds nuw %Basic, ptr addrspace({{.*}}) %[[CB_BASIC]], i32 0, i32 1
+ float f1 = cb_basic.b;
+
+ // Scenario 2
+ // CHECK: %[[CB_ROW:.*]] = call {{.*}} ptr addrspace({{.*}}) @_ZNK4hlsl14ConstantBufferI11RowCrossingEcvRU{{.*}}S1_Ev
+ // CHECK: getelementptr inbounds nuw %RowCrossing, ptr addrspace({{.*}}) %[[CB_ROW]], i32 0, i32 2
+ float3 f2 = cb_row_crossing.b;
+
+ // Scenario 3
+ // CHECK: %[[CB_ARRAY:.*]] = call {{.*}} ptr addrspace({{.*}}) @_ZNK4hlsl14ConstantBufferI12ArrayPaddingEcvRU{{.*}}S1_Ev
+ // CHECK: getelementptr inbounds nuw %ArrayPadding, ptr addrspace({{.*}}) %[[CB_ARRAY]], i32 0, i32 1
+ float f3 = cb_array.b;
+
+ // Scenario 4
+ // CHECK: %[[CB_NESTED:.*]] = call {{.*}} ptr addrspace({{.*}}) @_ZNK4hlsl14ConstantBufferI5OuterEcvRU{{.*}}S1_Ev
+ // CHECK: getelementptr inbounds nuw %Outer, ptr addrspace({{.*}}) %[[CB_NESTED]], i32 0, i32 1
+ float3 f4 = cb_nested.b;
+}
diff --git a/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl b/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
new file mode 100644
index 0000000000000..cb853694a9d0d
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-DXIL
+// RUN: %clang_cc1 -finclude-default-header -triple spirv-vulkan-library -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV
+
+struct S {
+ float a;
+ int b;
+};
+
+// CHECK-DXIL: %"class.hlsl::ConstantBuffer" = type { target("dx.CBuffer", %S) }
+// CHECK-SPIRV: %"class.hlsl::ConstantBuffer" = type { target("spirv.VulkanBuffer", %S, 2, 0) }
+ConstantBuffer<S> cb;
+
+// CHECK-LABEL: define {{.*}} void @_Z4mainv()
+// CHECK-DXIL: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2KS1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
+// CHECK-DXIL: [[GEP_A:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(2) [[CB_CONV]], i32 0, i32 0
+// CHECK-DXIL: [[LOAD_A:%.*]] = load float, ptr addrspace(2) [[GEP_A]], align 4
+
+// CHECK-SPIRV: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12KS1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
+// CHECK-SPIRV: [[GEP_A:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(12) [[CB_CONV]], i32 0, i32 0
+// CHECK-SPIRV: [[LOAD_A:%.*]] = load float, ptr addrspace(12) [[GEP_A]], align 4
+
+// CHECK: store float [[LOAD_A]], ptr %f, align 4
+[numthreads(1,1,1)]
+void main() {
+ float f = cb.a;
+}
+
+struct Nested {
+ S s;
+ float c;
+};
+
+ConstantBuffer<Nested> cb_nested[2];
+
+[numthreads(1,1,1)]
+void foo() {
+ // CHECK-LABEL: define {{.*}} void @_Z3foov()
+ // CHECK-DXIL: [[TMP_CB:%.*]] = alloca %"class.hlsl::ConstantBuffer.0", align 4
+ // CHECK-DXIL: call void @_ZN4hlsl14ConstantBufferI6NestedE27__createFromImplicitBindingEjjijPKc(ptr dead_on_unwind writable sret(%"class.hlsl::ConstantBuffer.0") align 4 [[TMP_CB]], i32 noundef 1, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr noundef @cb_nested.str)
+ // CHECK-DXIL: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI6NestedEcvRU3AS2KS1_Ev(ptr noundef nonnull align 4 dereferenceable(4) [[TMP_CB]])
+ // CHECK-DXIL: [[GEP_S:%.*]] = getelementptr inbounds nuw %Nested, ptr addrspace(2) [[CB_CONV]], i32 0, i32 0
+ // CHECK-DXIL: [[GEP_A2:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(2) [[GEP_S]], i32 0, i32 0
+ // CHECK-DXIL: [[LOAD_A2:%.*]] = load float, ptr addrspace(2) [[GEP_A2]], align 4
+
+ // CHECK-SPIRV: [[TMP_CB:%.*]] = alloca %"class.hlsl::ConstantBuffer.0", align 8
+ // CHECK-SPIRV: call void @_ZN4hlsl14ConstantBufferI6NestedE27__createFromImplicitBindingEjjijPKc(ptr dead_on_unwind writable sret(%"class.hlsl::ConstantBuffer.0") align 8 [[TMP_CB]], i32 noundef 1, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr noundef @cb_nested.str)
+ // CHECK-SPIRV: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI6NestedEcvRU4AS12KS1_Ev(ptr noundef nonnull align 8 dereferenceable(8) [[TMP_CB]])
+ // CHECK-SPIRV: [[GEP_S:%.*]] = getelementptr inbounds nuw %Nested, ptr addrspace(12) [[CB_CONV]], i32 0, i32 0
+ // CHECK-SPIRV: [[GEP_A2:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(12) [[GEP_S]], i32 0, i32 0
+ // CHECK-SPIRV: [[LOAD_A2:%.*]] = load float, ptr addrspace(12) [[GEP_A2]], align 4
+
+ // CHECK: store float [[LOAD_A2]], ptr %f2, align 4
+ float f2 = cb_nested[1].s.a;
+}
+
+void takes_cb(ConstantBuffer<S> c) {}
+
+[numthreads(1,1,1)]
+void test_params() {
+ // CHECK-LABEL: define {{.*}} void @_Z11test_paramsv()
+ // CHECK: call void @_ZN4hlsl14ConstantBufferI1SEC1ERKS2_(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %agg.tmp, ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @_ZL2cb)
+ // CHECK-DXIL: call void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp)
+ // CHECK-SPIRV: call {{.*}} void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp)
+ takes_cb(cb);
+}
diff --git a/clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl b/clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl
new file mode 100644
index 0000000000000..ed1fe3ac0014e
--- /dev/null
+++ b/clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl
@@ -0,0 +1,24 @@
+// RUN: not %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
+
+// Unimplemented: https://github.com/llvm/llvm-project/issues/195093
+// These cases should work. When fixed we should add proper CHECKs.
+
+struct S {
+ float3 a;
+ float2 b;
+};
+
+cbuffer CB {
+ S s_cb;
+}
+
+ConstantBuffer<S> cb;
+
+[numthreads(1,1,1)]
+void main() {
+ // CHECK: error: no matching constructor for initialization of 'S'
+ S l1 = s_cb;
+
+ // CHECK: error: no viable constructor copying variable of type 'const hlsl_constant S'
+ S l2 = cb;
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
new file mode 100644
index 0000000000000..179a4e1fec101
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -x hlsl -finclude-default-header -fsyntax-only -verify %s
+
+struct S {
+ float a;
+
+ float foo() const {
+ return a;
+ };
+
+ void bar() { // expected-note {{'bar' declared here}}
+ a = 1.0;
+ }
+};
+
+ConstantBuffer<S> CB;
+
+[numthreads(4,1,1)]
+void main() {
+ // Bug: https://github.com/llvm/llvm-project/issues/153055
+ // Calling non-const member function is allowed, but not implemented yet.
+ // We should remove the expected error when done.
+ // expected-error at +1 {{cannot initialize object parameter of type 'const S' with an expression of type 'const hlsl_constant S'}}
+ float tmp = CB.foo();
+
+ // Calling non-const member function is not allowed.
+ // expected-error at +1 {{'this' argument to member function 'bar' has type 'const hlsl_constant S', but function is not marked const}}
+ CB.bar();
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
new file mode 100644
index 0000000000000..10c65031b79f2
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -finclude-default-header -fsyntax-only -verify %s
+
+struct S { // expected-note 3 {{candidate constructor}}
+ float a;
+ int b;
+};
+
+struct Empty {};
+
+struct ContainsResource {
+ Texture2D tex;
+};
+
+union U {
+ float a;
+ int b;
+};
+
+// Valid
+ConstantBuffer<S> cb;
+ConstantBuffer<Empty> cb_empty;
+
+void takes_inout_s(inout S s) {}
+
+void foo() {
+ // This case should fail because we cannot writeback to `cb` after the call.
+ // expected-error at +1 {{no viable constructor copying parameter of type 'const hlsl_constant S'}}
+ takes_inout_s(cb);
+}
+
+void test_direct_assignment() {
+ // expected-error at +2 {{cannot assign to return value because function 'operator const hlsl_constant S &' returns a const value}}
+ // expected-note@* {{function 'operator const hlsl_constant S &' which returns const-qualified type 'const hlsl_constant S &' declared here}}
+ cb.a = 5.0;
+}
>From bec6fa7914011909a7e496c0f0ec1d854bda6c05 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Wed, 6 May 2026 13:18:41 -0400
Subject: [PATCH 4/6] Changes from code review.
---
clang/include/clang/Sema/SemaHLSL.h | 2 +-
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 3 ++-
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 2 +-
clang/lib/Sema/HLSLExternalSemaSource.cpp | 2 +-
clang/lib/Sema/SemaExprMember.cpp | 4 ++--
clang/lib/Sema/SemaHLSL.cpp | 2 +-
6 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 7a8fb5492f8df..68c2f209976c4 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -147,7 +147,7 @@ class SemaHLSL : public SemaBase {
// `const hlsl_constant T&`. If `BaseExpr`'s type is not ConstantBuffer<T>
// then the return value is `std::nullopt`.
std::optional<ExprResult>
- performConstantBufferConversion(ExprResult &BaseExpr);
+ tryPerformConstantBufferConversion(ExprResult &BaseExpr);
// Returns the conversion operator to convert `RD` to `const hlsl_constant
// Type&`. Returns `nullptr` if it could not be found.
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 75ab5962f2b40..fb7d495b7bd30 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -1060,7 +1060,8 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addSamplerHandle() {
return *this;
}
-BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addConversionToType() {
+BuiltinTypeDeclBuilder &
+BuiltinTypeDeclBuilder::addConstantBufferConversionToType() {
assert(!Record->isCompleteDefinition() && "record is already complete");
ASTContext &AST = SemaRef.getASTContext();
using PH = BuiltinTypeMethodBuilder::PlaceHolder;
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index 72e7bed2b991d..f8fc2eeb3af55 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -83,7 +83,7 @@ class BuiltinTypeDeclBuilder {
addTextureHandle(ResourceClass RC, bool IsROV, ResourceDimension RD,
AccessSpecifier Access = AccessSpecifier::AS_private);
BuiltinTypeDeclBuilder &addSamplerHandle();
- BuiltinTypeDeclBuilder &addConversionToType();
+ BuiltinTypeDeclBuilder &addConstantBufferConversionToType();
BuiltinTypeDeclBuilder &addArraySubscriptOperators(
ResourceDimension Dim = ResourceDimension::Unknown);
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index 10ffa7d6ab370..9769eee10ae2f 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -479,7 +479,7 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
setupBufferType(Decl, *SemaPtr, ResourceClass::CBuffer, /*IsROV=*/false,
/*RawBuffer=*/false, /*HasCounter=*/false)
- .addConversionToType()
+ .addConstantBufferConversionToType()
.completeDefinition();
});
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 1e8ed5f678259..851d58c49f7b9 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1296,9 +1296,9 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
// through the handle in the ConstantBuffer<T>. If BaseType is a
// ConstantBuffer, the conversion function to type T is called before trying
// to access the member.
- if (S.getLangOpts().HLSL) {
+ if (S.getLangOpts().HLSL && BaseType->isHLSLResourceRecord()) {
if (std::optional<ExprResult> ConvBase =
- S.HLSL().performConstantBufferConversion(BaseExpr)) {
+ S.HLSL().tryPerformConstantBufferConversion(BaseExpr)) {
assert(!ConvBase->isInvalid());
BaseExpr = *ConvBase;
BaseType = BaseExpr.get()->getType();
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 3280ea2338bb6..717dfdcb36048 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -3199,7 +3199,7 @@ NamedDecl *SemaHLSL::getConstantBufferConversionFunction(QualType Type,
}
std::optional<ExprResult>
-SemaHLSL::performConstantBufferConversion(ExprResult &BaseExpr) {
+SemaHLSL::tryPerformConstantBufferConversion(ExprResult &BaseExpr) {
QualType BaseType = BaseExpr.get()->getType();
const HLSLAttributedResourceType *ResTy =
HLSLAttributedResourceType::findHandleTypeOnResource(
>From ad5664b666021c189d2ac0e1318c4cd8d4832c96 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Thu, 30 Apr 2026 11:52:48 -0400
Subject: [PATCH 5/6] [HLSL] Add type traits for ConstantBuffers templates
This commit adds the type traits to restrict the template type in a
ConstantBuffer to structs or classes that do not contain a resource
type.
Assisted-by: Gemini
---
clang/include/clang/Basic/TokenKinds.def | 1 +
clang/include/clang/Sema/SemaHLSL.h | 1 +
clang/lib/Sema/HLSLExternalSemaSource.cpp | 50 ++++++++++++++++---
clang/lib/Sema/SemaHLSL.cpp | 13 +++++
clang/lib/Sema/SemaTypeTraits.cpp | 9 ++++
.../SemaHLSL/BuiltIns/ConstantBuffers.hlsl | 33 ++++++++++++
6 files changed, 101 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 005d81b5b9282..f07d8ebb75035 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -691,6 +691,7 @@ KEYWORD(column_major , KEYHLSL)
TYPE_TRAIT_2(__builtin_hlsl_is_scalarized_layout_compatible, IsScalarizedLayoutCompatible, KEYHLSL)
TYPE_TRAIT_1(__builtin_hlsl_is_intangible, IsIntangibleType, KEYHLSL)
TYPE_TRAIT_1(__builtin_hlsl_is_typed_resource_element_compatible, IsTypedResourceElementCompatible, KEYHLSL)
+TYPE_TRAIT_1(__builtin_hlsl_is_constant_buffer_element_compatible, IsConstantBufferElementCompatible, KEYHLSL)
// OpenMP Type Traits
UNARY_EXPR_OR_TYPE_TRAIT(__builtin_omp_required_simd_align, OpenMPRequiredSimdAlign, KEYALL)
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 68c2f209976c4..e65de5d4aa4c3 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -216,6 +216,7 @@ class SemaHLSL : public SemaBase {
// HLSL Type trait implementations
bool IsScalarizedLayoutCompatible(QualType T1, QualType T2) const;
bool IsTypedResourceElementCompatible(QualType T1);
+ bool IsConstantBufferElementCompatible(QualType T1);
bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index 9769eee10ae2f..449b32a215631 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -365,6 +365,32 @@ static Expr *constructTypedBufferConstraintExpr(Sema &S, SourceLocation NameLoc,
return TypedResExpr;
}
+// This function is responsible for constructing the constraint expression for
+// this concept:
+// template<typename T> concept is_constant_buffer_element_compatible =
+// std::is_class_v<T> && !__is_intangible(T);
+static Expr *constructConstantBufferConstraintExpr(Sema &S,
+ SourceLocation NameLoc,
+ TemplateTypeParmDecl *T) {
+ ASTContext &Context = S.getASTContext();
+
+ // Obtain the QualType for 'bool'
+ QualType BoolTy = Context.BoolTy;
+
+ // Create a QualType that points to this TemplateTypeParmDecl
+ QualType TType = Context.getTypeDeclType(T);
+
+ // Create a TypeSourceInfo for the template type parameter 'T'
+ TypeSourceInfo *TTypeSourceInfo =
+ Context.getTrivialTypeSourceInfo(TType, NameLoc);
+
+ TypeTraitExpr *ResExpr = TypeTraitExpr::Create(
+ Context, BoolTy, NameLoc, UTT_IsConstantBufferElementCompatible,
+ {TTypeSourceInfo}, NameLoc, true);
+
+ return ResExpr;
+}
+
// This function is responsible for constructing the constraint expression for
// this concept:
// template<typename T> concept is_structured_resource_element_compatible =
@@ -415,8 +441,10 @@ static Expr *constructStructuredBufferConstraintExpr(Sema &S,
return CombinedExpr;
}
+enum class HLSLBufferType { Typed, Structured, Constant };
+
static ConceptDecl *constructBufferConceptDecl(Sema &S, NamespaceDecl *NSD,
- bool isTypedBuffer) {
+ HLSLBufferType BT) {
ASTContext &Context = S.getASTContext();
DeclContext *DC = NSD->getDeclContext();
SourceLocation DeclLoc = SourceLocation();
@@ -440,14 +468,22 @@ static ConceptDecl *constructBufferConceptDecl(Sema &S, NamespaceDecl *NSD,
DeclarationName DeclName;
Expr *ConstraintExpr = nullptr;
- if (isTypedBuffer) {
+ switch (BT) {
+ case HLSLBufferType::Typed:
DeclName = DeclarationName(
&Context.Idents.get("__is_typed_resource_element_compatible"));
ConstraintExpr = constructTypedBufferConstraintExpr(S, DeclLoc, T);
- } else {
+ break;
+ case HLSLBufferType::Structured:
DeclName = DeclarationName(
&Context.Idents.get("__is_structured_resource_element_compatible"));
ConstraintExpr = constructStructuredBufferConstraintExpr(S, DeclLoc, T);
+ break;
+ case HLSLBufferType::Constant:
+ DeclName = DeclarationName(
+ &Context.Idents.get("__is_constant_buffer_element_compatible"));
+ ConstraintExpr = constructConstantBufferConstraintExpr(S, DeclLoc, T);
+ break;
}
// Create a ConceptDecl
@@ -468,12 +504,14 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
ASTContext &AST = SemaPtr->getASTContext();
CXXRecordDecl *Decl;
ConceptDecl *TypedBufferConcept = constructBufferConceptDecl(
- *SemaPtr, HLSLNamespace, /*isTypedBuffer*/ true);
+ *SemaPtr, HLSLNamespace, HLSLBufferType::Typed);
ConceptDecl *StructuredBufferConcept = constructBufferConceptDecl(
- *SemaPtr, HLSLNamespace, /*isTypedBuffer*/ false);
+ *SemaPtr, HLSLNamespace, HLSLBufferType::Structured);
+ ConceptDecl *ConstantBufferConcept = constructBufferConceptDecl(
+ *SemaPtr, HLSLNamespace, HLSLBufferType::Constant);
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "ConstantBuffer")
- .addSimpleTemplateParams({"element_type"})
+ .addSimpleTemplateParams({"element_type"}, ConstantBufferConcept)
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 717dfdcb36048..402a4a59c2b61 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -4699,6 +4699,19 @@ static void BuildFlattenedTypeList(QualType BaseTy,
}
}
+bool SemaHLSL::IsConstantBufferElementCompatible(clang::QualType QT) {
+ if (QT.isNull())
+ return false;
+
+ // Must be a class/struct.
+ const auto *RD = QT->getAsCXXRecordDecl();
+ if (!RD || RD->isUnion())
+ return false;
+
+ // Cannot be a resource type or contain one.
+ return !QT->isHLSLIntangibleType();
+}
+
bool SemaHLSL::IsTypedResourceElementCompatible(clang::QualType QT) {
// null and array types are not allowed.
if (QT.isNull() || QT->isArrayType())
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index a94a59e8add7b..c79b3f7045ca6 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -367,6 +367,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
case UTT_IsCompound:
case UTT_IsMemberPointer:
case UTT_IsTypedResourceElementCompatible:
+ case UTT_IsConstantBufferElementCompatible:
// Fall-through
// These traits are modeled on type predicates in C++0x [meta.unary.prop]
@@ -1131,6 +1132,14 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
return false;
return Self.HLSL().IsTypedResourceElementCompatible(T);
+
+ case UTT_IsConstantBufferElementCompatible:
+ assert(Self.getLangOpts().HLSL &&
+ "constant buffer element compatible types are an HLSL-only feature");
+ if (T->isIncompleteType())
+ return false;
+
+ return Self.HLSL().IsConstantBufferElementCompatible(T);
}
}
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
index 10c65031b79f2..e6c42429a7d97 100644
--- a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
@@ -20,6 +20,39 @@ union U {
ConstantBuffer<S> cb;
ConstantBuffer<Empty> cb_empty;
+// Invalid: non-struct/class
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<float> cb_float;
+// expected-note@* {{because 'float' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(float)' evaluated to false}}
+
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<float4> cb_float4;
+// expected-note@* {{because 'float4' (aka 'vector<float, 4>') does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(vector<float, 4>)' evaluated to false}}
+
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<float[4]> cb_array;
+// expected-note@* {{because 'float[4]' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(float[4])' evaluated to false}}
+
+// Invalid: contains resource
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<ContainsResource> cb_res;
+// expected-note@* {{because 'ContainsResource' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(ContainsResource)' evaluated to false}}
+
+// Invalid: intangible type
+// expected-error at +1 {{use of class template 'Texture2D' requires template arguments}}
+ConstantBuffer<Texture2D> cb_tex;
+// expected-note@* {{template declaration from hidden source}}
+
+// Invalid: union
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<U> cb_union;
+// expected-note@* {{because 'U' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(U)' evaluated to false}}
+
void takes_inout_s(inout S s) {}
void foo() {
>From e0039cba4a8110ce6f3daadbf368981e9301ccf7 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Thu, 7 May 2026 16:30:50 -0400
Subject: [PATCH 6/6] Move test file, and add new test.
---
.../SemaHLSL/{BuiltIns => Resources}/ConstantBuffers.hlsl | 6 ++++++
1 file changed, 6 insertions(+)
rename clang/test/SemaHLSL/{BuiltIns => Resources}/ConstantBuffers.hlsl (88%)
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl b/clang/test/SemaHLSL/Resources/ConstantBuffers.hlsl
similarity index 88%
rename from clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
rename to clang/test/SemaHLSL/Resources/ConstantBuffers.hlsl
index e6c42429a7d97..0ef3ada50c988 100644
--- a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
+++ b/clang/test/SemaHLSL/Resources/ConstantBuffers.hlsl
@@ -47,6 +47,12 @@ ConstantBuffer<ContainsResource> cb_res;
ConstantBuffer<Texture2D> cb_tex;
// expected-note@* {{template declaration from hidden source}}
+// Invalid: intangible type
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<Texture2D<float>> cb_tex;
+// expected-note@* {{because 'Texture2D<float>' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@*:* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(hlsl::Texture2D<float>)' evaluated to false}}
+
// Invalid: union
// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
ConstantBuffer<U> cb_union;
More information about the cfe-commits
mailing list