blob: e58393c15deee029379d75f0ce055922bef0f105 [file] [log] [blame]
#!/usr/bin/python3 -i
#
# Copyright (c) 2020-2024 The Khronos Group 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.
import sys
import os
import json
from generators.vulkan_object import SpirvEnables
from generators.base_generator import BaseGenerator
from generators.generator_utils import IsNonVulkanSprivCapability
#
# Generate SPIR-V validation for SPIR-V extensions and capabilities
class SpirvValidationHelperOutputGenerator(BaseGenerator):
def __init__(self, grammar):
BaseGenerator.__init__(self)
# Sometimes the Vulkan-Headers XML will mention new SPIR-V capability or extensions
# that require an update of the SPIRV-Headers which might not be ready to pull in.
# Get the list of safe enum values to use from the SPIR-V grammar
self.capabilityList = []
self.provisionalList = []
with open(grammar) as grammar_file:
grammar_dict = json.load(grammar_file)
for kind in grammar_dict['operand_kinds']:
if kind['kind'] == 'Capability':
for enum in kind['enumerants']:
if not IsNonVulkanSprivCapability(enum['enumerant']):
self.capabilityList.append(enum['enumerant'])
if 'provisional' in enum:
self.provisionalList.append(enum['enumerant'])
break
# Promoted features structure in state_tracker.cpp are put in the VkPhysicalDeviceVulkan*Features structs
# but the XML can still list them. This list all promoted structs to ignore since they are aliased.
# Tried to generate these, but no reliable way from vk.xml
self.promotedFeatures = [
# 1.1
"VkPhysicalDevice16BitStorageFeatures",
"VkPhysicalDeviceMultiviewFeatures",
"VkPhysicalDeviceVariablePointersFeatures",
"VkPhysicalDeviceProtectedMemoryFeatures",
"VkPhysicalDeviceSamplerYcbcrConversionFeatures",
"VkPhysicalDeviceShaderDrawParametersFeatures",
# 1.2
"VkPhysicalDevice8BitStorageFeatures",
"VkPhysicalDeviceShaderFloat16Int8Features",
"VkPhysicalDeviceDescriptorIndexingFeatures",
"VkPhysicalDeviceScalarBlockLayoutFeatures",
"VkPhysicalDeviceImagelessFramebufferFeatures",
"VkPhysicalDeviceUniformBufferStandardLayoutFeatures",
"VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures",
"VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures",
"VkPhysicalDeviceTimelineSemaphoreFeatures",
"VkPhysicalDeviceBufferDeviceAddressFeatures",
"VkPhysicalDeviceShaderAtomicInt64Features",
"VkPhysicalDeviceVulkanMemoryModelFeatures",
# 1.3
"VkPhysicalDeviceShaderDemoteToHelperInvocationFeatures",
"VkPhysicalDeviceShaderIntegerDotProductFeatures",
]
# Properties are harder to handle genearted without generating a template for every property struct type
# The simpler solution is create strings that will be printed out as static comparisons at compile time
# The Map is used to map Vulkan property structs with the state tracker variable name
self.propertyInfo = dict()
self.propertyMap = {
'VkPhysicalDeviceVulkan11Properties' : 'phys_dev_props_core11',
'VkPhysicalDeviceVulkan12Properties' : 'phys_dev_props_core12',
'VkPhysicalDeviceVulkan13Properties' : 'phys_dev_props_core13', # Not used, but left for grep searching
}
#
# Creates the value of the struct declared in RequiredSpirvInfo
def createMapValue(self, name: str, enable: SpirvEnables, isExtension: bool) -> str:
out = []
if enable.version is not None:
# Version should be VK_VERSION_x_x as defined in header but need to get as VK_API_VERSION_x_x
version = enable.version.replace('VK_VERSION', 'VK_API_VERSION')
out.append(f'{{{version}, nullptr, nullptr, ""}}')
elif enable.feature is not None:
out.append(f'{{0, &DeviceFeatures::{enable.feature}, nullptr, ""}}')
elif enable.extension is not None:
# All fields in DeviceExtensions should just be the extension name lowercase
out.append(f'{{0, nullptr, &DeviceExtensions::{enable.extension.lower()}, ""}}')
elif enable.property is not None:
propertyStruct = enable.property
# Need to make sure to return a boolean value to prevent compiler warning for implicit conversions
propertyLogic = f'({propertyStruct}::{enable.member} & {enable.value}) != 0'
# Property might have multiple items per capability/extension
if name not in self.propertyInfo:
self.propertyInfo[name] = []
# Save info later to be printed out
self.propertyInfo[name].append({
"logic" : propertyLogic,
"struct" : propertyStruct,
"isExtension" : isExtension
})
# For properties, this string is just for human readableness
out.append(f'{{0, nullptr, nullptr, "{propertyLogic}"}}')
else:
out.append('{0, nullptr, nullptr, ""}')
return "".join(out)
def generate(self):
out = []
out.append(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
// See {os.path.basename(__file__)} for modifications
/***************************************************************************
*
* Copyright (c) 2020-2024 The Khronos Group 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.
*
* This file is related to anything that is found in the Vulkan XML related
* to SPIR-V. Anything related to the SPIR-V grammar belongs in spirv_grammar_helper
*
****************************************************************************/
''')
out.append('// NOLINTBEGIN') # Wrap for clang-tidy to ignore
out.append('''
#include <string>
#include <string_view>
#include <functional>
#include <spirv/unified1/spirv.hpp>
#include "vk_extension_helper.h"
#include "state_tracker/shader_instruction.h"
#include "core_checks/core_validation.h"
''')
#
# Creates the FeaturePointer struct to map features with those in the layers state tracker
out.append('''
struct FeaturePointer {
// Callable object to test if this feature is enabled in the given aggregate feature struct
const std::function<bool(const DeviceFeatures &)> IsEnabled;
// Test if feature pointer is populated
explicit operator bool() const { return static_cast<bool>(IsEnabled); }
// Default and nullptr constructor to create an empty FeaturePointer
FeaturePointer() : IsEnabled(nullptr) {}
FeaturePointer(std::nullptr_t ptr) : IsEnabled(nullptr) {}
FeaturePointer(bool DeviceFeatures::*ptr)
: IsEnabled([=](const DeviceFeatures &features) { return features.*ptr; }) {}
};
''')
out.append('''
// Each instance of the struct will only have a singel field non-null
struct RequiredSpirvInfo {
uint32_t version;
FeaturePointer feature;
ExtEnabled DeviceExtensions::*extension;
const char* property; // For human readability and make some capabilities unique
};
''')
#
# Build the struct with all the requirments for the spirv capabilities
out.append('const std::unordered_multimap<uint32_t, RequiredSpirvInfo>& GetSpirvCapabilites() {\n')
out.append('// clang-format off\n')
out.append(' static const std::unordered_multimap<uint32_t, RequiredSpirvInfo> spirv_capabilities = {')
for spirv in [x for x in self.vk.spirv if x.capability]:
for enable in [x for x in spirv.enable if x.struct is None or x.struct not in self.promotedFeatures]:
if spirv.name not in self.capabilityList:
out.append('\n // Not found in current SPIR-V Headers\n // ')
elif spirv.name in self.provisionalList:
out.append('\n#ifdef VK_ENABLE_BETA_EXTENSIONS\n ')
else:
out.append('\n ')
out.append(f'{{spv::Capability{spirv.name}, {self.createMapValue(spirv.name, enable, False)}}},')
out.append('\n#endif') if spirv.name in self.provisionalList else None
out.append('\n };\n')
out.append('// clang-format on\n')
out.append(' return spirv_capabilities;\n')
out.append('};\n')
out.append('\n')
#
# Build the struct with all the requirments for the spirv extensions
out.append('const std::unordered_multimap<std::string_view, RequiredSpirvInfo>& GetSpirvExtensions() {\n')
out.append('// clang-format off\n')
out.append(' static const std::unordered_multimap<std::string_view, RequiredSpirvInfo> spirv_extensions = {')
for spirv in [x for x in self.vk.spirv if x.extension]:
for enable in spirv.enable:
out.append(f'\n {{"{spirv.name}", {self.createMapValue(spirv.name, enable, True)}}},')
out.append('\n };\n')
out.append('// clang-format on\n')
out.append(' return spirv_extensions;\n')
out.append('}\n')
out.append('\n')
#
# Creates the Enum string helpers for better error messages. Same idea of vk_enum_string_helper.h but for SPIR-V
out.append('static inline const char* string_SpvCapability(uint32_t input_value) {\n')
out.append(' switch ((spv::Capability)input_value) {\n')
for name in self.capabilityList:
out.append('#ifdef VK_ENABLE_BETA_EXTENSIONS\n') if name in self.provisionalList else None
out.append(f' case spv::Capability{name}:\n')
out.append(f' return "{name}";\n')
out.append('#endif\n') if name in self.provisionalList else None
out.append(' default:\n')
out.append(' return \"Unhandled OpCapability\";\n')
out.append(' };\n')
out.append('}\n')
#
# Creates SPIR-V image format helper
out.append('''
// Will return the Vulkan format for a given SPIR-V image format value
// Note: will return VK_FORMAT_UNDEFINED if non valid input
// This was in vk_format_utils but the SPIR-V Header dependency was an issue
// see https://github.com/KhronosGroup/Vulkan-ValidationLayers/pull/4647
VkFormat CoreChecks::CompatibleSpirvImageFormat(uint32_t spirv_image_format) const {
switch (spirv_image_format) {
''')
for format in [x for x in self.vk.formats.values() if x.spirvImageFormat]:
out.append(f' case spv::ImageFormat{format.spirvImageFormat}:\n')
out.append(f' return {format.name};\n')
out.append(' default:\n')
out.append(' return VK_FORMAT_UNDEFINED;\n')
out.append(' };\n')
out.append('}\n')
out.append('''
// clang-format off
static inline const char* SpvCapabilityRequirements(uint32_t capability) {
static const vvl::unordered_map<uint32_t, std::string_view> table {
''')
for spirv in [x for x in self.vk.spirv if x.capability and x.name in self.capabilityList]:
requirment = ''
for index, enable in enumerate([x for x in spirv.enable if x.struct is None or x.struct not in self.promotedFeatures]):
requirment += ' OR ' if (index != 0) else ''
if enable.version is not None:
requirment += enable.version
elif enable.feature is not None:
requirment += f'{enable.struct}::{enable.feature}'
elif enable.extension is not None:
requirment += enable.extension
elif enable.property is not None:
requirment += f'({enable.property}::{enable.member} == {enable.value})'
out.append('#ifdef VK_ENABLE_BETA_EXTENSIONS\n') if spirv.name in self.provisionalList else None
out.append(f' {{spv::Capability{spirv.name}, "{requirment}"}},\n')
out.append('#endif // VK_ENABLE_BETA_EXTENSIONS\n') if spirv.name in self.provisionalList else None
out.append(''' };
// VUs before catch unknown capabilities
const auto entry = table.find(capability);
return entry->second.data();
}
// clang-format on
''')
out.append('''
// clang-format off
static inline std::string SpvExtensionRequirments(std::string_view extension) {
static const vvl::unordered_map<std::string_view, vvl::Requirements> table {
''')
for spirv in [x for x in self.vk.spirv if x.extension]:
requirment = ''
for index, enable in enumerate(spirv.enable):
requirment += ', ' if (index != 0) else ''
if enable.version is not None:
requirment += f'{{vvl::Version::_{enable.version}}}'
elif enable.extension is not None:
requirment += f'{{vvl::Extension::_{enable.extension}}}'
elif enable.feature is not None or enable.property is not None:
print("Need to add support for feature/properties in spirv extensions")
sys.exit(1)
out.append(f' {{"{spirv.name}", {{{requirment}}}}},\n')
out.append(''' };
// VUs before catch unknown extensions
const auto entry = table.find(extension);
return String(entry->second);
}
// clang-format on
''')
# The chance of an SPIR-V extension having a property as a requirement is low
# Instead of writting complex (and more confusing) code, just go back match what
# we do for capabilities if one is ever added to the XML
if len([infos for infos in self.propertyInfo.values() if infos[0]['isExtension']]) > 0:
print("Error: XML has added a property requirement to a SPIR-V Extension")
sys.exit(1)
#
# The main function to validate all the extensions and capabilities
out.append('''
bool CoreChecks::ValidateShaderCapabilitiesAndExtensions(const spirv::Instruction &insn, const Location& loc) const {
bool skip = false;
const bool pipeline = loc.function != vvl::Func::vkCreateShadersEXT;
if (insn.Opcode() == spv::OpCapability) {
// All capabilities are generated so if it is not in the list it is not supported by Vulkan
if (GetSpirvCapabilites().count(insn.Word(1)) == 0) {
const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08739" : "VUID-VkShaderCreateInfoEXT-pCode-08739";
skip |= LogError(vuid, device, loc,
"SPIR-V has Capability (%s) declared, but this is not supported by Vulkan.", string_SpvCapability(insn.Word(1)));
return skip; // no known capability to validate
}
// Each capability has one or more requirements to check
// Only one item has to be satisfied and an error only occurs
// when all are not satisfied
auto caps = GetSpirvCapabilites().equal_range(insn.Word(1));
bool has_support = false;
for (auto it = caps.first; (it != caps.second) && (has_support == false); ++it) {
if (it->second.version) {
if (api_version >= it->second.version) {
has_support = true;
}
} else if (it->second.feature) {
if (it->second.feature.IsEnabled(enabled_features)) {
has_support = true;
}
} else if (it->second.extension) {
// kEnabledByApiLevel is not valid as some extension are promoted with feature bits to be used.
// If the new Api Level gives support, it will be caught in the "it->second.version" check instead.
if (IsExtEnabledByCreateinfo(device_extensions.*(it->second.extension))) {
has_support = true;
}
} else if (it->second.property) {
// support is or'ed as only one has to be supported (if applicable)
switch (insn.Word(1)) {''')
for name, infos in sorted(self.propertyInfo.items()):
# Only capabilities here (all items in array are the same)
if infos[0]['isExtension']:
continue
# use triple-tick syntax to keep tab alignment for generated code
out.append(f'''
case spv::Capability{name}:''')
for info in infos:
# Need to string replace property string to create valid C++ logic
logic = info['logic'].replace('::', '.')
logic = logic.replace(info['struct'], self.propertyMap[info['struct']])
out.append(f'''
has_support |= ({logic});''')
out.append('''
break;''')
out.append('''
default:
break;
}
}
}
''')
out.append('''
if (has_support == false) {
const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08740" : "VUID-VkShaderCreateInfoEXT-pCode-08740";
skip |= LogError(vuid, device, loc,
"SPIR-V Capability %s was declared, but one of the following requirements is required (%s).", string_SpvCapability(insn.Word(1)), SpvCapabilityRequirements(insn.Word(1)));
}
// Portability checks
if (IsExtEnabled(device_extensions.vk_khr_portability_subset)) {
if ((VK_FALSE == enabled_features.shaderSampleRateInterpolationFunctions) &&
(spv::CapabilityInterpolationFunction == insn.Word(1))) {
skip |= LogError("VUID-RuntimeSpirv-shaderSampleRateInterpolationFunctions-06325", device, loc,
"SPIR-V (portability error) InterpolationFunction Capability are not supported "
"by this platform");
}
}
} else if (insn.Opcode() == spv::OpExtension) {
static const std::string spv_prefix = "SPV_";
std::string extension_name = insn.GetAsString(1);
if (0 == extension_name.compare(0, spv_prefix.size(), spv_prefix)) {
if (GetSpirvExtensions().count(extension_name) == 0) {
const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08741" : "VUID-VkShaderCreateInfoEXT-pCode-08741";
skip |= LogError(vuid, device,loc,
"SPIR-V Extension %s was declared, but that is not supported by Vulkan.", extension_name.c_str());
return skip; // no known extension to validate
}
} else {
const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08741" : "VUID-VkShaderCreateInfoEXT-pCode-08741";
skip |= LogError( vuid, device,loc,
"SPIR-V Extension %s was declared, but this is not a SPIR-V extension. Please use a SPIR-V"
" extension (https://github.com/KhronosGroup/SPIRV-Registry) for OpExtension instructions. Non-SPIR-V extensions can be"
" recorded in SPIR-V using the OpSourceExtension instruction.", extension_name.c_str());
return skip; // no known extension to validate
}
// Each SPIR-V Extension has one or more requirements to check
// Only one item has to be satisfied and an error only occurs
// when all are not satisfied
auto ext = GetSpirvExtensions().equal_range(extension_name);
bool has_support = false;
for (auto it = ext.first; (it != ext.second) && (has_support == false); ++it) {
if (it->second.version) {
if (api_version >= it->second.version) {
has_support = true;
}
} else if (it->second.feature) {
if (it->second.feature.IsEnabled(enabled_features)) {
has_support = true;
}
} else if (it->second.extension) {
if (IsExtEnabled(device_extensions.*(it->second.extension))) {
has_support = true;
}
}
}
if (has_support == false) {
const char *vuid = pipeline ? "VUID-VkShaderModuleCreateInfo-pCode-08742" : "VUID-VkShaderCreateInfoEXT-pCode-08742";
skip |= LogError(vuid, device, loc,
"SPIR-V Extension %s was declared, but one of the following requirements is required (%s).", extension_name.c_str(), SpvExtensionRequirments(extension_name).c_str());
}
} //spv::OpExtension
return skip;
}
''')
out.append('// NOLINTEND') # Wrap for clang-tidy to ignore
self.write("".join(out))