Skip to content

[SPIR-V] Add descriptor heap -fvk-resource-heap-stride / -fvk-sampler-heap-stride CLI flags#8519

Open
jzakharovnv wants to merge 7 commits into
microsoft:mainfrom
jzakharovnv:pr3-cli-stride-flags
Open

[SPIR-V] Add descriptor heap -fvk-resource-heap-stride / -fvk-sampler-heap-stride CLI flags#8519
jzakharovnv wants to merge 7 commits into
microsoft:mainfrom
jzakharovnv:pr3-cli-stride-flags

Conversation

@jzakharovnv

Copy link
Copy Markdown
Collaborator

Building off of #8518, this PR adds two new command-line flags that override the ArrayStride of the descriptor heap runtime arrays emitted by -fspv-use-descriptor-heap. It is part 3/4 in a series.

-fvk-resource-heap-stride and -fvk-sampler-heap-stride sets the stride for ResourceDescriptorHeap SamplerDescriptorHeap arrays respectively. N and M must be a power of two in [8, 256]. When set, the CLI value takes the highest precedence.

Assisted by an AI agent.

@dnovillo

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff 02e491eb019766b866bcf09c427365713353841b 9c5badc64f7c13c517694a59a9aa068d7d198ac0 -- include/dxc/Support/SPIRVOptions.h lib/DxcSupport/HLSLOptions.cpp tools/clang/include/clang/SPIRV/AstTypeProbe.h tools/clang/include/clang/SPIRV/SpirvBuilder.h tools/clang/include/clang/SPIRV/SpirvContext.h tools/clang/include/clang/SPIRV/SpirvInstruction.h tools/clang/include/clang/SPIRV/SpirvType.h tools/clang/include/clang/SPIRV/SpirvVisitor.h tools/clang/lib/SPIRV/AstTypeProbe.cpp tools/clang/lib/SPIRV/CapabilityVisitor.cpp tools/clang/lib/SPIRV/DeclResultIdMapper.cpp tools/clang/lib/SPIRV/DeclResultIdMapper.h tools/clang/lib/SPIRV/EmitVisitor.cpp tools/clang/lib/SPIRV/EmitVisitor.h tools/clang/lib/SPIRV/LowerTypeVisitor.cpp tools/clang/lib/SPIRV/SpirvBuilder.cpp tools/clang/lib/SPIRV/SpirvContext.cpp tools/clang/lib/SPIRV/SpirvEmitter.cpp tools/clang/lib/SPIRV/SpirvEmitter.h tools/clang/lib/SPIRV/SpirvInstruction.cpp tools/clang/lib/SPIRV/SpirvType.cpp tools/clang/unittests/SPIRV/SpirvContextTest.cpp
View the diff from clang-format here.
diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp
index 3ef3e334..72b17886 100644
--- a/lib/DxcSupport/HLSLOptions.cpp
+++ b/lib/DxcSupport/HLSLOptions.cpp
@@ -384,7 +384,8 @@ static bool handleHeapStride(const InputArgList &args, OptSpecifier id,
   }
   // Power of 2 in [8, 256] inclusive.
   if (number < 8 || number > 256 || (number & (number - 1)) != 0) {
-    errors << name << " must be a power of 2 between 8 and 256 (inclusive); got "
+    errors << name
+           << " must be a power of 2 between 8 and 256 (inclusive); got "
            << value;
     return false;
   }
diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp
index a146a5dd..9fce2c57 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/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp
index c61a572a..4d023a88 100644
--- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp
+++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp
@@ -5106,20 +5106,20 @@ SpirvEmitter::processStructuredBufferLoad(const CXXMemberCallExpr *expr) {
   auto *zero = spvBuilder.getConstantInt(astContext.IntTy, llvm::APInt(32, 0));
   auto *index = doExpr(expr->getArg(0));
 
-  auto *result = 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
+  // 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);
+    result =
+        spvBuilder.createLoad(structType, result, buffer->getExprLoc(), range);
 
   return result;
 }
@@ -5171,8 +5171,8 @@ bool SpirvEmitter::tryToAssignDescriptorHeapImageAlias(
     return false;
 
   const auto *dstVar = dyn_cast<VarDecl>(dstDecl);
-  if (!dstVar || (!isRWTexture(dstVar->getType()) &&
-                  !isRWBuffer(dstVar->getType())))
+  if (!dstVar ||
+      (!isRWTexture(dstVar->getType()) && !isRWBuffer(dstVar->getType())))
     return false;
 
   const auto *src = srcExpr->IgnoreParenCasts();
@@ -5270,7 +5270,7 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapImageTexelPointer(
 
 // 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 
+// (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
@@ -5292,8 +5292,9 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess(
   const SpirvPointerType *bufferDataPointerType = nullptr;
   SpirvLayoutRule layoutRule = spirvOptions.sBufferLayoutRule;
   if (isConstantTextureBuffer(resourceType)) {
-    layoutRule = isConstantBuffer(resourceType) ? spirvOptions.cBufferLayoutRule
-                                                : spirvOptions.tBufferLayoutRule;
+    layoutRule = isConstantBuffer(resourceType)
+                     ? spirvOptions.cBufferLayoutRule
+                     : spirvOptions.tBufferLayoutRule;
     bufferDataPointerType = spvContext.getPointerType(
         bufferDataType, getDescriptorHeapBufferStorageClass(resourceType));
   } else {
@@ -5313,13 +5314,15 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess(
   const spv::StorageClass bufferExtSC = isConstantBuffer(resourceType)
                                             ? spv::StorageClass::Uniform
                                             : spv::StorageClass::StorageBuffer;
-  const SpirvType *bufferDescriptorType = spvContext.getBufferEXTType(bufferExtSC);
+  const SpirvType *bufferDescriptorType =
+      spvContext.getBufferEXTType(bufferExtSC);
   const SpirvType *arrayType =
       getDescriptorHeapRuntimeArrayType(bufferDescriptorType,
                                         /*onSamplerHeap=*/false);
-  SpirvInstruction *untypedAccessChainPtr = spvBuilder.createUntypedAccessChainKHR(
-      untypedUniformConstantType, arrayType, heapVar, index,
-      baseExpr->getExprLoc());
+  SpirvInstruction *untypedAccessChainPtr =
+      spvBuilder.createUntypedAccessChainKHR(untypedUniformConstantType,
+                                             arrayType, heapVar, index,
+                                             baseExpr->getExprLoc());
   SpirvInstruction *bufferDataPtr = spvBuilder.createUnaryOp(
       spv::Op::OpBufferPointerEXT, bufferDataPointerType, untypedAccessChainPtr,
       baseExpr->getExprLoc());
@@ -5332,9 +5335,9 @@ SpirvInstruction *SpirvEmitter::emitDescriptorHeapBufferAccess(
                                 declIdMapper.getInterlockExecutionMode(), {},
                                 baseExpr->getExprLoc());
   }
-  descriptorHeapBufferAccesses[expr] = {bufferDataPointerType, arrayType,
-                                        heapVar,               index,
-                                        indexExpr->getType(),  layoutRule};
+  descriptorHeapBufferAccesses[expr] = {
+      bufferDataPointerType, arrayType, heapVar, index,
+      indexExpr->getType(),  layoutRule};
   return bufferDataPtr;
 }
 
@@ -5370,7 +5373,8 @@ SpirvEmitter::incDecRWACSBufferCounter(const CXXMemberCallExpr *expr,
       (isAppendStructuredBuffer(object->getType()) ||
        isConsumeStructuredBuffer(object->getType()))) {
     emitError("append/consume structured buffers are not supported with "
-      "SPV_EXT_descriptor_heap", expr->getCallee()->getExprLoc());
+              "SPV_EXT_descriptor_heap",
+              expr->getCallee()->getExprLoc());
     return nullptr;
   }
 
@@ -6024,18 +6028,18 @@ SpirvEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr,
     retVal = processTextureLevelOfDetail(expr, /* unclamped */ true);
     break;
   case IntrinsicOp::MOP_IncrementCounter:
-    if (SpirvInstruction *counter = incDecRWACSBufferCounter(expr, /*isInc*/ true))
-      retVal = spvBuilder.createUnaryOp(spv::Op::OpBitcast,
-                                        astContext.UnsignedIntTy, counter,
-                                        expr->getCallee()->getExprLoc(),
-                                        expr->getCallee()->getSourceRange());
+    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 (SpirvInstruction *counter = incDecRWACSBufferCounter(expr, /*isInc*/ false))
-      retVal = spvBuilder.createUnaryOp(spv::Op::OpBitcast,
-                                        astContext.UnsignedIntTy, counter,
-                                        expr->getCallee()->getExprLoc(),
-                                        expr->getCallee()->getSourceRange());
+    if (SpirvInstruction *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(
@@ -6992,8 +6996,9 @@ 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.
+      // 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<DeclRefExpr>(baseExpr->IgnoreCasts());
       const auto *decl =
           declRefExpr ? dyn_cast<VarDecl>(declRefExpr->getDecl()) : nullptr;
@@ -7013,7 +7018,8 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr,
         if (isAppendStructuredBuffer(resourceType) ||
             isConsumeStructuredBuffer(resourceType)) {
           emitError("append/consume structured buffers are not supported with "
-              "SPV_EXT_descriptor_heap", expr->getExprLoc());
+                    "SPV_EXT_descriptor_heap",
+                    expr->getExprLoc());
           return nullptr;
         }
 
@@ -7033,16 +7039,16 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr,
         const SpirvType *arrayType = getDescriptorHeapRuntimeArrayType(
             handleType, isSamplerDescriptorHeap(decl));
         SpirvInstruction *untypedAccessChainPtr =
-            spvBuilder.createUntypedAccessChainKHR(
-                untypedUniformConstantType, arrayType, var, index,
-                baseExpr->getExprLoc());
+            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()};
+          spvBuilder.addExecutionMode(entryFunction,
+                                      declIdMapper.getInterlockExecutionMode(),
+                                      {}, baseExpr->getExprLoc());
+        descriptorHeapImageAccesses[expr] = {
+            untypedAccessChainPtr, handleType, arrayType, var, index,
+            indexExpr->getType()};
         return spvBuilder.createLoad(resourceType, untypedAccessChainPtr,
                                      baseExpr->getExprLoc(), range);
       }
diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h
index ec650c24..7966afea 100644
--- a/tools/clang/lib/SPIRV/SpirvEmitter.h
+++ b/tools/clang/lib/SPIRV/SpirvEmitter.h
@@ -412,12 +412,9 @@ private:
   /// 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);
+  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.
@@ -1226,14 +1223,16 @@ private:
   /// \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.
+  /// \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.
+  /// \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,
@@ -1243,8 +1242,8 @@ private:
   bool tryToAssignDescriptorHeapBufferAlias(const Expr *dstExpr,
                                             const Expr *srcExpr);
 
-  /// \brief Creates the "<name>.descriptor.index" function variable used to remember
-  /// the descriptor heap index of a local resource alias dstVar.
+  /// \brief Creates the "<name>.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
@@ -1253,8 +1252,8 @@ private:
   /// 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
+  /// \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<SpirvInstruction *>
@@ -1267,16 +1266,16 @@ private:
   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
+  /// \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
+  /// \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,
diff --git a/tools/clang/lib/SPIRV/SpirvInstruction.cpp b/tools/clang/lib/SPIRV/SpirvInstruction.cpp
index 0668f247..0f39e24d 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)
@@ -987,8 +987,8 @@ SpirvImageTexelPointer::SpirvImageTexelPointer(QualType resultType,
 
 SpirvUntypedImageTexelPointerEXT::SpirvUntypedImageTexelPointerEXT(
     QualType resultType, SourceLocation loc, const SpirvType *spvImageType,
-    SpirvInstruction *imageInst,
-    SpirvInstruction *coordinateInst, SpirvInstruction *sampleInst)
+    SpirvInstruction *imageInst, SpirvInstruction *coordinateInst,
+    SpirvInstruction *sampleInst)
     : SpirvInstruction(IK_UntypedImageTexelPointerEXT,
                        spv::Op::OpUntypedImageTexelPointerEXT, resultType, loc),
       imageType(spvImageType), image(imageInst), coordinate(coordinateInst),
  • Check this box to apply formatting changes to this branch.

@jzakharovnv jzakharovnv force-pushed the pr3-cli-stride-flags branch 3 times, most recently from 1e2a278 to 5e7b928 Compare June 4, 2026 22:59
@jzakharovnv

Copy link
Copy Markdown
Collaborator Author

@microsoft-github-policy-service agree company="NVIDIA"

@jzakharovnv jzakharovnv force-pushed the pr3-cli-stride-flags branch from 5e7b928 to 9c5badc Compare June 29, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

1 participant