spirv-val: Add constant check to mesh execution mode (#6560)
We discussed in https://gitlab.khronos.org/vulkan/vulkan/-/issues/4694
that having a `SetMeshOutputsEXT` with bogus constant values should be
disallowed, even if never taken as it currently causes various drivers
issues trying to lower that code
I added test to ensure that with spec constants, this is only validated
when they are frozen
diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp
index 26273af..7b3b3af 100644
--- a/source/val/validate_instruction.cpp
+++ b/source/val/validate_instruction.cpp
@@ -491,6 +491,10 @@
spv::ExecutionMode::OutputPrimitivesEXT) {
_.RegisterEntryPointOutputPrimitivesEXT(entry_point, inst);
}
+ if (inst->GetOperandAs<spv::ExecutionMode>(1) ==
+ spv::ExecutionMode::OutputVertices) {
+ _.RegisterEntryPointOutputVertices(entry_point, inst);
+ }
} else if (opcode == spv::Op::OpVariable) {
const auto storage_class = inst->GetOperandAs<spv::StorageClass>(2);
if (auto error = LimitCheckNumVars(_, inst->id(), storage_class)) {
diff --git a/source/val/validate_mesh_shading.cpp b/source/val/validate_mesh_shading.cpp
index 121f339..dd9bb4a 100644
--- a/source/val/validate_mesh_shading.cpp
+++ b/source/val/validate_mesh_shading.cpp
@@ -14,6 +14,8 @@
// Validates ray query instructions from SPV_KHR_ray_query
+#include <string>
+
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"
@@ -118,6 +120,50 @@
<< "Primitive Count must be a 32-bit unsigned int scalar";
}
+ // Will only validate if constants are used (or spec constant frozen)
+ uint64_t vertex_count_value = 0;
+ if (_.EvalConstantValUint64(inst->GetOperandAs<uint32_t>(0),
+ &vertex_count_value)) {
+ _.function(inst->function()->id())
+ ->RegisterLimitation(
+ [vertex_count_value](const ValidationState_t& state,
+ const Function* entry_point,
+ std::string* message) {
+ const uint32_t output_vertices =
+ state.GetOutputVertices(entry_point->id());
+ if (vertex_count_value > output_vertices) {
+ *message =
+ "OpSetMeshOutputsEXT Vertex Count (" +
+ std::to_string(vertex_count_value) +
+ ") is larger than the OutputVertices in OpExecutionMode (" +
+ std::to_string(output_vertices) + ").";
+ return false;
+ }
+ return true;
+ });
+ }
+ uint64_t primitive_count_value = 0;
+ if (_.EvalConstantValUint64(inst->GetOperandAs<uint32_t>(1),
+ &primitive_count_value)) {
+ _.function(inst->function()->id())
+ ->RegisterLimitation(
+ [primitive_count_value](const ValidationState_t& state,
+ const Function* entry_point,
+ std::string* message) {
+ const uint32_t output_primitives =
+ state.GetOutputPrimitivesEXT(entry_point->id());
+ if (primitive_count_value > output_primitives) {
+ *message = "OpSetMeshOutputsEXT Primitive Count (" +
+ std::to_string(primitive_count_value) +
+ ") is larger than the OutputPrimitivesEXT in "
+ "OpExecutionMode (" +
+ std::to_string(output_primitives) + ").";
+ return false;
+ }
+ return true;
+ });
+ }
+
return SPV_SUCCESS;
}
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 5601224..b3c5359 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -271,7 +271,7 @@
}
/// Returns the maximum number of primitives mesh shader can emit
- uint32_t GetOutputPrimitivesEXT(uint32_t entry_point) {
+ uint32_t GetOutputPrimitivesEXT(uint32_t entry_point) const {
auto entry = entry_point_to_output_primitives_.find(entry_point);
if (entry != entry_point_to_output_primitives_.end()) {
auto inst = entry->second;
@@ -280,6 +280,23 @@
return 0;
}
+ /// Registers that the entry point maximum number of vertices
+ /// mesh shader will ever emit
+ void RegisterEntryPointOutputVertices(uint32_t entry_point,
+ const Instruction* inst) {
+ entry_point_to_output_vertices_[entry_point] = inst;
+ }
+
+ /// Returns the maximum number of primitives mesh shader can emit
+ uint32_t GetOutputVertices(uint32_t entry_point) const {
+ auto entry = entry_point_to_output_vertices_.find(entry_point);
+ if (entry != entry_point_to_output_vertices_.end()) {
+ auto inst = entry->second;
+ return inst->GetOperandAs<uint32_t>(2);
+ }
+ return 0;
+ }
+
/// Returns whether the entry point declares its local size
bool EntryPointHasLocalSizeOrId(uint32_t entry_point) const {
return entry_point_to_local_size_or_id_.find(entry_point) !=
@@ -1132,6 +1149,10 @@
std::unordered_map<uint32_t, const Instruction*>
entry_point_to_output_primitives_;
+ // Mapping entry point -> OutputVertices execution mode instruction
+ std::unordered_map<uint32_t, const Instruction*>
+ entry_point_to_output_vertices_;
+
/// Mapping function -> array of entry points inside this
/// module which can (indirectly) call the function.
std::unordered_map<uint32_t, std::vector<uint32_t>> function_to_entry_points_;
diff --git a/test/val/val_mesh_shading_test.cpp b/test/val/val_mesh_shading_test.cpp
index d12c27d..95eb5e8 100644
--- a/test/val/val_mesh_shading_test.cpp
+++ b/test/val/val_mesh_shading_test.cpp
@@ -1383,6 +1383,159 @@
"explicitly laid out storage class"));
}
+// https://godbolt.org/z/8s5W19xoc
+TEST_F(ValidateMeshShading, SetMeshOutputsConstantInCondition) {
+ const std::string body = R"(
+ OpCapability MeshShadingEXT
+ OpExtension "SPV_EXT_mesh_shader"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint MeshEXT %main "main" %_ %gl_MeshVerticesEXT %gl_PrimitiveTriangleIndicesEXT
+ OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+ OpExecutionMode %main OutputVertices 3
+ OpExecutionMode %main OutputPrimitivesEXT 1
+ OpExecutionMode %main OutputTrianglesEXT
+ OpDecorate %SSBO Block
+ OpMemberDecorate %SSBO 0 Offset 0
+ OpMemberDecorate %SSBO 1 Offset 4
+ OpMemberDecorate %SSBO 2 Offset 8
+ OpDecorate %_ Binding 0
+ OpDecorate %_ DescriptorSet 0
+ OpDecorate %gl_MeshPerVertexEXT Block
+ OpMemberDecorate %gl_MeshPerVertexEXT 0 BuiltIn Position
+ OpMemberDecorate %gl_MeshPerVertexEXT 1 BuiltIn PointSize
+ OpMemberDecorate %gl_MeshPerVertexEXT 2 BuiltIn ClipDistance
+ OpMemberDecorate %gl_MeshPerVertexEXT 3 BuiltIn CullDistance
+ OpDecorate %gl_PrimitiveTriangleIndicesEXT BuiltIn PrimitiveTriangleIndicesEXT
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %SSBO = OpTypeStruct %uint %uint %uint
+%_ptr_StorageBuffer_SSBO = OpTypePointer StorageBuffer %SSBO
+ %_ = OpVariable %_ptr_StorageBuffer_SSBO StorageBuffer
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+ %bool = OpTypeBool
+ %uint_500 = OpConstant %uint 500
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_MeshPerVertexEXT = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+ %uint_3 = OpConstant %uint 3
+%_arr_gl_MeshPerVertexEXT_uint_3 = OpTypeArray %gl_MeshPerVertexEXT %uint_3
+%_ptr_Output__arr_gl_MeshPerVertexEXT_uint_3 = OpTypePointer Output %_arr_gl_MeshPerVertexEXT_uint_3
+%gl_MeshVerticesEXT = OpVariable %_ptr_Output__arr_gl_MeshPerVertexEXT_uint_3 Output
+ %float_0 = OpConstant %float 0
+ %38 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %v3uint = OpTypeVector %uint 3
+%_arr_v3uint_uint_1 = OpTypeArray %v3uint %uint_1
+%_ptr_Output__arr_v3uint_uint_1 = OpTypePointer Output %_arr_v3uint_uint_1
+%gl_PrimitiveTriangleIndicesEXT = OpVariable %_ptr_Output__arr_v3uint_uint_1 Output
+ %uint_0 = OpConstant %uint 0
+ %uint_2 = OpConstant %uint 2
+ %47 = OpConstantComposite %v3uint %uint_0 %uint_1 %uint_2
+%_ptr_Output_v3uint = OpTypePointer Output %v3uint
+ %main = OpFunction %void None %4
+ %6 = OpLabel
+ %15 = OpAccessChain %_ptr_StorageBuffer_uint %_ %int_0
+ %16 = OpLoad %uint %15
+ %18 = OpIEqual %bool %16 %uint_1
+ OpSelectionMerge %20 None
+ OpBranchConditional %18 %19 %22
+ %19 = OpLabel
+ OpSetMeshOutputsEXT %uint_500 %uint_500
+ OpBranch %20
+ %22 = OpLabel
+ %24 = OpAccessChain %_ptr_StorageBuffer_uint %_ %int_1
+ %25 = OpLoad %uint %24
+ %27 = OpAccessChain %_ptr_StorageBuffer_uint %_ %int_2
+ %28 = OpLoad %uint %27
+ OpSetMeshOutputsEXT %25 %28
+ OpBranch %20
+ %20 = OpLabel
+ %40 = OpAccessChain %_ptr_Output_v4float %gl_MeshVerticesEXT %int_0 %int_0
+ OpStore %40 %38
+ %49 = OpAccessChain %_ptr_Output_v3uint %gl_PrimitiveTriangleIndicesEXT %int_0
+ OpStore %49 %47
+ 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("OpSetMeshOutputsEXT Vertex Count (500) is larger than the "
+ "OutputVertices in OpExecutionMode (3).\nOpSetMeshOutputsEXT "
+ "Primitive Count (500) is larger than the OutputPrimitivesEXT "
+ "in OpExecutionMode (1)"));
+}
+
+// https://godbolt.org/z/TzaKxYGrY (allowed until spec constants are frozen)
+TEST_F(ValidateMeshShading, SetMeshOutputsSpecConstants) {
+ const std::string body = R"(
+ OpCapability MeshShadingEXT
+ OpExtension "SPV_EXT_mesh_shader"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint MeshEXT %main "main" %gl_MeshVerticesEXT %gl_PrimitiveTriangleIndicesEXT
+ OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+ OpExecutionMode %main OutputVertices 3
+ OpExecutionMode %main OutputPrimitivesEXT 1
+ OpExecutionMode %main OutputTrianglesEXT
+ OpDecorate %V SpecId 0
+ OpDecorate %P SpecId 1
+ OpDecorate %gl_MeshPerVertexEXT Block
+ OpMemberDecorate %gl_MeshPerVertexEXT 0 BuiltIn Position
+ OpMemberDecorate %gl_MeshPerVertexEXT 1 BuiltIn PointSize
+ OpMemberDecorate %gl_MeshPerVertexEXT 2 BuiltIn ClipDistance
+ OpMemberDecorate %gl_MeshPerVertexEXT 3 BuiltIn CullDistance
+ OpDecorate %gl_PrimitiveTriangleIndicesEXT BuiltIn PrimitiveTriangleIndicesEXT
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %V = OpSpecConstant %uint 500
+ %P = OpSpecConstant %uint 500
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_MeshPerVertexEXT = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+ %uint_3 = OpConstant %uint 3
+%_arr_gl_MeshPerVertexEXT_uint_3 = OpTypeArray %gl_MeshPerVertexEXT %uint_3
+%_ptr_Output__arr_gl_MeshPerVertexEXT_uint_3 = OpTypePointer Output %_arr_gl_MeshPerVertexEXT_uint_3
+%gl_MeshVerticesEXT = OpVariable %_ptr_Output__arr_gl_MeshPerVertexEXT_uint_3 Output
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %22 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %v3uint = OpTypeVector %uint 3
+%_arr_v3uint_uint_1 = OpTypeArray %v3uint %uint_1
+%_ptr_Output__arr_v3uint_uint_1 = OpTypePointer Output %_arr_v3uint_uint_1
+%gl_PrimitiveTriangleIndicesEXT = OpVariable %_ptr_Output__arr_v3uint_uint_1 Output
+ %uint_0 = OpConstant %uint 0
+ %uint_2 = OpConstant %uint 2
+ %31 = OpConstantComposite %v3uint %uint_0 %uint_1 %uint_2
+%_ptr_Output_v3uint = OpTypePointer Output %v3uint
+ %main = OpFunction %void None %4
+ %6 = OpLabel
+ OpSetMeshOutputsEXT %V %P
+ %24 = OpAccessChain %_ptr_Output_v4float %gl_MeshVerticesEXT %int_0 %int_0
+ OpStore %24 %22
+ %33 = OpAccessChain %_ptr_Output_v3uint %gl_PrimitiveTriangleIndicesEXT %int_0
+ OpStore %33 %31
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(body, SPV_ENV_VULKAN_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+}
+
} // namespace
} // namespace val
} // namespace spvtools