| // 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 <cassert> | |
| #include <string> | |
| #include <unordered_set> | |
| #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 |