spirv-val: Add Mesh Shading Read Output check (#6426)
adds `VUID-StandaloneSpirv-MeshEXT-07107`
<img width="869" height="63" alt="image"
src="https://github.com/user-attachments/assets/2645938a-7da1-4365-85e5-bc1eff53a151"
/>
- Atomics are not allowed on Output, so `OpLoad` should cover it all
- Added (for practice) and Untyped Pointer test, but they aren't allowed
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index 701b4c6..1225722 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -15,6 +15,7 @@
// limitations under the License.
#include <algorithm>
+#include <cstdint>
#include <string>
#include <vector>
@@ -1119,6 +1120,29 @@
}
}
+ // Skip checking if there is zero chance for this having a mesh shader
+ // entrypoint
+ if (_.HasCapability(spv::Capability::MeshShadingEXT) &&
+ pointer_type->GetOperandAs<spv::StorageClass>(1) ==
+ spv::StorageClass::Output) {
+ std::string errorVUID = _.VkErrorID(7107);
+ _.function(inst->function()->id())
+ ->RegisterExecutionModelLimitation(
+ [errorVUID](spv::ExecutionModel model, std::string* message) {
+ // Seems the NV Mesh extension was less strict and allowed
+ // writting to outputs
+ if (model == spv::ExecutionModel::MeshEXT) {
+ if (message) {
+ *message = errorVUID +
+ "The Output Storage Class in a Mesh Execution "
+ "Model must not be read from";
+ }
+ return false;
+ }
+ return true;
+ });
+ }
+
_.RegisterQCOMImageProcessingTextureConsumer(pointer_id, inst, nullptr);
return SPV_SUCCESS;
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index fcdb126..cf2ff3b 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -2703,6 +2703,8 @@
return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-07060);
case 7102:
return VUID_WRAP(VUID-StandaloneSpirv-MeshEXT-07102);
+ case 7107:
+ return VUID_WRAP(VUID-StandaloneSpirv-MeshEXT-07107);
case 7290:
return VUID_WRAP(VUID-StandaloneSpirv-Input-07290);
case 7320:
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index 8d5f362..27b86d7 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -6526,16 +6526,20 @@
%void = OpTypeVoid
%3 = OpTypeFunction %void
%uint = OpTypeInt 32 0
+%int = OpTypeInt 32 1
%uint_32 = OpConstant %uint 32
%uint_1 = OpConstant %uint 1
-%int = OpTypeInt 32 1
+%uint_0 = OpConstant %uint 0
+%int_0 = OpConstant %int 0
%bool = OpTypeBool
%_arr_gl_PrimitiveShadingRateEXT_uint_32 = OpTypeArray %int %uint_32
%_ptr_Output__arr_gl_PrimitiveShadingRateEXT_uint_32 = OpTypePointer Output %_arr_gl_PrimitiveShadingRateEXT_uint_32
%gl_PrimitiveShadingRateEXT = OpVariable %_ptr_Output__arr_gl_PrimitiveShadingRateEXT_uint_32 Output
+%uint_ptr = OpTypePointer Output %int
%main = OpFunction %void None %3
%5 = OpLabel
-%ref_load = OpLoad %_arr_gl_PrimitiveShadingRateEXT_uint_32 %gl_PrimitiveShadingRateEXT
+ %21 = OpAccessChain %uint_ptr %gl_PrimitiveShadingRateEXT %uint_0
+ OpStore %21 %int_0
OpReturn
OpFunctionEnd
)";
diff --git a/test/val/val_mesh_shading_test.cpp b/test/val/val_mesh_shading_test.cpp
index 1897015..d12c27d 100644
--- a/test/val/val_mesh_shading_test.cpp
+++ b/test/val/val_mesh_shading_test.cpp
@@ -1158,6 +1158,231 @@
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
}
+TEST_F(ValidateMeshShading, MeshWriteOutput) {
+ const std::string body = R"(
+ OpCapability MeshShadingEXT
+ OpExtension "SPV_EXT_mesh_shader"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint MeshEXT %main "main" %x %y
+ OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+ OpExecutionMode %main OutputVertices 3
+ OpExecutionMode %main OutputPrimitivesEXT 1
+ OpExecutionMode %main OutputTrianglesEXT
+ OpDecorate %x Location 1
+ OpDecorate %y Location 0
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %uint_3 = OpConstant %uint 3
+%_arr_uint_uint_3 = OpTypeArray %uint %uint_3
+%_ptr_Output__arr_uint_uint_3 = OpTypePointer Output %_arr_uint_uint_3
+ %x = OpVariable %_ptr_Output__arr_uint_uint_3 Output
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_Output_uint = OpTypePointer Output %uint
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_arr_v4float_uint_3 = OpTypeArray %v4float %uint_3
+%_ptr_Output__arr_v4float_uint_3 = OpTypePointer Output %_arr_v4float_uint_3
+ %y = OpVariable %_ptr_Output__arr_v4float_uint_3 Output
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %main = OpFunction %void None %4
+ %6 = OpLabel
+ %16 = OpAccessChain %_ptr_Output_uint %x %int_0
+ OpStore %16 %uint_1
+ %25 = OpAccessChain %_ptr_Output_v4float %y %int_0
+ OpStore %25 %23
+ OpSetMeshOutputsEXT %uint_3 %uint_1
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(body, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+}
+
+TEST_F(ValidateMeshShading, MeshReadOutputInt) {
+ const std::string body = R"(
+ OpCapability MeshShadingEXT
+ OpExtension "SPV_EXT_mesh_shader"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint MeshEXT %main "main" %x
+ OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+ OpExecutionMode %main OutputVertices 3
+ OpExecutionMode %main OutputPrimitivesEXT 1
+ OpExecutionMode %main OutputTrianglesEXT
+ OpDecorate %x Location 0
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+%_ptr_Function_uint = OpTypePointer Function %uint
+ %uint_3 = OpConstant %uint 3
+%_arr_uint_uint_3 = OpTypeArray %uint %uint_3
+%_ptr_Output__arr_uint_uint_3 = OpTypePointer Output %_arr_uint_uint_3
+ %x = OpVariable %_ptr_Output__arr_uint_uint_3 Output
+ %int = OpTypeInt 32 1
+ %int_1 = OpConstant %int 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+ %main = OpFunction %void None %4
+ %6 = OpLabel
+ %y = OpVariable %_ptr_Function_uint Function
+ %18 = OpAccessChain %_ptr_Output_uint %x %int_1
+ %19 = OpLoad %uint %18
+ OpStore %y %19
+ OpSetMeshOutputsEXT %uint_3 %uint_1
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(body, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(getDiagnosticString(),
+ AnyVUID("VUID-StandaloneSpirv-MeshEXT-07107"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("The Output Storage Class in a Mesh Execution Model "
+ "must not be read from"));
+}
+
+TEST_F(ValidateMeshShading, MeshReadOutputVec) {
+ const std::string body = R"(
+ OpCapability MeshShadingEXT
+ OpExtension "SPV_EXT_mesh_shader"
+ %2 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint MeshEXT %main "main" %x
+ OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+ OpExecutionMode %main OutputVertices 3
+ OpExecutionMode %main OutputPrimitivesEXT 1
+ OpExecutionMode %main OutputTrianglesEXT
+ OpDecorate %x Location 0
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %uint_3 = OpConstant %uint 3
+%_arr_v4float_uint_3 = OpTypeArray %v4float %uint_3
+%_ptr_Output__arr_v4float_uint_3 = OpTypePointer Output %_arr_v4float_uint_3
+ %x = OpVariable %_ptr_Output__arr_v4float_uint_3 Output
+ %int = OpTypeInt 32 1
+ %int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %main = OpFunction %void None %4
+ %6 = OpLabel
+ %y = OpVariable %_ptr_Function_v4float Function
+ %20 = OpAccessChain %_ptr_Output_v4float %x %int_1
+ %21 = OpLoad %v4float %20
+ OpStore %y %21
+ OpSetMeshOutputsEXT %uint_3 %uint_1
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(body, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(getDiagnosticString(),
+ AnyVUID("VUID-StandaloneSpirv-MeshEXT-07107"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("The Output Storage Class in a Mesh Execution Model "
+ "must not be read from"));
+}
+
+TEST_F(ValidateMeshShading, MeshReadOutputStruct) {
+ const std::string body = R"(
+ OpCapability MeshShadingEXT
+ OpExtension "SPV_EXT_mesh_shader"
+ %2 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint MeshEXT %main "main" %x_0
+ OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+ OpExecutionMode %main OutputVertices 3
+ OpExecutionMode %main OutputPrimitivesEXT 1
+ OpExecutionMode %main OutputTrianglesEXT
+ OpDecorate %x_0 Location 0
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+%_ptr_Function_uint = OpTypePointer Function %uint
+ %Bar = OpTypeStruct %uint
+ %Foo = OpTypeStruct %Bar
+ %uint_3 = OpConstant %uint 3
+%_arr_Foo_uint_3 = OpTypeArray %Foo %uint_3
+%_ptr_Output__arr_Foo_uint_3 = OpTypePointer Output %_arr_Foo_uint_3
+ %x_0 = OpVariable %_ptr_Output__arr_Foo_uint_3 Output
+ %int = OpTypeInt 32 1
+ %int_2 = OpConstant %int 2
+ %int_0 = OpConstant %int 0
+%_ptr_Output_uint = OpTypePointer Output %uint
+ %main = OpFunction %void None %4
+ %6 = OpLabel
+ %x = OpVariable %_ptr_Function_uint Function
+ %21 = OpAccessChain %_ptr_Output_uint %x_0 %int_2 %int_0 %int_0
+ %22 = OpLoad %uint %21
+ OpStore %x %22
+ OpSetMeshOutputsEXT %uint_3 %uint_1
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(body, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(getDiagnosticString(),
+ AnyVUID("VUID-StandaloneSpirv-MeshEXT-07107"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("The Output Storage Class in a Mesh Execution Model "
+ "must not be read from"));
+}
+
+TEST_F(ValidateMeshShading, MeshReadOutputIntUntyped) {
+ const std::string body = R"(
+ OpCapability MeshShadingEXT
+ OpCapability UntypedPointersKHR
+ OpExtension "SPV_EXT_mesh_shader"
+ OpExtension "SPV_KHR_untyped_pointers"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint MeshEXT %main "main" %x
+ OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+ OpExecutionMode %main OutputVertices 3
+ OpExecutionMode %main OutputPrimitivesEXT 1
+ OpExecutionMode %main OutputTrianglesEXT
+ OpDecorate %x Location 0
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+%_ptr_Function_uint = OpTypePointer Function %uint
+ %uint_3 = OpConstant %uint 3
+%_arr_uint_uint_3 = OpTypeArray %uint %uint_3
+%_ptr_Output__arr_uint_uint_3 = OpTypePointer Output %_arr_uint_uint_3
+ %x = OpVariable %_ptr_Output__arr_uint_uint_3 Output
+ %int = OpTypeInt 32 1
+ %int_1 = OpConstant %int 1
+%_ptr_Output_uint = OpTypeUntypedPointerKHR Output
+ %main = OpFunction %void None %4
+ %6 = OpLabel
+ %y = OpVariable %_ptr_Function_uint Function
+ %18 = OpUntypedAccessChainKHR %_ptr_Output_uint %_arr_uint_uint_3 %x %int_1
+ %19 = OpLoad %uint %18
+ OpStore %y %19
+ OpSetMeshOutputsEXT %uint_3 %uint_1
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(body, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("In Vulkan, untyped pointers can only be used in an "
+ "explicitly laid out storage class"));
+}
+
} // namespace
} // namespace val
} // namespace spvtools