// 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 #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(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 '" << _.getIdName(entry_point_id) << "' is not a function."; } // Only check the shader execution models const SpvExecutionModel execution_model = inst->GetOperandAs(0); if (execution_model != SpvExecutionModelKernel) { const auto entry_point_type_id = entry_point->GetOperandAs(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 '" << _.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 '" << _.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(1) == SpvDecorationBuiltIn && i.GetOperandAs(2) == SpvBuiltInWorkgroupSize) { ok = true; break; } } } } if (!ok) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4683) << "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(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 '" << _.getIdName(entry_point_id) << "' is not the Entry Point " "operand of an OpEntryPoint."; } const auto mode = inst->GetOperandAs(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(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