// Copyright (c) 2017 Google Inc. // // 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. // Validates OpCapability instruction. #include #include #include #include "source/diagnostic.h" #include "source/opcode.h" #include "source/val/instruction.h" #include "source/val/validate.h" #include "source/val/validation_state.h" namespace spvtools { namespace val { namespace { bool IsSupportGuaranteedVulkan_1_0(uint32_t capability) { switch (spv::Capability(capability)) { case spv::Capability::Matrix: case spv::Capability::Shader: case spv::Capability::InputAttachment: case spv::Capability::Sampled1D: case spv::Capability::Image1D: case spv::Capability::SampledBuffer: case spv::Capability::ImageBuffer: case spv::Capability::ImageQuery: case spv::Capability::DerivativeControl: return true; default: break; } return false; } bool IsSupportGuaranteedVulkan_1_1(uint32_t capability) { if (IsSupportGuaranteedVulkan_1_0(capability)) return true; switch (spv::Capability(capability)) { case spv::Capability::DeviceGroup: case spv::Capability::MultiView: return true; default: break; } return false; } bool IsSupportGuaranteedVulkan_1_2(uint32_t capability) { if (IsSupportGuaranteedVulkan_1_1(capability)) return true; switch (spv::Capability(capability)) { case spv::Capability::ShaderNonUniform: return true; default: break; } return false; } bool IsSupportOptionalVulkan_1_0(uint32_t capability) { switch (spv::Capability(capability)) { case spv::Capability::Geometry: case spv::Capability::Tessellation: case spv::Capability::Float64: case spv::Capability::Int64: case spv::Capability::Int16: case spv::Capability::TessellationPointSize: case spv::Capability::GeometryPointSize: case spv::Capability::ImageGatherExtended: case spv::Capability::StorageImageMultisample: case spv::Capability::UniformBufferArrayDynamicIndexing: case spv::Capability::SampledImageArrayDynamicIndexing: case spv::Capability::StorageBufferArrayDynamicIndexing: case spv::Capability::StorageImageArrayDynamicIndexing: case spv::Capability::ClipDistance: case spv::Capability::CullDistance: case spv::Capability::ImageCubeArray: case spv::Capability::SampleRateShading: case spv::Capability::SparseResidency: case spv::Capability::MinLod: case spv::Capability::SampledCubeArray: case spv::Capability::ImageMSArray: case spv::Capability::StorageImageExtendedFormats: case spv::Capability::InterpolationFunction: case spv::Capability::StorageImageReadWithoutFormat: case spv::Capability::StorageImageWriteWithoutFormat: case spv::Capability::MultiViewport: case spv::Capability::Int64Atomics: case spv::Capability::TransformFeedback: case spv::Capability::GeometryStreams: case spv::Capability::Float16: case spv::Capability::Int8: return true; default: break; } return false; } bool IsSupportOptionalVulkan_1_1(uint32_t capability) { if (IsSupportOptionalVulkan_1_0(capability)) return true; switch (spv::Capability(capability)) { case spv::Capability::GroupNonUniform: case spv::Capability::GroupNonUniformVote: case spv::Capability::GroupNonUniformArithmetic: case spv::Capability::GroupNonUniformBallot: case spv::Capability::GroupNonUniformShuffle: case spv::Capability::GroupNonUniformShuffleRelative: case spv::Capability::GroupNonUniformClustered: case spv::Capability::GroupNonUniformQuad: case spv::Capability::DrawParameters: // Alias spv::Capability::StorageBuffer16BitAccess. case spv::Capability::StorageUniformBufferBlock16: // Alias spv::Capability::UniformAndStorageBuffer16BitAccess. case spv::Capability::StorageUniform16: case spv::Capability::StoragePushConstant16: case spv::Capability::StorageInputOutput16: case spv::Capability::DeviceGroup: case spv::Capability::MultiView: case spv::Capability::VariablePointersStorageBuffer: case spv::Capability::VariablePointers: return true; default: break; } return false; } bool IsSupportOptionalVulkan_1_2(uint32_t capability) { if (IsSupportOptionalVulkan_1_1(capability)) return true; switch (spv::Capability(capability)) { case spv::Capability::DenormPreserve: case spv::Capability::DenormFlushToZero: case spv::Capability::SignedZeroInfNanPreserve: case spv::Capability::RoundingModeRTE: case spv::Capability::RoundingModeRTZ: case spv::Capability::VulkanMemoryModel: case spv::Capability::VulkanMemoryModelDeviceScope: case spv::Capability::StorageBuffer8BitAccess: case spv::Capability::UniformAndStorageBuffer8BitAccess: case spv::Capability::StoragePushConstant8: case spv::Capability::ShaderViewportIndex: case spv::Capability::ShaderLayer: case spv::Capability::PhysicalStorageBufferAddresses: case spv::Capability::RuntimeDescriptorArray: case spv::Capability::UniformTexelBufferArrayDynamicIndexing: case spv::Capability::StorageTexelBufferArrayDynamicIndexing: case spv::Capability::UniformBufferArrayNonUniformIndexing: case spv::Capability::SampledImageArrayNonUniformIndexing: case spv::Capability::StorageBufferArrayNonUniformIndexing: case spv::Capability::StorageImageArrayNonUniformIndexing: case spv::Capability::InputAttachmentArrayNonUniformIndexing: case spv::Capability::UniformTexelBufferArrayNonUniformIndexing: case spv::Capability::StorageTexelBufferArrayNonUniformIndexing: return true; default: break; } return false; } bool IsSupportGuaranteedOpenCL_1_2(uint32_t capability, bool embedded_profile) { switch (spv::Capability(capability)) { case spv::Capability::Addresses: case spv::Capability::Float16Buffer: case spv::Capability::Int16: case spv::Capability::Int8: case spv::Capability::Kernel: case spv::Capability::Linkage: case spv::Capability::Vector16: return true; case spv::Capability::Int64: return !embedded_profile; default: break; } return false; } bool IsSupportGuaranteedOpenCL_2_0(uint32_t capability, bool embedded_profile) { if (IsSupportGuaranteedOpenCL_1_2(capability, embedded_profile)) return true; switch (spv::Capability(capability)) { case spv::Capability::DeviceEnqueue: case spv::Capability::GenericPointer: case spv::Capability::Groups: case spv::Capability::Pipes: return true; default: break; } return false; } bool IsSupportGuaranteedOpenCL_2_2(uint32_t capability, bool embedded_profile) { if (IsSupportGuaranteedOpenCL_2_0(capability, embedded_profile)) return true; switch (spv::Capability(capability)) { case spv::Capability::SubgroupDispatch: case spv::Capability::PipeStorage: return true; default: break; } return false; } bool IsSupportOptionalOpenCL_1_2(uint32_t capability) { switch (spv::Capability(capability)) { case spv::Capability::ImageBasic: case spv::Capability::Float64: return true; default: break; } return false; } // Checks if |capability| was enabled by extension. bool IsEnabledByExtension(ValidationState_t& _, uint32_t capability) { spv_operand_desc operand_desc = nullptr; _.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, &operand_desc); // operand_desc is expected to be not null, otherwise validator would have // failed at an earlier stage. This 'assert' is 'just in case'. assert(operand_desc); ExtensionSet operand_exts(operand_desc->numExtensions, operand_desc->extensions); if (operand_exts.IsEmpty()) return false; return _.HasAnyOfExtensions(operand_exts); } bool IsEnabledByCapabilityOpenCL_1_2(ValidationState_t& _, uint32_t capability) { if (_.HasCapability(spv::Capability::ImageBasic)) { switch (spv::Capability(capability)) { case spv::Capability::LiteralSampler: case spv::Capability::Sampled1D: case spv::Capability::Image1D: case spv::Capability::SampledBuffer: case spv::Capability::ImageBuffer: return true; default: break; } return false; } return false; } bool IsEnabledByCapabilityOpenCL_2_0(ValidationState_t& _, uint32_t capability) { if (_.HasCapability(spv::Capability::ImageBasic)) { switch (spv::Capability(capability)) { case spv::Capability::ImageReadWrite: case spv::Capability::LiteralSampler: case spv::Capability::Sampled1D: case spv::Capability::Image1D: case spv::Capability::SampledBuffer: case spv::Capability::ImageBuffer: return true; default: break; } return false; } return false; } } // namespace // Validates that capability declarations use operands allowed in the current // context. spv_result_t CapabilityPass(ValidationState_t& _, const Instruction* inst) { if (inst->opcode() != spv::Op::OpCapability) return SPV_SUCCESS; assert(inst->operands().size() == 1); const spv_parsed_operand_t& operand = inst->operand(0); assert(operand.num_words == 1); assert(operand.offset < inst->words().size()); const uint32_t capability = inst->word(operand.offset); const auto capability_str = [&_, capability]() { spv_operand_desc desc = nullptr; if (_.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, &desc) != SPV_SUCCESS || !desc) { return std::string("Unknown"); } return std::string(desc->name); }; const auto env = _.context()->target_env; const bool opencl_embedded = env == SPV_ENV_OPENCL_EMBEDDED_1_2 || env == SPV_ENV_OPENCL_EMBEDDED_2_0 || env == SPV_ENV_OPENCL_EMBEDDED_2_1 || env == SPV_ENV_OPENCL_EMBEDDED_2_2; const std::string opencl_profile = opencl_embedded ? "Embedded" : "Full"; if (env == SPV_ENV_VULKAN_1_0) { if (!IsSupportGuaranteedVulkan_1_0(capability) && !IsSupportOptionalVulkan_1_0(capability) && !IsEnabledByExtension(_, capability)) { return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) << "Capability " << capability_str() << " is not allowed by Vulkan 1.0 specification" << " (or requires extension)"; } } else if (env == SPV_ENV_VULKAN_1_1) { if (!IsSupportGuaranteedVulkan_1_1(capability) && !IsSupportOptionalVulkan_1_1(capability) && !IsEnabledByExtension(_, capability)) { return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) << "Capability " << capability_str() << " is not allowed by Vulkan 1.1 specification" << " (or requires extension)"; } } else if (env == SPV_ENV_VULKAN_1_2) { if (!IsSupportGuaranteedVulkan_1_2(capability) && !IsSupportOptionalVulkan_1_2(capability) && !IsEnabledByExtension(_, capability)) { return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) << "Capability " << capability_str() << " is not allowed by Vulkan 1.2 specification" << " (or requires extension)"; } } else if (env == SPV_ENV_OPENCL_1_2 || env == SPV_ENV_OPENCL_EMBEDDED_1_2) { if (!IsSupportGuaranteedOpenCL_1_2(capability, opencl_embedded) && !IsSupportOptionalOpenCL_1_2(capability) && !IsEnabledByExtension(_, capability) && !IsEnabledByCapabilityOpenCL_1_2(_, capability)) { return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) << "Capability " << capability_str() << " is not allowed by OpenCL 1.2 " << opencl_profile << " Profile specification" << " (or requires extension or capability)"; } } else if (env == SPV_ENV_OPENCL_2_0 || env == SPV_ENV_OPENCL_EMBEDDED_2_0 || env == SPV_ENV_OPENCL_2_1 || env == SPV_ENV_OPENCL_EMBEDDED_2_1) { if (!IsSupportGuaranteedOpenCL_2_0(capability, opencl_embedded) && !IsSupportOptionalOpenCL_1_2(capability) && !IsEnabledByExtension(_, capability) && !IsEnabledByCapabilityOpenCL_2_0(_, capability)) { return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) << "Capability " << capability_str() << " is not allowed by OpenCL 2.0/2.1 " << opencl_profile << " Profile specification" << " (or requires extension or capability)"; } } else if (env == SPV_ENV_OPENCL_2_2 || env == SPV_ENV_OPENCL_EMBEDDED_2_2) { if (!IsSupportGuaranteedOpenCL_2_2(capability, opencl_embedded) && !IsSupportOptionalOpenCL_1_2(capability) && !IsEnabledByExtension(_, capability) && !IsEnabledByCapabilityOpenCL_2_0(_, capability)) { return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) << "Capability " << capability_str() << " is not allowed by OpenCL 2.2 " << opencl_profile << " Profile specification" << " (or requires extension or capability)"; } } return SPV_SUCCESS; } } // namespace val } // namespace spvtools