| // Copyright (c) 2018 Google LLC. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <sstream> | |
| #include <string> | |
| #include "gmock/gmock.h" | |
| #include "test/unit_spirv.h" | |
| #include "test/val/val_fixtures.h" | |
| namespace spvtools { | |
| namespace val { | |
| namespace { | |
| using ::testing::HasSubstr; | |
| using ::testing::Not; | |
| using ValidateBarriers = spvtest::ValidateBase<bool>; | |
| std::string GenerateShaderCodeImpl( | |
| const std::string& body, const std::string& capabilities_and_extensions, | |
| const std::string& definitions, const std::string& execution_model, | |
| const std::string& memory_model) { | |
| std::ostringstream ss; | |
| ss << R"( | |
| OpCapability Shader | |
| )"; | |
| ss << capabilities_and_extensions; | |
| ss << memory_model << std::endl; | |
| ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; | |
| if (execution_model == "Fragment") { | |
| ss << "OpExecutionMode %main OriginUpperLeft\n"; | |
| } else if (execution_model == "Geometry") { | |
| ss << "OpExecutionMode %main InputPoints\n"; | |
| ss << "OpExecutionMode %main OutputPoints\n"; | |
| } else if (execution_model == "GLCompute") { | |
| ss << "OpExecutionMode %main LocalSize 1 1 1\n"; | |
| } | |
| ss << R"( | |
| %void = OpTypeVoid | |
| %func = OpTypeFunction %void | |
| %bool = OpTypeBool | |
| %f32 = OpTypeFloat 32 | |
| %u32 = OpTypeInt 32 0 | |
| %f32_0 = OpConstant %f32 0 | |
| %f32_1 = OpConstant %f32 1 | |
| %u32_0 = OpConstant %u32 0 | |
| %u32_1 = OpConstant %u32 1 | |
| %u32_4 = OpConstant %u32 4 | |
| )"; | |
| ss << definitions; | |
| ss << R"( | |
| %cross_device = OpConstant %u32 0 | |
| %device = OpConstant %u32 1 | |
| %workgroup = OpConstant %u32 2 | |
| %subgroup = OpConstant %u32 3 | |
| %invocation = OpConstant %u32 4 | |
| %queuefamily = OpConstant %u32 5 | |
| %shadercall = OpConstant %u32 6 | |
| %none = OpConstant %u32 0 | |
| %acquire = OpConstant %u32 2 | |
| %release = OpConstant %u32 4 | |
| %acquire_release = OpConstant %u32 8 | |
| %acquire_and_release = OpConstant %u32 6 | |
| %sequentially_consistent = OpConstant %u32 16 | |
| %acquire_release_uniform_workgroup = OpConstant %u32 328 | |
| %acquire_uniform_workgroup = OpConstant %u32 322 | |
| %release_uniform_workgroup = OpConstant %u32 324 | |
| %acquire_and_release_uniform = OpConstant %u32 70 | |
| %acquire_release_subgroup = OpConstant %u32 136 | |
| %acquire_release_workgroup = OpConstant %u32 264 | |
| %uniform = OpConstant %u32 64 | |
| %uniform_workgroup = OpConstant %u32 320 | |
| %workgroup_memory = OpConstant %u32 256 | |
| %image_memory = OpConstant %u32 2048 | |
| %uniform_image_memory = OpConstant %u32 2112 | |
| %main = OpFunction %void None %func | |
| %main_entry = OpLabel | |
| )"; | |
| ss << body; | |
| ss << R"( | |
| OpReturn | |
| OpFunctionEnd)"; | |
| return ss.str(); | |
| } | |
| std::string GenerateShaderCode( | |
| const std::string& body, | |
| const std::string& capabilities_and_extensions = "", | |
| const std::string& execution_model = "GLCompute") { | |
| const std::string int64_capability = R"( | |
| OpCapability Int64 | |
| )"; | |
| const std::string int64_declarations = R"( | |
| %u64 = OpTypeInt 64 0 | |
| %u64_0 = OpConstant %u64 0 | |
| %u64_1 = OpConstant %u64 1 | |
| )"; | |
| const std::string memory_model = "OpMemoryModel Logical GLSL450"; | |
| return GenerateShaderCodeImpl( | |
| body, int64_capability + capabilities_and_extensions, int64_declarations, | |
| execution_model, memory_model); | |
| } | |
| std::string GenerateVulkanVertexShaderCode( | |
| const std::string& body, | |
| const std::string& capabilities_and_extensions = "", | |
| const std::string& execution_model = "Vertex") { | |
| const std::string memory_model = "OpMemoryModel Logical GLSL450"; | |
| return GenerateShaderCodeImpl(body, capabilities_and_extensions, "", | |
| execution_model, memory_model); | |
| } | |
| std::string GenerateKernelCode( | |
| const std::string& body, | |
| const std::string& capabilities_and_extensions = "") { | |
| std::ostringstream ss; | |
| ss << R"( | |
| OpCapability Addresses | |
| OpCapability Kernel | |
| OpCapability Linkage | |
| OpCapability Int64 | |
| OpCapability NamedBarrier | |
| )"; | |
| ss << capabilities_and_extensions; | |
| ss << R"( | |
| OpMemoryModel Physical32 OpenCL | |
| %void = OpTypeVoid | |
| %func = OpTypeFunction %void | |
| %bool = OpTypeBool | |
| %f32 = OpTypeFloat 32 | |
| %u32 = OpTypeInt 32 0 | |
| %u64 = OpTypeInt 64 0 | |
| %f32_0 = OpConstant %f32 0 | |
| %f32_1 = OpConstant %f32 1 | |
| %f32_4 = OpConstant %f32 4 | |
| %u32_0 = OpConstant %u32 0 | |
| %u32_1 = OpConstant %u32 1 | |
| %u32_4 = OpConstant %u32 4 | |
| %u64_0 = OpConstant %u64 0 | |
| %u64_1 = OpConstant %u64 1 | |
| %u64_4 = OpConstant %u64 4 | |
| %cross_device = OpConstant %u32 0 | |
| %device = OpConstant %u32 1 | |
| %workgroup = OpConstant %u32 2 | |
| %subgroup = OpConstant %u32 3 | |
| %invocation = OpConstant %u32 4 | |
| %none = OpConstant %u32 0 | |
| %acquire = OpConstant %u32 2 | |
| %release = OpConstant %u32 4 | |
| %acquire_release = OpConstant %u32 8 | |
| %acquire_and_release = OpConstant %u32 6 | |
| %sequentially_consistent = OpConstant %u32 16 | |
| %acquire_release_workgroup = OpConstant %u32 264 | |
| %named_barrier = OpTypeNamedBarrier | |
| %main = OpFunction %void None %func | |
| %main_entry = OpLabel | |
| )"; | |
| ss << body; | |
| ss << R"( | |
| OpReturn | |
| OpFunctionEnd)"; | |
| return ss.str(); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierGLComputeSuccess) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %none | |
| OpControlBarrier %workgroup %workgroup %acquire | |
| OpControlBarrier %workgroup %device %release | |
| OpControlBarrier %cross_device %cross_device %acquire_release | |
| OpControlBarrier %cross_device %cross_device %sequentially_consistent | |
| OpControlBarrier %cross_device %cross_device %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierKernelSuccess) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %none | |
| OpControlBarrier %workgroup %workgroup %acquire | |
| OpControlBarrier %workgroup %device %release | |
| OpControlBarrier %cross_device %cross_device %acquire_release | |
| OpControlBarrier %cross_device %cross_device %sequentially_consistent | |
| OpControlBarrier %cross_device %cross_device %acquire_release_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierTesselationControlSuccess) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %none | |
| OpControlBarrier %workgroup %workgroup %acquire | |
| OpControlBarrier %workgroup %device %release | |
| OpControlBarrier %cross_device %cross_device %acquire_release | |
| OpControlBarrier %cross_device %cross_device %sequentially_consistent | |
| OpControlBarrier %cross_device %cross_device %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "OpCapability Tessellation\n", | |
| "TessellationControl")); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkanSuccess) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %device %none | |
| OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"), | |
| SPV_ENV_UNIVERSAL_1_2); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("OpControlBarrier requires one of the following " | |
| "Execution Models: TessellationControl, GLCompute, Kernel, " | |
| "MeshNV or TaskNV")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv13) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"), | |
| SPV_ENV_UNIVERSAL_1_3); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierFloatExecutionScope) { | |
| const std::string body = R"( | |
| OpControlBarrier %f32_1 %device %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: expected scope to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierU64ExecutionScope) { | |
| const std::string body = R"( | |
| OpControlBarrier %u64_1 %device %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: expected scope to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierFloatMemoryScope) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %f32_1 %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: expected scope to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierU64MemoryScope) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %u64_1 %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: expected scope to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierFloatMemorySemantics) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %f32_0 | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "ControlBarrier: expected Memory Semantics to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierU64MemorySemantics) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %u64_0 | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "ControlBarrier: expected Memory Semantics to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkanExecutionScopeDevice) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %workgroup %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-None-04636")); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: in Vulkan environment Execution Scope " | |
| "is limited to Workgroup and Subgroup")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroup) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-SubgroupVoteKHR-07951")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "ControlBarrier: in Vulkan 1.0 environment Memory Scope is can not " | |
| "be Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroupVoteKHR) { | |
| const std::string capabilities = R"( | |
| OpCapability SubgroupVoteKHR | |
| OpExtension "SPV_KHR_subgroup_vote" | |
| )"; | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, capabilities), | |
| SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1MemoryScopeSubgroup) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1MemoryScopeCrossDevice) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %cross_device %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-None-04638")); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: in Vulkan environment Memory Scope is " | |
| "limited to Device, QueueFamily, Workgroup, " | |
| "ShaderCallKHR, Subgroup, or Invocation")); | |
| } | |
| TEST_F(ValidateBarriers, | |
| OpControlBarrierVulkan1p1WorkgroupNonComputeMemoryFailure) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %workgroup %acquire | |
| )"; | |
| CompileSuccessfully(GenerateVulkanVertexShaderCode(body), SPV_ENV_VULKAN_1_1); | |
| EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-None-07321")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("Workgroup Memory Scope is limited to MeshNV, " | |
| "TaskNV, MeshEXT, TaskEXT, TessellationControl, and GLCompute " | |
| "execution model")); | |
| } | |
| TEST_F(ValidateBarriers, | |
| OpControlBarrierVulkan1p1WorkgroupNonComputeExecutionFailure) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %subgroup %acquire | |
| )"; | |
| CompileSuccessfully(GenerateVulkanVertexShaderCode(body), SPV_ENV_VULKAN_1_1); | |
| EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-None-04637")); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("in Vulkan environment, Workgroup execution scope is " | |
| "only for TaskNV, MeshNV, TaskEXT, MeshEXT, " | |
| "TessellationControl, and GLCompute execution models")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1WorkgroupComputeSuccess) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %workgroup %acquire | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1WorkgroupNonComputeSuccess) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %acquire | |
| )"; | |
| CompileSuccessfully(GenerateVulkanVertexShaderCode(body), SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkanInvocationSuccess) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %invocation %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierVulkanInvocationFailure) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %invocation %acquire | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-None-04641")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("ControlBarrier: Vulkan specification requires Memory " | |
| "Semantics to be None if used with Invocation Memory Scope")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierAcquireAndRelease) { | |
| const std::string body = R"( | |
| OpControlBarrier %device %device %acquire_and_release_uniform | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: Memory Semantics can have at most one " | |
| "of the following bits set: Acquire, Release, " | |
| "AcquireRelease or SequentiallyConsistent")); | |
| } | |
| // TODO(atgoo@github.com): the corresponding check fails Vulkan CTS, | |
| // reenable once fixed. | |
| TEST_F(ValidateBarriers, DISABLED_OpControlBarrierVulkanSubgroupStorageClass) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %device %acquire_release_subgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "ControlBarrier: expected Memory Semantics to include a " | |
| "Vulkan-supported storage class if Memory Semantics is not None")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionFragment1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %acquire_release_subgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierWorkgroupExecutionFragment1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "OpControlBarrier execution scope must be Subgroup for Fragment, " | |
| "Vertex, Geometry, TessellationEvaluation, RayGeneration, " | |
| "Intersection, AnyHit, ClosestHit, and Miss execution models")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionFragment1p0) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"), | |
| SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("OpControlBarrier requires one of the following " | |
| "Execution " | |
| "Models: TessellationControl, GLCompute, Kernel, " | |
| "MeshNV or TaskNV")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionVertex1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %acquire_release_subgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Vertex"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierWorkgroupExecutionVertex1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Vertex"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "OpControlBarrier execution scope must be Subgroup for Fragment, " | |
| "Vertex, Geometry, TessellationEvaluation, RayGeneration, " | |
| "Intersection, AnyHit, ClosestHit, and Miss execution models")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionVertex1p0) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "", "Vertex"), | |
| SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("OpControlBarrier requires one of the following " | |
| "Execution Models: TessellationControl, GLCompute, Kernel, " | |
| "MeshNV or TaskNV")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionGeometry1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %acquire_release_subgroup | |
| )"; | |
| CompileSuccessfully( | |
| GenerateShaderCode(body, "OpCapability Geometry\n", "Geometry"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierWorkgroupExecutionGeometry1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully( | |
| GenerateShaderCode(body, "OpCapability Geometry\n", "Geometry"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "OpControlBarrier execution scope must be Subgroup for Fragment, " | |
| "Vertex, Geometry, TessellationEvaluation, RayGeneration, " | |
| "Intersection, AnyHit, ClosestHit, and Miss execution models")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionGeometry1p0) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully( | |
| GenerateShaderCode(body, "OpCapability Geometry\n", "Geometry"), | |
| SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("OpControlBarrier requires one of the following " | |
| "Execution " | |
| "Models: TessellationControl, GLCompute, Kernel, " | |
| "MeshNV or TaskNV")); | |
| } | |
| TEST_F(ValidateBarriers, | |
| OpControlBarrierSubgroupExecutionTessellationEvaluation1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %subgroup %acquire_release_subgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "OpCapability Tessellation\n", | |
| "TessellationEvaluation"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, | |
| OpControlBarrierWorkgroupExecutionTessellationEvaluation1p1) { | |
| const std::string body = R"( | |
| OpControlBarrier %workgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "OpCapability Tessellation\n", | |
| "TessellationEvaluation"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "OpControlBarrier execution scope must be Subgroup for Fragment, " | |
| "Vertex, Geometry, TessellationEvaluation, RayGeneration, " | |
| "Intersection, AnyHit, ClosestHit, and Miss execution models")); | |
| } | |
| TEST_F(ValidateBarriers, | |
| OpControlBarrierSubgroupExecutionTessellationEvaluation1p0) { | |
| const std::string body = R"( | |
| OpControlBarrier %subgroup %workgroup %acquire_release | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body, "OpCapability Tessellation\n", | |
| "TessellationEvaluation"), | |
| SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("OpControlBarrier requires one of the following " | |
| "Execution " | |
| "Models: TessellationControl, GLCompute, Kernel, " | |
| "MeshNV or TaskNV")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierSuccess) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %cross_device %acquire_release_uniform_workgroup | |
| OpMemoryBarrier %device %uniform | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierKernelSuccess) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %cross_device %acquire_release_workgroup | |
| OpMemoryBarrier %device %none | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierVulkanSuccess) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %f32_1 %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected scope to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierU64MemoryScope) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %u64_1 %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected scope to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemorySemantics) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %device %f32_0 | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected Memory Semantics to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierU64MemorySemantics) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %device %u64_0 | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected Memory Semantics to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemoryScopeSubgroup) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %subgroup %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-SubgroupVoteKHR-07951")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "MemoryBarrier: in Vulkan 1.0 environment Memory Scope is can not be " | |
| "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierVulkan1p1MemoryScopeSubgroup) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %subgroup %acquire_release_uniform_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierAcquireAndRelease) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %device %acquire_and_release_uniform | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body)); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: Memory Semantics can have at most one " | |
| "of the following bits set: Acquire, Release, " | |
| "AcquireRelease or SequentiallyConsistent")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemorySemanticsNone) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %device %none | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04732")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: Vulkan specification requires Memory Semantics " | |
| "to have one of the following bits set: Acquire, Release, " | |
| "AcquireRelease or SequentiallyConsistent")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemorySemanticsAcquire) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %device %acquire | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04733")); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected Memory Semantics to include a " | |
| "Vulkan-supported storage class")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierVulkanSubgroupStorageClass) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %device %acquire_release_subgroup | |
| )"; | |
| CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04733")); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected Memory Semantics to include a " | |
| "Vulkan-supported storage class")); | |
| } | |
| TEST_F(ValidateBarriers, OpNamedBarrierInitializeSuccess) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %named_barrier %u32_4 | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpNamedBarrierInitializeWrongResultType) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %u32 %u32_4 | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("NamedBarrierInitialize: expected Result Type to be " | |
| "OpTypeNamedBarrier")); | |
| } | |
| TEST_F(ValidateBarriers, OpNamedBarrierInitializeFloatSubgroupCount) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %named_barrier %f32_4 | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("NamedBarrierInitialize: expected Subgroup Count to be " | |
| "a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpNamedBarrierInitializeU64SubgroupCount) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %named_barrier %u64_4 | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("NamedBarrierInitialize: expected Subgroup Count to be " | |
| "a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryNamedBarrierSuccess) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %named_barrier %u32_4 | |
| OpMemoryNamedBarrier %barrier %workgroup %acquire_release_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryNamedBarrierNotNamedBarrier) { | |
| const std::string body = R"( | |
| OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryNamedBarrier: expected Named Barrier to be of " | |
| "type OpTypeNamedBarrier")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemoryScope) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %named_barrier %u32_4 | |
| OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_workgroup | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("MemoryNamedBarrier: expected scope to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemorySemantics) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %named_barrier %u32_4 | |
| OpMemoryNamedBarrier %barrier %workgroup %f32_0 | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "MemoryNamedBarrier: expected Memory Semantics to be a 32-bit int")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryNamedBarrierAcquireAndRelease) { | |
| const std::string body = R"( | |
| %barrier = OpNamedBarrierInitialize %named_barrier %u32_4 | |
| OpMemoryNamedBarrier %barrier %workgroup %acquire_and_release | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryNamedBarrier: Memory Semantics can have at most " | |
| "one of the following bits set: Acquire, Release, " | |
| "AcquireRelease or SequentiallyConsistent")); | |
| } | |
| TEST_F(ValidateBarriers, TypeAsMemoryScope) { | |
| const std::string body = R"( | |
| OpMemoryBarrier %u32 %u32_0 | |
| )"; | |
| CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); | |
| EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand '5[%uint]' cannot be a " | |
| "type")); | |
| } | |
| TEST_F(ValidateBarriers, | |
| OpControlBarrierVulkanMemoryModelBanSequentiallyConsistent) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %1 "func" | |
| OpExecutionMode %1 OriginUpperLeft | |
| %2 = OpTypeVoid | |
| %3 = OpTypeInt 32 0 | |
| %4 = OpConstant %3 16 | |
| %5 = OpTypeFunction %2 | |
| %6 = OpConstant %3 5 | |
| %1 = OpFunction %2 None %5 | |
| %7 = OpLabel | |
| OpControlBarrier %6 %6 %4 | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("SequentiallyConsistent memory semantics cannot be " | |
| "used with the VulkanKHR memory model.")); | |
| } | |
| TEST_F(ValidateBarriers, | |
| OpMemoryBarrierVulkanMemoryModelBanSequentiallyConsistent) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %1 "func" | |
| OpExecutionMode %1 OriginUpperLeft | |
| %2 = OpTypeVoid | |
| %3 = OpTypeInt 32 0 | |
| %4 = OpConstant %3 16 | |
| %5 = OpTypeFunction %2 | |
| %6 = OpConstant %3 5 | |
| %1 = OpFunction %2 None %5 | |
| %7 = OpLabel | |
| OpMemoryBarrier %6 %4 | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("SequentiallyConsistent memory semantics cannot be " | |
| "used with the VulkanKHR memory model.")); | |
| } | |
| TEST_F(ValidateBarriers, OutputMemoryKHRRequireVulkanMemoryModelKHR) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpMemoryModel Logical GLSL450 | |
| OpEntryPoint Fragment %1 "func" | |
| OpExecutionMode %1 OriginUpperLeft | |
| %2 = OpTypeVoid | |
| %3 = OpTypeInt 32 0 | |
| %semantics = OpConstant %3 4104 | |
| %5 = OpTypeFunction %2 | |
| %device = OpConstant %3 1 | |
| %1 = OpFunction %2 None %5 | |
| %7 = OpLabel | |
| OpControlBarrier %device %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: Memory Semantics OutputMemoryKHR " | |
| "requires capability VulkanMemoryModelKHR")); | |
| } | |
| TEST_F(ValidateBarriers, MakeAvailableKHRRequireVulkanMemoryModelKHR) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpMemoryModel Logical GLSL450 | |
| OpEntryPoint Fragment %1 "func" | |
| OpExecutionMode %1 OriginUpperLeft | |
| %2 = OpTypeVoid | |
| %3 = OpTypeInt 32 0 | |
| %semantics = OpConstant %3 8264 | |
| %5 = OpTypeFunction %2 | |
| %device = OpConstant %3 1 | |
| %1 = OpFunction %2 None %5 | |
| %7 = OpLabel | |
| OpControlBarrier %device %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: Memory Semantics MakeAvailableKHR " | |
| "requires capability VulkanMemoryModelKHR")); | |
| } | |
| TEST_F(ValidateBarriers, MakeVisibleKHRRequireVulkanMemoryModelKHR) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpMemoryModel Logical GLSL450 | |
| OpEntryPoint Fragment %1 "func" | |
| OpExecutionMode %1 OriginUpperLeft | |
| %2 = OpTypeVoid | |
| %3 = OpTypeInt 32 0 | |
| %semantics = OpConstant %3 16456 | |
| %5 = OpTypeFunction %2 | |
| %device = OpConstant %3 1 | |
| %1 = OpFunction %2 None %5 | |
| %7 = OpLabel | |
| OpControlBarrier %device %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("ControlBarrier: Memory Semantics MakeVisibleKHR " | |
| "requires capability VulkanMemoryModelKHR")); | |
| } | |
| TEST_F(ValidateBarriers, MakeAvailableKHRRequiresReleaseSemantics) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %workgroup = OpConstant %int 2 | |
| %semantics = OpConstant %int 8448 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpControlBarrier %workgroup %workgroup %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("ControlBarrier: MakeAvailableKHR Memory Semantics also " | |
| "requires either Release or AcquireRelease Memory Semantics")); | |
| } | |
| TEST_F(ValidateBarriers, MakeVisibleKHRRequiresAcquireSemantics) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %workgroup = OpConstant %int 2 | |
| %semantics = OpConstant %int 16640 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpControlBarrier %workgroup %workgroup %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("ControlBarrier: MakeVisibleKHR Memory Semantics also requires " | |
| "either Acquire or AcquireRelease Memory Semantics")); | |
| } | |
| TEST_F(ValidateBarriers, MakeAvailableKHRRequiresStorageSemantics) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %workgroup = OpConstant %int 2 | |
| %semantics = OpConstant %int 8196 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpMemoryBarrier %workgroup %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected Memory Semantics to include a " | |
| "storage class")); | |
| } | |
| TEST_F(ValidateBarriers, MakeVisibleKHRRequiresStorageSemantics) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %workgroup = OpConstant %int 2 | |
| %semantics = OpConstant %int 16386 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpMemoryBarrier %workgroup %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("MemoryBarrier: expected Memory Semantics to include a " | |
| "storage class")); | |
| } | |
| TEST_F(ValidateBarriers, SemanticsSpecConstantShader) { | |
| const std::string spirv = R"( | |
| OpCapability Shader | |
| OpMemoryModel Logical GLSL450 | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %ptr_int_workgroup = OpTypePointer Workgroup %int | |
| %var = OpVariable %ptr_int_workgroup Workgroup | |
| %voidfn = OpTypeFunction %void | |
| %spec_const = OpSpecConstant %int 0 | |
| %workgroup = OpConstant %int 2 | |
| %func = OpFunction %void None %voidfn | |
| %entry = OpLabel | |
| OpMemoryBarrier %workgroup %spec_const | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(spirv); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("Memory Semantics ids must be OpConstant when Shader " | |
| "capability is present")); | |
| } | |
| TEST_F(ValidateBarriers, SemanticsSpecConstantKernel) { | |
| const std::string spirv = R"( | |
| OpCapability Kernel | |
| OpCapability Linkage | |
| OpMemoryModel Logical OpenCL | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %ptr_int_workgroup = OpTypePointer Workgroup %int | |
| %var = OpVariable %ptr_int_workgroup Workgroup | |
| %voidfn = OpTypeFunction %void | |
| %spec_const = OpSpecConstant %int 0 | |
| %workgroup = OpConstant %int 2 | |
| %func = OpFunction %void None %voidfn | |
| %entry = OpLabel | |
| OpMemoryBarrier %workgroup %spec_const | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(spirv); | |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); | |
| } | |
| TEST_F(ValidateBarriers, ScopeSpecConstantShader) { | |
| const std::string spirv = R"( | |
| OpCapability Shader | |
| OpMemoryModel Logical GLSL450 | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %ptr_int_workgroup = OpTypePointer Workgroup %int | |
| %var = OpVariable %ptr_int_workgroup Workgroup | |
| %voidfn = OpTypeFunction %void | |
| %spec_const = OpSpecConstant %int 0 | |
| %relaxed = OpConstant %int 0 | |
| %func = OpFunction %void None %voidfn | |
| %entry = OpLabel | |
| OpMemoryBarrier %spec_const %relaxed | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(spirv); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("Scope ids must be OpConstant when Shader " | |
| "capability is present")); | |
| } | |
| TEST_F(ValidateBarriers, ScopeSpecConstantKernel) { | |
| const std::string spirv = R"( | |
| OpCapability Kernel | |
| OpCapability Linkage | |
| OpMemoryModel Logical OpenCL | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %ptr_int_workgroup = OpTypePointer Workgroup %int | |
| %var = OpVariable %ptr_int_workgroup Workgroup | |
| %voidfn = OpTypeFunction %void | |
| %spec_const = OpSpecConstant %int 0 | |
| %relaxed = OpConstant %int 0 | |
| %func = OpFunction %void None %voidfn | |
| %entry = OpLabel | |
| OpMemoryBarrier %spec_const %relaxed | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(spirv); | |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); | |
| } | |
| TEST_F(ValidateBarriers, VulkanMemoryModelDeviceScopeBad) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %device = OpConstant %int 1 | |
| %semantics = OpConstant %int 0 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpMemoryBarrier %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, | |
| ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr("Use of device scope with VulkanKHR memory model requires the " | |
| "VulkanMemoryModelDeviceScopeKHR capability")); | |
| } | |
| TEST_F(ValidateBarriers, VulkanMemoryModelDeviceScopeGood) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpCapability VulkanMemoryModelDeviceScopeKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| OpEntryPoint Fragment %func "func" | |
| OpExecutionMode %func OriginUpperLeft | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %device = OpConstant %int 1 | |
| %semantics = OpConstant %int 0 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpMemoryBarrier %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); | |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); | |
| } | |
| TEST_F(ValidateBarriers, VolatileMemoryBarrier) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpCapability VulkanMemoryModelDeviceScopeKHR | |
| OpCapability Linkage | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %device = OpConstant %int 1 | |
| %semantics = OpConstant %int 32768 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpMemoryBarrier %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("Memory Semantics Volatile can only be used with " | |
| "atomic instructions")); | |
| } | |
| TEST_F(ValidateBarriers, VolatileControlBarrier) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpCapability VulkanMemoryModelDeviceScopeKHR | |
| OpCapability Linkage | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpMemoryModel Logical VulkanKHR | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %device = OpConstant %int 1 | |
| %semantics = OpConstant %int 32768 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpControlBarrier %device %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("Memory Semantics Volatile can only be used with " | |
| "atomic instructions")); | |
| } | |
| TEST_F(ValidateBarriers, CooperativeMatrixSpecConstantVolatile) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpCapability VulkanMemoryModelDeviceScopeKHR | |
| OpCapability CooperativeMatrixNV | |
| OpCapability Linkage | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpExtension "SPV_NV_cooperative_matrix" | |
| OpMemoryModel Logical VulkanKHR | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %device = OpConstant %int 1 | |
| %semantics = OpSpecConstant %int 32768 | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpControlBarrier %device %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text); | |
| EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); | |
| } | |
| TEST_F(ValidateBarriers, CooperativeMatrixNonConstantSemantics) { | |
| const std::string text = R"( | |
| OpCapability Shader | |
| OpCapability VulkanMemoryModelKHR | |
| OpCapability VulkanMemoryModelDeviceScopeKHR | |
| OpCapability CooperativeMatrixNV | |
| OpCapability Linkage | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpExtension "SPV_NV_cooperative_matrix" | |
| OpMemoryModel Logical VulkanKHR | |
| %void = OpTypeVoid | |
| %int = OpTypeInt 32 0 | |
| %device = OpConstant %int 1 | |
| %semantics = OpUndef %int | |
| %functy = OpTypeFunction %void | |
| %func = OpFunction %void None %functy | |
| %1 = OpLabel | |
| OpControlBarrier %device %device %semantics | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| CompileSuccessfully(text); | |
| EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("Memory Semantics must be a constant instruction when " | |
| "CooperativeMatrixNV capability is present")); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierShaderCallRayGenSuccess) { | |
| const std::string body = | |
| "OpMemoryBarrier %shadercall %release_uniform_workgroup"; | |
| CompileSuccessfully(GenerateShaderCodeImpl(body, | |
| // capabilities_and_extensions | |
| R"( | |
| OpCapability VulkanMemoryModelKHR | |
| OpCapability RayTracingKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpExtension "SPV_KHR_ray_tracing" | |
| )", | |
| // definitions | |
| "", | |
| // execution_model | |
| "RayGenerationKHR", | |
| // memory_model | |
| "OpMemoryModel Logical VulkanKHR"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| } | |
| TEST_F(ValidateBarriers, OpMemoryBarrierShaderCallComputeFailure) { | |
| const std::string body = | |
| "OpMemoryBarrier %shadercall %release_uniform_workgroup"; | |
| CompileSuccessfully(GenerateShaderCodeImpl(body, | |
| // capabilities_and_extensions | |
| R"( | |
| OpCapability VulkanMemoryModelKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| )", | |
| // definitions | |
| "", | |
| // execution_model | |
| "GLCompute", | |
| // memory_model | |
| "OpMemoryModel Logical VulkanKHR"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-None-04640")); | |
| EXPECT_THAT( | |
| getDiagnosticString(), | |
| HasSubstr( | |
| "ShaderCallKHR Memory Scope requires a ray tracing execution model")); | |
| } | |
| TEST_F(ValidateBarriers, OpControlBarrierShaderCallRayGenFailure) { | |
| const std::string body = "OpControlBarrier %shadercall %shadercall %none"; | |
| CompileSuccessfully(GenerateShaderCodeImpl(body, | |
| // capabilities_and_extensions | |
| R"( | |
| OpCapability VulkanMemoryModelKHR | |
| OpCapability RayTracingKHR | |
| OpExtension "SPV_KHR_vulkan_memory_model" | |
| OpExtension "SPV_KHR_ray_tracing" | |
| )", | |
| // definitions | |
| "", | |
| // execution_model | |
| "RayGenerationKHR", | |
| // memory_model | |
| "OpMemoryModel Logical VulkanKHR"), | |
| SPV_ENV_VULKAN_1_1); | |
| ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1)); | |
| EXPECT_THAT(getDiagnosticString(), | |
| AnyVUID("VUID-StandaloneSpirv-None-04636")); | |
| EXPECT_THAT(getDiagnosticString(), | |
| HasSubstr("in Vulkan environment Execution Scope is limited to " | |
| "Workgroup and Subgroup")); | |
| } | |
| } // namespace | |
| } // namespace val | |
| } // namespace spvtools |