Browse Source

[spirv] Add support for [[vk::counter_binding(X)]] (#730)

A new attribute [[vk::counter_binding(X)]] can be used to specify
the binding number for associated counters for RW/append/consume
structured buffers.

Also added support for .IncrementCounter() and .DecrementCounter()
for RWStructuredBuffer.

Also fixed the type error of OpAtomicI{Add|Sub}.
Lei Zhang 7 years ago
parent
commit
108f1658d2

+ 13 - 1
docs/SPIR-V.rst

@@ -70,6 +70,9 @@ The namespace ``vk`` will be used for all Vulkan attributes:
 - ``binding(X[, Y])``: For specifying the descriptor set (``Y``) and binding
   (``X``) numbers for resource variables. The descriptor set (``Y``) is
   optional; if missing, it will be set to 0. Allowed on global variables.
+- ``counter_binding(X)``: For specifying the binding number (``X``) for the
+  associated counter for RW/Append/Consume structured buffer. The descriptor
+  set number for the associated counter is always the same as the main resource.
 
 Only ``vk::`` attributes in the above list are supported. Other attributes will
 result in warnings and be ignored by the compiler. All C++11 attributes will
@@ -365,6 +368,11 @@ will be translated into
   ; Variable
   %myCbuffer = OpVariable %_ptr_Uniform_type_ConstantBuffer_T Uniform
 
+If ``.IncrementCounter()`` or ``.DecrementCounter()`` is used in the source
+code, an additional associated counter variable will be created for manipulating
+the counter. The counter variable will be of ``OpTypeStruct`` type, which only
+contains a 32-bit integer. The counter variable takes its own binding number.
+
 ``AppendStructuredBuffer`` and ``ConsumeStructuredBuffer``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -656,7 +664,11 @@ Explicit binding number assignment
 
 ``[[vk::binding(X[, Y])]]`` can be attached to global variables to specify the
 descriptor set as ``Y`` and binding number as ``X``. The descriptor set number
-is optional; if missing, it will be zero.
+is optional; if missing, it will be zero. RW/append/consume structured buffers
+have associated counters, which will occupy their own Vulkan descriptors.
+``[vk::counter_binding(Z)]`` can be attached to a RW/append/consume structured
+buffers to specify the binding number for the associated counter to ``Z``. Note
+that the set number of the counter is always the same as the main buffer.
 
 Implicit binding number assignment
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ 17 - 1
tools/clang/include/clang/Basic/Attr.td

@@ -865,12 +865,28 @@ def VKLocation : InheritableAttr {
 
 def VKBinding : InheritableAttr {
   let Spellings = [CXX11<"vk", "binding">];
-  let Subjects = SubjectList<[GlobalVar, HLSLBuffer], ErrorDiag, "ExpectedVariable">;
+  let Subjects = SubjectList<[GlobalVar, HLSLBuffer], ErrorDiag, "ExpectedGlobalVarOrCTBuffer">;
   let Args = [IntArgument<"Binding">, DefaultIntArgument<"Set", 0>];
   let LangOpts = [SPIRV];
   let Documentation = [Undocumented];
 }
 
+// StructuredBuffer types that can have associated counters.
+def CounterStructuredBuffer : SubsetSubject<
+    Var,
+    [{S->hasGlobalStorage() && S->getType()->getAs<RecordType>() &&
+      (S->getType()->getAs<RecordType>()->getDecl()->getName() == "RWStructuredBuffer" ||
+       S->getType()->getAs<RecordType>()->getDecl()->getName() == "AppendStructuredBuffer" ||
+       S->getType()->getAs<RecordType>()->getDecl()->getName() == "ConsumeStructuredBuffer")}]>;
+
+def VKCounterBinding : InheritableAttr {
+  let Spellings = [CXX11<"vk", "counter_binding">];
+  let Subjects = SubjectList<[CounterStructuredBuffer], ErrorDiag, "ExpectedCounterStructuredBuffer">;
+  let Args = [IntArgument<"Binding">];
+  let LangOpts = [SPIRV];
+  let Documentation = [Undocumented];
+}
+
 // SPIRV Change Ends
 
 def C11NoReturn : InheritableAttr {

+ 4 - 0
tools/clang/include/clang/Basic/DiagnosticSemaKinds.td

@@ -2325,6 +2325,10 @@ def warn_attribute_wrong_decl_type : Warning<
   "variables, functions and classes|Objective-C protocols|"
   "functions and global variables|structs, unions, and typedefs|structs and typedefs|"
   "interface or protocol declarations|kernel functions|"
+  // SPIRV Change Starts
+  "global variables, cbuffers, and tbuffers|"
+  "RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers|"
+  // SPIRV Change Ends
   // HLSL Change Starts - add 3 more enum values
   "varibales and parameters|functions, parameters, and fields|"
   "functions, variables, parameters, fields, and types}1">,

+ 4 - 0
tools/clang/include/clang/Sema/AttributeList.h

@@ -855,6 +855,10 @@ enum AttributeDeclKind {
   ExpectedStructOrTypedef,
   ExpectedObjectiveCInterfaceOrProtocol,
   ExpectedKernelFunction
+  // SPIRV Change Begins
+  ,ExpectedGlobalVarOrCTBuffer
+  ,ExpectedCounterStructuredBuffer
+  // SPIRV Change Ends
   // HLSL Change Begins - add attribute decl combinations
   ,ExpectedVariableOrParam,
   ExpectedFunctionOrParamOrField,

+ 121 - 65
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -158,21 +158,18 @@ uint32_t DeclResultIdMapper::createExternVar(const VarDecl *var) {
   const uint32_t id = theBuilder.addModuleVar(varType, storageClass,
                                               var->getName(), llvm::None);
   astDecls[var] = {id, storageClass, rule};
-  resourceVars.emplace_back(id, getResourceCategory(var->getType()),
-                            getResourceBinding(var),
-                            var->getAttr<VKBindingAttr>());
 
-  if (isACSBuffer) {
-    // For {Append|Consume}StructuredBuffer, we need to create another variable
-    // for its associated counter.
-    const uint32_t counterType = typeTranslator.getACSBufferCounter();
-    const std::string counterName = "counter.var." + var->getName().str();
-    const uint32_t counterId =
-        theBuilder.addModuleVar(counterType, storageClass, counterName);
+  const auto *regAttr = getResourceBinding(var);
+  const auto *bindingAttr = var->getAttr<VKBindingAttr>();
+  const auto *counterBindingAttr = var->getAttr<VKCounterBindingAttr>();
+
+  resourceVars.emplace_back(id, getResourceCategory(var->getType()), regAttr,
+                            bindingAttr, counterBindingAttr);
 
-    resourceVars.emplace_back(counterId, ResourceVar::Category::Other, nullptr,
-                              nullptr);
-    counterVars[var] = counterId;
+  if (isACSBuffer) {
+    // For {Append|Consume}StructuredBuffer, we need to always create another
+    // variable for its associated counter.
+    createCounterVar(var);
   }
 
   return id;
@@ -237,9 +234,9 @@ uint32_t DeclResultIdMapper::createCTBuffer(const HLSLBufferDecl *decl) {
     astDecls[varDecl] = {bufferVar, spv::StorageClass::Uniform,
                          LayoutRule::GLSLStd140, index++};
   }
-  resourceVars.emplace_back(bufferVar, ResourceVar::Category::Other,
-                            getResourceBinding(decl),
-                            decl->getAttr<VKBindingAttr>());
+  resourceVars.emplace_back(
+      bufferVar, ResourceVar::Category::Other, getResourceBinding(decl),
+      decl->getAttr<VKBindingAttr>(), decl->getAttr<VKCounterBindingAttr>());
 
   return bufferVar;
 }
@@ -260,9 +257,9 @@ uint32_t DeclResultIdMapper::createCTBuffer(const VarDecl *decl) {
   // TODO: std140 rules may not suit tbuffers.
   astDecls[decl] = {bufferVar, spv::StorageClass::Uniform,
                     LayoutRule::GLSLStd140};
-  resourceVars.emplace_back(bufferVar, ResourceVar::Category::Other,
-                            getResourceBinding(context),
-                            decl->getAttr<VKBindingAttr>());
+  resourceVars.emplace_back(
+      bufferVar, ResourceVar::Category::Other, getResourceBinding(context),
+      decl->getAttr<VKBindingAttr>(), decl->getAttr<VKCounterBindingAttr>());
 
   return bufferVar;
 }
@@ -277,11 +274,25 @@ uint32_t DeclResultIdMapper::getOrRegisterFnResultId(const FunctionDecl *fn) {
   return id;
 }
 
-uint32_t DeclResultIdMapper::getCounterId(const VarDecl *decl) {
+uint32_t DeclResultIdMapper::getOrCreateCounterId(const ValueDecl *decl) {
   const auto counter = counterVars.find(decl);
   if (counter != counterVars.end())
     return counter->second;
-  return 0;
+  return createCounterVar(decl);
+}
+
+uint32_t DeclResultIdMapper::createCounterVar(const ValueDecl *decl) {
+  const auto *info = getDeclSpirvInfo(decl);
+  const uint32_t counterType = typeTranslator.getACSBufferCounter();
+  const std::string counterName = "counter.var." + decl->getName().str();
+  const uint32_t counterId =
+      theBuilder.addModuleVar(counterType, info->storageClass, counterName);
+
+  resourceVars.emplace_back(counterId, ResourceVar::Category::Other,
+                            getResourceBinding(decl),
+                            decl->getAttr<VKBindingAttr>(),
+                            decl->getAttr<VKCounterBindingAttr>(), true);
+  return counterVars[decl] = counterId;
 }
 
 std::vector<uint32_t> DeclResultIdMapper::collectStageVars() const {
@@ -328,8 +339,6 @@ private:
 /// set and binding number.
 class BindingSet {
 public:
-  BindingSet() : nextBinding(0) {}
-
   /// Tries to use the given set and binding number. Returns true if possible,
   /// false otherwise.
   bool tryToUseBinding(uint32_t binding, uint32_t set,
@@ -337,26 +346,28 @@ public:
     const auto cat = static_cast<uint32_t>(category);
     // Note that we will create the entry for binding in bindings[set] here.
     // But that should not have bad effects since it defaults to zero.
-    if ((bindings[set][binding] & cat) == 0) {
-      bindings[set][binding] |= cat;
+    if ((usedBindings[set][binding] & cat) == 0) {
+      usedBindings[set][binding] |= cat;
       return true;
     }
     return false;
   }
 
   /// Uses the next avaiable binding number in set 0.
-  uint32_t useNextBinding(ResourceVar::Category category) {
-    auto &set0bindings = bindings[0];
-    while (set0bindings.count(nextBinding))
-      nextBinding++;
-    set0bindings[nextBinding] = static_cast<uint32_t>(category);
-    return nextBinding++;
+  uint32_t useNextBinding(uint32_t set, ResourceVar::Category category) {
+    auto &binding = usedBindings[set];
+    auto &next = nextBindings[set];
+    while (binding.count(next))
+      ++next;
+    binding[next] = static_cast<uint32_t>(category);
+    return next++;
   }
 
 private:
   ///< set number -> (binding number -> resource category)
-  llvm::DenseMap<uint32_t, llvm::DenseMap<uint32_t, uint32_t>> bindings;
-  uint32_t nextBinding; ///< Next available binding number in set 0
+  llvm::DenseMap<uint32_t, llvm::DenseMap<uint32_t, uint32_t>> usedBindings;
+  ///< set number -> next available binding number
+  llvm::DenseMap<uint32_t, uint32_t> nextBindings;
 };
 } // namespace
 
@@ -511,35 +522,74 @@ private:
 }
 
 bool DeclResultIdMapper::decorateResourceBindings() {
+  // For normal resource, we support 3 approaches of setting binding numbers:
+  // - m1: [[vk::binding(...)]]
+  // - m2: :register(...)
+  // - m3: None
+  //
+  // For associated counters, we support 2 approaches:
+  // - c1: [[vk::counter_binding(...)]
+  // - c2: None
+  //
+  // In combination, we need to handle 9 cases:
+  // - 3 cases for nomral resoures (m1, m2, m3)
+  // - 6 cases for associated counters (mX * cY)
+  //
+  // In the following order:
+  // - m1, mX * c1
+  // - m2
+  // - m3, mX * c2
+
   BindingSet bindingSet;
   bool noError = true;
 
-  // Process variables with [[vk::binding(...)]] binding assignment
-  for (const auto &var : resourceVars)
-    if (const auto *vkBinding = var.getBinding()) {
-      const auto cat = var.getCategory();
-      const auto set = vkBinding->getSet();
-      const auto binding = vkBinding->getBinding();
+  // Tries to decorate the given varId of the given category with set number
+  // setNo, binding number bindingNo. Emits error on failure.
+  const auto tryToDecorate = [this, &bindingSet, &noError](
+      const uint32_t varId, const uint32_t setNo, const uint32_t bindingNo,
+      const ResourceVar::Category cat, SourceLocation loc) {
+    if (bindingSet.tryToUseBinding(bindingNo, setNo, cat)) {
+      theBuilder.decorateDSetBinding(varId, setNo, bindingNo);
+    } else {
+      emitError("resource binding #%0 in descriptor set #%1 already assigned",
+                loc)
+          << bindingNo << setNo;
+      noError = false;
+    }
+  };
 
-      if (bindingSet.tryToUseBinding(binding, set, cat)) {
-        theBuilder.decorateDSetBinding(var.getSpirvId(), set, binding);
-      } else {
-        emitError("resource binding #%0 in descriptor set #%1 already assigned",
-                  vkBinding->getLocation())
-            << binding << set;
-        noError = false;
+  for (const auto &var : resourceVars) {
+    if (var.isCounter()) {
+      if (const auto *vkCBinding = var.getCounterBinding()) {
+        // Process mX * c1
+        uint32_t set = 0;
+        if (const auto *vkBinding = var.getBinding())
+          set = vkBinding->getSet();
+        if (const auto *reg = var.getRegister())
+          set = reg->RegisterSpace;
+
+        tryToDecorate(var.getSpirvId(), set, vkCBinding->getBinding(),
+                      var.getCategory(), vkCBinding->getLocation());
+      }
+    } else {
+      if (const auto *vkBinding = var.getBinding()) {
+        // Process m1
+        tryToDecorate(var.getSpirvId(), vkBinding->getSet(),
+                      vkBinding->getBinding(), var.getCategory(),
+                      vkBinding->getLocation());
       }
     }
+  }
 
   BindingShiftMapper bShiftMapper(spirvOptions.bShift);
   BindingShiftMapper tShiftMapper(spirvOptions.tShift);
   BindingShiftMapper sShiftMapper(spirvOptions.sShift);
   BindingShiftMapper uShiftMapper(spirvOptions.uShift);
 
-  // Process variables with register(...) binding assignment
+  // Process m2
   for (const auto &var : resourceVars)
-    if (const auto *reg = var.getRegister())
-      if (!var.getBinding()) {
+    if (!var.isCounter() && !var.getBinding())
+      if (const auto *reg = var.getRegister()) {
         const uint32_t set = reg->RegisterSpace;
         uint32_t binding = reg->RegisterNumber;
         switch (reg->RegisterType) {
@@ -562,24 +612,30 @@ bool DeclResultIdMapper::decorateResourceBindings() {
           llvm_unreachable("unknown register type found");
         }
 
-        const auto cat = var.getCategory();
-
-        if (bindingSet.tryToUseBinding(binding, set, cat)) {
-          theBuilder.decorateDSetBinding(var.getSpirvId(), set, binding);
-        } else {
-          emitError(
-              "resource binding #%0 in descriptor set #%1 already assigned",
-              reg->Loc)
-              << binding << set;
-          noError = false;
-        }
+        tryToDecorate(var.getSpirvId(), set, binding, var.getCategory(),
+                      reg->Loc);
       }
 
-  // Process variables with no binding assignment
-  for (const auto &var : resourceVars)
-    if (!var.getBinding() && !var.getRegister())
-      theBuilder.decorateDSetBinding(
-          var.getSpirvId(), 0, bindingSet.useNextBinding(var.getCategory()));
+  for (const auto &var : resourceVars) {
+    const auto cat = var.getCategory();
+    if (var.isCounter()) {
+      if (!var.getCounterBinding()) {
+        // Process mX * c2
+        uint32_t set = 0;
+        if (const auto *vkBinding = var.getBinding())
+          set = vkBinding->getSet();
+        else if (const auto *reg = var.getRegister())
+          set = reg->RegisterSpace;
+
+        theBuilder.decorateDSetBinding(var.getSpirvId(), set,
+                                       bindingSet.useNextBinding(set, cat));
+      }
+    } else if (!var.getBinding() && !var.getRegister()) {
+      // Process m3
+      theBuilder.decorateDSetBinding(var.getSpirvId(), 0,
+                                     bindingSet.useNextBinding(0, cat));
+    }
+  }
 
   return noError;
 }

+ 24 - 12
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -99,19 +99,25 @@ public:
   };
 
   ResourceVar(uint32_t id, Category cat, const hlsl::RegisterAssignment *r,
-              const VKBindingAttr *b)
-      : varId(id), category(cat), reg(r), binding(b) {}
+              const VKBindingAttr *b, const VKCounterBindingAttr *cb,
+              bool counter = false)
+      : varId(id), category(cat), reg(r), binding(b), counterBinding(cb),
+        isCounterVar(counter) {}
 
   uint32_t getSpirvId() const { return varId; }
   Category getCategory() const { return category; }
   const hlsl::RegisterAssignment *getRegister() const { return reg; }
   const VKBindingAttr *getBinding() const { return binding; }
+  bool isCounter() const { return isCounterVar; }
+  const auto *getCounterBinding() const { return counterBinding; }
 
 private:
-  uint32_t varId;                      ///< <result-id>
-  Category category;                   ///< Resource category
-  const hlsl::RegisterAssignment *reg; ///< HLSL register assignment
-  const VKBindingAttr *binding;        ///< Vulkan binding assignment
+  uint32_t varId;                             ///< <result-id>
+  Category category;                          ///< Resource category
+  const hlsl::RegisterAssignment *reg;        ///< HLSL register assignment
+  const VKBindingAttr *binding;               ///< Vulkan binding assignment
+  const VKCounterBindingAttr *counterBinding; ///< Vulkan counter binding
+  bool isCounterVar;                          ///< Couter variable or not
 };
 
 /// \brief The class containing mappings from Clang frontend Decls to their
@@ -239,8 +245,8 @@ public:
   uint32_t getOrRegisterFnResultId(const FunctionDecl *fn);
 
   /// \brief Returns the associated counter's <result-id> for the given
-  /// {Append|Consume}StructuredBuffer variable.
-  uint32_t getCounterId(const VarDecl *decl);
+  /// {RW|Append|Consume}StructuredBuffer variable.
+  uint32_t getOrCreateCounterId(const ValueDecl *decl);
 
   /// \brief Returns all defined stage (builtin/input/ouput) variables in this
   /// mapper.
@@ -311,15 +317,16 @@ private:
   /// creating a stage input/output variable.
   uint32_t createSpirvStageVar(StageVar *, const llvm::Twine &name);
 
+  /// Creates the associated counter variable for RW/Append/Consume
+  /// structured buffer.
+  uint32_t createCounterVar(const ValueDecl *decl);
+
   /// Returns the proper SPIR-V storage class (Input or Output) for the given
   /// SigPoint.
   spv::StorageClass getStorageClassForSigPoint(const hlsl::SigPoint *);
 
   /// Returns true if the given SPIR-V stage variable has Input storage class.
-  inline bool isInputStorageClass(const StageVar &v) {
-    return getStorageClassForSigPoint(v.getSigPoint()) ==
-           spv::StorageClass::Input;
-  }
+  inline bool isInputStorageClass(const StageVar &v);
 
 private:
   const hlsl::ShaderModel &shaderModel;
@@ -354,6 +361,11 @@ bool DeclResultIdMapper::decorateStageIOLocations() {
   return finalizeStageIOLocations(true) & finalizeStageIOLocations(false);
 }
 
+bool DeclResultIdMapper::isInputStorageClass(const StageVar &v) {
+  return getStorageClassForSigPoint(v.getSigPoint()) ==
+         spv::StorageClass::Input;
+}
+
 } // end namespace spirv
 } // end namespace clang
 

+ 41 - 21
tools/clang/lib/SPIRV/SPIRVEmitter.cpp

@@ -2038,37 +2038,50 @@ SPIRVEmitter::processStructuredBufferLoad(const CXXMemberCallExpr *expr) {
   return info;
 }
 
-SpirvEvalInfo
-SPIRVEmitter::processACSBufferAppendConsume(const CXXMemberCallExpr *expr) {
-  const bool isAppend = expr->getNumArgs() == 1;
-
-  const uint32_t u32Type = theBuilder.getUint32Type();
+uint32_t SPIRVEmitter::incDecRWACSBufferCounter(const CXXMemberCallExpr *expr,
+                                                bool isInc) {
+  const uint32_t i32Type = theBuilder.getInt32Type();
   const uint32_t one = theBuilder.getConstantUint32(1);  // As scope: Device
   const uint32_t zero = theBuilder.getConstantUint32(0); // As memory sema: None
+  const uint32_t sOne = theBuilder.getConstantInt32(1);
 
-  const auto *object = expr->getImplicitObjectArgument();
+  const auto *object =
+      expr->getImplicitObjectArgument()->IgnoreParenNoopCasts(astContext);
   const auto *buffer = cast<DeclRefExpr>(object)->getDecl();
 
-  // Calculate the index we should use for appending the value
-  const uint32_t counterVar = declIdMapper.getCounterId(cast<VarDecl>(buffer));
+  const uint32_t counterVar = declIdMapper.getOrCreateCounterId(buffer);
   const uint32_t counterPtrType = theBuilder.getPointerType(
       theBuilder.getInt32Type(), spv::StorageClass::Uniform);
   const uint32_t counterPtr =
       theBuilder.createAccessChain(counterPtrType, counterVar, {zero});
+
   uint32_t index = 0;
-  if (isAppend) {
-    // For append, we add one to the counter.
-    index = theBuilder.createAtomicOp(spv::Op::OpAtomicIAdd, u32Type,
-                                      counterPtr, one, zero, one);
+  if (isInc) {
+    index = theBuilder.createAtomicOp(spv::Op::OpAtomicIAdd, i32Type,
+                                      counterPtr, one, zero, sOne);
   } else {
-    // For consume, we substract one from the counter. Note that OpAtomicIAdd
-    // returns the value before the addition; so we need to do substraction
-    // again with OpAtomicIAdd's return value.
-    const auto prevIndex = theBuilder.createAtomicOp(
-        spv::Op::OpAtomicISub, u32Type, counterPtr, one, zero, one);
-    index = theBuilder.createBinaryOp(spv::Op::OpISub, u32Type, prevIndex, one);
+    // Note that OpAtomicISub returns the value before the subtraction;
+    // so we need to do substraction again with OpAtomicISub's return value.
+    const auto prev = theBuilder.createAtomicOp(spv::Op::OpAtomicISub, i32Type,
+                                                counterPtr, one, zero, sOne);
+    index = theBuilder.createBinaryOp(spv::Op::OpISub, i32Type, prev, sOne);
   }
 
+  return index;
+}
+
+SpirvEvalInfo
+SPIRVEmitter::processACSBufferAppendConsume(const CXXMemberCallExpr *expr) {
+  const bool isAppend = expr->getNumArgs() == 1;
+
+  const uint32_t zero = theBuilder.getConstantUint32(0);
+
+  const auto *object =
+      expr->getImplicitObjectArgument()->IgnoreParenNoopCasts(astContext);
+  const auto *buffer = cast<DeclRefExpr>(object)->getDecl();
+
+  uint32_t index = incDecRWACSBufferCounter(expr, isAppend);
+
   auto bufferInfo = declIdMapper.getDeclResultId(buffer);
 
   const auto bufferElemTy = hlsl::GetHLSLResourceResultType(object->getType());
@@ -2177,6 +2190,14 @@ SPIRVEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr,
     return processGetDimensions(expr);
   case IntrinsicOp::MOP_CalculateLevelOfDetail:
     return processTextureLevelOfDetail(expr);
+  case IntrinsicOp::MOP_IncrementCounter:
+    return theBuilder.createUnaryOp(
+        spv::Op::OpBitcast, theBuilder.getUint32Type(),
+        incDecRWACSBufferCounter(expr, /*isInc*/ true));
+  case IntrinsicOp::MOP_DecrementCounter:
+    return theBuilder.createUnaryOp(
+        spv::Op::OpBitcast, theBuilder.getUint32Type(),
+        incDecRWACSBufferCounter(expr, /*isInc*/ false));
   case IntrinsicOp::MOP_Append:
   case IntrinsicOp::MOP_Consume:
     return processACSBufferAppendConsume(expr);
@@ -3938,9 +3959,8 @@ SPIRVEmitter::processIntrinsicInterlockedMethod(const CallExpr *expr,
     return argId;
   };
 
-  const auto writeToOutputArg = [&baseType, this](uint32_t toWrite,
-                                                  const CallExpr *callExpr,
-                                                  uint32_t outputArgIndex) {
+  const auto writeToOutputArg = [&baseType, this](
+      uint32_t toWrite, const CallExpr *callExpr, uint32_t outputArgIndex) {
     const auto outputArg = callExpr->getArg(outputArgIndex);
     const auto outputArgType = outputArg->getType();
     if (baseType != outputArgType)

+ 4 - 0
tools/clang/lib/SPIRV/SPIRVEmitter.h

@@ -570,6 +570,10 @@ private:
   /// (RW)StructuredBuffer.Load() method call.
   SpirvEvalInfo processStructuredBufferLoad(const CXXMemberCallExpr *expr);
 
+  /// \brief Increments or decrements the counter for RW/Append/Consume
+  /// structured buffer.
+  uint32_t incDecRWACSBufferCounter(const CXXMemberCallExpr *, bool isInc);
+
   /// \brief Loads numWords 32-bit unsigned integers or stores numWords 32-bit
   /// unsigned integers (based on the doStore parameter) to the given
   /// ByteAddressBuffer. Loading is allowed from a ByteAddressBuffer or

+ 4 - 0
tools/clang/lib/Sema/SemaHLSL.cpp

@@ -10178,6 +10178,10 @@ void hlsl::HandleDeclAttributeForHLSL(Sema &S, Decl *D, const AttributeList &A,
       ValidateAttributeIntArg(S, A), ValidateAttributeIntArg(S, A, 1),
       A.getAttributeSpellingListIndex());
     break;
+  case AttributeList::AT_VKCounterBinding:
+    declAttr = ::new (S.Context) VKCounterBindingAttr(A.getRange(), S.Context,
+      ValidateAttributeIntArg(S, A), A.getAttributeSpellingListIndex());
+    break;
   default:
     Handled = false;
     return;

+ 2 - 2
tools/clang/test/CodeGenSPIRV/method.append-structured-buffer.append.hlsl

@@ -11,7 +11,7 @@ AppendStructuredBuffer<S>      buffer2;
 
 void main(float4 vec: A) {
 // CHECK:      [[counter:%\d+]] = OpAccessChain %_ptr_Uniform_int %counter_var_buffer1 %uint_0
-// CHECK-NEXT: [[index:%\d+]] = OpAtomicIAdd %uint [[counter]] %uint_1 %uint_0 %uint_1
+// CHECK-NEXT: [[index:%\d+]] = OpAtomicIAdd %int [[counter]] %uint_1 %uint_0 %int_1
 // CHECK-NEXT: [[buffer1:%\d+]] = OpAccessChain %_ptr_Uniform_v4float %buffer1 %uint_0 [[index]]
 // CHECK-NEXT: [[vec:%\d+]] = OpLoad %v4float %vec
 // CHECK-NEXT: OpStore [[buffer1]] [[vec]]
@@ -20,7 +20,7 @@ void main(float4 vec: A) {
     S s; // Will use a separate S type without layout decorations
 
 // CHECK-NEXT: [[counter:%\d+]] = OpAccessChain %_ptr_Uniform_int %counter_var_buffer2 %uint_0
-// CHECK-NEXT: [[index:%\d+]] = OpAtomicIAdd %uint [[counter]] %uint_1 %uint_0 %uint_1
+// CHECK-NEXT: [[index:%\d+]] = OpAtomicIAdd %int [[counter]] %uint_1 %uint_0 %int_1
 
 // CHECK-NEXT: [[buffer2:%\d+]] = OpAccessChain %_ptr_Uniform_S %buffer2 %uint_0 [[index]]
 // CHECK-NEXT: [[s:%\d+]] = OpLoad %S_0 %s

+ 4 - 4
tools/clang/test/CodeGenSPIRV/method.consume-structured-buffer.consume.hlsl

@@ -11,8 +11,8 @@ ConsumeStructuredBuffer<S>      buffer2;
 
 float4 main() : A {
 // CHECK:      [[counter:%\d+]] = OpAccessChain %_ptr_Uniform_int %counter_var_buffer1 %uint_0
-// CHECK-NEXT: [[prev:%\d+]] = OpAtomicISub %uint [[counter]] %uint_1 %uint_0 %uint_1
-// CHECK-NEXT: [[index:%\d+]] = OpISub %uint [[prev]] %uint_1
+// CHECK-NEXT: [[prev:%\d+]] = OpAtomicISub %int [[counter]] %uint_1 %uint_0 %int_1
+// CHECK-NEXT: [[index:%\d+]] = OpISub %int [[prev]] %int_1
 // CHECK-NEXT: [[buffer1:%\d+]] = OpAccessChain %_ptr_Uniform_v4float %buffer1 %uint_0 [[index]]
 // CHECK-NEXT: [[val:%\d+]] = OpLoad %v4float [[buffer1]]
 // CHECK-NEXT: OpStore %v [[val]]
@@ -21,8 +21,8 @@ float4 main() : A {
     S s; // Will use a separate S type without layout decorations
 
 // CHECK-NEXT: [[counter:%\d+]] = OpAccessChain %_ptr_Uniform_int %counter_var_buffer2 %uint_0
-// CHECK-NEXT: [[prev:%\d+]] = OpAtomicISub %uint [[counter]] %uint_1 %uint_0 %uint_1
-// CHECK-NEXT: [[index:%\d+]] = OpISub %uint [[prev]] %uint_1
+// CHECK-NEXT: [[prev:%\d+]] = OpAtomicISub %int [[counter]] %uint_1 %uint_0 %int_1
+// CHECK-NEXT: [[index:%\d+]] = OpISub %int [[prev]] %int_1
 
 // CHECK-NEXT: [[buffer2:%\d+]] = OpAccessChain %_ptr_Uniform_S %buffer2 %uint_0 [[index]]
 // CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[buffer2]]

+ 33 - 0
tools/clang/test/CodeGenSPIRV/method.rw-structured-buffer.counter.hlsl

@@ -0,0 +1,33 @@
+// Run: %dxc -T ps_6_0 -E main
+
+struct S {
+    float4 f;
+};
+
+// CHECK: OpMemberDecorate %type_ACSBuffer_counter 0 Offset 0
+// CHECK: OpDecorate %type_ACSBuffer_counter BufferBlock
+
+// CHECK: %type_ACSBuffer_counter = OpTypeStruct %int
+
+// CHECK: %counter_var_wCounter1 = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+// CHECK: %counter_var_wCounter2 = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+RWStructuredBuffer<S> wCounter1;
+RWStructuredBuffer<S> wCounter2;
+// CHECK-NOT: %counter_var_woCounter
+RWStructuredBuffer<S> woCounter;
+
+float4 main() : SV_Target {
+// CHECK:      [[ptr1:%\d+]] = OpAccessChain %_ptr_Uniform_int %counter_var_wCounter1 %uint_0
+// CHECK-NEXT: [[pre1:%\d+]] = OpAtomicIAdd %int [[ptr1]] %uint_1 %uint_0 %int_1
+// CHECK-NEXT: [[val1:%\d+]] = OpBitcast %uint [[pre1]]
+// CHECK-NEXT:                 OpStore %a [[val1]]
+    uint a = wCounter1.IncrementCounter();
+// CHECK:      [[ptr2:%\d+]] = OpAccessChain %_ptr_Uniform_int %counter_var_wCounter2 %uint_0
+// CHECK-NEXT: [[pre2:%\d+]] = OpAtomicISub %int [[ptr2]] %uint_1 %uint_0 %int_1
+// CHECK-NEXT: [[cnt2:%\d+]] = OpISub %int [[pre2]] %int_1
+// CHECK-NEXT: [[val2:%\d+]] = OpBitcast %uint [[cnt2]]
+// CHECK-NEXT:                 OpStore %b [[val2]]
+    uint b = wCounter2.DecrementCounter();
+
+    return woCounter[0].f + float4(a, b, 0, 0);
+}

+ 39 - 0
tools/clang/test/CodeGenSPIRV/vk.attribute.error.hlsl

@@ -0,0 +1,39 @@
+// Run: %dxc -T ps_6_0 -E main
+
+struct S {
+    [[vk::binding(5)]] // error
+    float4 f;
+    [[vk::counter_binding(11)]] // error
+    float4 g;
+};
+
+[[vk::counter_binding(3)]] // error
+SamplerState mySampler;
+[[vk::counter_binding(4)]] // error
+Texture2D    myTexture;
+[[vk::counter_binding(5)]] // error
+StructuredBuffer<S> mySBuffer;
+
+[[vk::location(5)]] // error
+ConsumeStructuredBuffer<S> myCSBuffer;
+
+[[vk::location(12)]] // error
+cbuffer myCBuffer {
+    float field;
+}
+
+[[vk::binding(10)]] // error
+float4 main([[vk::binding(15)]] float4 a: A // error
+           ) : SV_Target {
+ return 1.0;
+}
+
+// CHECK:   :4:7: error: 'binding' attribute only applies to global variables, cbuffers, and tbuffers
+// CHECK:   :6:7: error: 'counter_binding' attribute only applies to RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers
+// CHECK:  :10:3: error: 'counter_binding' attribute only applies to RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers
+// CHECK:  :12:3: error: 'counter_binding' attribute only applies to RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers
+// CHECK:  :14:3: error: 'counter_binding' attribute only applies to RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers
+// CHECK:  :17:3: error: 'location' attribute only applies to functions, parameters, and fields
+// CHECK:  :20:3: error: 'location' attribute only applies to functions, parameters, and fields
+// CHECK: :26:15: error: 'binding' attribute only applies to global variables, cbuffers, and tbuffers
+// CHECK:  :25:3: error: 'binding' attribute only applies to global variables, cbuffers, and tbuffers

+ 64 - 0
tools/clang/test/CodeGenSPIRV/vk.binding.counter.hlsl

@@ -0,0 +1,64 @@
+// Run: %dxc -T ps_6_0 -E main
+
+struct S {
+    float4 f;
+};
+
+// vk::binding + vk::counter_binding
+[[vk::binding(5, 3), vk::counter_binding(10)]]
+RWStructuredBuffer<S> mySBuffer1;
+// CHECK:      OpDecorate %mySBuffer1 DescriptorSet 3
+// CHECK-NEXT: OpDecorate %mySBuffer1 Binding 5
+
+// :register + vk::counter_binding
+[[vk::counter_binding(20)]]
+AppendStructuredBuffer<S> myASBuffer1 : register(u1, space1);
+// CHECK:      OpDecorate %counter_var_myASBuffer1 DescriptorSet 1
+// CHECK-NEXT: OpDecorate %counter_var_myASBuffer1 Binding 20
+
+// none + vk::counter_binding
+[[vk::counter_binding(2)]]
+ConsumeStructuredBuffer<S> myCSBuffer1;
+// CHECK:      OpDecorate %counter_var_myCSBuffer1 DescriptorSet 0
+// CHECK-NEXT: OpDecorate %counter_var_myCSBuffer1 Binding 2
+
+// vk::binding + none
+[[vk::binding(1)]]
+RWStructuredBuffer<S> mySBuffer2;
+// CHECK:      OpDecorate %mySBuffer2 DescriptorSet 0
+// CHECK-NEXT: OpDecorate %mySBuffer2 Binding 1
+
+// CHECK:      OpDecorate %counter_var_mySBuffer1 DescriptorSet 3
+// CHECK-NEXT: OpDecorate %counter_var_mySBuffer1 Binding 10
+
+// CHECK-NEXT: OpDecorate %myASBuffer1 DescriptorSet 1
+// CHECK-NEXT: OpDecorate %myASBuffer1 Binding 1
+
+// :register + none
+AppendStructuredBuffer<S> myASBuffer2 : register(u3, space2);
+// CHECK-NEXT: OpDecorate %myASBuffer2 DescriptorSet 2
+// CHECK-NEXT: OpDecorate %myASBuffer2 Binding 3
+
+// CHECK-NEXT: OpDecorate %myCSBuffer1 DescriptorSet 0
+// CHECK-NEXT: OpDecorate %myCSBuffer1 Binding 0
+
+// CHECK-NEXT: OpDecorate %counter_var_myASBuffer2 DescriptorSet 2
+// CHECK-NEXT: OpDecorate %counter_var_myASBuffer2 Binding 0
+
+// none + none
+ConsumeStructuredBuffer<S> myCSBuffer2;
+// CHECK-NEXT: OpDecorate %myCSBuffer2 DescriptorSet 0
+// CHECK-NEXT: OpDecorate %myCSBuffer2 Binding 3
+
+// CHECK-NEXT: OpDecorate %counter_var_myCSBuffer2 DescriptorSet 0
+// CHECK-NEXT: OpDecorate %counter_var_myCSBuffer2 Binding 4
+
+// CHECK-NEXT: OpDecorate %counter_var_mySBuffer2 DescriptorSet 0
+// CHECK-NEXT: OpDecorate %counter_var_mySBuffer2 Binding 5
+
+float4 main() : SV_Target {
+    uint a = mySBuffer1.IncrementCounter();
+    uint b = mySBuffer2.DecrementCounter();
+
+    return  a + b;
+}

+ 8 - 8
tools/clang/test/CodeGenSPIRV/vk.binding.explicit.hlsl

@@ -56,19 +56,19 @@ ConstantBuffer<S> myConstantBuffer: register(b1, space1);
 RWStructuredBuffer<S> sbuffer2 : register(u6);
 
 // CHECK:      OpDecorate %asbuffer DescriptorSet 1
-// CHECK-NEXT: OpDecorate %asbuffer Binding 20
+// CHECK-NEXT: OpDecorate %asbuffer Binding 1
 // CHECK-NEXT: OpDecorate %csbuffer DescriptorSet 1
 // CHECK-NEXT: OpDecorate %csbuffer Binding 21
-// CHECK-NEXT: OpDecorate %counter_var_asbuffer DescriptorSet 0
-// CHECK-NEXT: OpDecorate %counter_var_asbuffer Binding 1
-// CHECK-NEXT: OpDecorate %counter_var_csbuffer DescriptorSet 0
-// CHECK-NEXT: OpDecorate %counter_var_csbuffer Binding 4
-[[vk::binding(20, 1)]]
+// CHECK-NEXT: OpDecorate %counter_var_asbuffer DescriptorSet 1
+// CHECK-NEXT: OpDecorate %counter_var_asbuffer Binding 0
+// CHECK-NEXT: OpDecorate %counter_var_csbuffer DescriptorSet 1
+// CHECK-NEXT: OpDecorate %counter_var_csbuffer Binding 2
+[[vk::binding(1, 1)]]
 AppendStructuredBuffer<S> asbuffer : register(u10);
-// Next available "hole": binding #1 in set #0
+// Next available "hole" in set #1: binding #0
 [[vk::binding(21, 1)]]
 ConsumeStructuredBuffer<S> csbuffer : register(u11);
-// Next available "hole": binding #4 in set #0
+// Next available "hole" in set #1: binding #2
 
 float4 main() : SV_Target {
     return 1.0;

+ 20 - 7
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -489,6 +489,9 @@ TEST_F(FileTest, StructuredBufferLoad) {
 TEST_F(FileTest, StructuredBufferGetDimensions) {
   runFileTest("method.structured-buffer.get-dimensions.hlsl");
 }
+TEST_F(FileTest, RWStructuredBufferIncDecCounter) {
+  runFileTest("method.rw-structured-buffer.counter.hlsl");
+}
 TEST_F(FileTest, AppendStructuredBufferAppend) {
   runFileTest("method.append-structured-buffer.append.hlsl");
 }
@@ -687,6 +690,17 @@ TEST_F(FileTest, SpirvEntryFunctionInOut) {
   runFileTest("spirv.entry-function.inout.hlsl");
 }
 
+TEST_F(FileTest, SpirvInterpolation) {
+  runFileTest("spirv.interpolation.hlsl");
+}
+TEST_F(FileTest, SpirvInterpolationError) {
+  runFileTest("spirv.interpolation.error.hlsl", /*expectSuccess*/ false);
+}
+
+TEST_F(FileTest, VulkanAttributeErrors) {
+  runFileTest("vk.attribute.error.hlsl", /*expectSuccess*/ false);
+}
+
 TEST_F(FileTest, VulkanLocation) { runFileTest("vk.location.hlsl"); }
 TEST_F(FileTest, VulkanLocationInputExplicitOutputImplicit) {
   runFileTest("vk.location.exp-in.hlsl");
@@ -704,13 +718,6 @@ TEST_F(FileTest, VulkanLocationPartiallyAssigned) {
   runFileTest("vk.location.mixed.hlsl", /*expectSuccess*/ false);
 }
 
-TEST_F(FileTest, SpirvInterpolation) {
-  runFileTest("spirv.interpolation.hlsl");
-}
-TEST_F(FileTest, SpirvInterpolationError) {
-  runFileTest("spirv.interpolation.error.hlsl", /*expectSuccess*/ false);
-}
-
 TEST_F(FileTest, VulkanExplicitBinding) {
   // Resource binding from [[vk::binding()]]
   runFileTest("vk.binding.explicit.hlsl");
@@ -737,6 +744,12 @@ TEST_F(FileTest, VulkanRegisterBindingReassigned) {
 TEST_F(FileTest, VulkanRegisterBindingShiftReassigned) {
   runFileTest("vk.binding.cl.error.hlsl", /*expectSuccess*/ false);
 }
+TEST_F(FileTest, VulkanStructuredBufferCounter) {
+  // [[vk::counter_binding()]] for RWStructuredBuffer, AppendStructuredBuffer,
+  // and ConsumeStructuredBuffer
+  runFileTest("vk.binding.counter.hlsl");
+}
+
 TEST_F(FileTest, VulkanLayoutCBufferStd140) {
   runFileTest("vk.layout.cbuffer.std140.hlsl");
 }