// 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 "source/val/validate.h"
#include <cassert>
#include <string>
#include <unordered_set>
#include "source/diagnostic.h"
#include "source/opcode.h"
#include "source/val/instruction.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
namespace {
bool IsSupportGuaranteedVulkan_1_0(uint32_t capability) {
switch (capability) {
case SpvCapabilityMatrix:
case SpvCapabilityShader:
case SpvCapabilityInputAttachment:
case SpvCapabilitySampled1D:
case SpvCapabilityImage1D:
case SpvCapabilitySampledBuffer:
case SpvCapabilityImageBuffer:
case SpvCapabilityImageQuery:
case SpvCapabilityDerivativeControl:
return true;
}
return false;
}
bool IsSupportGuaranteedVulkan_1_1(uint32_t capability) {
if (IsSupportGuaranteedVulkan_1_0(capability)) return true;
switch (capability) {
case SpvCapabilityDeviceGroup:
case SpvCapabilityMultiView:
return true;
}
return false;
}
bool IsSupportOptionalVulkan_1_0(uint32_t capability) {
switch (capability) {
case SpvCapabilityGeometry:
case SpvCapabilityTessellation:
case SpvCapabilityFloat64:
case SpvCapabilityInt64:
case SpvCapabilityInt16:
case SpvCapabilityTessellationPointSize:
case SpvCapabilityGeometryPointSize:
case SpvCapabilityImageGatherExtended:
case SpvCapabilityStorageImageMultisample:
case SpvCapabilityUniformBufferArrayDynamicIndexing:
case SpvCapabilitySampledImageArrayDynamicIndexing:
case SpvCapabilityStorageBufferArrayDynamicIndexing:
case SpvCapabilityStorageImageArrayDynamicIndexing:
case SpvCapabilityClipDistance:
case SpvCapabilityCullDistance:
case SpvCapabilityImageCubeArray:
case SpvCapabilitySampleRateShading:
case SpvCapabilitySparseResidency:
case SpvCapabilityMinLod:
case SpvCapabilitySampledCubeArray:
case SpvCapabilityImageMSArray:
case SpvCapabilityStorageImageExtendedFormats:
case SpvCapabilityInterpolationFunction:
case SpvCapabilityStorageImageReadWithoutFormat:
case SpvCapabilityStorageImageWriteWithoutFormat:
case SpvCapabilityMultiViewport:
case SpvCapabilityInt64Atomics:
case SpvCapabilityTransformFeedback:
case SpvCapabilityGeometryStreams:
case SpvCapabilityFloat16:
case SpvCapabilityInt8:
return true;
}
return false;
}
bool IsSupportOptionalVulkan_1_1(uint32_t capability) {
if (IsSupportOptionalVulkan_1_0(capability)) return true;
switch (capability) {
case SpvCapabilityGroupNonUniform:
case SpvCapabilityGroupNonUniformVote:
case SpvCapabilityGroupNonUniformArithmetic:
case SpvCapabilityGroupNonUniformBallot:
case SpvCapabilityGroupNonUniformShuffle:
case SpvCapabilityGroupNonUniformShuffleRelative:
case SpvCapabilityGroupNonUniformClustered:
case SpvCapabilityGroupNonUniformQuad:
case SpvCapabilityDrawParameters:
// Alias SpvCapabilityStorageBuffer16BitAccess.
case SpvCapabilityStorageUniformBufferBlock16:
// Alias SpvCapabilityUniformAndStorageBuffer16BitAccess.
case SpvCapabilityStorageUniform16:
case SpvCapabilityStoragePushConstant16:
case SpvCapabilityStorageInputOutput16:
case SpvCapabilityDeviceGroup:
case SpvCapabilityMultiView:
case SpvCapabilityVariablePointersStorageBuffer:
case SpvCapabilityVariablePointers:
return true;
}
return false;
}
bool IsSupportGuaranteedOpenCL_1_2(uint32_t capability, bool embedded_profile) {
switch (capability) {
case SpvCapabilityAddresses:
case SpvCapabilityFloat16Buffer:
case SpvCapabilityGroups:
case SpvCapabilityInt16:
case SpvCapabilityInt8:
case SpvCapabilityKernel:
case SpvCapabilityLinkage:
case SpvCapabilityVector16:
return true;
case SpvCapabilityInt64:
return !embedded_profile;
case SpvCapabilityPipes:
return embedded_profile;
}
return false;
}
bool IsSupportGuaranteedOpenCL_2_0(uint32_t capability, bool embedded_profile) {
if (IsSupportGuaranteedOpenCL_1_2(capability, embedded_profile)) return true;
switch (capability) {
case SpvCapabilityDeviceEnqueue:
case SpvCapabilityGenericPointer:
case SpvCapabilityPipes:
return true;
}
return false;
}
bool IsSupportGuaranteedOpenCL_2_2(uint32_t capability, bool embedded_profile) {
if (IsSupportGuaranteedOpenCL_2_0(capability, embedded_profile)) return true;
switch (capability) {
case SpvCapabilitySubgroupDispatch:
case SpvCapabilityPipeStorage:
return true;
}
return false;
}
bool IsSupportOptionalOpenCL_1_2(uint32_t capability) {
switch (capability) {
case SpvCapabilityImageBasic:
case SpvCapabilityFloat64:
return true;
}
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(SpvCapabilityImageBasic)) {
switch (capability) {
case SpvCapabilityLiteralSampler:
case SpvCapabilitySampled1D:
case SpvCapabilityImage1D:
case SpvCapabilitySampledBuffer:
case SpvCapabilityImageBuffer:
return true;
}
return false;
}
return false;
}
bool IsEnabledByCapabilityOpenCL_2_0(ValidationState_t& _,
uint32_t capability) {
if (_.HasCapability(SpvCapabilityImageBasic)) {
switch (capability) {
case SpvCapabilityImageReadWrite:
case SpvCapabilityLiteralSampler:
case SpvCapabilitySampled1D:
case SpvCapabilityImage1D:
case SpvCapabilitySampledBuffer:
case SpvCapabilityImageBuffer:
return true;
}
return false;
}
return false;
}
bool IsSupportGuaranteedWebGPU(uint32_t capability) {
switch (capability) {
case SpvCapabilityMatrix:
case SpvCapabilityShader:
case SpvCapabilitySampled1D:
case SpvCapabilityImage1D:
case SpvCapabilityDerivativeControl:
case SpvCapabilityImageQuery:
return true;
}
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() != SpvOpCapability) 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_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)";
}
} else if (env == SPV_ENV_WEBGPU_0) {
if (!IsSupportGuaranteedWebGPU(capability) &&
!IsEnabledByExtension(_, capability)) {
return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
<< "Capability " << capability_str()
<< " is not allowed by WebGPU specification"
<< " (or requires extension)";
}
}
return SPV_SUCCESS;
}
} // namespace val
} // namespace spvtools