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 <[email protected]> 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@+1 {{too few arguments to function call, expected 2, have 1}} + // expected-error@+1 {{used type 'unsigned int' where __hlsl_resource_t is required}} __builtin_hlsl_resource_getpointer(x); - // expected-error@+1 {{too many arguments to function call, expected 2, have 3}} + // expected-error@+1 {{too many arguments to function call, expected at most 2, have 3}} __builtin_hlsl_resource_getpointer(x, x, x); - // expected-error@+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@+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 <[email protected]> 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" + [email protected] = private unnamed_addr constant [2 x i8] c"B\00", align 1 [email protected] = 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 <[email protected]> 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@+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@+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@+1 {{no viable constructor copying parameter of type 'const hlsl_constant S'}} + takes_inout_s(cb); +} + +void test_direct_assignment() { + // expected-error@+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 <[email protected]> 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 <[email protected]> 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@+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@+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@+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@+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@+1 {{use of class template 'Texture2D' requires template arguments}} +ConstantBuffer<Texture2D> cb_tex; +// expected-note@* {{template declaration from hidden source}} + +// Invalid: union +// expected-error@+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 <[email protected]> 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@+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@+1 {{constraints not satisfied for class template 'ConstantBuffer'}} ConstantBuffer<U> cb_union; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
