From bfb5defb5b48f40b15d863be34c2332780756f82 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:50 -0700 Subject: [PATCH 01/26] [SPIR-V] Add SPV_EXT_descriptor_heap + SPV_KHR_untyped_pointers codegen Building off of #8281, this commit adds a native lowering via SPV_EXT_descriptor_heap and SPV_KHR_untyped_pointers. ResourceDescriptorHeap and SamplerDescriptorHeap are lowered to untyped variables decorated with ResourceHeapEXT and SamplerHeapEXT. Each heap access emits OpUntypedAccessChainKHR into a runtime array of the appropriate descriptor type. Buffer-like resources (StructuredBuffer, ByteAddressBuffer, ConstantBuffer, TextureBuffer) use OpTypeBufferEXT and OpBufferPointerEXT; image and sampler resources use OpLoad. Interlocked operations on RWTexture use OpUntypedImageTexelPointerEXT. Requires -fspv-target-env=vulkan1.3. Assisted-by: Claude. --- docs/SPIR-V.rst | 87 +++- .../clang/include/clang/SPIRV/SpirvBuilder.h | 2 +- .../clang/include/clang/SPIRV/SpirvContext.h | 4 + .../include/clang/SPIRV/SpirvInstruction.h | 12 + tools/clang/lib/SPIRV/CapabilityVisitor.cpp | 6 +- tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 6 + tools/clang/lib/SPIRV/DeclResultIdMapper.h | 4 +- tools/clang/lib/SPIRV/EmitVisitor.cpp | 1 + tools/clang/lib/SPIRV/LowerTypeVisitor.cpp | 6 + tools/clang/lib/SPIRV/SpirvBuilder.cpp | 3 +- tools/clang/lib/SPIRV/SpirvContext.cpp | 11 + tools/clang/lib/SPIRV/SpirvEmitter.cpp | 458 ++++++++++++++++-- tools/clang/lib/SPIRV/SpirvEmitter.h | 131 +++++ tools/clang/lib/SPIRV/SpirvInstruction.cpp | 8 +- .../resource-heap-ext-texture.hlsl | 59 --- ...scriptorheap.ext.append-consume.error.hlsl | 19 + .../sm6_6.descriptorheap.ext.buffer.hlsl | 57 +++ ...orheap.ext.constant-buffer-assignment.hlsl | 78 +++ ...iptorheap.ext.constant-texture-buffer.hlsl | 51 ++ ....descriptorheap.ext.counter-ops.error.hlsl | 24 + ..._6.descriptorheap.ext.discarded.error.hlsl | 14 + ..._6.descriptorheap.ext.function-params.hlsl | 97 ++++ .../sm6_6.descriptorheap.ext.gather.hlsl | 42 ++ .../sm6_6.descriptorheap.ext.groupshared.hlsl | 35 ++ .../sm6_6.descriptorheap.ext.load-offset.hlsl | 27 ++ .../sm6_6.descriptorheap.ext.mixed-bound.hlsl | 54 +++ .../sm6_6.descriptorheap.ext.nonuniform.hlsl | 30 ++ ...escriptorheap.ext.rwbyteaddressbuffer.hlsl | 21 + ....descriptorheap.ext.rwtexture-atomics.hlsl | 53 ++ ...6_6.descriptorheap.ext.rwtexture-dims.hlsl | 53 ++ ...6.descriptorheap.ext.sample-grad-bias.hlsl | 40 ++ ...descriptorheap.ext.sampler-comparison.hlsl | 30 ++ ...m6_6.descriptorheap.ext.static-global.hlsl | 64 +++ ...ptorheap.ext.structured-buffer-atomic.hlsl | 28 ++ ...sm6_6.descriptorheap.ext.texture-dims.hlsl | 54 +++ .../sm6_6.descriptorheap.ext.texture-ms.hlsl | 33 ++ ...orheap.ext.texture-sampler-assignment.hlsl | 77 +++ .../sm6_6.descriptorheap.ext.texture.hlsl | 34 ++ .../sm6_6.descriptorheap.ext.texturecube.hlsl | 46 ++ ...m6_6.descriptorheap.ext.typed-formats.hlsl | 59 +++ .../unittests/SPIRV/SpirvContextTest.cpp | 3 + 41 files changed, 1818 insertions(+), 103 deletions(-) delete mode 100644 tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index da5e914e3e..042dbfd416 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -336,6 +336,8 @@ Supported extensions * SPV_KHR_float_controls * SPV_NV_shader_subgroup_partitioned * SPV_KHR_quad_control +* SPV_KHR_untyped_pointers +* SPV_EXT_descriptor_heap Vulkan specific attributes -------------------------- @@ -1993,10 +1995,14 @@ responsibility to provide proper numbers and avoid binding overlaps. ResourceDescriptorHeaps & SamplerDescriptorHeaps ------------------------------------------------ -The SPIR-V backend supported SM6.6 resource heaps, using 2 extensions: +By default, the SPIR-V backend supports SM6.6 resource heaps by emulating the +heaps with descriptor-indexing runtime arrays, using 2 extensions: + - `SPV_EXT_descriptor_indexing` - `VK_EXT_mutable_descriptor_type` +This is also the behavior selected by ``-fspv-use-emulated-heap``. + Each type loaded from a heap is considered to be an unbounded RuntimeArray bound to the descriptor set 0. @@ -2074,6 +2080,85 @@ Bindings & sets associated with each heap can be explicitly set using: - `-fvk-bind-counter-heap `: Specify Vulkan binding number and set number for the counter heap. +Native descriptor heap extension lowering +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When ``-fspv-use-descriptor-heap`` is specified, DXC lowers +``ResourceDescriptorHeap`` and ``SamplerDescriptorHeap`` through +``SPV_EXT_descriptor_heap`` instead of the default emulated heap path. This +also requires ``SPV_KHR_untyped_pointers`` and ``-fspv-target-env=vulkan1.3`` +(targeting a lower environment is an error), and a SPIRV-Headers / SPIRV-Tools +build that defines these extensions. The emitted module declares the heap +objects as untyped variables in ``UniformConstant`` storage class: + +.. code:: spirv + + %uptr_uc = OpTypeUntypedPointerKHR UniformConstant + %resource_heap = OpUntypedVariableKHR %uptr_uc UniformConstant + %sampler_heap = OpUntypedVariableKHR %uptr_uc UniformConstant + OpDecorate %resource_heap BuiltIn ResourceHeapEXT + OpDecorate %sampler_heap BuiltIn SamplerHeapEXT + +The concrete descriptor type is selected at each heap access. For image, +sampler, and texel buffer resources, DXC forms a runtime array of that +descriptor type and decorates the array with a byte ``ArrayStride``, then uses +``OpUntypedAccessChainKHR`` followed by ``OpLoad``: + +.. code:: spirv + + %image_type = OpTypeImage %float 2D 2 0 0 1 Unknown + %image_array = OpTypeRuntimeArray %image_type + OpDecorate %image_array ArrayStride 64 + %descriptor = OpUntypedAccessChainKHR %uptr_uc %image_array %resource_heap %index + %image = OpLoad %image_type %descriptor + +For buffer-like resources, DXC uses ``OpTypeBufferEXT`` as the descriptor type +and ``OpBufferPointerEXT`` to recover the pointer to the buffer data. The +descriptor storage class matches the recovered buffer pointer storage class; for +example, ``ConstantBuffer`` uses ``Uniform`` and ``TextureBuffer`` uses +``StorageBuffer``: + +.. code:: spirv + + %buffer_type = OpTypeBufferEXT Uniform + %buffer_array = OpTypeRuntimeArray %buffer_type + OpDecorate %buffer_array ArrayStride 64 + %descriptor = OpUntypedAccessChainKHR %uptr_uc %buffer_array %resource_heap %index + %buffer_ptr = OpBufferPointerEXT %_ptr_Uniform_type_BufferData %descriptor + +For ``RWTexture`` resources loaded from ``ResourceDescriptorHeap``, interlocked +operations that need a texel pointer use ``OpUntypedImageTexelPointerEXT``. +The image descriptor pointer produced by ``OpUntypedAccessChainKHR`` is passed +directly to the texel-pointer instruction instead of first storing the image +handle into a function-scope image variable: + +.. code:: spirv + + %image_type = OpTypeImage %uint 2D 2 0 0 2 R32ui + %image_array = OpTypeRuntimeArray %image_type + %descriptor = OpUntypedAccessChainKHR %uptr_uc %image_array %resource_heap %index + %uptr_image = OpTypeUntypedPointerKHR Image + %texel_ptr = OpUntypedImageTexelPointerEXT %uptr_image %image_type %descriptor %coord %sample + %old = OpAtomicIAdd %uint %texel_ptr %scope %semantics %value + +This path supports texture, RWTexture, sampler, Buffer/RWBuffer, +StructuredBuffer/RWStructuredBuffer without associated counter operations, +ByteAddressBuffer/RWByteAddressBuffer, ConstantBuffer, and TextureBuffer heap +loads, including direct field and array-element accesses for +``ConstantBuffer`` and ``TextureBuffer``. ``NonUniformResourceIndex`` is +accepted but the ``NonUniform`` decoration is not emitted on +``OpUntypedAccessChainKHR`` or the loaded value; ``SPV_EXT_descriptor_heap`` +deprecates the ``NonUniform`` decoration for heap accesses. + +Append/consume structured buffers and UAV counter heap lowering are not +supported by the native descriptor heap path yet. Those forms should continue +to use the default emulated heap lowering, or DXC will emit a diagnostic for +unsupported append/consume structured-buffer heap loads. Heap-loaded +``RWStructuredBuffer`` resources are supported for ordinary data access, but +associated counter operations such as ``IncrementCounter`` and +``DecrementCounter`` emit a diagnostic because the native descriptor heap path +does not recover an associated counter descriptor. + HLSL Expressions ================ diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index dba103207c..ae8add20a9 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -277,7 +277,7 @@ class SpirvBuilder { /// \brief Creates an OpUntypedImageTexelPointerEXT SPIR-V instruction with /// the given parameters. SpirvUntypedImageTexelPointerEXT *createUntypedImageTexelPointerEXT( - QualType resultType, SpirvInstruction *image, + QualType resultType, const SpirvType *imageType, SpirvInstruction *image, SpirvInstruction *coordinate, SpirvInstruction *sample, SourceLocation); /// \brief Creates an OpConverPtrToU SPIR-V instruction with the given diff --git a/tools/clang/include/clang/SPIRV/SpirvContext.h b/tools/clang/include/clang/SPIRV/SpirvContext.h index 97a62d4af8..8a49f961ca 100644 --- a/tools/clang/include/clang/SPIRV/SpirvContext.h +++ b/tools/clang/include/clang/SPIRV/SpirvContext.h @@ -298,6 +298,7 @@ class SpirvContext { spv::StorageClass); const UntypedPointerKHRType *getUntypedPointerKHRType(spv::StorageClass sc); + const BufferEXTType *getBufferEXTType(spv::StorageClass sc); FunctionType *getFunctionType(const SpirvType *ret, llvm::ArrayRef param); @@ -536,6 +537,9 @@ class SpirvContext { llvm::DenseMap untypedPointerKHRTypes; + llvm::DenseMap + bufferEXTTypes; llvm::MapVector forwardPointerTypes; llvm::MapVector forwardReferences; llvm::DenseSet functionTypes; diff --git a/tools/clang/include/clang/SPIRV/SpirvInstruction.h b/tools/clang/include/clang/SPIRV/SpirvInstruction.h index 39cff251c6..d83d8f7294 100644 --- a/tools/clang/include/clang/SPIRV/SpirvInstruction.h +++ b/tools/clang/include/clang/SPIRV/SpirvInstruction.h @@ -2065,6 +2065,7 @@ class SpirvImageTexelPointer : public SpirvInstruction { class SpirvUntypedImageTexelPointerEXT : public SpirvInstruction { public: SpirvUntypedImageTexelPointerEXT(QualType resultType, SourceLocation loc, + const SpirvType *imageType, SpirvInstruction *image, SpirvInstruction *coordinate, SpirvInstruction *sample); @@ -2078,11 +2079,22 @@ class SpirvUntypedImageTexelPointerEXT : public SpirvInstruction { bool invokeVisitor(Visitor *v) override; + const SpirvType *getImageType() const { return imageType; } SpirvInstruction *getImage() const { return image; } SpirvInstruction *getCoordinate() const { return coordinate; } SpirvInstruction *getSample() const { return sample; } + void replaceOperand( + llvm::function_ref remapOp, + bool inEntryFunctionWrapper) override { + // imageType is a compile-time SpirvType, not an SSA operand. + image = remapOp(image); + coordinate = remapOp(coordinate); + sample = remapOp(sample); + } + private: + const SpirvType *imageType; SpirvInstruction *image; SpirvInstruction *coordinate; SpirvInstruction *sample; diff --git a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp index 9f012d24e8..c48f898a28 100644 --- a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp +++ b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp @@ -955,8 +955,10 @@ bool CapabilityVisitor::visit(SpirvModule *, Visitor::Phase phase) { {spv::Capability::QuadControlKHR}); if (spvOptions.useDescriptorHeap) { - addExtension(Extension::EXT_descriptor_heap, "DescriptorHeap", {}); - addExtension(Extension::KHR_untyped_pointers, "DescriptorHeap", {}); + const llvm::StringRef feature = "DescriptorHeap"; + featureManager.requestTargetEnv(SPV_ENV_VULKAN_1_3, feature, {}); + addExtension(Extension::EXT_descriptor_heap, feature, {}); + addExtension(Extension::KHR_untyped_pointers, feature, {}); addCapability(spv::Capability::DescriptorHeapEXT); } diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 1a1078bf0b..62e5ff1e3e 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -1136,6 +1136,12 @@ DeclResultIdMapper::createFnVar(const VarDecl *var, return varInstr; } +void DeclResultIdMapper::registerFnVarAlias(const VarDecl *var, + SpirvInstruction *varInstr) { + if (varInstr) + registerVariableForDecl(var, createDeclSpirvInfo(varInstr)); +} + SpirvDebugGlobalVariable *DeclResultIdMapper::createDebugGlobalVariable( SpirvVariable *var, const QualType &type, const SourceLocation &loc, const StringRef &name) { diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.h b/tools/clang/lib/SPIRV/DeclResultIdMapper.h index 6e8177edf2..d6ae75e8fa 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.h +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.h @@ -10,7 +10,6 @@ #ifndef LLVM_CLANG_LIB_SPIRV_DECLRESULTIDMAPPER_H #define LLVM_CLANG_LIB_SPIRV_DECLRESULTIDMAPPER_H -#include #include #include "dxc/Support/SPIRVOptions.h" @@ -286,6 +285,9 @@ class DeclResultIdMapper { SpirvVariable *createFnVar(const VarDecl *var, llvm::Optional init); + /// \brief Registers a function-scope alias to an existing instruction. + void registerFnVarAlias(const VarDecl *var, SpirvInstruction *varInstr); + /// \brief Creates a file-scope variable and returns its instruction. SpirvVariable *createFileVar(const VarDecl *var, llvm::Optional init); diff --git a/tools/clang/lib/SPIRV/EmitVisitor.cpp b/tools/clang/lib/SPIRV/EmitVisitor.cpp index fcdcf5bcd0..75d081ee62 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.cpp +++ b/tools/clang/lib/SPIRV/EmitVisitor.cpp @@ -820,6 +820,7 @@ bool EmitVisitor::visit(SpirvUntypedImageTexelPointerEXT *inst) { initInstruction(inst); curInst.push_back(inst->getResultTypeId()); curInst.push_back(getOrAssignResultId(inst)); + curInst.push_back(typeHandler.emitType(inst->getImageType())); curInst.push_back(getOrAssignResultId(inst->getImage())); curInst.push_back( getOrAssignResultId(inst->getCoordinate())); diff --git a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp index 4c06cd4113..2d9abfb1c9 100644 --- a/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp +++ b/tools/clang/lib/SPIRV/LowerTypeVisitor.cpp @@ -224,6 +224,12 @@ bool LowerTypeVisitor::visitInstruction(SpirvInstruction *instr) { instr->setResultType(pointerType); break; } + case spv::Op::OpUntypedImageTexelPointerEXT: { + instr->setResultType( + spvContext.getUntypedPointerKHRType(spv::StorageClass::Image)); + instr->setStorageClass(spv::StorageClass::Image); + break; + } // Sparse image operations return a sparse residency struct. case spv::Op::OpImageSparseSampleImplicitLod: case spv::Op::OpImageSparseSampleExplicitLod: diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index f61d61ed71..9a48ed5f85 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -533,13 +533,14 @@ SpirvImageTexelPointer *SpirvBuilder::createImageTexelPointer( SpirvUntypedImageTexelPointerEXT * SpirvBuilder::createUntypedImageTexelPointerEXT(QualType resultType, + const SpirvType *imageType, SpirvInstruction *image, SpirvInstruction *coordinate, SpirvInstruction *sample, SourceLocation loc) { assert(insertPoint && "null insert point"); auto *instruction = new (context) SpirvUntypedImageTexelPointerEXT( - resultType, loc, image, coordinate, sample); + resultType, loc, imageType, image, coordinate, sample); insertPoint->addInstruction(instruction); return instruction; } diff --git a/tools/clang/lib/SPIRV/SpirvContext.cpp b/tools/clang/lib/SPIRV/SpirvContext.cpp index c28da78f9d..a3397d74b4 100644 --- a/tools/clang/lib/SPIRV/SpirvContext.cpp +++ b/tools/clang/lib/SPIRV/SpirvContext.cpp @@ -65,6 +65,9 @@ SpirvContext::~SpirvContext() { for (auto *npaType : nodePayloadArrayTypes) npaType->~NodePayloadArrayType(); + for (auto &pair : bufferEXTTypes) + pair.second->~BufferEXTType(); + for (auto *fnType : functionTypes) fnType->~FunctionType(); @@ -402,6 +405,14 @@ const StructType *SpirvContext::getByteAddressBufferType(bool isWritable) { !isWritable, StructInterfaceType::StorageBuffer); } +const BufferEXTType *SpirvContext::getBufferEXTType(spv::StorageClass sc) { + auto found = bufferEXTTypes.find(sc); + if (found != bufferEXTTypes.end()) + return found->second; + + return bufferEXTTypes[sc] = new (this) BufferEXTType(sc); +} + const StructType *SpirvContext::getACSBufferCounterType() { // Create int32. const auto *int32Type = getSIntType(32); diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index d940b460ed..e38bf375bb 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -1227,6 +1227,12 @@ SpirvInstruction *SpirvEmitter::doExpr(const Expr *expr, auto *decl = declRefExpr->getDecl(); if (isImplicitVarDeclInVkNamespace(declRefExpr->getDecl())) { result = doExpr(cast(decl)->getInit()); + } else if (const auto *varDecl = dyn_cast(decl)) { + if (auto *alias = + emitDescriptorHeapBufferPointer(varDecl, expr->getLocStart())) + result = alias; + else + result = declIdMapper.getDeclEvalInfo(decl, expr->getLocStart(), range); } else { result = declIdMapper.getDeclEvalInfo(decl, expr->getLocStart(), range); } @@ -2020,6 +2026,22 @@ void SpirvEmitter::doEnumDecl(const EnumDecl *decl) { declIdMapper.createEnumConstant(*it); } +bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, + const Expr *init) { + if (!spirvOptions.useDescriptorHeap || !init || + !isDescriptorHeap(init->IgnoreParenCasts())) + return false; + + if (isConstantTextureBuffer(decl->getType()) || + isAKindOfStructuredOrByteBuffer(decl->getType())) { + (void)doExpr(init->IgnoreParenCasts()); + tryToAssignDescriptorHeapBufferAlias(decl, init); + return true; + } + + return false; +} + void SpirvEmitter::doVarDecl(const VarDecl *decl) { if (!validateVKAttributes(decl)) return; @@ -2209,8 +2231,11 @@ void SpirvEmitter::doVarDecl(const VarDecl *decl) { declIdMapper.tryToCreateConstantVar(decl)) return; var = declIdMapper.createFileVar(decl, llvm::None); - } else + } else { + if (tryToCreateDescriptorHeapAlias(decl, decl->getInit())) + return; var = declIdMapper.createFnVar(decl, llvm::None); + } // Emit OpStore to initialize the variable // TODO: revert back to use OpVariable initializer @@ -2233,6 +2258,7 @@ void SpirvEmitter::doVarDecl(const VarDecl *decl) { spvBuilder.createStore(var, constInit, loc, range); } else { storeValue(var, loadIfGLValue(init), decl->getType(), loc, range); + tryToAssignDescriptorHeapImageAlias(decl, init); } // Update counter variable associated with local variables @@ -3123,6 +3149,33 @@ SpirvEmitter::doArraySubscriptExpr(const ArraySubscriptExpr *expr, return loadVal; } +llvm::Optional +SpirvEmitter::tryToAssignToDescriptorHeapBuffer( + const BinaryOperator *assignExpr) { + if (!spirvOptions.useDescriptorHeap) + return llvm::None; + + const QualType lhsType = assignExpr->getLHS()->getType(); + if (!isConstantTextureBuffer(lhsType) && + !isAKindOfStructuredOrByteBuffer(lhsType)) + return llvm::None; + + const Expr *rhsValue = assignExpr->getRHS()->IgnoreParenCasts(); + if (!isDescriptorHeap(rhsValue)) + return llvm::None; + + (void)doExpr(rhsValue); + if (!tryToAssignDescriptorHeapBufferAlias(assignExpr->getLHS(), + assignExpr->getRHS())) + return llvm::None; + + const auto *decl = + dyn_cast_or_null(getReferencedDef(assignExpr->getLHS())); + if (!decl) + return static_cast(nullptr); + return emitDescriptorHeapBufferPointer(decl, assignExpr->getExprLoc()); +} + SpirvInstruction *SpirvEmitter::doBinaryOperator(const BinaryOperator *expr) { const auto opcode = expr->getOpcode(); @@ -3132,7 +3185,14 @@ SpirvInstruction *SpirvEmitter::doBinaryOperator(const BinaryOperator *expr) { // Update counter variable associated with lhs of assignments tryToAssignCounterVar(expr->getLHS(), expr->getRHS()); - return processAssignment(expr->getLHS(), loadIfGLValue(expr->getRHS()), + if (llvm::Optional aliasResult = + tryToAssignToDescriptorHeapBuffer(expr)) + return aliasResult.getValue(); + + auto *rhs = loadIfGLValue(expr->getRHS()); + tryToAssignDescriptorHeapImageAlias(expr->getLHS(), expr->getRHS()); + + return processAssignment(expr->getLHS(), rhs, /*isCompoundAssignment=*/false, nullptr, expr->getSourceRange()); } @@ -5036,9 +5096,237 @@ SpirvEmitter::processStructuredBufferLoad(const CXXMemberCallExpr *expr) { auto *zero = spvBuilder.getConstantInt(astContext.IntTy, llvm::APInt(32, 0)); auto *index = doExpr(expr->getArg(0)); - return derefOrCreatePointerToValue(buffer->getType(), info, structType, - {zero, index}, buffer->getExprLoc(), - range); + auto *result = + derefOrCreatePointerToValue(buffer->getType(), info, structType, + {zero, index}, buffer->getExprLoc(), range); + + // derefOrCreatePointerToValue returns an lvalue (AccessChain) when the base + // is an lvalue. This covers descriptor-heap buffers reached either directly + // (ResourceDescriptorHeap[i].Load()) or through a local alias var. + // StructuredBuffer::Load semantically returns a value, and the AST emits no + // LValueToRValue cast for the call expression, so emit the load explicitly. + // (Verified required: scoping this to alias vars only regresses the direct + // heap-access tests; non-heap callers are unaffected in the existing suite.) + if (result && !result->isRValue()) { + result = + spvBuilder.createLoad(structType, result, buffer->getExprLoc(), range); + } + + return result; +} + +void SpirvEmitter::markDescriptorHeapCounterUnsupported( + const DeclaratorDecl *decl) { + if (decl) + descriptorHeapUnsupportedCounters.insert(decl); +} + +bool SpirvEmitter::isDescriptorHeapCounterUnsupported(const Expr *expr) const { + if (const auto *decl = getReferencedDef(expr)) + return descriptorHeapUnsupportedCounters.count(decl) != 0; + return false; +} + +SpirvInstruction *SpirvEmitter::emitDescriptorHeapAccessChain( + const SpirvType *arrayType, SpirvInstruction *heap, SpirvVariable *indexVar, + SourceLocation loc) { + const auto *untypedUniformConstantType = + spvContext.getUntypedPointerKHRType(spv::StorageClass::UniformConstant); + auto *index = spvBuilder.createLoad(astContext.UnsignedIntTy, indexVar, loc); + return spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, + arrayType, heap, index, loc); +} + +void SpirvEmitter::storeDescriptorHeapIndex(SpirvVariable *indexVar, + SpirvInstruction *index, + QualType indexType, + const Expr *srcExpr) { + if (!astContext.hasSameType(indexType, astContext.UnsignedIntTy)) + index = castToType(index, indexType, astContext.UnsignedIntTy, + srcExpr->getExprLoc(), srcExpr->getSourceRange()); + spvBuilder.createStore(indexVar, index, srcExpr->getExprLoc(), + srcExpr->getSourceRange()); +} + +SpirvVariable * +SpirvEmitter::createDescriptorHeapIndexVar(const VarDecl *dstVar) { + const std::string name = dstVar->getName().str() + ".descriptor.index"; + return spvBuilder.addFnVar(astContext.UnsignedIntTy, dstVar->getLocation(), + name); +} + +bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias( + const DeclaratorDecl *dstDecl, const Expr *srcExpr) { + if (!spirvOptions.useDescriptorHeap || !dstDecl || !srcExpr) + return false; + + const auto *dstVar = dyn_cast(dstDecl); + if (!dstVar || + (!isRWTexture(dstVar->getType()) && !isRWBuffer(dstVar->getType()))) + return false; + + const auto *src = srcExpr->IgnoreParenCasts(); + auto found = descriptorHeapImageAccesses.find(src); + if (found == descriptorHeapImageAccesses.end()) + return false; + + auto &alias = descriptorHeapImageAliasVars[dstVar]; + if (!alias.indexVar) + alias.indexVar = createDescriptorHeapIndexVar(dstVar); + alias.imageType = found->second.imageType; + alias.arrayType = found->second.arrayType; + alias.heap = found->second.heap; + storeDescriptorHeapIndex(alias.indexVar, found->second.index, + found->second.indexType, srcExpr); + return true; +} + +bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias(const Expr *dstExpr, + const Expr *srcExpr) { + return tryToAssignDescriptorHeapImageAlias(getReferencedDef(dstExpr), + srcExpr); +} + +bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias( + const DeclaratorDecl *dstDecl, const Expr *srcExpr) { + if (!spirvOptions.useDescriptorHeap || !dstDecl || !srcExpr) + return false; + + const auto *dstVar = dyn_cast(dstDecl); + if (!dstVar || !(isConstantTextureBuffer(dstVar->getType()) || + isAKindOfStructuredOrByteBuffer(dstVar->getType()))) + return false; + + const auto *src = srcExpr->IgnoreParenCasts(); + auto found = descriptorHeapBufferAccesses.find(src); + if (found == descriptorHeapBufferAccesses.end()) + return false; + + if (isRWStructuredBuffer(dstVar->getType())) + markDescriptorHeapCounterUnsupported(dstVar); + + auto &alias = descriptorHeapBufferAliasVars[dstVar]; + if (!alias.indexVar) + alias.indexVar = createDescriptorHeapIndexVar(dstVar); + alias.bufferPointerType = found->second.bufferPointerType; + alias.arrayType = found->second.arrayType; + alias.heap = found->second.heap; + alias.layoutRule = found->second.layoutRule; + storeDescriptorHeapIndex(alias.indexVar, found->second.index, + found->second.indexType, srcExpr); + return true; +} + +bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias(const Expr *dstExpr, + const Expr *srcExpr) { + return tryToAssignDescriptorHeapBufferAlias(getReferencedDef(dstExpr), + srcExpr); +} + +SpirvInstruction * +SpirvEmitter::emitDescriptorHeapBufferPointer(const VarDecl *decl, + SourceLocation loc) { + auto found = descriptorHeapBufferAliasVars.find(decl); + if (found == descriptorHeapBufferAliasVars.end()) + return nullptr; + + auto *descriptorPtr = emitDescriptorHeapAccessChain( + found->second.arrayType, found->second.heap, found->second.indexVar, loc); + auto *bufferDataPtr = spvBuilder.createUnaryOp( + spv::Op::OpBufferPointerEXT, found->second.bufferPointerType, + descriptorPtr, loc); + bufferDataPtr->setStorageClass( + found->second.bufferPointerType->getStorageClass()); + bufferDataPtr->setLayoutRule(found->second.layoutRule); + bufferDataPtr->setRValue(false); + return bufferDataPtr; +} + +SpirvInstruction *SpirvEmitter::emitDescriptorHeapImageTexelPointer( + const VarDecl *decl, SpirvInstruction *coordinate, SpirvInstruction *sample, + QualType resultType, SourceLocation loc) { + auto found = descriptorHeapImageAliasVars.find(decl); + if (found == descriptorHeapImageAliasVars.end()) + return nullptr; + + auto *descriptorPtr = emitDescriptorHeapAccessChain( + found->second.arrayType, found->second.heap, found->second.indexVar, loc); + auto *ptr = spvBuilder.createUntypedImageTexelPointerEXT( + resultType, found->second.imageType, descriptorPtr, coordinate, sample, + loc); + ptr->setStorageClass(spv::StorageClass::Image); + return ptr; +} + +// Descriptor-heap buffers: ConstantBuffer is a UBO (Uniform); every other +// buffer resource (Structured/RW/ByteAddress, TextureBuffer) is an SSBO +// (StorageBuffer). Here because the opaque OpTypeBufferEXT descriptor +// carries no pointee interface type, so RemoveBufferBlockVisitor +// cannot infer/correct its storage class post-lowering. +static spv::StorageClass +getDescriptorHeapBufferStorageClass(QualType resourceType) { + return isConstantBuffer(resourceType) ? spv::StorageClass::Uniform + : spv::StorageClass::StorageBuffer; +} + +SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( + QualType resourceType, SpirvInstruction *heapVar, SpirvInstruction *index, + const Expr *expr, const Expr *baseExpr, const Expr *indexExpr) { + const auto *untypedUniformConstantType = + spvContext.getUntypedPointerKHRType(spv::StorageClass::UniformConstant); + LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, + spvBuilder); + const SpirvType *bufferDataType = lowerTypeVisitor.lowerType( + resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); + + const SpirvPointerType *bufferDataPointerType = nullptr; + SpirvLayoutRule layoutRule = spirvOptions.sBufferLayoutRule; + if (isConstantTextureBuffer(resourceType)) { + layoutRule = isConstantBuffer(resourceType) + ? spirvOptions.cBufferLayoutRule + : spirvOptions.tBufferLayoutRule; + bufferDataPointerType = spvContext.getPointerType( + bufferDataType, getDescriptorHeapBufferStorageClass(resourceType)); + } else { + bufferDataPointerType = dyn_cast(bufferDataType); + } + + if (!bufferDataPointerType) { + emitError("descriptor heap buffer type lowering failed", + expr->getExprLoc()); + return nullptr; + } + + // ConstantBuffer -> Uniform (UBO); all others -> StorageBuffer (SSBO) + // TODO: Remove this manual override once LowerTypeVisitor returns the + // correct StorageClass for descriptor-heap alias pointer types + // (currently it returns Uniform for all of them). + const spv::StorageClass bufferExtSC = isConstantBuffer(resourceType) + ? spv::StorageClass::Uniform + : spv::StorageClass::StorageBuffer; + const auto *bufferDescriptorType = spvContext.getBufferEXTType(bufferExtSC); + // Buffer descriptors are always on the resource heap. + const auto *arrayType = getDescriptorHeapRuntimeArrayType( + bufferDescriptorType, /*onSamplerHeap=*/false); + auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( + untypedUniformConstantType, arrayType, heapVar, index, + baseExpr->getExprLoc()); + auto *bufferDataPtr = spvBuilder.createUnaryOp( + spv::Op::OpBufferPointerEXT, bufferDataPointerType, untypedAccessChainPtr, + baseExpr->getExprLoc()); + bufferDataPtr->setStorageClass(bufferDataPointerType->getStorageClass()); + bufferDataPtr->setLayoutRule(layoutRule); + bufferDataPtr->setRValue(false); + if (isRasterizerOrderedView(resourceType)) { + bufferDataPtr->setRasterizerOrdered(true); + spvBuilder.addExecutionMode(entryFunction, + declIdMapper.getInterlockExecutionMode(), {}, + baseExpr->getExprLoc()); + } + descriptorHeapBufferAccesses[expr] = { + bufferDataPointerType, arrayType, heapVar, index, + indexExpr->getType(), layoutRule}; + return bufferDataPtr; } SpirvInstruction * @@ -5062,6 +5350,22 @@ SpirvEmitter::incDecRWACSBufferCounter(const CXXMemberCallExpr *expr, (void)doExpr(object); } + if (isDescriptorHeapCounterUnsupported(object)) { + emitError("counter operations on heap-loaded RWStructuredBuffer are not " + "supported with SPV_EXT_descriptor_heap", + expr->getCallee()->getExprLoc()); + return nullptr; + } + + if (spirvOptions.useDescriptorHeap && + (isAppendStructuredBuffer(object->getType()) || + isConsumeStructuredBuffer(object->getType()))) { + emitError("append/consume structured buffers are not supported with " + "SPV_EXT_descriptor_heap", + expr->getCallee()->getExprLoc()); + return nullptr; + } + auto *counter = getFinalACSBufferCounterInstruction(object); if (!counter) { emitFatalError("Cannot access associated counter variable for an array of " @@ -5108,6 +5412,11 @@ bool SpirvEmitter::tryToAssignCounterVar(const DeclaratorDecl *dstDecl, declIdMapper.getOrCreateCounterIdAliasPair(dstDecl)) { auto *srcCounter = getFinalACSBufferCounterInstruction(srcExpr); if (!srcCounter) { + if (spirvOptions.useDescriptorHeap && + isDescriptorHeap(srcExpr->IgnoreParenCasts())) { + markDescriptorHeapCounterUnsupported(dstDecl); + return true; + } emitFatalError("cannot find the associated counter variable", srcExpr->getExprLoc()); return false; @@ -5147,6 +5456,11 @@ bool SpirvEmitter::tryToAssignCounterVar(const Expr *dstExpr, auto *srcCounter = getFinalACSBufferCounterInstruction(srcExpr); if ((dstCounter == nullptr) != (srcCounter == nullptr)) { + if (spirvOptions.useDescriptorHeap && dstCounter && + isDescriptorHeap(srcExpr->IgnoreParenCasts())) { + markDescriptorHeapCounterUnsupported(getReferencedDef(dstExpr)); + return true; + } emitFatalError("cannot handle associated counter variable assignment", srcExpr->getExprLoc()); return false; @@ -5278,6 +5592,8 @@ SpirvEmitter::processACSBufferAppendConsume(const CXXMemberCallExpr *expr) { expr, isAppend, // We have already translated the object in the above. Avoid duplication. /*loadObject=*/false); + if (!index) + return nullptr; auto bufferElemTy = hlsl::GetHLSLResourceResultType(object->getType()); @@ -5700,16 +6016,16 @@ SpirvEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr, retVal = processTextureLevelOfDetail(expr, /* unclamped */ true); break; case IntrinsicOp::MOP_IncrementCounter: - retVal = spvBuilder.createUnaryOp( - spv::Op::OpBitcast, astContext.UnsignedIntTy, - incDecRWACSBufferCounter(expr, /*isInc*/ true), - expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); + if (auto *counter = incDecRWACSBufferCounter(expr, /*isInc*/ true)) + retVal = spvBuilder.createUnaryOp( + spv::Op::OpBitcast, astContext.UnsignedIntTy, counter, + expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); break; case IntrinsicOp::MOP_DecrementCounter: - retVal = spvBuilder.createUnaryOp( - spv::Op::OpBitcast, astContext.UnsignedIntTy, - incDecRWACSBufferCounter(expr, /*isInc*/ false), - expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); + if (auto *counter = incDecRWACSBufferCounter(expr, /*isInc*/ false)) + retVal = spvBuilder.createUnaryOp( + spv::Op::OpBitcast, astContext.UnsignedIntTy, counter, + expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); break; case IntrinsicOp::MOP_Append: if (hlsl::IsHLSLStreamOutputType( @@ -6652,10 +6968,32 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, const Expr *indexExpr = nullptr; getDescriptorHeapOperands(expr, &baseExpr, &indexExpr); - const Expr *parentExpr = cast(parentMap->getParent(expr)); + // The heap index expression must be immediately converted to a concrete + // resource type (an implicit cast inserted by the front-end). If the + // parent is missing or is not a cast (e.g. the result is discarded as + // a statement, or used in a context with no target resource type) we + // cannot determine the resource type. + const auto *parentExpr = + dyn_cast_or_null(parentMap->getParent(expr)); + if (!parentExpr) { + emitError("ResourceDescriptorHeap/SamplerDescriptorHeap indexing must " + "be used as a resource", + expr->getExprLoc()); + return nullptr; + } QualType resourceType = parentExpr->getType(); + // The heap object must be a direct reference to the builtin heap + // variable. Anything else (e.g. a non-variable expression) has no backing + // VarDecl. const auto *declRefExpr = dyn_cast(baseExpr->IgnoreCasts()); - auto *decl = cast(declRefExpr->getDecl()); + const auto *decl = + declRefExpr ? dyn_cast(declRefExpr->getDecl()) : nullptr; + if (!decl) { + emitError("unsupported ResourceDescriptorHeap/SamplerDescriptorHeap " + "expression", + baseExpr->getExprLoc()); + return nullptr; + } auto *var = declIdMapper.createResourceHeap(decl, resourceType); if (hlsl::HasHLSLGloballyCoherent(resourceType)) @@ -6663,27 +7001,44 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, auto *index = doExpr(indexExpr); if (spirvOptions.useDescriptorHeap) { - emitWarning("SPV_EXT_descriptor_heap support is incomplete.", - baseExpr->getExprLoc()); needsLegalization = true; - if (isAKindOfStructuredOrByteBuffer(resourceType)) { - emitError("UAV support not implemented with non-emulated heaps.", + if (isAppendStructuredBuffer(resourceType) || + isConsumeStructuredBuffer(resourceType)) { + emitError("append/consume structured buffers are not supported with " + "SPV_EXT_descriptor_heap", expr->getExprLoc()); return nullptr; } - const auto *untypedType = spvContext.getUntypedPointerKHRType( - spv::StorageClass::UniformConstant); + if (isAKindOfStructuredOrByteBuffer(resourceType) || + isConstantTextureBuffer(resourceType)) { + return emitDescriptorHeapBufferAccess(resourceType, var, index, expr, + baseExpr, indexExpr); + } + + const auto *untypedUniformConstantType = + spvContext.getUntypedPointerKHRType( + spv::StorageClass::UniformConstant); LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, spvBuilder); const SpirvType *handleType = lowerTypeVisitor.lowerType(resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - const auto *arrayType = - spvContext.getRuntimeArrayType(handleType, llvm::None); + // Images/samplers may come from either heap; pick the right stride. + const auto *arrayType = getDescriptorHeapRuntimeArrayType( + handleType, isSamplerDescriptorHeap(decl)); auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( - untypedType, arrayType, var, index, baseExpr->getExprLoc()); + untypedUniformConstantType, arrayType, var, index, + baseExpr->getExprLoc()); + if (isRasterizerOrderedView(resourceType)) { + spvBuilder.addExecutionMode(entryFunction, + declIdMapper.getInterlockExecutionMode(), + {}, baseExpr->getExprLoc()); + } + descriptorHeapImageAccesses[expr] = { + untypedAccessChainPtr, handleType, arrayType, var, index, + indexExpr->getType()}; return spvBuilder.createLoad(resourceType, untypedAccessChainPtr, baseExpr->getExprLoc(), range); } @@ -8878,6 +9233,16 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { declIdMapper.registerSpecConstant(varDecl, specConstant); } +const SpirvType * +SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap) { + constexpr uint32_t kDefaultResourceHeapStride = 64; + constexpr uint32_t kDefaultSamplerHeapStride = 32; + const uint32_t stride = + onSamplerHeap ? kDefaultSamplerHeapStride : kDefaultResourceHeapStride; + return spvContext.getRuntimeArrayType(elemType, stride); +} + SpirvInstruction * SpirvEmitter::processMatrixBinaryOp(const Expr *lhs, const Expr *rhs, const BinaryOperatorKind opcode, @@ -10577,18 +10942,41 @@ SpirvEmitter::processIntrinsicInterlockedMethod(const CallExpr *expr, return nullptr; } } - auto *baseInstr = doExpr(base); - if (baseInstr->isRValue()) { - // OpImageTexelPointer's Image argument must have a type of - // OpTypePointer with Type OpTypeImage. Need to create a temporary - // variable if the baseId is an rvalue. - baseInstr = - createTemporaryVar(base->getType(), getAstTypeName(base->getType()), - baseInstr, base->getExprLoc()); - } auto *coordInstr = doExpr(index); - ptr = spvBuilder.createImageTexelPointer(baseType, baseInstr, coordInstr, - zero, srcLoc); + + if (spirvOptions.useDescriptorHeap) { + const Expr *heapBase = base->IgnoreParenCasts(); + auto access = descriptorHeapImageAccesses.find(heapBase); + if (access == descriptorHeapImageAccesses.end() && + isDescriptorHeap(heapBase)) { + (void)doExpr(heapBase); + access = descriptorHeapImageAccesses.find(heapBase); + } + if (access != descriptorHeapImageAccesses.end()) { + ptr = spvBuilder.createUntypedImageTexelPointerEXT( + baseType, access->second.imageType, access->second.accessChain, + coordInstr, zero, srcLoc); + ptr->setStorageClass(spv::StorageClass::Image); + } else if (const auto *decl = + dyn_cast_or_null(getReferencedDef(base))) { + ptr = emitDescriptorHeapImageTexelPointer(decl, coordInstr, zero, + baseType, srcLoc); + } + } + + if (!ptr) { + auto *baseInstr = doExpr(base); + if (baseInstr->isRValue()) { + // OpImageTexelPointer's Image argument must have a type of + // OpTypePointer with Type OpTypeImage. Need to create a temporary + // variable if the baseId is an rvalue. + baseInstr = createTemporaryVar(base->getType(), + getAstTypeName(base->getType()), + baseInstr, base->getExprLoc()); + } + ptr = spvBuilder.createImageTexelPointer(baseType, baseInstr, + coordInstr, zero, srcLoc); + } } } if (!ptr) { diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 10cc31023c..76c174eab4 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -33,6 +33,7 @@ #include "clang/SPIRV/FeatureManager.h" #include "clang/SPIRV/SpirvBuilder.h" #include "clang/SPIRV/SpirvContext.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "ConstEvaluator.h" @@ -398,6 +399,23 @@ class SpirvEmitter : public ASTConsumer { /// Translates the given varDecl into a spec constant. void createSpecConstant(const VarDecl *varDecl); + /// Returns the OpTypeRuntimeArray for a descriptor-heap array of elemType + /// decorated with the default ArrayStride (64 bytes for the resource heap, + /// 32 bytes for the sampler heap). + const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap); + + /// Emits the native (SPV_EXT_descriptor_heap) access for a buffer-like + /// resource (StructuredBuffer/ByteAddressBuffer/ConstantBuffer/TextureBuffer + /// and their RW variants) loaded from heapVar at index: + /// OpUntypedAccessChainKHR -> OpBufferPointerEXT. Records the access in + /// descriptorHeapBufferAccesses[expr] and returns the buffer-data pointer, or + /// nullptr (after emitting an error) on type-lowering failure. Caller must + /// have already checked the resource is buffer-like. + SpirvInstruction *emitDescriptorHeapBufferAccess( + QualType resourceType, SpirvInstruction *heapVar, SpirvInstruction *index, + const Expr *expr, const Expr *baseExpr, const Expr *indexExpr); + /// Generates the necessary instructions for conducting the given binary /// operation on lhs and rhs. /// @@ -1202,6 +1220,74 @@ class SpirvEmitter : public ASTConsumer { const Expr *srcExpr); bool tryToAssignCounterVar(const Expr *dstExpr, const Expr *srcExpr); + /// \brief Marks an alias resource as heap-loaded with no associated counter. + void markDescriptorHeapCounterUnsupported(const DeclaratorDecl *decl); + + /// \brief Returns true if counter operations on the resource expression are + /// known to be unsupported because the resource came from + /// ResourceDescriptorHeap. + bool isDescriptorHeapCounterUnsupported(const Expr *expr) const; + + /// \brief Records the descriptor heap index assigned to a local image + /// resource alias, if the source expression came directly from a descriptor + /// heap. This mirrors the normal resource handle store while preserving + /// enough information to recreate OpUntypedImageTexelPointerEXT after + /// reassignment. + bool tryToAssignDescriptorHeapImageAlias(const DeclaratorDecl *dstDecl, + const Expr *srcExpr); + bool tryToAssignDescriptorHeapImageAlias(const Expr *dstExpr, + const Expr *srcExpr); + bool tryToAssignDescriptorHeapBufferAlias(const DeclaratorDecl *dstDecl, + const Expr *srcExpr); + bool tryToAssignDescriptorHeapBufferAlias(const Expr *dstExpr, + const Expr *srcExpr); + + /// \brief Creates the ".descriptor.index" function variable used to + /// remember the descriptor heap index of a local resource alias dstVar. + SpirvVariable *createDescriptorHeapIndexVar(const VarDecl *dstVar); + + /// \brief If decl is a function-local variable initialized directly from a + /// descriptor heap subscript (e.g. ResourceDescriptorHeap[i]), creates the + /// appropriate alias and returns true. Returns false if decl is not such a + /// descriptor-heap alias and should be emitted as a normal variable. + bool tryToCreateDescriptorHeapAlias(const VarDecl *decl, const Expr *init); + + /// \brief Handles a buffer = ResourceDescriptorHeap[i] assignment. Returns + /// None if assignExpr is not such an assignment (caller should fall back to a + /// normal assignment). Otherwise the alias was created and the wrapped value + /// is the result of the assignment expression (possibly nullptr). + llvm::Optional + tryToAssignToDescriptorHeapBuffer(const BinaryOperator *assignExpr); + + /// \brief Emits the instructions that re-derive the buffer-data pointer for a + /// descriptor-heap buffer alias decl (OpLoad of the saved index, then + /// OpUntypedAccessChainKHR + OpBufferPointerEXT). Returns nullptr if decl + /// is not a recorded heap buffer alias. Not a pure lookup -- it emits. + SpirvInstruction *emitDescriptorHeapBufferPointer(const VarDecl *decl, + SourceLocation loc); + + /// \brief Emits an OpUntypedImageTexelPointerEXT for a descriptor-heap image + /// alias decl (OpLoad of the saved index, then OpUntypedAccessChainKHR + /// feeding the texel pointer). Returns nullptr if decl is not a recorded heap + /// image alias. Symmetric with emitDescriptorHeapBufferPointer. + SpirvInstruction *emitDescriptorHeapImageTexelPointer( + const VarDecl *decl, SpirvInstruction *coordinate, + SpirvInstruction *sample, QualType resultType, SourceLocation loc); + + /// \brief Emits OpLoad of indexVar then OpUntypedAccessChainKHR into the + /// heap, yielding the per-descriptor pointer shared by the buffer/image alias + /// re-derivation paths above. + SpirvInstruction *emitDescriptorHeapAccessChain(const SpirvType *arrayType, + SpirvInstruction *heap, + SpirvVariable *indexVar, + SourceLocation loc); + + /// \brief Stores index (cast to uint when needed) into the alias indexVar, + /// shared by the image/buffer alias-assignment paths. + void storeDescriptorHeapIndex(SpirvVariable *indexVar, + SpirvInstruction *index, QualType indexType, + const Expr *srcExpr); + /// Returns an instruction that points to the alias counter variable with the /// entity represented by expr. /// @@ -1555,6 +1641,51 @@ class SpirvEmitter : public ASTConsumer { /// The SPIR-V function parameter for the current this object. SpirvInstruction *curThis; + /// Native descriptor heap image descriptors used to directly form image + /// atomics. The emitter is single-use per translation unit, so these + /// AST-pointer maps live for the emitter lifetime. + struct DescriptorHeapImageAccess { + SpirvInstruction *accessChain; + const SpirvType *imageType; + const SpirvType *arrayType; + SpirvInstruction *heap; + SpirvInstruction *index; + QualType indexType; + }; + struct DescriptorHeapImageAlias { + SpirvVariable *indexVar; + const SpirvType *imageType; + const SpirvType *arrayType; + SpirvInstruction *heap; + }; + struct DescriptorHeapBufferAccess { + const SpirvPointerType *bufferPointerType; + const SpirvType *arrayType; + SpirvInstruction *heap; + SpirvInstruction *index; + QualType indexType; + SpirvLayoutRule layoutRule; + }; + struct DescriptorHeapBufferAlias { + SpirvVariable *indexVar; + const SpirvPointerType *bufferPointerType; + const SpirvType *arrayType; + SpirvInstruction *heap; + SpirvLayoutRule layoutRule; + }; + llvm::DenseMap + descriptorHeapImageAccesses; + llvm::DenseMap + descriptorHeapImageAliasVars; + llvm::DenseMap + descriptorHeapBufferAccesses; + llvm::DenseMap + descriptorHeapBufferAliasVars; + + /// RWStructuredBuffer aliases loaded from ResourceDescriptorHeap have no + /// associated UAV counter descriptor in the native descriptor heap path. + llvm::DenseSet descriptorHeapUnsupportedCounters; + /// The source location of a push constant block we have previously seen. /// Invalid means no push constant blocks defined thus far. SourceLocation seenPushConstantAt; diff --git a/tools/clang/lib/SPIRV/SpirvInstruction.cpp b/tools/clang/lib/SPIRV/SpirvInstruction.cpp index ea985e5da4..153a2f9c66 100644 --- a/tools/clang/lib/SPIRV/SpirvInstruction.cpp +++ b/tools/clang/lib/SPIRV/SpirvInstruction.cpp @@ -968,11 +968,13 @@ SpirvImageTexelPointer::SpirvImageTexelPointer(QualType resultType, image(imageInst), coordinate(coordinateInst), sample(sampleInst) {} SpirvUntypedImageTexelPointerEXT::SpirvUntypedImageTexelPointerEXT( - QualType resultType, SourceLocation loc, SpirvInstruction *imageInst, - SpirvInstruction *coordinateInst, SpirvInstruction *sampleInst) + QualType resultType, SourceLocation loc, const SpirvType *spvImageType, + SpirvInstruction *imageInst, SpirvInstruction *coordinateInst, + SpirvInstruction *sampleInst) : SpirvInstruction(IK_UntypedImageTexelPointerEXT, spv::Op::OpUntypedImageTexelPointerEXT, resultType, loc), - image(imageInst), coordinate(coordinateInst), sample(sampleInst) {} + imageType(spvImageType), image(imageInst), coordinate(coordinateInst), + sample(sampleInst) {} SpirvLoad::SpirvLoad(QualType resultType, SourceLocation loc, SpirvInstruction *pointerInst, SourceRange range, diff --git a/tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl b/tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl deleted file mode 100644 index 1641d1f038..0000000000 --- a/tools/clang/test/CodeGenSPIRV/resource-heap-ext-texture.hlsl +++ /dev/null @@ -1,59 +0,0 @@ -// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -spirv %s | FileCheck %s - -// CHECK: OpCapability DescriptorHeapEXT -// CHECK: OpExtension "SPV_EXT_descriptor_heap" - -// CHECK-DAG: OpDecorate %[[ResourceHeap:[a-zA-Z0-9_]+]] BuiltIn ResourceHeapEXT -// CHECK-DAG: OpDecorate %[[SamplerHeap:[a-zA-Z0-9_]+]] BuiltIn SamplerHeapEXT - -// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant -// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown -// CHECK-DAG: %[[RWTex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 2 Rgba32f -// CHECK-DAG: %[[BufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 1 Rgba32f -// CHECK-DAG: %[[RWBufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 2 Rgba32f -// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler - -// CHECK-DAG: %[[RA_Tex2DType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]] -// CHECK-DAG: %[[RA_RWTex2DType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTex2DType]] -// CHECK-DAG: %[[RA_BufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferType]] -// CHECK-DAG: %[[RA_RWBufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufferType]] -// CHECK-DAG: %[[RA_SamplerType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]] - -// CHECK: %[[ResourceHeap]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant -// CHECK: %[[SamplerHeap]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant - -[numthreads(1, 1, 1)] -void main(uint3 tid : SV_DispatchThreadID) { - Texture2D myTex = ResourceDescriptorHeap[0]; - // CHECK: %[[TexIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_Tex2DType]] %[[ResourceHeap]] %uint_0 - // CHECK: %[[TexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexIndex]] - - RWTexture2D myRWTex = ResourceDescriptorHeap[1]; - // CHECK: %[[RWTexIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWTex2DType]] %[[ResourceHeap]] %uint_1 - // CHECK: %[[RWTexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWTex2DType]] %[[RWTexIndex]] - - Buffer myBuf = ResourceDescriptorHeap[2]; - // CHECK: %[[BufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_BufferType]] %[[ResourceHeap]] %uint_2 - // CHECK: %[[BufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[BufferType]] %[[BufIndex]] - - RWBuffer myRWBuf = ResourceDescriptorHeap[3]; - // CHECK: %[[RWBufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWBufferType]] %[[ResourceHeap]] %uint_3 - // CHECK: %[[RWBufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufferType]] %[[RWBufIndex]] - - SamplerState mySamp = SamplerDescriptorHeap[0]; - // CHECK: %[[SampIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_SamplerType]] %[[SamplerHeap]] %uint_0 - // CHECK: %[[SampHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampIndex]] - - // CHECK: %[[SampledImage:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[TexHandle]] %[[SampHandle]] - // CHECK: %[[TexResult:[a-zA-Z0-9_]+]] = OpImageSampleExplicitLod %v4float %[[SampledImage]] - float4 texVal = myTex.SampleLevel(mySamp, float2(0, 0), 0); - - // CHECK: %[[BufResult:[a-zA-Z0-9_]+]] = OpImageFetch %v4float %[[BufHandle]] - float4 bufVal = myBuf.Load(tid.x); - - // CHECK: OpImageWrite %[[RWTexHandle]] - myRWTex[tid.xy] = texVal; - - // CHECK: OpImageWrite %[[RWBufHandle]] - myRWBuf[tid.x] = bufVal; -} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl new file mode 100644 index 0000000000..89bf18acaf --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.append-consume.error.hlsl @@ -0,0 +1,19 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DTEST_APPEND %s 2>&1 | FileCheck --check-prefix=APPEND %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck --check-prefix=CONSUME %s + +// Verifies: Append/Consume structured buffers via ResourceDescriptorHeap +// emit a hard error under SPV_EXT_descriptor_heap. + +// APPEND: append/consume structured buffers are not supported with SPV_EXT_descriptor_heap +// CONSUME: append/consume structured buffers are not supported with SPV_EXT_descriptor_heap + +[numthreads(1, 1, 1)] +void main() { +#ifdef TEST_APPEND + AppendStructuredBuffer output = ResourceDescriptorHeap[0]; + output.Append(1); +#else + ConsumeStructuredBuffer input = ResourceDescriptorHeap[0]; + uint val = input.Consume(); +#endif +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl new file mode 100644 index 0000000000..eb0b6b25ca --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer.hlsl @@ -0,0 +1,57 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: StructuredBuffer / RWStructuredBuffer / ByteAddressBuffer +// share one StorageBuffer runtime array, yet each materializes its own +// typed OpBufferPointerEXT, ConstantBuffer uses a separate Uniform array, +// and reassignment re-indexes the shared array. +// +// StructuredBuffer -> shared OpTypeBufferEXT StorageBuffer array -> own typed OpBufferPointerEXT (%type_StructuredBuffer_*) +// RWStructuredBuffer -> shared OpTypeBufferEXT StorageBuffer array -> own typed OpBufferPointerEXT (%type_RWStructuredBuffer_*) +// ByteAddressBuffer -> shared OpTypeBufferEXT StorageBuffer array -> own typed OpBufferPointerEXT (%type_ByteAddressBuffer) +// ConstantBuffer -> separate OpTypeBufferEXT Uniform array -> own typed OpBufferPointerEXT (%type_ConstantBuffer_*) +// reassignment -> re-indexes shared StorageBuffer array (uint_4) -> same %type_StructuredBuffer_* pointer + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] +// CHECK-DAG: %[[UBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[UBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[UBufDesc]] +// Anchor each pointer type to its struct-type prefix to prevent FileCheck from +// binding all three StorageBuffer captures to the scalar %_ptr_StorageBuffer_uint. +// CHECK-DAG: %[[SBInputPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_StructuredBuffer_{{.*}} +// CHECK-DAG: %[[SBOutputPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_RWStructuredBuffer_{{.*}} +// CHECK-DAG: %[[SBBytesPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_ByteAddressBuffer{{$}} +// CHECK-DAG: %[[UConstPtr:[a-zA-Z0-9_]+]] = OpTypePointer Uniform %type_ConstantBuffer_{{.*}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + +struct Constants { + uint value; +}; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + StructuredBuffer input = ResourceDescriptorHeap[0]; + RWStructuredBuffer output = ResourceDescriptorHeap[1]; + ByteAddressBuffer inputBytes = ResourceDescriptorHeap[2]; + ConstantBuffer constants = ResourceDescriptorHeap[3]; + + // CHECK: %[[InputDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT %[[SBInputPtr]] %[[InputDesc]] + // CHECK: %[[OutputDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_1 + // CHECK: OpBufferPointerEXT %[[SBOutputPtr]] %[[OutputDesc]] + // CHECK: %[[InputBytesDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_2 + // CHECK: OpBufferPointerEXT %[[SBBytesPtr]] %[[InputBytesDesc]] + // CHECK: %[[ConstantsDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[UBufArray]] %[[ResourceHeap]] %uint_3 + // CHECK: OpBufferPointerEXT %[[UConstPtr]] %[[ConstantsDesc]] + output[tid.x] = input.Load(tid.x) + inputBytes.Load(tid.x * 4) + constants.value; + outputBytes.Store(tid.x * 4, output[tid.x]); + + // Reassignment: verify new descriptor (index 4) is used after reassign. + input = ResourceDescriptorHeap[4]; + // CHECK: %[[ReassignedDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[SBBufArray]] %[[ResourceHeap]] %uint_4 + // CHECK: OpBufferPointerEXT %[[SBInputPtr]] %[[ReassignedDesc]] + outputBytes.Store(tid.x * 4 + 4, input.Load(tid.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl new file mode 100644 index 0000000000..3db71dcc13 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-buffer-assignment.hlsl @@ -0,0 +1,78 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: ConstantBuffer reassignment from the descriptor heap re-indexes +// the Uniform array on every assignment and each member load uses the latest +// descriptor, in three forms that buffer.hlsl does not cover: +// +// dynamic runtime index -> %[[Idx]] (OpLoad %uint) + OpAccessChain %int_0 +// OpIAdd-computed index -> %[[IdxPlus2]] (OpIAdd) + OpAccessChain %int_1 +// reassignment inside a branch -> %[[IdxMinus2]] (OpUGreaterThan/OpBranchConditional/OpISub) + OpAccessChain %int_2 + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[BufferDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[BufferArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferDesc]] +// CHECK-DAG: %[[CBPtr:[a-zA-Z0-9_]+]] = OpTypePointer Uniform %type_ConstantBuffer_Constants + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +struct Constants +{ + uint a; + uint b; + uint c; +}; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) +{ + uint idx = tid.x; + uint cond = tid.y; + + ConstantBuffer constants = ResourceDescriptorHeap[idx]; + + // CHECK: %[[Idx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[Cond:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[InitDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[Idx]] + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[InitDesc]] + // CHECK: %[[ADesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[Idx]] + // CHECK: %[[APtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[ADesc]] + // CHECK: %[[AAccess:[a-zA-Z0-9_]+]] = OpAccessChain %{{[a-zA-Z0-9_]+}} %[[APtr]] %int_0 + // CHECK: %[[A:[a-zA-Z0-9_]+]] = OpLoad %uint %[[AAccess]] + uint value = constants.a; + + constants = ResourceDescriptorHeap[idx + 2]; + + // CHECK: %[[IdxPlus2:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[Idx]] %uint_2 + // CHECK: %[[AssignDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxPlus2]] + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[AssignDesc]] + // CHECK: %[[BDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxPlus2]] + // CHECK: %[[BPtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[BDesc]] + // CHECK: %[[BUseDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxPlus2]] + // CHECK: %[[BUsePtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[BUseDesc]] + // CHECK: %[[BAccess:[a-zA-Z0-9_]+]] = OpAccessChain %{{[a-zA-Z0-9_]+}} %[[BUsePtr]] %int_1 + // CHECK: %[[B:[a-zA-Z0-9_]+]] = OpLoad %uint %[[BAccess]] + // CHECK: %[[AB:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[A]] %[[B]] + value += constants.b; + + if (cond > 4) { + constants = ResourceDescriptorHeap[idx - 2]; + + // CHECK: %[[CondCmp:[a-zA-Z0-9_]+]] = OpUGreaterThan %bool %[[Cond]] %uint_4 + // CHECK: OpBranchConditional %[[CondCmp]] + // CHECK: %[[IdxMinus2:[a-zA-Z0-9_]+]] = OpISub %uint %[[Idx]] %uint_2 + // CHECK: %[[BranchDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxMinus2]] + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[BranchDesc]] + // CHECK: %[[CDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxMinus2]] + // CHECK: %[[CPtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CDesc]] + // CHECK: %[[CUseDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[BufferArray]] %[[ResourceHeap]] %[[IdxMinus2]] + // CHECK: %[[CUsePtr:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CUseDesc]] + // CHECK: %[[CAccess:[a-zA-Z0-9_]+]] = OpAccessChain %{{[a-zA-Z0-9_]+}} %[[CUsePtr]] %int_2 + // CHECK: %[[C:[a-zA-Z0-9_]+]] = OpLoad %uint %[[CAccess]] + // CHECK: OpIAdd %uint %[[AB]] %[[C]] + value += constants.c; + } + + outputBytes.Store(0, value); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl new file mode 100644 index 0000000000..1a41f3ea44 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.constant-texture-buffer.hlsl @@ -0,0 +1,51 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: ConstantBuffer and TextureBuffer from the heap map to different element types, +// both carrying Block and supporting member access. +// +// ConstantBuffer -> OpTypeBufferEXT Uniform -> Block +// TextureBuffer -> OpTypeBufferEXT StorageBuffer -> Block + NonWritable members + +// CHECK-DAG: OpDecorate %type_ConstantBuffer_Data Block +// CHECK-DAG: OpDecorate %type_TextureBuffer_Data Block +// CHECK-DAG: OpMemberDecorate %type_TextureBuffer_Data 0 NonWritable +// CHECK-DAG: OpMemberDecorate %type_TextureBuffer_Data 1 NonWritable + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[CBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[CBDescArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CBDesc]] +// CHECK-DAG: %[[CBPtr:[a-zA-Z0-9_]+]] = OpTypePointer Uniform %type_ConstantBuffer_Data +// CHECK-DAG: %[[TBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[TBDescArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TBDesc]] +// CHECK-DAG: %[[TBPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_TextureBuffer_Data + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +struct Data { + uint a; + uint b[2]; +}; + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + ConstantBuffer cb = ResourceDescriptorHeap[0]; + TextureBuffer tb = ResourceDescriptorHeap[1]; + + uint index = tid.x & 1; + + // CHECK: %[[CBDescPtr:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[CBDescArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT %[[CBPtr]] %[[CBDescPtr]] + // CHECK: %[[TBDescPtr:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[TBDescArray]] %[[ResourceHeap]] %uint_1 + // CHECK: OpBufferPointerEXT %[[TBPtr]] %[[TBDescPtr]] + // CHECK: %[[CBDataA:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[CBDataA]] %int_0 + // CHECK: %[[CBDataB:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[CBPtr]] %[[CBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[CBDataB]] %int_1 %{{[a-zA-Z0-9_]+}} + // CHECK: %[[TBDataA:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[TBPtr]] %[[TBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[TBDataA]] %int_0 + // CHECK: %[[TBDataB:[a-zA-Z0-9_]+]] = OpBufferPointerEXT %[[TBPtr]] %[[TBDescPtr]] + // CHECK: OpAccessChain %{{[a-zA-Z0-9_]+}} %[[TBDataB]] %int_1 %int_1 + outputBytes.Store(0, cb.a + cb.b[index] + tb.a + tb.b[1]); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl new file mode 100644 index 0000000000..c0b406428c --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.counter-ops.error.hlsl @@ -0,0 +1,24 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DTEST_INCREMENT %s 2>&1 | FileCheck --check-prefix=INC %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck --check-prefix=DEC %s + +// Verifies: IncrementCounter/DecrementCounter on a +// heap-loaded RWStructuredBuffer is rejected with a hard error under +// SPV_EXT_descriptor_heap. + +// INC: counter operations on heap-loaded RWStructuredBuffer are not supported with SPV_EXT_descriptor_heap +// DEC: counter operations on heap-loaded RWStructuredBuffer are not supported with SPV_EXT_descriptor_heap + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main() { + RWStructuredBuffer buffer = ResourceDescriptorHeap[0]; + +#ifdef TEST_INCREMENT + uint value = buffer.IncrementCounter(); +#else + uint value = buffer.DecrementCounter(); +#endif + + outputBytes.Store(0, value); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl new file mode 100644 index 0000000000..3e26d746e4 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.discarded.error.hlsl @@ -0,0 +1,14 @@ +// RUN: not %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck %s + +// Regression test for an unguarded cast in doCXXOperatorCallExpr: +// cast(parentMap->getParent(expr)) +// A descriptor-heap index whose result is discarded (used as a bare expression +// statement) has no parent cast to a concrete resource type, so the resource +// type cannot be determined. This must emit a diagnostic, not an ICE. + +// CHECK: error: {{.*}}ResourceDescriptorHeap/SamplerDescriptorHeap indexing must be used as a resource + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + ResourceDescriptorHeap[tid.x]; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl new file mode 100644 index 0000000000..3a72fa3482 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.function-params.hlsl @@ -0,0 +1,97 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fcgl -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: a heap-loaded resource passed by value to a user function lowers to +// pass-by-Function-pointer. +// +// heap handle -> OpStore into a Function-class var -> per-call OpLoad/OpStore/OpFunctionCall +// parameter slot -> OpFunctionParameter %[[*PtrType]] -> Function storage class + +// CHECK-DAG: OpName %[[ReadTex:[a-zA-Z0-9_]+]] "ReadTex" +// CHECK-DAG: OpName %[[ReadBuf:[a-zA-Z0-9_]+]] "ReadBuf" +// CHECK-DAG: OpName %[[WriteBuf:[a-zA-Z0-9_]+]] "WriteBuf" + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[TexType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[BufType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 1 Rgba32f +// CHECK-DAG: %[[RWBufType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 2 Rgba32f +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[TexPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[TexType]] +// CHECK-DAG: %[[BufPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[BufType]] +// CHECK-DAG: %[[RWBufPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[RWBufType]] +// CHECK-DAG: %[[SamplerPtrType:[a-zA-Z0-9_]+]] = OpTypePointer Function %[[SamplerType]] +// CHECK-DAG: %[[RA_TexType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexType]] +// CHECK-DAG: %[[RA_BufType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufType]] +// CHECK-DAG: %[[RA_RWBufType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufType]] +// CHECK-DAG: %[[RA_SamplerType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]] + +float4 ReadTex(Texture2D tex, SamplerState samp); +float4 ReadBuf(Buffer buf, uint index); +void WriteBuf(RWBuffer buf, uint index, float4 value); + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2D tex = ResourceDescriptorHeap[0]; + Buffer buf = ResourceDescriptorHeap[1]; + RWBuffer outBuf = ResourceDescriptorHeap[2]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + // CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + + // CHECK: %[[TexDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_TexType]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[TexType]] %[[TexDesc]] + // CHECK: OpStore %[[TexVar:[a-zA-Z0-9_]+]] %[[TexHandle]] + // CHECK: %[[BufDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_BufType]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[BufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[BufType]] %[[BufDesc]] + // CHECK: OpStore %[[BufVar:[a-zA-Z0-9_]+]] %[[BufHandle]] + // CHECK: %[[RWBufDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWBufType]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[RWBufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufType]] %[[RWBufDesc]] + // CHECK: OpStore %[[RWBufVar:[a-zA-Z0-9_]+]] %[[RWBufHandle]] + // CHECK: %[[SamplerDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_SamplerType]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SamplerHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SamplerDesc]] + // CHECK: OpStore %[[SamplerVar:[a-zA-Z0-9_]+]] %[[SamplerHandle]] + + // CHECK: %[[TexArg:[a-zA-Z0-9_]+]] = OpLoad %[[TexType]] %[[TexVar]] + // CHECK: OpStore %[[TexParam:[a-zA-Z0-9_]+]] %[[TexArg]] + // CHECK: %[[SamplerArg:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SamplerVar]] + // CHECK: OpStore %[[SamplerParam:[a-zA-Z0-9_]+]] %[[SamplerArg]] + // CHECK: %[[TexValue:[a-zA-Z0-9_]+]] = OpFunctionCall %v4float %[[ReadTex]] %[[TexParam]] %[[SamplerParam]] + float4 value = ReadTex(tex, samp); + + // CHECK: %[[BufArg:[a-zA-Z0-9_]+]] = OpLoad %[[BufType]] %[[BufVar]] + // CHECK: OpStore %[[BufParam:[a-zA-Z0-9_]+]] %[[BufArg]] + // CHECK: %[[BufValue:[a-zA-Z0-9_]+]] = OpFunctionCall %v4float %[[ReadBuf]] %[[BufParam]] + value += ReadBuf(buf, tid.x); + + // CHECK: %[[RWBufArg:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufType]] %[[RWBufVar]] + // CHECK: OpStore %[[RWBufParam:[a-zA-Z0-9_]+]] %[[RWBufArg]] + // CHECK: OpFunctionCall %void %[[WriteBuf]] %[[RWBufParam]] + WriteBuf(outBuf, tid.x, value); + + outputBytes.Store(tid.x * 4, asuint(value.x)); +} + +// Callees emit after the entry point; each receives its resource by Function pointer. +float4 ReadTex(Texture2D tex, SamplerState samp) { + // CHECK: %[[ReadTex]] = OpFunction %v4float + // CHECK: OpFunctionParameter %[[TexPtrType]] + // CHECK: OpFunctionParameter %[[SamplerPtrType]] + // CHECK: OpSampledImage + return tex.SampleLevel(samp, float2(0.0, 0.0), 0.0); +} + +float4 ReadBuf(Buffer buf, uint index) { + // CHECK: %[[ReadBuf]] = OpFunction %v4float + // CHECK: OpFunctionParameter %[[BufPtrType]] + // CHECK: OpImageFetch + return buf.Load(index); +} + +void WriteBuf(RWBuffer buf, uint index, float4 value) { + // CHECK: %[[WriteBuf]] = OpFunction %void + // CHECK: OpFunctionParameter %[[RWBufPtrType]] + // CHECK: OpImageWrite + buf[index] = value; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl new file mode 100644 index 0000000000..9b1a720201 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.gather.hlsl @@ -0,0 +1,42 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: GatherRed/Green/Blue/Alpha each emit a fresh OpSampledImage that +// reuses the once-loaded heap-sourced texture and sampler handles, selecting +// channel %int_0/1/2/3 via OpImageGather %v4float. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[SampledImgType:[a-zA-Z0-9_]+]] = OpTypeSampledImage %[[Tex2DType]] +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +// CHECK-DAG: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK-DAG: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // Tex and sampler are each loaded once from the heap; each Gather* creates a + // fresh OpSampledImage but reuses the same loaded image and sampler handles. + // CHECK: %[[TexChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexH:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexChain]] + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampH:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + + // CHECK: %[[SI0:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI0]] {{.*}} %int_0 + float4 r = tex.GatherRed(samp, uv); + // CHECK: %[[SI1:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI1]] {{.*}} %int_1 + float4 g = tex.GatherGreen(samp, uv); + // CHECK: %[[SI2:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI2]] {{.*}} %int_2 + float4 b = tex.GatherBlue(samp, uv); + // CHECK: %[[SI3:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageGather %v4float %[[SI3]] {{.*}} %int_3 + float4 a = tex.GatherAlpha(samp, uv); + + return r + g + b + a; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl new file mode 100644 index 0000000000..f68e7b6b59 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.groupshared.hlsl @@ -0,0 +1,35 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: heap StructuredBuffer/RWStructuredBuffer coexist with groupshared +// (Workgroup storage class) and GroupMemoryBarrierWithGroupSync. +// 1) groupshared backed by OpTypePointer Workgroup, distinct from heap descriptor pointers. +// 2) GroupMemoryBarrierWithGroupSync lowers to OpControlBarrier alongside heap accesses. +// 3) heap SB/RWSB accessed via OpUntypedAccessChainKHR + OpBufferPointerEXT in same entry point. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] + +// groupshared lives in Workgroup storage class. +// CHECK-DAG: %[[WorkgroupPtr:[a-zA-Z0-9_]+]] = OpTypePointer Workgroup +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +groupshared float4 shared_data[64]; + +[numthreads(64, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID, uint gi : SV_GroupIndex) { + StructuredBuffer input = ResourceDescriptorHeap[0]; + RWStructuredBuffer output = ResourceDescriptorHeap[1]; + + // CHECK-DAG: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK-DAG: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_1 + shared_data[gi] = input[tid.x]; + + // CHECK: OpControlBarrier + GroupMemoryBarrierWithGroupSync(); + + uint neighbor = (gi + 1) % 64; + + // CHECK: OpBufferPointerEXT + output[tid.x] = shared_data[gi] + shared_data[neighbor]; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl new file mode 100644 index 0000000000..a2390ee75e --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.load-offset.hlsl @@ -0,0 +1,27 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: Texture.Load with a constant offset emits the ConstOffset +// image operand on OpImageFetch, contrasted against the no-offset fetch baseline. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} + +float4 main(float4 pos : SV_Position) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[0]; + + // CHECK: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap:[a-zA-Z0-9_]+]] %uint_0 + // CHECK: %[[Handle:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] + + int3 coord = int3(pos.xy, 0); + + // Load without offset. + // CHECK: OpImageFetch %v4float %[[Handle]] + float4 a = tex.Load(coord); + + // Load with constant offset — the ConstOffset image operand must appear. + // CHECK: OpImageFetch %v4float %[[Handle]] {{.*}}ConstOffset + float4 b = tex.Load(coord, int2(1, -1)); + + return a + b; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl new file mode 100644 index 0000000000..25089d9e43 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-bound.hlsl @@ -0,0 +1,54 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: explicitly-bound resources coexist with descriptor-heap resources, and a bound image combines with a heap sampler. +// register(t0) Texture2D -> OpVariable %[[PtrTex]] UniformConstant + DescriptorSet/Binding -> explicitly-bound resource +// ResourceHeap/SamplerHeap -> OpUntypedVariableKHR UniformConstant (BuiltIn ResourceHeapEXT/SamplerHeapEXT) -> heap resources +// bound image + heap sampler -> OpSampledImage %[[BoundVal]] %[[SampH]] -> bound-image/heap-sampler combination + +// CHECK: OpCapability DescriptorHeapEXT +// CHECK-NOT: OpCapability UntypedPointersKHR +// CHECK: OpExtension "SPV_EXT_descriptor_heap" +// CHECK: OpExtension "SPV_KHR_untyped_pointers" + +// CHECK-DAG: OpDecorate %[[ResourceHeap:[a-zA-Z0-9_]+]] BuiltIn ResourceHeapEXT +// CHECK-DAG: OpDecorate %[[SamplerHeap:[a-zA-Z0-9_]+]] BuiltIn SamplerHeapEXT + +// CHECK-DAG: OpDecorate %[[BoundTex:[a-zA-Z0-9_]+]] DescriptorSet +// CHECK-DAG: OpDecorate %[[BoundTex]] Binding + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} +// CHECK-DAG: %[[PtrTex:[a-zA-Z0-9_]+]] = OpTypePointer UniformConstant %[[Tex2DType]] + +// CHECK: %[[BoundTex]] = OpVariable %[[PtrTex]] UniformConstant +// CHECK-DAG: %[[ResourceHeap]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK-DAG: %[[SamplerHeap]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[[vk::binding(0, 0)]] +Texture2D boundTex : register(t0); + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D heapTex = ResourceDescriptorHeap[1]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // CHECK: %[[HeapDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[HeapVal:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[HeapDesc]] + + // One heap sampler drives both samples, proving heap-sampler reuse across bound and heap images. + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampH:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + + // CHECK: %[[BoundVal:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[BoundTex]] + + // OWNED: bound image (OpVariable) combined with heap sampler. + // CHECK: OpSampledImage %{{.*}} %[[BoundVal]] %[[SampH]] + float4 a = boundTex.Sample(samp, uv); + + // CHECK: OpSampledImage %{{.*}} %[[HeapVal]] %[[SampH]] + float4 b = heapTex.Sample(samp, uv + 0.5); + + return a + b; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl new file mode 100644 index 0000000000..591fac84ff --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.nonuniform.hlsl @@ -0,0 +1,30 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: NonUniformResourceIndex on descriptor-heap accesses does NOT +// propagate NonUniform to the access-chain result or the loaded value. +// SPV_EXT_descriptor_heap deprecates the NonUniform decoration; drivers handle +// divergent indices natively and the decoration must be omitted. + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[RA_Tex2DType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]] +// CHECK-DAG: %[[RA_SamplerType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]] + +// CHECK-NOT: OpDecorate %{{.*}} NonUniform + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + +float4 main(uint idx : A) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[NonUniformResourceIndex(idx)]; + SamplerState samp = SamplerDescriptorHeap[NonUniformResourceIndex(idx + 1)]; + + // CHECK: %[[TexChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_Tex2DType]] %[[ResourceHeap]] %{{.*}} + // CHECK: %[[TexLoaded:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexChain]] + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_SamplerType]] %[[SamplerHeap]] %{{.*}} + // CHECK: %[[SampLoaded:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + // CHECK: %[[Combined:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[TexLoaded]] %[[SampLoaded]] + // CHECK: OpImageSampleExplicitLod %v4float %[[Combined]] + return tex.SampleLevel(samp, float2(0.0, 0.0), 0.0); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl new file mode 100644 index 0000000000..3e84ff294d --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwbyteaddressbuffer.hlsl @@ -0,0 +1,21 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: an RWByteAddressBuffer sourced from the descriptor heap +// produces the typed StorageBuffer pointer %type_RWByteAddressBuffer +// and a read-WRITE OpBufferPointerEXT through it. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] +// CHECK-DAG: %[[RWBABPtr:[a-zA-Z0-9_]+]] = OpTypePointer StorageBuffer %type_RWByteAddressBuffer +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[numthreads(64, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWByteAddressBuffer buf = ResourceDescriptorHeap[0]; + + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT %[[RWBABPtr]] %[[Desc]] + uint val = buf.Load(tid.x * 4); + buf.Store(tid.x * 4 + 256, val + 1); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl new file mode 100644 index 0000000000..6937f828ef --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-atomics.hlsl @@ -0,0 +1,53 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: InterlockedAdd on a heap-sourced RWTexture2D lowers +// to OpUntypedImageTexelPointerEXT (not OpImageTexelPointer) +// feeding OpAtomicIAdd, and reassignment targets the new descriptor. +// +// RWTexture2D heap access -> OpUntypedAccessChainKHR -> descriptor pointer in resource heap +// atomic texel pointer -> OpUntypedImageTexelPointerEXT -> EXT untyped image texel pointer +// InterlockedAdd -> OpAtomicIAdd -> image atomic on untyped texel pointer +// reassignment -> OpUntypedAccessChainKHR %uint_3 -> atomic uses new descriptor index + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[RWTexType:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 2 R32ui +// CHECK-DAG: %[[RWTexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexType]] +// CHECK-DAG: %[[UntypedImage:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR Image + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWTexture2D tex = ResourceDescriptorHeap[0]; + + uint original; + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_0 + // CHECK-NOT: OpImageTexelPointer + // CHECK: %[[TexelPtr:[a-zA-Z0-9_]+]] = OpUntypedImageTexelPointerEXT %[[UntypedImage]] %[[RWTexType]] %[[Desc]] + // CHECK: OpAtomicIAdd %uint %[[TexelPtr]] + InterlockedAdd(tex[tid.xy], 1, original); + + uint directOriginal; + // CHECK: %[[DirectDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_1 + // CHECK-NOT: OpImageTexelPointer + // CHECK: %[[DirectTexelPtr:[a-zA-Z0-9_]+]] = OpUntypedImageTexelPointerEXT %[[UntypedImage]] %[[RWTexType]] %[[DirectDesc]] + // CHECK: OpAtomicIAdd %uint %[[DirectTexelPtr]] + InterlockedAdd(((RWTexture2D)ResourceDescriptorHeap[1])[tid.xy], 2, + directOriginal); + + // Reassignment: atomic must use the NEW descriptor (index 3, not 2). + RWTexture2D reassigned = ResourceDescriptorHeap[2]; + reassigned = ResourceDescriptorHeap[3]; + uint reassignedOriginal; + // CHECK: %[[ReassignDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_3 + // CHECK-NOT: OpImageTexelPointer + // CHECK: %[[ReassignTexelPtr:[a-zA-Z0-9_]+]] = OpUntypedImageTexelPointerEXT %[[UntypedImage]] %[[RWTexType]] %[[ReassignDesc]] + // CHECK: OpAtomicIAdd %uint %[[ReassignTexelPtr]] + InterlockedAdd(reassigned[tid.xy], 3, reassignedOriginal); + + outputBytes.Store(0, original); + outputBytes.Store(4, directOriginal); + outputBytes.Store(8, reassignedOriginal); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl new file mode 100644 index 0000000000..87d2591318 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl @@ -0,0 +1,53 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: storage RW texture dimensionality lowers to distinct +// OpTypeImage dim/flags, each driving OpImageRead/OpImageWrite. +// +// RWTexture1D -> OpTypeImage %float 1D 2 0 0 2 Rgba32f -> storage +// RWTexture1DArray -> OpTypeImage %float 1D 2 1 0 2 Rgba32f -> storage +// RWTexture2DArray -> OpTypeImage %float 2D 2 1 0 2 Rgba32f -> storage +// RWTexture3D -> OpTypeImage %float 3D 2 0 0 2 Rgba32f -> storage + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[RW1DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 0 0 2 Rgba32f +// CHECK-DAG: %[[RW1DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 1 0 2 Rgba32f +// CHECK-DAG: %[[RW2DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 1 0 2 Rgba32f +// CHECK-DAG: %[[RW3DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 3D 2 0 0 2 Rgba32f + +// CHECK-DAG: %[[RA_RW1D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW1DType]]{{$}} +// CHECK-DAG: %[[RA_RW1DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW1DArrType]]{{$}} +// CHECK-DAG: %[[RA_RW2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW2DArrType]]{{$}} +// CHECK-DAG: %[[RA_RW3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW3DType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWTexture1D rwTex1d = ResourceDescriptorHeap[0]; + // CHECK: %[[RW1D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW1D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[RW1D:[a-zA-Z0-9_]+]] = OpLoad %[[RW1DType]] %[[RW1D_Desc]] + + RWTexture1DArray rwTex1dArr = ResourceDescriptorHeap[1]; + // CHECK: %[[RW1DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW1DArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[RW1DA:[a-zA-Z0-9_]+]] = OpLoad %[[RW1DArrType]] %[[RW1DA_Desc]] + + RWTexture2DArray rwTex2dArr = ResourceDescriptorHeap[2]; + // CHECK: %[[RW2DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW2DArr]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[RW2DA:[a-zA-Z0-9_]+]] = OpLoad %[[RW2DArrType]] %[[RW2DA_Desc]] + + RWTexture3D rwTex3d = ResourceDescriptorHeap[3]; + // CHECK: %[[RW3D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RW3D]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[RW3D:[a-zA-Z0-9_]+]] = OpLoad %[[RW3DType]] %[[RW3D_Desc]] + + // CHECK: OpImageRead %v4float %[[RW1D]] + float4 v = rwTex1d[tid.x]; + + // CHECK: OpImageWrite %[[RW1DA]] + rwTex1dArr[uint2(tid.x, 0)] = v; + + // CHECK: OpImageWrite %[[RW2DA]] + rwTex2dArr[uint3(tid.xy, 0)] = v; + + // CHECK: OpImageWrite %[[RW3D]] + rwTex3d[tid.xyz] = v; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl new file mode 100644 index 0000000000..756dd53e2d --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sample-grad-bias.hlsl @@ -0,0 +1,40 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: SampleGrad lowers to OpImageSampleExplicitLod with the Grad image +// operand and SampleBias lowers to OpImageSampleImplicitLod with the Bias +// image operand, where the texture and sampler handles are loaded once from +// the descriptor heaps and reused across both samples (each sample a fresh +// OpSampledImage). + +// Capture anchors only (heap-source decorations owned by mixed-bound). +// CHECK-DAG: OpDecorate %[[ResourceHeap:[a-zA-Z0-9_]+]] BuiltIn ResourceHeapEXT +// CHECK-DAG: OpDecorate %[[SamplerHeap:[a-zA-Z0-9_]+]] BuiltIn SamplerHeapEXT + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[SampledImgType:[a-zA-Z0-9_]+]] = OpTypeSampledImage %[[Tex2DType]] +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D tex = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + + // Tex and sampler each loaded once; each sample op creates its own OpSampledImage + // but both reuse the same loaded handles. + // CHECK: %[[TexChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexH:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DType]] %[[TexChain]] + // CHECK: %[[SampChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampH:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampChain]] + + // CHECK: %[[SI0:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageSampleExplicitLod %v4float %[[SI0]] {{.*}} Grad + float4 a = tex.SampleGrad(samp, uv, ddx(uv), ddy(uv)); + + // CHECK: %[[SI1:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImgType]] %[[TexH]] %[[SampH]] + // CHECK: OpImageSampleImplicitLod %v4float %[[SI1]] {{.*}} Bias + float4 b = tex.SampleBias(samp, uv, -1.0); + + return a + b; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl new file mode 100644 index 0000000000..f5a118715e --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.sampler-comparison.hlsl @@ -0,0 +1,30 @@ +// RUN: %dxc -T ps_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: a heap-sourced SamplerComparisonState combined with a heap-sourced +// Texture2D drives OpImageSampleDrefExplicitLod (SampleCmpLevelZero), +// with the comparison sampler loaded from the sampler heap and joined +// via OpSampledImage. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[TexType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[RA_Tex:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +float4 main(float2 uv : TEXCOORD0) : SV_Target { + Texture2D depthTex = ResourceDescriptorHeap[0]; + // CHECK: %[[TexDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexHandle:[a-zA-Z0-9_]+]] = OpLoad %[[TexType]] %[[TexDesc]] + + SamplerComparisonState shadowSamp = SamplerDescriptorHeap[0]; + // CHECK: %[[SampDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampDesc]] + + // CHECK: %[[Combined:[a-zA-Z0-9_]+]] = OpSampledImage %{{[a-zA-Z0-9_]+}} %[[TexHandle]] %[[SampHandle]] + // CHECK: %[[Shadow:[a-zA-Z0-9_]+]] = OpImageSampleDrefExplicitLod %float %[[Combined]] + float shadow = depthTex.SampleCmpLevelZero(shadowSamp, uv, 0.5); + return float4(shadow, shadow, shadow, 1.0); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl new file mode 100644 index 0000000000..e12cf41076 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.static-global.hlsl @@ -0,0 +1,64 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: UE bindless idiom — static const resources at global scope +// initialized from dynamic runtime uint indices ($Globals), across +// Texture2D / StructuredBuffer / RWTexture2D, plus a literal-index +// SamplerState. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[RWTex2DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 2 Rgba32f + +// CHECK-DAG: %[[RA_Tex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} +// CHECK-DAG: %[[RA_SBBuf:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]]{{$}} +// CHECK-DAG: %[[RA_RWTex2D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTex2DType]]{{$}} + +// CHECK-DAG: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK-DAG: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +// Runtime uint indices (placed in $Globals cbuffer by DXC). +uint BindlessSRV_ColorTex; +uint BindlessSRV_DataBuf; +uint BindlessUAV_OutTex; + +// UE bindless pattern: static const from runtime heap index. +static const Texture2D ColorTex = ResourceDescriptorHeap[BindlessSRV_ColorTex]; +static const StructuredBuffer DataBuf = ResourceDescriptorHeap[BindlessSRV_DataBuf]; +static const RWTexture2D OutTex = ResourceDescriptorHeap[BindlessUAV_OutTex]; + +// Literal-index sampler (common UE pattern for global bilinear/point samplers). +static const SamplerState BilinearSamp = SamplerDescriptorHeap[2]; + +[numthreads(8, 8, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + // Heap indices are dynamic (loaded from $Globals), not literal constants. + // CHECK: %[[TexIdx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[TexDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2D]] %[[ResourceHeap]] %[[TexIdx]] + // CHECK: OpLoad %[[Tex2DType]] %[[TexDesc]] + + // CHECK: %[[BufIdx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_SBBuf]] %[[ResourceHeap]] %[[BufIdx]] + // CHECK: OpBufferPointerEXT + + // CHECK: %[[OutIdx:[a-zA-Z0-9_]+]] = OpLoad %uint + // CHECK: %[[OutDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTex2D]] %[[ResourceHeap]] %[[OutIdx]] + // CHECK: OpLoad %[[RWTex2DType]] %[[OutDesc]] + + // Literal sampler index. + // CHECK: OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_2 + // CHECK: OpLoad %[[SamplerType]] + + float2 uv = float2(tid.xy) / 512.0; + + // CHECK: OpSampledImage + // CHECK: OpImageSampleExplicitLod + float4 color = ColorTex.SampleLevel(BilinearSamp, uv, 0); + + float4 data = DataBuf[tid.x]; + + // CHECK: OpImageWrite %{{[a-zA-Z0-9_]+}} + OutTex[tid.xy] = color + data; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl new file mode 100644 index 0000000000..bbf811e6ca --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.structured-buffer-atomic.hlsl @@ -0,0 +1,28 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: BUFFER atomics on a heap-sourced RWStructuredBuffer +// lower through a heap access-chain + OpBufferPointerEXT into +// OpAtomicIAdd/OpAtomicCompareExchange on %uint. +// +// RWStructuredBuffer -> heap access-chain + OpBufferPointerEXT -> OpAtomicIAdd %uint +// RWStructuredBuffer -> heap access-chain + OpBufferPointerEXT -> OpAtomicCompareExchange %uint + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +[numthreads(64, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWStructuredBuffer counter = ResourceDescriptorHeap[0]; + + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SBBufArray]] %[[ResourceHeap]] %uint_0 + // CHECK: OpBufferPointerEXT + uint original; + // CHECK: OpAtomicIAdd %uint + InterlockedAdd(counter[0], 1, original); + + // CHECK: OpAtomicCompareExchange %uint + uint cmp; + InterlockedCompareExchange(counter[1], original, original + 1, cmp); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl new file mode 100644 index 0000000000..120830aa4c --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl @@ -0,0 +1,54 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: sampled (non-MS, non-cube) texture dimensionalities lower +// to the correct OpTypeImage dim flags and feed OpImageFetch. +// +// Texture1D -> OpTypeImage %float 1D 2 0 0 1 Unknown -> sampled +// Texture1DArray -> OpTypeImage %float 1D 2 1 0 1 Unknown -> sampled +// Texture2DArray -> OpTypeImage %float 2D 2 1 0 1 Unknown -> sampled +// Texture3D -> OpTypeImage %float 3D 2 0 0 1 Unknown -> sampled + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex1DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 0 0 1 Unknown +// CHECK-DAG: %[[Tex1DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 1D 2 1 0 1 Unknown +// CHECK-DAG: %[[Tex2DArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 1 0 1 Unknown +// CHECK-DAG: %[[Tex3DType:[a-zA-Z0-9_]+]] = OpTypeImage %float 3D 2 0 0 1 Unknown + +// CHECK-DAG: %[[RA_Tex1D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex1DType]]{{$}} +// CHECK-DAG: %[[RA_Tex1DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex1DArrType]]{{$}} +// CHECK-DAG: %[[RA_Tex2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DArrType]]{{$}} +// CHECK-DAG: %[[RA_Tex3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex3DType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture1D tex1d = ResourceDescriptorHeap[0]; + // CHECK: %[[T1D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex1D]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[T1D:[a-zA-Z0-9_]+]] = OpLoad %[[Tex1DType]] %[[T1D_Desc]] + + Texture1DArray tex1dArr = ResourceDescriptorHeap[1]; + // CHECK: %[[T1DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex1DArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[T1DA:[a-zA-Z0-9_]+]] = OpLoad %[[Tex1DArrType]] %[[T1DA_Desc]] + + Texture2DArray tex2dArr = ResourceDescriptorHeap[2]; + // CHECK: %[[T2DA_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex2DArr]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[T2DA:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2DArrType]] %[[T2DA_Desc]] + + Texture3D tex3d = ResourceDescriptorHeap[3]; + // CHECK: %[[T3D_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Tex3D]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[T3D:[a-zA-Z0-9_]+]] = OpLoad %[[Tex3DType]] %[[T3D_Desc]] + + // CHECK: OpImageFetch %v4float %[[T1D]] + float4 v = tex1d.Load(int2(tid.x, 0)); + // CHECK: OpImageFetch %v4float %[[T1DA]] + v += tex1dArr.Load(int3(tid.x, 0, 0)); + // CHECK: OpImageFetch %v4float %[[T2DA]] + v += tex2dArr.Load(int4(tid.xy, 0, 0)); + // CHECK: OpImageFetch %v4float %[[T3D]] + v += tex3d.Load(int4(tid.xyz, 0)); + + output.Store(0, asuint(v.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl new file mode 100644 index 0000000000..edb2d78821 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl @@ -0,0 +1,33 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: multisampled image types (Texture2DMS `%float 2D 2 0 1 1` and Texture2DMSArray `2D 2 1 1 1`) +// sourced from the descriptor heap are loaded and drive OpImageFetch %v4float. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[MSType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 1 1 Unknown +// CHECK-DAG: %[[MSArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 1 1 1 Unknown + +// CHECK-DAG: %[[RA_MS:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSType]]{{$}} +// CHECK-DAG: %[[RA_MSArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSArrType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2DMS texMS = ResourceDescriptorHeap[0]; + // CHECK: %[[MS_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_MS]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[MS:[a-zA-Z0-9_]+]] = OpLoad %[[MSType]] %[[MS_Desc]] + + Texture2DMSArray texMSArr = ResourceDescriptorHeap[1]; + // CHECK: %[[MSArr_Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_MSArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[MSArr:[a-zA-Z0-9_]+]] = OpLoad %[[MSArrType]] %[[MSArr_Desc]] + + // CHECK: OpImageFetch %v4float %[[MS]] + float4 v = texMS.Load(int2(tid.xy), 0); + // CHECK: OpImageFetch %v4float %[[MSArr]] + v += texMSArr.Load(int3(tid.xy, 0), 0); + + output.Store(0, asuint(v.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl new file mode 100644 index 0000000000..6b4297741e --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-sampler-assignment.hlsl @@ -0,0 +1,77 @@ +// RUN: %dxc -T ps_6_6 -E PSMain -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: reassigning both a Texture2D and a SamplerState across a +// dynamic index, a +2 index, and inside a conditional branch reloads +// the latest texture+sampler from BOTH the resource and sampler +// heaps per reassignment, producing a fresh +// OpSampledImage/OpImageSampleImplicitLod each time. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Tex2D:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[Sampler:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[TexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2D]] +// CHECK-DAG: %[[SamplerArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Sampler]] +// CHECK-DAG: %[[SampledImage:[a-zA-Z0-9_]+]] = OpTypeSampledImage %[[Tex2D]] + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +struct PSInput +{ + float4 position : SV_Position; + float4 color : COLOR0; +}; + +float4 PSMain(PSInput input) : SV_Target0 +{ + uint texIdx = uint(input.color.x); + uint sampIdx = uint(input.color.y); + uint cond = uint(input.color.z); + + Texture2D myTexture = ResourceDescriptorHeap[texIdx]; + SamplerState samp = SamplerDescriptorHeap[sampIdx]; + + // CHECK: %[[TexIdx:[a-zA-Z0-9_]+]] = OpConvertFToU %uint + // CHECK: %[[SampIdx:[a-zA-Z0-9_]+]] = OpConvertFToU %uint + // CHECK: %[[Cond:[a-zA-Z0-9_]+]] = OpConvertFToU %uint + // CHECK: %[[TexDesc0:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[TexArray]] %[[ResourceHeap]] %[[TexIdx]] + // CHECK: %[[Tex0:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2D]] %[[TexDesc0]] + // CHECK: %[[SampDesc0:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SamplerArray]] %[[SamplerHeap]] %[[SampIdx]] + // CHECK: %[[Samp0:[a-zA-Z0-9_]+]] = OpLoad %[[Sampler]] %[[SampDesc0]] + // CHECK: %[[Sampled0:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImage]] %[[Tex0]] %[[Samp0]] + // CHECK: %[[Color0:[a-zA-Z0-9_]+]] = OpImageSampleImplicitLod %v4float %[[Sampled0]] + float4 color = myTexture.Sample(samp, float2(1,1)); + + myTexture = ResourceDescriptorHeap[texIdx + 2]; + samp = SamplerDescriptorHeap[sampIdx]; + + // CHECK: %[[TexIdxPlus2:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[TexIdx]] %uint_2 + // CHECK: %[[TexDesc1:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[TexArray]] %[[ResourceHeap]] %[[TexIdxPlus2]] + // CHECK: %[[Tex1:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2D]] %[[TexDesc1]] + // CHECK: %[[SampDesc1:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SamplerArray]] %[[SamplerHeap]] %[[SampIdx]] + // CHECK: %[[Samp1:[a-zA-Z0-9_]+]] = OpLoad %[[Sampler]] %[[SampDesc1]] + // CHECK: %[[Sampled1:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImage]] %[[Tex1]] %[[Samp1]] + // CHECK: %[[Color1:[a-zA-Z0-9_]+]] = OpImageSampleImplicitLod %v4float %[[Sampled1]] + // CHECK: %[[ColorSum:[a-zA-Z0-9_]+]] = OpFAdd %v4float %[[Color0]] %[[Color1]] + color += myTexture.Sample(samp, float2(2,2)); + + if (cond > 4) { + myTexture = ResourceDescriptorHeap[texIdx - 2]; + samp = SamplerDescriptorHeap[sampIdx + 2]; + + // CHECK: %[[CondCmp:[a-zA-Z0-9_]+]] = OpUGreaterThan %bool %[[Cond]] %uint_4 + // CHECK: OpBranchConditional %[[CondCmp]] + // CHECK: %[[TexIdxMinus2:[a-zA-Z0-9_]+]] = OpISub %uint %[[TexIdx]] %uint_2 + // CHECK: %[[TexDesc2:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[TexArray]] %[[ResourceHeap]] %[[TexIdxMinus2]] + // CHECK: %[[Tex2:[a-zA-Z0-9_]+]] = OpLoad %[[Tex2D]] %[[TexDesc2]] + // CHECK: %[[SampIdxPlus2:[a-zA-Z0-9_]+]] = OpIAdd %uint %[[SampIdx]] %uint_2 + // CHECK: %[[SampDesc2:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[SamplerArray]] %[[SamplerHeap]] %[[SampIdxPlus2]] + // CHECK: %[[Samp2:[a-zA-Z0-9_]+]] = OpLoad %[[Sampler]] %[[SampDesc2]] + // CHECK: %[[Sampled2:[a-zA-Z0-9_]+]] = OpSampledImage %[[SampledImage]] %[[Tex2]] %[[Samp2]] + // CHECK: %[[Color2:[a-zA-Z0-9_]+]] = OpImageSampleImplicitLod %v4float %[[Sampled2]] + // CHECK: OpFAdd %v4float %[[ColorSum]] %[[Color2]] + color += myTexture.Sample(samp, float2(2,2)); + } + + return color; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl new file mode 100644 index 0000000000..6d769383f6 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl @@ -0,0 +1,34 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: Buffer and RWBuffer from the heap lower to +// Buffer-dimension OpTypeImage (sampled vs storage), +// load a typed image handle, and drive OpImageFetch / OpImageWrite. +// +// Buffer -> OpTypeImage %float Buffer ... Sampled(1), handle OpLoad, OpImageFetch (Load) +// RWBuffer -> OpTypeImage %float Buffer ... Storage(2), handle OpLoad, OpImageWrite (store) + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[BufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 1 Rgba32f +// CHECK-DAG: %[[RWBufferType:[a-zA-Z0-9_]+]] = OpTypeImage %float Buffer 2 0 0 2 Rgba32f + +// CHECK-DAG: %[[RA_BufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferType]]{{$}} +// CHECK-DAG: %[[RA_RWBufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufferType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Buffer myBuf = ResourceDescriptorHeap[0]; + // CHECK: %[[BufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_BufferType]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[BufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[BufferType]] %[[BufIndex]] + + RWBuffer myRWBuf = ResourceDescriptorHeap[1]; + // CHECK: %[[RWBufIndex:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtrType]] %[[RA_RWBufferType]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[RWBufHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWBufferType]] %[[RWBufIndex]] + + // CHECK: %[[BufResult:[a-zA-Z0-9_]+]] = OpImageFetch %v4float %[[BufHandle]] + float4 bufVal = myBuf.Load(tid.x); + + // CHECK: OpImageWrite %[[RWBufHandle]] + myRWBuf[tid.x] = bufVal; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl new file mode 100644 index 0000000000..237fed4aa9 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl @@ -0,0 +1,46 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: TextureCube and TextureCubeArray cube image types, +// each loaded from the resource heap and combined with a heap +// sampler to drive OpImageSampleExplicitLod. + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[CubeType:[a-zA-Z0-9_]+]] = OpTypeImage %float Cube 2 0 0 1 Unknown +// CHECK-DAG: %[[CubeArrType:[a-zA-Z0-9_]+]] = OpTypeImage %float Cube 2 1 0 1 Unknown +// CHECK-DAG: %[[SamplerType:[a-zA-Z0-9_]+]] = OpTypeSampler + +// CHECK-DAG: %[[RA_Cube:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CubeType]]{{$}} +// CHECK-DAG: %[[RA_CubeArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CubeArrType]]{{$}} +// CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant +// CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + TextureCube cube = ResourceDescriptorHeap[0]; + // CHECK: %[[CubeDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Cube]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[CubeHandle:[a-zA-Z0-9_]+]] = OpLoad %[[CubeType]] %[[CubeDesc]] + + TextureCubeArray cubeArr = ResourceDescriptorHeap[1]; + // CHECK: %[[CubeArrDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_CubeArr]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[CubeArrHandle:[a-zA-Z0-9_]+]] = OpLoad %[[CubeArrType]] %[[CubeArrDesc]] + + SamplerState samp = SamplerDescriptorHeap[0]; + // CHECK: %[[SampDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_Sampler]] %[[SamplerHeap]] %uint_0 + // CHECK: %[[SampHandle:[a-zA-Z0-9_]+]] = OpLoad %[[SamplerType]] %[[SampDesc]] + + float3 dir = normalize(float3(tid)); + + // CHECK: %[[CubeSI:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[CubeHandle]] %[[SampHandle]] + // CHECK: OpImageSampleExplicitLod %v4float %[[CubeSI]] + float4 v = cube.SampleLevel(samp, dir, 0); + + // CHECK: %[[CubeArrSI:[a-zA-Z0-9_]+]] = OpSampledImage %{{.*}} %[[CubeArrHandle]] %[[SampHandle]] + // CHECK: OpImageSampleExplicitLod %v4float %[[CubeArrSI]] + v += cubeArr.SampleLevel(samp, float4(dir, 0), 0); + + output.Store(0, asuint(v.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl new file mode 100644 index 0000000000..4fb723c186 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl @@ -0,0 +1,59 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies: each HLSL resource element type lowers to the +// correct OpTypeImage format and sampled-vs-storage mode +// when accessed through the descriptor heap. +// +// Texture2D -> OpTypeImage %uint 2D ... Unknown -> sampled +// RWTexture2D -> OpTypeImage %float 2D ... Rg32f -> storage +// RWTexture2D -> OpTypeImage %uint 2D ... Rg32ui -> storage +// RWTexture2D -> OpTypeImage %int 2D ... R32i -> storage + +// CHECK-DAG: %[[UntypedPtr:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant + +// CHECK-DAG: %[[TexUintType:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[RA_TexUint:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexUintType]]{{$}} + +// CHECK-DAG: %[[RWTexF2Type:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 2 Rg32f +// CHECK-DAG: %[[RA_RWTexF2:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexF2Type]]{{$}} + +// CHECK-DAG: %[[RWTexU2Type:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 2 Rg32ui +// CHECK-DAG: %[[RA_RWTexU2:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexU2Type]]{{$}} + +// CHECK-DAG: %[[RWTexIType:[a-zA-Z0-9_]+]] = OpTypeImage %int 2D 2 0 0 2 R32i +// CHECK-DAG: %[[RA_RWTexI:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexIType]]{{$}} + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant + +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2D texUint = ResourceDescriptorHeap[0]; + // CHECK: %[[TexUintChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_TexUint]] %[[ResourceHeap]] %uint_0 + // CHECK: %[[TexUintH:[a-zA-Z0-9_]+]] = OpLoad %[[TexUintType]] %[[TexUintChain]] + + RWTexture2D rwTexF2 = ResourceDescriptorHeap[1]; + // CHECK: %[[RWTexF2Chain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTexF2]] %[[ResourceHeap]] %uint_1 + // CHECK: %[[RWTexF2H:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexF2Type]] %[[RWTexF2Chain]] + + RWTexture2D rwTexU2 = ResourceDescriptorHeap[2]; + // CHECK: %[[RWTexU2Chain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTexU2]] %[[ResourceHeap]] %uint_2 + // CHECK: %[[RWTexU2H:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexU2Type]] %[[RWTexU2Chain]] + + RWTexture2D rwTexI = ResourceDescriptorHeap[3]; + // CHECK: %[[RWTexIChain:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedPtr]] %[[RA_RWTexI]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[RWTexIH:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexIType]] %[[RWTexIChain]] + + // CHECK: OpImageFetch %v4uint %[[TexUintH]] + uint val = texUint.Load(int3(tid.xy, 0)).x; + + // CHECK: OpImageWrite %[[RWTexF2H]] + rwTexF2[tid.xy] = float2(val, val); + // CHECK: OpImageWrite %[[RWTexU2H]] + rwTexU2[tid.xy] = uint2(val, val); + // CHECK: OpImageWrite %[[RWTexIH]] + rwTexI[tid.xy] = int(val); + + output.Store(0, val); +} diff --git a/tools/clang/unittests/SPIRV/SpirvContextTest.cpp b/tools/clang/unittests/SPIRV/SpirvContextTest.cpp index a0f7a4b4c3..08d33e4892 100644 --- a/tools/clang/unittests/SPIRV/SpirvContextTest.cpp +++ b/tools/clang/unittests/SPIRV/SpirvContextTest.cpp @@ -324,6 +324,9 @@ TEST_F(SpirvContextTest, RuntimeArrayTypeUnique3) { EXPECT_NE(spvContext.getRuntimeArrayType(int32, 4), spvContext.getRuntimeArrayType(int32, llvm::None)); + + EXPECT_NE(spvContext.getRuntimeArrayType(int32, llvm::None), + spvContext.getRuntimeArrayType(int32, 32)); } TEST_F(SpirvContextTest, PointerTypeUnique1) { From c7d2389ccbeca2845e9a8a3a46deed5e35467de4 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Fri, 26 Jun 2026 14:22:21 -0700 Subject: [PATCH 02/26] Added OpConstantSizeOfEXT --- .../clang/include/clang/SPIRV/SpirvBuilder.h | 9 +++ .../clang/include/clang/SPIRV/SpirvContext.h | 6 +- .../include/clang/SPIRV/SpirvInstruction.h | 28 +++++++ tools/clang/include/clang/SPIRV/SpirvType.h | 14 +++- .../clang/include/clang/SPIRV/SpirvVisitor.h | 1 + tools/clang/lib/SPIRV/EmitVisitor.cpp | 33 +++++++- tools/clang/lib/SPIRV/EmitVisitor.h | 2 + tools/clang/lib/SPIRV/SpirvBuilder.cpp | 15 ++++ tools/clang/lib/SPIRV/SpirvContext.cpp | 7 +- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 21 ++---- tools/clang/lib/SPIRV/SpirvEmitter.h | 9 +-- tools/clang/lib/SPIRV/SpirvInstruction.cpp | 18 +++++ tools/clang/lib/SPIRV/SpirvType.cpp | 3 +- ...sm6_6.descriptorheap.ext.array-stride.hlsl | 75 +++++++++++++++++++ ...6_6.descriptorheap.ext.rwtexture-dims.hlsl | 10 +++ ...sm6_6.descriptorheap.ext.texture-dims.hlsl | 10 +++ .../sm6_6.descriptorheap.ext.texture-ms.hlsl | 6 ++ .../sm6_6.descriptorheap.ext.texture.hlsl | 6 ++ .../sm6_6.descriptorheap.ext.texturecube.hlsl | 8 ++ ...m6_6.descriptorheap.ext.typed-formats.hlsl | 10 +++ 20 files changed, 263 insertions(+), 28 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index ae8add20a9..95c1a44235 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -825,6 +825,11 @@ class SpirvBuilder { bool specConst = false); SpirvConstant *getConstantNull(QualType); SpirvConstant *getConstantString(llvm::StringRef str, bool specConst = false); + /// \brief Returns the OpConstantSizeOfEXT (SPV_EXT_descriptor_heap) for the + /// given descriptor operandType, yielding its client-API size in bytes as + /// a 32-bit unsigned value. The result is cached per operand type, so each + /// descriptor type emits at most one instruction. + SpirvConstant *getConstantSizeOfEXT(const SpirvType *operandType); SpirvUndef *getUndef(QualType); SpirvString *createString(llvm::StringRef str); @@ -941,6 +946,10 @@ class SpirvBuilder { /// Used as caches for all created builtin variables to avoid duplication. llvm::SmallVector builtinVars; + /// Cache of OpConstantSizeOfEXT instructions keyed on the descriptor operand + /// type, so each distinct descriptor type emits at most one instruction. + llvm::DenseMap constantSizeOfEXTMap; + SpirvDebugInfoNone *debugNone; /// DebugExpression that does not reference any DebugOperation diff --git a/tools/clang/include/clang/SPIRV/SpirvContext.h b/tools/clang/include/clang/SPIRV/SpirvContext.h index 8a49f961ca..93c4b150cf 100644 --- a/tools/clang/include/clang/SPIRV/SpirvContext.h +++ b/tools/clang/include/clang/SPIRV/SpirvContext.h @@ -89,7 +89,8 @@ struct RuntimeArrayTypeMapInfo { static inline RuntimeArrayType *getTombstoneKey() { return nullptr; } static unsigned getHashValue(const RuntimeArrayType *Val) { return llvm::hash_combine(Val->getElementType(), - Val->getStride().hasValue()); + Val->getStride().hasValue(), + Val->getArrayStrideId()); } static bool isEqual(const RuntimeArrayType *LHS, const RuntimeArrayType *RHS) { @@ -284,7 +285,8 @@ class SpirvContext { llvm::Optional arrayStride); const RuntimeArrayType * getRuntimeArrayType(const SpirvType *elemType, - llvm::Optional arrayStride); + llvm::Optional arrayStride, + SpirvInstruction *arrayStrideId = nullptr); const NodePayloadArrayType * getNodePayloadArrayType(const SpirvType *elemType, const ParmVarDecl *nodeDecl); diff --git a/tools/clang/include/clang/SPIRV/SpirvInstruction.h b/tools/clang/include/clang/SPIRV/SpirvInstruction.h index d83d8f7294..ffc1a781b4 100644 --- a/tools/clang/include/clang/SPIRV/SpirvInstruction.h +++ b/tools/clang/include/clang/SPIRV/SpirvInstruction.h @@ -69,6 +69,7 @@ class SpirvInstruction { IK_ConstantFloat, IK_ConstantComposite, IK_ConstantString, + IK_ConstantSizeOfEXT, IK_ConstantNull, // Pointer <-> uint conversions. @@ -1537,6 +1538,33 @@ class SpirvConstantNull : public SpirvConstant { bool operator==(const SpirvConstantNull &that) const; }; +/// \brief Represents OpConstantSizeOfEXT (SPV_EXT_descriptor_heap). +/// +/// Yields the client-API-defined size of a descriptor type in bytes. Unlike +/// other constants its operand is a SPIR-V type (a descriptor type such as +/// OpTypeBufferEXT/OpTypeImage/OpTypeSampler), not a value. Used as the operand +/// of an ArrayStrideIdEXT decoration on descriptor-heap runtime arrays. +class SpirvConstantSizeOfEXT : public SpirvConstant { +public: + SpirvConstantSizeOfEXT(QualType resultType, const SpirvType *operandType); + + DEFINE_RELEASE_MEMORY_FOR_CLASS(SpirvConstantSizeOfEXT) + + // For LLVM-style RTTI + static bool classof(const SpirvInstruction *inst) { + return inst->getKind() == IK_ConstantSizeOfEXT; + } + + bool invokeVisitor(Visitor *v) override; + + bool operator==(const SpirvConstantSizeOfEXT &that) const; + + const SpirvType *getOperandType() const { return operandType; } + +private: + const SpirvType *operandType; +}; + class SpirvConstantString : public SpirvConstant { public: SpirvConstantString(llvm::StringRef stringLiteral, bool isSpecConst = false); diff --git a/tools/clang/include/clang/SPIRV/SpirvType.h b/tools/clang/include/clang/SPIRV/SpirvType.h index 654dad509f..6c71d4e1fd 100644 --- a/tools/clang/include/clang/SPIRV/SpirvType.h +++ b/tools/clang/include/clang/SPIRV/SpirvType.h @@ -23,6 +23,7 @@ namespace clang { namespace spirv { class HybridType; +class SpirvInstruction; enum class StructInterfaceType : uint32_t { InternalStorage = 0, @@ -274,8 +275,12 @@ class ArrayType : public SpirvType { class RuntimeArrayType : public SpirvType { public: RuntimeArrayType(const SpirvType *elemType, - llvm::Optional arrayStride) - : SpirvType(TK_RuntimeArray), elementType(elemType), stride(arrayStride) { + llvm::Optional arrayStride, + SpirvInstruction *strideId = nullptr) + : SpirvType(TK_RuntimeArray), elementType(elemType), stride(arrayStride), + arrayStrideId(strideId) { + assert(!(arrayStride.hasValue() && strideId) && + "literal stride and stride-id are mutually exclusive"); } static bool classof(const SpirvType *t) { @@ -286,12 +291,17 @@ class RuntimeArrayType : public SpirvType { const SpirvType *getElementType() const { return elementType; } llvm::Optional getStride() const { return stride; } + // When non-null, the array is decorated with ArrayStrideIdEXT referencing + // this constant (SPV_EXT_descriptor_heap) instead of a literal + // ArrayStride. Mutually exclusive with a literal stride. + SpirvInstruction *getArrayStrideId() const { return arrayStrideId; } private: const SpirvType *elementType; // Two runtime arrays with different ArrayStride decorations, are in fact two // different types. If no layout information is needed, use llvm::None. llvm::Optional stride; + SpirvInstruction *arrayStrideId = nullptr; }; class NodePayloadArrayType : public SpirvType { diff --git a/tools/clang/include/clang/SPIRV/SpirvVisitor.h b/tools/clang/include/clang/SPIRV/SpirvVisitor.h index 894f86ada2..fa2d1009ab 100644 --- a/tools/clang/include/clang/SPIRV/SpirvVisitor.h +++ b/tools/clang/include/clang/SPIRV/SpirvVisitor.h @@ -97,6 +97,7 @@ class Visitor { DEFINE_VISIT_METHOD(SpirvConstantFloat) DEFINE_VISIT_METHOD(SpirvConstantComposite) DEFINE_VISIT_METHOD(SpirvConstantString) + DEFINE_VISIT_METHOD(SpirvConstantSizeOfEXT) DEFINE_VISIT_METHOD(SpirvConstantNull) DEFINE_VISIT_METHOD(SpirvConvertPtrToU) DEFINE_VISIT_METHOD(SpirvConvertUToPtr) diff --git a/tools/clang/lib/SPIRV/EmitVisitor.cpp b/tools/clang/lib/SPIRV/EmitVisitor.cpp index 75d081ee62..618c1083b4 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.cpp +++ b/tools/clang/lib/SPIRV/EmitVisitor.cpp @@ -1132,6 +1132,13 @@ bool EmitVisitor::visit(SpirvConstantString *inst) { return true; } +bool EmitVisitor::visit(SpirvConstantSizeOfEXT *inst) { + typeHandler.emitConstantSizeOfEXT(inst); + emitDebugNameForInstruction(getOrAssignResultId(inst), + inst->getDebugName()); + return true; +} + bool EmitVisitor::visit(SpirvConstantNull *inst) { typeHandler.getOrCreateConstant(inst); emitDebugNameForInstruction(getOrAssignResultId(inst), @@ -2195,6 +2202,8 @@ uint32_t EmitTypeHandler::getOrCreateConstant(SpirvConstant *inst) { return getOrCreateConstantBool(constBool); } else if (auto *constString = dyn_cast(inst)) { return getOrCreateConstantString(constString); + } else if (auto *constSizeOf = dyn_cast(inst)) { + return emitConstantSizeOfEXT(constSizeOf); } else if (auto *constUndef = dyn_cast(inst)) { return getOrCreateUndef(constUndef); } @@ -2287,6 +2296,20 @@ uint32_t EmitTypeHandler::getOrCreateConstantNull(SpirvConstantNull *inst) { return inst->getResultId(); } +uint32_t EmitTypeHandler::emitConstantSizeOfEXT(SpirvConstantSizeOfEXT *inst) { + // Uniqueness is already guaranteed upstream by + // SpirvBuilder::getConstantSizeOfEXT (one instruction per descriptor type), + // so there is no emit-time cache to consult here. + const uint32_t typeId = emitType(inst->getResultType()); + const uint32_t operandTypeId = emitType(inst->getOperandType()); + initTypeInstruction(spv::Op::OpConstantSizeOfEXT); + curTypeInst.push_back(typeId); + curTypeInst.push_back(getOrAssignResultId(inst)); + curTypeInst.push_back(operandTypeId); + finalizeTypeInstruction(); + return inst->getResultId(); +} + uint32_t EmitTypeHandler::getOrCreateUndef(SpirvUndef *inst) { auto canonicalType = inst->getAstResultType().getCanonicalType(); auto found = std::find_if( @@ -2695,9 +2718,15 @@ uint32_t EmitTypeHandler::emitType(const SpirvType *type) { curTypeInst.push_back(elemTypeId); finalizeTypeInstruction(); - auto stride = raType->getStride(); - if (stride.hasValue()) + if (auto *strideId = raType->getArrayStrideId()) { + // SPV_EXT_descriptor_heap: stride given by a constant rather than a + // literal, emitted as OpDecorateId ... ArrayStrideIdEXT . + emitDecoration(id, spv::Decoration::ArrayStrideIdEXT, + {getOrAssignResultId(strideId)}, + llvm::None, /*usesIdParams=*/true); + } else if (auto stride = raType->getStride()) { emitDecoration(id, spv::Decoration::ArrayStride, {stride.getValue()}); + } } // NodePayloadArray types else if (const auto *npaType = dyn_cast(type)) { diff --git a/tools/clang/lib/SPIRV/EmitVisitor.h b/tools/clang/lib/SPIRV/EmitVisitor.h index d123243448..a859f6a651 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.h +++ b/tools/clang/lib/SPIRV/EmitVisitor.h @@ -109,6 +109,7 @@ class EmitTypeHandler { uint32_t getOrCreateConstantFloat(SpirvConstantFloat *); uint32_t getOrCreateConstantComposite(SpirvConstantComposite *); uint32_t getOrCreateConstantNull(SpirvConstantNull *); + uint32_t emitConstantSizeOfEXT(SpirvConstantSizeOfEXT *); uint32_t getOrCreateUndef(SpirvUndef *); uint32_t getOrCreateConstantBool(SpirvConstantBoolean *); uint32_t getOrCreateConstantString(SpirvConstantString *); @@ -271,6 +272,7 @@ class EmitVisitor : public Visitor { bool visit(SpirvConstantFloat *) override; bool visit(SpirvConstantComposite *) override; bool visit(SpirvConstantString *) override; + bool visit(SpirvConstantSizeOfEXT *) override; bool visit(SpirvConstantNull *) override; bool visit(SpirvConvertPtrToU *) override; bool visit(SpirvConvertUToPtr *) override; diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index 9a48ed5f85..a146a5dd18 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -2012,6 +2012,21 @@ SpirvConstant *SpirvBuilder::getConstantNull(QualType type) { return nullConst; } +SpirvConstant *SpirvBuilder::getConstantSizeOfEXT(const SpirvType *operandType) { + // Reuse the existing instruction for a given descriptor type; multiple heap + // accesses of the same element type share one OpConstantSizeOfEXT. + auto found = constantSizeOfEXTMap.find(operandType); + if (found != constantSizeOfEXTMap.end()) + return found->second; + + // size is a non-negative 32-bit unsigned value, though spec allows signed + auto *sizeOfConst = new (context) + SpirvConstantSizeOfEXT(astContext.UnsignedIntTy, operandType); + mod->addConstant(sizeOfConst); + constantSizeOfEXTMap[operandType] = sizeOfConst; + return sizeOfConst; +} + SpirvConstant *SpirvBuilder::getConstantString(llvm::StringRef str, bool specConst) { // We do not care about making unique constants at this point. diff --git a/tools/clang/lib/SPIRV/SpirvContext.cpp b/tools/clang/lib/SPIRV/SpirvContext.cpp index a3397d74b4..1439861e89 100644 --- a/tools/clang/lib/SPIRV/SpirvContext.cpp +++ b/tools/clang/lib/SPIRV/SpirvContext.cpp @@ -268,14 +268,15 @@ SpirvContext::getArrayType(const SpirvType *elemType, uint32_t elemCount, const RuntimeArrayType * SpirvContext::getRuntimeArrayType(const SpirvType *elemType, - llvm::Optional arrayStride) { - RuntimeArrayType type(elemType, arrayStride); + llvm::Optional arrayStride, + SpirvInstruction *arrayStrideId) { + RuntimeArrayType type(elemType, arrayStride, arrayStrideId); auto found = runtimeArrayTypes.find(&type); if (found != runtimeArrayTypes.end()) return *found; auto inserted = runtimeArrayTypes.insert( - new (this) RuntimeArrayType(elemType, arrayStride)); + new (this) RuntimeArrayType(elemType, arrayStride, arrayStrideId)); return *(inserted.first); } diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index e38bf375bb..4de8046017 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -5305,9 +5305,8 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( ? spv::StorageClass::Uniform : spv::StorageClass::StorageBuffer; const auto *bufferDescriptorType = spvContext.getBufferEXTType(bufferExtSC); - // Buffer descriptors are always on the resource heap. - const auto *arrayType = getDescriptorHeapRuntimeArrayType( - bufferDescriptorType, /*onSamplerHeap=*/false); + const auto *arrayType = + getDescriptorHeapRuntimeArrayType(bufferDescriptorType); auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( untypedUniformConstantType, arrayType, heapVar, index, baseExpr->getExprLoc()); @@ -7025,9 +7024,7 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, const SpirvType *handleType = lowerTypeVisitor.lowerType(resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - // Images/samplers may come from either heap; pick the right stride. - const auto *arrayType = getDescriptorHeapRuntimeArrayType( - handleType, isSamplerDescriptorHeap(decl)); + const auto *arrayType = getDescriptorHeapRuntimeArrayType(handleType); auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( untypedUniformConstantType, arrayType, var, index, baseExpr->getExprLoc()); @@ -9234,13 +9231,11 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { } const SpirvType * -SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, - bool onSamplerHeap) { - constexpr uint32_t kDefaultResourceHeapStride = 64; - constexpr uint32_t kDefaultSamplerHeapStride = 32; - const uint32_t stride = - onSamplerHeap ? kDefaultSamplerHeapStride : kDefaultResourceHeapStride; - return spvContext.getRuntimeArrayType(elemType, stride); +SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType) { + // The stride is the client-API defined size of the element descriptor type, + // given by OpConstantSizeOfEXT and applied via ArrayStrideIdEXT decoration. + SpirvInstruction *sizeOf = spvBuilder.getConstantSizeOfEXT(elemType); + return spvContext.getRuntimeArrayType(elemType, llvm::None, sizeOf); } SpirvInstruction * diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 76c174eab4..9f206fcb17 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -399,11 +399,10 @@ class SpirvEmitter : public ASTConsumer { /// Translates the given varDecl into a spec constant. void createSpecConstant(const VarDecl *varDecl); - /// Returns the OpTypeRuntimeArray for a descriptor-heap array of elemType - /// decorated with the default ArrayStride (64 bytes for the resource heap, - /// 32 bytes for the sampler heap). - const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, - bool onSamplerHeap); + /// Returns the OpTypeRuntimeArray for a descriptor heap array of elemType, + /// decorated with ArrayStrideIdEXT referencing an OpConstantSizeOfEXT of the + /// element (descriptor) type. + const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType); /// Emits the native (SPV_EXT_descriptor_heap) access for a buffer-like /// resource (StructuredBuffer/ByteAddressBuffer/ConstantBuffer/TextureBuffer diff --git a/tools/clang/lib/SPIRV/SpirvInstruction.cpp b/tools/clang/lib/SPIRV/SpirvInstruction.cpp index 153a2f9c66..0a9371ef6b 100644 --- a/tools/clang/lib/SPIRV/SpirvInstruction.cpp +++ b/tools/clang/lib/SPIRV/SpirvInstruction.cpp @@ -67,6 +67,7 @@ DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConstantInteger) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConstantFloat) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConstantComposite) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConstantString) +DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConstantSizeOfEXT) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConstantNull) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConvertPtrToU) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvConvertUToPtr) @@ -626,6 +627,11 @@ bool SpirvConstant::operator==(const SpirvConstant &that) const { if (thatNullInst == nullptr) return false; return *nullInst == *thatNullInst; + } else if (auto *sizeOfInst = dyn_cast(this)) { + auto *thatSizeOfInst = dyn_cast(&that); + if (thatSizeOfInst == nullptr) + return false; + return *sizeOfInst == *thatSizeOfInst; } else if (auto *nullInst = dyn_cast(this)) { auto *thatNullInst = dyn_cast(&that); if (thatNullInst == nullptr) @@ -710,6 +716,18 @@ bool SpirvConstantString::operator==(const SpirvConstantString &that) const { str == that.str; } +SpirvConstantSizeOfEXT::SpirvConstantSizeOfEXT(QualType resultType, + const SpirvType *operandType) + : SpirvConstant(IK_ConstantSizeOfEXT, spv::Op::OpConstantSizeOfEXT, + resultType), + operandType(operandType) {} + +bool SpirvConstantSizeOfEXT::operator==( + const SpirvConstantSizeOfEXT &that) const { + return resultType == that.resultType && + astResultType == that.astResultType && operandType == that.operandType; +} + SpirvConstantNull::SpirvConstantNull(QualType type) : SpirvConstant(IK_ConstantNull, spv::Op::OpConstantNull, type) {} diff --git a/tools/clang/lib/SPIRV/SpirvType.cpp b/tools/clang/lib/SPIRV/SpirvType.cpp index caa4f6d52b..e43c6fe9ae 100644 --- a/tools/clang/lib/SPIRV/SpirvType.cpp +++ b/tools/clang/lib/SPIRV/SpirvType.cpp @@ -169,7 +169,8 @@ bool ArrayType::operator==(const ArrayType &that) const { bool RuntimeArrayType::operator==(const RuntimeArrayType &that) const { return elementType == that.elementType && stride.hasValue() == that.stride.hasValue() && - (!stride.hasValue() || stride.getValue() == that.stride.getValue()); + (!stride.hasValue() || stride.getValue() == that.stride.getValue()) && + arrayStrideId == that.arrayStrideId; } bool NodePayloadArrayType::operator==(const NodePayloadArrayType &that) const { diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl new file mode 100644 index 0000000000..29758fbd8e --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl @@ -0,0 +1,75 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s --check-prefix=DEDUP + +// Verifies the default descriptor-heap array stride. +// +// With no manual intervention, every heap runtime array must be decorated with ArrayStrideIdEXT +// referencing an OpConstantSizeOfEXT of the array's element (descriptor) type: +// +// StructuredBuffer / RWStructuredBuffer / ByteAddressBuffer -> OpTypeBufferEXT StorageBuffer +// ConstantBuffer -> OpTypeBufferEXT Uniform +// Texture2D -> OpTypeImage +// SamplerState -> OpTypeSampler +// +// The OpConstantSizeOfEXT result type is %uint + +// CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant + +// Element (descriptor) types. +// CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// CHECK-DAG: %[[UBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// CHECK-DAG: %[[TexDesc:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D +// CHECK-DAG: %[[SamplerDesc:[a-zA-Z0-9_]+]] = OpTypeSampler + +// Heap runtime arrays of those element types. +// CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] +// CHECK-DAG: %[[UBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[UBufDesc]] +// CHECK-DAG: %[[TexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexDesc]] +// CHECK-DAG: %[[SamplerArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerDesc]] + +// One OpConstantSizeOfEXT per distinct element type, result type %uint. +// CHECK-DAG: %[[SBBufSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SBBufDesc]] +// CHECK-DAG: %[[UBufSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[UBufDesc]] +// CHECK-DAG: %[[TexSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[TexDesc]] +// CHECK-DAG: %[[SamplerSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SamplerDesc]] + +// Each heap array is decorated ArrayStrideIdEXT with its element's sizeof. +// CHECK-DAG: OpDecorateId %[[SBBufArray]] ArrayStrideIdEXT %[[SBBufSize]] +// CHECK-DAG: OpDecorateId %[[UBufArray]] ArrayStrideIdEXT %[[UBufSize]] +// CHECK-DAG: OpDecorateId %[[TexArray]] ArrayStrideIdEXT %[[TexSize]] +// CHECK-DAG: OpDecorateId %[[SamplerArray]] ArrayStrideIdEXT %[[SamplerSize]] + +// No literal default stride decoration on any heap array. +// CHECK-NOT: OpDecorate %{{.*}} ArrayStride 64 +// CHECK-NOT: OpDecorate %{{.*}} ArrayStride 32 + +// Dedup: StructuredBuffer and ByteAddressBuffer below both lower to the same +// OpTypeBufferEXT StorageBuffer descriptor, so its OpConstantSizeOfEXT must be +// emitted exactly ONCE and shared, not one per heap access. + +// Separate prefix: annotations precede the constants section in SPIR-V, so a +// trailing ordered check in the main pass would search past the sizeof. +// {{$}} anchors the match: "StorageBuffer" is a prefix of "StorageBuffer_0" +// (the Uniform descriptor name), so without it DEDUP-NOT would false-fire. + +// DEDUP: %[[SBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer +// DEDUP-COUNT-1: OpConstantSizeOfEXT %uint %[[SBDesc]]{{$}} +// DEDUP-NOT: OpConstantSizeOfEXT %uint %[[SBDesc]]{{$}} + +struct Constants { + uint value; +}; + +RWStructuredBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + StructuredBuffer sbuf = ResourceDescriptorHeap[0]; + ByteAddressBuffer babuf = ResourceDescriptorHeap[1]; + ConstantBuffer cbuf = ResourceDescriptorHeap[2]; + Texture2D tex = ResourceDescriptorHeap[3]; + SamplerState samp = SamplerDescriptorHeap[0]; + + float4 color = tex.SampleLevel(samp, float2(0, 0), 0); + output[tid.x] = color + sbuf.Load(tid.x) + babuf.Load(tid.x * 4) + cbuf.value; +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl index 87d2591318..37d45993a7 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl @@ -19,6 +19,16 @@ // CHECK-DAG: %[[RA_RW2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW2DArrType]]{{$}} // CHECK-DAG: %[[RA_RW3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW3DType]]{{$}} +// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. +// CHECK-DAG: %[[RW1DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW1DType]] +// CHECK-DAG: %[[RW1DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW1DArrType]] +// CHECK-DAG: %[[RW2DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW2DArrType]] +// CHECK-DAG: %[[RW3DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW3DType]] +// CHECK-DAG: OpDecorateId %[[RA_RW1D]] ArrayStrideIdEXT %[[RW1DSize]] +// CHECK-DAG: OpDecorateId %[[RA_RW1DArr]] ArrayStrideIdEXT %[[RW1DArrSize]] +// CHECK-DAG: OpDecorateId %[[RA_RW2DArr]] ArrayStrideIdEXT %[[RW2DArrSize]] +// CHECK-DAG: OpDecorateId %[[RA_RW3D]] ArrayStrideIdEXT %[[RW3DSize]] + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant [numthreads(1, 1, 1)] diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl index 120830aa4c..274bcd11bc 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl @@ -19,6 +19,16 @@ // CHECK-DAG: %[[RA_Tex2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DArrType]]{{$}} // CHECK-DAG: %[[RA_Tex3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex3DType]]{{$}} +// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. +// CHECK-DAG: %[[Tex1DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex1DType]] +// CHECK-DAG: %[[Tex1DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex1DArrType]] +// CHECK-DAG: %[[Tex2DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex2DArrType]] +// CHECK-DAG: %[[Tex3DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex3DType]] +// CHECK-DAG: OpDecorateId %[[RA_Tex1D]] ArrayStrideIdEXT %[[Tex1DSize]] +// CHECK-DAG: OpDecorateId %[[RA_Tex1DArr]] ArrayStrideIdEXT %[[Tex1DArrSize]] +// CHECK-DAG: OpDecorateId %[[RA_Tex2DArr]] ArrayStrideIdEXT %[[Tex2DArrSize]] +// CHECK-DAG: OpDecorateId %[[RA_Tex3D]] ArrayStrideIdEXT %[[Tex3DSize]] + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant RWByteAddressBuffer output : register(u0); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl index edb2d78821..cd7e2db43f 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl @@ -10,6 +10,12 @@ // CHECK-DAG: %[[RA_MS:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSType]]{{$}} // CHECK-DAG: %[[RA_MSArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSArrType]]{{$}} +// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. +// CHECK-DAG: %[[MSSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[MSType]] +// CHECK-DAG: %[[MSArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[MSArrType]] +// CHECK-DAG: OpDecorateId %[[RA_MS]] ArrayStrideIdEXT %[[MSSize]] +// CHECK-DAG: OpDecorateId %[[RA_MSArr]] ArrayStrideIdEXT %[[MSArrSize]] + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant RWByteAddressBuffer output : register(u0); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl index 6d769383f6..84c15a2869 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl @@ -14,6 +14,12 @@ // CHECK-DAG: %[[RA_BufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferType]]{{$}} // CHECK-DAG: %[[RA_RWBufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufferType]]{{$}} +// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. +// CHECK-DAG: %[[BufferSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[BufferType]] +// CHECK-DAG: %[[RWBufferSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWBufferType]] +// CHECK-DAG: OpDecorateId %[[RA_BufferType]] ArrayStrideIdEXT %[[BufferSize]] +// CHECK-DAG: OpDecorateId %[[RA_RWBufferType]] ArrayStrideIdEXT %[[RWBufferSize]] + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant [numthreads(1, 1, 1)] diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl index 237fed4aa9..755b4adefb 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl @@ -13,6 +13,14 @@ // CHECK-DAG: %[[RA_CubeArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CubeArrType]]{{$}} // CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} +// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. +// CHECK-DAG: %[[CubeSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[CubeType]] +// CHECK-DAG: %[[CubeArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[CubeArrType]] +// CHECK-DAG: %[[SamplerSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SamplerType]] +// CHECK-DAG: OpDecorateId %[[RA_Cube]] ArrayStrideIdEXT %[[CubeSize]] +// CHECK-DAG: OpDecorateId %[[RA_CubeArr]] ArrayStrideIdEXT %[[CubeArrSize]] +// CHECK-DAG: OpDecorateId %[[RA_Sampler]] ArrayStrideIdEXT %[[SamplerSize]] + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant // CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl index 4fb723c186..d001691ced 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl @@ -23,6 +23,16 @@ // CHECK-DAG: %[[RWTexIType:[a-zA-Z0-9_]+]] = OpTypeImage %int 2D 2 0 0 2 R32i // CHECK-DAG: %[[RA_RWTexI:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexIType]]{{$}} +// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. +// CHECK-DAG: %[[TexUintSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[TexUintType]] +// CHECK-DAG: %[[RWTexF2Size:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWTexF2Type]] +// CHECK-DAG: %[[RWTexU2Size:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWTexU2Type]] +// CHECK-DAG: %[[RWTexISize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWTexIType]] +// CHECK-DAG: OpDecorateId %[[RA_TexUint]] ArrayStrideIdEXT %[[TexUintSize]] +// CHECK-DAG: OpDecorateId %[[RA_RWTexF2]] ArrayStrideIdEXT %[[RWTexF2Size]] +// CHECK-DAG: OpDecorateId %[[RA_RWTexU2]] ArrayStrideIdEXT %[[RWTexU2Size]] +// CHECK-DAG: OpDecorateId %[[RA_RWTexI]] ArrayStrideIdEXT %[[RWTexISize]] + // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant RWByteAddressBuffer output : register(u0); From 153e928ea84c48e886d94be93faf6ca40b96d8b9 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 6 Jul 2026 12:23:21 -0700 Subject: [PATCH 03/26] clang-format --- tools/clang/lib/SPIRV/SpirvBuilder.cpp | 3 ++- tools/clang/lib/SPIRV/SpirvInstruction.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index a146a5dd18..9fce2c57da 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -2012,7 +2012,8 @@ SpirvConstant *SpirvBuilder::getConstantNull(QualType type) { return nullConst; } -SpirvConstant *SpirvBuilder::getConstantSizeOfEXT(const SpirvType *operandType) { +SpirvConstant * +SpirvBuilder::getConstantSizeOfEXT(const SpirvType *operandType) { // Reuse the existing instruction for a given descriptor type; multiple heap // accesses of the same element type share one OpConstantSizeOfEXT. auto found = constantSizeOfEXTMap.find(operandType); diff --git a/tools/clang/lib/SPIRV/SpirvInstruction.cpp b/tools/clang/lib/SPIRV/SpirvInstruction.cpp index 0a9371ef6b..0f39e24dce 100644 --- a/tools/clang/lib/SPIRV/SpirvInstruction.cpp +++ b/tools/clang/lib/SPIRV/SpirvInstruction.cpp @@ -724,8 +724,8 @@ SpirvConstantSizeOfEXT::SpirvConstantSizeOfEXT(QualType resultType, bool SpirvConstantSizeOfEXT::operator==( const SpirvConstantSizeOfEXT &that) const { - return resultType == that.resultType && - astResultType == that.astResultType && operandType == that.operandType; + return resultType == that.resultType && astResultType == that.astResultType && + operandType == that.operandType; } SpirvConstantNull::SpirvConstantNull(QualType type) From eb4a9bcbeea3e27bccfc3c2beaaf784a83f71fd8 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 9 Jul 2026 18:10:39 -0700 Subject: [PATCH 04/26] Shared max(image,buffer) stride for descriptor heap resource arrays --- .../clang/include/clang/SPIRV/SpirvBuilder.h | 20 +++++ .../include/clang/SPIRV/SpirvInstruction.h | 30 +++++++ .../clang/include/clang/SPIRV/SpirvVisitor.h | 1 + tools/clang/lib/SPIRV/EmitVisitor.cpp | 14 ++++ tools/clang/lib/SPIRV/EmitVisitor.h | 1 + tools/clang/lib/SPIRV/SpirvBuilder.cpp | 54 ++++++++++++ tools/clang/lib/SPIRV/SpirvEmitter.cpp | 74 +++++++++------- tools/clang/lib/SPIRV/SpirvInstruction.cpp | 8 ++ ...sm6_6.descriptorheap.ext.array-stride.hlsl | 84 ++++++++++--------- ...6_6.descriptorheap.ext.rwtexture-dims.hlsl | 15 ++-- ...sm6_6.descriptorheap.ext.texture-dims.hlsl | 15 ++-- .../sm6_6.descriptorheap.ext.texture-ms.hlsl | 9 +- .../sm6_6.descriptorheap.ext.texture.hlsl | 9 +- .../sm6_6.descriptorheap.ext.texturecube.hlsl | 12 ++- ...m6_6.descriptorheap.ext.typed-formats.hlsl | 15 ++-- 15 files changed, 247 insertions(+), 114 deletions(-) diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index 95c1a44235..867e6f4957 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -830,6 +830,20 @@ class SpirvBuilder { /// a 32-bit unsigned value. The result is cached per operand type, so each /// descriptor type emits at most one instruction. SpirvConstant *getConstantSizeOfEXT(const SpirvType *operandType); + + SpirvSpecConstantTernaryOp * + createSpecConstantTernaryOp(spv::Op op, QualType resultType, + SpirvInstruction *op1, SpirvInstruction *op2, + SpirvInstruction *op3, SourceLocation loc); + + /// \brief Shared ArrayStrideIdEXT operand for resource-heap runtime arrays: + /// max(sizeof(image), sizeof(buffer)) computed via OpSpecConstantOp. + /// Cached per module. + SpirvInstruction *getResourceHeapArrayStride(); + + /// \brief Shared ArrayStrideIdEXT operand for sampler-heap runtime arrays: + /// the sampler descriptor size. Cached per module. + SpirvInstruction *getSamplerHeapArrayStride(); SpirvUndef *getUndef(QualType); SpirvString *createString(llvm::StringRef str); @@ -950,6 +964,12 @@ class SpirvBuilder { /// type, so each distinct descriptor type emits at most one instruction. llvm::DenseMap constantSizeOfEXTMap; + /// Cached shared descriptor-heap array strides (SPV_EXT_descriptor_heap), so + /// each is emitted once per module (see + /// get{Resource,Sampler}HeapArrayStride). + SpirvInstruction *resourceHeapArrayStride = nullptr; + SpirvInstruction *samplerHeapArrayStride = nullptr; + SpirvDebugInfoNone *debugNone; /// DebugExpression that does not reference any DebugOperation diff --git a/tools/clang/include/clang/SPIRV/SpirvInstruction.h b/tools/clang/include/clang/SPIRV/SpirvInstruction.h index ffc1a781b4..428464be31 100644 --- a/tools/clang/include/clang/SPIRV/SpirvInstruction.h +++ b/tools/clang/include/clang/SPIRV/SpirvInstruction.h @@ -138,6 +138,7 @@ class SpirvInstruction { IK_ReadClock, // OpReadClock IK_SampledImage, // OpSampledImage IK_Select, // OpSelect + IK_SpecConstantTernaryOp, // SpecConstant ternary operations IK_SpecConstantBinaryOp, // SpecConstant binary operations IK_SpecConstantUnaryOp, // SpecConstant unary operations IK_Store, // OpStore @@ -2255,6 +2256,35 @@ class SpirvSelect : public SpirvInstruction { SpirvInstruction *falseObject; }; +/// \brief OpSpecConstantOp instruction where the operation is ternary. +class SpirvSpecConstantTernaryOp : public SpirvInstruction { +public: + SpirvSpecConstantTernaryOp(spv::Op specConstantOp, QualType resultType, + SourceLocation loc, SpirvInstruction *operand1, + SpirvInstruction *operand2, + SpirvInstruction *operand3); + + DEFINE_RELEASE_MEMORY_FOR_CLASS(SpirvSpecConstantTernaryOp) + + // For LLVM-style RTTI + static bool classof(const SpirvInstruction *inst) { + return inst->getKind() == IK_SpecConstantTernaryOp; + } + + bool invokeVisitor(Visitor *v) override; + + spv::Op getSpecConstantopcode() const { return specOp; } + SpirvInstruction *getOperand1() const { return operand1; } + SpirvInstruction *getOperand2() const { return operand2; } + SpirvInstruction *getOperand3() const { return operand3; } + +private: + spv::Op specOp; + SpirvInstruction *operand1; + SpirvInstruction *operand2; + SpirvInstruction *operand3; +}; + /// \brief OpSpecConstantOp instruction where the operation is binary. class SpirvSpecConstantBinaryOp : public SpirvInstruction { public: diff --git a/tools/clang/include/clang/SPIRV/SpirvVisitor.h b/tools/clang/include/clang/SPIRV/SpirvVisitor.h index fa2d1009ab..5dd37bc4db 100644 --- a/tools/clang/include/clang/SPIRV/SpirvVisitor.h +++ b/tools/clang/include/clang/SPIRV/SpirvVisitor.h @@ -119,6 +119,7 @@ class Visitor { DEFINE_VISIT_METHOD(SpirvCopyObject) DEFINE_VISIT_METHOD(SpirvSampledImage) DEFINE_VISIT_METHOD(SpirvSelect) + DEFINE_VISIT_METHOD(SpirvSpecConstantTernaryOp) DEFINE_VISIT_METHOD(SpirvSpecConstantBinaryOp) DEFINE_VISIT_METHOD(SpirvSpecConstantUnaryOp) DEFINE_VISIT_METHOD(SpirvStore) diff --git a/tools/clang/lib/SPIRV/EmitVisitor.cpp b/tools/clang/lib/SPIRV/EmitVisitor.cpp index 618c1083b4..6cf8450f34 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.cpp +++ b/tools/clang/lib/SPIRV/EmitVisitor.cpp @@ -1430,6 +1430,20 @@ bool EmitVisitor::visit(SpirvSelect *inst) { return true; } +bool EmitVisitor::visit(SpirvSpecConstantTernaryOp *inst) { + initInstruction(inst); + curInst.push_back(inst->getResultTypeId()); + curInst.push_back(getOrAssignResultId(inst)); + curInst.push_back(static_cast(inst->getSpecConstantopcode())); + curInst.push_back(getOrAssignResultId(inst->getOperand1())); + curInst.push_back(getOrAssignResultId(inst->getOperand2())); + curInst.push_back(getOrAssignResultId(inst->getOperand3())); + finalizeInstruction(&typeConstantBinary); + emitDebugNameForInstruction(getOrAssignResultId(inst), + inst->getDebugName()); + return true; +} + bool EmitVisitor::visit(SpirvSpecConstantBinaryOp *inst) { initInstruction(inst); curInst.push_back(inst->getResultTypeId()); diff --git a/tools/clang/lib/SPIRV/EmitVisitor.h b/tools/clang/lib/SPIRV/EmitVisitor.h index a859f6a651..d3c97c6281 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.h +++ b/tools/clang/lib/SPIRV/EmitVisitor.h @@ -291,6 +291,7 @@ class EmitVisitor : public Visitor { bool visit(SpirvCopyObject *) override; bool visit(SpirvSampledImage *) override; bool visit(SpirvSelect *) override; + bool visit(SpirvSpecConstantTernaryOp *) override; bool visit(SpirvSpecConstantBinaryOp *) override; bool visit(SpirvSpecConstantUnaryOp *) override; bool visit(SpirvStore *) override; diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index 9fce2c57da..b4b5c834ff 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -471,6 +471,16 @@ SpirvSpecConstantBinaryOp *SpirvBuilder::createSpecConstantBinaryOp( return instruction; } +SpirvSpecConstantTernaryOp *SpirvBuilder::createSpecConstantTernaryOp( + spv::Op op, QualType resultType, SpirvInstruction *op1, + SpirvInstruction *op2, SpirvInstruction *op3, SourceLocation loc) { + assert(insertPoint && "null insert point"); + auto *instruction = new (context) + SpirvSpecConstantTernaryOp(op, resultType, loc, op1, op2, op3); + insertPoint->addInstruction(instruction); + return instruction; +} + SpirvGroupNonUniformOp *SpirvBuilder::createGroupNonUniformOp( spv::Op op, QualType resultType, llvm::Optional execScope, llvm::ArrayRef operands, SourceLocation loc, @@ -2028,6 +2038,50 @@ SpirvBuilder::getConstantSizeOfEXT(const SpirvType *operandType) { return sizeOfConst; } +SpirvInstruction *SpirvBuilder::getResourceHeapArrayStride() { + if (resourceHeapArrayStride) + return resourceHeapArrayStride; + + // The HLSL SM6.6 ResourceDescriptorHeap is a single flat array in which the + // client may place any resource descriptor at any slot. To match DX12 + // semantics, all resource descriptor arrays must share one stride equal to + // the largest resource descriptor size: max(sizeof(image), sizeof(buffer)). + // Images and buffers are the two resource descriptor categories defined by + // VkPhysicalDeviceDescriptorHeapPropertiesEXT (imageDescriptorSize / + // bufferDescriptorSize); textures lower to OpTypeImage, so image/buffer + // covers all relevant HLSL resource kinds. + // Both sizes are driver defined and known only at pipeline creation time, so + // the maximum is computed with OpSpecConstantOp over two OpConstantSizeOfEXT + // placeholders. A canonical sampled 2D float image and a Uniform buffer stand + // in as representatives; VkPhysicalDeviceDescriptorHeapPropertiesEXT reports + // one size per category, so subtype and storage class do not affect the size. + const SpirvType *placeholderImage = context.getImageType( + context.getFloatType(32), spv::Dim::Dim2D, ImageType::WithDepth::No, + /*arrayed*/ false, /*ms*/ false, ImageType::WithSampler::Yes, + spv::ImageFormat::Unknown); + const SpirvType *placeholderBuffer = + context.getBufferEXTType(spv::StorageClass::Uniform); + + SpirvInstruction *imageSize = getConstantSizeOfEXT(placeholderImage); + SpirvInstruction *bufferSize = getConstantSizeOfEXT(placeholderBuffer); + SpirvInstruction *imageIsBigger = createSpecConstantBinaryOp( + spv::Op::OpUGreaterThan, astContext.BoolTy, imageSize, bufferSize, {}); + resourceHeapArrayStride = + createSpecConstantTernaryOp(spv::Op::OpSelect, astContext.UnsignedIntTy, + imageIsBigger, imageSize, bufferSize, {}); + return resourceHeapArrayStride; +} + +SpirvInstruction *SpirvBuilder::getSamplerHeapArrayStride() { + if (samplerHeapArrayStride) + return samplerHeapArrayStride; + // The sampler heap holds only OpTypeSampler descriptors. All samplers are the + // same size (unlike resources, which split into image and buffer categories + // that may differ), so the stride is just that one descriptor size. + samplerHeapArrayStride = getConstantSizeOfEXT(context.getSamplerType()); + return samplerHeapArrayStride; +} + SpirvConstant *SpirvBuilder::getConstantString(llvm::StringRef str, bool specConst) { // We do not care about making unique constants at this point. diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 4de8046017..25989a12a6 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -5130,9 +5130,10 @@ bool SpirvEmitter::isDescriptorHeapCounterUnsupported(const Expr *expr) const { SpirvInstruction *SpirvEmitter::emitDescriptorHeapAccessChain( const SpirvType *arrayType, SpirvInstruction *heap, SpirvVariable *indexVar, SourceLocation loc) { - const auto *untypedUniformConstantType = + const UntypedPointerKHRType *untypedUniformConstantType = spvContext.getUntypedPointerKHRType(spv::StorageClass::UniformConstant); - auto *index = spvBuilder.createLoad(astContext.UnsignedIntTy, indexVar, loc); + SpirvInstruction *index = + spvBuilder.createLoad(astContext.UnsignedIntTy, indexVar, loc); return spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, arrayType, heap, index, loc); } @@ -5230,9 +5231,9 @@ SpirvEmitter::emitDescriptorHeapBufferPointer(const VarDecl *decl, if (found == descriptorHeapBufferAliasVars.end()) return nullptr; - auto *descriptorPtr = emitDescriptorHeapAccessChain( + SpirvInstruction *descriptorPtr = emitDescriptorHeapAccessChain( found->second.arrayType, found->second.heap, found->second.indexVar, loc); - auto *bufferDataPtr = spvBuilder.createUnaryOp( + SpirvUnaryOp *bufferDataPtr = spvBuilder.createUnaryOp( spv::Op::OpBufferPointerEXT, found->second.bufferPointerType, descriptorPtr, loc); bufferDataPtr->setStorageClass( @@ -5249,11 +5250,12 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapImageTexelPointer( if (found == descriptorHeapImageAliasVars.end()) return nullptr; - auto *descriptorPtr = emitDescriptorHeapAccessChain( + SpirvInstruction *descriptorPtr = emitDescriptorHeapAccessChain( found->second.arrayType, found->second.heap, found->second.indexVar, loc); - auto *ptr = spvBuilder.createUntypedImageTexelPointerEXT( - resultType, found->second.imageType, descriptorPtr, coordinate, sample, - loc); + SpirvUntypedImageTexelPointerEXT *ptr = + spvBuilder.createUntypedImageTexelPointerEXT( + resultType, found->second.imageType, descriptorPtr, coordinate, + sample, loc); ptr->setStorageClass(spv::StorageClass::Image); return ptr; } @@ -5272,7 +5274,7 @@ getDescriptorHeapBufferStorageClass(QualType resourceType) { SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( QualType resourceType, SpirvInstruction *heapVar, SpirvInstruction *index, const Expr *expr, const Expr *baseExpr, const Expr *indexExpr) { - const auto *untypedUniformConstantType = + const UntypedPointerKHRType *untypedUniformConstantType = spvContext.getUntypedPointerKHRType(spv::StorageClass::UniformConstant); LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, spvBuilder); @@ -5304,13 +5306,15 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( const spv::StorageClass bufferExtSC = isConstantBuffer(resourceType) ? spv::StorageClass::Uniform : spv::StorageClass::StorageBuffer; - const auto *bufferDescriptorType = spvContext.getBufferEXTType(bufferExtSC); - const auto *arrayType = + const BufferEXTType *bufferDescriptorType = + spvContext.getBufferEXTType(bufferExtSC); + const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType(bufferDescriptorType); - auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( - untypedUniformConstantType, arrayType, heapVar, index, - baseExpr->getExprLoc()); - auto *bufferDataPtr = spvBuilder.createUnaryOp( + SpirvUntypedAccessChainKHR *untypedAccessChainPtr = + spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, + arrayType, heapVar, index, + baseExpr->getExprLoc()); + SpirvUnaryOp *bufferDataPtr = spvBuilder.createUnaryOp( spv::Op::OpBufferPointerEXT, bufferDataPointerType, untypedAccessChainPtr, baseExpr->getExprLoc()); bufferDataPtr->setStorageClass(bufferDataPointerType->getStorageClass()); @@ -6993,11 +6997,12 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, baseExpr->getExprLoc()); return nullptr; } - auto *var = declIdMapper.createResourceHeap(decl, resourceType); + SpirvVariableLike *var = + declIdMapper.createResourceHeap(decl, resourceType); if (hlsl::HasHLSLGloballyCoherent(resourceType)) spvBuilder.decorateCoherent(var, baseExpr->getExprLoc()); - auto *index = doExpr(indexExpr); + SpirvInstruction *index = doExpr(indexExpr); if (spirvOptions.useDescriptorHeap) { needsLegalization = true; @@ -7011,12 +7016,11 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, } if (isAKindOfStructuredOrByteBuffer(resourceType) || - isConstantTextureBuffer(resourceType)) { + isConstantTextureBuffer(resourceType)) return emitDescriptorHeapBufferAccess(resourceType, var, index, expr, baseExpr, indexExpr); - } - const auto *untypedUniformConstantType = + const UntypedPointerKHRType *untypedUniformConstantType = spvContext.getUntypedPointerKHRType( spv::StorageClass::UniformConstant); LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, @@ -7024,15 +7028,16 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, const SpirvType *handleType = lowerTypeVisitor.lowerType(resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - const auto *arrayType = getDescriptorHeapRuntimeArrayType(handleType); - auto *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR( - untypedUniformConstantType, arrayType, var, index, - baseExpr->getExprLoc()); - if (isRasterizerOrderedView(resourceType)) { + const SpirvType *arrayType = + getDescriptorHeapRuntimeArrayType(handleType); + SpirvUntypedAccessChainKHR *untypedAccessChainPtr = + spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, + arrayType, var, index, + baseExpr->getExprLoc()); + if (isRasterizerOrderedView(resourceType)) spvBuilder.addExecutionMode(entryFunction, declIdMapper.getInterlockExecutionMode(), {}, baseExpr->getExprLoc()); - } descriptorHeapImageAccesses[expr] = { untypedAccessChainPtr, handleType, arrayType, var, index, indexExpr->getType()}; @@ -7040,7 +7045,7 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, baseExpr->getExprLoc(), range); } - auto *accessChainPtr = spvBuilder.createAccessChain( + SpirvInstruction *accessChainPtr = spvBuilder.createAccessChain( resourceType, var, index, baseExpr->getExprLoc(), range); if (!isAKindOfStructuredOrByteBuffer(resourceType) && @@ -9232,10 +9237,17 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { const SpirvType * SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType) { - // The stride is the client-API defined size of the element descriptor type, - // given by OpConstantSizeOfEXT and applied via ArrayStrideIdEXT decoration. - SpirvInstruction *sizeOf = spvBuilder.getConstantSizeOfEXT(elemType); - return spvContext.getRuntimeArrayType(elemType, llvm::None, sizeOf); + // SPV_EXT_descriptor_heap: apply a client-API-defined byte stride via an + // ArrayStrideIdEXT decoration. The sampler heap holds a single descriptor + // type, so its stride is the sampler descriptor size. The resource heap is a + // shared flat array in which any resource descriptor may sit at any slot, so + // every resource runtime array must use one common stride: max(sizeof(image), + // sizeof(buffer)). Using the accessed element size would be wrong for the + // resource heap. + SpirvInstruction *strideId = isa(elemType) + ? spvBuilder.getSamplerHeapArrayStride() + : spvBuilder.getResourceHeapArrayStride(); + return spvContext.getRuntimeArrayType(elemType, llvm::None, strideId); } SpirvInstruction * diff --git a/tools/clang/lib/SPIRV/SpirvInstruction.cpp b/tools/clang/lib/SPIRV/SpirvInstruction.cpp index 0f39e24dce..0d84b72d0e 100644 --- a/tools/clang/lib/SPIRV/SpirvInstruction.cpp +++ b/tools/clang/lib/SPIRV/SpirvInstruction.cpp @@ -90,6 +90,7 @@ DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvCopyObject) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvSampledImage) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvSelect) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvSpecConstantBinaryOp) +DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvSpecConstantTernaryOp) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvSpecConstantUnaryOp) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvStore) DEFINE_INVOKE_VISITOR_FOR_CLASS(SpirvNullaryOp) @@ -1040,6 +1041,13 @@ SpirvSpecConstantBinaryOp::SpirvSpecConstantBinaryOp(spv::Op specConstantOp, resultType, loc), specOp(specConstantOp), operand1(op1), operand2(op2) {} +SpirvSpecConstantTernaryOp::SpirvSpecConstantTernaryOp( + spv::Op specConstantOp, QualType resultType, SourceLocation loc, + SpirvInstruction *op1, SpirvInstruction *op2, SpirvInstruction *op3) + : SpirvInstruction(IK_SpecConstantTernaryOp, spv::Op::OpSpecConstantOp, + resultType, loc), + specOp(specConstantOp), operand1(op1), operand2(op2), operand3(op3) {} + SpirvSpecConstantUnaryOp::SpirvSpecConstantUnaryOp(spv::Op specConstantOp, QualType resultType, SourceLocation loc, diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl index 29758fbd8e..64d4a94899 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl @@ -1,60 +1,64 @@ // RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s -// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s --check-prefix=DEDUP +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s --check-prefix=COUNT // Verifies the default descriptor-heap array stride. // -// With no manual intervention, every heap runtime array must be decorated with ArrayStrideIdEXT -// referencing an OpConstantSizeOfEXT of the array's element (descriptor) type: +// The HLSL SM6.6 ResourceDescriptorHeap is a single flat array in which the +// client may place any resource descriptor at any slot. To match DX12 +// semantics, all resource descriptor arrays must share one stride equal to +// the largest resource descriptor size: max(sizeof(image), sizeof(buffer)). +// Images and buffers are the two resource descriptor categories defined by +// VkPhysicalDeviceDescriptorHeapPropertiesEXT (imageDescriptorSize / +// bufferDescriptorSize); textures lower to OpTypeImage, so image/buffer +// covers all relevant HLSL resource kinds. +// Both sizes are driver defined and known only at pipeline creation time, so +// the maximum is computed with OpSpecConstantOp over two OpConstantSizeOfEXT +// placeholders. // -// StructuredBuffer / RWStructuredBuffer / ByteAddressBuffer -> OpTypeBufferEXT StorageBuffer -// ConstantBuffer -> OpTypeBufferEXT Uniform -// Texture2D -> OpTypeImage -// SamplerState -> OpTypeSampler +// The sampler heap holds a single descriptor type, so its stride is simply the +// sampler descriptor size. // -// The OpConstantSizeOfEXT result type is %uint +// OpConstantSizeOfEXT result type is %uint. // CHECK-DAG: %[[UntypedPtrType:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant // Element (descriptor) types. // CHECK-DAG: %[[SBBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer // CHECK-DAG: %[[UBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform -// CHECK-DAG: %[[TexDesc:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D // CHECK-DAG: %[[SamplerDesc:[a-zA-Z0-9_]+]] = OpTypeSampler -// Heap runtime arrays of those element types. +// Image descriptor type. Texture2D lowers to this same type +// (Dim2D, depth=0, not-arrayed, non-MS, sampled, Unknown format), so +// %[[TexDesc]] serves as both the placeholder for the size calculation and +// the real descriptor type for the Texture2D access below. +// CHECK-DAG: %[[TexDesc:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 0 0 0 1 Unknown + +// Heap runtime arrays of the accessed element types. // CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] // CHECK-DAG: %[[UBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[UBufDesc]] // CHECK-DAG: %[[TexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexDesc]] // CHECK-DAG: %[[SamplerArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerDesc]] -// One OpConstantSizeOfEXT per distinct element type, result type %uint. -// CHECK-DAG: %[[SBBufSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SBBufDesc]] -// CHECK-DAG: %[[UBufSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[UBufDesc]] -// CHECK-DAG: %[[TexSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[TexDesc]] -// CHECK-DAG: %[[SamplerSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SamplerDesc]] - -// Each heap array is decorated ArrayStrideIdEXT with its element's sizeof. -// CHECK-DAG: OpDecorateId %[[SBBufArray]] ArrayStrideIdEXT %[[SBBufSize]] -// CHECK-DAG: OpDecorateId %[[UBufArray]] ArrayStrideIdEXT %[[UBufSize]] -// CHECK-DAG: OpDecorateId %[[TexArray]] ArrayStrideIdEXT %[[TexSize]] -// CHECK-DAG: OpDecorateId %[[SamplerArray]] ArrayStrideIdEXT %[[SamplerSize]] - -// No literal default stride decoration on any heap array. -// CHECK-NOT: OpDecorate %{{.*}} ArrayStride 64 -// CHECK-NOT: OpDecorate %{{.*}} ArrayStride 32 - -// Dedup: StructuredBuffer and ByteAddressBuffer below both lower to the same -// OpTypeBufferEXT StorageBuffer descriptor, so its OpConstantSizeOfEXT must be -// emitted exactly ONCE and shared, not one per heap access. +// Resource stride = max(image_size, buffer_size); sampler stride = sampler_size. +// CHECK-DAG: %[[ImgSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[TexDesc]] +// CHECK-DAG: %[[BufSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[UBufDesc]] +// CHECK-DAG: %[[ImgBigger:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[ImgSize]] %[[BufSize]] +// CHECK-DAG: %[[ResSize:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[ImgBigger]] %[[ImgSize]] %[[BufSize]] +// CHECK-DAG: %[[SampSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SamplerDesc]] -// Separate prefix: annotations precede the constants section in SPIR-V, so a -// trailing ordered check in the main pass would search past the sizeof. -// {{$}} anchors the match: "StorageBuffer" is a prefix of "StorageBuffer_0" -// (the Uniform descriptor name), so without it DEDUP-NOT would false-fire. +// Every resource runtime array shares the one resource stride; the sampler +// array uses the sampler size. +// CHECK-DAG: OpDecorateId %[[SBBufArray]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[UBufArray]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[TexArray]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[SamplerArray]] ArrayStrideIdEXT %[[SampSize]] -// DEDUP: %[[SBDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT StorageBuffer -// DEDUP-COUNT-1: OpConstantSizeOfEXT %uint %[[SBDesc]]{{$}} -// DEDUP-NOT: OpConstantSizeOfEXT %uint %[[SBDesc]]{{$}} +// Three distinct descriptor heap sizes exist: +// the image/Texture2D type (%[[TexDesc]]), the Uniform buffer, and the sampler. +// Regression test: make sure of no per-element nor per acces behavior exists +// otherwise StorageBuffer would add a fourth, pushing the count above three. +// COUNT-COUNT-3: OpConstantSizeOfEXT %uint +// COUNT-NOT: OpConstantSizeOfEXT %uint struct Constants { uint value; @@ -66,10 +70,12 @@ RWStructuredBuffer output : register(u0); void main(uint3 tid : SV_DispatchThreadID) { StructuredBuffer sbuf = ResourceDescriptorHeap[0]; ByteAddressBuffer babuf = ResourceDescriptorHeap[1]; - ConstantBuffer cbuf = ResourceDescriptorHeap[2]; - Texture2D tex = ResourceDescriptorHeap[3]; + Texture2D tex = ResourceDescriptorHeap[2]; + ConstantBuffer cbuf = ResourceDescriptorHeap[3]; + ConstantBuffer cbuf2 = ResourceDescriptorHeap[3]; + SamplerState samp = SamplerDescriptorHeap[0]; float4 color = tex.SampleLevel(samp, float2(0, 0), 0); - output[tid.x] = color + sbuf.Load(tid.x) + babuf.Load(tid.x * 4) + cbuf.value; + output[tid.x] = color + sbuf.Load(tid.x) + babuf.Load(tid.x * 4) + cbuf.value + cbuf2.value; } diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl index 37d45993a7..c44da5b066 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.rwtexture-dims.hlsl @@ -19,15 +19,12 @@ // CHECK-DAG: %[[RA_RW2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW2DArrType]]{{$}} // CHECK-DAG: %[[RA_RW3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RW3DType]]{{$}} -// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. -// CHECK-DAG: %[[RW1DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW1DType]] -// CHECK-DAG: %[[RW1DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW1DArrType]] -// CHECK-DAG: %[[RW2DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW2DArrType]] -// CHECK-DAG: %[[RW3DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RW3DType]] -// CHECK-DAG: OpDecorateId %[[RA_RW1D]] ArrayStrideIdEXT %[[RW1DSize]] -// CHECK-DAG: OpDecorateId %[[RA_RW1DArr]] ArrayStrideIdEXT %[[RW1DArrSize]] -// CHECK-DAG: OpDecorateId %[[RA_RW2DArr]] ArrayStrideIdEXT %[[RW2DArrSize]] -// CHECK-DAG: OpDecorateId %[[RA_RW3D]] ArrayStrideIdEXT %[[RW3DSize]] +// All RWTextures are resource descriptors, so every array shares one resource stride +// (derivation covered by sm6_6.descriptorheap.ext.array-stride.hlsl) +// CHECK-DAG: OpDecorateId %[[RA_RW1D]] ArrayStrideIdEXT %[[ResSize:[a-zA-Z0-9_]+]] +// CHECK-DAG: OpDecorateId %[[RA_RW1DArr]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[RA_RW2DArr]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[RA_RW3D]] ArrayStrideIdEXT %[[ResSize]] // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl index 274bcd11bc..c489f08d0c 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-dims.hlsl @@ -19,15 +19,12 @@ // CHECK-DAG: %[[RA_Tex2DArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex2DArrType]]{{$}} // CHECK-DAG: %[[RA_Tex3D:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Tex3DType]]{{$}} -// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. -// CHECK-DAG: %[[Tex1DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex1DType]] -// CHECK-DAG: %[[Tex1DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex1DArrType]] -// CHECK-DAG: %[[Tex2DArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex2DArrType]] -// CHECK-DAG: %[[Tex3DSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Tex3DType]] -// CHECK-DAG: OpDecorateId %[[RA_Tex1D]] ArrayStrideIdEXT %[[Tex1DSize]] -// CHECK-DAG: OpDecorateId %[[RA_Tex1DArr]] ArrayStrideIdEXT %[[Tex1DArrSize]] -// CHECK-DAG: OpDecorateId %[[RA_Tex2DArr]] ArrayStrideIdEXT %[[Tex2DArrSize]] -// CHECK-DAG: OpDecorateId %[[RA_Tex3D]] ArrayStrideIdEXT %[[Tex3DSize]] +// All textures are resource descriptors, so every array shares one resource stride +// (derivation covered by sm6_6.descriptorheap.ext.array-stride.hlsl) +// CHECK-DAG: OpDecorateId %[[RA_Tex1D]] ArrayStrideIdEXT %[[ResSize:[a-zA-Z0-9_]+]] +// CHECK-DAG: OpDecorateId %[[RA_Tex1DArr]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[RA_Tex2DArr]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[RA_Tex3D]] ArrayStrideIdEXT %[[ResSize]] // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl index cd7e2db43f..d99312409f 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture-ms.hlsl @@ -10,11 +10,10 @@ // CHECK-DAG: %[[RA_MS:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSType]]{{$}} // CHECK-DAG: %[[RA_MSArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[MSArrType]]{{$}} -// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. -// CHECK-DAG: %[[MSSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[MSType]] -// CHECK-DAG: %[[MSArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[MSArrType]] -// CHECK-DAG: OpDecorateId %[[RA_MS]] ArrayStrideIdEXT %[[MSSize]] -// CHECK-DAG: OpDecorateId %[[RA_MSArr]] ArrayStrideIdEXT %[[MSArrSize]] +// Both multisampled textures are resource descriptors, so they share one resource stride +// (derivation covered by sm6_6.descriptorheap.ext.array-stride.hlsl) +// CHECK-DAG: OpDecorateId %[[RA_MS]] ArrayStrideIdEXT %[[ResSize:[a-zA-Z0-9_]+]] +// CHECK-DAG: OpDecorateId %[[RA_MSArr]] ArrayStrideIdEXT %[[ResSize]] // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl index 84c15a2869..e83e13be26 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texture.hlsl @@ -14,11 +14,10 @@ // CHECK-DAG: %[[RA_BufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[BufferType]]{{$}} // CHECK-DAG: %[[RA_RWBufferType:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWBufferType]]{{$}} -// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. -// CHECK-DAG: %[[BufferSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[BufferType]] -// CHECK-DAG: %[[RWBufferSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWBufferType]] -// CHECK-DAG: OpDecorateId %[[RA_BufferType]] ArrayStrideIdEXT %[[BufferSize]] -// CHECK-DAG: OpDecorateId %[[RA_RWBufferType]] ArrayStrideIdEXT %[[RWBufferSize]] +// Buffer and RWBuffer are both resource descriptors, so both arrays share one resource stride +// (derivation covered by sm6_6.descriptorheap.ext.array-stride.hlsl) +// CHECK-DAG: OpDecorateId %[[RA_BufferType]] ArrayStrideIdEXT %[[ResSize:[a-zA-Z0-9_]+]] +// CHECK-DAG: OpDecorateId %[[RA_RWBufferType]] ArrayStrideIdEXT %[[ResSize]] // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtrType]] UniformConstant diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl index 755b4adefb..19c168ff5f 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.texturecube.hlsl @@ -13,13 +13,11 @@ // CHECK-DAG: %[[RA_CubeArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[CubeArrType]]{{$}} // CHECK-DAG: %[[RA_Sampler:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerType]]{{$}} -// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. -// CHECK-DAG: %[[CubeSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[CubeType]] -// CHECK-DAG: %[[CubeArrSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[CubeArrType]] -// CHECK-DAG: %[[SamplerSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SamplerType]] -// CHECK-DAG: OpDecorateId %[[RA_Cube]] ArrayStrideIdEXT %[[CubeSize]] -// CHECK-DAG: OpDecorateId %[[RA_CubeArr]] ArrayStrideIdEXT %[[CubeArrSize]] -// CHECK-DAG: OpDecorateId %[[RA_Sampler]] ArrayStrideIdEXT %[[SamplerSize]] +// Both cube arrays are resource descriptors and share one resource stride; the sampler array uses the separate sampler stride +// (derivation covered by sm6_6.descriptorheap.ext.array-stride.hlsl) +// CHECK-DAG: OpDecorateId %[[RA_Cube]] ArrayStrideIdEXT %[[ResSize:[a-zA-Z0-9_]+]] +// CHECK-DAG: OpDecorateId %[[RA_CubeArr]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[RA_Sampler]] ArrayStrideIdEXT %[[SampSize:[a-zA-Z0-9_]+]] // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant // CHECK: %[[SamplerHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl index d001691ced..67bad07c47 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.typed-formats.hlsl @@ -23,15 +23,12 @@ // CHECK-DAG: %[[RWTexIType:[a-zA-Z0-9_]+]] = OpTypeImage %int 2D 2 0 0 2 R32i // CHECK-DAG: %[[RA_RWTexI:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexIType]]{{$}} -// Default heap stride: OpConstantSizeOfEXT + ArrayStrideIdEXT per element type. -// CHECK-DAG: %[[TexUintSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[TexUintType]] -// CHECK-DAG: %[[RWTexF2Size:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWTexF2Type]] -// CHECK-DAG: %[[RWTexU2Size:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWTexU2Type]] -// CHECK-DAG: %[[RWTexISize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[RWTexIType]] -// CHECK-DAG: OpDecorateId %[[RA_TexUint]] ArrayStrideIdEXT %[[TexUintSize]] -// CHECK-DAG: OpDecorateId %[[RA_RWTexF2]] ArrayStrideIdEXT %[[RWTexF2Size]] -// CHECK-DAG: OpDecorateId %[[RA_RWTexU2]] ArrayStrideIdEXT %[[RWTexU2Size]] -// CHECK-DAG: OpDecorateId %[[RA_RWTexI]] ArrayStrideIdEXT %[[RWTexISize]] +// All of these are resource descriptors, so every array shares one resource stride +// (derivation covered by sm6_6.descriptorheap.ext.array-stride.hlsl) +// CHECK-DAG: OpDecorateId %[[RA_TexUint]] ArrayStrideIdEXT %[[ResSize:[a-zA-Z0-9_]+]] +// CHECK-DAG: OpDecorateId %[[RA_RWTexF2]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[RA_RWTexU2]] ArrayStrideIdEXT %[[ResSize]] +// CHECK-DAG: OpDecorateId %[[RA_RWTexI]] ArrayStrideIdEXT %[[ResSize]] // CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedPtr]] UniformConstant From 25c5a0a337915bea31721a95a8efeedd5f4a0a3b Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Wed, 15 Jul 2026 11:33:44 -0700 Subject: [PATCH 05/26] Fixed correct StorageClass for descriptor heap buffer alias pointers TODO --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 50 ++++++++++++-------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 25989a12a6..41cb93331c 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -5278,36 +5278,30 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( spvContext.getUntypedPointerKHRType(spv::StorageClass::UniformConstant); LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, spvBuilder); - const SpirvType *bufferDataType = lowerTypeVisitor.lowerType( - resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - - const SpirvPointerType *bufferDataPointerType = nullptr; - SpirvLayoutRule layoutRule = spirvOptions.sBufferLayoutRule; - if (isConstantTextureBuffer(resourceType)) { - layoutRule = isConstantBuffer(resourceType) - ? spirvOptions.cBufferLayoutRule - : spirvOptions.tBufferLayoutRule; - bufferDataPointerType = spvContext.getPointerType( - bufferDataType, getDescriptorHeapBufferStorageClass(resourceType)); - } else { - bufferDataPointerType = dyn_cast(bufferDataType); - } - if (!bufferDataPointerType) { - emitError("descriptor heap buffer type lowering failed", - expr->getExprLoc()); - return nullptr; - } + // Select storage class and concrete layout rule for this buffer kind. + // ConstantBuffer -> Uniform (UBO); all others -> StorageBuffer (SSBO). + // Passing the concrete layout rule (not Void) causes lowerType to return + // the bare struct/buffer type rather than a Uniform alias pointer, so we + // can wrap it with the correct storage class here without touching + // LowerTypeVisitor or relying on RemoveBufferBlockVisitor to fix it later. + const spv::StorageClass bufferSC = + getDescriptorHeapBufferStorageClass(resourceType); + SpirvLayoutRule layoutRule; + if (isConstantBuffer(resourceType)) + layoutRule = spirvOptions.cBufferLayoutRule; + else if (isTextureBuffer(resourceType)) + layoutRule = spirvOptions.tBufferLayoutRule; + else + layoutRule = spirvOptions.sBufferLayoutRule; + + const SpirvType *bufferDataType = lowerTypeVisitor.lowerType( + resourceType, layoutRule, llvm::None, baseExpr->getExprLoc()); + const SpirvPointerType *bufferDataPointerType = + spvContext.getPointerType(bufferDataType, bufferSC); - // ConstantBuffer -> Uniform (UBO); all others -> StorageBuffer (SSBO) - // TODO: Remove this manual override once LowerTypeVisitor returns the - // correct StorageClass for descriptor-heap alias pointer types - // (currently it returns Uniform for all of them). - const spv::StorageClass bufferExtSC = isConstantBuffer(resourceType) - ? spv::StorageClass::Uniform - : spv::StorageClass::StorageBuffer; const BufferEXTType *bufferDescriptorType = - spvContext.getBufferEXTType(bufferExtSC); + spvContext.getBufferEXTType(bufferSC); const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType(bufferDescriptorType); SpirvUntypedAccessChainKHR *untypedAccessChainPtr = @@ -5317,7 +5311,7 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( SpirvUnaryOp *bufferDataPtr = spvBuilder.createUnaryOp( spv::Op::OpBufferPointerEXT, bufferDataPointerType, untypedAccessChainPtr, baseExpr->getExprLoc()); - bufferDataPtr->setStorageClass(bufferDataPointerType->getStorageClass()); + bufferDataPtr->setStorageClass(bufferSC); bufferDataPtr->setLayoutRule(layoutRule); bufferDataPtr->setRValue(false); if (isRasterizerOrderedView(resourceType)) { From 88bd33cf620dd395330e87c01268452aaddd5bc4 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Wed, 22 Jul 2026 15:50:05 -0700 Subject: [PATCH 06/26] Ammend keyword usage to follow LLVM Coding Standards --- tools/clang/lib/SPIRV/EmitVisitor.cpp | 4 ++-- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tools/clang/lib/SPIRV/EmitVisitor.cpp b/tools/clang/lib/SPIRV/EmitVisitor.cpp index 6cf8450f34..aebe48180a 100644 --- a/tools/clang/lib/SPIRV/EmitVisitor.cpp +++ b/tools/clang/lib/SPIRV/EmitVisitor.cpp @@ -2732,13 +2732,13 @@ uint32_t EmitTypeHandler::emitType(const SpirvType *type) { curTypeInst.push_back(elemTypeId); finalizeTypeInstruction(); - if (auto *strideId = raType->getArrayStrideId()) { + if (SpirvInstruction *strideId = raType->getArrayStrideId()) { // SPV_EXT_descriptor_heap: stride given by a constant rather than a // literal, emitted as OpDecorateId ... ArrayStrideIdEXT . emitDecoration(id, spv::Decoration::ArrayStrideIdEXT, {getOrAssignResultId(strideId)}, llvm::None, /*usesIdParams=*/true); - } else if (auto stride = raType->getStride()) { + } else if (llvm::Optional stride = raType->getStride()) { emitDecoration(id, spv::Decoration::ArrayStride, {stride.getValue()}); } } diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 41cb93331c..6354feb35f 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -3189,7 +3189,7 @@ SpirvInstruction *SpirvEmitter::doBinaryOperator(const BinaryOperator *expr) { tryToAssignToDescriptorHeapBuffer(expr)) return aliasResult.getValue(); - auto *rhs = loadIfGLValue(expr->getRHS()); + SpirvInstruction *rhs = loadIfGLValue(expr->getRHS()); tryToAssignDescriptorHeapImageAlias(expr->getLHS(), expr->getRHS()); return processAssignment(expr->getLHS(), rhs, @@ -5096,7 +5096,7 @@ SpirvEmitter::processStructuredBufferLoad(const CXXMemberCallExpr *expr) { auto *zero = spvBuilder.getConstantInt(astContext.IntTy, llvm::APInt(32, 0)); auto *index = doExpr(expr->getArg(0)); - auto *result = + SpirvInstruction *result = derefOrCreatePointerToValue(buffer->getType(), info, structType, {zero, index}, buffer->getExprLoc(), range); @@ -5122,7 +5122,7 @@ void SpirvEmitter::markDescriptorHeapCounterUnsupported( } bool SpirvEmitter::isDescriptorHeapCounterUnsupported(const Expr *expr) const { - if (const auto *decl = getReferencedDef(expr)) + if (const DeclaratorDecl *decl = getReferencedDef(expr)) return descriptorHeapUnsupportedCounters.count(decl) != 0; return false; } @@ -5166,7 +5166,7 @@ bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias( (!isRWTexture(dstVar->getType()) && !isRWBuffer(dstVar->getType()))) return false; - const auto *src = srcExpr->IgnoreParenCasts(); + const Expr *src = srcExpr->IgnoreParenCasts(); auto found = descriptorHeapImageAccesses.find(src); if (found == descriptorHeapImageAccesses.end()) return false; @@ -5198,7 +5198,7 @@ bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias( isAKindOfStructuredOrByteBuffer(dstVar->getType()))) return false; - const auto *src = srcExpr->IgnoreParenCasts(); + const Expr *src = srcExpr->IgnoreParenCasts(); auto found = descriptorHeapBufferAccesses.find(src); if (found == descriptorHeapBufferAccesses.end()) return false; @@ -5523,7 +5523,7 @@ SpirvEmitter::getFinalACSBufferCounterInstruction(const Expr *expr) { const CounterIdAliasPair * SpirvEmitter::getFinalACSBufferCounter(const Expr *expr) { // AssocCounter#1: referencing some stand-alone variable - if (const auto *decl = getReferencedDef(expr)) + if (const DeclaratorDecl *decl = getReferencedDef(expr)) return declIdMapper.getOrCreateCounterIdAliasPair(decl); const Expr *expr_withoutcasts = expr->IgnoreParenCasts(); @@ -6013,13 +6013,15 @@ SpirvEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr, retVal = processTextureLevelOfDetail(expr, /* unclamped */ true); break; case IntrinsicOp::MOP_IncrementCounter: - if (auto *counter = incDecRWACSBufferCounter(expr, /*isInc*/ true)) + if (SpirvInstruction *counter = + incDecRWACSBufferCounter(expr, /*isInc*/ true)) retVal = spvBuilder.createUnaryOp( spv::Op::OpBitcast, astContext.UnsignedIntTy, counter, expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); break; case IntrinsicOp::MOP_DecrementCounter: - if (auto *counter = incDecRWACSBufferCounter(expr, /*isInc*/ false)) + if (SpirvInstruction *counter = + incDecRWACSBufferCounter(expr, /*isInc*/ false)) retVal = spvBuilder.createUnaryOp( spv::Op::OpBitcast, astContext.UnsignedIntTy, counter, expr->getCallee()->getExprLoc(), expr->getCallee()->getSourceRange()); @@ -10966,7 +10968,7 @@ SpirvEmitter::processIntrinsicInterlockedMethod(const CallExpr *expr, } if (!ptr) { - auto *baseInstr = doExpr(base); + SpirvInstruction *baseInstr = doExpr(base); if (baseInstr->isRValue()) { // OpImageTexelPointer's Image argument must have a type of // OpTypePointer with Type OpTypeImage. Need to create a temporary From 1007865ebdf27881c6ce852e54334bd43a31d27f Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 30 Jul 2026 14:30:00 -0700 Subject: [PATCH 07/26] fix stride test --- ...sm6_6.descriptorheap.ext.array-stride.hlsl | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl index 64d4a94899..e690564fb8 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.hlsl @@ -11,6 +11,11 @@ // VkPhysicalDeviceDescriptorHeapPropertiesEXT (imageDescriptorSize / // bufferDescriptorSize); textures lower to OpTypeImage, so image/buffer // covers all relevant HLSL resource kinds. +// +// NOTE: This covers the case with no RaytracingAccelerationStructures. +// When present, the stride formula expands to a three-way max: +// max(max(sizeof(image), sizeof(buffer)), sizeof(accelerationStructure)). +// // Both sizes are driver defined and known only at pipeline creation time, so // the maximum is computed with OpSpecConstantOp over two OpConstantSizeOfEXT // placeholders. @@ -27,11 +32,16 @@ // CHECK-DAG: %[[UBufDesc:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform // CHECK-DAG: %[[SamplerDesc:[a-zA-Z0-9_]+]] = OpTypeSampler -// Image descriptor type. Texture2D lowers to this same type -// (Dim2D, depth=0, not-arrayed, non-MS, sampled, Unknown format), so -// %[[TexDesc]] serves as both the placeholder for the size calculation and -// the real descriptor type for the Texture2D access below. -// CHECK-DAG: %[[TexDesc:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 0 0 0 1 Unknown +// Two distinct OpTypeImage types appear in the output: +// +// 1) ImgPlaceholder: a canonical sampled 2D float image used only as +// the operand to OpConstantSizeOfEXT. +// +// 2) TexDesc: the actual lowered type for Texture2D. +// This is the element type of the texture heap runtime array. +// +// CHECK-DAG: %[[ImgPlaceholder:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 0 0 0 1 Unknown +// CHECK-DAG: %[[TexDesc:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown // Heap runtime arrays of the accessed element types. // CHECK-DAG: %[[SBBufArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SBBufDesc]] @@ -40,22 +50,23 @@ // CHECK-DAG: %[[SamplerArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SamplerDesc]] // Resource stride = max(image_size, buffer_size); sampler stride = sampler_size. -// CHECK-DAG: %[[ImgSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[TexDesc]] +// The size query uses ImgPlaceholder (depth=0); the driver returns the same +// imageDescriptorSize regardless of which image subtype is used as the operand. +// CHECK-DAG: %[[ImgSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[ImgPlaceholder]] // CHECK-DAG: %[[BufSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[UBufDesc]] // CHECK-DAG: %[[ImgBigger:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[ImgSize]] %[[BufSize]] // CHECK-DAG: %[[ResSize:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[ImgBigger]] %[[ImgSize]] %[[BufSize]] // CHECK-DAG: %[[SampSize:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[SamplerDesc]] -// Every resource runtime array shares the one resource stride; the sampler -// array uses the sampler size. +// Every resource runtime array shares the one resource stride. +// The sampler array just uses the sampler size as it's stride. // CHECK-DAG: OpDecorateId %[[SBBufArray]] ArrayStrideIdEXT %[[ResSize]] // CHECK-DAG: OpDecorateId %[[UBufArray]] ArrayStrideIdEXT %[[ResSize]] // CHECK-DAG: OpDecorateId %[[TexArray]] ArrayStrideIdEXT %[[ResSize]] // CHECK-DAG: OpDecorateId %[[SamplerArray]] ArrayStrideIdEXT %[[SampSize]] -// Three distinct descriptor heap sizes exist: -// the image/Texture2D type (%[[TexDesc]]), the Uniform buffer, and the sampler. -// Regression test: make sure of no per-element nor per acces behavior exists +// Three distinct OpConstantSizeOfEXT calls exist: ImgPlaceholder, Uniform +// buffer, and sampler. Regression test: no per-element or per-access behavior; // otherwise StorageBuffer would add a fourth, pushing the count above three. // COUNT-COUNT-3: OpConstantSizeOfEXT %uint // COUNT-NOT: OpConstantSizeOfEXT %uint From f502bcf27149a0b875cd79f710bdf9fddda6705e Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Fri, 31 Jul 2026 14:31:10 -0700 Subject: [PATCH 08/26] Cover arrayStrideId uniquing in SpirvContextTest --- .../unittests/SPIRV/SpirvContextTest.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tools/clang/unittests/SPIRV/SpirvContextTest.cpp b/tools/clang/unittests/SPIRV/SpirvContextTest.cpp index 08d33e4892..5cc9bb6b74 100644 --- a/tools/clang/unittests/SPIRV/SpirvContextTest.cpp +++ b/tools/clang/unittests/SPIRV/SpirvContextTest.cpp @@ -329,6 +329,34 @@ TEST_F(SpirvContextTest, RuntimeArrayTypeUnique3) { spvContext.getRuntimeArrayType(int32, 32)); } +TEST_F(SpirvContextTest, RuntimeArrayTypeUnique4) { + // Uniquing behaviour for the arrayStrideId (SpirvInstruction*) overload. + SpirvContext &spvContext = getSpirvContext(); + const auto *int32 = spvContext.getSIntType(32); + + // Two distinct pointer values stand in for two distinct SpirvInstruction + // operands. getRuntimeArrayType compares them by pointer identity and never + // dereferences them during the lookup. + auto *instr1 = + reinterpret_cast(static_cast(1)); + auto *instr2 = + reinterpret_cast(static_cast(2)); + + // 1. Different arrayStrideId values -> different RuntimeArrayType pointers. + EXPECT_NE(spvContext.getRuntimeArrayType(int32, llvm::None, instr1), + spvContext.getRuntimeArrayType(int32, llvm::None, instr2)); + + // 2. Same arrayStrideId value -> same RuntimeArrayType pointer (uniqued). + EXPECT_EQ(spvContext.getRuntimeArrayType(int32, llvm::None, instr1), + spvContext.getRuntimeArrayType(int32, llvm::None, instr1)); + + // 3. Literal-stride type != id-stride type even when the numeric values + // could be considered equivalent (arrayStride=64, no id) vs + // (arrayStride=None, instr1 as id). + EXPECT_NE(spvContext.getRuntimeArrayType(int32, 64), + spvContext.getRuntimeArrayType(int32, llvm::None, instr1)); +} + TEST_F(SpirvContextTest, PointerTypeUnique1) { SpirvContext &spvContext = getSpirvContext(); const auto *int32 = spvContext.getSIntType(32); From 034f3c5c7083efa82f565a93d582a52ac4ee9fea Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Fri, 31 Jul 2026 15:27:24 -0700 Subject: [PATCH 09/26] Diagnose mixed bound/heap aliasing and correct documentation --- docs/SPIR-V.rst | 58 +++++++-- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 116 ++++++++++++++++-- tools/clang/lib/SPIRV/SpirvEmitter.h | 47 ++++++- ....descriptorheap.ext.mixed-alias.error.hlsl | 92 ++++++++++++++ 4 files changed, 289 insertions(+), 24 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 042dbfd416..9d04b7e6f8 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2101,14 +2101,16 @@ objects as untyped variables in ``UniformConstant`` storage class: The concrete descriptor type is selected at each heap access. For image, sampler, and texel buffer resources, DXC forms a runtime array of that -descriptor type and decorates the array with a byte ``ArrayStride``, then uses -``OpUntypedAccessChainKHR`` followed by ``OpLoad``: +descriptor type, decorates the array with a byte stride, and uses +``OpUntypedAccessChainKHR`` followed by ``OpLoad``. The stride is an +``ArrayStrideIdEXT`` decoration referencing a specialization constant rather +than a literal ``ArrayStride`` (see `Descriptor heap array stride`_ below): .. code:: spirv %image_type = OpTypeImage %float 2D 2 0 0 1 Unknown %image_array = OpTypeRuntimeArray %image_type - OpDecorate %image_array ArrayStride 64 + OpDecorateId %image_array ArrayStrideIdEXT %resource_stride %descriptor = OpUntypedAccessChainKHR %uptr_uc %image_array %resource_heap %index %image = OpLoad %image_type %descriptor @@ -2122,7 +2124,7 @@ example, ``ConstantBuffer`` uses ``Uniform`` and ``TextureBuffer`` uses %buffer_type = OpTypeBufferEXT Uniform %buffer_array = OpTypeRuntimeArray %buffer_type - OpDecorate %buffer_array ArrayStride 64 + OpDecorateId %buffer_array ArrayStrideIdEXT %resource_stride %descriptor = OpUntypedAccessChainKHR %uptr_uc %buffer_array %resource_heap %index %buffer_ptr = OpBufferPointerEXT %_ptr_Uniform_type_BufferData %descriptor @@ -2146,9 +2148,11 @@ StructuredBuffer/RWStructuredBuffer without associated counter operations, ByteAddressBuffer/RWByteAddressBuffer, ConstantBuffer, and TextureBuffer heap loads, including direct field and array-element accesses for ``ConstantBuffer`` and ``TextureBuffer``. ``NonUniformResourceIndex`` is -accepted but the ``NonUniform`` decoration is not emitted on -``OpUntypedAccessChainKHR`` or the loaded value; ``SPV_EXT_descriptor_heap`` -deprecates the ``NonUniform`` decoration for heap accesses. +accepted, but no ``NonUniform`` decoration is emitted on the +``OpUntypedAccessChainKHR`` result or on the loaded descriptor; +``SPV_EXT_descriptor_heap`` deprecates the decoration for heap accesses and +drivers handle divergent heap indices natively. The index operand itself may +still carry ``NonUniform`` from the surrounding expression. Append/consume structured buffers and UAV counter heap lowering are not supported by the native descriptor heap path yet. Those forms should continue @@ -2159,6 +2163,46 @@ associated counter operations such as ``IncrementCounter`` and ``DecrementCounter`` emit a diagnostic because the native descriptor heap path does not recover an associated counter descriptor. +A local resource variable initialized from a heap access is resolved entirely at +compile time: the variable is recorded as an alias for the heap index, and every +later use is re-lowered as a fresh access chain rather than as a load of a stored +descriptor handle. This is sound only when the variable holds a heap descriptor +on every path that reaches the use. DXC therefore rejects a variable that holds +both a bound resource and a heap descriptor, whether through a conditional +assignment, a reassignment back to a bound resource, or an assignment inside a +loop:: + + error: mixing bound and descriptor heap resources in the same variable is not + supported with SPV_EXT_descriptor_heap + +Supporting these forms requires modelling the alias as a value with real +control-flow merges instead of as compile-time state. + +Two further restrictions on the heap access expression itself produce +diagnostics. The object being subscripted must be a direct reference to the +builtin ``ResourceDescriptorHeap`` or ``SamplerDescriptorHeap`` variable, and +the subscript result must be immediately converted to a concrete resource type +so that DXC can select a descriptor type for the access. A subscript whose +result is discarded, or used in a context that supplies no target resource +type, is rejected. + +Descriptor heap array stride +++++++++++++++++++++++++++++ + +All resource heap runtime arrays share a single ``ArrayStrideIdEXT`` decoration +rather than a literal ``ArrayStride``, because descriptor sizes are not known +until pipeline creation. The shared value is built from ``OpConstantSizeOfEXT`` +and ``OpSpecConstantOp`` and evaluates to +``max(sizeof(image_descriptor), sizeof(buffer_descriptor))``. The sampler heap +carries its own ``ArrayStrideIdEXT`` equal to ``sizeof(sampler_descriptor)``. + +The ``OpConstantSizeOfEXT`` operands are placeholder types chosen only for their +descriptor class. All image types report the same descriptor size, so the +placeholder is a plain sampled 2D float image and bears no relation to the image +types the shader actually uses; a module will normally contain both the +placeholder type and the distinct image types its heap accesses lower to. The +stride value is built once and cached on first use. + HLSL Expressions ================ diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 6354feb35f..58229ad399 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2258,6 +2258,7 @@ void SpirvEmitter::doVarDecl(const VarDecl *decl) { spvBuilder.createStore(var, constInit, loc, range); } else { storeValue(var, loadIfGLValue(init), decl->getType(), loc, range); + diagnoseDescriptorHeapAliasMixing(decl, init, loc); tryToAssignDescriptorHeapImageAlias(decl, init); } @@ -3176,6 +3177,27 @@ SpirvEmitter::tryToAssignToDescriptorHeapBuffer( return emitDescriptorHeapBufferPointer(decl, assignExpr->getExprLoc()); } +llvm::Optional +SpirvEmitter::tryToAssignToDescriptorHeapAlias( + const BinaryOperator *assignExpr) { + if (!spirvOptions.useDescriptorHeap) + return llvm::None; + + const auto *dstVar = + dyn_cast_or_null(getReferencedDef(assignExpr->getLHS())); + if (!diagnoseDescriptorHeapAliasMixing(dstVar, assignExpr->getRHS(), + assignExpr->getExprLoc())) + return tryToAssignToDescriptorHeapBuffer(assignExpr); + + // The assignment was rejected. A heap buffer alias is represented only by its + // index variable, so the normal path has no destination to store into and + // would null-deref; consume the assignment instead. Image aliases do have a + // function variable, so they can fall through to the plain handle store. + if (descriptorHeapBufferAliasVars.count(dstVar)) + return static_cast(nullptr); + return llvm::None; +} + SpirvInstruction *SpirvEmitter::doBinaryOperator(const BinaryOperator *expr) { const auto opcode = expr->getOpcode(); @@ -3186,7 +3208,7 @@ SpirvInstruction *SpirvEmitter::doBinaryOperator(const BinaryOperator *expr) { tryToAssignCounterVar(expr->getLHS(), expr->getRHS()); if (llvm::Optional aliasResult = - tryToAssignToDescriptorHeapBuffer(expr)) + tryToAssignToDescriptorHeapAlias(expr)) return aliasResult.getValue(); SpirvInstruction *rhs = loadIfGLValue(expr->getRHS()); @@ -5117,13 +5139,19 @@ SpirvEmitter::processStructuredBufferLoad(const CXXMemberCallExpr *expr) { void SpirvEmitter::markDescriptorHeapCounterUnsupported( const DeclaratorDecl *decl) { - if (decl) - descriptorHeapUnsupportedCounters.insert(decl); + if (const auto *var = dyn_cast_or_null(decl)) { + auto it = descriptorHeapBufferAliasVars.find(var); + if (it != descriptorHeapBufferAliasVars.end()) + it->second.counterUnsupported = true; + } } bool SpirvEmitter::isDescriptorHeapCounterUnsupported(const Expr *expr) const { - if (const DeclaratorDecl *decl = getReferencedDef(expr)) - return descriptorHeapUnsupportedCounters.count(decl) != 0; + if (const auto *var = dyn_cast_or_null(getReferencedDef(expr))) { + auto it = descriptorHeapBufferAliasVars.find(var); + if (it != descriptorHeapBufferAliasVars.end()) + return it->second.counterUnsupported; + } return false; } @@ -5156,6 +5184,52 @@ SpirvEmitter::createDescriptorHeapIndexVar(const VarDecl *dstVar) { name); } +bool SpirvEmitter::diagnoseDescriptorHeapAliasMixing(const VarDecl *dstVar, + const Expr *srcExpr, + SourceLocation loc) { + if (!spirvOptions.useDescriptorHeap || !dstVar || !srcExpr) + return false; + + // Report once per variable; every later assignment stays rejected. + auto stateIt = descriptorHeapVarState.find(dstVar); + if (stateIt != descriptorHeapVarState.end() && + stateIt->second == DescriptorHeapVarState::Mixed) + return true; + + // Only the resource kinds that use the compile-time alias mechanism can be + // miscompiled by a mixed assignment. Other resources are stored into a real + // function variable, which merges correctly across control flow. + const QualType dstType = dstVar->getType(); + if (!isRWTexture(dstType) && !isRWBuffer(dstType) && + !isConstantTextureBuffer(dstType) && + !isAKindOfStructuredOrByteBuffer(dstType)) + return false; + + const bool srcIsHeap = isDescriptorHeap(srcExpr->IgnoreParenCasts()); + const bool wasHeap = descriptorHeapImageAliasVars.count(dstVar) || + descriptorHeapBufferAliasVars.count(dstVar); + const bool wasBound = stateIt != descriptorHeapVarState.end() && + stateIt->second == DescriptorHeapVarState::Bound; + const bool mixingDetected = + (srcIsHeap && wasBound) || (!srcIsHeap && wasHeap); + + if (mixingDetected) { + emitError("mixing bound and descriptor heap resources in the same variable " + "is not supported with SPV_EXT_descriptor_heap", + loc); + // Leave any recorded alias in place. For heap-initialized buffer variables, + // doVarDecl returns early before calling createFnVar, so the alias is the + // only handle they have. Erasing it would crash downstream uses; keeping it + // is safe because the emitted error already fails the compilation. + descriptorHeapVarState[dstVar] = DescriptorHeapVarState::Mixed; + return true; + } + + if (!srcIsHeap) + descriptorHeapVarState[dstVar] = DescriptorHeapVarState::Bound; + return false; +} + bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias( const DeclaratorDecl *dstDecl, const Expr *srcExpr) { if (!spirvOptions.useDescriptorHeap || !dstDecl || !srcExpr) @@ -5166,6 +5240,13 @@ bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias( (!isRWTexture(dstVar->getType()) && !isRWBuffer(dstVar->getType()))) return false; + { + auto stateIt = descriptorHeapVarState.find(dstVar); + if (stateIt != descriptorHeapVarState.end() && + stateIt->second == DescriptorHeapVarState::Mixed) + return false; + } + const Expr *src = srcExpr->IgnoreParenCasts(); auto found = descriptorHeapImageAccesses.find(src); if (found == descriptorHeapImageAccesses.end()) @@ -5198,14 +5279,18 @@ bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias( isAKindOfStructuredOrByteBuffer(dstVar->getType()))) return false; + { + auto stateIt = descriptorHeapVarState.find(dstVar); + if (stateIt != descriptorHeapVarState.end() && + stateIt->second == DescriptorHeapVarState::Mixed) + return false; + } + const Expr *src = srcExpr->IgnoreParenCasts(); auto found = descriptorHeapBufferAccesses.find(src); if (found == descriptorHeapBufferAccesses.end()) return false; - if (isRWStructuredBuffer(dstVar->getType())) - markDescriptorHeapCounterUnsupported(dstVar); - auto &alias = descriptorHeapBufferAliasVars[dstVar]; if (!alias.indexVar) alias.indexVar = createDescriptorHeapIndexVar(dstVar); @@ -5213,6 +5298,7 @@ bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias( alias.arrayType = found->second.arrayType; alias.heap = found->second.heap; alias.layoutRule = found->second.layoutRule; + alias.counterUnsupported = isRWStructuredBuffer(dstVar->getType()); storeDescriptorHeapIndex(alias.indexVar, found->second.index, found->second.indexType, srcExpr); return true; @@ -5354,13 +5440,19 @@ SpirvEmitter::incDecRWACSBufferCounter(const CXXMemberCallExpr *expr, return nullptr; } + // Only heap-loaded append/consume buffers are unsupported. Explicitly bound + // ones keep working in native heap mode, so key the diagnostic on the object + // actually having a heap alias rather than on the option alone. if (spirvOptions.useDescriptorHeap && (isAppendStructuredBuffer(object->getType()) || isConsumeStructuredBuffer(object->getType()))) { - emitError("append/consume structured buffers are not supported with " - "SPV_EXT_descriptor_heap", - expr->getCallee()->getExprLoc()); - return nullptr; + const auto *objVar = dyn_cast_or_null(getReferencedDef(object)); + if (objVar && descriptorHeapBufferAliasVars.count(objVar)) { + emitError("append/consume structured buffers are not supported with " + "SPV_EXT_descriptor_heap", + expr->getCallee()->getExprLoc()); + return nullptr; + } } auto *counter = getFinalACSBufferCounterInstruction(object); diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 9f206fcb17..ddc3caa18f 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -1224,7 +1224,8 @@ class SpirvEmitter : public ASTConsumer { /// \brief Returns true if counter operations on the resource expression are /// known to be unsupported because the resource came from - /// ResourceDescriptorHeap. + /// ResourceDescriptorHeap. Consults the counterUnsupported field of the + /// buffer alias entry for the referenced variable. bool isDescriptorHeapCounterUnsupported(const Expr *expr) const; /// \brief Records the descriptor heap index assigned to a local image @@ -1241,6 +1242,24 @@ class SpirvEmitter : public ASTConsumer { bool tryToAssignDescriptorHeapBufferAlias(const Expr *dstExpr, const Expr *srcExpr); + /// \brief Diagnoses a local resource variable assigned from both a bound + /// resource and ResourceDescriptorHeap. + /// + /// The alias table maps each aliased VarDecl to heap descriptor info at + /// compile time. Once a variable is recorded as an alias, every later use is + /// re-lowered as a heap access chain, ignoring control flow. That is only + /// correct when the variable holds a heap descriptor on every path reaching + /// the use, so assigning it from both kinds of source must be diagnosed + /// rather than silently miscompiled. + /// + /// Returns true if the assignment was rejected (either a new diagnostic was + /// emitted, or the variable was already diagnosed). Callers need not act on + /// the return value: the alias-recording helpers check descriptorHeapVarState + /// themselves and skip rejected variables automatically. + bool diagnoseDescriptorHeapAliasMixing(const VarDecl *dstVar, + const Expr *srcExpr, + SourceLocation loc); + /// \brief Creates the ".descriptor.index" function variable used to /// remember the descriptor heap index of a local resource alias dstVar. SpirvVariable *createDescriptorHeapIndexVar(const VarDecl *dstVar); @@ -1258,6 +1277,14 @@ class SpirvEmitter : public ASTConsumer { llvm::Optional tryToAssignToDescriptorHeapBuffer(const BinaryOperator *assignExpr); + /// \brief Entry point for the descriptor-heap handling of a simple + /// assignment: rejects a destination that mixes bound and heap resources, + /// then defers to tryToAssignToDescriptorHeapBuffer. Returns None if the + /// assignment needs no heap-specific treatment and the caller should lower it + /// as a normal assignment. + llvm::Optional + tryToAssignToDescriptorHeapAlias(const BinaryOperator *assignExpr); + /// \brief Emits the instructions that re-derive the buffer-data pointer for a /// descriptor-heap buffer alias decl (OpLoad of the saved index, then /// OpUntypedAccessChainKHR + OpBufferPointerEXT). Returns nullptr if decl @@ -1640,6 +1667,10 @@ class SpirvEmitter : public ASTConsumer { /// The SPIR-V function parameter for the current this object. SpirvInstruction *curThis; + // TODO: The following ~15 descriptorHeap* members and their methods are good + // candidates for refactoring into a dedicated DescriptorHeapAliasEmitter + // helper class. + /// Native descriptor heap image descriptors used to directly form image /// atomics. The emitter is single-use per translation unit, so these /// AST-pointer maps live for the emitter lifetime. @@ -1671,7 +1702,17 @@ class SpirvEmitter : public ASTConsumer { const SpirvType *arrayType; SpirvInstruction *heap; SpirvLayoutRule layoutRule; + bool counterUnsupported = false; }; + /// Tracks per-variable assignment history for the mixed-alias diagnostic. + /// Bound: the variable has been assigned from a bound resource; a later heap + /// assignment to it would be a diagnosable mix. + /// Mixed: mixing was already diagnosed; alias recording stays suppressed for + /// the remainder of the function so the error fires only once. + enum class DescriptorHeapVarState : uint8_t { Bound, Mixed }; + llvm::DenseMap + descriptorHeapVarState; + llvm::DenseMap descriptorHeapImageAccesses; llvm::DenseMap @@ -1681,10 +1722,6 @@ class SpirvEmitter : public ASTConsumer { llvm::DenseMap descriptorHeapBufferAliasVars; - /// RWStructuredBuffer aliases loaded from ResourceDescriptorHeap have no - /// associated UAV counter descriptor in the native descriptor heap path. - llvm::DenseSet descriptorHeapUnsupportedCounters; - /// The source location of a push constant block we have previously seen. /// Invalid means no push constant blocks defined thus far. SourceLocation seenPushConstantAt; diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl new file mode 100644 index 0000000000..ce0a3f0878 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl @@ -0,0 +1,92 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=0 %s | FileCheck %s --check-prefix=OK +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=1 %s 2>&1 | FileCheck %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=2 %s 2>&1 | FileCheck %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=3 %s 2>&1 | FileCheck %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=4 %s 2>&1 | FileCheck %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=5 %s 2>&1 | FileCheck %s + +// Verifies that a local resource variable assigned from both a bound resource +// and ResourceDescriptorHeap is rejected. +// +// The alias table maps each aliased VarDecl to heap descriptor info at compile +// time. Once a variable is recorded as an alias, every later use is re-lowered +// as a heap access chain, ignoring control flow. That is only correct when the +// variable holds a heap descriptor on every path reaching the use, so assigning +// it from both kinds of source must be diagnosed rather than silently +// miscompiled. +// +// Before this was diagnosed, CASE=1 emitted +// %index = OpSelect %uint %cond %uint_1 %undef +// OpUntypedAccessChainKHR ... %resource_heap %index +// i.e. the else path indexed the resource heap with an undefined value, and +// CASE=2 atomically updated heap descriptor 2 after the variable had been +// reassigned to `boundTex`. +// +// Both assignment orderings are detected: bound-then-heap assignments (CASE=1, +// CASE=3, CASE=5) are tracked in descriptorHeapVarState; heap-then-bound +// assignments (CASE=2, CASE=4) are detected by observing that the alias map +// already contains an entry for the variable. + +// CHECK: error: {{.*}}mixing bound and descriptor heap resources in the same variable is not supported with SPV_EXT_descriptor_heap +// OK: OpUntypedImageTexelPointerEXT + +RWByteAddressBuffer outputBytes : register(u0); +RWTexture2D boundTex : register(u1); +RWByteAddressBuffer boundBuf : register(u2); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + uint original; + +#if CASE == 0 + // Control: heap-only reassignment is legal. Both sources are heap descriptors, + // so the alias index is simply updated on each assignment and every path holds + // a heap descriptor. + RWTexture2D heapOnly = ResourceDescriptorHeap[1]; + if (tid.x == 0) + heapOnly = ResourceDescriptorHeap[2]; + InterlockedAdd(heapOnly[tid.xy], 4, original); + +#elif CASE == 1 + // Bound-then-heap (conditional): the heap descriptor is assigned on one path + // only. The else path still holds the bound resource, but the atomic would be + // lowered as a heap access with an undefined index. + RWTexture2D mixed = boundTex; + if (tid.x == 0) + mixed = ResourceDescriptorHeap[1]; + InterlockedAdd(mixed[tid.xy], 1, original); + +#elif CASE == 2 + // Heap-then-bound: the alias would never be cleared, so the atomic would keep + // targeting heap descriptor 2 even after the variable was reassigned to the + // bound texture. + RWTexture2D mixed = ResourceDescriptorHeap[2]; + mixed = boundTex; + InterlockedAdd(mixed[tid.xy], 2, original); + +#elif CASE == 3 + // Bound-then-heap (loop): same as CASE=1 but the heap index is undefined when + // the loop body never executes. + RWTexture2D mixed = boundTex; + for (uint i = 0; i < tid.y; ++i) + mixed = ResourceDescriptorHeap[i]; + InterlockedAdd(mixed[tid.xy], 3, original); + +#elif CASE == 4 + // Buffer heap-then-bound: the alias records heap index 3; the reassignment to + // a bound buffer must be rejected before any load sees the stale alias. + RWByteAddressBuffer mixedBuf = ResourceDescriptorHeap[3]; + mixedBuf = boundBuf; + original = mixedBuf.Load(0); + +#elif CASE == 5 + // Buffer bound-then-heap: the bound assignment is recorded in + // descriptorHeapVarState; the later heap assignment must be rejected. + RWByteAddressBuffer mixedBuf = boundBuf; + mixedBuf = ResourceDescriptorHeap[3]; + original = mixedBuf.Load(0); + +#endif + + outputBytes.Store(0, original); +} From d8fdf4460dbb51a291e33738a99abfd62fb518d1 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 13 Aug 2026 15:59:36 -0700 Subject: [PATCH 10/26] Diagnose heap buffer alias use in function calls + returns and support alias-to-alias --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 147 +++++++++++++++--- tools/clang/lib/SPIRV/SpirvEmitter.h | 11 +- ...descriptorheap.ext.alias-fn-readwrite.hlsl | 56 +++++++ ...sm6_6.descriptorheap.ext.alias-return.hlsl | 33 ++++ ...6_6.descriptorheap.ext.alias-to-alias.hlsl | 60 +++++++ ...rheap.ext.buffer-alias-fn-param.error.hlsl | 31 ++++ ...scriptorheap.ext.image-alias-fn-param.hlsl | 41 +++++ ....descriptorheap.ext.mixed-alias.error.hlsl | 13 ++ 8 files changed, 371 insertions(+), 21 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-fn-readwrite.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-return.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-to-alias.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer-alias-fn-param.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.image-alias-fn-param.hlsl diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 58229ad399..86d010dd2c 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2029,12 +2029,16 @@ void SpirvEmitter::doEnumDecl(const EnumDecl *decl) { bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, const Expr *init) { if (!spirvOptions.useDescriptorHeap || !init || - !isDescriptorHeap(init->IgnoreParenCasts())) + !isHeapSourcedValue(init->IgnoreParenCasts())) return false; if (isConstantTextureBuffer(decl->getType()) || isAKindOfStructuredOrByteBuffer(decl->getType())) { - (void)doExpr(init->IgnoreParenCasts()); + // doExpr populates descriptorHeapBufferAccesses for the direct-subscript + // path; the VarDecl fallback in tryToAssignDescriptorHeapBufferAlias + // bypasses that map, so skip the call to avoid emitting dead instructions. + if (isDescriptorHeap(init->IgnoreParenCasts())) + (void)doExpr(init->IgnoreParenCasts()); tryToAssignDescriptorHeapBufferAlias(decl, init); return true; } @@ -2984,14 +2988,33 @@ void SpirvEmitter::doReturnStmt(const ReturnStmt *stmt) { if (!returnsVoid) { assert(retVal); const Expr *srcExpr = retVal->IgnoreParenCasts(); - if (isDescriptorHeap(srcExpr)) { - const Expr *base = nullptr; - getDescriptorHeapOperands(srcExpr, &base, /* index= */ nullptr); - const Expr *parentExpr = cast(parentMap->getParent(srcExpr)); - QualType resourceType = parentExpr->getType(); - const auto *declRefExpr = dyn_cast(base->IgnoreCasts()); - auto *decl = cast(declRefExpr->getDecl()); - declIdMapper.createResourceHeap(decl, resourceType); + if (isHeapSourcedValue(srcExpr)) { + if (isDescriptorHeap(srcExpr)) { + // Direct heap subscript: register the heap variable so the declaration + // mapper can resolve it for the return instruction. + const Expr *base = nullptr; + getDescriptorHeapOperands(srcExpr, &base, /* index= */ nullptr); + const Expr *parentExpr = cast(parentMap->getParent(srcExpr)); + QualType resourceType = parentExpr->getType(); + const auto *declRefExpr = dyn_cast(base->IgnoreCasts()); + declIdMapper.createResourceHeap(cast(declRefExpr->getDecl()), + resourceType); + } + // Buffer alias: emitting a load of the whole resource (runtime-array + // struct) produces invalid SPIR-V. emitError does not halt codegen, + // so return early to prevent the loadIfGLValue call below from + // emitting that invalid load. + // TODO: implement cross-function alias propagation for buffer aliases + // using VariablePointersStorageBuffer (tracked as follow-up). + else if (const auto *var = + dyn_cast_or_null(getReferencedDef(srcExpr))) { + if (descriptorHeapBufferAliasVars.count(var)) { + emitError("heap buffer alias cannot be returned from a function; " + "access the buffer element directly at the return site", + retVal->getLocStart()); + return; + } + } } auto *retInfo = loadIfGLValue(retVal); @@ -3162,10 +3185,14 @@ SpirvEmitter::tryToAssignToDescriptorHeapBuffer( return llvm::None; const Expr *rhsValue = assignExpr->getRHS()->IgnoreParenCasts(); - if (!isDescriptorHeap(rhsValue)) + if (!isHeapSourcedValue(rhsValue)) return llvm::None; - (void)doExpr(rhsValue); + // doExpr populates descriptorHeapBufferAccesses for the direct-subscript + // path; the VarDecl fallback bypasses that map, so skip the call to avoid + // emitting dead instructions when the source is an alias variable. + if (isDescriptorHeap(rhsValue)) + (void)doExpr(rhsValue); if (!tryToAssignDescriptorHeapBufferAlias(assignExpr->getLHS(), assignExpr->getRHS())) return llvm::None; @@ -3432,6 +3459,28 @@ SpirvInstruction *SpirvEmitter::processCall(const CallExpr *callExpr) { // for it if it can act as out parameter. SpirvInstruction *argInfo = nullptr; if (const auto *declRefExpr = dyn_cast(arg)) { + // Heap buffer alias vars are not registered in astDecls, so + // getDeclEvalInfo would crash. Passing a buffer alias by value to a + // user function also requires VariablePointersStorageBuffer and callee + // parameter type changes that are not yet implemented; emit a diagnostic + // instead of crashing. TODO: implement full buffer-alias function-call + // support (VariablePointersStorageBuffer + matching createFnParam type). + const auto *var = dyn_cast(declRefExpr->getDecl()); + if (var && descriptorHeapBufferAliasVars.count(var)) { + emitError("heap buffer alias cannot be passed to a user function; " + "access the buffer element directly at the call site", + arg->getLocStart()); + // emitError does not halt codegen; returning nullptr here propagates + // to spvBuilder and causes an access violation before the diagnostic + // surfaces. Return a zero uint placeholder so downstream expression + // consumers remain valid. The emitted error ensures the shader is + // rejected even if codegen continues with the placeholder. + QualType retTy = callExpr->getCallReturnType(astContext); + if (retTy->isVoidType()) + return nullptr; + return spvBuilder.getConstantInt(astContext.UnsignedIntTy, + llvm::APInt(32, 0), false); + } argInfo = declIdMapper.getDeclEvalInfo(declRefExpr->getDecl(), arg->getLocStart()); } @@ -5205,7 +5254,7 @@ bool SpirvEmitter::diagnoseDescriptorHeapAliasMixing(const VarDecl *dstVar, !isAKindOfStructuredOrByteBuffer(dstType)) return false; - const bool srcIsHeap = isDescriptorHeap(srcExpr->IgnoreParenCasts()); + const bool srcIsHeap = isHeapSourcedValue(srcExpr->IgnoreParenCasts()); const bool wasHeap = descriptorHeapImageAliasVars.count(dstVar) || descriptorHeapBufferAliasVars.count(dstVar); const bool wasBound = stateIt != descriptorHeapVarState.end() && @@ -5249,8 +5298,31 @@ bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias( const Expr *src = srcExpr->IgnoreParenCasts(); auto found = descriptorHeapImageAccesses.find(src); - if (found == descriptorHeapImageAccesses.end()) - return false; + if (found == descriptorHeapImageAccesses.end()) { + // Fallback: src is a DeclRefExpr referencing an existing image alias + // variable. The Expr*-keyed access map is only populated for direct heap + // subscripts; consult the VarDecl-keyed alias map instead. + const auto *srcVar = dyn_cast_or_null(getReferencedDef(src)); + if (!srcVar) + return false; + const auto srcAliasIt = descriptorHeapImageAliasVars.find(srcVar); + if (srcAliasIt == descriptorHeapImageAliasVars.end()) + return false; + const DescriptorHeapImageAlias &srcAlias = srcAliasIt->second; + DescriptorHeapImageAlias &dstAlias = descriptorHeapImageAliasVars[dstVar]; + if (!dstAlias.indexVar) + dstAlias.indexVar = createDescriptorHeapIndexVar(dstVar); + dstAlias.imageType = srcAlias.imageType; + dstAlias.arrayType = srcAlias.arrayType; + dstAlias.heap = srcAlias.heap; + // Propagate the runtime index: load current slot from the source alias + // variable and store it into the destination's index variable. + SpirvInstruction *index = spvBuilder.createLoad( + astContext.UnsignedIntTy, srcAlias.indexVar, srcExpr->getExprLoc()); + spvBuilder.createStore(dstAlias.indexVar, index, srcExpr->getExprLoc(), + srcExpr->getSourceRange()); + return true; + } auto &alias = descriptorHeapImageAliasVars[dstVar]; if (!alias.indexVar) @@ -5288,8 +5360,33 @@ bool SpirvEmitter::tryToAssignDescriptorHeapBufferAlias( const Expr *src = srcExpr->IgnoreParenCasts(); auto found = descriptorHeapBufferAccesses.find(src); - if (found == descriptorHeapBufferAccesses.end()) - return false; + if (found == descriptorHeapBufferAccesses.end()) { + // Fallback: src is a DeclRefExpr referencing an existing buffer alias + // variable. The Expr*-keyed access map is only populated for direct heap + // subscripts; consult the VarDecl-keyed alias map instead. + const auto *srcVar = dyn_cast_or_null(getReferencedDef(src)); + if (!srcVar) + return false; + const auto srcAliasIt = descriptorHeapBufferAliasVars.find(srcVar); + if (srcAliasIt == descriptorHeapBufferAliasVars.end()) + return false; + const DescriptorHeapBufferAlias &srcAlias = srcAliasIt->second; + DescriptorHeapBufferAlias &dstAlias = descriptorHeapBufferAliasVars[dstVar]; + if (!dstAlias.indexVar) + dstAlias.indexVar = createDescriptorHeapIndexVar(dstVar); + dstAlias.bufferPointerType = srcAlias.bufferPointerType; + dstAlias.arrayType = srcAlias.arrayType; + dstAlias.heap = srcAlias.heap; + dstAlias.layoutRule = srcAlias.layoutRule; + dstAlias.counterUnsupported = isRWStructuredBuffer(dstVar->getType()); + // Propagate the runtime index: load current slot from the source alias + // variable and store it into the destination's index variable. + SpirvInstruction *index = spvBuilder.createLoad( + astContext.UnsignedIntTy, srcAlias.indexVar, srcExpr->getExprLoc()); + spvBuilder.createStore(dstAlias.indexVar, index, srcExpr->getExprLoc(), + srcExpr->getSourceRange()); + return true; + } auto &alias = descriptorHeapBufferAliasVars[dstVar]; if (!alias.indexVar) @@ -5502,7 +5599,7 @@ bool SpirvEmitter::tryToAssignCounterVar(const DeclaratorDecl *dstDecl, auto *srcCounter = getFinalACSBufferCounterInstruction(srcExpr); if (!srcCounter) { if (spirvOptions.useDescriptorHeap && - isDescriptorHeap(srcExpr->IgnoreParenCasts())) { + isHeapSourcedValue(srcExpr->IgnoreParenCasts())) { markDescriptorHeapCounterUnsupported(dstDecl); return true; } @@ -5546,7 +5643,7 @@ bool SpirvEmitter::tryToAssignCounterVar(const Expr *dstExpr, if ((dstCounter == nullptr) != (srcCounter == nullptr)) { if (spirvOptions.useDescriptorHeap && dstCounter && - isDescriptorHeap(srcExpr->IgnoreParenCasts())) { + isHeapSourcedValue(srcExpr->IgnoreParenCasts())) { markDescriptorHeapCounterUnsupported(getReferencedDef(dstExpr)); return true; } @@ -8431,7 +8528,7 @@ bool SpirvEmitter::isBufferTextureIndexing(const CXXOperatorCallExpr *indexExpr, return false; } -bool SpirvEmitter::isDescriptorHeap(const Expr *expr) { +bool SpirvEmitter::isDescriptorHeap(const Expr *expr) const { const CXXOperatorCallExpr *operatorExpr = dyn_cast(expr); if (!operatorExpr) return false; @@ -8446,6 +8543,16 @@ bool SpirvEmitter::isDescriptorHeap(const Expr *expr) { isSamplerDescriptorHeap(objectType); } +bool SpirvEmitter::isHeapSourcedValue(const Expr *expr) const { + if (isDescriptorHeap(expr->IgnoreParenCasts())) + return true; + const auto *var = dyn_cast_or_null(getReferencedDef(expr)); + if (!var) + return false; + return descriptorHeapImageAliasVars.count(var) || + descriptorHeapBufferAliasVars.count(var); +} + void SpirvEmitter::getDescriptorHeapOperands(const Expr *expr, const Expr **base, const Expr **index) { diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index ddc3caa18f..f6cce7b6e4 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -284,7 +284,16 @@ class SpirvEmitter : public ASTConsumer { const Expr **base = nullptr, const Expr **index = nullptr); - bool isDescriptorHeap(const Expr *expr); + bool isDescriptorHeap(const Expr *expr) const; + + /// Returns true if expr is heap-sourced: either a direct descriptor + /// heap subscript (ResourceDescriptorHeap[i]) or a DeclRefExpr referencing a + /// local variable that was previously assigned from a heap subscript and is + /// recorded in the image or buffer alias maps. + /// + /// Use this in place of bare isDescriptorHeap() at all sites that ask "is + /// this value heap-sourced?" so that alias-to-alias flows are recognized. + bool isHeapSourcedValue(const Expr *expr) const; void getDescriptorHeapOperands(const Expr *expr, const Expr **base, const Expr **index); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-fn-readwrite.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-fn-readwrite.hlsl new file mode 100644 index 0000000000..2bdb84cf87 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-fn-readwrite.hlsl @@ -0,0 +1,56 @@ +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies that heap-alias image reads and writes survive function boundaries +// (both return and parameter directions) with the correct descriptor slot. +// +// returned from a function -> slot 40, OpImageRead/OpImageWrite +// passed as a parameter -> slot 41, OpImageRead/OpImageWrite +// +// Plain reads/writes work because the loaded handle is enough for spirv-opt to +// promote the copies. Atomics fail because the descriptor index does not cross +// OpFunctionCall — see alias-return.hlsl and image-alias-fn-param.hlsl. +// Keep this passing when fixing the atomic case. + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[RWTexType:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 2 R32ui +// CHECK-DAG: %[[RWTexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexType]] + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +RWByteAddressBuffer outputBytes : register(u0); + +RWTexture2D getTex() { + RWTexture2D t = ResourceDescriptorHeap[40]; + return t; +} + +uint bump(RWTexture2D t, uint2 coord) { + uint v = t[coord]; + t[coord] = v + 1; + return v; +} + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + // Returned from a function: uses of the returned resource hit slot 40. + // CHECK: %[[RetDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_40 + // CHECK: %[[RetHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexType]] %[[RetDesc]] + // CHECK-NOT: OpStore {{.*}} %[[RetHandle]] + // CHECK: OpImageRead %v4uint %[[RetHandle]] + // CHECK: OpImageWrite %[[RetHandle]] + RWTexture2D returned = getTex(); + uint r0 = returned[tid.xy]; + returned[tid.xy] = r0 + 1; + + // Passed to a function: uses inside the callee hit slot 41. + // CHECK: %[[ParamDesc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_41 + // CHECK: %[[ParamHandle:[a-zA-Z0-9_]+]] = OpLoad %[[RWTexType]] %[[ParamDesc]] + // CHECK-NOT: OpStore {{.*}} %[[ParamHandle]] + // CHECK: OpImageRead %v4uint %[[ParamHandle]] + // CHECK: OpImageWrite %[[ParamHandle]] + RWTexture2D passed = ResourceDescriptorHeap[41]; + uint r1 = bump(passed, tid.xy); + + outputBytes.Store(0, r0); + outputBytes.Store(4, r1); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-return.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-return.hlsl new file mode 100644 index 0000000000..c2aa1a5ae3 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-return.hlsl @@ -0,0 +1,33 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck %s + +// Verifies: returning a heap buffer alias from a function emits a clear +// diagnostic instead of producing invalid SPIR-V. +// +// Regression (B2 / C3): returning a StructuredBuffer alias variable caused +// loadIfGLValue to emit an OpLoad of the whole runtime-array struct through a +// StorageBuffer pointer, which is invalid SPIR-V. The fix emits an actionable +// error before the load is attempted. +// +// Cross-function alias propagation (returning the alias and using it in the +// caller) is not yet implemented; it requires VariablePointersStorageBuffer so +// that the OpBufferPointerEXT value can survive an OpReturnValue boundary. +// Tracked as a follow-up; until then this path emits an error. +// +// Image aliases (RWTexture2D) are handled by the legalization pipeline (System A) +// and are not covered by this test. + +// CHECK: heap buffer alias cannot be returned from a function + +RWByteAddressBuffer outputBytes : register(u0); + +StructuredBuffer makeAlias() { + StructuredBuffer a = ResourceDescriptorHeap[40]; + StructuredBuffer b = ResourceDescriptorHeap[41]; + b = a; + return b; +} + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + outputBytes.Store(0, makeAlias()[0]); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-to-alias.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-to-alias.hlsl new file mode 100644 index 0000000000..1773ebb6c6 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.alias-to-alias.hlsl @@ -0,0 +1,60 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// Verifies that alias-to-alias assignment (sb2 = sb1) and copy-init (sb3 = sb1) +// propagate the source's descriptor slot to the destination. +// +// Propagation is compile-time: tryToAssignDescriptorHeapBufferAlias copies the +// source's index SpirvInstruction into the destination alias; legalization then +// constant-folds the index-variable loads away. As a result the overwritten +// slot (%uint_31, %uint_21) appears only in the dead declaration-site access +// chain, never at a use site. A second occurrence means propagation failed. +// +// Regression: alias-var sources previously crashed in getDeclEvalInfo because +// buffer alias VarDecls are not registered in astDecls. + +struct Payload { uint value; }; +RWByteAddressBuffer outputBytes : register(u0); + +// ---- StructuredBuffer section ---------------------------------------- +// sb1->slot 30, sb2->slot 31 (overwritten to 30 by sb2=sb1), sb3 copy-init from sb1. +// +// sb2's dead declaration chain is the ONLY %uint_31 StructuredBuffer chain: +// CHECK: OpUntypedAccessChainKHR %type_untyped_pointer %_runtimearr_type_buffer_ext_0 %resource_heap %uint_31 +// CHECK-NOT: OpUntypedAccessChainKHR %type_untyped_pointer %_runtimearr_type_buffer_ext_0 %resource_heap %uint_31 +// +// sb2[0] and sb3[0] produce element loads (via propagated slot 30): +// CHECK: OpLoad %uint +// CHECK: OpLoad %uint + +// ---- ConstantBuffer section ------------------------------------------ +// cb1->slot 20, cb2->slot 21 (overwritten to 20 by cb2=cb1). +// +// cb2's dead declaration chain is the ONLY %uint_21 ConstantBuffer chain: +// CHECK: OpUntypedAccessChainKHR %type_untyped_pointer %_runtimearr_type_buffer_ext %resource_heap %uint_21 +// CHECK-NOT: OpUntypedAccessChainKHR %type_untyped_pointer %_runtimearr_type_buffer_ext %resource_heap %uint_21 +// +// cb2.value produces a member load (via propagated slot 20): +// CHECK: OpLoad %uint + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + uint r0, r1; + + StructuredBuffer sb1 = ResourceDescriptorHeap[30]; + StructuredBuffer sb2 = ResourceDescriptorHeap[31]; + sb2 = sb1; // alias assign: sb2 now uses sb1's slot (30) + + StructuredBuffer sb3 = sb1; // copy-init: sb3 uses sb1's slot (30) + + r0 = sb2[0]; // must use slot 30 + r0 += sb3[0]; // must use slot 30 + + ConstantBuffer cb1 = ResourceDescriptorHeap[20]; + ConstantBuffer cb2 = ResourceDescriptorHeap[21]; + cb2 = cb1; // alias assign: cb2 now uses cb1's slot (20) + + r1 = cb2.value; // must use slot 20 + + outputBytes.Store(0, r0); + outputBytes.Store(4, r1); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer-alias-fn-param.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer-alias-fn-param.error.hlsl new file mode 100644 index 0000000000..985f309c5d --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.buffer-alias-fn-param.error.hlsl @@ -0,0 +1,31 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DTEST_STRUCTURED %s 2>&1 | FileCheck --check-prefix=SB %s +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck --check-prefix=BAB %s + +// Verifies that passing a heap buffer alias to a user function emits a +// diagnostic instead of crashing (buffer alias VarDecls are not in astDecls, +// so getDeclEvalInfo would fault). +// +// TODO: remove once VariablePointersStorageBuffer-based pass-by-value is +// implemented and buffer aliases can be passed to functions. + +// SB: heap buffer alias cannot be passed to a user function +// BAB: heap buffer alias cannot be passed to a user function + +RWByteAddressBuffer outputBytes : register(u0); + +#ifdef TEST_STRUCTURED +uint consume(StructuredBuffer buf) { return buf[0]; } +#else +uint consume(ByteAddressBuffer buf) { return buf.Load(0); } +#endif + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { +#ifdef TEST_STRUCTURED + StructuredBuffer sb = ResourceDescriptorHeap[0]; + outputBytes.Store(0, consume(sb)); +#else + ByteAddressBuffer bab = ResourceDescriptorHeap[0]; + outputBytes.Store(0, consume(bab)); +#endif +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.image-alias-fn-param.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.image-alias-fn-param.hlsl new file mode 100644 index 0000000000..c6c57c12c9 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.image-alias-fn-param.hlsl @@ -0,0 +1,41 @@ +// XFAIL: * +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv %s | FileCheck %s + +// XFAIL: heap image alias passed to a function and used in an atomic. +// Same failure as alias-return.hlsl: [VUID-StandaloneSpirv-OpTypeImage-06924]. +// +// The argument is copied into a Function-class "param.var.*" variable. The +// callee has no entry in the image alias map for its parameter, so the atomic +// falls back to OpImageTexelPointer on the parameter, pinning it as a variable +// and keeping the image-typed OpStore alive through validation. +// +// Plain reads/writes through the same parameter work — see alias-fn-readwrite.hlsl. +// Note: function-params.hlsl only checks the pre-legalization form (-fcgl), +// where the validator relaxes this rule, so it does not catch this failure. +// +// TODO: propagate descriptor index across function boundaries, then remove XFAIL. + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[RWTexType:[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 2 0 0 2 R32ui +// CHECK-DAG: %[[RWTexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[RWTexType]] +// CHECK-DAG: %[[UntypedImage:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR Image + +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +RWByteAddressBuffer outputBytes : register(u0); + +void bump(RWTexture2D t, uint2 coord, out uint orig) { + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[RWTexArray]] %[[ResourceHeap]] %uint_40 + // CHECK-NOT: OpImageTexelPointer + // CHECK: %[[TexelPtr:[a-zA-Z0-9_]+]] = OpUntypedImageTexelPointerEXT %[[UntypedImage]] %[[RWTexType]] %[[Desc]] + // CHECK: OpAtomicIAdd %uint %[[TexelPtr]] + InterlockedAdd(t[coord], 1, orig); +} + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RWTexture2D tex = ResourceDescriptorHeap[40]; + uint r; + bump(tex, tid.xy, r); + outputBytes.Store(0, r); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl index ce0a3f0878..bb765be87f 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.mixed-alias.error.hlsl @@ -4,6 +4,7 @@ // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=3 %s 2>&1 | FileCheck %s // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=4 %s 2>&1 | FileCheck %s // RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=5 %s 2>&1 | FileCheck %s +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv -DCASE=6 %s | FileCheck %s --check-prefix=OK // Verifies that a local resource variable assigned from both a bound resource // and ResourceDescriptorHeap is rejected. @@ -26,6 +27,9 @@ // CASE=3, CASE=5) are tracked in descriptorHeapVarState; heap-then-bound // assignments (CASE=2, CASE=4) are detected by observing that the alias map // already contains an entry for the variable. +// +// CASE=6 (regression): heap-to-heap alias copy must NOT trigger the mixing +// diagnostic; only a bound source mixed with a heap destination is true mixing. // CHECK: error: {{.*}}mixing bound and descriptor heap resources in the same variable is not supported with SPV_EXT_descriptor_heap // OK: OpUntypedImageTexelPointerEXT @@ -86,6 +90,15 @@ void main(uint3 tid : SV_DispatchThreadID) { mixedBuf = ResourceDescriptorHeap[3]; original = mixedBuf.Load(0); +#elif CASE == 6 + // Heap-to-heap alias copy: both sources are heap-backed, so this is NOT + // mixing. heapB = heapA must propagate heapA's descriptor index to heapB + // without emitting the mixing error. + RWTexture2D heapA = ResourceDescriptorHeap[1]; + RWTexture2D heapB = ResourceDescriptorHeap[2]; + heapB = heapA; + InterlockedAdd(heapB[tid.xy], 6, original); + #endif outputBytes.Store(0, original); From 6a75ed5c9d68432952dcd0814f7b3a607433747a Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 11:57:18 -0700 Subject: [PATCH 11/26] Remove useless scope block in tryToAssignDescriptorHeapImageAlias --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 86d010dd2c..72e422bfef 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -5289,12 +5289,10 @@ bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias( (!isRWTexture(dstVar->getType()) && !isRWBuffer(dstVar->getType()))) return false; - { - auto stateIt = descriptorHeapVarState.find(dstVar); - if (stateIt != descriptorHeapVarState.end() && - stateIt->second == DescriptorHeapVarState::Mixed) - return false; - } + auto stateIt = descriptorHeapVarState.find(dstVar); + if (stateIt != descriptorHeapVarState.end() && + stateIt->second == DescriptorHeapVarState::Mixed) + return false; const Expr *src = srcExpr->IgnoreParenCasts(); auto found = descriptorHeapImageAccesses.find(src); From 7f8964080b8d7acef26a0e550682978f5b6eab60 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 11:57:50 -0700 Subject: [PATCH 12/26] Remove dead append/consume guard in incDecRWACSBufferCounter --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 72e422bfef..944ee4dc58 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -5535,21 +5535,6 @@ SpirvEmitter::incDecRWACSBufferCounter(const CXXMemberCallExpr *expr, return nullptr; } - // Only heap-loaded append/consume buffers are unsupported. Explicitly bound - // ones keep working in native heap mode, so key the diagnostic on the object - // actually having a heap alias rather than on the option alone. - if (spirvOptions.useDescriptorHeap && - (isAppendStructuredBuffer(object->getType()) || - isConsumeStructuredBuffer(object->getType()))) { - const auto *objVar = dyn_cast_or_null(getReferencedDef(object)); - if (objVar && descriptorHeapBufferAliasVars.count(objVar)) { - emitError("append/consume structured buffers are not supported with " - "SPV_EXT_descriptor_heap", - expr->getCallee()->getExprLoc()); - return nullptr; - } - } - auto *counter = getFinalACSBufferCounterInstruction(object); if (!counter) { emitFatalError("Cannot access associated counter variable for an array of " From 8dc7484baf885d870b6b7653fb32d2e2af4c5873 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 11:58:31 -0700 Subject: [PATCH 13/26] Remove unreachable !decl guard in heap index codegen path --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 944ee4dc58..c2f4f58d24 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -7153,18 +7153,10 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, return nullptr; } QualType resourceType = parentExpr->getType(); - // The heap object must be a direct reference to the builtin heap - // variable. Anything else (e.g. a non-variable expression) has no backing - // VarDecl. - const auto *declRefExpr = dyn_cast(baseExpr->IgnoreCasts()); + // The heap object is always a direct DeclRefExpr to the builtin heap + // VarDecl; sema rejects any other form. const auto *decl = - declRefExpr ? dyn_cast(declRefExpr->getDecl()) : nullptr; - if (!decl) { - emitError("unsupported ResourceDescriptorHeap/SamplerDescriptorHeap " - "expression", - baseExpr->getExprLoc()); - return nullptr; - } + cast(cast(baseExpr->IgnoreCasts())->getDecl()); SpirvVariableLike *var = declIdMapper.createResourceHeap(decl, resourceType); From 9812952615a90f7016efa03d6404bfd3f7d1e5c1 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 11:59:30 -0700 Subject: [PATCH 14/26] Propagate requestTargetEnv failure for desc heap CapabilityVisitor::visitInstructionAllTypes was dropping the bool return of requestTargetEnv, so a vk<1.3 target-env would emit the diagnostic but keep lowering. Added sm6_6.descriptorheap.ext.targetenv.error.hlsl to cover rejection of -fspv-use-descriptor-heap with -fspv-target-env=vulkan1.2. --- tools/clang/lib/SPIRV/CapabilityVisitor.cpp | 3 ++- .../sm6_6.descriptorheap.ext.targetenv.error.hlsl | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.targetenv.error.hlsl diff --git a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp index c48f898a28..f79c455319 100644 --- a/tools/clang/lib/SPIRV/CapabilityVisitor.cpp +++ b/tools/clang/lib/SPIRV/CapabilityVisitor.cpp @@ -956,7 +956,8 @@ bool CapabilityVisitor::visit(SpirvModule *, Visitor::Phase phase) { if (spvOptions.useDescriptorHeap) { const llvm::StringRef feature = "DescriptorHeap"; - featureManager.requestTargetEnv(SPV_ENV_VULKAN_1_3, feature, {}); + if (!featureManager.requestTargetEnv(SPV_ENV_VULKAN_1_3, feature, {})) + return false; addExtension(Extension::EXT_descriptor_heap, feature, {}); addExtension(Extension::KHR_untyped_pointers, feature, {}); addCapability(spv::Capability::DescriptorHeapEXT); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.targetenv.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.targetenv.error.hlsl new file mode 100644 index 0000000000..cfd6d056ad --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.targetenv.error.hlsl @@ -0,0 +1,12 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.2 -spirv %s 2>&1 | FileCheck %s + +// Verifies that -fspv-use-descriptor-heap rejects target environments below +// Vulkan 1.3 with a clear diagnostic. + +// CHECK: error: Vulkan 1.3 is required for DescriptorHeap but not permitted to use + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2D tex = ResourceDescriptorHeap[tid.x]; + (void)tex; +} From dab0e1eb02db8de5b9ef5f555cb39001244d7634 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 12:40:00 -0700 Subject: [PATCH 15/26] [SPIR-V] Add release note for SPV_EXT_descriptor_heap native lowering (#8517) --- docs/ReleaseNotes.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 915142185d..b84287d587 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -19,6 +19,13 @@ The included licenses apply to the following files: ### Upcoming Release +#### SPIR-V + +- Added native `SPV_EXT_descriptor_heap` lowering for `ResourceDescriptorHeap` + and `SamplerDescriptorHeap` via `-fspv-use-descriptor-heap`. Requires + `-fspv-target-env=vulkan1.3` + [#8517](https://github.com/microsoft/DirectXShaderCompiler/pull/8517). + Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. From fae23d735f92490deaf42a6196f5118b5e7df7d2 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 14:55:20 -0700 Subject: [PATCH 16/26] nit fixes --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index c2f4f58d24..53a6095be7 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2039,8 +2039,7 @@ bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, // bypasses that map, so skip the call to avoid emitting dead instructions. if (isDescriptorHeap(init->IgnoreParenCasts())) (void)doExpr(init->IgnoreParenCasts()); - tryToAssignDescriptorHeapBufferAlias(decl, init); - return true; + return tryToAssignDescriptorHeapBufferAlias(decl, init); } return false; @@ -3002,8 +3001,9 @@ void SpirvEmitter::doReturnStmt(const ReturnStmt *stmt) { } // Buffer alias: emitting a load of the whole resource (runtime-array // struct) produces invalid SPIR-V. emitError does not halt codegen, - // so return early to prevent the loadIfGLValue call below from - // emitting that invalid load. + // so terminate the basic block with an undef return value before + // returning to prevent loadIfGLValue from emitting an invalid load and + // leaving the block without a terminator. // TODO: implement cross-function alias propagation for buffer aliases // using VariablePointersStorageBuffer (tracked as follow-up). else if (const auto *var = @@ -3012,6 +3012,9 @@ void SpirvEmitter::doReturnStmt(const ReturnStmt *stmt) { emitError("heap buffer alias cannot be returned from a function; " "access the buffer element directly at the return site", retVal->getLocStart()); + spvBuilder.createReturnValue( + spvBuilder.getUndef(curFunction->getReturnType()), + stmt->getReturnLoc()); return; } } @@ -3472,14 +3475,13 @@ SpirvInstruction *SpirvEmitter::processCall(const CallExpr *callExpr) { arg->getLocStart()); // emitError does not halt codegen; returning nullptr here propagates // to spvBuilder and causes an access violation before the diagnostic - // surfaces. Return a zero uint placeholder so downstream expression + // surfaces. Return a typed undef placeholder so downstream expression // consumers remain valid. The emitted error ensures the shader is // rejected even if codegen continues with the placeholder. QualType retTy = callExpr->getCallReturnType(astContext); if (retTy->isVoidType()) return nullptr; - return spvBuilder.getConstantInt(astContext.UnsignedIntTy, - llvm::APInt(32, 0), false); + return spvBuilder.getUndef(retTy); } argInfo = declIdMapper.getDeclEvalInfo(declRefExpr->getDecl(), arg->getLocStart()); @@ -5179,8 +5181,8 @@ SpirvEmitter::processStructuredBufferLoad(const CXXMemberCallExpr *expr) { // (Verified required: scoping this to alias vars only regresses the direct // heap-access tests; non-heap callers are unaffected in the existing suite.) if (result && !result->isRValue()) { - result = - spvBuilder.createLoad(structType, result, buffer->getExprLoc(), range); + result = spvBuilder.createLoad(expr->getType(), result, + buffer->getExprLoc(), range); } return result; From 421f778027871115215ad6d92ac4411922fbcaa4 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:50 -0700 Subject: [PATCH 17/26] [SPIR-V] Add descriptor heap RaytracingAccelerationStructure support Extends the SPV_EXT_descriptor_heap native heap lowering to cover RaytracingAccelerationStructure resources loaded from ResourceDescriptorHeap. Acceleration structure descriptors are accessed via OpUntypedAccessChainKHR into a runtime array of OpTypeAccelerationStructureKHR, consistent with the image and sampler paths added in the previous commit. --- .../clang/include/clang/SPIRV/AstTypeProbe.h | 4 ++ tools/clang/lib/SPIRV/AstTypeProbe.cpp | 7 ++++ tools/clang/lib/SPIRV/SpirvEmitter.cpp | 10 +++++ ...riptorheap.ext.acceleration-structure.hlsl | 42 +++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl diff --git a/tools/clang/include/clang/SPIRV/AstTypeProbe.h b/tools/clang/include/clang/SPIRV/AstTypeProbe.h index 214df82d45..207d02a3b2 100644 --- a/tools/clang/include/clang/SPIRV/AstTypeProbe.h +++ b/tools/clang/include/clang/SPIRV/AstTypeProbe.h @@ -250,6 +250,10 @@ bool isBuffer(QualType type); /// \brief Returns true if the given type is the HLSL RWBuffer type. bool isRWBuffer(QualType type); +/// \brief Returns true if the given type is the HLSL +/// RaytracingAccelerationStructure type. +bool isRaytracingAccelerationStructure(QualType type); + /// \brief Returns true if the given type is an HLSL Texture type. bool isTexture(QualType); diff --git a/tools/clang/lib/SPIRV/AstTypeProbe.cpp b/tools/clang/lib/SPIRV/AstTypeProbe.cpp index c933a82c16..7a9f5e859c 100644 --- a/tools/clang/lib/SPIRV/AstTypeProbe.cpp +++ b/tools/clang/lib/SPIRV/AstTypeProbe.cpp @@ -917,6 +917,13 @@ bool isBuffer(QualType type) { return false; } +bool isRaytracingAccelerationStructure(QualType type) { + if (const auto *rt = type->getAs()) { + return rt->getDecl()->getName() == "RaytracingAccelerationStructure"; + } + return false; +} + bool isRWTexture(QualType type) { if (const auto *rt = type->getAs()) { const auto name = rt->getDecl()->getName(); diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 53a6095be7..dc86579faf 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2042,6 +2042,16 @@ bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, return tryToAssignDescriptorHeapBufferAlias(decl, init); } + if (isRaytracingAccelerationStructure(decl->getType())) { + if (auto *initVal = loadIfGLValue(init)) + declIdMapper.registerFnVarAlias(decl, initVal); + else + emitError("cannot create descriptor heap acceleration structure alias " + "from initializer", + init->getExprLoc()); + return true; + } + return false; } diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl new file mode 100644 index 0000000000..7c5c9fdcb9 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl @@ -0,0 +1,42 @@ +// RUN: %dxc -T lib_6_6 -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 32 -fspv-extension=SPV_KHR_ray_tracing -fspv-extension=SPV_EXT_descriptor_heap -fspv-extension=SPV_KHR_untyped_pointers -spirv %s | FileCheck %s + +// Verifies: ResourceDescriptorHeap of a RaytracingAccelerationStructure +// lowers to an acceleration-structure runtime array, loads the accel +// handle from the heap, and emits OpTraceRayKHR under the ray-tracing +// capability/extension. + +// CHECK: OpCapability RayTracingKHR +// CHECK: OpExtension "SPV_KHR_ray_tracing" + +// CHECK-DAG: %[[UntypedUniformConstant:[a-zA-Z0-9_]+]] = OpTypeUntypedPointerKHR UniformConstant +// CHECK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR +// CHECK-DAG: %[[ASArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] +// CHECK: %[[ResourceHeap:[a-zA-Z0-9_]+]] = OpUntypedVariableKHR %[[UntypedUniformConstant]] UniformConstant + +struct Payload { + float4 color; +}; + +struct Attribute { + float2 bary; +}; + +[shader("closesthit")] +void main(inout Payload payload, in Attribute attr) { + RaytracingAccelerationStructure scene = ResourceDescriptorHeap[3]; + + RayDesc ray; + ray.Origin = float3(0.0f, 0.0f, 0.0f); + ray.Direction = float3(0.0f, 0.0f, -1.0f); + ray.TMin = 0.0f; + ray.TMax = 1000.0f; + + Payload childPayload = { float4(attr.bary, 0.0f, 1.0f) }; + + // CHECK: %[[Desc:[a-zA-Z0-9_]+]] = OpUntypedAccessChainKHR %[[UntypedUniformConstant]] %[[ASArray]] %[[ResourceHeap]] %uint_3 + // CHECK: %[[Scene:[a-zA-Z0-9_]+]] = OpLoad %[[Accel]] %[[Desc]] + // CHECK: OpTraceRayKHR %[[Scene]] + TraceRay(scene, 0x0, 0xff, 0, 1, 0, ray, childPayload); + + payload.color = childPayload.color; +} From c5909ae733ea51d4d976e5ac2fc599ec9ef4a91c Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 20 Jul 2026 15:40:40 -0700 Subject: [PATCH 18/26] removed erronous CLI options from test --- .../sm6_6.descriptorheap.ext.acceleration-structure.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl index 7c5c9fdcb9..57ba2c3d68 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.hlsl @@ -1,4 +1,4 @@ -// RUN: %dxc -T lib_6_6 -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 32 -fspv-extension=SPV_KHR_ray_tracing -fspv-extension=SPV_EXT_descriptor_heap -fspv-extension=SPV_KHR_untyped_pointers -spirv %s | FileCheck %s +// RUN: %dxc -T lib_6_6 -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fspv-extension=SPV_KHR_ray_tracing -fspv-extension=SPV_EXT_descriptor_heap -fspv-extension=SPV_KHR_untyped_pointers -spirv %s | FileCheck %s // Verifies: ResourceDescriptorHeap of a RaytracingAccelerationStructure // lowers to an acceleration-structure runtime array, loads the accel From f69efdb8c79a0268e86223a4b5c442e3a5a08064 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Wed, 22 Jul 2026 14:12:52 -0700 Subject: [PATCH 19/26] Included AccelerationStructure in resource heap stride calculation --- .../clang/include/clang/SPIRV/SpirvBuilder.h | 30 +++- tools/clang/lib/SPIRV/SpirvBuilder.cpp | 48 ++++-- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 103 +++++++++++- ...t.acceleration-structure.stride.error.hlsl | 46 ++++++ ...ptorheap.ext.array-stride.accelstruct.hlsl | 154 ++++++++++++++++++ 5 files changed, 361 insertions(+), 20 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index 867e6f4957..52f8c3d98d 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -836,9 +836,27 @@ class SpirvBuilder { SpirvInstruction *op1, SpirvInstruction *op2, SpirvInstruction *op3, SourceLocation loc); - /// \brief Shared ArrayStrideIdEXT operand for resource-heap runtime arrays: - /// max(sizeof(image), sizeof(buffer)) computed via OpSpecConstantOp. - /// Cached per module. + /// \brief Record that acceleration structures may occupy the resource heap. + /// Note: Must be called before getResourceHeapArrayStride() (before the + /// code-gen loop in HandleTranslationUnit) so the cached stride is correct + /// on the first call. Calling it later has no effect because the result is + /// frozen after the first getResourceHeapArrayStride() invocation. + void noteResourceHeapHasAccelStruct() { resourceHeapHasAccelStruct = true; } + + /// \brief Returns whether the resource-heap stride accounts for acceleration + /// structure descriptors. Code-gen must reject an acceleration structure heap + /// access when this is false: the decision is made before the code-gen loop + /// and the stride cannot be widened afterwards. + bool resourceHeapStrideIncludesAccelStruct() const { + return resourceHeapHasAccelStruct; + } + + /// \brief Shared ArrayStrideIdEXT operand for resource-heap runtime arrays. + /// Default: max(sizeof(image), sizeof(buffer)) + /// With RT: max(max(sizeof(image), sizeof(buffer)), sizeof(accel_struct)) + /// Computed via OpSpecConstantOp and cached per module. + /// Note: noteResourceHeapHasAccelStruct() must be called before this if AS + /// may be present (result is frozen on the first call). SpirvInstruction *getResourceHeapArrayStride(); /// \brief Shared ArrayStrideIdEXT operand for sampler-heap runtime arrays: @@ -970,6 +988,12 @@ class SpirvBuilder { SpirvInstruction *resourceHeapArrayStride = nullptr; SpirvInstruction *samplerHeapArrayStride = nullptr; + /// Set by noteResourceHeapHasAccelStruct() when HandleTranslationUnit + /// detects that the shader uses ray-tracing features. When true, + /// getResourceHeapArrayStride() extends the stride to include + /// sizeof(acceleration_structure). + bool resourceHeapHasAccelStruct = false; + SpirvDebugInfoNone *debugNone; /// DebugExpression that does not reference any DebugOperation diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index b4b5c834ff..d20b387b72 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -2045,16 +2045,31 @@ SpirvInstruction *SpirvBuilder::getResourceHeapArrayStride() { // The HLSL SM6.6 ResourceDescriptorHeap is a single flat array in which the // client may place any resource descriptor at any slot. To match DX12 // semantics, all resource descriptor arrays must share one stride equal to - // the largest resource descriptor size: max(sizeof(image), sizeof(buffer)). - // Images and buffers are the two resource descriptor categories defined by + // the largest resource descriptor size across all categories that may appear + // in this shader. + // + // Base categories (always included): image and buffer, as defined by // VkPhysicalDeviceDescriptorHeapPropertiesEXT (imageDescriptorSize / - // bufferDescriptorSize); textures lower to OpTypeImage, so image/buffer - // covers all relevant HLSL resource kinds. - // Both sizes are driver defined and known only at pipeline creation time, so - // the maximum is computed with OpSpecConstantOp over two OpConstantSizeOfEXT - // placeholders. A canonical sampled 2D float image and a Uniform buffer stand - // in as representatives; VkPhysicalDeviceDescriptorHeapPropertiesEXT reports - // one size per category, so subtype and storage class do not affect the size. + // bufferDescriptorSize). Textures lower to OpTypeImage so image/buffer + // covers all non-RT resource kinds. + // + // Optional category: acceleration structure, included only when the shader + // uses ray-tracing features (noteResourceHeapHasAccelStruct() was called). + // The placeholder is getAccelerationStructureTypeNV(), which emits opcode + // 5341 — the same opcode shared by OpTypeAccelerationStructureNV and + // OpTypeAccelerationStructureKHR. SPIRV-Tools disassembles it as + // OpTypeAccelerationStructureKHR. Emitting OpConstantSizeOfEXT on this type + // requires OpCapability RayTracingKHR, which is guaranteed present whenever + // noteResourceHeapHasAccelStruct() has been called. + // + // All sizes are driver-defined and known only at pipeline creation time, so + // the maximum is computed with OpSpecConstantOp over OpConstantSizeOfEXT + // placeholders (two for non-RT shaders; three when acceleration structures + // are present). A canonical sampled 2D float image and a Uniform buffer stand + // in as representatives for the image and buffer categories. + // + // VkPhysicalDeviceDescriptorHeapPropertiesEXT reports one size per category, + // so subtype and storage class do not affect the size. const SpirvType *placeholderImage = context.getImageType( context.getFloatType(32), spv::Dim::Dim2D, ImageType::WithDepth::No, /*arrayed*/ false, /*ms*/ false, ImageType::WithSampler::Yes, @@ -2066,9 +2081,22 @@ SpirvInstruction *SpirvBuilder::getResourceHeapArrayStride() { SpirvInstruction *bufferSize = getConstantSizeOfEXT(placeholderBuffer); SpirvInstruction *imageIsBigger = createSpecConstantBinaryOp( spv::Op::OpUGreaterThan, astContext.BoolTy, imageSize, bufferSize, {}); - resourceHeapArrayStride = + SpirvInstruction *maxImgBuf = createSpecConstantTernaryOp(spv::Op::OpSelect, astContext.UnsignedIntTy, imageIsBigger, imageSize, bufferSize, {}); + + if (resourceHeapHasAccelStruct) { + // Extend to max(max(img, buf), accel_struct). + const SpirvType *placeholderAS = context.getAccelerationStructureTypeNV(); + SpirvInstruction *asSize = getConstantSizeOfEXT(placeholderAS); + SpirvInstruction *maxImgBufIsBigger = createSpecConstantBinaryOp( + spv::Op::OpUGreaterThan, astContext.BoolTy, maxImgBuf, asSize, {}); + resourceHeapArrayStride = + createSpecConstantTernaryOp(spv::Op::OpSelect, astContext.UnsignedIntTy, + maxImgBufIsBigger, maxImgBuf, asSize, {}); + } else { + resourceHeapArrayStride = maxImgBuf; + } return resourceHeapArrayStride; } diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index dc86579faf..903c272b8a 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -67,6 +67,23 @@ bool isImplicitVarDeclInVkNamespace(const Decl *decl) { return false; } +// Returns true if the given shader model kind is a ray-tracing stage. +// Used to detect whether the resource heap stride must include +// sizeof(acceleration_structure). +bool shaderModelKindIsRayTracing(hlsl::ShaderModel::Kind k) { + switch (k) { + case hlsl::ShaderModel::Kind::RayGeneration: + case hlsl::ShaderModel::Kind::Intersection: + case hlsl::ShaderModel::Kind::AnyHit: + case hlsl::ShaderModel::Kind::ClosestHit: + case hlsl::ShaderModel::Kind::Miss: + case hlsl::ShaderModel::Kind::Callable: + return true; + default: + return false; + } +} + // Returns true if the given decl has the given semantic. bool hasSemantic(const DeclaratorDecl *decl, hlsl::DXIL::SemanticKind semanticKind) { @@ -803,6 +820,57 @@ void SpirvEmitter::HandleTranslationUnit(ASTContext &context) { return; } + // Pre-detect whether the resource-heap array stride must include the + // acceleration structure descriptor size. + // + // getResourceHeapArrayStride() caches its result on the first call. All + // resource runtime arrays created during code-gen share that one cached + // stride instruction pointer. To make the value correct the decision must + // be made here, before any descriptor-heap subscript expression is evaluated. + // + // Specifically, needsAccelStruct is set true under any of three conditions: + // + // Condition 1, ray-tracing entry point: any workQueue entry is an RT stage. + // RT stages unconditionally emit OpCapability RayTracingKHR; that capability + // is what permits OpConstantSizeOfEXT on AccelerationStructureKHR, so the + // instruction is safe to emit. + // + // Condition 2, explicit KHR_ray_tracing / NV_ray_tracing extension: a + // compute or graphics shader declared one of these extensions explicitly, + // meaning it may use acceleration structures via descriptor heap. + // + // Condition 3, explicit KHR_ray_query extension: same rationale for + // RayQuery users. + // + // isExtensionEnabled() cannot be used without the guard: in default mode + // (no -fspv-extension flags), FeatureManager enables all by-default + // extensions (including KHR_ray_tracing and KHR_ray_query) causing false + // positives. The guard !spirvOptions.allowedExtensions.empty() reliably + // distinguishes "user-listed" from "allowed by default." + // + // Note: the global-decl pass above runs before this block. HLSL forbids + // descriptor-heap access in global initializers, so + // getResourceHeapArrayStride() cannot be called there. If that restriction is + // ever lifted, this block should be moved before that pass. + if (spirvOptions.useDescriptorHeap) { + bool needsAccelStruct = false; + + for (const FunctionInfo *fi : workQueue) + if (shaderModelKindIsRayTracing(fi->shaderModelKind)) { + needsAccelStruct = true; + break; + } + + if (!needsAccelStruct && !spirvOptions.allowedExtensions.empty()) + needsAccelStruct = + featureManager.isExtensionEnabled(Extension::KHR_ray_tracing) || + featureManager.isExtensionEnabled(Extension::NV_ray_tracing) || + featureManager.isExtensionEnabled(Extension::KHR_ray_query); + + if (needsAccelStruct) + spvBuilder.noteResourceHeapHasAccelStruct(); + } + // Translate all functions reachable from the entry function. // The queue can grow in the meanwhile; so need to keep evaluating // workQueue.size(). @@ -7187,6 +7255,21 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, return nullptr; } + // The resource-heap stride is decided before the code-gen loop and is + // frozen on its first use, so it cannot be widened here. Reject rather + // than emit a stride that may be too narrow for the acceleration + // structure descriptor. + if (isRaytracingAccelerationStructure(resourceType) && + !spvBuilder.resourceHeapStrideIncludesAccelStruct()) { + emitError("acceleration structure loaded from ResourceDescriptorHeap " + "requires the resource heap stride to account for " + "acceleration structure descriptors; compile with " + "-fspv-extension=SPV_KHR_ray_tracing or " + "-fspv-extension=SPV_KHR_ray_query", + expr->getExprLoc()); + return nullptr; + } + if (isAKindOfStructuredOrByteBuffer(resourceType) || isConstantTextureBuffer(resourceType)) return emitDescriptorHeapBufferAccess(resourceType, var, index, expr, @@ -9419,13 +9502,19 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { const SpirvType * SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType) { - // SPV_EXT_descriptor_heap: apply a client-API-defined byte stride via an - // ArrayStrideIdEXT decoration. The sampler heap holds a single descriptor - // type, so its stride is the sampler descriptor size. The resource heap is a - // shared flat array in which any resource descriptor may sit at any slot, so - // every resource runtime array must use one common stride: max(sizeof(image), - // sizeof(buffer)). Using the accessed element size would be wrong for the - // resource heap. + // Apply a client-API-defined byte stride via an ArrayStrideIdEXT decoration. + // The sampler heap holds a single descriptor type, so its stride is the + // sampler descriptor size. The resource heap is a shared flat array in which + // any resource descriptor may sit at any slot, so every resource runtime + // array must use one common stride. + // + // [non-RT shaders]: + // max(sizeof(image), sizeof(buffer)) + // [RT shaders]: + // max(sizeof(image), sizeof(buffer), sizeof(acceleration_structure)) + // + // The stride is determined once before the code-gen loop and cached in + // spvBuilder. SpirvInstruction *strideId = isa(elemType) ? spvBuilder.getSamplerHeapArrayStride() : spvBuilder.getResourceHeapArrayStride(); diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl new file mode 100644 index 0000000000..5c94f614b9 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl @@ -0,0 +1,46 @@ +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 -spirv %s 2>&1 | FileCheck %s + +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 \ +// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ +// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ +// RUN: -fspv-extension=SPV_KHR_ray_query \ +// RUN: -spirv %s | FileCheck %s --check-prefix=OK + +// Verifies: an acceleration structure loaded from ResourceDescriptorHeap is +// rejected when the resource-heap stride was not widened to include the +// acceleration structure descriptor size. +// +// The stride is decided before the code-gen loop (see HandleTranslationUnit) +// and frozen on its first use, so it cannot be widened once an acceleration +// structure heap access is reached. Without the widening this shader would +// silently get max(sizeof(image), sizeof(buffer)), which may be too narrow. +// +// A compute shader is not a ray-tracing stage, so the widening only happens +// when the user lists a ray-tracing/ray-query extension explicitly. The first +// run does not, and must fail; the second one does, and must compile. + +// CHECK: error: acceleration structure loaded from ResourceDescriptorHeap requires the resource heap stride to account for acceleration structure descriptors; compile with -fspv-extension=SPV_KHR_ray_tracing or -fspv-extension=SPV_KHR_ray_query + +// OK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR +// OK-DAG: %[[AccelSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Accel]] + +RWBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + RaytracingAccelerationStructure scene = ResourceDescriptorHeap[1]; + + RayDesc ray; + ray.Origin = float3(0.0, 0.0, 0.0); + ray.Direction = float3(0.0, 0.0, 1.0); + ray.TMin = 0.0; + ray.TMax = 1000.0; + + RayQuery q; + q.TraceRayInline(scene, RAY_FLAG_NONE, 0xff, ray); + bool hit = q.Proceed(); + + output[tid.x] = float4(hit ? 1.0 : 0.0, 0.0, 0.0, 0.0); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl new file mode 100644 index 0000000000..aabb9aa5b9 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl @@ -0,0 +1,154 @@ +// Two test paths share this file; select via -D RT_STAGE: +// +// Path A; Condition 1 (RT stage): +// -T lib_6_6 -D RT_STAGE -fspv-extension=SPV_KHR_ray_tracing +// A closesthit entry point is in the workQueue; shaderModelKindIsRayTracing() +// returns true and noteResourceHeapHasAccelStruct() is called unconditionally. +// +// Path B; Condition 3 (explicit KHR_ray_query extension): +// -T cs_6_6 -E main -fspv-extension=SPV_KHR_ray_query +// No RT stage in workQueue, so Condition 1 is skipped. The +// !spirvOptions.allowedExtensions.empty() guard passes (user listed an +// explicit extension), isExtensionEnabled(KHR_ray_query) is true, and +// noteResourceHeapHasAccelStruct() is called. +// +// Both paths verify that the shared resource-heap array stride expands to +// max(max(sizeof(image), sizeof(buffer)), sizeof(accel_struct)) +// and that ALL resource runtime arrays share that three-way max stride. +// +// Ordering stress test: Texture2D is accessed BEFORE the AS in source order. +// The stride cache must already hold sizeof(accel_struct) when the first +// runtime array type is created so the texture array uses the correct stride. +// +// The regression test for the "default-extension-mode" false positive (no +// -fspv-extension flags -> allowedExtensions is empty -> Condition 3 is skipped) +// is sm6_6.descriptorheap.ext.array-stride.hlsl, which must emit exactly 3 +// OpConstantSizeOfEXT (img, buf, sampler - no accel_struct). + +// RUN: %dxc -T lib_6_6 -D RT_STAGE -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 \ +// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ +// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ +// RUN: -fspv-extension=SPV_KHR_ray_tracing \ +// RUN: -spirv %s | FileCheck %s --check-prefixes=CHECK,RT + +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 \ +// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ +// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ +// RUN: -fspv-extension=SPV_KHR_ray_query \ +// RUN: -spirv %s | FileCheck %s + +// RUN: %dxc -T lib_6_6 -D RT_STAGE -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 \ +// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ +// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ +// RUN: -fspv-extension=SPV_KHR_ray_tracing \ +// RUN: -spirv %s | FileCheck %s --check-prefix=SZRT + +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 \ +// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ +// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ +// RUN: -fspv-extension=SPV_KHR_ray_query \ +// RUN: -spirv %s | FileCheck %s --check-prefix=SZRQ + +// --- Types (user-declared + stride-computation placeholders) --------------- +// CHECK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR +// CHECK-DAG: %[[Img:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 0 0 0 1 Unknown + +// UBuf is not user-declared; it is the canonical placeholder injected by +// getResourceHeapArrayStride() for the buffer descriptor size. +// CHECK-DAG: %[[UBuf:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform + +// Sampler only present in path A (closesthit). +// RT-DAG: %[[Samp:[a-zA-Z0-9_]+]] = OpTypeSampler + +// --- Runtime array types --------------------------------------------------- +// CHECK-DAG: %[[AccelArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] +// CHECK-DAG: %[[ImgArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Img]] + +// RT-DAG: %[[SampArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Samp]] + +// --- Size operands --------------------------------------------------------- +// CHECK-DAG: %[[ImgSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Img]] +// CHECK-DAG: %[[BufSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[UBuf]] +// CHECK-DAG: %[[ASSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Accel]] +// RT-DAG: %[[SampSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Samp]] + +// --- Three-way max: max(max(img, buf), accel) identical in both paths ---- +// CHECK-DAG: %[[IB:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[ImgSz]] %[[BufSz]] +// CHECK-DAG: %[[MaxIB:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[IB]] %[[ImgSz]] %[[BufSz]] +// CHECK-DAG: %[[IBA:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[MaxIB]] %[[ASSz]] +// CHECK-DAG: %[[Stride:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[IBA]] %[[MaxIB]] %[[ASSz]] + +// --- All resource arrays share the three-way-max stride ------------------- +// %[[ImgArr]] is created FIRST (texture before AS in source); most important. +// If the stride cache were populated before noteResourceHeapHasAccelStruct() +// was called, ImgArr would be decorated with a two-way max stride, which +// would not match %[[Stride]] (bound to the three-way max above), failing here. +// CHECK-DAG: OpDecorateId %[[ImgArr]] ArrayStrideIdEXT %[[Stride]] +// CHECK-DAG: OpDecorateId %[[AccelArr]] ArrayStrideIdEXT %[[Stride]] + +// --- Sampler stride is independent (path A only) -------------------------- +// RT-DAG: OpDecorateId %[[SampArr]] ArrayStrideIdEXT %[[SampSz]] + +// --- Exact OpConstantSizeOfEXT counts ------------------------------------- +// Path A: img, buf, accel, sampler = 4 +// SZRT-COUNT-4: OpConstantSizeOfEXT %uint +// SZRT-NOT: OpConstantSizeOfEXT %uint + +// Path B: img, buf, accel = 3 (no sampler) +// SZRQ-COUNT-3: OpConstantSizeOfEXT %uint +// SZRQ-NOT: OpConstantSizeOfEXT %uint + +#ifdef RT_STAGE + +struct Payload { float4 color; }; +struct Attribute { float2 bary; }; + +[shader("closesthit")] +void main(inout Payload payload, in Attribute attr) { + // Ordering stress test: access texture first + Texture2D tex = ResourceDescriptorHeap[0]; + RaytracingAccelerationStructure scene = ResourceDescriptorHeap[1]; + SamplerState samp = SamplerDescriptorHeap[0]; + + float4 color = tex.SampleLevel(samp, float2(0.0, 0.0), 0.0); + payload.color = color; + + RayDesc ray; + ray.Origin = float3(0.0, 0.0, 0.0); + ray.Direction = float3(0.0, 0.0, -1.0); + ray.TMin = 0.0; + ray.TMax = 1000.0; + + Payload child = { color }; + TraceRay(scene, 0x0, 0xff, 0, 1, 0, ray, child); +} + +#else // !RT_STAGE, compute shader with RayQuery (path B) + +RWBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + // Ordering stress test: access texture first + Texture2D tex = ResourceDescriptorHeap[0]; + RaytracingAccelerationStructure scene = ResourceDescriptorHeap[1]; + + RayDesc ray; + ray.Origin = float3(0.0, 0.0, 0.0); + ray.Direction = float3(0.0, 0.0, 1.0); + ray.TMin = 0.0; + ray.TMax = 1000.0; + + RayQuery q; + q.TraceRayInline(scene, RAY_FLAG_NONE, 0xff, ray); + bool hit = q.Proceed(); + + int3 coord = int3(tid.x, 0, 0); + output[tid.x] = tex.Load(coord) + float4(hit ? 1.0 : 0.0, 0.0, 0.0, 0.0); +} + +#endif // RT_STAGE \ No newline at end of file From b3c27b381697f609709e90791314d8dc883c34b9 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 3 Aug 2026 10:03:44 -0700 Subject: [PATCH 20/26] Added AS reassignment diagnostic, fixed tests and docs --- docs/SPIR-V.rst | 63 +++++++-- tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 7 + tools/clang/lib/SPIRV/DeclResultIdMapper.h | 3 + tools/clang/lib/SPIRV/SpirvEmitter.cpp | 43 ++++-- tools/clang/lib/SPIRV/SpirvEmitter.h | 24 ++-- ...t.acceleration-structure.stride.error.hlsl | 2 +- ...ptorheap.ext.array-stride.accelstruct.hlsl | 131 ++++++++++-------- 7 files changed, 177 insertions(+), 96 deletions(-) diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 9d04b7e6f8..499f996881 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2143,13 +2143,27 @@ handle into a function-scope image variable: %texel_ptr = OpUntypedImageTexelPointerEXT %uptr_image %image_type %descriptor %coord %sample %old = OpAtomicIAdd %uint %texel_ptr %scope %semantics %value +``RaytracingAccelerationStructure`` resources loaded from +``ResourceDescriptorHeap`` follow the same access-chain-then-load shape over a +runtime array of ``OpTypeAccelerationStructureKHR``: + +.. code:: spirv + + %accel_type = OpTypeAccelerationStructureKHR + %accel_array = OpTypeRuntimeArray %accel_type + OpDecorateId %accel_array ArrayStrideIdEXT %resource_stride + %descriptor = OpUntypedAccessChainKHR %uptr_uc %accel_array %resource_heap %index + %accel = OpLoad %accel_type %descriptor + This path supports texture, RWTexture, sampler, Buffer/RWBuffer, StructuredBuffer/RWStructuredBuffer without associated counter operations, -ByteAddressBuffer/RWByteAddressBuffer, ConstantBuffer, and TextureBuffer heap -loads, including direct field and array-element accesses for -``ConstantBuffer`` and ``TextureBuffer``. ``NonUniformResourceIndex`` is -accepted, but no ``NonUniform`` decoration is emitted on the -``OpUntypedAccessChainKHR`` result or on the loaded descriptor; +ByteAddressBuffer/RWByteAddressBuffer, ConstantBuffer, TextureBuffer, and +``RaytracingAccelerationStructure`` heap loads, including direct field and +array-element accesses for ``ConstantBuffer`` and ``TextureBuffer``. +Acceleration structure loads are additionally subject to the stride requirement +described in `Descriptor heap array stride`_ below. +``NonUniformResourceIndex`` is accepted, but no ``NonUniform`` decoration is +emitted on the ``OpUntypedAccessChainKHR`` result or on the loaded descriptor; ``SPV_EXT_descriptor_heap`` deprecates the decoration for heap accesses and drivers handle divergent heap indices natively. The index operand itself may still carry ``NonUniform`` from the surrounding expression. @@ -2178,13 +2192,15 @@ loop:: Supporting these forms requires modelling the alias as a value with real control-flow merges instead of as compile-time state. -Two further restrictions on the heap access expression itself produce +Three further restrictions on the heap access expression itself produce diagnostics. The object being subscripted must be a direct reference to the -builtin ``ResourceDescriptorHeap`` or ``SamplerDescriptorHeap`` variable, and -the subscript result must be immediately converted to a concrete resource type -so that DXC can select a descriptor type for the access. A subscript whose -result is discarded, or used in a context that supplies no target resource -type, is rejected. +builtin ``ResourceDescriptorHeap`` or ``SamplerDescriptorHeap`` variable; the +subscript result must be immediately converted to a concrete resource type so +that DXC can select a descriptor type for the access, so a subscript whose +result is discarded or used in a context that supplies no target resource type +is rejected; and a local ``RaytracingAccelerationStructure`` must be initialized +from a loadable heap access, since the alias has no backing descriptor +otherwise. Descriptor heap array stride ++++++++++++++++++++++++++++ @@ -2194,7 +2210,8 @@ rather than a literal ``ArrayStride``, because descriptor sizes are not known until pipeline creation. The shared value is built from ``OpConstantSizeOfEXT`` and ``OpSpecConstantOp`` and evaluates to ``max(sizeof(image_descriptor), sizeof(buffer_descriptor))``. The sampler heap -carries its own ``ArrayStrideIdEXT`` equal to ``sizeof(sampler_descriptor)``. +carries its own ``ArrayStrideIdEXT`` equal to ``sizeof(sampler_descriptor)``, +regardless of resource heap contents. The ``OpConstantSizeOfEXT`` operands are placeholder types chosen only for their descriptor class. All image types report the same descriptor size, so the @@ -2203,6 +2220,28 @@ types the shader actually uses; a module will normally contain both the placeholder type and the distinct image types its heap accesses lower to. The stride value is built once and cached on first use. +When acceleration structure descriptors may appear on the resource heap, the +formula expands to a three-way max +``max(max(sizeof(image_descriptor), sizeof(buffer_descriptor)), sizeof(acceleration_structure))``. + +Because the stride is cached on first use, this decision is committed before +code generation and is **not** based on whether the shader actually performs an +acceleration structure heap load: a ray-tracing shader that only heap-loads a +texture still gets the three-way max. The widening happens when either + +- any entry point is a ray-tracing stage, or +- the user explicitly passed ``-fspv-extension=SPV_KHR_ray_tracing``, + ``-fspv-extension=SPV_NV_ray_tracing``, or + ``-fspv-extension=SPV_KHR_ray_query``. + +The second condition requires an explicit ``-fspv-extension`` flag. In the +default extension mode DXC allows the ray-tracing and ray-query extensions +implicitly, but that does not widen the stride. Because the stride cannot be +widened once it has been built, a shader that is not a ray-tracing stage and +loads a ``RaytracingAccelerationStructure`` from ``ResourceDescriptorHeap`` +without an explicit ray extension flag is rejected rather than given a stride +that may be too narrow. + HLSL Expressions ================ diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 62e5ff1e3e..6acfd6a647 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -1142,6 +1142,13 @@ void DeclResultIdMapper::registerFnVarAlias(const VarDecl *var, registerVariableForDecl(var, createDeclSpirvInfo(varInstr)); } +bool DeclResultIdMapper::hasFnVarAlias(const VarDecl *var) const { + const DeclSpirvInfo *info = getDeclSpirvInfo(var); + if (!info || !info->instr) + return false; + return !isa(info->instr); +} + SpirvDebugGlobalVariable *DeclResultIdMapper::createDebugGlobalVariable( SpirvVariable *var, const QualType &type, const SourceLocation &loc, const StringRef &name) { diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.h b/tools/clang/lib/SPIRV/DeclResultIdMapper.h index d6ae75e8fa..778355d2ab 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.h +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.h @@ -288,6 +288,9 @@ class DeclResultIdMapper { /// \brief Registers a function-scope alias to an existing instruction. void registerFnVarAlias(const VarDecl *var, SpirvInstruction *varInstr); + /// \brief Returns true if the decl was registered via registerFnVarAlias, + bool hasFnVarAlias(const VarDecl *var) const; + /// \brief Creates a file-scope variable and returns its instruction. SpirvVariable *createFileVar(const VarDecl *var, llvm::Optional init); diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 903c272b8a..962606490b 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2111,12 +2111,17 @@ bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, } if (isRaytracingAccelerationStructure(decl->getType())) { - if (auto *initVal = loadIfGLValue(init)) + if (SpirvInstruction *initVal = loadIfGLValue(init)) { declIdMapper.registerFnVarAlias(decl, initVal); - else + // Track the AS as heap-initialized so diagnoseDescriptorHeapAliasMixing + // can detect any later reassignment (even heap-to-heap, since + // registerFnVarAlias cannot be updated after the fact). + descriptorHeapVarState[decl] = DescriptorHeapVarState::Heap; + } else { emitError("cannot create descriptor heap acceleration structure alias " "from initializer", init->getExprLoc()); + } return true; } @@ -3297,11 +3302,14 @@ SpirvEmitter::tryToAssignToDescriptorHeapAlias( assignExpr->getExprLoc())) return tryToAssignToDescriptorHeapBuffer(assignExpr); - // The assignment was rejected. A heap buffer alias is represented only by its - // index variable, so the normal path has no destination to store into and - // would null-deref; consume the assignment instead. Image aliases do have a - // function variable, so they can fall through to the plain handle store. - if (descriptorHeapBufferAliasVars.count(dstVar)) + // The assignment was rejected. Buffer aliases (index-only, no backing + // function variable) and AS aliases (value registered via registerFnVarAlias, + // not a SpirvVariable) have no valid destination for processAssignment; + // consume the assignment to prevent a null-deref. Image aliases do have a + // backing function variable, so they can fall through to the plain handle + // store. + if (descriptorHeapBufferAliasVars.count(dstVar) || + declIdMapper.hasFnVarAlias(dstVar)) return static_cast(nullptr); return llvm::None; } @@ -5328,19 +5336,29 @@ bool SpirvEmitter::diagnoseDescriptorHeapAliasMixing(const VarDecl *dstVar, // Only the resource kinds that use the compile-time alias mechanism can be // miscompiled by a mixed assignment. Other resources are stored into a real // function variable, which merges correctly across control flow. + // RaytracingAccelerationStructure uses registerFnVarAlias and is covered + // here too; its Heap state is set in tryToCreateDescriptorHeapAlias. const QualType dstType = dstVar->getType(); - if (!isRWTexture(dstType) && !isRWBuffer(dstType) && + const bool isASType = isRaytracingAccelerationStructure(dstType); + if (!isASType && !isRWTexture(dstType) && !isRWBuffer(dstType) && !isConstantTextureBuffer(dstType) && !isAKindOfStructuredOrByteBuffer(dstType)) return false; const bool srcIsHeap = isHeapSourcedValue(srcExpr->IgnoreParenCasts()); const bool wasHeap = descriptorHeapImageAliasVars.count(dstVar) || - descriptorHeapBufferAliasVars.count(dstVar); + descriptorHeapBufferAliasVars.count(dstVar) || + (stateIt != descriptorHeapVarState.end() && + stateIt->second == DescriptorHeapVarState::Heap); const bool wasBound = stateIt != descriptorHeapVarState.end() && stateIt->second == DescriptorHeapVarState::Bound; - const bool mixingDetected = - (srcIsHeap && wasBound) || (!srcIsHeap && wasHeap); + // AS aliases cannot be updated after initialization (registerFnVarAlias is + // frozen), so any reassignment (including heap-to-heap) must be rejected. + // Image and buffer aliases support heap-to-heap reassignment via their + // respective alias-update paths, so only the cross-kind cases are errors. + const bool mixingDetected = (isASType && wasHeap) || + (srcIsHeap && wasBound) || + (!srcIsHeap && wasHeap); if (mixingDetected) { emitError("mixing bound and descriptor heap resources in the same variable " @@ -7264,7 +7282,8 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, emitError("acceleration structure loaded from ResourceDescriptorHeap " "requires the resource heap stride to account for " "acceleration structure descriptors; compile with " - "-fspv-extension=SPV_KHR_ray_tracing or " + "-fspv-extension=SPV_KHR_ray_tracing, " + "-fspv-extension=SPV_NV_ray_tracing, or " "-fspv-extension=SPV_KHR_ray_query", expr->getExprLoc()); return nullptr; diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index f6cce7b6e4..78cc60d124 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -1254,17 +1254,16 @@ class SpirvEmitter : public ASTConsumer { /// \brief Diagnoses a local resource variable assigned from both a bound /// resource and ResourceDescriptorHeap. /// - /// The alias table maps each aliased VarDecl to heap descriptor info at - /// compile time. Once a variable is recorded as an alias, every later use is - /// re-lowered as a heap access chain, ignoring control flow. That is only - /// correct when the variable holds a heap descriptor on every path reaching - /// the use, so assigning it from both kinds of source must be diagnosed - /// rather than silently miscompiled. + /// The per-resource alias maps (descriptorHeapImageAliasVars, + /// descriptorHeapBufferAliasVars, and the acceleration structure alias + /// registered via registerFnVarAlias) record each aliased VarDecl at compile + /// time. Every later use is unconditionally re-lowered as a heap access + /// chain, ignoring control flow; correct only when the variable holds a heap + /// descriptor on every reaching path. Mixed assignments must therefore be + /// diagnosed rather than generate incorrect code. /// - /// Returns true if the assignment was rejected (either a new diagnostic was - /// emitted, or the variable was already diagnosed). Callers need not act on - /// the return value: the alias-recording helpers check descriptorHeapVarState - /// themselves and skip rejected variables automatically. + /// Returns true if the assignment was rejected. Alias-recording helpers check + /// descriptorHeapVarState themselves and skip Mixed variables automatically. bool diagnoseDescriptorHeapAliasMixing(const VarDecl *dstVar, const Expr *srcExpr, SourceLocation loc); @@ -1714,11 +1713,14 @@ class SpirvEmitter : public ASTConsumer { bool counterUnsupported = false; }; /// Tracks per-variable assignment history for the mixed-alias diagnostic. + /// Heap: the variable was initialized from ResourceDescriptorHeap. For AS + /// variables this is the only state that can follow initialization, + /// since registerFnVarAlias cannot be updated after the fact. /// Bound: the variable has been assigned from a bound resource; a later heap /// assignment to it would be a diagnosable mix. /// Mixed: mixing was already diagnosed; alias recording stays suppressed for /// the remainder of the function so the error fires only once. - enum class DescriptorHeapVarState : uint8_t { Bound, Mixed }; + enum class DescriptorHeapVarState : uint8_t { Heap, Bound, Mixed }; llvm::DenseMap descriptorHeapVarState; diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl index 5c94f614b9..26519a5cb8 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl @@ -21,7 +21,7 @@ // when the user lists a ray-tracing/ray-query extension explicitly. The first // run does not, and must fail; the second one does, and must compile. -// CHECK: error: acceleration structure loaded from ResourceDescriptorHeap requires the resource heap stride to account for acceleration structure descriptors; compile with -fspv-extension=SPV_KHR_ray_tracing or -fspv-extension=SPV_KHR_ray_query +// CHECK: error: acceleration structure loaded from ResourceDescriptorHeap requires the resource heap stride to account for acceleration structure descriptors; compile with -fspv-extension=SPV_KHR_ray_tracing, -fspv-extension=SPV_NV_ray_tracing, or -fspv-extension=SPV_KHR_ray_query // OK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR // OK-DAG: %[[AccelSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Accel]] diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl index aabb9aa5b9..5d39a64730 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.array-stride.accelstruct.hlsl @@ -1,104 +1,115 @@ -// Two test paths share this file; select via -D RT_STAGE: +// Verifies the descriptor-heap array stride when a RaytracingAccelerationStructure +// is present in the resource heap. // -// Path A; Condition 1 (RT stage): -// -T lib_6_6 -D RT_STAGE -fspv-extension=SPV_KHR_ray_tracing -// A closesthit entry point is in the workQueue; shaderModelKindIsRayTracing() -// returns true and noteResourceHeapHasAccelStruct() is called unconditionally. +// When acceleration structures are accessed from ResourceDescriptorHeap, the +// stride formula expands from max(sizeof(image), sizeof(buffer)) to the +// three-way max: max(max(sizeof(image), sizeof(buffer)), sizeof(accel_struct)). +// All resource runtime arrays must share this wider stride so that any slot +// can hold any descriptor type. // -// Path B; Condition 3 (explicit KHR_ray_query extension): -// -T cs_6_6 -E main -fspv-extension=SPV_KHR_ray_query -// No RT stage in workQueue, so Condition 1 is skipped. The -// !spirvOptions.allowedExtensions.empty() guard passes (user listed an -// explicit extension), isExtensionEnabled(KHR_ray_query) is true, and -// noteResourceHeapHasAccelStruct() is called. +// The compiler widens the stride under two conditions: // -// Both paths verify that the shared resource-heap array stride expands to -// max(max(sizeof(image), sizeof(buffer)), sizeof(accel_struct)) -// and that ALL resource runtime arrays share that three-way max stride. +// Path A (RT stage): the shader model is a ray-tracing stage, so any heap +// access may load an acceleration structure. Ray-tracing extensions are +// requested explicitly via -fspv-extension=SPV_KHR_ray_tracing. +// +// Path B (ray query): the SPV_KHR_ray_query extension is explicitly requested +// by the user, signalling that AS descriptors may appear in the heap even +// without a ray-tracing stage. // // Ordering stress test: Texture2D is accessed BEFORE the AS in source order. -// The stride cache must already hold sizeof(accel_struct) when the first -// runtime array type is created so the texture array uses the correct stride. +// If the stride cache were populated before the AS widens it, the texture +// runtime array would be decorated with the narrower two-way max stride. // -// The regression test for the "default-extension-mode" false positive (no -// -fspv-extension flags -> allowedExtensions is empty -> Condition 3 is skipped) -// is sm6_6.descriptorheap.ext.array-stride.hlsl, which must emit exactly 3 -// OpConstantSizeOfEXT (img, buf, sampler - no accel_struct). +// For the no-AS baseline (exactly 3 OpConstantSizeOfEXT: img, buf, sampler) +// see sm6_6.descriptorheap.ext.array-stride.hlsl. -// RUN: %dxc -T lib_6_6 -D RT_STAGE -fspv-use-descriptor-heap \ +// RUN: %dxc -T lib_6_6 -D RT_STAGE -Od -fspv-use-descriptor-heap \ // RUN: -fspv-target-env=vulkan1.3 \ // RUN: -fspv-extension=SPV_EXT_descriptor_heap \ // RUN: -fspv-extension=SPV_KHR_untyped_pointers \ // RUN: -fspv-extension=SPV_KHR_ray_tracing \ // RUN: -spirv %s | FileCheck %s --check-prefixes=CHECK,RT -// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap \ // RUN: -fspv-target-env=vulkan1.3 \ // RUN: -fspv-extension=SPV_EXT_descriptor_heap \ // RUN: -fspv-extension=SPV_KHR_untyped_pointers \ // RUN: -fspv-extension=SPV_KHR_ray_query \ // RUN: -spirv %s | FileCheck %s -// RUN: %dxc -T lib_6_6 -D RT_STAGE -fspv-use-descriptor-heap \ +// RUN: %dxc -T lib_6_6 -D RT_STAGE -Od -fspv-use-descriptor-heap \ // RUN: -fspv-target-env=vulkan1.3 \ // RUN: -fspv-extension=SPV_EXT_descriptor_heap \ // RUN: -fspv-extension=SPV_KHR_untyped_pointers \ // RUN: -fspv-extension=SPV_KHR_ray_tracing \ // RUN: -spirv %s | FileCheck %s --check-prefix=SZRT -// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap \ // RUN: -fspv-target-env=vulkan1.3 \ // RUN: -fspv-extension=SPV_EXT_descriptor_heap \ // RUN: -fspv-extension=SPV_KHR_untyped_pointers \ // RUN: -fspv-extension=SPV_KHR_ray_query \ // RUN: -spirv %s | FileCheck %s --check-prefix=SZRQ -// --- Types (user-declared + stride-computation placeholders) --------------- -// CHECK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR -// CHECK-DAG: %[[Img:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 0 0 0 1 Unknown +// Element (descriptor) types. +// CHECK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR + +// Two distinct OpTypeImage types appear in the output: +// +// 1) ImgPlaceholder: a canonical sampled 2D float image (depth=0) used only as +// the operand to OpConstantSizeOfEXT. All image subtypes report the same +// imageDescriptorSize, so the specific depth field is irrelevant to the query. +// +// 2) TexDesc: the actual lowered type for Texture2D (depth=2, +// WithDepth::Unknown), produced by LowerTypeVisitor for sampled textures. +// This is the element type of the texture heap runtime array. +// +// CHECK-DAG: %[[ImgPlaceholder:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 0 0 0 1 Unknown +// CHECK-DAG: %[[TexDesc:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown -// UBuf is not user-declared; it is the canonical placeholder injected by -// getResourceHeapArrayStride() for the buffer descriptor size. -// CHECK-DAG: %[[UBuf:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform +// UBuf is the canonical buffer placeholder injected for the buffer descriptor +// size query; it is not user-declared. +// CHECK-DAG: %[[UBuf:[a-zA-Z0-9_]+]] = OpTypeBufferEXT Uniform -// Sampler only present in path A (closesthit). +// Sampler only present in path A (RT stage). // RT-DAG: %[[Samp:[a-zA-Z0-9_]+]] = OpTypeSampler -// --- Runtime array types --------------------------------------------------- -// CHECK-DAG: %[[AccelArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] -// CHECK-DAG: %[[ImgArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Img]] - +// Heap runtime arrays of the accessed element types. +// CHECK-DAG: %[[AccelArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] +// CHECK-DAG: %[[ImgArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexDesc]] // RT-DAG: %[[SampArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Samp]] -// --- Size operands --------------------------------------------------------- -// CHECK-DAG: %[[ImgSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Img]] -// CHECK-DAG: %[[BufSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[UBuf]] -// CHECK-DAG: %[[ASSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Accel]] +// Resource stride = max(max(image_size, buffer_size), accel_size). +// The size query uses ImgPlaceholder (depth=0); the driver returns the same +// imageDescriptorSize regardless of which image subtype is used as the operand. +// CHECK-DAG: %[[ImgSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[ImgPlaceholder]] +// CHECK-DAG: %[[BufSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[UBuf]] +// CHECK-DAG: %[[ASSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Accel]] // RT-DAG: %[[SampSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Samp]] -// --- Three-way max: max(max(img, buf), accel) identical in both paths ---- -// CHECK-DAG: %[[IB:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[ImgSz]] %[[BufSz]] -// CHECK-DAG: %[[MaxIB:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[IB]] %[[ImgSz]] %[[BufSz]] -// CHECK-DAG: %[[IBA:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[MaxIB]] %[[ASSz]] -// CHECK-DAG: %[[Stride:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[IBA]] %[[MaxIB]] %[[ASSz]] - -// --- All resource arrays share the three-way-max stride ------------------- -// %[[ImgArr]] is created FIRST (texture before AS in source); most important. -// If the stride cache were populated before noteResourceHeapHasAccelStruct() -// was called, ImgArr would be decorated with a two-way max stride, which -// would not match %[[Stride]] (bound to the three-way max above), failing here. -// CHECK-DAG: OpDecorateId %[[ImgArr]] ArrayStrideIdEXT %[[Stride]] -// CHECK-DAG: OpDecorateId %[[AccelArr]] ArrayStrideIdEXT %[[Stride]] - -// --- Sampler stride is independent (path A only) -------------------------- +// Three-way max: max(max(img, buf), accel), identical in both paths. +// CHECK-DAG: %[[IB:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[ImgSz]] %[[BufSz]] +// CHECK-DAG: %[[MaxIB:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[IB]] %[[ImgSz]] %[[BufSz]] +// CHECK-DAG: %[[IBA:[a-zA-Z0-9_]+]] = OpSpecConstantOp %bool UGreaterThan %[[MaxIB]] %[[ASSz]] +// CHECK-DAG: %[[Stride:[a-zA-Z0-9_]+]] = OpSpecConstantOp %uint Select %[[IBA]] %[[MaxIB]] %[[ASSz]] + +// All resource arrays share the three-way-max stride. +// %[[ImgArr]] is created FIRST (texture before AS in source order); if the +// stride were not yet widened at array-type creation time, this decoration +// would reference the narrower two-way max and the check below would fail. +// CHECK-DAG: OpDecorateId %[[ImgArr]] ArrayStrideIdEXT %[[Stride]] +// CHECK-DAG: OpDecorateId %[[AccelArr]] ArrayStrideIdEXT %[[Stride]] + +// Sampler stride is independent (path A only). // RT-DAG: OpDecorateId %[[SampArr]] ArrayStrideIdEXT %[[SampSz]] -// --- Exact OpConstantSizeOfEXT counts ------------------------------------- -// Path A: img, buf, accel, sampler = 4 +// Exact OpConstantSizeOfEXT counts. +// Path A (RT stage): img, buf, accel, sampler = 4. // SZRT-COUNT-4: OpConstantSizeOfEXT %uint // SZRT-NOT: OpConstantSizeOfEXT %uint -// Path B: img, buf, accel = 3 (no sampler) +// Path B (ray query): img, buf, accel = 3 (no sampler). // SZRQ-COUNT-3: OpConstantSizeOfEXT %uint // SZRQ-NOT: OpConstantSizeOfEXT %uint @@ -109,7 +120,7 @@ struct Attribute { float2 bary; }; [shader("closesthit")] void main(inout Payload payload, in Attribute attr) { - // Ordering stress test: access texture first + // Ordering stress test: access texture first. Texture2D tex = ResourceDescriptorHeap[0]; RaytracingAccelerationStructure scene = ResourceDescriptorHeap[1]; SamplerState samp = SamplerDescriptorHeap[0]; @@ -133,7 +144,7 @@ RWBuffer output : register(u0); [numthreads(1, 1, 1)] void main(uint3 tid : SV_DispatchThreadID) { - // Ordering stress test: access texture first + // Ordering stress test: access texture first. Texture2D tex = ResourceDescriptorHeap[0]; RaytracingAccelerationStructure scene = ResourceDescriptorHeap[1]; @@ -151,4 +162,4 @@ void main(uint3 tid : SV_DispatchThreadID) { output[tid.x] = tex.Load(coord) + float4(hit ? 1.0 : 0.0, 0.0, 0.0, 0.0); } -#endif // RT_STAGE \ No newline at end of file +#endif // RT_STAGE From abba0d5faf6aa39a5fe2fc30e0fa0ca9ff6d174a Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 13:15:50 -0700 Subject: [PATCH 21/26] [SPIR-V] Add release note for descriptor heap RaytracingAccelerationStructure support (#8518) --- docs/ReleaseNotes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index b84287d587..18ec0d1551 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -25,6 +25,9 @@ The included licenses apply to the following files: and `SamplerDescriptorHeap` via `-fspv-use-descriptor-heap`. Requires `-fspv-target-env=vulkan1.3` [#8517](https://github.com/microsoft/DirectXShaderCompiler/pull/8517). +- Extended `-fspv-use-descriptor-heap` to support `RaytracingAccelerationStructure` + loaded from `ResourceDescriptorHeap` + [#8518](https://github.com/microsoft/DirectXShaderCompiler/pull/8518). Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. From a0b84772cb0f21cc2b372a57fa851bb51326a306 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:50 -0700 Subject: [PATCH 22/26] [SPIR-V] Add descriptor heap RaytracingAccelerationStructure support Extends the SPV_EXT_descriptor_heap native heap lowering to cover RaytracingAccelerationStructure resources loaded from ResourceDescriptorHeap. Acceleration structure descriptors are accessed via OpUntypedAccessChainKHR into a runtime array of OpTypeAccelerationStructureKHR, consistent with the image and sampler paths added in the previous commit. --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 962606490b..0b38ff5ef1 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -2125,6 +2125,16 @@ bool SpirvEmitter::tryToCreateDescriptorHeapAlias(const VarDecl *decl, return true; } + if (isRaytracingAccelerationStructure(decl->getType())) { + if (auto *initVal = loadIfGLValue(init)) + declIdMapper.registerFnVarAlias(decl, initVal); + else + emitError("cannot create descriptor heap acceleration structure alias " + "from initializer", + init->getExprLoc()); + return true; + } + return false; } From 2d83642ad95aff95339bb8cd76a189a3b7488242 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 3 Aug 2026 10:03:44 -0700 Subject: [PATCH 23/26] Added AS reassignment diagnostic, fixed tests and docs --- include/dxc/Support/HLSLOptions.td | 4 + include/dxc/Support/SPIRVOptions.h | 7 ++ lib/DxcSupport/HLSLOptions.cpp | 51 +++++++++++- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 22 +++-- tools/clang/lib/SPIRV/SpirvEmitter.h | 11 ++- ...descriptorheap.ext.stride-cli-permute.hlsl | 80 +++++++++++++++++++ .../sm6_6.descriptorheap.ext.stride-cli.hlsl | 19 +++++ 7 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl diff --git a/include/dxc/Support/HLSLOptions.td b/include/dxc/Support/HLSLOptions.td index f35e4809df..0d74ad46fd 100644 --- a/include/dxc/Support/HLSLOptions.td +++ b/include/dxc/Support/HLSLOptions.td @@ -454,6 +454,10 @@ def fvk_bind_sampler_heap : MultiArg<["-"], "fvk-bind-sampler-heap", 2>, MetaVar HelpText<"Specify Vulkan binding number and set number for the sampler heap.">; def fvk_bind_counter_heap : MultiArg<["-"], "fvk-bind-counter-heap", 2>, MetaVarName<" ">, Group, Flags<[CoreOption, DriverOption]>, HelpText<"Specify Vulkan binding number and set number for the counter heap.">; +def fvk_resource_heap_stride : Separate<["-"], "fvk-resource-heap-stride">, MetaVarName<"">, Group, Flags<[CoreOption, DriverOption]>, + HelpText<"Override the byte ArrayStride of the resource descriptor heap runtime array. Must be a power of 2 in [8, 256].">; +def fvk_sampler_heap_stride : Separate<["-"], "fvk-sampler-heap-stride">, MetaVarName<"">, Group, Flags<[CoreOption, DriverOption]>, + HelpText<"Override the byte ArrayStride of the sampler descriptor heap runtime array. Must be a power of 2 in [8, 256].">; // SPIRV Change Ends ////////////////////////////////////////////////////////////////////////////// diff --git a/include/dxc/Support/SPIRVOptions.h b/include/dxc/Support/SPIRVOptions.h index 0253caba63..06b414d372 100644 --- a/include/dxc/Support/SPIRVOptions.h +++ b/include/dxc/Support/SPIRVOptions.h @@ -110,6 +110,13 @@ struct SpirvCodeGenOptions { std::optional samplerHeapBinding; std::optional counterHeapBinding; + // User-defined byte ArrayStride overrides for the resource/sampler descriptor + // heap runtime arrays (-fvk-resource-heap-stride / -fvk-sampler-heap-stride). + // When set, the value is a literal power of 2 in [8, 256] and replaces the + // ArrayStrideIdEXT decoration that heap would otherwise carry. + std::optional resourceHeapStride; + std::optional samplerHeapStride; + bool signaturePacking = false; ///< Whether signature packing is enabled or not diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index 1630243aab..4398a414a6 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -357,6 +357,43 @@ handleFixedBinding(const InputArgList &args, OptSpecifier id, return true; } +// Parses the single-integer descriptor-heap stride flag |id| in |args|. If +// present, validates that the value is a power of 2 in [8, 256] and stores it +// in |stride|. Returns true on success (including when the flag is absent). +// Returns false and writes to |errors| when the value is malformed or invalid, +// using |name| as the pretty flag name. +static bool handleHeapStride(const InputArgList &args, OptSpecifier id, + std::optional *stride, + llvm::StringRef name, llvm::raw_ostream &errors) { + Arg *arg = args.getLastArg(id); + if (!arg) { + *stride = std::nullopt; + return true; + } + + if (!args.hasArg(OPT_spirv)) { + errors << name << " requires -spirv"; + return false; + } + + llvm::StringRef value = arg->getValue(); + uint32_t number = 0; + if (value.getAsInteger(10, number)) { + errors << "invalid " << name << " argument: '" << value << "'"; + return false; + } + // Power of 2 in [8, 256] inclusive. + if (number < 8 || number > 256 || (number & (number - 1)) != 0) { + errors << name + << " must be a power of 2 between 8 and 256 (inclusive); got " + << value; + return false; + } + + *stride = number; + return true; +} + // Check if any options that are unsupported with SPIR-V are used. static bool hasUnsupportedSpirvOption(const InputArgList &args, llvm::raw_ostream &errors) { @@ -1175,6 +1212,16 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, return 1; } + bool strideOk = true; + strideOk &= handleHeapStride(Args, OPT_fvk_resource_heap_stride, + &opts.SpirvOptions.resourceHeapStride, + "-fvk-resource-heap-stride", errors); + strideOk &= handleHeapStride(Args, OPT_fvk_sampler_heap_stride, + &opts.SpirvOptions.samplerHeapStride, + "-fvk-sampler-heap-stride", errors); + if (!strideOk) + return 1; + for (const Arg *A : Args.filtered(OPT_fspv_extension_EQ)) { opts.SpirvOptions.allowedExtensions.push_back(A->getValue()); } @@ -1316,7 +1363,9 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, !Args.getLastArgValue(OPT_fvk_u_shift).empty() || !Args.getLastArgValue(OPT_fvk_bind_resource_heap).empty() || !Args.getLastArgValue(OPT_fvk_bind_sampler_heap).empty() || - !Args.getLastArgValue(OPT_fvk_bind_counter_heap).empty()) { + !Args.getLastArgValue(OPT_fvk_bind_counter_heap).empty() || + !Args.getLastArgValue(OPT_fvk_resource_heap_stride).empty() || + !Args.getLastArgValue(OPT_fvk_sampler_heap_stride).empty()) { errors << "SPIR-V CodeGen not available. " "Please recompile with -DENABLE_SPIRV_CODEGEN=ON."; return 1; diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 0b38ff5ef1..c2b4686baa 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -5591,8 +5591,9 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess( const BufferEXTType *bufferDescriptorType = spvContext.getBufferEXTType(bufferSC); - const SpirvType *arrayType = - getDescriptorHeapRuntimeArrayType(bufferDescriptorType); + // Buffer descriptors are always on the resource heap. + const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType( + bufferDescriptorType, /*onSamplerHeap=*/false); SpirvUntypedAccessChainKHR *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, arrayType, heapVar, index, @@ -7312,8 +7313,9 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, const SpirvType *handleType = lowerTypeVisitor.lowerType(resourceType, SpirvLayoutRule::Void, llvm::None, baseExpr->getExprLoc()); - const SpirvType *arrayType = - getDescriptorHeapRuntimeArrayType(handleType); + // Images and samplers may come from either heap; pick the right stride. + const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType( + handleType, isSamplerDescriptorHeap(decl)); SpirvUntypedAccessChainKHR *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType, arrayType, var, index, @@ -9530,7 +9532,17 @@ void SpirvEmitter::createSpecConstant(const VarDecl *varDecl) { } const SpirvType * -SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType) { +SpirvEmitter::getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap) { + // -fvk-{resource,sampler}-heap-stride has the highest precedence: the array + // carries a literal ArrayStride and no ArrayStrideIdEXT, so none of the + // spec-constant machinery below is reached for that heap. + const std::optional &cliStride = + onSamplerHeap ? spirvOptions.samplerHeapStride + : spirvOptions.resourceHeapStride; + if (cliStride.has_value()) + return spvContext.getRuntimeArrayType(elemType, *cliStride); + // Apply a client-API-defined byte stride via an ArrayStrideIdEXT decoration. // The sampler heap holds a single descriptor type, so its stride is the // sampler descriptor size. The resource heap is a shared flat array in which diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 78cc60d124..96d3cb439b 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -408,10 +408,13 @@ class SpirvEmitter : public ASTConsumer { /// Translates the given varDecl into a spec constant. void createSpecConstant(const VarDecl *varDecl); - /// Returns the OpTypeRuntimeArray for a descriptor heap array of elemType, - /// decorated with ArrayStrideIdEXT referencing an OpConstantSizeOfEXT of the - /// element (descriptor) type. - const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType); + /// Returns the OpTypeRuntimeArray for a descriptor heap array of elemType. + /// onSamplerHeap selects which heap the descriptor is loaded from. If that + /// heap has a -fvk-{resource,sampler}-heap-stride override, the array carries + /// that literal ArrayStride; otherwise it is decorated with ArrayStrideIdEXT + /// referencing the shared stride for the heap. + const SpirvType *getDescriptorHeapRuntimeArrayType(const SpirvType *elemType, + bool onSamplerHeap); /// Emits the native (SPV_EXT_descriptor_heap) access for a buffer-like /// resource (StructuredBuffer/ByteAddressBuffer/ConstantBuffer/TextureBuffer diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl new file mode 100644 index 0000000000..d1e1ff5544 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli-permute.hlsl @@ -0,0 +1,80 @@ +// Verifies: every legal CLI stride value flows independently to each heap's literal ArrayStride +// (full 6x6 cross product, RS!=SS proves no cross-contamination), and invalid values are rejected at option-parse time. +// +// [[RS]] = resource-heap stride, [[SS]] = sampler-heap stride (per-RUN via -D). + +// ---- Full 6x6 cross product of {8,16,32,64,128,256} x {8,16,32,64,128,256} ---- +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=8 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=8 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=8 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=8 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=8 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 8 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=8 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=16 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=16 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=16 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=16 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=16 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 16 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=16 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=32 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=32 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=32 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=32 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=32 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 32 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=32 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=64 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=64 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=64 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=64 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=64 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 64 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=64 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=128 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=128 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=128 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=128 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=128 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=128 -DSS=256 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 8 -spirv %s | FileCheck %s -DRS=256 -DSS=8 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s -DRS=256 -DSS=16 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 32 -spirv %s | FileCheck %s -DRS=256 -DSS=32 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 64 -spirv %s | FileCheck %s -DRS=256 -DSS=64 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 128 -spirv %s | FileCheck %s -DRS=256 -DSS=128 +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 256 -fvk-sampler-heap-stride 256 -spirv %s | FileCheck %s -DRS=256 -DSS=256 + +// ---- Invalid values are rejected at option-parsing time (power-of-two in [8,256]) ---- +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 48 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 4 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 512 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 0 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADRS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-sampler-heap-stride 24 -spirv %s 2>&1 | FileCheck %s --check-prefix=BADSS +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride abc -spirv %s 2>&1 | FileCheck %s --check-prefix=BADNUM + +// ---- Both flags invalid: both errors must be reported (no short-circuit) ---- +// RUN: not %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 48 -fvk-sampler-heap-stride 24 -spirv %s 2>&1 | FileCheck %s --check-prefix=BOTH + +// Bind each heap's runtime array to its element type so the strides are checked +// independently. The resource Texture2D image array carries [[RS]]; the sampler +// array carries [[SS]]. +// CHECK-DAG: %[[TexType:[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 2 0 0 1 Unknown +// CHECK-DAG: %[[SampType:[a-zA-Z0-9_]+]] = OpTypeSampler +// CHECK-DAG: %[[TexArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[TexType]]{{$}} +// CHECK-DAG: %[[SampArray:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[SampType]]{{$}} +// CHECK-DAG: OpDecorate %[[TexArray]] ArrayStride [[RS]] +// CHECK-DAG: OpDecorate %[[SampArray]] ArrayStride [[SS]] + +// BADRS: -fvk-resource-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// BADSS: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// BADNUM: invalid -fvk-resource-heap-stride argument: 'abc' +// Both flags invalid: resource error appears first, sampler error follows on next line. +// BOTH: -fvk-resource-heap-stride must be a power of 2 between 8 and 256 (inclusive) +// BOTH: -fvk-sampler-heap-stride must be a power of 2 between 8 and 256 (inclusive) + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + Texture2D tex = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + float4 c = tex.SampleLevel(samp, float2(0, 0), 0); + outputBytes.Store(0, asuint(c.x)); +} diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl new file mode 100644 index 0000000000..dcd46db0d3 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.stride-cli.hlsl @@ -0,0 +1,19 @@ +// RUN: %dxc -T cs_6_6 -E main -Od -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -fvk-resource-heap-stride 128 -fvk-sampler-heap-stride 16 -spirv %s | FileCheck %s + +// Verifies: -fvk-resource-heap-stride / -fvk-sampler-heap-stride emit a literal +// ArrayStride and suppress the default OpConstantSizeOfEXT stride. + +// CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 128 +// CHECK-DAG: OpDecorate %{{[a-zA-Z0-9_]+}} ArrayStride 16 +// CHECK-NOT: ArrayStrideIdEXT + +RWByteAddressBuffer outputBytes : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + StructuredBuffer sb = ResourceDescriptorHeap[0]; + SamplerState samp = SamplerDescriptorHeap[0]; + Texture2D tex = ResourceDescriptorHeap[1]; + outputBytes.Store(0, sb.Load(tid.x)); + outputBytes.Store(4, (uint)tex.SampleLevel(samp, float2(0, 0), 0).r); +} From 12c46c75ada638295b889bee371dc2f54c0b2032 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Mon, 3 Aug 2026 17:37:33 -0700 Subject: [PATCH 24/26] update docs --- docs/SPIR-V.rst | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 499f996881..2cffe9085e 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2102,9 +2102,10 @@ objects as untyped variables in ``UniformConstant`` storage class: The concrete descriptor type is selected at each heap access. For image, sampler, and texel buffer resources, DXC forms a runtime array of that descriptor type, decorates the array with a byte stride, and uses -``OpUntypedAccessChainKHR`` followed by ``OpLoad``. The stride is an -``ArrayStrideIdEXT`` decoration referencing a specialization constant rather -than a literal ``ArrayStride`` (see `Descriptor heap array stride`_ below): +``OpUntypedAccessChainKHR`` followed by ``OpLoad``. By default the stride is an +``ArrayStrideIdEXT`` decoration referencing a specialization constant; a literal +``ArrayStride`` is emitted only when the stride is overridden on the command +line (see `Descriptor heap array stride`_ below): .. code:: spirv @@ -2205,10 +2206,10 @@ otherwise. Descriptor heap array stride ++++++++++++++++++++++++++++ -All resource heap runtime arrays share a single ``ArrayStrideIdEXT`` decoration -rather than a literal ``ArrayStride``, because descriptor sizes are not known -until pipeline creation. The shared value is built from ``OpConstantSizeOfEXT`` -and ``OpSpecConstantOp`` and evaluates to +By default, all resource heap runtime arrays share a single ``ArrayStrideIdEXT`` +decoration rather than a literal ``ArrayStride``, because descriptor sizes are +not known until pipeline creation. The shared value is built from +``OpConstantSizeOfEXT`` and ``OpSpecConstantOp`` and evaluates to ``max(sizeof(image_descriptor), sizeof(buffer_descriptor))``. The sampler heap carries its own ``ArrayStrideIdEXT`` equal to ``sizeof(sampler_descriptor)``, regardless of resource heap contents. @@ -2242,6 +2243,20 @@ loads a ``RaytracingAccelerationStructure`` from ``ResourceDescriptorHeap`` without an explicit ray extension flag is rejected rather than given a stride that may be too narrow. +The computed stride can be replaced with a fixed literal using +``-fvk-resource-heap-stride `` and ``-fvk-sampler-heap-stride ``, which +emit ``OpDecorate ArrayStride N`` on the resource and sampler heap +arrays respectively. ``N`` must be a power of two in the inclusive range +[8, 256], and both flags require ``-spirv``. The command-line override has the +highest precedence: when it is set for a heap, no ``ArrayStrideIdEXT`` is +emitted for that heap and no ``OpConstantSizeOfEXT`` is built for it. The two +flags are independent, so overriding one heap leaves the other on its computed +stride. + +The literal is not validated against the descriptor sizes of the target +implementation. A value smaller than the largest descriptor that may appear in +the heap produces out-of-bounds descriptor accesses at runtime. + HLSL Expressions ================ From 913e6d45b7755f53105165267de4982d82b11753 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 13:57:12 -0700 Subject: [PATCH 25/26] Suppress heap stride diagnostic when CLI option given --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 11 +++++++--- ...t.acceleration-structure.stride.error.hlsl | 22 ++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index c2b4686baa..44f6216d41 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -7288,14 +7288,19 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, // frozen on its first use, so it cannot be widened here. Reject rather // than emit a stride that may be too narrow for the acceleration // structure descriptor. + // Exception: -fvk-resource-heap-stride supplies a literal stride that + // bypasses the spec-constant machinery entirely, so the user has + // explicitly taken ownership of the stride value. if (isRaytracingAccelerationStructure(resourceType) && - !spvBuilder.resourceHeapStrideIncludesAccelStruct()) { + !spvBuilder.resourceHeapStrideIncludesAccelStruct() && + !spirvOptions.resourceHeapStride.has_value()) { emitError("acceleration structure loaded from ResourceDescriptorHeap " "requires the resource heap stride to account for " "acceleration structure descriptors; compile with " "-fspv-extension=SPV_KHR_ray_tracing, " - "-fspv-extension=SPV_NV_ray_tracing, or " - "-fspv-extension=SPV_KHR_ray_query", + "-fspv-extension=SPV_NV_ray_tracing, " + "-fspv-extension=SPV_KHR_ray_query, or " + "-fvk-resource-heap-stride", expr->getExprLoc()); return nullptr; } diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl index 26519a5cb8..46d218c325 100644 --- a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.ext.acceleration-structure.stride.error.hlsl @@ -8,6 +8,18 @@ // RUN: -fspv-extension=SPV_KHR_ray_query \ // RUN: -spirv %s | FileCheck %s --check-prefix=OK +// Verify -fvk-resource-heap-stride suppresses the diagnostic: the stride is +// user-supplied so no ray-extension pre-scan is needed to widen it. +// The AS runtime array must carry a literal ArrayStride (not ArrayStrideIdEXT) +// and the compiler must not emit OpConstantSizeOfEXT for the AS placeholder. +// RUN: %dxc -T cs_6_6 -E main -fspv-use-descriptor-heap \ +// RUN: -fspv-target-env=vulkan1.3 \ +// RUN: -fspv-extension=SPV_EXT_descriptor_heap \ +// RUN: -fspv-extension=SPV_KHR_untyped_pointers \ +// RUN: -fspv-extension=SPV_KHR_ray_query \ +// RUN: -fvk-resource-heap-stride 256 \ +// RUN: -spirv %s | FileCheck %s --check-prefix=CLI + // Verifies: an acceleration structure loaded from ResourceDescriptorHeap is // rejected when the resource-heap stride was not widened to include the // acceleration structure descriptor size. @@ -21,11 +33,19 @@ // when the user lists a ray-tracing/ray-query extension explicitly. The first // run does not, and must fail; the second one does, and must compile. -// CHECK: error: acceleration structure loaded from ResourceDescriptorHeap requires the resource heap stride to account for acceleration structure descriptors; compile with -fspv-extension=SPV_KHR_ray_tracing, -fspv-extension=SPV_NV_ray_tracing, or -fspv-extension=SPV_KHR_ray_query +// CHECK: error: acceleration structure loaded from ResourceDescriptorHeap requires the resource heap stride to account for acceleration structure descriptors; compile with -fspv-extension=SPV_KHR_ray_tracing, -fspv-extension=SPV_NV_ray_tracing, -fspv-extension=SPV_KHR_ray_query, or -fvk-resource-heap-stride // OK-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR // OK-DAG: %[[AccelSz:[a-zA-Z0-9_]+]] = OpConstantSizeOfEXT %uint %[[Accel]] +// AS runtime array carries the user-supplied literal stride (not ArrayStrideIdEXT), +// and no OpConstantSizeOfEXT is emitted for the AS placeholder. +// CLI-DAG: %[[Accel:[a-zA-Z0-9_]+]] = OpTypeAccelerationStructureKHR +// CLI-DAG: %[[AccelArr:[a-zA-Z0-9_]+]] = OpTypeRuntimeArray %[[Accel]] +// CLI-DAG: OpDecorate %[[AccelArr]] ArrayStride 256 +// CLI-NOT: OpDecorateId %[[AccelArr]] ArrayStrideIdEXT +// CLI-NOT: OpConstantSizeOfEXT %uint %[[Accel]] + RWBuffer output : register(u0); [numthreads(1, 1, 1)] From 4ada70b24385a4b0c6940e462cdae8de43669abf Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Sun, 16 Aug 2026 14:36:03 -0700 Subject: [PATCH 26/26] [SPIR-V] Add release note for descriptor heap stride CLI support (#8519) --- docs/ReleaseNotes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 18ec0d1551..54580473d8 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -28,6 +28,11 @@ The included licenses apply to the following files: - Extended `-fspv-use-descriptor-heap` to support `RaytracingAccelerationStructure` loaded from `ResourceDescriptorHeap` [#8518](https://github.com/microsoft/DirectXShaderCompiler/pull/8518). +- Added `-fvk-resource-heap-stride` and `-fvk-sampler-heap-stride` to override + the `ArrayStride` of the descriptor heap runtime arrays emitted by + `-fspv-use-descriptor-heap`. The value must be a power of two in `[8, 256]` + and takes precedence over the default `OpConstantSizeOfEXT`-based stride. + [#8519](https://github.com/microsoft/DirectXShaderCompiler/pull/8519). Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names.