| // 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 "source/val/validate.h" | |
| #include <algorithm> | |
| #include "source/opcode.h" | |
| #include "source/spirv_target_env.h" | |
| #include "source/val/instruction.h" | |
| #include "source/val/validation_state.h" | |
| namespace spvtools { | |
| namespace val { | |
| namespace { | |
| spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) { | |
| const auto entry_point_id = inst->GetOperandAs<uint32_t>(1); | |
| auto entry_point = _.FindDef(entry_point_id); | |
| if (!entry_point || SpvOpFunction != entry_point->opcode()) { | |
| return _.diag(SPV_ERROR_INVALID_ID, inst) | |
| << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id) | |
| << "' is not a function."; | |
| } | |
| // Only check the shader execution models | |
| const SpvExecutionModel execution_model = | |
| inst->GetOperandAs<SpvExecutionModel>(0); | |
| if (execution_model != SpvExecutionModelKernel) { | |
| const auto entry_point_type_id = entry_point->GetOperandAs<uint32_t>(3); | |
| const auto entry_point_type = _.FindDef(entry_point_type_id); | |
| if (!entry_point_type || 3 != entry_point_type->words().size()) { | |
| return _.diag(SPV_ERROR_INVALID_ID, inst) | |
| << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> '" | |
| << _.getIdName(entry_point_id) | |
| << "'s function parameter count is not zero."; | |
| } | |
| } | |
| auto return_type = _.FindDef(entry_point->type_id()); | |
| if (!return_type || SpvOpTypeVoid != return_type->opcode()) { | |
| return _.diag(SPV_ERROR_INVALID_ID, inst) | |
| << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> '" | |
| << _.getIdName(entry_point_id) | |
| << "'s function return type is not void."; | |
| } | |
| const auto* execution_modes = _.GetExecutionModes(entry_point_id); | |
| if (_.HasCapability(SpvCapabilityShader)) { | |
| switch (execution_model) { | |
| case SpvExecutionModelFragment: | |
| if (execution_modes && | |
| execution_modes->count(SpvExecutionModeOriginUpperLeft) && | |
| execution_modes->count(SpvExecutionModeOriginLowerLeft)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Fragment execution model entry points can only specify " | |
| "one of OriginUpperLeft or OriginLowerLeft execution " | |
| "modes."; | |
| } | |
| if (!execution_modes || | |
| (!execution_modes->count(SpvExecutionModeOriginUpperLeft) && | |
| !execution_modes->count(SpvExecutionModeOriginLowerLeft))) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Fragment execution model entry points require either an " | |
| "OriginUpperLeft or OriginLowerLeft execution mode."; | |
| } | |
| if (execution_modes && | |
| 1 < std::count_if(execution_modes->begin(), execution_modes->end(), | |
| [](const SpvExecutionMode& mode) { | |
| switch (mode) { | |
| case SpvExecutionModeDepthGreater: | |
| case SpvExecutionModeDepthLess: | |
| case SpvExecutionModeDepthUnchanged: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Fragment execution model entry points can specify at most " | |
| "one of DepthGreater, DepthLess or DepthUnchanged " | |
| "execution modes."; | |
| } | |
| if (execution_modes && | |
| 1 < std::count_if( | |
| execution_modes->begin(), execution_modes->end(), | |
| [](const SpvExecutionMode& mode) { | |
| switch (mode) { | |
| case SpvExecutionModePixelInterlockOrderedEXT: | |
| case SpvExecutionModePixelInterlockUnorderedEXT: | |
| case SpvExecutionModeSampleInterlockOrderedEXT: | |
| case SpvExecutionModeSampleInterlockUnorderedEXT: | |
| case SpvExecutionModeShadingRateInterlockOrderedEXT: | |
| case SpvExecutionModeShadingRateInterlockUnorderedEXT: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Fragment execution model entry points can specify at most " | |
| "one fragment shader interlock execution mode."; | |
| } | |
| break; | |
| case SpvExecutionModelTessellationControl: | |
| case SpvExecutionModelTessellationEvaluation: | |
| if (execution_modes && | |
| 1 < std::count_if(execution_modes->begin(), execution_modes->end(), | |
| [](const SpvExecutionMode& mode) { | |
| switch (mode) { | |
| case SpvExecutionModeSpacingEqual: | |
| case SpvExecutionModeSpacingFractionalEven: | |
| case SpvExecutionModeSpacingFractionalOdd: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Tessellation execution model entry points can specify at " | |
| "most one of SpacingEqual, SpacingFractionalOdd or " | |
| "SpacingFractionalEven execution modes."; | |
| } | |
| if (execution_modes && | |
| 1 < std::count_if(execution_modes->begin(), execution_modes->end(), | |
| [](const SpvExecutionMode& mode) { | |
| switch (mode) { | |
| case SpvExecutionModeTriangles: | |
| case SpvExecutionModeQuads: | |
| case SpvExecutionModeIsolines: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Tessellation execution model entry points can specify at " | |
| "most one of Triangles, Quads or Isolines execution modes."; | |
| } | |
| if (execution_modes && | |
| 1 < std::count_if(execution_modes->begin(), execution_modes->end(), | |
| [](const SpvExecutionMode& mode) { | |
| switch (mode) { | |
| case SpvExecutionModeVertexOrderCw: | |
| case SpvExecutionModeVertexOrderCcw: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Tessellation execution model entry points can specify at " | |
| "most one of VertexOrderCw or VertexOrderCcw execution " | |
| "modes."; | |
| } | |
| break; | |
| case SpvExecutionModelGeometry: | |
| if (!execution_modes || | |
| 1 != std::count_if(execution_modes->begin(), execution_modes->end(), | |
| [](const SpvExecutionMode& mode) { | |
| switch (mode) { | |
| case SpvExecutionModeInputPoints: | |
| case SpvExecutionModeInputLines: | |
| case SpvExecutionModeInputLinesAdjacency: | |
| case SpvExecutionModeTriangles: | |
| case SpvExecutionModeInputTrianglesAdjacency: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Geometry execution model entry points must specify " | |
| "exactly one of InputPoints, InputLines, " | |
| "InputLinesAdjacency, Triangles or InputTrianglesAdjacency " | |
| "execution modes."; | |
| } | |
| if (!execution_modes || | |
| 1 != std::count_if(execution_modes->begin(), execution_modes->end(), | |
| [](const SpvExecutionMode& mode) { | |
| switch (mode) { | |
| case SpvExecutionModeOutputPoints: | |
| case SpvExecutionModeOutputLineStrip: | |
| case SpvExecutionModeOutputTriangleStrip: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Geometry execution model entry points must specify " | |
| "exactly one of OutputPoints, OutputLineStrip or " | |
| "OutputTriangleStrip execution modes."; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| if (spvIsVulkanEnv(_.context()->target_env)) { | |
| switch (execution_model) { | |
| case SpvExecutionModelGLCompute: | |
| if (!execution_modes || | |
| !execution_modes->count(SpvExecutionModeLocalSize)) { | |
| bool ok = false; | |
| for (auto& i : _.ordered_instructions()) { | |
| if (i.opcode() == SpvOpDecorate) { | |
| if (i.operands().size() > 2) { | |
| if (i.GetOperandAs<SpvDecoration>(1) == SpvDecorationBuiltIn && | |
| i.GetOperandAs<SpvBuiltIn>(2) == SpvBuiltInWorkgroupSize) { | |
| ok = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if (!ok) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "In the Vulkan environment, GLCompute execution model " | |
| "entry points require either the LocalSize execution " | |
| "mode or an object decorated with WorkgroupSize must be " | |
| "specified."; | |
| } | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateExecutionMode(ValidationState_t& _, | |
| const Instruction* inst) { | |
| const auto entry_point_id = inst->GetOperandAs<uint32_t>(0); | |
| const auto found = std::find(_.entry_points().cbegin(), | |
| _.entry_points().cend(), entry_point_id); | |
| if (found == _.entry_points().cend()) { | |
| return _.diag(SPV_ERROR_INVALID_ID, inst) | |
| << "OpExecutionMode Entry Point <id> '" | |
| << _.getIdName(entry_point_id) | |
| << "' is not the Entry Point " | |
| "operand of an OpEntryPoint."; | |
| } | |
| const auto mode = inst->GetOperandAs<SpvExecutionMode>(1); | |
| if (inst->opcode() == SpvOpExecutionModeId) { | |
| size_t operand_count = inst->operands().size(); | |
| for (size_t i = 2; i < operand_count; ++i) { | |
| const auto operand_id = inst->GetOperandAs<uint32_t>(2); | |
| const auto* operand_inst = _.FindDef(operand_id); | |
| if (mode == SpvExecutionModeSubgroupsPerWorkgroupId || | |
| mode == SpvExecutionModeLocalSizeHintId || | |
| mode == SpvExecutionModeLocalSizeId) { | |
| if (!spvOpcodeIsConstant(operand_inst->opcode())) { | |
| return _.diag(SPV_ERROR_INVALID_ID, inst) | |
| << "For OpExecutionModeId all Extra Operand ids must be " | |
| "constant " | |
| "instructions."; | |
| } | |
| } else { | |
| return _.diag(SPV_ERROR_INVALID_ID, inst) | |
| << "OpExecutionModeId is only valid when the Mode operand is an " | |
| "execution mode that takes Extra Operands that are id " | |
| "operands."; | |
| } | |
| } | |
| } else if (mode == SpvExecutionModeSubgroupsPerWorkgroupId || | |
| mode == SpvExecutionModeLocalSizeHintId || | |
| mode == SpvExecutionModeLocalSizeId) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "OpExecutionMode is only valid when the Mode operand is an " | |
| "execution mode that takes no Extra Operands, or takes Extra " | |
| "Operands that are not id operands."; | |
| } | |
| const auto* models = _.GetExecutionModels(entry_point_id); | |
| switch (mode) { | |
| case SpvExecutionModeInvocations: | |
| case SpvExecutionModeInputPoints: | |
| case SpvExecutionModeInputLines: | |
| case SpvExecutionModeInputLinesAdjacency: | |
| case SpvExecutionModeInputTrianglesAdjacency: | |
| case SpvExecutionModeOutputLineStrip: | |
| case SpvExecutionModeOutputTriangleStrip: | |
| if (!std::all_of(models->begin(), models->end(), | |
| [](const SpvExecutionModel& model) { | |
| return model == SpvExecutionModelGeometry; | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with the Geometry execution " | |
| "model."; | |
| } | |
| break; | |
| case SpvExecutionModeOutputPoints: | |
| if (!std::all_of(models->begin(), models->end(), | |
| [&_](const SpvExecutionModel& model) { | |
| switch (model) { | |
| case SpvExecutionModelGeometry: | |
| return true; | |
| case SpvExecutionModelMeshNV: | |
| return _.HasCapability(SpvCapabilityMeshShadingNV); | |
| default: | |
| return false; | |
| } | |
| })) { | |
| if (_.HasCapability(SpvCapabilityMeshShadingNV)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with the Geometry or " | |
| "MeshNV execution model."; | |
| } else { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with the Geometry " | |
| "execution " | |
| "model."; | |
| } | |
| } | |
| break; | |
| case SpvExecutionModeSpacingEqual: | |
| case SpvExecutionModeSpacingFractionalEven: | |
| case SpvExecutionModeSpacingFractionalOdd: | |
| case SpvExecutionModeVertexOrderCw: | |
| case SpvExecutionModeVertexOrderCcw: | |
| case SpvExecutionModePointMode: | |
| case SpvExecutionModeQuads: | |
| case SpvExecutionModeIsolines: | |
| if (!std::all_of( | |
| models->begin(), models->end(), | |
| [](const SpvExecutionModel& model) { | |
| return (model == SpvExecutionModelTessellationControl) || | |
| (model == SpvExecutionModelTessellationEvaluation); | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with a tessellation " | |
| "execution model."; | |
| } | |
| break; | |
| case SpvExecutionModeTriangles: | |
| if (!std::all_of(models->begin(), models->end(), | |
| [](const SpvExecutionModel& model) { | |
| switch (model) { | |
| case SpvExecutionModelGeometry: | |
| case SpvExecutionModelTessellationControl: | |
| case SpvExecutionModelTessellationEvaluation: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with a Geometry or " | |
| "tessellation execution model."; | |
| } | |
| break; | |
| case SpvExecutionModeOutputVertices: | |
| if (!std::all_of(models->begin(), models->end(), | |
| [&_](const SpvExecutionModel& model) { | |
| switch (model) { | |
| case SpvExecutionModelGeometry: | |
| case SpvExecutionModelTessellationControl: | |
| case SpvExecutionModelTessellationEvaluation: | |
| return true; | |
| case SpvExecutionModelMeshNV: | |
| return _.HasCapability(SpvCapabilityMeshShadingNV); | |
| default: | |
| return false; | |
| } | |
| })) { | |
| if (_.HasCapability(SpvCapabilityMeshShadingNV)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with a Geometry, " | |
| "tessellation or MeshNV execution model."; | |
| } else { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with a Geometry or " | |
| "tessellation execution model."; | |
| } | |
| } | |
| break; | |
| case SpvExecutionModePixelCenterInteger: | |
| case SpvExecutionModeOriginUpperLeft: | |
| case SpvExecutionModeOriginLowerLeft: | |
| case SpvExecutionModeEarlyFragmentTests: | |
| case SpvExecutionModeDepthReplacing: | |
| case SpvExecutionModeDepthGreater: | |
| case SpvExecutionModeDepthLess: | |
| case SpvExecutionModeDepthUnchanged: | |
| case SpvExecutionModePixelInterlockOrderedEXT: | |
| case SpvExecutionModePixelInterlockUnorderedEXT: | |
| case SpvExecutionModeSampleInterlockOrderedEXT: | |
| case SpvExecutionModeSampleInterlockUnorderedEXT: | |
| case SpvExecutionModeShadingRateInterlockOrderedEXT: | |
| case SpvExecutionModeShadingRateInterlockUnorderedEXT: | |
| if (!std::all_of(models->begin(), models->end(), | |
| [](const SpvExecutionModel& model) { | |
| return model == SpvExecutionModelFragment; | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with the Fragment execution " | |
| "model."; | |
| } | |
| break; | |
| case SpvExecutionModeLocalSizeHint: | |
| case SpvExecutionModeVecTypeHint: | |
| case SpvExecutionModeContractionOff: | |
| case SpvExecutionModeLocalSizeHintId: | |
| if (!std::all_of(models->begin(), models->end(), | |
| [](const SpvExecutionModel& model) { | |
| return model == SpvExecutionModelKernel; | |
| })) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with the Kernel execution " | |
| "model."; | |
| } | |
| break; | |
| case SpvExecutionModeLocalSize: | |
| case SpvExecutionModeLocalSizeId: | |
| if (!std::all_of(models->begin(), models->end(), | |
| [&_](const SpvExecutionModel& model) { | |
| switch (model) { | |
| case SpvExecutionModelKernel: | |
| case SpvExecutionModelGLCompute: | |
| return true; | |
| case SpvExecutionModelTaskNV: | |
| case SpvExecutionModelMeshNV: | |
| return _.HasCapability(SpvCapabilityMeshShadingNV); | |
| default: | |
| return false; | |
| } | |
| })) { | |
| if (_.HasCapability(SpvCapabilityMeshShadingNV)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with a Kernel, GLCompute, " | |
| "MeshNV, or TaskNV execution model."; | |
| } else { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Execution mode can only be used with a Kernel or " | |
| "GLCompute " | |
| "execution model."; | |
| } | |
| } | |
| default: | |
| break; | |
| } | |
| if (spvIsVulkanEnv(_.context()->target_env)) { | |
| if (mode == SpvExecutionModeOriginLowerLeft) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << _.VkErrorID(4653) | |
| << "In the Vulkan environment, the OriginLowerLeft execution mode " | |
| "must not be used."; | |
| } | |
| if (mode == SpvExecutionModePixelCenterInteger) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << _.VkErrorID(4654) | |
| << "In the Vulkan environment, the PixelCenterInteger execution " | |
| "mode must not be used."; | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateMemoryModel(ValidationState_t& _, | |
| const Instruction* inst) { | |
| // Already produced an error if multiple memory model instructions are | |
| // present. | |
| if (_.memory_model() != SpvMemoryModelVulkanKHR && | |
| _.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "VulkanMemoryModelKHR capability must only be specified if " | |
| "the VulkanKHR memory model is used."; | |
| } | |
| if (spvIsOpenCLEnv(_.context()->target_env)) { | |
| if ((_.addressing_model() != SpvAddressingModelPhysical32) && | |
| (_.addressing_model() != SpvAddressingModelPhysical64)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Addressing model must be Physical32 or Physical64 " | |
| << "in the OpenCL environment."; | |
| } | |
| if (_.memory_model() != SpvMemoryModelOpenCL) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Memory model must be OpenCL in the OpenCL environment."; | |
| } | |
| } | |
| if (spvIsVulkanEnv(_.context()->target_env)) { | |
| if ((_.addressing_model() != SpvAddressingModelLogical) && | |
| (_.addressing_model() != SpvAddressingModelPhysicalStorageBuffer64)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << _.VkErrorID(4635) | |
| << "Addressing model must be Logical or PhysicalStorageBuffer64 " | |
| << "in the Vulkan environment."; | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| } // namespace | |
| spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) { | |
| switch (inst->opcode()) { | |
| case SpvOpEntryPoint: | |
| if (auto error = ValidateEntryPoint(_, inst)) return error; | |
| break; | |
| case SpvOpExecutionMode: | |
| case SpvOpExecutionModeId: | |
| if (auto error = ValidateExecutionMode(_, inst)) return error; | |
| break; | |
| case SpvOpMemoryModel: | |
| if (auto error = ValidateMemoryModel(_, inst)) return error; | |
| break; | |
| default: | |
| break; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| } // namespace val | |
| } // namespace spvtools |