From bfb5defb5b48f40b15d863be34c2332780756f82 Mon Sep 17 00:00:00 2001 From: Jonathan Zakharov Date: Thu, 4 Jun 2026 13:19:50 -0700 Subject: [PATCH 01/16] [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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] [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/16] 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;