spirv-val: Add PerVertexKHR (#4807)

diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 233861a..40f2118 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -366,6 +366,11 @@
           return fail(4670) << "storage class must be Input or Output";
         }
         break;
+      case SpvDecorationPerVertexKHR:
+        if (sc != SpvStorageClassInput) {
+          return fail(6777) << "storage class must be Input";
+        }
+        break;
       default:
         break;
     }
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index 9a9a7fb..7f2d648 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -273,6 +273,18 @@
     } else if (dec.dec_type() == SpvDecorationPerTaskNV) {
       has_per_task_nv = true;
     } else if (dec.dec_type() == SpvDecorationPerVertexKHR) {
+      if (!is_fragment) {
+        return _.diag(SPV_ERROR_INVALID_DATA, variable)
+               << _.VkErrorID(6777)
+               << "PerVertexKHR can only be applied to Fragment Execution "
+                  "Models";
+      }
+      if (type->opcode() != SpvOpTypeArray &&
+          type->opcode() != SpvOpTypeRuntimeArray) {
+        return _.diag(SPV_ERROR_INVALID_DATA, variable)
+               << _.VkErrorID(6778)
+               << "PerVertexKHR must be declared as arrays";
+      }
       has_per_vertex_khr = true;
     }
   }
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 87f71ee..d9422b2 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -1937,6 +1937,10 @@
       return VUID_WRAP(VUID-StandaloneSpirv-UniformConstant-06677);
     case 6678:
       return VUID_WRAP(VUID-StandaloneSpirv-InputAttachmentIndex-06678);
+    case 6777:
+      return VUID_WRAP(VUID-StandaloneSpirv-PerVertexKHR-06777);
+    case 6778:
+      return VUID_WRAP(VUID-StandaloneSpirv-Input-06778);
     default:
       return "";  // unknown id
   }
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 2db44a4..e7ecb61 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -8220,6 +8220,149 @@
                         "Offset decorations"));
 }
 
+TEST_F(ValidateDecorations, PerVertexVulkanGood) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %vertexIDs
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+%ptrFloat = OpTypePointer Input %float
+     %uint_3 = OpConstant %uint 3
+%floatArray = OpTypeArray %float %uint_3
+%ptrFloatArray = OpTypePointer Input %floatArray
+  %vertexIDs = OpVariable %ptrFloatArray Input
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+     %access = OpAccessChain %ptrFloat %vertexIDs %int_0
+       %load = OpLoad %float %access
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, PerVertexVulkanOutput) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %vertexIDs
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+%ptrFloat = OpTypePointer Output %float
+     %uint_3 = OpConstant %uint 3
+%floatArray = OpTypeArray %float %uint_3
+%ptrFloatArray = OpTypePointer Output %floatArray
+  %vertexIDs = OpVariable %ptrFloatArray Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+     %access = OpAccessChain %ptrFloat %vertexIDs %int_0
+       %load = OpLoad %float %access
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PerVertexKHR-06777"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("storage class must be Input"));
+}
+
+TEST_F(ValidateDecorations, PerVertexVulkanNonFragment) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %vertexIDs
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+%ptrFloat = OpTypePointer Input %float
+     %uint_3 = OpConstant %uint 3
+%floatArray = OpTypeArray %float %uint_3
+%ptrFloatArray = OpTypePointer Input %floatArray
+  %vertexIDs = OpVariable %ptrFloatArray Input
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+     %access = OpAccessChain %ptrFloat %vertexIDs %int_0
+       %load = OpLoad %float %access
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PerVertexKHR-06777"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "PerVertexKHR can only be applied to Fragment Execution Models"));
+}
+
+TEST_F(ValidateDecorations, PerVertexVulkanNonArray) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %vertexIDs
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+   %ptrFloat = OpTypePointer Input %float
+  %vertexIDs = OpVariable %ptrFloat Input
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+       %load = OpLoad %float %vertexIDs
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Input-06778"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("PerVertexKHR must be declared as arrays"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools