https://github.com/s-perron updated https://github.com/llvm/llvm-project/pull/195152
>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/2] [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/2] [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)) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
