// 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 #include "source/opcode.h" #include "source/spirv_target_env.h" #include "source/val/instruction.h" #include "source/val/validate.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 || spv::Op::OpFunction != 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 spv::ExecutionModel execution_model = inst->GetOperandAs(0); if (execution_model != spv::ExecutionModel::Kernel) { 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 || spv::Op::OpTypeVoid != 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(spv::Capability::Shader)) { switch (execution_model) { case spv::ExecutionModel::Fragment: if (execution_modes && execution_modes->count(spv::ExecutionMode::OriginUpperLeft) && execution_modes->count(spv::ExecutionMode::OriginLowerLeft)) { 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(spv::ExecutionMode::OriginUpperLeft) && !execution_modes->count(spv::ExecutionMode::OriginLowerLeft))) { 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 spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::DepthGreater: case spv::ExecutionMode::DepthLess: case spv::ExecutionMode::DepthUnchanged: 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 spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::PixelInterlockOrderedEXT: case spv::ExecutionMode::PixelInterlockUnorderedEXT: case spv::ExecutionMode::SampleInterlockOrderedEXT: case spv::ExecutionMode::SampleInterlockUnorderedEXT: case spv::ExecutionMode::ShadingRateInterlockOrderedEXT: case spv::ExecutionMode:: ShadingRateInterlockUnorderedEXT: 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."; } if (execution_modes && 1 < std::count_if( execution_modes->begin(), execution_modes->end(), [](const spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::StencilRefUnchangedFrontAMD: case spv::ExecutionMode::StencilRefLessFrontAMD: case spv::ExecutionMode::StencilRefGreaterFrontAMD: return true; default: return false; } })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Fragment execution model entry points can specify at most " "one of StencilRefUnchangedFrontAMD, " "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD " "execution modes."; } if (execution_modes && 1 < std::count_if( execution_modes->begin(), execution_modes->end(), [](const spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::StencilRefUnchangedBackAMD: case spv::ExecutionMode::StencilRefLessBackAMD: case spv::ExecutionMode::StencilRefGreaterBackAMD: return true; default: return false; } })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Fragment execution model entry points can specify at most " "one of StencilRefUnchangedBackAMD, " "StencilRefLessBackAMD or StencilRefGreaterBackAMD " "execution modes."; } break; case spv::ExecutionModel::TessellationControl: case spv::ExecutionModel::TessellationEvaluation: if (execution_modes && 1 < std::count_if( execution_modes->begin(), execution_modes->end(), [](const spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::SpacingEqual: case spv::ExecutionMode::SpacingFractionalEven: case spv::ExecutionMode::SpacingFractionalOdd: 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 spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::Triangles: case spv::ExecutionMode::Quads: case spv::ExecutionMode::Isolines: 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 spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::VertexOrderCw: case spv::ExecutionMode::VertexOrderCcw: 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 spv::ExecutionModel::Geometry: if (!execution_modes || 1 != std::count_if( execution_modes->begin(), execution_modes->end(), [](const spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::InputPoints: case spv::ExecutionMode::InputLines: case spv::ExecutionMode::InputLinesAdjacency: case spv::ExecutionMode::Triangles: case spv::ExecutionMode::InputTrianglesAdjacency: 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 spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::OutputPoints: case spv::ExecutionMode::OutputLineStrip: case spv::ExecutionMode::OutputTriangleStrip: 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; case spv::ExecutionModel::MeshEXT: if (!execution_modes || 1 != std::count_if(execution_modes->begin(), execution_modes->end(), [](const spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::OutputPoints: case spv::ExecutionMode::OutputLinesEXT: case spv::ExecutionMode::OutputTrianglesEXT: return true; default: return false; } })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "MeshEXT execution model entry points must specify exactly " "one of OutputPoints, OutputLinesEXT, or " "OutputTrianglesEXT Execution Modes."; } else if (2 != std::count_if( execution_modes->begin(), execution_modes->end(), [](const spv::ExecutionMode& mode) { switch (mode) { case spv::ExecutionMode::OutputPrimitivesEXT: case spv::ExecutionMode::OutputVertices: return true; default: return false; } })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "MeshEXT execution model entry points must specify both " "OutputPrimitivesEXT and OutputVertices Execution Modes."; } break; default: break; } } if (spvIsVulkanEnv(_.context()->target_env)) { switch (execution_model) { case spv::ExecutionModel::GLCompute: if (!execution_modes || !execution_modes->count(spv::ExecutionMode::LocalSize)) { bool ok = false; for (auto& i : _.ordered_instructions()) { if (i.opcode() == spv::Op::OpDecorate) { if (i.operands().size() > 2) { if (i.GetOperandAs(1) == spv::Decoration::BuiltIn && i.GetOperandAs(2) == spv::BuiltIn::WorkgroupSize) { ok = true; break; } } } if (i.opcode() == spv::Op::OpExecutionModeId) { const auto mode = i.GetOperandAs(1); if (mode == spv::ExecutionMode::LocalSizeId) { ok = true; break; } } } if (!ok) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(6426) << "In the Vulkan environment, GLCompute execution model " "entry points require either the LocalSize or " "LocalSizeId 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() == spv::Op::OpExecutionModeId) { 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 == spv::ExecutionMode::SubgroupsPerWorkgroupId || mode == spv::ExecutionMode::LocalSizeHintId || mode == spv::ExecutionMode::LocalSizeId) { 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 == spv::ExecutionMode::SubgroupsPerWorkgroupId || mode == spv::ExecutionMode::LocalSizeHintId || mode == spv::ExecutionMode::LocalSizeId) { 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 spv::ExecutionMode::Invocations: case spv::ExecutionMode::InputPoints: case spv::ExecutionMode::InputLines: case spv::ExecutionMode::InputLinesAdjacency: case spv::ExecutionMode::InputTrianglesAdjacency: case spv::ExecutionMode::OutputLineStrip: case spv::ExecutionMode::OutputTriangleStrip: if (!std::all_of(models->begin(), models->end(), [](const spv::ExecutionModel& model) { return model == spv::ExecutionModel::Geometry; })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with the Geometry execution " "model."; } break; case spv::ExecutionMode::OutputPoints: if (!std::all_of( models->begin(), models->end(), [&_](const spv::ExecutionModel& model) { switch (model) { case spv::ExecutionModel::Geometry: return true; case spv::ExecutionModel::MeshNV: return _.HasCapability(spv::Capability::MeshShadingNV); case spv::ExecutionModel::MeshEXT: return _.HasCapability(spv::Capability::MeshShadingEXT); default: return false; } })) { if (_.HasCapability(spv::Capability::MeshShadingNV) || _.HasCapability(spv::Capability::MeshShadingEXT)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with the Geometry " "MeshNV or MeshEXT execution model."; } else { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with the Geometry " "execution " "model."; } } break; case spv::ExecutionMode::SpacingEqual: case spv::ExecutionMode::SpacingFractionalEven: case spv::ExecutionMode::SpacingFractionalOdd: case spv::ExecutionMode::VertexOrderCw: case spv::ExecutionMode::VertexOrderCcw: case spv::ExecutionMode::PointMode: case spv::ExecutionMode::Quads: case spv::ExecutionMode::Isolines: if (!std::all_of( models->begin(), models->end(), [](const spv::ExecutionModel& model) { return (model == spv::ExecutionModel::TessellationControl) || (model == spv::ExecutionModel::TessellationEvaluation); })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with a tessellation " "execution model."; } break; case spv::ExecutionMode::Triangles: if (!std::all_of(models->begin(), models->end(), [](const spv::ExecutionModel& model) { switch (model) { case spv::ExecutionModel::Geometry: case spv::ExecutionModel::TessellationControl: case spv::ExecutionModel::TessellationEvaluation: 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 spv::ExecutionMode::OutputVertices: if (!std::all_of( models->begin(), models->end(), [&_](const spv::ExecutionModel& model) { switch (model) { case spv::ExecutionModel::Geometry: case spv::ExecutionModel::TessellationControl: case spv::ExecutionModel::TessellationEvaluation: return true; case spv::ExecutionModel::MeshNV: return _.HasCapability(spv::Capability::MeshShadingNV); case spv::ExecutionModel::MeshEXT: return _.HasCapability(spv::Capability::MeshShadingEXT); default: return false; } })) { if (_.HasCapability(spv::Capability::MeshShadingNV) || _.HasCapability(spv::Capability::MeshShadingEXT)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with a Geometry, " "tessellation, MeshNV or MeshEXT 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 spv::ExecutionMode::OutputLinesEXT: case spv::ExecutionMode::OutputTrianglesEXT: case spv::ExecutionMode::OutputPrimitivesEXT: if (!std::all_of(models->begin(), models->end(), [](const spv::ExecutionModel& model) { return (model == spv::ExecutionModel::MeshEXT || model == spv::ExecutionModel::MeshNV); })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with the MeshEXT or MeshNV " "execution " "model."; } break; case spv::ExecutionMode::PixelCenterInteger: case spv::ExecutionMode::OriginUpperLeft: case spv::ExecutionMode::OriginLowerLeft: case spv::ExecutionMode::EarlyFragmentTests: case spv::ExecutionMode::DepthReplacing: case spv::ExecutionMode::DepthGreater: case spv::ExecutionMode::DepthLess: case spv::ExecutionMode::DepthUnchanged: case spv::ExecutionMode::PixelInterlockOrderedEXT: case spv::ExecutionMode::PixelInterlockUnorderedEXT: case spv::ExecutionMode::SampleInterlockOrderedEXT: case spv::ExecutionMode::SampleInterlockUnorderedEXT: case spv::ExecutionMode::ShadingRateInterlockOrderedEXT: case spv::ExecutionMode::ShadingRateInterlockUnorderedEXT: case spv::ExecutionMode::EarlyAndLateFragmentTestsAMD: case spv::ExecutionMode::StencilRefUnchangedFrontAMD: case spv::ExecutionMode::StencilRefGreaterFrontAMD: case spv::ExecutionMode::StencilRefLessFrontAMD: case spv::ExecutionMode::StencilRefUnchangedBackAMD: case spv::ExecutionMode::StencilRefGreaterBackAMD: case spv::ExecutionMode::StencilRefLessBackAMD: if (!std::all_of(models->begin(), models->end(), [](const spv::ExecutionModel& model) { return model == spv::ExecutionModel::Fragment; })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with the Fragment execution " "model."; } break; case spv::ExecutionMode::LocalSizeHint: case spv::ExecutionMode::VecTypeHint: case spv::ExecutionMode::ContractionOff: case spv::ExecutionMode::LocalSizeHintId: if (!std::all_of(models->begin(), models->end(), [](const spv::ExecutionModel& model) { return model == spv::ExecutionModel::Kernel; })) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with the Kernel execution " "model."; } break; case spv::ExecutionMode::LocalSize: case spv::ExecutionMode::LocalSizeId: if (mode == spv::ExecutionMode::LocalSizeId && !_.IsLocalSizeIdAllowed()) return _.diag(SPV_ERROR_INVALID_DATA, inst) << "LocalSizeId mode is not allowed by the current environment."; if (!std::all_of( models->begin(), models->end(), [&_](const spv::ExecutionModel& model) { switch (model) { case spv::ExecutionModel::Kernel: case spv::ExecutionModel::GLCompute: return true; case spv::ExecutionModel::TaskNV: case spv::ExecutionModel::MeshNV: return _.HasCapability(spv::Capability::MeshShadingNV); case spv::ExecutionModel::TaskEXT: case spv::ExecutionModel::MeshEXT: return _.HasCapability(spv::Capability::MeshShadingEXT); default: return false; } })) { if (_.HasCapability(spv::Capability::MeshShadingNV) || _.HasCapability(spv::Capability::MeshShadingEXT)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Execution mode can only be used with a Kernel, GLCompute, " "MeshNV, MeshEXT, TaskNV or TaskEXT 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 == spv::ExecutionMode::OriginLowerLeft) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4653) << "In the Vulkan environment, the OriginLowerLeft execution mode " "must not be used."; } if (mode == spv::ExecutionMode::PixelCenterInteger) { 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() != spv::MemoryModel::VulkanKHR && _.HasCapability(spv::Capability::VulkanMemoryModelKHR)) { 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() != spv::AddressingModel::Physical32) && (_.addressing_model() != spv::AddressingModel::Physical64)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Addressing model must be Physical32 or Physical64 " << "in the OpenCL environment."; } if (_.memory_model() != spv::MemoryModel::OpenCL) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Memory model must be OpenCL in the OpenCL environment."; } } if (spvIsVulkanEnv(_.context()->target_env)) { if ((_.addressing_model() != spv::AddressingModel::Logical) && (_.addressing_model() != spv::AddressingModel::PhysicalStorageBuffer64)) { 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 spv::Op::OpEntryPoint: if (auto error = ValidateEntryPoint(_, inst)) return error; break; case spv::Op::OpExecutionMode: case spv::Op::OpExecutionModeId: if (auto error = ValidateExecutionMode(_, inst)) return error; break; case spv::Op::OpMemoryModel: if (auto error = ValidateMemoryModel(_, inst)) return error; break; default: break; } return SPV_SUCCESS; } } // namespace val } // namespace spvtools