// 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_scopes.h" #include "source/diagnostic.h" #include "source/spirv_target_env.h" #include "source/val/instruction.h" #include "source/val/validation_state.h" namespace spvtools { namespace val { bool IsValidScope(uint32_t scope) { // Deliberately avoid a default case so we have to update the list when the // scopes list changes. switch (static_cast(scope)) { case SpvScopeCrossDevice: case SpvScopeDevice: case SpvScopeWorkgroup: case SpvScopeSubgroup: case SpvScopeInvocation: case SpvScopeQueueFamilyKHR: case SpvScopeShaderCallKHR: return true; case SpvScopeMax: break; } return false; } spv_result_t ValidateScope(ValidationState_t& _, const Instruction* inst, uint32_t scope) { SpvOp opcode = inst->opcode(); bool is_int32 = false, is_const_int32 = false; uint32_t value = 0; std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope); if (!is_int32) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": expected scope to be a 32-bit int"; } if (!is_const_int32) { if (_.HasCapability(SpvCapabilityShader) && !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be OpConstant when Shader capability is " << "present"; } if (_.HasCapability(SpvCapabilityShader) && _.HasCapability(SpvCapabilityCooperativeMatrixNV) && !spvOpcodeIsConstant(_.GetIdOpcode(scope))) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be constant or specialization constant when " << "CooperativeMatrixNV capability is present"; } } if (is_const_int32 && !IsValidScope(value)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope)); } return SPV_SUCCESS; } spv_result_t ValidateExecutionScope(ValidationState_t& _, const Instruction* inst, uint32_t scope) { SpvOp opcode = inst->opcode(); bool is_int32 = false, is_const_int32 = false; uint32_t value = 0; std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope); if (auto error = ValidateScope(_, inst, scope)) { return error; } if (!is_const_int32) { return SPV_SUCCESS; } // Vulkan specific rules if (spvIsVulkanEnv(_.context()->target_env)) { // Vulkan 1.1 specific rules if (_.context()->target_env != SPV_ENV_VULKAN_1_0) { // Scope for Non Uniform Group Operations must be limited to Subgroup if (spvOpcodeIsNonUniformGroupOperation(opcode) && value != SpvScopeSubgroup) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4642) << spvOpcodeString(opcode) << ": in Vulkan environment Execution scope is limited to " << "Subgroup"; } } // OpControlBarrier must only use Subgroup execution scope for a subset of // execution models. if (opcode == SpvOpControlBarrier && value != SpvScopeSubgroup) { std::string errorVUID = _.VkErrorID(4682); _.function(inst->function()->id()) ->RegisterExecutionModelLimitation([errorVUID]( SpvExecutionModel model, std::string* message) { if (model == SpvExecutionModelFragment || model == SpvExecutionModelVertex || model == SpvExecutionModelGeometry || model == SpvExecutionModelTessellationEvaluation || model == SpvExecutionModelRayGenerationKHR || model == SpvExecutionModelIntersectionKHR || model == SpvExecutionModelAnyHitKHR || model == SpvExecutionModelClosestHitKHR || model == SpvExecutionModelMissKHR) { if (message) { *message = errorVUID + "in Vulkan environment, OpControlBarrier execution scope " "must be Subgroup for Fragment, Vertex, Geometry, " "TessellationEvaluation, RayGeneration, Intersection, " "AnyHit, ClosestHit, and Miss execution models"; } return false; } return true; }); } // Only subset of execution models support Workgroup. if (value == SpvScopeWorkgroup) { std::string errorVUID = _.VkErrorID(4637); _.function(inst->function()->id()) ->RegisterExecutionModelLimitation( [errorVUID](SpvExecutionModel model, std::string* message) { if (model != SpvExecutionModelTaskNV && model != SpvExecutionModelMeshNV && model != SpvExecutionModelTaskEXT && model != SpvExecutionModelMeshEXT && model != SpvExecutionModelTessellationControl && model != SpvExecutionModelGLCompute) { if (message) { *message = errorVUID + "in Vulkan environment, Workgroup execution scope is " "only for TaskNV, MeshNV, TaskEXT, MeshEXT, " "TessellationControl, and GLCompute execution models"; } return false; } return true; }); } // Vulkan generic rules // Scope for execution must be limited to Workgroup or Subgroup if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4636) << spvOpcodeString(opcode) << ": in Vulkan environment Execution Scope is limited to " << "Workgroup and Subgroup"; } } // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. // General SPIRV rules // Scope for execution must be limited to Workgroup or Subgroup for // non-uniform operations if (spvOpcodeIsNonUniformGroupOperation(opcode) && value != SpvScopeSubgroup && value != SpvScopeWorkgroup) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": Execution scope is limited to Subgroup or Workgroup"; } return SPV_SUCCESS; } spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, uint32_t scope) { const SpvOp opcode = inst->opcode(); bool is_int32 = false, is_const_int32 = false; uint32_t value = 0; std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope); if (auto error = ValidateScope(_, inst, scope)) { return error; } if (!is_const_int32) { return SPV_SUCCESS; } if (value == SpvScopeQueueFamilyKHR) { if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) { return SPV_SUCCESS; } else { return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": Memory Scope QueueFamilyKHR requires capability " << "VulkanMemoryModelKHR"; } } if (value == SpvScopeDevice && _.HasCapability(SpvCapabilityVulkanMemoryModelKHR) && !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Use of device scope with VulkanKHR memory model requires the " << "VulkanMemoryModelDeviceScopeKHR capability"; } // Vulkan Specific rules if (spvIsVulkanEnv(_.context()->target_env)) { if (value != SpvScopeDevice && value != SpvScopeWorkgroup && value != SpvScopeSubgroup && value != SpvScopeInvocation && value != SpvScopeShaderCallKHR && value != SpvScopeQueueFamily) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4638) << spvOpcodeString(opcode) << ": in Vulkan environment Memory Scope is limited to Device, " "QueueFamily, Workgroup, ShaderCallKHR, Subgroup, or " "Invocation"; } else if (_.context()->target_env == SPV_ENV_VULKAN_1_0 && value == SpvScopeSubgroup && !_.HasCapability(SpvCapabilitySubgroupBallotKHR) && !_.HasCapability(SpvCapabilitySubgroupVoteKHR)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(6997) << spvOpcodeString(opcode) << ": in Vulkan 1.0 environment Memory Scope is can not be " "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR " "declared"; } if (value == SpvScopeShaderCallKHR) { std::string errorVUID = _.VkErrorID(4640); _.function(inst->function()->id()) ->RegisterExecutionModelLimitation( [errorVUID](SpvExecutionModel model, std::string* message) { if (model != SpvExecutionModelRayGenerationKHR && model != SpvExecutionModelIntersectionKHR && model != SpvExecutionModelAnyHitKHR && model != SpvExecutionModelClosestHitKHR && model != SpvExecutionModelMissKHR && model != SpvExecutionModelCallableKHR) { if (message) { *message = errorVUID + "ShaderCallKHR Memory Scope requires a ray tracing " "execution model"; } return false; } return true; }); } if (value == SpvScopeWorkgroup) { std::string errorVUID = _.VkErrorID(4639); _.function(inst->function()->id()) ->RegisterExecutionModelLimitation( [errorVUID](SpvExecutionModel model, std::string* message) { if (model != SpvExecutionModelGLCompute && model != SpvExecutionModelTaskNV && model != SpvExecutionModelMeshNV && model != SpvExecutionModelTaskEXT && model != SpvExecutionModelMeshEXT) { if (message) { *message = errorVUID + "Workgroup Memory Scope is limited to MeshNV, " "TaskNV, MeshEXT, TaskEXT and GLCompute " "execution model"; } return false; } return true; }); } } // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. return SPV_SUCCESS; } } // namespace val } // namespace spvtools