Browse Source

[spirv] Add support for [[vk::push_constant]] (#791)

[[vk::push_constant]] can be attached to a global variable of
struct type to put that variable in the PushConstant storage
class.

PushConstant should be of OpTypeStruct type with std430 layout.
Lei Zhang 7 years ago
parent
commit
6788ebea4a

+ 28 - 3
docs/SPIR-V.rst

@@ -152,13 +152,35 @@ Shader Stage Input Output
 
 
 More details in the `gl_PerVertex`_ section.
 More details in the `gl_PerVertex`_ section.
 
 
+Vulkan specific features
+------------------------
+
+We try to implement Vulkan specific features using the most intuitive and
+non-intrusive ways in HLSL, which means we will prefer native language
+constructs when possible. If that is inadequate, we then consider attaching
+`Vulkan specific attributes`_ to them, or introducing new syntax.
+
+Descriptor sets
+~~~~~~~~~~~~~~~
+
+To specify which Vulkan descriptor a particular resource binds to, use the
+``[[vk::binding(X[, Y])]]`` attribute.
+
+Push constants
+~~~~~~~~~~~~~~
+
+Vulkan push constant blocks are represented using normal global variables of
+struct types in HLSL. The variables (not the underlying struct types) should be
+annotated with the ``[[vk::push_constant]]`` attribute.
+
+Please note as per the requirements of Vulkan, "there must be no more than one
+push constant block statically used per shader entry point."
+
 Vulkan specific attributes
 Vulkan specific attributes
 --------------------------
 --------------------------
 
 
-To provide additional information required by Vulkan in HLSL, we need to extend
-the syntax of HLSL.
 `C++ attribute specifier sequence <http://en.cppreference.com/w/cpp/language/attributes>`_
 `C++ attribute specifier sequence <http://en.cppreference.com/w/cpp/language/attributes>`_
-is a non-intrusive way of achieving such purpose.
+is a non-intrusive way of providing Vulkan specific information in HLSL.
 
 
 The namespace ``vk`` will be used for all Vulkan attributes:
 The namespace ``vk`` will be used for all Vulkan attributes:
 
 
@@ -171,6 +193,9 @@ The namespace ``vk`` will be used for all Vulkan attributes:
 - ``counter_binding(X)``: For specifying the binding number (``X``) for the
 - ``counter_binding(X)``: For specifying the binding number (``X``) for the
   associated counter for RW/Append/Consume structured buffer. The descriptor
   associated counter for RW/Append/Consume structured buffer. The descriptor
   set number for the associated counter is always the same as the main resource.
   set number for the associated counter is always the same as the main resource.
+- ``push_constant``: For marking a variable as the push constant block. Allowed
+  on global variables of struct type. At most one variable can be marked as
+  ``push_constant`` in a shader.
 
 
 Only ``vk::`` attributes in the above list are supported. Other attributes will
 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
 result in warnings and be ignored by the compiler. All C++11 attributes will

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

@@ -879,7 +879,7 @@ def VKBinding : InheritableAttr {
   let Documentation = [Undocumented];
   let Documentation = [Undocumented];
 }
 }
 
 
-// StructuredBuffer types that can have associated counters.
+// StructuredBuffer types that can have associated counters
 def CounterStructuredBuffer : SubsetSubject<
 def CounterStructuredBuffer : SubsetSubject<
     Var,
     Var,
     [{S->hasGlobalStorage() && S->getType()->getAs<RecordType>() &&
     [{S->hasGlobalStorage() && S->getType()->getAs<RecordType>() &&
@@ -895,6 +895,17 @@ def VKCounterBinding : InheritableAttr {
   let Documentation = [Undocumented];
   let Documentation = [Undocumented];
 }
 }
 
 
+// Global variables that are of struct type
+def StructGlobalVar : SubsetSubject<Var, [{S->hasGlobalStorage() && S->getType()->isStructureType()}]>;
+
+def VKPushConstant : InheritableAttr {
+  let Spellings = [CXX11<"vk", "push_constant">];
+  let Subjects = SubjectList<[StructGlobalVar], ErrorDiag, "ExpectedStructGlobalVar">;
+  let Args = [];
+  let LangOpts = [SPIRV];
+  let Documentation = [Undocumented];
+}
+
 // SPIRV Change Ends
 // SPIRV Change Ends
 
 
 def C11NoReturn : InheritableAttr {
 def C11NoReturn : InheritableAttr {

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

@@ -2326,6 +2326,7 @@ def warn_attribute_wrong_decl_type : Warning<
   "functions and global variables|structs, unions, and typedefs|structs and typedefs|"
   "functions and global variables|structs, unions, and typedefs|structs and typedefs|"
   "interface or protocol declarations|kernel functions|"
   "interface or protocol declarations|kernel functions|"
   // SPIRV Change Starts
   // SPIRV Change Starts
+  "global variables of struct type|"
   "global variables, cbuffers, and tbuffers|"
   "global variables, cbuffers, and tbuffers|"
   "RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers|"
   "RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers|"
   // SPIRV Change Ends
   // SPIRV Change Ends

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

@@ -856,6 +856,7 @@ enum AttributeDeclKind {
   ExpectedObjectiveCInterfaceOrProtocol,
   ExpectedObjectiveCInterfaceOrProtocol,
   ExpectedKernelFunction
   ExpectedKernelFunction
   // SPIRV Change Begins
   // SPIRV Change Begins
+  ,ExpectedStructGlobalVar
   ,ExpectedGlobalVarOrCTBuffer
   ,ExpectedGlobalVarOrCTBuffer
   ,ExpectedCounterStructuredBuffer
   ,ExpectedCounterStructuredBuffer
   // SPIRV Change Ends
   // SPIRV Change Ends

+ 53 - 22
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -289,20 +289,24 @@ uint32_t DeclResultIdMapper::createExternVar(const VarDecl *var) {
 }
 }
 
 
 uint32_t DeclResultIdMapper::createVarOfExplicitLayoutStruct(
 uint32_t DeclResultIdMapper::createVarOfExplicitLayoutStruct(
-    const DeclContext *decl, llvm::StringRef typeName, llvm::StringRef varName,
-    bool isCBuffer) {
-  LayoutRule layoutRule =
-      isCBuffer ? LayoutRule::GLSLStd140 : LayoutRule::GLSLStd430;
-  const auto *blockDec =
-      isCBuffer ? Decoration::getBlock(*theBuilder.getSPIRVContext())
-                : Decoration::getBufferBlock(*theBuilder.getSPIRVContext());
-
-  // Get the type for the whole buffer
+    const DeclContext *decl, const ContextUsageKind usageKind,
+    llvm::StringRef typeName, llvm::StringRef varName) {
   // cbuffers are translated into OpTypeStruct with Block decoration.
   // cbuffers are translated into OpTypeStruct with Block decoration.
   // tbuffers are translated into OpTypeStruct with BufferBlock decoration.
   // tbuffers are translated into OpTypeStruct with BufferBlock decoration.
+  // PushConstants are translated into OpTypeStruct with Block decoration.
+  //
   // Both cbuffers and tbuffers have the SPIR-V Uniform storage class. cbuffers
   // Both cbuffers and tbuffers have the SPIR-V Uniform storage class. cbuffers
-  // satisfy GLSL std140 layout rules, and tbuffers satisfy GLSL std430 layout
-  // rules.
+  // follow GLSL std140 layout rules, and tbuffers follow GLSL std430 layout
+  // rules. PushConstants follow GLSL std430 layout rules.
+
+  auto &context = *theBuilder.getSPIRVContext();
+  const LayoutRule layoutRule = usageKind == ContextUsageKind::CBuffer
+                                    ? LayoutRule::GLSLStd140
+                                    : LayoutRule::GLSLStd430;
+  const auto *blockDec = usageKind == ContextUsageKind::TBuffer
+                             ? Decoration::getBufferBlock(context)
+                             : Decoration::getBlock(context);
+
   auto decorations = typeTranslator.getLayoutDecorations(decl, layoutRule);
   auto decorations = typeTranslator.getLayoutDecorations(decl, layoutRule);
   decorations.push_back(blockDec);
   decorations.push_back(blockDec);
 
 
@@ -330,26 +334,32 @@ uint32_t DeclResultIdMapper::createVarOfExplicitLayoutStruct(
 
 
     // tbuffer/TextureBuffers are non-writable SSBOs. OpMemberDecorate
     // tbuffer/TextureBuffers are non-writable SSBOs. OpMemberDecorate
     // NonWritable must be applied to all fields.
     // NonWritable must be applied to all fields.
-    if (!isCBuffer) {
+    if (usageKind == ContextUsageKind::TBuffer) {
       decorations.push_back(Decoration::getNonWritable(
       decorations.push_back(Decoration::getNonWritable(
           *theBuilder.getSPIRVContext(), fieldIndex));
           *theBuilder.getSPIRVContext(), fieldIndex));
     }
     }
     ++fieldIndex;
     ++fieldIndex;
   }
   }
 
 
+  // Get the type for the whole struct
   const uint32_t structType =
   const uint32_t structType =
       theBuilder.getStructType(fieldTypes, typeName, fieldNames, decorations);
       theBuilder.getStructType(fieldTypes, typeName, fieldNames, decorations);
 
 
-  // Create the variable for the whole buffer
-  return theBuilder.addModuleVar(structType, spv::StorageClass::Uniform,
-                                 varName);
+  const auto sc = usageKind == ContextUsageKind::PushConstant
+                      ? spv::StorageClass::PushConstant
+                      : spv::StorageClass::Uniform;
+
+  // Create the variable for the whole struct
+  return theBuilder.addModuleVar(structType, sc, varName);
 }
 }
 
 
 uint32_t DeclResultIdMapper::createCTBuffer(const HLSLBufferDecl *decl) {
 uint32_t DeclResultIdMapper::createCTBuffer(const HLSLBufferDecl *decl) {
+  const auto usageKind =
+      decl->isCBuffer() ? ContextUsageKind::CBuffer : ContextUsageKind::TBuffer;
   const std::string structName = "type." + decl->getName().str();
   const std::string structName = "type." + decl->getName().str();
   const std::string varName = "var." + decl->getName().str();
   const std::string varName = "var." + decl->getName().str();
-  const uint32_t bufferVar = createVarOfExplicitLayoutStruct(
-      decl, structName, varName, decl->isCBuffer());
+  const uint32_t bufferVar =
+      createVarOfExplicitLayoutStruct(decl, usageKind, structName, varName);
 
 
   // We still register all VarDecls seperately here. All the VarDecls are
   // We still register all VarDecls seperately here. All the VarDecls are
   // mapped to the <result-id> of the buffer object, which means when querying
   // mapped to the <result-id> of the buffer object, which means when querying
@@ -374,18 +384,20 @@ uint32_t DeclResultIdMapper::createCTBuffer(const VarDecl *decl) {
   const auto *recordType = decl->getType()->getAs<RecordType>();
   const auto *recordType = decl->getType()->getAs<RecordType>();
   assert(recordType);
   assert(recordType);
   const auto *context = cast<HLSLBufferDecl>(decl->getDeclContext());
   const auto *context = cast<HLSLBufferDecl>(decl->getDeclContext());
-  const bool isCBuffer = context->isCBuffer();
+  const auto usageKind = context->isCBuffer() ? ContextUsageKind::CBuffer
+                                              : ContextUsageKind::TBuffer;
 
 
   const std::string structName =
   const std::string structName =
-      "type." + std::string(isCBuffer ? "ConstantBuffer." : "TextureBuffer.") +
+      "type." +
+      std::string(context->isCBuffer() ? "ConstantBuffer." : "TextureBuffer.") +
       recordType->getDecl()->getName().str();
       recordType->getDecl()->getName().str();
   const uint32_t bufferVar = createVarOfExplicitLayoutStruct(
   const uint32_t bufferVar = createVarOfExplicitLayoutStruct(
-      recordType->getDecl(), structName, decl->getName(), isCBuffer);
+      recordType->getDecl(), usageKind, structName, decl->getName());
 
 
   // We register the VarDecl here.
   // We register the VarDecl here.
   astDecls[decl] = {bufferVar, spv::StorageClass::Uniform,
   astDecls[decl] = {bufferVar, spv::StorageClass::Uniform,
-                    isCBuffer ? LayoutRule::GLSLStd140
-                              : LayoutRule::GLSLStd430};
+                    context->isCBuffer() ? LayoutRule::GLSLStd140
+                                         : LayoutRule::GLSLStd430};
   resourceVars.emplace_back(
   resourceVars.emplace_back(
       bufferVar, ResourceVar::Category::Other, getResourceBinding(context),
       bufferVar, ResourceVar::Category::Other, getResourceBinding(context),
       decl->getAttr<VKBindingAttr>(), decl->getAttr<VKCounterBindingAttr>());
       decl->getAttr<VKBindingAttr>(), decl->getAttr<VKCounterBindingAttr>());
@@ -393,6 +405,25 @@ uint32_t DeclResultIdMapper::createCTBuffer(const VarDecl *decl) {
   return bufferVar;
   return bufferVar;
 }
 }
 
 
+uint32_t DeclResultIdMapper::createPushConstant(const VarDecl *decl) {
+  const auto *recordType = decl->getType()->getAs<RecordType>();
+  assert(recordType);
+
+  const std::string structName =
+      "type.PushConstant." + recordType->getDecl()->getName().str();
+  const uint32_t var = createVarOfExplicitLayoutStruct(
+      recordType->getDecl(), ContextUsageKind::PushConstant, structName,
+      decl->getName());
+
+  // Register the VarDecl
+  astDecls[decl] = {var, spv::StorageClass::PushConstant,
+                    LayoutRule::GLSLStd430};
+  // Do not push this variable into resourceVars since it does not need
+  // descriptor set.
+
+  return var;
+}
+
 uint32_t DeclResultIdMapper::getOrRegisterFnResultId(const FunctionDecl *fn) {
 uint32_t DeclResultIdMapper::getOrRegisterFnResultId(const FunctionDecl *fn) {
   if (const auto *info = getDeclSpirvInfo(fn))
   if (const auto *info = getDeclSpirvInfo(fn))
     return info->resultId;
     return info->resultId;

+ 16 - 7
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -203,6 +203,9 @@ public:
   /// VarDecl does not need an extra OpAccessChain.
   /// VarDecl does not need an extra OpAccessChain.
   uint32_t createCTBuffer(const VarDecl *decl);
   uint32_t createCTBuffer(const VarDecl *decl);
 
 
+  /// \brief Creates a PushConstant block from the given decl.
+  uint32_t createPushConstant(const VarDecl *decl);
+
   /// \brief Sets the <result-id> of the entry function.
   /// \brief Sets the <result-id> of the entry function.
   void setEntryFunctionId(uint32_t id) { entryFunctionId = id; }
   void setEntryFunctionId(uint32_t id) { entryFunctionId = id; }
 
 
@@ -282,8 +285,7 @@ private:
   /// \brief Wrapper method to create an error message and report it
   /// \brief Wrapper method to create an error message and report it
   /// in the diagnostic engine associated with this consumer.
   /// in the diagnostic engine associated with this consumer.
   template <unsigned N>
   template <unsigned N>
-  DiagnosticBuilder emitError(const char (&message)[N],
-                              SourceLocation loc) {
+  DiagnosticBuilder emitError(const char (&message)[N], SourceLocation loc) {
     const auto diagId =
     const auto diagId =
         diags.getCustomDiagID(clang::DiagnosticsEngine::Error, message);
         diags.getCustomDiagID(clang::DiagnosticsEngine::Error, message);
     return diags.Report(loc, diagId);
     return diags.Report(loc, diagId);
@@ -301,20 +303,27 @@ private:
   /// construction.
   /// construction.
   bool finalizeStageIOLocations(bool forInput);
   bool finalizeStageIOLocations(bool forInput);
 
 
+  /// \brief An enum class for representing what the DeclContext is used for
+  enum class ContextUsageKind {
+    CBuffer,
+    TBuffer,
+    PushConstant,
+  };
+
   /// Creates a variable of struct type with explicit layout decorations.
   /// Creates a variable of struct type with explicit layout decorations.
   /// The sub-Decls in the given DeclContext will be treated as the struct
   /// The sub-Decls in the given DeclContext will be treated as the struct
   /// fields. The struct type will be named as typeName, and the variable
   /// fields. The struct type will be named as typeName, and the variable
   /// will be named as varName.
   /// will be named as varName.
   ///
   ///
-  /// This method should only be used for cbuffers/ContantBuffers and
-  /// tbuffers/TextureBuffers. isCBuffer must be set appropriately based on the
-  /// type of the buffer.
+  /// This method should only be used for cbuffers/ContantBuffers, tbuffers/
+  /// TextureBuffers, and PushConstants. usageKind must be set properly
+  /// depending on the usage kind.
   ///
   ///
   /// Panics if the DeclContext is neither HLSLBufferDecl or RecordDecl.
   /// Panics if the DeclContext is neither HLSLBufferDecl or RecordDecl.
   uint32_t createVarOfExplicitLayoutStruct(const DeclContext *decl,
   uint32_t createVarOfExplicitLayoutStruct(const DeclContext *decl,
+                                           ContextUsageKind usageKind,
                                            llvm::StringRef typeName,
                                            llvm::StringRef typeName,
-                                           llvm::StringRef varName,
-                                           bool isCBuffer);
+                                           llvm::StringRef varName);
 
 
   /// Creates all the stage variables mapped from semantics on the given decl.
   /// Creates all the stage variables mapped from semantics on the given decl.
   /// Returns true on sucess.
   /// Returns true on sucess.

+ 13 - 6
tools/clang/lib/SPIRV/SPIRVEmitter.cpp

@@ -295,12 +295,7 @@ void SPIRVEmitter::HandleTranslationUnit(ASTContext &context) {
         patchConstFunc = funcDecl;
         patchConstFunc = funcDecl;
       }
       }
     } else if (auto *varDecl = dyn_cast<VarDecl>(decl)) {
     } else if (auto *varDecl = dyn_cast<VarDecl>(decl)) {
-      if (isa<HLSLBufferDecl>(varDecl->getDeclContext())) {
-        // This is a VarDecl of a ConstantBuffer/TextureBuffer type.
-        (void)declIdMapper.createCTBuffer(varDecl);
-      } else {
-        doVarDecl(varDecl);
-      }
+      doVarDecl(varDecl);
     } else if (auto *bufferDecl = dyn_cast<HLSLBufferDecl>(decl)) {
     } else if (auto *bufferDecl = dyn_cast<HLSLBufferDecl>(decl)) {
       // This is a cbuffer/tbuffer decl.
       // This is a cbuffer/tbuffer decl.
 
 
@@ -637,6 +632,18 @@ void SPIRVEmitter::doFunctionDecl(const FunctionDecl *decl) {
 }
 }
 
 
 void SPIRVEmitter::doVarDecl(const VarDecl *decl) {
 void SPIRVEmitter::doVarDecl(const VarDecl *decl) {
+  if (decl->hasAttr<VKPushConstantAttr>()) {
+    // This is a VarDecl for PushConstant block.
+    (void)declIdMapper.createPushConstant(decl);
+    return;
+  }
+
+  if (isa<HLSLBufferDecl>(decl->getDeclContext())) {
+    // This is a VarDecl of a ConstantBuffer/TextureBuffer type.
+    (void)declIdMapper.createCTBuffer(decl);
+    return;
+  }
+
   uint32_t varId = 0;
   uint32_t varId = 0;
 
 
   // The contents in externally visible variables can be updated via the
   // The contents in externally visible variables can be updated via the

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

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

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

@@ -28,6 +28,20 @@ float4 main([[vk::binding(15)]] float4 a: A // error
  return 1.0;
  return 1.0;
 }
 }
 
 
+struct T {
+    [[vk::push_constant]] // error
+    float4 f;
+};
+
+[[vk::push_constant]] // error
+float foo([[vk::push_constant]] int param) // error
+{
+    return param;
+}
+
+[[vk::push_constant(5)]]
+T pcs;
+
 // CHECK:   :4:7: error: 'binding' attribute only applies to global variables, cbuffers, and tbuffers
 // 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:   :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:  :10:3: error: 'counter_binding' attribute only applies to RWStructuredBuffers, AppendStructuredBuffers, and ConsumeStructuredBuffers
@@ -37,3 +51,7 @@ float4 main([[vk::binding(15)]] float4 a: A // error
 // CHECK:  :20: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: :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
 // CHECK:  :25:3: error: 'binding' attribute only applies to global variables, cbuffers, and tbuffers
+// CHECK:  :32:7: error: 'push_constant' attribute only applies to global variables of struct type
+// CHECK: :37:13: error: 'push_constant' attribute only applies to global variables of struct type
+// CHECK:  :36:3: error: 'push_constant' attribute only applies to global variables of struct type
+// CHECK:  :42:3: error: 'push_constant' attribute takes no arguments

+ 35 - 0
tools/clang/test/CodeGenSPIRV/vk.layout.push-constant.std430.hlsl

@@ -0,0 +1,35 @@
+// Run: %dxc -T vs_6_0 -E main
+
+// CHECK: OpDecorate %_arr_v2float_uint_3 ArrayStride 8
+// CHECK: OpDecorate %_arr_mat3v2float_uint_2 ArrayStride 32
+
+// CHECK: OpMemberDecorate %T 0 Offset 0
+// CHECK: OpMemberDecorate %T 1 Offset 32
+// CHECK: OpMemberDecorate %T 1 MatrixStride 16
+// CHECK: OpMemberDecorate %T 1 RowMajor
+struct T {
+                 float2   f1[3];
+    column_major float3x2 f2[2];
+};
+
+// CHECK: OpMemberDecorate %type_PushConstant_S 0 Offset 0
+// CHECK: OpMemberDecorate %type_PushConstant_S 1 Offset 16
+// CHECK: OpMemberDecorate %type_PushConstant_S 2 Offset 32
+// CHECK: OpMemberDecorate %type_PushConstant_S 3 Offset 128
+// CHECK: OpMemberDecorate %type_PushConstant_S 3 MatrixStride 16
+// CHECK: OpMemberDecorate %type_PushConstant_S 3 ColMajor
+
+// CHECK: OpDecorate %type_PushConstant_S Block
+struct S {
+              float    f1;
+              float3   f2;
+              T        f4;
+    row_major float2x3 f3;
+};
+
+[[vk::push_constant]]
+S pcs;
+
+float main() : A {
+    return pcs.f1;
+}

+ 38 - 0
tools/clang/test/CodeGenSPIRV/vk.push-constant.hlsl

@@ -0,0 +1,38 @@
+// Run: %dxc -T vs_6_0 -E main
+
+struct T {
+    float2 val[3];
+};
+
+// CHECK: OpName %type_PushConstant_S "type.PushConstant.S"
+// CHECK: OpMemberName %type_PushConstant_S 0 "f1"
+// CHECK: OpMemberName %type_PushConstant_S 1 "f2"
+// CHECK: OpMemberName %type_PushConstant_S 2 "f3"
+// CHECK: OpMemberName %type_PushConstant_S 3 "f4"
+
+// CHECK: %type_PushConstant_S = OpTypeStruct %float %v3float %mat2v3float %T
+struct S {
+    float    f1;
+    float3   f2;
+    float2x3 f3;
+    T        f4;
+};
+// CHECK: %_ptr_PushConstant_type_PushConstant_S = OpTypePointer PushConstant %type_PushConstant_S
+
+// CHECK: %pcs = OpVariable %_ptr_PushConstant_type_PushConstant_S PushConstant
+[[vk::push_constant]]
+S pcs;
+
+float main() : A {
+    return
+// CHECK:     {{%\d+}} = OpAccessChain %_ptr_PushConstant_float %pcs %int_0
+        pcs.f1 +
+// CHECK: [[ptr:%\d+]] = OpAccessChain %_ptr_PushConstant_v3float %pcs %int_1
+// CHECK:     {{%\d+}} = OpAccessChain %_ptr_PushConstant_float [[ptr]] %int_2
+        pcs.f2.z +
+// CHECK:     {{%\d+}} = OpAccessChain %_ptr_PushConstant_float %pcs %int_2 %uint_1 %uint_2
+        pcs.f3[1][2] +
+// CHECK: [[ptr:%\d+]] = OpAccessChain %_ptr_PushConstant_v2float %pcs %int_3 %int_0 %int_2
+// CHECK:     {{%\d+}} = OpAccessChain %_ptr_PushConstant_float [[ptr]] %int_1
+        pcs.f4.val[2].y;
+}

+ 6 - 0
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -913,6 +913,8 @@ TEST_F(FileTest, VulkanStructuredBufferCounter) {
   runFileTest("vk.binding.counter.hlsl");
   runFileTest("vk.binding.counter.hlsl");
 }
 }
 
 
+TEST_F(FileTest, VulkanPushConstant) { runFileTest("vk.push-constant.hlsl"); }
+
 TEST_F(FileTest, VulkanLayoutCBufferStd140) {
 TEST_F(FileTest, VulkanLayoutCBufferStd140) {
   runFileTest("vk.layout.cbuffer.std140.hlsl");
   runFileTest("vk.layout.cbuffer.std140.hlsl");
 }
 }
@@ -941,6 +943,10 @@ TEST_F(FileTest, VulkanLayoutTextureBufferStd430) {
   runFileTest("vk.layout.texture-buffer.std430.hlsl");
   runFileTest("vk.layout.texture-buffer.std430.hlsl");
 }
 }
 
 
+TEST_F(FileTest, VulkanLayoutPushConstantStd430) {
+  runFileTest("vk.layout.push-constant.std430.hlsl");
+}
+
 // HS: for different Patch Constant Functions
 // HS: for different Patch Constant Functions
 TEST_F(FileTest, HullShaderPCFVoid) { runFileTest("hs.pcf.void.hlsl"); }
 TEST_F(FileTest, HullShaderPCFVoid) { runFileTest("hs.pcf.void.hlsl"); }
 TEST_F(FileTest, HullShaderPCFTakesInputPatch) {
 TEST_F(FileTest, HullShaderPCFTakesInputPatch) {