Validate that there is at most one push constant block (#2163)
Fixes #2006
Validates that there is at most one PushConstant interface per entry point for Vulkan environment.
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 508c9cf..c565890 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -781,6 +781,8 @@
}
spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
+ // Set of entry points that are known to use a push constant.
+ std::unordered_set<uint32_t> uses_push_constant;
for (const auto& inst : vstate.ordered_instructions()) {
const auto& words = inst.words();
if (SpvOpVariable == inst.opcode()) {
@@ -794,9 +796,25 @@
const bool push_constant = storageClass == SpvStorageClassPushConstant;
const bool storage_buffer = storageClass == SpvStorageClassStorageBuffer;
- // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
- // UniformConstant which cannot be a struct.
if (spvIsVulkanEnv(vstate.context()->target_env)) {
+ // Vulkan 14.5.1: There must be no more than one PushConstant block
+ // per entry point.
+ if (push_constant) {
+ auto entry_points = vstate.EntryPointReferences(var_id);
+ for (auto ep_id : entry_points) {
+ const bool already_used = !uses_push_constant.insert(ep_id).second;
+ if (already_used) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+ << "Entry point id '" << ep_id
+ << "' uses more than one PushConstant interface.\n"
+ << "From Vulkan spec, section 14.5.1:\n"
+ << "There must be no more than one push constant block "
+ << "statically used per shader entry point.";
+ }
+ }
+ }
+ // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
+ // UniformConstant which cannot be a struct.
if (uniform_constant) {
auto entry_points = vstate.EntryPointReferences(var_id);
if (!entry_points.empty() &&
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index e066193..e9b748b 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -2283,6 +2283,272 @@
"decoration"));
}
+TEST_F(ValidateDecorations, MultiplePushConstantsSingleEntryPointGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %pc1 %int_0
+ %3 = OpLoad %float %2
+ %4 = OpAccessChain %ptr_float %pc2 %int_0
+ %5 = OpLoad %float %4
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsDifferentEntryPointGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "func1"
+ OpEntryPoint Fragment %2 "func2"
+ OpExecutionMode %2 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label1 = OpLabel
+ %3 = OpAccessChain %ptr_float %pc1 %int_0
+ %4 = OpLoad %float %3
+ OpReturn
+ OpFunctionEnd
+
+ %2 = OpFunction %void None %voidfn
+ %label2 = OpLabel
+ %5 = OpAccessChain %ptr_float %pc2 %int_0
+ %6 = OpLoad %float %5
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsUnusedSingleEntryPointGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, VulkanMultiplePushConstantsSingleEntryPointBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %pc1 %int_0
+ %3 = OpLoad %float %2
+ %4 = OpAccessChain %ptr_float %pc2 %int_0
+ %5 = OpLoad %float %4
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr(
+ "Entry point id '1' uses more than one PushConstant interface.\n"
+ "From Vulkan spec, section 14.5.1:\n"
+ "There must be no more than one push constant block "
+ "statically used per shader entry point."));
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsDifferentEntryPointSubFunctionGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "func1"
+ OpEntryPoint Fragment %2 "func2"
+ OpExecutionMode %2 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %sub1 = OpFunction %void None %voidfn
+ %label_sub1 = OpLabel
+ %3 = OpAccessChain %ptr_float %pc1 %int_0
+ %4 = OpLoad %float %3
+ OpReturn
+ OpFunctionEnd
+
+ %sub2 = OpFunction %void None %voidfn
+ %label_sub2 = OpLabel
+ %5 = OpAccessChain %ptr_float %pc2 %int_0
+ %6 = OpLoad %float %5
+ OpReturn
+ OpFunctionEnd
+
+ %1 = OpFunction %void None %voidfn
+ %label1 = OpLabel
+ %call1 = OpFunctionCall %void %sub1
+ OpReturn
+ OpFunctionEnd
+
+ %2 = OpFunction %void None %voidfn
+ %label2 = OpLabel
+ %call2 = OpFunctionCall %void %sub2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsSingleEntryPointSubFunctionBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %sub1 = OpFunction %void None %voidfn
+ %label_sub1 = OpLabel
+ %3 = OpAccessChain %ptr_float %pc1 %int_0
+ %4 = OpLoad %float %3
+ OpReturn
+ OpFunctionEnd
+
+ %sub2 = OpFunction %void None %voidfn
+ %label_sub2 = OpLabel
+ %5 = OpAccessChain %ptr_float %pc2 %int_0
+ %6 = OpLoad %float %5
+ OpReturn
+ OpFunctionEnd
+
+ %1 = OpFunction %void None %voidfn
+ %label1 = OpLabel
+ %call1 = OpFunctionCall %void %sub1
+ %call2 = OpFunctionCall %void %sub2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr(
+ "Entry point id '1' uses more than one PushConstant interface.\n"
+ "From Vulkan spec, section 14.5.1:\n"
+ "There must be no more than one push constant block "
+ "statically used per shader entry point."));
+}
+
TEST_F(ValidateDecorations, VulkanUniformMissingDescriptorSetBad) {
std::string spirv = R"(
OpCapability Shader